AmendHub

Download

jcs

/

wallops

/

chatter.c

 

(View History)

jcs   chatter: Pull conn out of tab during destruction Latest amendment: 89 on 2024-09-12

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