AmendHub

Download

jcs

/

wallops

/

chatter.c

 

(View History)

jcs   *: Support /monitor and its numerics, do better server reconnecting Latest amendment: 129 on 2024-09-22

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->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 was_state = tab->conn->state;
540
541 irc_process(tab->conn);
542
543 if (tab->conn->state != was_state)
544 redraw = true;
545 }
546
547 if (chatter->need_tab_bar_redraw) {
548 redraw = true;
549 chatter->need_tab_bar_redraw = false;
550 }
551
552 if (redraw) {
553 chatter_draw_tab_bar(chatter);
554 chatter_update_titlebar(chatter);
555 }
556
557 if (chatter->shadow_refcnt != 0) {
558 warn("shadow refcnt %d", chatter->shadow_refcnt);
559 chatter_reveal_shadow(chatter);
560 chatter->shadow_refcnt = 0;
561 }
562 }
563
564 void
565 chatter_update(struct focusable *focusable, EventRecord *event)
566 {
567 struct chatter *chatter = (struct chatter *)(focusable->cookie);
568 struct chatter_tab *tab = chatter->current_tab;
569 Rect r;
570 short what = -1;
571
572 if (event != NULL)
573 what = event->what;
574
575 switch (what) {
576 case -1:
577 case updateEvt:
578 chatter_use_shadow(chatter);
579
580 TextFont(applFont);
581 TextSize(10);
582
583 EraseRect(&chatter->win->portRect);
584
585 if (tab->nick_list) {
586 HLock(tab->nick_list);
587 r = (*(tab->nick_list))->rView;
588 HUnlock(tab->nick_list);
589 LUpdate(chatter->win->visRgn, tab->nick_list);
590 InsetRect(&r, -1, -1);
591 FrameRect(&r);
592 }
593
594 HLock(tab->messages_te);
595 r = (*(tab->messages_te))->viewRect;
596 InsetRect(&r, -1, -1);
597 FrameRect(&r);
598 TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te);
599 HUnlock(tab->messages_te);
600
601 HLock(chatter->input_te);
602 r = (*(chatter->input_te))->viewRect;
603 InsetRect(&r, -4, -1);
604 FrameRect(&r);
605 TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te);
606 HUnlock(chatter->input_te);
607
608 DrawControls(chatter->win);
609
610 chatter_draw_tab_bar(chatter);
611 chatter_reveal_shadow(chatter);
612 break;
613 case activateEvt:
614 if (event->modifiers & activeFlag) {
615 if (tab->nick_list)
616 LActivate(true, tab->nick_list);
617 TEActivate(tab->messages_te);
618 TEActivate(chatter->input_te);
619 } else {
620 if (tab->nick_list)
621 LActivate(false, tab->nick_list);
622 TEDeactivate(tab->messages_te);
623 TEDeactivate(chatter->input_te);
624 }
625 break;
626 }
627 }
628
629 void
630 chatter_draw_tab_bar(struct chatter *chatter)
631 {
632 static char label[64];
633 char *tlabel;
634 Rect r, r2;
635 RgnHandle clip;
636 BitMap cur_bits;
637 short tab_width, tab_offset, n, width;
638 size_t len;
639 static const char no_connection[] = "Disconnected";
640 struct chatter_tab *tab;
641
642 TextFont(geneva);
643 TextSize(9);
644
645 tab_offset = 5;
646 tab_width = (chatter->win->portRect.right -
647 chatter->win->portRect.left - tab_offset - tab_offset) /
648 chatter->ntabs;
649 if (tab_width > MAX_TAB_WIDTH)
650 tab_width = MAX_TAB_WIDTH;
651
652 HLock(chatter->input_te);
653 r.left = 0;
654 r.right = chatter->win->portRect.right - chatter->win->portRect.left;
655 r.bottom = (*(chatter->input_te))->viewRect.top;
656 r.top = r.bottom - TAB_BAR_HEIGHT;
657 HUnlock(chatter->input_te);
658 FillRect(&r, tab_bar_pattern);
659
660 r.left--;
661 r.right++;
662 FrameRect(&r);
663 r.left++;
664 r.right--;
665
666 r.left = tab_offset;
667 r.bottom -= 2;
668 r.right = r.left + tab_width;
669
670 r2.left = r.left + 1;
671 r2.right = r.right - 1;
672 r2.top = r.top;
673 r2.bottom = r2.top + 1;
674
675 GetClip(clip = NewRgn());
676
677 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
678 tab->label_rect = r;
679
680 EraseRect(&r);
681 FrameRect(&r);
682 if (tab == chatter->current_tab) {
683 FrameRect(&r2);
684 EraseRect(&r2);
685 tab->have_activity = false;
686 }
687
688 ClipRect(&r);
689
690 tlabel = label;
691 if (tab->channel && tab->channel->mode[0])
692 len = snprintf(label, sizeof(label), "%s (%s)",
693 tab->channel->name, tab->channel->mode);
694 else if (tab->channel)
695 len = strlcpy(label, tab->channel->name, sizeof(label));
696 else if (tab->query_nick[0])
697 len = strlcpy(label, tab->query_nick, sizeof(label));
698 else if (tab->conn->state >= IRC_STATE_UNREGISTERED)
699 len = strlcpy(label, tab->conn->hostname, sizeof(label));
700 else {
701 tlabel = (char *)&no_connection;
702 len = sizeof(no_connection) - 1;
703 }
704
705 if (tab->have_activity)
706 TextFace(bold | condense);
707 else
708 TextFace(0);
709 width = TextWidth(tlabel, 0, len);
710 if (width > tab_width - 4)
711 width = tab_width - 4;
712 MoveTo(r.left + ((tab_width - width) / 2), r.bottom - 3);
713 DrawText(tlabel, 0, len);
714
715 SetClip(clip);
716
717 r.left += tab_width + 2;
718 r.right += tab_width + 2;
719 r2.left += tab_width + 2;
720 r2.right += tab_width + 2;
721 }
722
723 DisposeRgn(clip);
724
725 TextFont(applFont);
726 TextSize(10);
727 TextFace(0);
728
729 chatter_draw_grow_icon(chatter);
730 }
731
732 void
733 chatter_draw_grow_icon(struct chatter *chatter)
734 {
735 Rect r, *te;
736 RgnHandle tmp;
737 WindowPtr win = chatter->win;
738
739 /*
740 * Our input bar is taller than a scrollbar, so we can't use the
741 * normal DrawGrowIcon or our DrawGrowIconOnly
742 */
743 HLock(*(chatter->input_te));
744 te = &(*(chatter->input_te))->viewRect;
745
746 r = win->portRect;
747 r.top = r.bottom - (te->bottom - te->top + 1);
748 r.left = r.right - SCROLLBAR_WIDTH + 1;
749 r.right += 1;
750 r.bottom += 1;
751 FrameRect(&r);
752
753 r.bottom -= 2;
754 r.right -= 2;
755 r.top = r.bottom - 9;
756 r.left = r.right - 9;
757 FrameRect(&r);
758
759 r.top -= 3;
760 r.left -= 2;
761 r.bottom -= 4;
762 r.right -= 4;
763 EraseRect(&r);
764 FrameRect(&r);
765
766 HUnlock(*(chatter->input_te));
767 }
768
769 void
770 chatter_mouse_down(struct focusable *focusable, EventRecord *event)
771 {
772 struct chatter *chatter = (struct chatter *)(focusable->cookie);
773 struct chatter_tab *tab = chatter->current_tab, *ttab;
774 Point p;
775 Cell selected = { 0 }, now = { 0 }, t = { 0 };
776 ControlHandle control;
777 Rect r;
778 short val, adj, ret, part, n;
779 bool dclick;
780 char nick[member_size(struct irc_user, nick) + 1];
781
782 p = event->where;
783 GlobalToLocal(&p);
784
785 if (tab->nick_list) {
786 HLock(tab->nick_list);
787 r = (*(tab->nick_list))->rView;
788 HUnlock(tab->nick_list);
789 r.right += SCROLLBAR_WIDTH;
790 if (PtInRect(p, &r)) {
791 /* store what is selected now */
792 LGetSelect(true, &selected, tab->nick_list);
793
794 /* possibly highlight a new cell or scroll */
795 dclick = LClick(p, event->modifiers, tab->nick_list);
796
797 LGetSelect(true, &now, tab->nick_list);
798 if (selected.v != now.v)
799 LSetSelect(false, selected, tab->nick_list);
800
801 if (dclick) {
802 /* double-click, query this user (or focus existing) */
803 n = sizeof(nick) - 2;
804 LGetCell(&nick, &n, now, tab->nick_list);
805 nick[n] = '\0';
806 n = 0;
807 if (nick[0] == '@' || nick[0] == '+')
808 n = 1;
809 chatter_add_tab(chatter, NULL, tab->conn, NULL, nick + n);
810 }
811
812 return;
813 }
814 }
815
816 HLock(tab->messages_te);
817 r = (*(tab->messages_te))->viewRect;
818 if (PtInRect(p, &r)) {
819 TEClick(p, ((event->modifiers & shiftKey) != 0), tab->messages_te);
820 HLock(tab->messages_te);
821 if ((*(tab->messages_te))->selStart !=
822 (*(tab->messages_te))->selEnd)
823 TESetSelect(0, 0, chatter->input_te);
824 HUnlock(tab->messages_te);
825 return;
826 }
827 HUnlock(tab->messages_te);
828
829 HLock(chatter->input_te);
830 r = (*(chatter->input_te))->viewRect;
831 if (PtInRect(p, &r)) {
832 TEClick(p, ((event->modifiers & shiftKey) != 0),
833 chatter->input_te);
834 HLock(chatter->input_te);
835 if ((*(chatter->input_te))->selStart !=
836 (*(chatter->input_te))->selEnd)
837 TESetSelect(0, 0, tab->messages_te);
838 HUnlock(chatter->input_te);
839 return;
840 }
841 HUnlock(chatter->input_te);
842
843 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
844 if (PtInRect(p, &ttab->label_rect)) {
845 chatter_focus_tab(chatter, ttab);
846 return;
847 }
848 }
849
850 switch (part = FindControl(p, chatter->win, &control)) {
851 case inUpButton:
852 case inDownButton:
853 case inPageUp:
854 case inPageDown:
855 if (control != tab->messages_scroller)
856 break;
857 SetTrackControlTE(tab->messages_te);
858 TrackControl(control, p, TrackMouseDownInControl);
859 break;
860 case inThumb:
861 val = GetCtlValue(control);
862 if (TrackControl(control, p, 0L) == 0)
863 break;
864 adj = val - GetCtlValue(control);
865 if (adj != 0) {
866 val -= adj;
867 if (control == tab->messages_scroller)
868 TEScroll(0, adj * TEGetHeight(0, 0, tab->messages_te),
869 tab->messages_te);
870 SetCtlValue(control, val);
871 }
872 break;
873 }
874 }
875
876 void
877 chatter_resize(struct focusable *focusable, EventRecord *event)
878 {
879 struct chatter *chatter = (struct chatter *)(focusable->cookie);
880 struct chatter_tab *ttab;
881 RgnHandle savergn;
882 Rect bounds;
883 long newsize, width, height;
884
885 bounds.left = 100;
886 bounds.top = 100;
887 bounds.right = screenBits.bounds.right;
888 bounds.bottom = screenBits.bounds.bottom;
889
890 newsize = GrowWindow(focusable->win, event->where, &bounds);
891
892 height = HiWord(newsize);
893 width = LoWord(newsize);
894 InvalRect(&chatter->win->portRect);
895 SizeWindow(focusable->win, width, height, true);
896 EraseRect(&chatter->win->portRect);
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 chatter_autoscroll(chatter, ttab->messages_te,
913 ttab->messages_scroller, true);
914 }
915
916 /* resume normal drawing */
917 SetClip(savergn);
918 DisposeRgn(savergn);
919
920 /* update the current tab as-is */
921 chatter_use_shadow(chatter);
922 chatter_layout(chatter, false, NULL);
923 HLock(chatter->current_tab->messages_te);
924 TEUpdate(&(*(chatter->current_tab->messages_te))->viewRect,
925 chatter->current_tab->messages_te);
926 HUnlock(chatter->current_tab->messages_te);
927 TEPinScroll(0, SHRT_MAX, chatter->current_tab->messages_te);
928 TEPinScroll(0, -SHRT_MAX, chatter->current_tab->messages_te);
929 chatter_autoscroll(chatter, chatter->current_tab->messages_te,
930 chatter->current_tab->messages_scroller, true);
931 chatter_update(focusable, NULL);
932 chatter_reveal_shadow(chatter);
933
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, *ttab;
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_PREV_TAB_ID: {
969 struct chatter_tab *last_tab = NULL;
970
971 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
972 if (ttab == tab) {
973 if (last_tab != NULL) {
974 chatter_use_shadow(chatter);
975 chatter_focus_tab(chatter, last_tab);
976 chatter_reveal_shadow(chatter);
977 }
978 /* TODO: otherwise wrap around to last tab? */
979 break;
980 }
981 last_tab = ttab;
982 }
983 return true;
984 }
985 case VIEW_MENU_NEXT_TAB_ID:
986 if ((ttab = SLIST_NEXT(tab, list))) {
987 chatter_use_shadow(chatter);
988 chatter_focus_tab(chatter, ttab);
989 chatter_reveal_shadow(chatter);
990 }
991 /* TODO: otherwise wrap around to first tab? */
992 return true;
993 case VIEW_MENU_CLOSE_ID:
994 if (chatter->current_tab->query_nick[0] ||
995 chatter->current_tab->channel)
996 chatter_close_tab(chatter, chatter->current_tab);
997 return true;
998 }
999 }
1000
1001 return false;
1002 }
1003
1004 void
1005 chatter_update_menu(struct focusable *focusable)
1006 {
1007 struct chatter *chatter = (struct chatter *)(focusable->cookie);
1008 struct chatter_tab *tab = chatter->current_tab;
1009
1010 if (!chatter)
1011 return;
1012
1013 HLock(chatter->input_te);
1014 HLock(tab->messages_te);
1015
1016 if (chatter->current_tab == SLIST_FIRST(&chatter->tabs_list))
1017 DisableItem(view_menu, VIEW_MENU_PREV_TAB_ID);
1018 else
1019 EnableItem(view_menu, VIEW_MENU_PREV_TAB_ID);
1020
1021 if (SLIST_NEXT(chatter->current_tab, list))
1022 EnableItem(view_menu, VIEW_MENU_NEXT_TAB_ID);
1023 else
1024 DisableItem(view_menu, VIEW_MENU_NEXT_TAB_ID);
1025
1026 if (chatter->current_tab->query_nick[0] ||
1027 chatter->current_tab->channel)
1028 EnableItem(view_menu, VIEW_MENU_CLOSE_ID);
1029 else
1030 DisableItem(view_menu, VIEW_MENU_CLOSE_ID);
1031
1032 EnableItem(window_menu, WINDOW_MENU_HIDE_ID);
1033
1034 EnableItem(edit_menu, EDIT_MENU_PASTE_ID);
1035
1036 if ((*(chatter->input_te))->selStart != (*(chatter->input_te))->selEnd) {
1037 EnableItem(edit_menu, EDIT_MENU_CUT_ID);
1038 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
1039 goto done;
1040 }
1041
1042 if ((*(tab->messages_te))->selStart != (*(tab->messages_te))->selEnd) {
1043 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
1044 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
1045 goto done;
1046 }
1047
1048 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
1049 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
1050
1051 done:
1052 HUnlock(tab->messages_te);
1053 HUnlock(chatter->input_te);
1054 }
1055
1056 void
1057 chatter_key_down(struct focusable *focusable, EventRecord *event)
1058 {
1059 struct chatter *chatter = (struct chatter *)(focusable->cookie);
1060 struct chatter_tab *tab = chatter->current_tab, *ttab;
1061 TERec *te;
1062 short n;
1063 char k;
1064
1065 k = (event->message & charCodeMask);
1066
1067 if ((event->modifiers & cmdKey) != 0) {
1068 /* cmd+number focuses that tab */
1069 if (k >= '1' && k <= '9') {
1070 n = 0;
1071 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
1072 if (n == (k - '1')) {
1073 chatter_use_shadow(chatter);
1074 chatter_focus_tab(chatter, ttab);
1075 chatter_reveal_shadow(chatter);
1076 break;
1077 }
1078 n++;
1079 }
1080 }
1081 return;
1082 }
1083
1084 if (k == '\t') {
1085 chatter_tab_complete(chatter);
1086 return;
1087 }
1088
1089 if (chatter->tab_comp_input[0] != '\0') {
1090 chatter->tab_comp_input[0] = '\0';
1091 chatter->tab_comp_match[0] = '\0';
1092 }
1093
1094 HLock(chatter->input_te);
1095 te = *(chatter->input_te);
1096
1097 if (k == '\r') {
1098 if (te->teLength == 0) {
1099 HUnlock(chatter->input_te);
1100 return;
1101 }
1102 HLock(te->hText);
1103 memcpy(chatter->input, *(te->hText),
1104 MIN(te->teLength, sizeof(chatter->input) - 1));
1105 HUnlock(te->hText);
1106 chatter->input[MIN(te->teLength, sizeof(chatter->input) - 1)] = '\0';
1107 EraseRect(&te->viewRect);
1108 chatter_set_input(chatter, "", 0);
1109 irc_process_input(tab->conn, tab->channel,
1110 (tab->query_nick[0] ? tab->query_nick : NULL), chatter->input);
1111 } else if (te->teLength < IRC_MAX_MSG_SIZE - 1) {
1112 TEKey(k, chatter->input_te);
1113 TESelView(chatter->input_te);
1114 } else
1115 SysBeep(10);
1116
1117 HUnlock(chatter->input_te);
1118 }
1119
1120 struct chatter_tab *
1121 chatter_find_tab(struct chatter *chatter, struct irc_connection *conn,
1122 char *dest_tab)
1123 {
1124 short n;
1125 struct chatter_tab *tab;
1126
1127 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
1128 if (conn != tab->conn)
1129 continue;
1130
1131 if (dest_tab == NULL || dest_tab[0] == '\0') {
1132 /* connection tab */
1133 if (tab->channel == NULL && tab->query_nick[0] == '\0')
1134 return tab;
1135 } else if (tab->channel) {
1136 if (strcasecmp(tab->channel->name, dest_tab) == 0)
1137 return tab;
1138 } else if (tab->query_nick[0]) {
1139 if (strcasecmp(tab->query_nick, dest_tab) == 0)
1140 return tab;
1141 }
1142 }
1143
1144 return NULL;
1145 }
1146
1147 size_t
1148 chatter_printf(struct chatter *chatter, struct irc_connection *conn,
1149 char *dest_tab, const char *format, ...)
1150 {
1151 static char buf[600], buf_out[600], conv_buf[601];
1152 struct chatter_tab *tab = chatter->current_tab, *ttab;
1153 StScrpRec *scrp_rec;
1154 ScrpSTElement *scrp_ele, *prev_scrp_ele;
1155 RgnHandle savergn;
1156 GrafPtr old_port;
1157 va_list argptr;
1158 size_t len, n, buf_out_len, in_this_style;
1159 time_t now = Time;
1160 short line_height = 0;
1161 bool stop_formatting = false, had_activity;
1162
1163 len = 0;
1164
1165 tab = chatter_find_tab(chatter, conn, dest_tab);
1166 if (tab == NULL) {
1167 tab = SLIST_FIRST(&chatter->tabs_list);
1168 if (tab == NULL)
1169 panic("chatter_printf: no tab for %s", dest_tab);
1170 }
1171
1172 had_activity = tab->have_activity;
1173
1174 HLock(tab->messages_te);
1175 if ((*(tab->messages_te))->teLength > 0) {
1176 buf[0] = '\r';
1177 len++;
1178 }
1179
1180 len += strftime(buf + len, sizeof(buf) - len, "$B[%H:%M]$0 ",
1181 localtime(&now));
1182
1183 va_start(argptr, format);
1184 len += vsnprintf(buf + len, sizeof(buf) - len, format, argptr);
1185 va_end(argptr);
1186
1187 len = utf8_to_macroman_string(buf, len, conv_buf);
1188
1189 if (scrp_rec_h == NULL) {
1190 scrp_rec_h = xNewHandle(4 + (20 * CHATTER_SCRAP_ELEMENTS));
1191 HLock(scrp_rec_h);
1192 memset(*scrp_rec_h, 0, (4 + (20 * CHATTER_SCRAP_ELEMENTS)));
1193 } else
1194 HLock(scrp_rec_h);
1195
1196 line_height = CHATTER_FONT_SIZE + 3;
1197
1198 scrp_rec = (StScrpRec *)(*scrp_rec_h);
1199 scrp_rec->scrpNStyles = 1;
1200 scrp_ele = &scrp_rec->scrpStyleTab[scrp_rec->scrpNStyles - 1];
1201 scrp_ele->scrpStartChar = 0;
1202 scrp_ele->scrpHeight = line_height;
1203 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
1204 scrp_ele->scrpFont = CHATTER_FONT;
1205 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
1206 scrp_ele->scrpFace = 0;
1207
1208 for (n = 0, buf_out_len = 0, in_this_style = 0; n < len; n++) {
1209 if (!stop_formatting && conv_buf[n] == '$') {
1210 if (in_this_style > 0) {
1211 scrp_rec->scrpNStyles++;
1212 if (scrp_rec->scrpNStyles >= CHATTER_SCRAP_ELEMENTS) {
1213 warn("chatter_printf: too many elements");
1214 return;
1215 }
1216 prev_scrp_ele = scrp_ele;
1217 scrp_ele = &scrp_rec->scrpStyleTab[
1218 scrp_rec->scrpNStyles - 1];
1219 /* carry style forward */
1220 memcpy(scrp_ele, prev_scrp_ele, sizeof(ScrpSTElement));
1221 }
1222 scrp_ele->scrpStartChar = buf_out_len;
1223
1224 switch (conv_buf[n + 1]) {
1225 case 'B':
1226 scrp_ele->scrpFace |= bold | condense;
1227 break;
1228 case 'U':
1229 scrp_ele->scrpFace |= underline;
1230 break;
1231 case 's':
1232 scrp_ele->scrpSize--;
1233 break;
1234 case 'S':
1235 scrp_ele->scrpSize++;
1236 break;
1237 case '/':
1238 stop_formatting = true;
1239 /* FALLTHROUGH */
1240 case '0':
1241 scrp_ele->scrpHeight = line_height;
1242 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
1243 scrp_ele->scrpFont = CHATTER_FONT;
1244 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
1245 scrp_ele->scrpFace = 0;
1246 break;
1247 }
1248 n++;
1249 continue;
1250 }
1251 buf_out[buf_out_len++] = conv_buf[n];
1252 in_this_style++;
1253 }
1254
1255 if (!buf_out_len) {
1256 HUnlock(scrp_rec_h);
1257 HUnlock(tab->messages_te);
1258 return 0;
1259 }
1260
1261 /* check for TE overflow */
1262
1263 /* too many lines */
1264 if ((*(tab->messages_te))->nLines >=
1265 (nitems((*(tab->messages_te))->lineStarts) - 10))
1266 goto te_overflow;
1267
1268 /* too many characters */
1269 if ((*(tab->messages_te))->teLength >= (SHRT_MAX - 500))
1270 goto te_overflow;
1271
1272 /* rect of all lines is too tall */
1273 if ((*(tab->messages_te))->nLines * line_height >= (SHRT_MAX - 100))
1274 goto te_overflow;
1275
1276 goto no_overflow;
1277
1278 te_overflow:
1279 savergn = NewRgn();
1280 GetClip(savergn);
1281 /* create an empty clip region so all TE updates are hidden */
1282 ClipRect(&zerorect);
1283
1284 /* select some lines at the start, delete them */
1285 TESetSelect(0, (*(tab->messages_te))->lineStarts[5], tab->messages_te);
1286 TEDelete(tab->messages_te);
1287
1288 /* scroll up, causing a repaint */
1289 TEPinScroll(0, SHRT_MAX, tab->messages_te);
1290
1291 /* then scroll back down to what it looked like before we did anything */
1292 TEPinScroll(0, -SHRT_MAX, tab->messages_te);
1293
1294 /* resume normal drawing */
1295 SetClip(savergn);
1296 DisposeRgn(savergn);
1297
1298 no_overflow:
1299 GetPort(&old_port);
1300 SetPort(chatter->win);
1301
1302 if (chatter->current_tab != tab) {
1303 savergn = NewRgn();
1304 GetClip(savergn);
1305 /* create an empty clip region so all TE updates are hidden */
1306 ClipRect(&zerorect);
1307 }
1308
1309 TESetSelect(SHRT_MAX, SHRT_MAX, tab->messages_te);
1310 TEStylInsert(buf_out, buf_out_len, scrp_rec_h, tab->messages_te);
1311 HUnlock(scrp_rec_h);
1312 HUnlock(tab->messages_te);
1313
1314 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller,
1315 false);
1316
1317 if (chatter->current_tab == tab) {
1318 tab->have_activity = false;
1319 } else {
1320 /* resume normal drawing */
1321 SetClip(savergn);
1322 DisposeRgn(savergn);
1323
1324 if (!had_activity && !tab->ignore_activity) {
1325 tab->have_activity = true;
1326 chatter_draw_tab_bar(chatter);
1327 }
1328 }
1329 SetPort(old_port);
1330
1331 return buf_out_len;
1332 }
1333
1334 void
1335 chatter_autoscroll(struct chatter *chatter, TEHandle te,
1336 ControlHandle scroller, bool force)
1337 {
1338 /* only scroll down if we're already at the last line */
1339 if (force || GetCtlValue(scroller) == GetCtlMax(scroller)) {
1340 TEPinScroll(0, -SHRT_MAX, te);
1341 SetCtlValue(scroller, GetCtlMax(scroller));
1342 }
1343
1344 UpdateScrollbarForTE(chatter->win, scroller, te, false);
1345 }
1346
1347 void
1348 chatter_clear_messages(struct chatter *chatter, struct chatter_tab *tab)
1349 {
1350 RgnHandle savergn;
1351
1352 savergn = NewRgn();
1353 GetClip(savergn);
1354 ClipRect(&zerorect);
1355
1356 TESetText(NULL, 0, tab->messages_te);
1357
1358 /* scroll up, causing a repaint */
1359 TEPinScroll(0, SHRT_MAX, tab->messages_te);
1360
1361 /* then scroll back down to what it looked like before we did anything */
1362 TEPinScroll(0, -SHRT_MAX, tab->messages_te);
1363
1364 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller,
1365 true);
1366
1367 SetClip(savergn);
1368 DisposeRgn(savergn);
1369
1370 if (tab == chatter->current_tab) {
1371 chatter_update(chatter->focusable, NULL);
1372 ValidRect(&chatter->win->portRect);
1373 }
1374 }
1375
1376 void
1377 chatter_close_tab(struct chatter *chatter, struct chatter_tab *tab)
1378 {
1379 struct chatter_tab *ttab = NULL, *next_tab = NULL;
1380 struct irc_connection *conn = NULL;
1381 struct irc_channel *channel = NULL;
1382 short n;
1383
1384 if (!tab)
1385 return;
1386
1387 conn = tab->conn;
1388 if (tab->channel)
1389 channel = tab->channel;
1390
1391 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
1392 if (ttab->conn != tab->conn)
1393 continue;
1394
1395 if (tab != ttab)
1396 next_tab = ttab;
1397 }
1398
1399 chatter_use_shadow(chatter);
1400
1401 if (tab->nick_list)
1402 LDispose(tab->nick_list);
1403 DisposeControl(tab->messages_scroller);
1404 TEDispose(tab->messages_te);
1405 SLIST_REMOVE(&chatter->tabs_list, tab, chatter_tab, list);
1406 chatter->ntabs--;
1407
1408 if (chatter->current_tab == tab)
1409 chatter->current_tab = NULL;
1410
1411 xfree(&tab);
1412
1413 if (channel)
1414 irc_part_channel(conn, channel);
1415
1416 if (next_tab == NULL) {
1417 chatter_draw_tab_bar(chatter);
1418 chatter_update_menu(chatter->focusable);
1419 } else
1420 chatter_focus_tab(chatter, next_tab);
1421
1422 chatter_reveal_shadow(chatter);
1423 }
1424
1425 void
1426 chatter_sync_nick_list(struct chatter *chatter, struct irc_channel *channel)
1427 {
1428 size_t n, i, j, tj, ops, voices;
1429 short ret, cellv;
1430 struct irc_channel_nick *nick = NULL;
1431 struct chatter_tab *tab;
1432
1433 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1434 if (!tab)
1435 return;
1436
1437 LDoDraw(false, tab->nick_list);
1438 LDelRow(0, 0, tab->nick_list);
1439
1440 if (channel) {
1441 cellv = 0;
1442 ops = voices = 0;
1443 nick = &channel->nicks[channel->first_nick];
1444 while (nick) {
1445 if (nick->flags & IRC_NICK_FLAG_OP)
1446 ops++;
1447 else if (nick->flags & IRC_NICK_FLAG_VOICE)
1448 voices++;
1449
1450 chatter_insert_to_nick_list(chatter, channel, nick, cellv);
1451
1452 cellv++;
1453
1454 if (nick->next_nick == -1)
1455 break;
1456 nick = &channel->nicks[nick->next_nick];
1457 }
1458
1459 tab->ignore_activity = true;
1460 chatter_printf(chatter, channel->connection, channel->name,
1461 "$B%s$0: Total of $B%ld$0 nick%s $B(%ld$0 op%s, $B%ld$0 "
1462 "voice%s$B)$0",
1463 channel->name, channel->nnicks,
1464 channel->nnicks == 1 ? "" : "s",
1465 ops, ops == 1 ? "" : "s",
1466 voices, voices == 1 ? "" : "s");
1467 tab->ignore_activity = false;
1468 }
1469
1470 LDoDraw(true, tab->nick_list);
1471
1472 if (tab == chatter->current_tab) {
1473 HLock(tab->nick_list);
1474 InvalRect(&(*(tab->nick_list))->rView);
1475 HUnlock(tab->nick_list);
1476 }
1477 }
1478
1479 void
1480 chatter_insert_to_nick_list(struct chatter *chatter,
1481 struct irc_channel *channel, struct irc_channel_nick *nick, short pos)
1482 {
1483 Cell cell = { 0, 0 };
1484 struct irc_channel_nick tnick;
1485 struct chatter_tab *tab;
1486 short j = 0;
1487
1488 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1489 if (!tab)
1490 return;
1491
1492 if (nick->flags & IRC_NICK_FLAG_OP) {
1493 tnick.nick[0] = '@';
1494 j++;
1495 } else if (nick->flags & IRC_NICK_FLAG_VOICE) {
1496 tnick.nick[0] = '+';
1497 j++;
1498 }
1499
1500 cell.v = pos;
1501 j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j);
1502 LAddRow(1, cell.v, tab->nick_list);
1503 LSetCell(&tnick.nick, j, cell, tab->nick_list);
1504 }
1505
1506 void
1507 chatter_remove_from_nick_list(struct chatter *chatter,
1508 struct irc_channel *channel, struct irc_channel_nick *nick, short pos)
1509 {
1510 struct chatter_tab *tab;
1511
1512 tab = chatter_find_tab(chatter, channel->connection, channel->name);
1513 if (!tab)
1514 return;
1515
1516 if (pos == -1) {
1517 /* TODO: find nick in list */
1518 }
1519
1520 LDelRow(1, pos, tab->nick_list);
1521 }
1522
1523 void
1524 chatter_use_shadow(struct chatter *chatter)
1525 {
1526 if (++chatter->shadow_refcnt != 1)
1527 return;
1528
1529 shadow_cur_bits = thePort->portBits;
1530
1531 SetRect(&chatter->shadow.bounds, 0, 0,
1532 chatter->win->portRect.right - chatter->win->portRect.left,
1533 chatter->win->portRect.bottom - chatter->win->portRect.top);
1534 SetPortBits(&chatter->shadow);
1535 CopyBits(&chatter->win->portBits, &chatter->shadow,
1536 &chatter->shadow.bounds, &chatter->shadow.bounds, srcCopy, nil);
1537 }
1538
1539 void
1540 chatter_reveal_shadow(struct chatter *chatter)
1541 {
1542 if (--chatter->shadow_refcnt != 0)
1543 return;
1544
1545 SetPortBits(&shadow_cur_bits);
1546 CopyBits(&chatter->shadow, &chatter->win->portBits,
1547 &chatter->shadow.bounds, &chatter->shadow.bounds, srcCopy, nil);
1548 ValidRect(&chatter->win->portRect);
1549 }
1550
1551 bool
1552 chatter_tab_complete(struct chatter *chatter)
1553 {
1554 TERec *te;
1555 size_t size;
1556 long n;
1557 struct irc_channel *channel;
1558 struct irc_channel_nick *nick;
1559 char *skip_until = NULL;
1560
1561 if (!chatter->current_tab || !chatter->current_tab->channel)
1562 return false;
1563
1564 if (chatter->tab_comp_input[0] == '\0') {
1565 /* new search, store original input */
1566 HLock(chatter->input_te);
1567 te = *(chatter->input_te);
1568 HLock(te->hText);
1569 size = MIN(te->teLength, sizeof(chatter->tab_comp_input) - 1);
1570 memcpy(chatter->tab_comp_input, *(te->hText), size);
1571 chatter->tab_comp_input[size] = '\0';
1572 } else
1573 size = strlen(chatter->tab_comp_input);
1574
1575 /* if previous match, skip nicks in chain until we see it again */
1576 if (chatter->tab_comp_match[0] != '\0')
1577 skip_until = chatter->tab_comp_match;
1578
1579 channel = chatter->current_tab->channel;
1580 nick = &channel->nicks[channel->first_nick];
1581 while (nick) {
1582 if (strncasecmp(nick->nick, chatter->tab_comp_input, size) != 0)
1583 goto next_nick;
1584
1585 /* but don't let us win */
1586 if (strcasecmp(nick->nick, channel->connection->nick) == 0)
1587 goto next_nick;
1588
1589 /* if we had a last match, skip until we see it again */
1590 if (skip_until) {
1591 if (strcmp(nick->nick, skip_until) == 0)
1592 skip_until = NULL;
1593 goto next_nick;
1594 }
1595
1596 strlcpy(chatter->tab_comp_match, nick->nick,
1597 sizeof(chatter->tab_comp_match));
1598 size = snprintf(chatter->input, sizeof(chatter->input), "%s: ",
1599 nick->nick);
1600 chatter_set_input(chatter, chatter->input, size);
1601 return true;
1602
1603 next_nick:
1604 if (nick->next_nick == -1)
1605 break;
1606 nick = &channel->nicks[nick->next_nick];
1607 }
1608
1609 /* no match (or cycled through all matches), return to original input */
1610 if (chatter->tab_comp_match[0] != '\0') {
1611 chatter_set_input(chatter, chatter->tab_comp_input,
1612 strlen(chatter->tab_comp_input));
1613 chatter->tab_comp_match[0] = chatter->tab_comp_input[0] = '\0';
1614 }
1615
1616 return false;
1617 }