AmendHub

Download

jcs

/

wallops

/

chatter.c

 

(View History)

jcs   chatter: Tab bar is no longer a GrafPort, rename it Latest amendment: 48 on 2023-01-18

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 "util.h"
23
24 #define NICK_LIST_WIDTH 75
25 #define CHATTER_SCRAP_ELEMENTS 20
26 #define MAX_TAB_WIDTH 100
27 #define TAB_BAR_HEIGHT 15
28
29 static Handle scrp_rec_h = NULL;
30 static Pattern tab_bar_pattern;
31
32 void chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds);
33 void chatter_draw_tab_bar(struct chatter *chatter);
34 void chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab);
35 void chatter_draw_grow_icon(struct chatter *chatter);
36 void chatter_autoscroll(struct chatter *chatter, TEHandle te,
37 ControlHandle scroller);
38 short chatter_wait_type(struct focusable *focusable);
39 void chatter_key_down(struct focusable *focusable, EventRecord *event);
40 void chatter_mouse_down(struct focusable *focusable, EventRecord *event);
41 void chatter_resize(struct focusable *focusable, EventRecord *event);
42 bool chatter_menu(struct focusable *focusable, short menu, short item);
43 void chatter_idle(struct focusable *focusable, EventRecord *event);
44 void chatter_update(struct focusable *focusable, EventRecord *event);
45 void chatter_resume(struct focusable *focusable, EventRecord *event);
46 bool chatter_close(struct focusable *focusable);
47 bool chatter_quit(struct focusable *focusable);
48 struct chatter_tab * chatter_find_tab_for_conn_and_channel(
49 struct chatter *chatter, struct irc_connection *conn,
50 struct irc_channel *channel);
51
52 struct chatter *
53 chatter_init(const char *server, const unsigned short port,
54 const char *password, const char *nick, const char *ident,
55 const char *realname, const char *channel)
56 {
57 struct focusable *focusable;
58 struct chatter *chatter;
59 struct chatter_tab *tab;
60 char title[64];
61 Rect bounds = { 0 };
62 short padding = 20;
63
64 GetIndPattern(&tab_bar_pattern, sysPatListID, 23);
65
66 chatter = xmalloczero(sizeof(struct chatter), "chatter");
67 SLIST_INIT(&chatter->tabs_list);
68
69 bounds.left = padding;
70 bounds.top = screenBits.bounds.top + padding +
71 (GetMBarHeight() * 2) - 1;
72 bounds.right = screenBits.bounds.right - padding - 1;
73 bounds.bottom = screenBits.bounds.bottom - padding - 1;
74
75 snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
76 chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false,
77 documentProc, (WindowPtr)-1L, true, 0);
78 if (!chatter->win)
79 panic("Can't create chatter window");
80
81 SetPort(chatter->win);
82 TextFont(applFont);
83 TextSize(CHATTER_FONT_SIZE);
84
85 bounds.right -= bounds.left;
86 bounds.bottom -= bounds.top;
87 bounds.top = bounds.left = 0;
88 chatter_layout(chatter, true, &bounds);
89
90 focusable = xmalloczero(sizeof(struct focusable), "focusable");
91 focusable->win = chatter->win;
92 focusable->cookie = chatter;
93 focusable->wait_type = chatter_wait_type;
94 focusable->idle = chatter_idle;
95 focusable->key_down = chatter_key_down;
96 focusable->mouse_down = chatter_mouse_down;
97 focusable->update = chatter_update;
98 focusable->close = chatter_close;
99 focusable->quit = chatter_quit;
100 focusable->resize = chatter_resize;
101 focusable->menu = chatter_menu;
102 focusable->resume = chatter_resume;
103 focusable_add(focusable);
104 chatter->focusable = focusable;
105
106 chatter_draw_tab_bar(chatter);
107
108 chatter_printf(chatter, NULL, NULL,
109 "$B***$0 Welcome to %s",
110 PROGRAM_NAME);
111
112 tab = SLIST_FIRST(&chatter->tabs_list);
113 tab->conn = irc_connect(chatter, server, port, password, nick, ident,
114 realname, channel);
115 DrawControls(chatter->win);
116 chatter_update_titlebar(chatter);
117
118 ValidRect(&bounds);
119
120 return chatter;
121 }
122
123 short
124 chatter_wait_type(struct focusable *focusable)
125 {
126 struct irc_connection *conn;
127 struct chatter *chatter = (struct chatter *)(focusable->cookie);
128 short n;
129
130 SLIST_FOREACH(conn, &irc_connections_list, list) {
131 if (conn->ibuflen)
132 return WAIT_TYPE_URGENT;
133 }
134
135 if (!focusable->visible)
136 return WAIT_TYPE_BACKGROUND;
137
138 return WAIT_TYPE_FOREGROUND;
139 }
140
141 void
142 chatter_layout(struct chatter *chatter, bool init, Rect *win_bounds)
143 {
144 Rect bounds, inset_bounds;
145 Rect control_bounds = { 0 };
146 struct chatter_tab *tab;
147
148 if (win_bounds == NULL)
149 win_bounds = &chatter->win->portRect;
150
151 /* input */
152 bounds.left = win_bounds->left;
153 bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1;
154 bounds.top = win_bounds->bottom - SCROLLBAR_WIDTH + 1;
155 bounds.bottom = win_bounds->bottom;
156 if (init) {
157 inset_bounds = bounds;
158 InsetRect(&inset_bounds, 3, 1);
159 inset_bounds.right = win_bounds->right * 2;
160 chatter->input_te = TENew(&inset_bounds, &bounds);
161 (*(chatter->input_te))->crOnly = -1;
162 TEAutoView(true, chatter->input_te);
163 TEActivate(chatter->input_te);
164 } else {
165 (*(chatter->input_te))->viewRect = bounds;
166 InsetRect(&bounds, 3, 1);
167 (*(chatter->input_te))->destRect = bounds;
168 TECalText(chatter->input_te);
169 }
170
171 if (init) {
172 chatter_add_tab(chatter, win_bounds, NULL, NULL);
173 } else {
174 /* TODO: move tabs around */
175 }
176 }
177
178 struct chatter_tab *
179 chatter_add_tab(struct chatter *chatter, Rect *win_bounds,
180 struct irc_connection *conn, struct irc_channel *channel)
181 {
182 struct chatter_tab *tab = NULL;
183 Rect bounds, inset_bounds;
184 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
185 Point cell_size = { 0 };
186 Cell cell = { 0, 0 };
187
188 if (win_bounds == NULL)
189 win_bounds = &chatter->win->portRect;
190
191 tab = xmalloczero(sizeof(struct chatter_tab), "tab");
192 SLIST_APPEND(&chatter->tabs_list, tab, chatter_tab, list);
193 tab->index = chatter->ntabs++;
194 tab->conn = conn;
195 tab->channel = channel;
196
197 bounds.bottom = (*(chatter->input_te))->viewRect.top - 15;
198
199 if (tab->channel) {
200 /* nick list */
201 bounds.top = 0;
202 bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1;
203 bounds.left = bounds.right - NICK_LIST_WIDTH;
204 tab->nick_list = LNew(&bounds, &data_bounds, cell_size, 0,
205 chatter->win, true, true, false, true);
206 if (!tab->nick_list)
207 panic("Can't create nick list");
208 LAddColumn(1, 0, tab->nick_list);
209 (*(tab->nick_list))->selFlags = lOnlyOne | lNoNilHilite;
210 }
211
212 /* messages scrollbar */
213 bounds.top = -1;
214 if (tab->channel)
215 bounds.right = (*(tab->nick_list))->rView.left;
216 else
217 bounds.right = win_bounds->right + 1;
218 bounds.left = bounds.right - SCROLLBAR_WIDTH;
219 bounds.bottom += 1;
220 tab->messages_scroller = NewControl(chatter->win, &bounds, "\p", true,
221 1, 1, 1, scrollBarProc, 0L);
222
223 /* messages */
224 bounds.right = (*(tab->messages_scroller))->contrlRect.left;
225 bounds.left = 0;
226 bounds.top = 0;
227 bounds.bottom -= 1;
228 inset_bounds = bounds;
229 InsetRect(&inset_bounds, 4, 4);
230 tab->messages_te = TEStylNew(&inset_bounds, &bounds);
231 (*(tab->messages_te))->caretHook = NullCaretHook;
232 TEActivate(tab->messages_te);
233 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller);
234
235 chatter_focus_tab(chatter, tab);
236
237 return tab;
238 }
239
240 void
241 chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab)
242 {
243 RgnHandle clip;
244 Rect zerorect = { 0, 0, 0, 0 }, r;
245
246 if (chatter->current_tab) {
247 if (chatter->current_tab == tab)
248 return;
249
250 /*
251 * Doing the HideControl takes out the top line of our tab bar,
252 * so clip to just above it
253 */
254 GetClip(clip = NewRgn());
255 r.left = 0;
256 r.top = 0;
257 r.right =
258 (*(chatter->current_tab->messages_scroller))->contrlRect.right;
259 r.bottom =
260 (*(chatter->current_tab->messages_scroller))->contrlRect.bottom
261 - 1;
262 ClipRect(&r);
263
264 TEDeactivate(chatter->current_tab->messages_te);
265 if (chatter->current_tab->nick_list)
266 LDoDraw(false, chatter->current_tab->nick_list);
267 HideControl(chatter->current_tab->messages_scroller);
268
269 SetClip(clip);
270 }
271
272 chatter->current_tab = tab;
273
274 FillRect(&(*(tab->messages_te))->viewRect, white);
275 TEActivate(tab->messages_te);
276 TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te);
277 ShowControl(tab->messages_scroller);
278 if (tab->nick_list) {
279 FillRect(&(*(tab->nick_list))->rView, white);
280 LDoDraw(true, tab->nick_list);
281 LUpdate(chatter->win->visRgn, tab->nick_list);
282 }
283
284 DrawControls(chatter->win);
285 chatter_draw_tab_bar(chatter);
286 }
287
288 void
289 chatter_resume(struct focusable *focusable, EventRecord *event)
290 {
291 struct chatter *chatter = (struct chatter *)(focusable->cookie);
292
293 focusable_show(focusable);
294 InvalRect(chatter->win->visRgn);
295 }
296
297 bool
298 chatter_close(struct focusable *focusable)
299 {
300 struct chatter *chatter = (struct chatter *)(focusable->cookie);
301 struct chatter_tab *tab;
302 struct irc_connection *conn, *tconn;
303 bool connected = false;
304
305 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
306 if (tab->conn->state == IRC_STATE_CONNECTED) {
307 connected = true;
308 break;
309 }
310 }
311
312 if (connected && !chatter->quitting) {
313 focusable_hide(focusable);
314 return false;
315 }
316
317 SLIST_FOREACH_SAFE(conn, &irc_connections_list, list, tconn) {
318 if (conn->chatter == chatter) {
319 irc_close_connection(conn);
320 /* this will kill any channels as well */
321 irc_dealloc_connection(conn);
322 }
323 }
324
325 if (chatter->tab_bar.baseAddr)
326 xfree(&chatter->tab_bar.baseAddr);
327
328 DisposeWindow(focusable->win);
329
330 return true;
331 }
332
333 bool
334 chatter_quit(struct focusable *focusable)
335 {
336 struct chatter *chatter = (struct chatter *)(focusable->cookie);
337
338 chatter->quitting = true;
339 focusable_close(focusable);
340
341 return true;
342 }
343
344 void
345 chatter_update_titlebar(struct chatter *chatter)
346 {
347 Str255 curtitle;
348 char title[64];
349 struct chatter_tab *tab = chatter->current_tab;
350
351 if (!tab->conn || tab->conn->state <= IRC_STATE_DISCONNECTED)
352 snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
353 else if (tab->conn->state == IRC_STATE_CONNECTING)
354 snprintf(title, sizeof(title), "%s: Connecting to %s",
355 PROGRAM_NAME, tab->conn->hostname);
356 else if (tab->channel)
357 snprintf(title, sizeof(title), "%s: %s@%s: %s", PROGRAM_NAME,
358 tab->conn->nick, tab->conn->hostname, tab->channel->name);
359 else
360 snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME,
361 tab->conn->nick, tab->conn->hostname);
362
363 GetWTitle(chatter->win, &curtitle);
364 PtoCstr(curtitle);
365
366 if (strcmp((char *)&curtitle, title) != 0)
367 SetWTitle(chatter->win, CtoPstr(title));
368 }
369
370 void
371 chatter_idle(struct focusable *focusable, EventRecord *event)
372 {
373 struct chatter *chatter = (struct chatter *)(focusable->cookie);
374 struct chatter_tab *tab;
375 short n;
376
377 TEIdle(chatter->input_te);
378
379 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
380 irc_process(tab->conn);
381 }
382 }
383
384 void
385 chatter_update(struct focusable *focusable, EventRecord *event)
386 {
387 struct chatter *chatter = (struct chatter *)(focusable->cookie);
388 struct chatter_tab *tab = chatter->current_tab;
389 GrafPtr old_port;
390 Rect r;
391 short what = -1;
392
393 GetPort(&old_port);
394
395 if (event != NULL)
396 what = event->what;
397
398 switch (what) {
399 case -1:
400 case updateEvt:
401 TextFont(applFont);
402 TextSize(10);
403
404 EraseRect(&chatter->win->portRect);
405
406 if (tab->nick_list) {
407 r = (*(tab->nick_list))->rView;
408 LUpdate(chatter->win->visRgn, tab->nick_list);
409 InsetRect(&r, -1, -1);
410 FrameRect(&r);
411 }
412
413 r = (*(tab->messages_te))->viewRect;
414 InsetRect(&r, -1, -1);
415 FrameRect(&r);
416 TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te);
417
418 r = (*(chatter->input_te))->viewRect;
419 InsetRect(&r, -1, -1);
420 FrameRect(&r);
421 TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te);
422
423 DrawControls(chatter->win);
424 chatter_draw_tab_bar(chatter);
425 break;
426 case activateEvt:
427 if (event->modifiers & activeFlag) {
428 if (tab->nick_list)
429 LActivate(true, tab->nick_list);
430 TEActivate(tab->messages_te);
431 TEActivate(chatter->input_te);
432 } else {
433 if (tab->nick_list)
434 LActivate(false, tab->nick_list);
435 TEDeactivate(tab->messages_te);
436 TEDeactivate(chatter->input_te);
437 }
438 break;
439 }
440 }
441
442 void
443 chatter_draw_tab_bar(struct chatter *chatter)
444 {
445 Rect r, r2;
446 RgnHandle clip;
447 BitMap cur_bits;
448 short tab_width, tab_offset, n, width;
449 size_t len;
450 static const char no_connection[] = "Disconnected";
451 struct chatter_tab *tab;
452 char *label;
453
454 cur_bits = chatter->win->portBits;
455
456 if (chatter->tab_bar.baseAddr == 0) {
457 width = chatter->win->portRect.right - chatter->win->portRect.left;
458 chatter->tab_bar.rowBytes = (((width - 1) / 16) + 1) * 2;
459 chatter->tab_bar.baseAddr = xmalloczero(
460 chatter->tab_bar.rowBytes * TAB_BAR_HEIGHT, "tab_bar");
461 SetRect(&chatter->tab_bar.bounds, 0, 0, width, TAB_BAR_HEIGHT);
462 }
463
464 SetPortBits(&chatter->tab_bar);
465
466 TextFont(geneva);
467 TextSize(9);
468
469 tab_offset = 5;
470
471 tab_width = (chatter->win->portRect.right -
472 chatter->win->portRect.left - tab_offset - tab_offset) /
473 chatter->ntabs;
474 if (tab_width > MAX_TAB_WIDTH)
475 tab_width = MAX_TAB_WIDTH;
476
477 r.top = 0;
478 r.bottom = TAB_BAR_HEIGHT;
479 r.left = 0;
480 r.right = chatter->win->portRect.right - chatter->win->portRect.left;
481 FillRect(&r, tab_bar_pattern);
482
483 r.left--;
484 r.right++;
485 FrameRect(&r);
486 r.left++;
487 r.right--;
488
489 r.left = tab_offset;
490 r.bottom -= 2;
491 r.right = r.left + tab_width;
492
493 r2.left = r.left + 1;
494 r2.right = r.right - 1;
495 r2.top = r.top;
496 r2.bottom = r2.top + 1;
497
498 GetClip(clip = NewRgn());
499
500 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
501 tab->label_rect = r;
502
503 FillRect(&r, white);
504 FrameRect(&r);
505 if (tab == chatter->current_tab) {
506 FrameRect(&r2);
507 FillRect(&r2, white);
508 tab->have_activity = false;
509 }
510
511 ClipRect(&r);
512
513 if (tab->conn) {
514 if (tab->channel)
515 label = tab->channel->name;
516 else
517 label = tab->conn->hostname;
518 } else
519 label = (char *)&no_connection;
520
521 len = strlen(label);
522 if (tab->have_activity)
523 TextFace(bold | condense);
524 else
525 TextFace(0);
526 width = TextWidth(label, 0, len);
527 if (width > tab_width - 4)
528 width = tab_width - 4;
529 MoveTo(r.left + ((tab_width - width) / 2), r.bottom - 3);
530 DrawText(label, 0, len);
531
532 SetClip(clip);
533
534 r.left += tab_width + 2;
535 r.right += tab_width + 2;
536 r2.left += tab_width + 2;
537 r2.right += tab_width + 2;
538 }
539
540 DisposeRgn(clip);
541
542 TextFont(applFont);
543 TextSize(10);
544 TextFace(0);
545
546 SetPortBits(&cur_bits);
547
548 r = chatter->tab_bar.bounds;
549 r.bottom = (*(chatter->input_te))->viewRect.top;
550 r.top += r.bottom - TAB_BAR_HEIGHT;
551
552 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
553 tab->label_rect.top += r.top;
554 tab->label_rect.bottom += r.top;
555 }
556
557 CopyBits(&chatter->tab_bar, &chatter->win->portBits,
558 &chatter->tab_bar.bounds, &r, srcCopy, nil);
559
560 chatter_draw_grow_icon(chatter);
561 }
562
563 void
564 chatter_draw_grow_icon(struct chatter *chatter)
565 {
566 Rect r, *te;
567 RgnHandle tmp;
568 WindowPtr win = chatter->win;
569
570 /*
571 * Our input bar is taller than a scrollbar, so we can't use the
572 * normal DrawGrowIcon or our DrawGrowIconOnly
573 */
574 HLock(*(chatter->input_te));
575 te = &(*(chatter->input_te))->viewRect;
576
577 r = win->portRect;
578 r.top = r.bottom - (te->bottom - te->top + 1);
579 r.left = r.right - SCROLLBAR_WIDTH + 1;
580 r.right += 1;
581 r.bottom += 1;
582 FrameRect(&r);
583
584 r.bottom -= 2;
585 r.right -= 2;
586 r.top = r.bottom - 9;
587 r.left = r.right - 9;
588 FrameRect(&r);
589
590 r.top -= 3;
591 r.left -= 2;
592 r.bottom -= 4;
593 r.right -= 4;
594 FillRect(&r, white);
595 FrameRect(&r);
596
597 HUnlock(*(chatter->input_te));
598 }
599
600 void
601 chatter_mouse_down(struct focusable *focusable, EventRecord *event)
602 {
603 struct chatter *chatter = (struct chatter *)(focusable->cookie);
604 struct chatter_tab *tab = chatter->current_tab, *ttab;
605 Point p;
606 Cell selected = { 0 }, now = { 0 }, t = { 0 };
607 ControlHandle control;
608 Rect r;
609 short val, adj, ret, part, n;
610
611 p = event->where;
612 GlobalToLocal(&p);
613
614 if (tab->nick_list) {
615 r = (*(tab->nick_list))->rView;
616 r.right += SCROLLBAR_WIDTH;
617 if (PtInRect(p, &r)) {
618 /* store what is selected now */
619 ret = LGetSelect(true, &selected, tab->nick_list);
620
621 /* possibly highlight a new cell */
622 LClick(p, event->modifiers, tab->nick_list);
623
624 if (selected.v != now.v) {
625 LSetSelect(false, selected, tab->nick_list);
626 /* TODO: detect double-click, open query window? */
627 }
628
629 return;
630 }
631 }
632
633 r = (*(tab->messages_te))->viewRect;
634 if (PtInRect(p, &r)) {
635 TEClick(p, ((event->modifiers & shiftKey) != 0), tab->messages_te);
636 HLock(tab->messages_te);
637 if ((*(tab->messages_te))->selStart !=
638 (*(tab->messages_te))->selEnd)
639 TESetSelect(0, 0, chatter->input_te);
640 HUnlock(tab->messages_te);
641 return;
642 }
643
644 r = (*(chatter->input_te))->viewRect;
645 if (PtInRect(p, &r)) {
646 TEClick(p, ((event->modifiers & shiftKey) != 0),
647 chatter->input_te);
648 HLock(chatter->input_te);
649 if ((*(chatter->input_te))->selStart !=
650 (*(chatter->input_te))->selEnd)
651 TESetSelect(0, 0, tab->messages_te);
652 HUnlock(chatter->input_te);
653 return;
654 }
655
656 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
657 if (PtInRect(p, &ttab->label_rect)) {
658 chatter_focus_tab(chatter, ttab);
659 return;
660 }
661 }
662
663 switch (part = FindControl(p, chatter->win, &control)) {
664 case inUpButton:
665 case inDownButton:
666 case inPageUp:
667 case inPageDown:
668 if (control != tab->messages_scroller)
669 break;
670 SetTrackControlTE(tab->messages_te);
671 TrackControl(control, p, TrackMouseDownInControl);
672 break;
673 case inThumb:
674 val = GetCtlValue(control);
675 if (TrackControl(control, p, 0L) == 0)
676 break;
677 adj = val - GetCtlValue(control);
678 if (adj != 0) {
679 val -= adj;
680 if (control == tab->messages_scroller)
681 TEScroll(0, adj * TEGetHeight(0, 0, tab->messages_te),
682 tab->messages_te);
683 SetCtlValue(control, val);
684 }
685 break;
686 }
687 }
688
689 void
690 chatter_resize(struct focusable *focusable, EventRecord *event)
691 {
692 struct chatter *chatter = (struct chatter *)(focusable->cookie);
693 Rect bounds;
694 long newsize, width, height;
695
696 bounds.left = 100;
697 bounds.top = 100;
698 bounds.right = screenBits.bounds.right;
699 bounds.bottom = screenBits.bounds.bottom;
700
701 newsize = GrowWindow(focusable->win, event->where, &bounds);
702
703 height = HiWord(newsize);
704 width = LoWord(newsize);
705 SizeWindow(focusable->win, width, height, true);
706 EraseRect(&chatter->win->portRect);
707
708 /* chatter_draw_tab_bar will recreate this to the new size */
709 if (chatter->tab_bar.baseAddr)
710 xfree(&chatter->tab_bar.baseAddr);
711
712 chatter_layout(chatter, false, NULL);
713 }
714
715 bool
716 chatter_menu(struct focusable *focusable, short menu, short item)
717 {
718 struct chatter *chatter = (struct chatter *)(focusable->cookie);
719 struct chatter_tab *tab = chatter->current_tab;
720
721 switch (menu) {
722 case EDIT_MENU_ID:
723 switch (item) {
724 case EDIT_MENU_CUT_ID:
725 TECut(chatter->input_te);
726 return true;
727 case EDIT_MENU_COPY_ID:
728 HLock(chatter->input_te);
729 HLock(tab->messages_te);
730 if ((*(chatter->input_te))->selStart !=
731 (*(chatter->input_te))->selEnd)
732 TECopy(chatter->input_te);
733 else if ((*(tab->messages_te))->selStart !=
734 (*(tab->messages_te))->selEnd)
735 TECopy(tab->messages_te);
736 HUnlock(tab->messages_te);
737 HUnlock(chatter->input_te);
738 return true;
739 case EDIT_MENU_PASTE_ID:
740 TEPaste(chatter->input_te);
741 return true;
742 }
743 }
744
745 return false;
746 }
747
748 void
749 chatter_key_down(struct focusable *focusable, EventRecord *event)
750 {
751 struct chatter *chatter = (struct chatter *)(focusable->cookie);
752 struct chatter_tab *tab = chatter->current_tab;
753 TERec *te;
754 char *input, k;
755
756 k = (event->message & charCodeMask);
757 if (k == '\r') {
758 HLock(chatter->input_te);
759 te = *(chatter->input_te);
760 HLock(te->hText);
761 (*(te->hText))[te->teLength] = '\0';
762 input = xstrdup(*(te->hText), "input");
763 TESetText(&k, 0, chatter->input_te);
764 EraseRect(&te->viewRect);
765 ValidRect(&te->viewRect);
766 TEIdle(chatter->input_te);
767 HUnlock(te->hText);
768 HUnlock(chatter->input_te);
769 irc_process_input(tab->conn, tab->channel, input);
770 xfree(&input);
771 } else {
772 TEKey(k, chatter->input_te);
773 TESelView(chatter->input_te);
774 }
775 }
776
777 struct chatter_tab *
778 chatter_find_tab_for_conn_and_channel(struct chatter *chatter,
779 struct irc_connection *conn, struct irc_channel *channel)
780 {
781 short n;
782 struct chatter_tab *tab;
783
784 if (conn == NULL)
785 return NULL;
786
787 SLIST_FOREACH(tab, &chatter->tabs_list, list) {
788 if (tab->conn == conn && channel == tab->channel)
789 return tab;
790 }
791
792 return NULL;
793 }
794
795 size_t
796 chatter_printf(struct chatter *chatter, struct irc_connection *conn,
797 struct irc_channel *channel, const char *format, ...)
798 {
799 static char buf[600], buf_out[600];
800 struct chatter_tab *tab = chatter->current_tab, *ttab;
801 StScrpRec *scrp_rec;
802 ScrpSTElement *scrp_ele, *prev_scrp_ele;
803 RgnHandle savergn;
804 Rect zerorect = { 0, 0, 0, 0 };
805 va_list argptr;
806 size_t len, n, buf_out_len, in_this_style;
807 time_t now = Time;
808 short line_height = 0;
809 bool stop_formatting = false, had_activity;
810
811 len = 0;
812
813 if (conn != NULL) {
814 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
815 if (ttab->conn == conn && channel == ttab->channel) {
816 tab = ttab;
817 break;
818 }
819 }
820 }
821
822 if (tab == NULL)
823 panic("no tab");
824
825 had_activity = tab->have_activity;
826
827 if ((*(tab->messages_te))->teLength > 0) {
828 buf[0] = '\r';
829 len++;
830 }
831
832 len += strftime(buf + len, sizeof(buf) - len, "$B[%H:%M]$0 ",
833 localtime(&now));
834
835 va_start(argptr, format);
836 len += vsnprintf(buf + len, sizeof(buf) - len, format, argptr);
837 va_end(argptr);
838
839 if (scrp_rec_h == NULL) {
840 scrp_rec_h = xNewHandle(4 + (20 * CHATTER_SCRAP_ELEMENTS));
841 HLock(scrp_rec_h);
842 memset(*scrp_rec_h, 0, (4 + (20 * CHATTER_SCRAP_ELEMENTS)));
843 } else
844 HLock(scrp_rec_h);
845
846 line_height = CHATTER_FONT_SIZE + 3;
847
848 scrp_rec = (StScrpRec *)(*scrp_rec_h);
849 scrp_rec->scrpNStyles = 1;
850 scrp_ele = &scrp_rec->scrpStyleTab[scrp_rec->scrpNStyles - 1];
851 scrp_ele->scrpStartChar = 0;
852 scrp_ele->scrpHeight = line_height;
853 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
854 scrp_ele->scrpFont = CHATTER_FONT;
855 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
856 scrp_ele->scrpFace = 0;
857
858 for (n = 0, buf_out_len = 0, in_this_style = 0; n < len; n++) {
859 if (!stop_formatting && buf[n] == '$') {
860 if (in_this_style > 0) {
861 scrp_rec->scrpNStyles++;
862 if (scrp_rec->scrpNStyles >= CHATTER_SCRAP_ELEMENTS)
863 panic("chatter_printf: too many elements");
864 prev_scrp_ele = scrp_ele;
865 scrp_ele = &scrp_rec->scrpStyleTab[
866 scrp_rec->scrpNStyles - 1];
867 /* carry style forward */
868 memcpy(scrp_ele, prev_scrp_ele, sizeof(ScrpSTElement));
869 }
870 scrp_ele->scrpStartChar = buf_out_len;
871
872 switch (buf[n + 1]) {
873 case 'B':
874 scrp_ele->scrpFace |= bold | condense;
875 break;
876 case 'U':
877 scrp_ele->scrpFace |= underline;
878 break;
879 case 's':
880 scrp_ele->scrpSize--;
881 break;
882 case 'S':
883 scrp_ele->scrpSize++;
884 break;
885 case '/':
886 stop_formatting = true;
887 /* FALLTHROUGH */
888 case '0':
889 scrp_ele->scrpHeight = line_height;
890 scrp_ele->scrpAscent = CHATTER_FONT_SIZE;
891 scrp_ele->scrpFont = CHATTER_FONT;
892 scrp_ele->scrpSize = CHATTER_FONT_SIZE;
893 scrp_ele->scrpFace = 0;
894 break;
895 }
896 n++;
897 continue;
898 }
899 buf_out[buf_out_len++] = buf[n];
900 in_this_style++;
901 }
902
903 if (!buf_out_len) {
904 HUnlock(scrp_rec_h);
905 return 0;
906 }
907
908 HLock(tab->messages_te);
909
910 /* check for TE overflow */
911
912 /* too many lines */
913 if ((*(tab->messages_te))->nLines >=
914 (nitems((*(tab->messages_te))->lineStarts) - 10))
915 goto te_overflow;
916
917 /* too many characters */
918 if ((*(tab->messages_te))->teLength >= (SHRT_MAX - 500))
919 goto te_overflow;
920
921 /* rect of all lines is too tall */
922 if ((*(tab->messages_te))->nLines * line_height >= (SHRT_MAX - 100))
923 goto te_overflow;
924
925 goto no_overflow;
926
927 te_overflow:
928 savergn = NewRgn();
929 GetClip(savergn);
930 /* create an empty clip region so all TE updates are hidden */
931 ClipRect(&zerorect);
932
933 /* select some lines at the start, delete them */
934 TESetSelect(0, (*(tab->messages_te))->lineStarts[5], tab->messages_te);
935 TEDelete(tab->messages_te);
936
937 /* scroll up, causing a repaint */
938 TEPinScroll(0, INT_MAX, tab->messages_te);
939
940 /* then scroll back down to what it looked like before we did anything */
941 TEPinScroll(0, -INT_MAX, tab->messages_te);
942
943 /* resume normal drawing */
944 SetClip(savergn);
945 DisposeRgn(savergn);
946
947 no_overflow:
948 if (chatter->current_tab != tab) {
949 savergn = NewRgn();
950 GetClip(savergn);
951 /* create an empty clip region so all TE updates are hidden */
952 ClipRect(&zerorect);
953 }
954
955 TESetSelect(SHRT_MAX, SHRT_MAX, tab->messages_te);
956 TEStylInsert(buf_out, buf_out_len, scrp_rec_h, tab->messages_te);
957 HUnlock(scrp_rec_h);
958 HUnlock(tab->messages_te);
959
960 chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller);
961
962 if (chatter->current_tab == tab)
963 tab->have_activity = false;
964 else {
965 /* resume normal drawing */
966 SetClip(savergn);
967 DisposeRgn(savergn);
968
969 if (!had_activity) {
970 tab->have_activity = true;
971 chatter_draw_tab_bar(chatter);
972 }
973 }
974
975 return buf_out_len;
976 }
977
978 void
979 chatter_autoscroll(struct chatter *chatter, TEHandle te,
980 ControlHandle scroller)
981 {
982 TEPinScroll(0, -INT_MAX, te);
983 SetCtlValue(scroller, GetCtlMax(scroller));
984 UpdateScrollbarForTE(chatter->win, scroller, te, false);
985 }
986
987 void
988 chatter_remove_channel(struct chatter *chatter,
989 struct irc_channel *channel)
990 {
991 struct chatter_tab *tab = NULL, *ttab = NULL, *next_tab = NULL;
992 short n;
993
994 SLIST_FOREACH(ttab, &chatter->tabs_list, list) {
995 if (ttab->conn == channel->connection) {
996 if (ttab->channel == channel)
997 tab = ttab;
998 else
999 next_tab = ttab;
1000 }
1001 }
1002
1003 if (tab != NULL) {
1004 LDispose(tab->nick_list);
1005 DisposeControl(tab->messages_scroller);
1006 TEDispose(tab->messages_te);
1007 SLIST_REMOVE(&chatter->tabs_list, tab, chatter_tab, list);
1008
1009 if (chatter->current_tab == tab)
1010 chatter->current_tab = NULL;
1011 }
1012
1013 if (next_tab == NULL)
1014 chatter_draw_tab_bar(chatter);
1015 else
1016 chatter_focus_tab(chatter, next_tab);
1017
1018 /*
1019 * Don't bother updating titlebar because we should be inside
1020 * irc_dealloc_channel which will then call chatter_update_titlebar
1021 */
1022 }
1023
1024 void
1025 chatter_sync_nick_list(struct chatter *chatter, struct irc_channel *channel,
1026 bool just_summary)
1027 {
1028 size_t n, i, j, tj, ops, voices;
1029 short ret, cellv;
1030 struct irc_channel_nick *nick = NULL;
1031 struct chatter_tab *tab;
1032
1033 tab = chatter_find_tab_for_conn_and_channel(chatter,
1034 channel->connection, channel);
1035 if (!tab)
1036 return;
1037
1038 if (!just_summary && tab == chatter->current_tab) {
1039 LDoDraw(false, tab->nick_list);
1040 LDelRow(0, 0, tab->nick_list);
1041 }
1042
1043 if (channel) {
1044 cellv = 0;
1045 ops = voices = 0;
1046 nick = &channel->nicks[channel->first_nick];
1047 while (nick) {
1048 if (nick->flags & IRC_NICK_FLAG_OP)
1049 ops++;
1050 else if (nick->flags & IRC_NICK_FLAG_VOICE)
1051 voices++;
1052
1053 if (!just_summary)
1054 chatter_insert_to_nick_list(chatter, channel, nick, cellv);
1055
1056 cellv++;
1057
1058 if (nick->next_nick == -1)
1059 break;
1060 nick = &channel->nicks[nick->next_nick];
1061 }
1062
1063 if (just_summary)
1064 chatter_printf(chatter, channel->connection, channel,
1065 "$B%s$0: Total of $B%ld$0 nick%s $B(%ld$0 op%s, $B%ld$0 "
1066 "voice%s$B)$0",
1067 channel->name, channel->nnicks,
1068 channel->nnicks == 1 ? "" : "s",
1069 ops, ops == 1 ? "" : "s",
1070 voices, voices == 1 ? "" : "s");
1071 }
1072
1073 if (!just_summary) {
1074 LDoDraw(true, tab->nick_list);
1075 InvalRect(&(*(tab->nick_list))->rView);
1076 }
1077 }
1078
1079 void
1080 chatter_insert_to_nick_list(struct chatter *chatter,
1081 struct irc_channel *channel, struct irc_channel_nick *nick, short pos)
1082 {
1083 Cell cell = { 0, 0 };
1084 struct irc_channel_nick tnick;
1085 struct chatter_tab *tab;
1086 short j = 0;
1087
1088 tab = chatter_find_tab_for_conn_and_channel(chatter,
1089 channel->connection, channel);
1090 if (!tab)
1091 return;
1092
1093 if (nick->flags & IRC_NICK_FLAG_OP) {
1094 tnick.nick[0] = '@';
1095 j++;
1096 } else if (nick->flags & IRC_NICK_FLAG_VOICE) {
1097 tnick.nick[0] = '+';
1098 j++;
1099 }
1100
1101 cell.v = pos;
1102 j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j);
1103 LAddRow(1, cell.v, tab->nick_list);
1104 LSetCell(&tnick.nick, j, cell, tab->nick_list);
1105 }