AmendHub

Download

jcs

/

wallops

/

chatter.c

 

(View History)

jcs   chatter: Pass proper arg to HLock/HUnlock, input_te is a Handle Latest amendment: 145 on 2026-01-25

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdio.h>
18 #include <stdarg.h>
19 #include <string.h>
20 #include "chatter.h"
21 #include "irc.h"
22 #include "settings.h"
23 #include "util.h"
24 #include "utf8.h"
25
26 #define NICK_LIST_WIDTH 75
27 #define CHATTER_SCRAP_ELEMENTS 20
28 #define MAX_TAB_WIDTH 120
29 #define TAB_BAR_HEIGHT 15
30
31 static Handle scrp_rec_h = NULL;
32 static Pattern tab_bar_pattern;
33 static Rect zerorect = { 0, 0, 0, 0 };
34 static BitMap shadow_cur_bits;
35
36 void chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds);
37 void chatter_draw_tab_bar(struct chatter *chatter);
38 void chatter_layout_tab(struct chatter *chatter, struct chatter_tab *tab,
39 Rect *win_bounds, bool init);
40 void chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab);
41 void chatter_draw_grow_icon(struct chatter *chatter);
42 void chatter_autoscroll(struct chatter *chatter, TEHandle te,
43 ControlHandle scroller, bool force);
44 void chatter_set_input(struct chatter *chatter, char *input, size_t len);
45 short chatter_wait_type(struct focusable *focusable);
46 void chatter_key_down(struct focusable *focusable, EventRecord *event);
47 void chatter_mouse_down(struct focusable *focusable, EventRecord *event);
48 void chatter_resize(struct focusable *focusable, EventRecord *event);
49 bool chatter_menu(struct focusable *focusable, short menu, short item);
50 void chatter_update_menu(struct focusable *focusable);
51 void chatter_idle(struct focusable *focusable, EventRecord *event);
52 void chatter_update(struct focusable *focusable, EventRecord *event);
53 void chatter_resume(struct focusable *focusable, EventRecord *event);
54 bool chatter_close(struct focusable *focusable);
55 bool chatter_quit(struct focusable *focusable);
56 void chatter_use_shadow(struct chatter *chatter);
57 void chatter_reveal_shadow(struct chatter *chatter);
58 bool chatter_tab_complete(struct chatter *chatter);
59
60 struct chatter *
61 chatter_init(const char *server, const unsigned short port,
62 const char *password, const char *nick, const char *ident,
63 const char *realname, bool hide_motd, const char *channel)
64 {
65 struct focusable *focusable;
66 struct chatter *chatter;
67 struct chatter_tab *tab;
68 char title[64];
69 Rect bounds = { 0 };
70 short padding = 20, width, height;
71
72 GetIndPattern(&tab_bar_pattern, sysPatListID, 23);
73
74 chatter = xmalloczero(sizeof(struct chatter));
75 if (chatter == NULL)
76 return NULL;
77 SLIST_INIT(&chatter->tabs_list);
78
79 focusable = xmalloczero(sizeof(struct focusable));
80 if (focusable == NULL) {
81 xfree(&chatter);
82 return NULL;
83 }
84 chatter->focusable = focusable;
85
86 width = screenBits.bounds.right - screenBits.bounds.left - padding;
87 width = MIN(width, 640);
88 height = screenBits.bounds.bottom - screenBits.bounds.top -
89 padding - (GetMBarHeight() * 2);
90 height = MIN(height, 350);
91 center_in_screen(width, height, true, &bounds);
92
93 width = screenBits.bounds.right - screenBits.bounds.left;
94 height = screenBits.bounds.bottom - screenBits.bounds.top;
95 chatter->shadow.rowBytes = (((width - 1) / 16) + 1) * 2;
96 chatter->shadow.baseAddr =
97 xmalloczero((long)chatter->shadow.rowBytes * height);
98 if (chatter->shadow.baseAddr == NULL) {
99 xfree(&chatter);
100 xfree(&focusable);
101 warn("malloc(%ld) failed",
102 (long)(chatter->shadow.rowBytes * height));
103 return NULL;
104 }
105
106 snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
107 chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false,
108 documentProc, (WindowPtr)-1L, true, 0);
109 if (!chatter->win)
110 panic("Can't create chatter window");
111
112 SetPort(chatter->win);
113 TextFont(applFont);
114 TextSize(CHATTER_FONT_SIZE);
115
116 bounds.right -= bounds.left;
117 bounds.bottom -= bounds.top;
118 bounds.top = bounds.left = 0;
119 chatter_layout(chatter, true, &bounds);
120
121 focusable->win = chatter->win;
122 focusable->cookie = chatter;
123 focusable->wait_type = chatter_wait_type;
124 focusable->idle = chatter_idle;
125 focusable->key_down = chatter_key_down;
126 focusable->mouse_down = chatter_mouse_down;
127 focusable->update = chatter_update;
128 focusable->close = chatter_close;
129 focusable->quit = chatter_quit;
130 focusable->resize = chatter_resize;
131 focusable->menu = chatter_menu;
132 focusable->update_menu = chatter_update_menu;
133 focusable->resume = chatter_resume;
134 focusable_add(focusable);
135
136 chatter_update_menu(focusable);
137 chatter_draw_tab_bar(chatter);
138
139 snprintf(title, sizeof(title), "Disconnected");
140 CtoPstr(title);
141 InsMenuItem(window_menu, title, WINDOW_MENU_N_ID + focusable->id);
142
143 chatter_printf(chatter, NULL, NULL,
144 "$B***$0 Welcome to %s %s",
145 PROGRAM_NAME, get_version(false));
146
147 tab = SLIST_FIRST(&chatter->tabs_list);
148 tab->conn = irc_connect(chatter, server, port, password, nick, ident,
149 realname, hide_motd, channel);
150
151 DrawControls(chatter->win);
152 chatter_update_titlebar(chatter);
153 chatter_draw_tab_bar(chatter);
154
155 ValidRect(&bounds);
156
157 return chatter;
158 }
159
160 short
161 chatter_wait_type(struct focusable *focusable)
162 {
163 struct irc_connection *conn;
164 struct chatter *chatter = (struct chatter *)(focusable->cookie);
165 short n;
166
167 SLIST_FOREACH(conn, &irc_connections_list, list) {
168 if (conn->ibuflen)
169 return WAIT_TYPE_URGENT;
170 }
171
172 if (!focusable->visible)
173 return WAIT_TYPE_BACKGROUND;
174
175 return WAIT_TYPE_FOREGROUND;
176 }
177
178 void
179 chatter_layout(struct chatter *chatter, bool init, Rect *win_bounds)
180 {
181 Rect bounds, inset_bounds;
182 Rect control_bounds = { 0 };
183 struct chatter_tab *tab;
184
185 if (win_bounds == NULL)
186 win_bounds = &chatter->win->portRect;
187
188 /* input */
189 bounds.left = win_bounds->left + 3;
190 bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1 - 3;
191 bounds.top = win_bounds->bottom - SCROLLBAR_WIDTH + 1;
192 bounds.bottom = win_bounds->bottom;
193 inset_bounds = bounds;
194 inset_bounds.top += 1;
195 inset_bounds.bottom -= 1;
196 inset_bounds.right = win_bounds->right * 2;
197 if (init) {
198 chatter->input_te = TENew(&inset_bounds, &bounds);
199 HLock(chatter->input_te);
200 (*(chatter->input_te))->crOnly = -1;
201 HUnlock(chatter->input_te);
202 TEAutoView(true, chatter->input_te);
203 TEActivate(chatter->input_te);
204 } else {
205 HLock(chatter->input_te);
206 (*(chatter->input_te))->viewRect = bounds;
207 (*(chatter->input_te))->destRect = inset_bounds;
208 TECalText(chatter->input_te);
209 HUnlock(chatter->input_te);
210 }
211
212 if (init) {
213 chatter_add_tab(chatter, win_bounds, NULL, NULL, NULL);
214 } else {
215 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
216 chatter_layout_tab(chatter, tab, win_bounds, false);
217 }
218 }
219 }
220
221 struct chatter_tab *
222 chatter_add_tab(struct chatter *chatter, Rect *win_bounds,
223 struct irc_connection *conn, struct irc_channel *channel,
224 char *query_nick)
225 {
226 struct chatter_tab *tab = NULL;
227 Rect bounds, inset_bounds;
228 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
229 Point cell_size = { 0 };
230 Cell cell = { 0, 0 };
231
232 if (channel && query_nick)
233 panic("chatter_add_tab called for both channel and query");
234
235 if (conn)
236 chatter_use_shadow(chatter);
237
238 if (conn && (tab = chatter_find_tab(chatter, conn,
239 channel ? channel->name : query_nick))) {
240 chatter_focus_tab(chatter, tab);
241 chatter_reveal_shadow(chatter);
242 return tab;
243 }
244
245 if (win_bounds == NULL)
246 win_bounds = &chatter->win->portRect;
247
248 tab = xmalloczero(sizeof(struct chatter_tab));
249 if (tab == NULL)
250 panic("Out of memory for new tab");
251 SLIST_APPEND(&chatter->tabs_list, tab, chatter_tab, list);
252 chatter->ntabs++;
253 tab->conn = conn;
254 tab->channel = channel;
255 if (query_nick)
256 strlcpy(tab->query_nick, query_nick, sizeof(tab->query_nick));
257
258 chatter_layout_tab(chatter, tab, win_bounds, true);
259 chatter_focus_tab(chatter, tab);
260
261 if (conn)
262 chatter_reveal_shadow(chatter);
263
264 return tab;
265 }
266
267 void
268 chatter_layout_tab(struct chatter *chatter, struct chatter_tab *tab,
269 Rect *win_bounds, bool init)
270 {
271 Rect bounds, inset_bounds;
272 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
273 Point cell_size = { 0 };
274 Cell cell = { 0, 0 };
275
276 if (win_bounds == NULL)
277 win_bounds = &chatter->win->portRect;
278
279 bounds.bottom = (*(chatter->input_te))->viewRect.top - 15;
280
281 if (tab->channel) {
282 /* nick list */
283 bounds.top = 0;
284 bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1;
285 bounds.left = bounds.right - NICK_LIST_WIDTH;
286 if (init) {
287 tab->nick_list = LNew(&bounds, &data_bounds, cell_size, 0,
288 chatter->win, true, true, false, true);
289 if (!tab->nick_list)
290 panic("Can't create nick list");
291 LAddColumn(1, 0, tab->nick_list);
292 HLock(tab->nick_list);
293 (*(tab->nick_list))->selFlags = lOnlyOne | lNoNilHilite;
294 } else {
295 HLock(tab->nick_list);
296 (*(tab->nick_list))->rView = bounds;
297 LSize(bounds.right - bounds.left, bounds.bottom - bounds.top,
298 tab->nick_list);
299 }
300 }
301
302 /* messages scrollbar */
303 bounds.top = -1;
304 if (tab->channel)
305 bounds.right = (*(tab->nick_list))->rView.left;
306 else
307 bounds.right = win_bounds->right + 1;
308 bounds.left = bounds.right - SCROLLBAR_WIDTH;
309 bounds.bottom += 1;
310 if (init)
311 tab->messages_scroller = NewControl(chatter->win, &bounds,
312 "\p", true, 1, 1, 1, scrollBarProc, 0L);
313 else {
314 HLock(tab->messages_scroller);
315 MoveControl(tab->messages_scroller, bounds.left, bounds.top);
316 SizeControl(tab->messages_scroller, bounds.right - bounds.left,
317 bounds.bottom - bounds.top);
318 }
319
320 /* messages */
321 bounds.right = (*(tab->messages_scroller))->contrlRect.left;
322 bounds.left = 0;
323 bounds.top = 0;
324 bounds.bottom -= 1;
325 inset_bounds = bounds;
326 InsetRect(&inset_bounds, 4, 4);
327 EraseRect(&bounds);
328 if (init) {
329 tab->messages_te = TEStylNew(&inset_bounds, &bounds);
330 (*(tab->messages_te))->caretHook = NullCaretHook;
331 TEActivate(tab->messages_te);
332 chatter_autoscroll(chatter, tab->messages_te,
333 tab->messages_scroller, false);
334 } else {
335 HLock(tab->messages_te);
336 (*(tab->messages_te))->viewRect = bounds;
337 (*(tab->messages_te))->destRect = inset_bounds;
338 TECalText(tab->messages_te);
339 }
340
341 HUnlock(tab->messages_te);
342 HUnlock(tab->messages_scroller);
343 if (tab->channel)
344 HUnlock(tab->nick_list);
345 }
346
347 void
348 chatter_set_input(struct chatter *chatter, char *input, size_t len)
349 {
350 TESetText(input, len, chatter->input_te);
351 TEPinScroll(-SHRT_MAX, SHRT_MAX, chatter->input_te);
352 TEIdle(chatter->input_te);
353 /* this is needed to get TEIdle working again */
354 TESelView(chatter->input_te);
355 }
356
357 void
358 chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab)
359 {
360 RgnHandle clip;
361 Rect r;
362
363 if (chatter->current_tab && chatter->current_tab == tab)
364 return;
365
366 chatter_use_shadow(chatter);
367
368 if (chatter->current_tab) {
369 /*
370 * Doing the HideControl takes out the top line of our tab bar,
371 * so clip to just above it
372 */
373 HLock(chatter->current_tab->messages_scroller);
374 HLock(chatter->current_tab->nick_list);
375 GetClip(clip = NewRgn());
376 r.left = 0;
377 r.top = 0;
378 r.bottom = (*(chatter->current_tab->messages_scroller))->contrlRect.bottom
379 - 1;
380 if (chatter->current_tab->nick_list)
381 r.right = (*(chatter->current_tab->nick_list))->rView.right;
382 else
383 r.right = (*(chatter->current_tab->messages_scroller))->contrlRect.right;
384 ClipRect(&r);
385 HUnlock(chatter->current_tab->nick_list);
386 HUnlock(chatter->current_tab->messages_scroller);
387
388 EraseRect(&r);
389 TEDeactivate(chatter->current_tab->messages_te);
390 if (chatter->current_tab->nick_list) {
391 LActivate(false, chatter->current_tab->nick_list);
392 LDoDraw(false, chatter->current_tab->nick_list);
393 }
394
395 /* HideControl will flash, clip it out */
396 r.right = 0;
397 ClipRect(&r);
398 HideControl(chatter->current_tab->messages_scroller);
399
400 SetClip(clip);
401 DisposeRgn(clip);
402 }
403
404 chatter->current_tab = tab;
405
406 HLock(tab->messages_te);
407 EraseRect(&(*(tab->messages_te))->viewRect);
408 TEActivate(tab->messages_te);
409 TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te);
410 HUnlock(tab->messages_te);
411 ShowControl(tab->messages_scroller);
412 if (tab->nick_list) {
413 HLock(tab->nick_list);
414 EraseRect(&(*(tab->nick_list))->rView);
415 LDoDraw(true, tab->nick_list);
416 LActivate(true, tab->nick_list);
417 LUpdate(chatter->win->visRgn, tab->nick_list);
418 HUnlock(tab->nick_list);
419 }
420
421 HLock(chatter->input_te);
422 EraseRect(&(*(chatter->input_te))->viewRect);
423 TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te);
424 HUnlock(chatter->input_te);
425
426 DrawControls(chatter->win);
427
428 chatter_draw_tab_bar(chatter);
429 chatter_reveal_shadow(chatter);
430
431 if (chatter->focusable)
432 chatter_update_menu(chatter->focusable);
433
434 /* invalidate any current tab completion */
435 chatter->tab_comp_match[0] = chatter->tab_comp_input[0] = '\0';
436 }
437
438 void
439 chatter_resume(struct focusable *focusable, EventRecord *event)
440 {
441 struct chatter *chatter = (struct chatter *)(focusable->cookie);
442
443 focusable_show(focusable);
444 if (chatter->current_tab->nick_list)
445 LActivate(true, chatter->current_tab->nick_list);
446 TEActivate(chatter->current_tab->messages_te);
447 TEActivate(chatter->input_te);
448 InvalRect(&chatter->win->portRect);
449 }
450
451 bool
452 chatter_close(struct focusable *focusable)
453 {
454 struct chatter *chatter = (struct chatter *)(focusable->cookie);
455 struct chatter_tab *tab;
456 struct irc_connection *conn, *tconn;
457 bool connected = false;
458
459 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
460 if (tab->conn && tab->conn->state == IRC_STATE_CONNECTED) {
461 connected = true;
462 break;
463 }
464 }
465
466 if (connected && !chatter->quitting) {
467 focusable_hide(focusable);
468 return false;
469 }
470
471 DelMenuItem(window_menu, WINDOW_MENU_N_ID + focusable->id);
472
473 SLIST_FOREACH_SAFE(conn, &irc_connections_list, list, tconn) {
474 if (conn->chatter == chatter) {
475 irc_close_connection(conn);
476 /* this will kill any channels as well */
477 irc_dealloc_connection(conn);
478 }
479 }
480
481 if (chatter->shadow.baseAddr)
482 xfree(&chatter->shadow.baseAddr);
483
484 DisposeWindow(focusable->win);
485
486 return true;
487 }
488
489 bool
490 chatter_quit(struct focusable *focusable)
491 {
492 struct chatter *chatter = (struct chatter *)(focusable->cookie);
493
494 chatter->quitting = true;
495 focusable_close(focusable);
496
497 return true;
498 }
499
500 void
501 chatter_update_titlebar(struct chatter *chatter)
502 {
503 Str255 curtitle;
504 char title[64], menu_title[64];
505 struct chatter_tab *tab = chatter->current_tab;
506
507 if (!tab->conn || tab->conn->state <= IRC_STATE_DISCONNECTED) {
508 snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
509 strlcpy(menu_title, "Disconnected", sizeof(menu_title));
510 } else {
511 snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME,
512 tab->conn->nick, tab->conn->hostname);
513 snprintf(menu_title, sizeof(menu_title),
514 "%s@%s", tab->conn->nick, tab->conn->hostname);
515 }
516
517 GetWTitle(chatter->win, &curtitle);
518 PtoCstr(curtitle);
519
520 if (strcmp((char *)&curtitle, title) != 0)
521 SetWTitle(chatter->win, CtoPstr(title));
522
523 CtoPstr(menu_title);
524 SetItem(window_menu, WINDOW_MENU_N_ID + chatter->focusable->id,
525 menu_title);
526 }
527
528 void
529 chatter_idle(struct focusable *focusable, EventRecord *event)
530 {
531 struct chatter *chatter = (struct chatter *)(focusable->cookie);
532 struct chatter_tab *tab;
533 short n, was_state;
534 bool redraw = false;
535
536 TEIdle(chatter->input_te);
537
538 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
539 if (!tab->conn)
540 continue;
541
542 was_state = tab->conn->state;
543
544 irc_process(tab->conn);
545
546 if (tab->conn->state != was_state)
547 redraw = true;
548 }
549
550 if (chatter->need_tab_bar_redraw) {
551 redraw = true;
552 chatter->need_tab_bar_redraw = false;
553 }
554
555 if (redraw) {
556 chatter_draw_tab_bar(chatter);
557 chatter_update_titlebar(chatter);
558 }
559
560 if (chatter->shadow_refcnt != 0) {
561 warn("shadow refcnt %d", chatter->shadow_refcnt);
562 chatter_reveal_shadow(chatter);
563 chatter->shadow_refcnt = 0;
564 }
565 }
566
567 void
568 chatter_update(struct focusable *focusable, EventRecord *event)
569 {
570 struct chatter *chatter = (struct chatter *)(focusable->cookie);
571 struct chatter_tab *tab = chatter->current_tab;
572 Rect r;
573 short what = -1;
574
575 if (event != NULL)
576 what = event->what;
577
578 switch (what) {
579 case -1:
580 case updateEvt:
581 chatter_use_shadow(chatter);
582
583 TextFont(applFont);
584 TextSize(10);
585
586 EraseRect(&chatter->win->portRect);
587
588 if (tab->nick_list) {
589 HLock(tab->nick_list);
590 r = (*(tab->nick_list))->rView;
591 HUnlock(tab->nick_list);
592 LUpdate(chatter->win->visRgn, tab->nick_list);
593 InsetRect(&r, -1, -1);
594 FrameRect(&r);
595 }
596
597 HLock(tab->messages_te);
598 r = (*(tab->messages_te))->viewRect;
599 InsetRect(&r, -1, -1);
600 FrameRect(&r);
601 TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te);
602 HUnlock(tab->messages_te);
603
604 HLock(chatter->input_te);
605 r = (*(chatter->input_te))->viewRect;
606 InsetRect(&r, -4, -1);
607 FrameRect(&r);
608 TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te);
609 HUnlock(chatter->input_te);
610
611 DrawControls(chatter->win);
612
613 chatter_draw_tab_bar(chatter);
614 chatter_reveal_shadow(chatter);
615 break;
616 case activateEvt:
617 if (event->modifiers & activeFlag) {
618 if (tab->nick_list)
619 LActivate(true, tab->nick_list);
620 TEActivate(tab->messages_te);
621 TEActivate(chatter->input_te);
622 } else {
623 if (tab->nick_list)
624 LActivate(false, tab->nick_list);
625 TEDeactivate(tab->messages_te);
626 TEDeactivate(chatter->input_te);
627 }
628 break;
629 }
630 }
631
632 void
633 chatter_draw_tab_bar(struct chatter *chatter)
634 {
635 static char label[64];
636 char *tlabel;
637 Rect r, r2;
638 RgnHandle clip;
639 BitMap cur_bits;
640 short tab_width, tab_offset, n, width;
641 size_t len;
642 static const char no_connection[] = "Disconnected";
643 struct chatter_tab *tab;
644
645 TextFont(geneva);
646 TextSize(9);
647
648 tab_offset = 5;
649 tab_width = (chatter->win->portRect.right -
650 chatter->win->portRect.left - tab_offset - tab_offset) /
651 chatter->ntabs;
652 if (tab_width > MAX_TAB_WIDTH)
653 tab_width = MAX_TAB_WIDTH;
654
655 HLock(chatter->input_te);
656 r.left = 0;
657 r.right = chatter->win->portRect.right - chatter->win->portRect.left;
658 r.bottom = (*(chatter->input_te))->viewRect.top;
659 r.top = r.bottom - TAB_BAR_HEIGHT;
660 HUnlock(chatter->input_te);
661 FillRect(&r, tab_bar_pattern);
662
663 r.left--;
664 r.right++;
665 FrameRect(&r);
666 r.left++;
667 r.right--;
668
669 r.left = tab_offset;
670 r.bottom -= 2;
671 r.right = r.left + tab_width;
672
673 r2.left = r.left + 1;
674 r2.right = r.right - 1;
675 r2.top = r.top;
676 r2.bottom = r2.top + 1;
677
678 GetClip(clip = NewRgn());
679
680 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
681 tab->label_rect = r;
682
683 EraseRect(&r);
684 FrameRect(&r);
685 if (tab == chatter->current_tab) {
686 FrameRect(&r2);
687 EraseRect(&r2);
688 tab->have_activity = false;
689 }
690
691 ClipRect(&r);
692
693 tlabel = label;
694 if (tab->channel && tab->channel->mode[0])
695 len = snprintf(label, sizeof(label), "%s (%s)",
696 tab->channel->name, tab->channel->mode);
697 else if (tab->channel)
698 len = strlcpy(label, tab->channel->name, sizeof(label));
699 else if (tab->query_nick[0])
700 len = strlcpy(label, tab->query_nick, sizeof(label));
701 else if (tab->conn && tab->conn->state >= IRC_STATE_UNREGISTERED)
702 len = strlcpy(label, tab->conn->hostname, sizeof(label));
703 else {
704 tlabel = (char *)&no_connection;
705 len = sizeof(no_connection) - 1;
706 }
707
708 if (tab->have_activity)
709 TextFace(bold | condense);
710 else
711 TextFace(0);
712 width = TextWidth(tlabel, 0, len);
713 if (width > tab_width - 4)
714 width = tab_width - 4;
715 MoveTo(r.left + ((tab_width - width) / 2), r.bottom - 3);
716 DrawText(tlabel, 0, len);
717
718 SetClip(clip);
719
720 r.left += tab_width + 2;
721 r.right += tab_width + 2;
722 r2.left += tab_width + 2;
723 r2.right += tab_width + 2;
724 }
725
726 DisposeRgn(clip);
727
728 TextFont(applFont);
729 TextSize(10);
730 TextFace(0);
731
732 chatter_draw_grow_icon(chatter);
733 }
734
735 void
736 chatter_draw_grow_icon(struct chatter *chatter)
737 {
738 Rect r, *te;
739 RgnHandle tmp;
740 WindowPtr win = chatter->win;
741
742 /*
743 * Our input bar is taller than a scrollbar, so we can't use the
744 * normal DrawGrowIcon or our DrawGrowIconOnly
745 */
746 HLock(chatter->input_te);
747 te = &(*(chatter->input_te))->viewRect;
748
749 r = win->portRect;
750 r.top = r.bottom - (te->bottom - te->top + 1);
751 r.left = r.right - SCROLLBAR_WIDTH + 1;
752 r.right += 1;
753 r.bottom += 1;
754 FrameRect(&r);
755
756 r.bottom -= 2;
757 r.right -= 2;
758 r.top = r.bottom - 9;
759 r.left = r.right - 9;
760 FrameRect(&r);
761
762 r.top -= 3;
763 r.left -= 2;
764 r.bottom -= 4;
765 r.right -= 4;
766 EraseRect(&r);
767 FrameRect(&r);
768
769 HUnlock(chatter->input_te);
770 }
771
772 void
773 chatter_mouse_down(struct focusable *focusable, EventRecord *event)
774 {
775 struct chatter *chatter = (struct chatter *)(focusable->cookie);
776 struct chatter_tab *tab = chatter->current_tab, *ttab;
777 Point p;
778 Cell selected = { 0 }, now = { 0 }, t = { 0 };
779 ControlHandle control;
780 Rect r;
781 short val, adj, ret, part, n;
782 bool dclick;
783 char nick[member_size(struct irc_user, nick) + 1];
784
785 p = event->where;
786 GlobalToLocal(&p);
787
788 if (tab->nick_list) {
789 HLock(tab->nick_list);
790 r = (*(tab->nick_list))->rView;
791 HUnlock(tab->nick_list);
792 r.right += SCROLLBAR_WIDTH;
793 if (PtInRect(p, &r)) {
794 /* store what is selected now */
795 LGetSelect(true, &selected, tab->nick_list);
796
797 /* possibly highlight a new cell or scroll */
798 dclick = LClick(p, event->modifiers, tab->nick_list);
799
800 LGetSelect(true, &now, tab->nick_list);
801 if (selected.v != now.v)
802 LSetSelect(false, selected, tab->nick_list);
803
804 if (dclick) {
805 /* double-click, query this user (or focus existing) */
806 n = sizeof(nick) - 2;
807 LGetCell(&nick, &n, now, tab->nick_list);
808 nick[n] = '\0';
809 n = 0;
810 if (nick[0] == '@' || nick[0] == '+')
811 n = 1;
812 chatter_add_tab(chatter, NULL, tab->conn, NULL, nick + n);
813 }
814
815 return;
816 }
817 }
818
819 HLock(tab->messages_te);
820 r = (*(tab->messages_te))->viewRect;
821 if (PtInRect(p, &r)) {
822 TEClick(p, ((event->modifiers & shiftKey) != 0), tab->messages_te);
823 HLock(tab->messages_te);
824 if ((*(tab->messages_te))->selStart !=
825 (*(tab->messages_te))->selEnd)
826 TESetSelect(0, 0, chatter->input_te);
827 HUnlock(tab->messages_te);
828 return;
829 }
830 HUnlock(tab->messages_te);
831
832 HLock(chatter->input_te);
833 r = (*(chatter->input_te))->viewRect;
834 if (PtInRect(p, &r)) {
835 TEClick(p, ((event->modifiers & shiftKey) != 0),
836 chatter->input_te);
837 HLock(chatter->input_te);
838 if ((*(chatter->input_te))->selStart !=
839 (*(chatter->input_te))->selEnd)
840 TESetSelect(0, 0, tab->messages_te);
841 HUnlock(chatter->input_te);
842 return;
843 }
844 HUnlock(chatter->input_te);
845
846 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
847 if (PtInRect(p, &ttab->label_rect)) {
848 chatter_focus_tab(chatter, ttab);
849 return;
850 }
851 }
852
853 switch (part = FindControl(p, chatter->win, &control)) {
854 case inUpButton:
855 case inDownButton:
856 case inPageUp:
857 case inPageDown:
858 if (control != tab->messages_scroller)
859 break;
860 SetTrackControlTE(tab->messages_te);
861 TrackControl(control, p, TrackMouseDownInControl);
862 break;
863 case inThumb:
864 val = GetCtlValue(control);
865 if (TrackControl(control, p, 0L) == 0)
866 break;
867 adj = val - GetCtlValue(control);
868 if (adj != 0) {
869 val -= adj;
870 if (control == tab->messages_scroller)
871 TEScroll(0, adj * TEGetHeight(0, 0, tab->messages_te),
872 tab->messages_te);
873 SetCtlValue(control, val);
874 }
875 break;
876 }
877 }
878
879 void
880 chatter_resize(struct focusable *focusable, EventRecord *event)
881 {
882 struct chatter *chatter = (struct chatter *)(focusable->cookie);
883 struct chatter_tab *ttab;
884 RgnHandle savergn;
885 Rect bounds;
886 long newsize, width, height;
887
888 bounds.left = 100;
889 bounds.top = 100;
890 bounds.right = screenBits.bounds.right;
891 bounds.bottom = screenBits.bounds.bottom;
892
893 newsize = GrowWindow(focusable->win, event->where, &bounds);
894
895 height = HiWord(newsize);
896 width = LoWord(newsize);
897 InvalRect(&chatter->win->portRect);
898 SizeWindow(focusable->win, width, height, true);
899 EraseRect(&chatter->win->portRect);
900
901 /* update each tab that isn't the current one */
902 savergn = NewRgn();
903 GetClip(savergn);
904 /* create an empty clip region so all TE updates are hidden */
905 ClipRect(&zerorect);
906
907 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
908 if (ttab == chatter->current_tab)
909 continue;
910
911 chatter_layout_tab(chatter, ttab, NULL, false);
912 HLock(ttab->messages_te);
913 TEUpdate(&(*(ttab->messages_te))->viewRect, ttab->messages_te);
914 HUnlock(ttab->messages_te);
915 chatter_autoscroll(chatter, ttab->messages_te,
916 ttab->messages_scroller, true);
917 }
918
919 /* resume normal drawing */
920 SetClip(savergn);
921 DisposeRgn(savergn);
922
923 /* update the current tab as-is */
924 chatter_use_shadow(chatter);
925 chatter_layout(chatter, false, NULL);
926 HLock(chatter->current_tab->messages_te);
927 TEUpdate(&(*(chatter->current_tab->messages_te))->viewRect,
928 chatter->current_tab->messages_te);
929 HUnlock(chatter->current_tab->messages_te);
930 TEPinScroll(0, SHRT_MAX, chatter->current_tab->messages_te);
931 TEPinScroll(0, -SHRT_MAX, chatter->current_tab->messages_te);
932 chatter_autoscroll(chatter, chatter->current_tab->messages_te,
933 chatter->current_tab->messages_scroller, true);
934 chatter_update(focusable, NULL);
935 chatter_reveal_shadow(chatter);
936
937 ValidRect(&chatter->win->portRect);
938 }
939
940 bool
941 chatter_menu(struct focusable *focusable, short menu, short item)
942 {
943 struct chatter *chatter = (struct chatter *)(focusable->cookie);
944 struct chatter_tab *tab = chatter->current_tab, *ttab;
945
946 switch (menu) {
947 case EDIT_MENU_ID:
948 switch (item) {
949 case EDIT_MENU_CUT_ID:
950 TECut(chatter->input_te);
951 return true;
952 case EDIT_MENU_COPY_ID:
953 HLock(chatter->input_te);
954 HLock(tab->messages_te);
955 if ((*(chatter->input_te))->selStart !=
956 (*(chatter->input_te))->selEnd)
957 TECopy(chatter->input_te);
958 else if ((*(tab->messages_te))->selStart !=
959 (*(tab->messages_te))->selEnd)
960 TECopy(tab->messages_te);
961 HUnlock(tab->messages_te);
962 HUnlock(chatter->input_te);
963 return true;
964 case EDIT_MENU_PASTE_ID:
965 TEPaste(chatter->input_te);
966 return true;
967 }
968 break;
969 case VIEW_MENU_ID:
970 switch (item) {
971 case VIEW_MENU_PREV_TAB_ID: {
972 struct chatter_tab *last_tab = NULL;
973
974 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
975 if (ttab == tab) {
976 if (last_tab != NULL) {
977 chatter_use_shadow(chatter);
978 chatter_focus_tab(chatter, last_tab);
979 chatter_reveal_shadow(chatter);
980 }
981 /* TODO: otherwise wrap around to last tab? */
982 break;
983 }
984 last_tab = ttab;
985 }
986 return true;
987 }
988 case VIEW_MENU_NEXT_TAB_ID:
989 if ((ttab = SLIST_NEXT(tab, list))) {
990 chatter_use_shadow(chatter);
991 chatter_focus_tab(chatter, ttab);
992 chatter_reveal_shadow(chatter);
993 }
994 /* TODO: otherwise wrap around to first tab? */
995 return true;
996 case VIEW_MENU_CLOSE_ID:
997 if (chatter->current_tab->query_nick[0] ||
998 chatter->current_tab->channel)
999 chatter_close_tab(chatter, chatter->current_tab);
1000 return true;
1001 }
1002 }
1003
1004 return false;
1005 }
1006
1007 void
1008 chatter_update_menu(struct focusable *focusable)
1009 {
1010 struct chatter *chatter = (struct chatter *)(focusable->cookie);
1011 struct chatter_tab *tab = chatter->current_tab;
1012
1013 if (!chatter)
1014 return;
1015
1016 HLock(chatter->input_te);
1017 HLock(tab->messages_te);
1018
1019 if (chatter->current_tab == SLIST_FIRST(&chatter->tabs_list))
1020 DisableItem(view_menu, VIEW_MENU_PREV_TAB_ID);
1021 else
1022 EnableItem(view_menu, VIEW_MENU_PREV_TAB_ID);
1023
1024 if (SLIST_NEXT(chatter->current_tab, list))
1025 EnableItem(view_menu, VIEW_MENU_NEXT_TAB_ID);
1026 else
1027 DisableItem(view_menu, VIEW_MENU_NEXT_TAB_ID);
1028
1029 if (chatter->current_tab->query_nick[0] ||
1030 chatter->current_tab->channel)
1031 EnableItem(view_menu, VIEW_MENU_CLOSE_ID);
1032 else
1033 DisableItem(view_menu, VIEW_MENU_CLOSE_ID);
1034
1035 EnableItem(window_menu, WINDOW_MENU_HIDE_ID);
1036
1037 EnableItem(edit_menu, EDIT_MENU_PASTE_ID);
1038
1039 if ((*(chatter->input_te))->selStart != (*(chatter->input_te))->selEnd) {
1040 EnableItem(edit_menu, EDIT_MENU_CUT_ID);
1041 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
1042 goto done;
1043 }
1044
1045 if ((*(tab->messages_te))->selStart != (*(tab->messages_te))->selEnd) {
1046 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
1047 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
1048 goto done;
1049 }
1050
1051 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
1052 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
1053
1054 done:
1055 HUnlock(tab->messages_te);
1056 HUnlock(chatter->input_te);
1057 }
1058
1059 void
1060 chatter_key_down(struct focusable *focusable, EventRecord *event)
1061 {
1062 struct chatter *chatter = (struct chatter *)(focusable->cookie);
1063 struct chatter_tab *tab = chatter->current_tab, *ttab;
1064 TERec *te;
1065 short n;
1066 char k;
1067
1068 k = (event->message & charCodeMask);
1069
1070 if ((event->modifiers & cmdKey) != 0) {
1071 /* cmd+number focuses that tab */
1072 if (k >= '1' && k <= '9') {
1073 n = 0;
1074 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
1075 if (n == (k - '1')) {
1076 chatter_use_shadow(chatter);
1077 chatter_focus_tab(chatter, ttab);
1078 chatter_reveal_shadow(chatter);
1079 break;
1080 }
1081 n++;
1082 }
1083 }
1084 return;
1085 }
1086
1087 if (k == '\t') {
1088 chatter_tab_complete(chatter);
1089 return;
1090 }
1091
1092 if (chatter->tab_comp_input[0] != '\0') {
1093 chatter->tab_comp_input[0] = '\0';
1094 chatter->tab_comp_match[0] = '\0';
1095 }
1096
1097 HLock(chatter->input_te);
1098 te = *(chatter->input_te);
1099
1100 if (k == '\r') {
1101 if (te->teLength == 0) {
1102 HUnlock(chatter->input_te);
1103 return;
1104 }
1105 HLock(te->hText);
1106 memcpy(chatter->input, *(te->hText),
1107 MIN(te->teLength, sizeof(chatter->input) - 1));
1108 HUnlock(te->hText);
1109 chatter->input[MIN(te->teLength, sizeof(chatter->input) - 1)] = '\0';
1110 EraseRect(&te->viewRect);
1111 chatter_set_input(chatter, "", 0);
1112 irc_process_input(tab->conn, tab->channel,
1113 (tab->query_nick[0] ? tab->query_nick : NULL), chatter->input);
1114 } else if (te->teLength < IRC_MAX_MSG_SIZE - 1) {
1115 TEKey(k, chatter->input_te);
1116 TESelView(chatter->input_te);
1117 } else
1118 SysBeep(10);
1119
1120 HUnlock(chatter->input_te);
1121 }
1122
1123 struct chatter_tab *
1124 chatter_find_tab(struct chatter *chatter, struct irc_connection *conn,
1125 char *dest_tab)
1126 {
1127 short n;
1128 struct chatter_tab *tab;
1129
1130 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
1131 if (conn != tab->conn)
1132 continue;
1133
1134 if (dest_tab == NULL || dest_tab[0] == '\0') {
1135 /* connection tab */
1136 if (tab->channel == NULL && tab->query_nick[0] == '\0')
1137 return tab;
1138 } else if (tab->channel) {
1139 if (strcasecmp(tab->channel->name, dest_tab) == 0)
1140 return tab;
1141 } else if (tab->query_nick[0]) {
1142 if (strcasecmp(tab->query_nick, dest_tab) == 0)
1143 return tab;
1144 }
1145 }
1146
1147 return NULL;
1148 }
1149
1150 size_t
1151 chatter_printf(struct chatter *chatter, struct irc_connection *conn,
1152 char *dest_tab, const char *format, ...)
1153 {
1154 static char buf[600], buf_out[600], conv_buf[601];
1155 struct chatter_tab *tab = chatter->current_tab, *ttab;
1156 StScrpRec *scrp_rec;
1157 ScrpSTElement *scrp_ele, *prev_scrp_ele;
1158 RgnHandle savergn;
1159 GrafPtr old_port;
1160 va_list argptr;
1161 size_t len, n, buf_out_len, in_this_style;
1162 time_t now = Time;
1163 short line_height = 0;
1164 bool stop_formatting = false, had_activity;
1165
1166 len = 0;
1167
1168 tab = chatter_find_tab(chatter, conn, dest_tab);
1169 if (tab == NULL) {
1170 tab = SLIST_FIRST(&chatter->tabs_list);
1171 if (tab == NULL)
1172 panic("chatter_printf: no tab for %s", dest_tab);
1173 }
1174
1175 had_activity = tab->have_activity;
1176
1177 HLock(tab->messages_te);
1178 if ((*(tab->messages_te))->teLength > 0) {
1179 buf[0] = '\r';
1180 len++;
1181 }
1182
1183 len += strftime(buf + len, sizeof(buf) - len, "$B[%H:%M]$0 ",
1184 localtime(&now));
1185
1186 va_start(argptr, format);
1187 len += vsnprintf(buf + len, sizeof(buf) - len, format, argptr);
1188 va_end(argptr);
1189
1190 len = utf8_to_macroman_string(buf, len, conv_buf);
1191
1192 if (scrp_rec_h == NULL) {
1193 scrp_rec_h = xNewHandle(4 + (20 * CHATTER_SCRAP_ELEMENTS));
1194 HLock(scrp_rec_h);
1195 memset(*scrp_rec_h, 0, (4 + (20 * CHATTER_SCRAP_ELEMENTS)));
1196 } else
1197 HLock(scrp_rec_h);
1198
1199 line_height = CHATTER_FONT_SIZE + 3;
1200
1201 scrp_rec = (StScrpRec *)(*scrp_rec_h);
1202 scrp_rec->scrpNStyles = 1;
1203 scrp_ele = &scrp_rec->scrpStyleTab[scrp_rec->scrpNStyles - 1];
1204 scrp_ele->scrpStartChar = 0;
1205 scrp_ele->scrpHeight = line_height;
1206 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
1207 scrp_ele->scrpFont = CHATTER_FONT;
1208 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
1209 scrp_ele->scrpFace = 0;
1210
1211 for (n = 0, buf_out_len = 0, in_this_style = 0; n < len; n++) {
1212 if (!stop_formatting && conv_buf[n] == '$') {
1213 if (in_this_style > 0) {
1214 scrp_rec->scrpNStyles++;
1215 if (scrp_rec->scrpNStyles >= CHATTER_SCRAP_ELEMENTS) {
1216 warn("chatter_printf: too many elements");
1217 return;
1218 }
1219 prev_scrp_ele = scrp_ele;
1220 scrp_ele = &scrp_rec->scrpStyleTab[
1221 scrp_rec->scrpNStyles - 1];
1222 /* carry style forward */
1223 memcpy(scrp_ele, prev_scrp_ele, sizeof(ScrpSTElement));
1224 }
1225 scrp_ele->scrpStartChar = buf_out_len;
1226
1227 switch (conv_buf[n + 1]) {
1228 case 'B':
1229 scrp_ele->scrpFace |= bold | condense;
1230 break;
1231 case 'U':
1232 scrp_ele->scrpFace |= underline;
1233 break;
1234 case 's':
1235 scrp_ele->scrpSize--;
1236 break;
1237 case 'S':
1238 scrp_ele->scrpSize++;
1239 break;
1240 case '/':
1241 stop_formatting = true;
1242 /* FALLTHROUGH */
1243 case '0':
1244 scrp_ele->scrpHeight = line_height;
1245 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
1246 scrp_ele->scrpFont = CHATTER_FONT;
1247 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
1248 scrp_ele->scrpFace = 0;
1249 break;
1250 }
1251 n++;
1252 continue;
1253 }
1254 buf_out[buf_out_len++] = conv_buf[n];
1255 in_this_style++;
1256 }
1257
1258 if (!buf_out_len) {
1259 HUnlock(scrp_rec_h);
1260 HUnlock(tab->messages_te);
1261 return 0;
1262 }
1263
1264 /* check for TE overflow */
1265
1266 /* too many lines */
1267 if ((*(tab->messages_te))->nLines >=
1268 (nitems((*(tab->messages_te))->lineStarts) - 10))
1269 goto te_overflow;
1270
1271 /* too many characters */
1272 if ((*(tab->messages_te))->teLength >= (SHRT_MAX - 500))
1273 goto te_overflow;
1274
1275 /* rect of all lines is too tall */
1276 if ((*(tab->messages_te))->nLines * line_height >= (SHRT_MAX - 100))
1277 goto te_overflow;
1278
1279 goto no_overflow;
1280
1281 te_overflow:
1282 savergn = NewRgn();
1283 GetClip(savergn);
1284 /* create an empty clip region so all TE updates are hidden */
1285 ClipRect(&zerorect);
1286
1287 /* select some lines at the start, delete them */
1288 TESetSelect(0, (*(tab->messages_te))->lineStarts[5], tab->messages_te);
1289 TEDelete(tab->messages_te);
1290
1291 /* scroll up, causing a repaint */
1292 TEPinScroll(0, SHRT_MAX, tab->messages_te);
1293
1294 /* then scroll back down to what it looked like before we did anything */
1295 TEPinScroll(0, -SHRT_MAX, tab->messages_te);
1296
1297 /* resume normal drawing */
1298 SetClip(savergn);
1299 DisposeRgn(savergn);
1300
1301 no_overflow:
1302 GetPort(&old_port);
1303 SetPort(chatter->win);
1304
1305 if (chatter->current_tab != tab) {
1306 savergn = NewRgn();
1307 GetClip(savergn);
1308 /* create an empty clip region so all TE updates are hidden */
1309 ClipRect(&zerorect);
1310 }
1311
1312 TESetSelect(SHRT_MAX, SHRT_MAX, tab->messages_te);
1313 TEStylInsert(buf_out, buf_out_len, scrp_rec_h, tab->messages_te);
1314 HUnlock(scrp_rec_h);
1315 HUnlock(tab->messages_te);
1316
1317 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller,
1318 false);
1319
1320 if (chatter->current_tab == tab) {
1321 tab->have_activity = false;
1322 } else {
1323 /* resume normal drawing */
1324 SetClip(savergn);
1325 DisposeRgn(savergn);
1326
1327 if (!had_activity && !tab->ignore_activity) {
1328 tab->have_activity = true;
1329 chatter_draw_tab_bar(chatter);
1330 }
1331 }
1332 SetPort(old_port);
1333
1334 return buf_out_len;
1335 }
1336
1337 void
1338 chatter_autoscroll(struct chatter *chatter, TEHandle te,
1339 ControlHandle scroller, bool force)
1340 {
1341 /* only scroll down if we're already at the last line */
1342 if (force || GetCtlValue(scroller) == GetCtlMax(scroller)) {
1343 TEPinScroll(0, -SHRT_MAX, te);
1344 SetCtlValue(scroller, GetCtlMax(scroller));
1345 }
1346
1347 UpdateScrollbarForTE(chatter->win, scroller, te, false);
1348 }
1349
1350 void
1351 chatter_clear_messages(struct chatter *chatter, struct chatter_tab *tab)
1352 {
1353 RgnHandle savergn;
1354
1355 savergn = NewRgn();
1356 GetClip(savergn);
1357 ClipRect(&zerorect);
1358
1359 TESetText(NULL, 0, tab->messages_te);
1360
1361 /* scroll up, causing a repaint */
1362 TEPinScroll(0, SHRT_MAX, tab->messages_te);
1363
1364 /* then scroll back down to what it looked like before we did anything */
1365 TEPinScroll(0, -SHRT_MAX, tab->messages_te);
1366
1367 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller,
1368 true);
1369
1370 SetClip(savergn);
1371 DisposeRgn(savergn);
1372
1373 if (tab == chatter->current_tab) {
1374 chatter_update(chatter->focusable, NULL);
1375 ValidRect(&chatter->win->portRect);
1376 }
1377 }
1378
1379 void
1380 chatter_close_tab(struct chatter *chatter, struct chatter_tab *tab)
1381 {
1382 struct chatter_tab *ttab = NULL, *next_tab = NULL;
1383 struct irc_connection *conn = NULL;
1384 struct irc_channel *channel = NULL;
1385 short n;
1386
1387 if (!tab)
1388 return;
1389
1390 conn = tab->conn;
1391 if (tab->channel)
1392 channel = tab->channel;
1393
1394 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
1395 if (ttab->conn != tab->conn)
1396 continue;
1397
1398 if (tab != ttab)
1399 next_tab = ttab;
1400 }
1401
1402 chatter_use_shadow(chatter);
1403
1404 if (tab->nick_list)
1405 LDispose(tab->nick_list);
1406 DisposeControl(tab->messages_scroller);
1407 TEDispose(tab->messages_te);
1408 SLIST_REMOVE(&chatter->tabs_list, tab, chatter_tab, list);
1409 chatter->ntabs--;
1410
1411 if (chatter->current_tab == tab)
1412 chatter->current_tab = NULL;
1413
1414 xfree(&tab);
1415
1416 if (channel)
1417 irc_part_channel(conn, channel);
1418
1419 if (next_tab == NULL) {
1420 chatter_draw_tab_bar(chatter);
1421 chatter_update_menu(chatter->focusable);
1422 } else
1423 chatter_focus_tab(chatter, next_tab);
1424
1425 chatter_reveal_shadow(chatter);
1426 }
1427
1428 void
1429 chatter_sync_nick_list(struct chatter *chatter, struct irc_channel *channel)
1430 {
1431 size_t n, i, j, tj, ops, voices;
1432 short ret, cellv;
1433 struct irc_channel_nick *nick = NULL;
1434 struct chatter_tab *tab;
1435
1436 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1437 if (!tab)
1438 return;
1439
1440 LDoDraw(false, tab->nick_list);
1441 LDelRow(0, 0, tab->nick_list);
1442
1443 if (channel) {
1444 cellv = 0;
1445 ops = voices = 0;
1446 nick = &channel->nicks[channel->first_nick];
1447 while (nick) {
1448 if (nick->flags & IRC_NICK_FLAG_OP)
1449 ops++;
1450 else if (nick->flags & IRC_NICK_FLAG_VOICE)
1451 voices++;
1452
1453 chatter_insert_to_nick_list(chatter, channel, nick, cellv);
1454
1455 cellv++;
1456
1457 if (nick->next_nick == -1)
1458 break;
1459 nick = &channel->nicks[nick->next_nick];
1460 }
1461
1462 tab->ignore_activity = true;
1463 chatter_printf(chatter, channel->connection, channel->name,
1464 "$B%s$0: Total of $B%ld$0 nick%s $B(%ld$0 op%s, $B%ld$0 "
1465 "voice%s$B)$0",
1466 channel->name, channel->nnicks,
1467 channel->nnicks == 1 ? "" : "s",
1468 ops, ops == 1 ? "" : "s",
1469 voices, voices == 1 ? "" : "s");
1470 tab->ignore_activity = false;
1471 }
1472
1473 LDoDraw(true, tab->nick_list);
1474
1475 if (tab == chatter->current_tab) {
1476 HLock(tab->nick_list);
1477 InvalRect(&(*(tab->nick_list))->rView);
1478 HUnlock(tab->nick_list);
1479 }
1480 }
1481
1482 void
1483 chatter_insert_to_nick_list(struct chatter *chatter,
1484 struct irc_channel *channel, struct irc_channel_nick *nick, short pos)
1485 {
1486 Cell cell = { 0, 0 };
1487 struct irc_channel_nick tnick;
1488 struct chatter_tab *tab;
1489 short j = 0;
1490
1491 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1492 if (!tab)
1493 return;
1494
1495 if (nick->flags & IRC_NICK_FLAG_OP) {
1496 tnick.nick[0] = '@';
1497 j++;
1498 } else if (nick->flags & IRC_NICK_FLAG_VOICE) {
1499 tnick.nick[0] = '+';
1500 j++;
1501 }
1502
1503 cell.v = pos;
1504 j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j);
1505 LAddRow(1, cell.v, tab->nick_list);
1506 LSetCell(&tnick.nick, j, cell, tab->nick_list);
1507 }
1508
1509 void
1510 chatter_remove_from_nick_list(struct chatter *chatter,
1511 struct irc_channel *channel, struct irc_channel_nick *nick, short pos)
1512 {
1513 struct chatter_tab *tab;
1514
1515 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1516 if (!tab)
1517 return;
1518
1519 if (pos == -1) {
1520 /* TODO: find nick in list */
1521 }
1522
1523 LDelRow(1, pos, tab->nick_list);
1524 }
1525
1526 void
1527 chatter_use_shadow(struct chatter *chatter)
1528 {
1529 if (++chatter->shadow_refcnt != 1)
1530 return;
1531
1532 shadow_cur_bits = thePort->portBits;
1533
1534 SetRect(&chatter->shadow.bounds, 0, 0,
1535 chatter->win->portRect.right - chatter->win->portRect.left,
1536 chatter->win->portRect.bottom - chatter->win->portRect.top);
1537 SetPortBits(&chatter->shadow);
1538 CopyBits(&chatter->win->portBits, &chatter->shadow,
1539 &chatter->shadow.bounds, &chatter->shadow.bounds, srcCopy, nil);
1540 }
1541
1542 void
1543 chatter_reveal_shadow(struct chatter *chatter)
1544 {
1545 if (--chatter->shadow_refcnt != 0)
1546 return;
1547
1548 SetPortBits(&shadow_cur_bits);
1549 CopyBits(&chatter->shadow, &chatter->win->portBits,
1550 &chatter->shadow.bounds, &chatter->shadow.bounds, srcCopy, nil);
1551 ValidRect(&chatter->win->portRect);
1552 }
1553
1554 bool
1555 chatter_tab_complete(struct chatter *chatter)
1556 {
1557 TERec *te;
1558 size_t size;
1559 long n;
1560 struct irc_channel *channel;
1561 struct irc_channel_nick *nick;
1562 char *skip_until = NULL;
1563
1564 if (!chatter->current_tab || !chatter->current_tab->channel)
1565 return false;
1566
1567 if (chatter->tab_comp_input[0] == '\0') {
1568 /* new search, store original input */
1569 HLock(chatter->input_te);
1570 te = *(chatter->input_te);
1571 HLock(te->hText);
1572 size = MIN(te->teLength, sizeof(chatter->tab_comp_input) - 1);
1573 memcpy(chatter->tab_comp_input, *(te->hText), size);
1574 chatter->tab_comp_input[size] = '\0';
1575 } else
1576 size = strlen(chatter->tab_comp_input);
1577
1578 /* if previous match, skip nicks in chain until we see it again */
1579 if (chatter->tab_comp_match[0] != '\0')
1580 skip_until = chatter->tab_comp_match;
1581
1582 channel = chatter->current_tab->channel;
1583 nick = &channel->nicks[channel->first_nick];
1584 while (nick) {
1585 if (strncasecmp(nick->nick, chatter->tab_comp_input, size) != 0)
1586 goto next_nick;
1587
1588 /* but don't let us win */
1589 if (strcasecmp(nick->nick, channel->connection->nick) == 0)
1590 goto next_nick;
1591
1592 /* if we had a last match, skip until we see it again */
1593 if (skip_until) {
1594 if (strcmp(nick->nick, skip_until) == 0)
1595 skip_until = NULL;
1596 goto next_nick;
1597 }
1598
1599 strlcpy(chatter->tab_comp_match, nick->nick,
1600 sizeof(chatter->tab_comp_match));
1601 size = snprintf(chatter->input, sizeof(chatter->input), "%s: ",
1602 nick->nick);
1603 chatter_set_input(chatter, chatter->input, size);
1604 return true;
1605
1606 next_nick:
1607 if (nick->next_nick == -1)
1608 break;
1609 nick = &channel->nicks[nick->next_nick];
1610 }
1611
1612 /* no match (or cycled through all matches), return to original input */
1613 if (chatter->tab_comp_match[0] != '\0') {
1614 chatter_set_input(chatter, chatter->tab_comp_input,
1615 strlen(chatter->tab_comp_input));
1616 chatter->tab_comp_match[0] = chatter->tab_comp_input[0] = '\0';
1617 }
1618
1619 return false;
1620 }