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