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 | } |