Download
jcs
/wallops
/chatter.c
(View History)
jcs chatter: Pass window to UpdateScrollbarForTE | Latest amendment: 38 on 2022-09-06 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <stdarg.h> |
19 | #include <string.h> |
20 | #include "chatter.h" |
21 | #include "irc.h" |
22 | #include "util.h" |
23 | |
24 | #define NICK_LIST_WIDTH 75 |
25 | #define CHATTER_SCRAP_ELEMENTS 20 |
26 | |
27 | static Handle scrp_rec_h = NULL; |
28 | |
29 | void chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds); |
30 | short chatter_wait_type(struct focusable *focusable); |
31 | void chatter_key_down(struct focusable *focusable, EventRecord *event); |
32 | void chatter_mouse_down(struct focusable *focusable, EventRecord *event); |
33 | bool chatter_menu(struct focusable *focusable, short menu, short item); |
34 | void chatter_idle(struct focusable *focusable, EventRecord *event); |
35 | void chatter_update(struct focusable *focusable, EventRecord *event); |
36 | void chatter_resume(struct focusable *focusable, EventRecord *event); |
37 | void chatter_close(struct focusable *focusable, EventRecord *event); |
38 | bool chatter_quit(struct focusable *focusable); |
39 | void chatter_atexit(struct focusable *focusable); |
40 | |
41 | void |
42 | chatter_init(const char *server, const unsigned short port, |
43 | const char *password, const char *nick, const char *ident, |
44 | const char *realname, const char *channel) |
45 | { |
46 | struct focusable *focusable; |
47 | struct chatter *chatter; |
48 | char title[64]; |
49 | Rect bounds = { 0 }; |
50 | short padding = 20; |
51 | |
52 | if (_TCPInit() != 0) |
53 | panic("TCPInit failed"); |
54 | |
55 | chatter = xmalloczero(sizeof(struct chatter), "chatter"); |
56 | chatter->irc_state = IRC_STATE_UNINITED; |
57 | chatter->irc_hostname = xstrdup(server, "server"); |
58 | chatter->irc_port = port; |
59 | if (password && password[0]) |
60 | chatter->irc_password = xstrdup(password, "password"); |
61 | chatter->irc_nick = xstrdup(nick, "nick"); |
62 | chatter->irc_ident = xstrdup(ident, "ident"); |
63 | chatter->irc_realname = xstrdup(realname, "realname"); |
64 | if (channel && channel[0]) |
65 | chatter->irc_channel_autojoin = xstrdup(channel, "chan"); |
66 | |
67 | bounds.left = padding; |
68 | bounds.top = screenBits.bounds.top + padding + |
69 | (GetMBarHeight() * 2) - 1; |
70 | bounds.right = screenBits.bounds.right - padding - 1; |
71 | bounds.bottom = screenBits.bounds.bottom - padding - 1; |
72 | |
73 | snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); |
74 | chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false, |
75 | documentProc, (WindowPtr)-1L, true, 0); |
76 | if (!chatter->win) |
77 | panic("Can't create chatter window"); |
78 | |
79 | SetPort(chatter->win); |
80 | TextFont(applFont); |
81 | TextSize(CHATTER_FONT_SIZE); |
82 | |
83 | bounds.right -= bounds.left; |
84 | bounds.bottom -= bounds.top; |
85 | bounds.top = bounds.left = 0; |
86 | chatter_layout(chatter, true, &bounds); |
87 | |
88 | focusable = xmalloczero(sizeof(struct focusable), "focusable"); |
89 | focusable->win = chatter->win; |
90 | focusable->cookie = chatter; |
91 | focusable->wait_type = chatter_wait_type; |
92 | focusable->idle = chatter_idle; |
93 | focusable->key_down = chatter_key_down; |
94 | focusable->mouse_down = chatter_mouse_down; |
95 | focusable->update = chatter_update; |
96 | focusable->close = chatter_close; |
97 | focusable->atexit = chatter_atexit; |
98 | focusable->quit = chatter_quit; |
99 | focusable->menu = chatter_menu; |
100 | focusable->resume = chatter_resume; |
101 | add_focusable(focusable); |
102 | chatter->focusable = focusable; |
103 | } |
104 | |
105 | short |
106 | chatter_wait_type(struct focusable *focusable) |
107 | { |
108 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
109 | |
110 | if (chatter->irc_ibuflen) |
111 | return WAIT_TYPE_URGENT; |
112 | else if (!focusable->visible) |
113 | return WAIT_TYPE_BACKGROUND; |
114 | |
115 | return WAIT_TYPE_FOREGROUND; |
116 | } |
117 | |
118 | void |
119 | chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds) |
120 | { |
121 | Rect bounds, inset_bounds, win_bounds; |
122 | Rect control_bounds = { 0 }; |
123 | Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ |
124 | Point cell_size = { 0 }; |
125 | Cell cell = { 0, 0 }; |
126 | |
127 | if (init) |
128 | win_bounds = *init_bounds; |
129 | else |
130 | win_bounds = chatter->win->portRect; |
131 | |
132 | /* input */ |
133 | bounds.left = win_bounds.left; |
134 | bounds.right = win_bounds.right - 3; |
135 | bounds.top = win_bounds.bottom - SCROLLBAR_WIDTH; |
136 | bounds.bottom = win_bounds.bottom; |
137 | if (init) { |
138 | inset_bounds = bounds; |
139 | InsetRect(&inset_bounds, 3, 1); |
140 | inset_bounds.right = win_bounds.right * 2; |
141 | chatter->input_te = TENew(&inset_bounds, &bounds); |
142 | (*(chatter->input_te))->crOnly = -1; |
143 | TEAutoView(true, chatter->input_te); |
144 | TEActivate(chatter->input_te); |
145 | } else { |
146 | (*(chatter->input_te))->viewRect = bounds; |
147 | InsetRect(&bounds, 3, 1); |
148 | (*(chatter->input_te))->destRect = bounds; |
149 | TECalText(chatter->input_te); |
150 | } |
151 | |
152 | /* nick list */ |
153 | bounds.top = 0; |
154 | bounds.right = win_bounds.right - SCROLLBAR_WIDTH + 1; |
155 | bounds.left = bounds.right - NICK_LIST_WIDTH; |
156 | bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; |
157 | if (init) { |
158 | chatter->nick_list = LNew(&bounds, &data_bounds, cell_size, 0, |
159 | chatter->win, true, true, false, true); |
160 | if (!chatter->nick_list) |
161 | panic("Can't create mailboxes list"); |
162 | LAddColumn(1, 0, chatter->nick_list); |
163 | (*(chatter->nick_list))->selFlags = lOnlyOne | lNoNilHilite; |
164 | } else { |
165 | (*(chatter->nick_list))->rView = bounds; |
166 | LSize(bounds.right - bounds.left, bounds.bottom - bounds.top - 10, |
167 | chatter->nick_list); |
168 | } |
169 | |
170 | /* messages scrollbar */ |
171 | bounds.top = -1; |
172 | bounds.right = (*(chatter->nick_list))->rView.left; |
173 | bounds.left = bounds.right - SCROLLBAR_WIDTH; |
174 | bounds.bottom = (*(chatter->input_te))->viewRect.top - 1; |
175 | if (init) |
176 | chatter->messages_scroller = NewControl(chatter->win, &bounds, |
177 | "\p", true, 1, 1, 1, scrollBarProc, 0L); |
178 | else |
179 | (*(chatter->messages_scroller))->contrlRect = bounds; |
180 | |
181 | /* messages */ |
182 | bounds.right = (*(chatter->messages_scroller))->contrlRect.left; |
183 | bounds.left = 0; |
184 | bounds.top = 0; |
185 | bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; |
186 | if (init) { |
187 | inset_bounds = bounds; |
188 | InsetRect(&inset_bounds, 4, 4); |
189 | chatter->messages_te = TEStylNew(&inset_bounds, &bounds); |
190 | (*(chatter->messages_te))->caretHook = NullCaretHook; |
191 | TEActivate(chatter->messages_te); |
192 | } else { |
193 | (*(chatter->messages_te))->viewRect = bounds; |
194 | InsetRect(&bounds, 4, 4); |
195 | (*(chatter->messages_te))->destRect = bounds; |
196 | TECalText(chatter->messages_te); |
197 | } |
198 | |
199 | InvalRect(chatter->win->visRgn); |
200 | } |
201 | |
202 | void |
203 | chatter_resume(struct focusable *focusable, EventRecord *event) |
204 | { |
205 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
206 | |
207 | show_focusable(focusable); |
208 | InvalRect(chatter->win->visRgn); |
209 | } |
210 | |
211 | void |
212 | chatter_close(struct focusable *focusable, EventRecord *event) |
213 | { |
214 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
215 | |
216 | if (chatter->irc_state < IRC_STATE_CONNECTED) { |
217 | irc_abort(chatter); |
218 | close_focusable(focusable); |
219 | } else |
220 | hide_focusable(focusable); |
221 | } |
222 | |
223 | bool |
224 | chatter_quit(struct focusable *focusable) |
225 | { |
226 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
227 | |
228 | if (chatter->irc_state < IRC_STATE_CONNECTED) |
229 | irc_abort(chatter); |
230 | else |
231 | irc_close(chatter); |
232 | |
233 | close_focusable(focusable); |
234 | return true; |
235 | } |
236 | |
237 | void |
238 | chatter_atexit(struct focusable *focusable) |
239 | { |
240 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
241 | |
242 | irc_abort(chatter); |
243 | } |
244 | |
245 | void |
246 | chatter_update_titlebar(struct chatter *chatter) |
247 | { |
248 | Str255 curtitle; |
249 | char title[64]; |
250 | |
251 | if (chatter->irc_state <= IRC_STATE_DISCONNECTED) |
252 | snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); |
253 | else if (chatter->irc_state == IRC_STATE_CONNECTING) |
254 | snprintf(title, sizeof(title), "%s: Connecting to %s", |
255 | PROGRAM_NAME, chatter->irc_hostname); |
256 | else if (chatter->irc_channel) |
257 | snprintf(title, sizeof(title), "%s: %s@%s: %s", PROGRAM_NAME, |
258 | chatter->irc_nick, chatter->irc_hostname, |
259 | chatter->irc_channel->name); |
260 | else |
261 | snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME, |
262 | chatter->irc_nick, chatter->irc_hostname); |
263 | |
264 | GetWTitle(chatter->win, &curtitle); |
265 | PtoCstr(curtitle); |
266 | |
267 | if (strcmp((char *)&curtitle, title) != 0) |
268 | SetWTitle(chatter->win, CtoPstr(title)); |
269 | } |
270 | |
271 | void |
272 | chatter_idle(struct focusable *focusable, EventRecord *event) |
273 | { |
274 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
275 | |
276 | TEIdle(chatter->input_te); |
277 | irc_process(chatter); |
278 | } |
279 | |
280 | void |
281 | chatter_update(struct focusable *focusable, EventRecord *event) |
282 | { |
283 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
284 | GrafPtr old_port; |
285 | Rect r; |
286 | short what = -1; |
287 | |
288 | GetPort(&old_port); |
289 | |
290 | if (event != NULL) |
291 | what = event->what; |
292 | |
293 | switch (what) { |
294 | case -1: |
295 | case updateEvt: |
296 | TextFont(applFont); |
297 | TextSize(10); |
298 | |
299 | EraseRect(&chatter->win->portRect); |
300 | |
301 | r = (*(chatter->nick_list))->rView; |
302 | EraseRect(&r); |
303 | LUpdate(chatter->win->visRgn, chatter->nick_list); |
304 | InsetRect(&r, -1, -1); |
305 | FrameRect(&r); |
306 | |
307 | r = (*(chatter->messages_te))->viewRect; |
308 | InsetRect(&r, -1, -1); |
309 | FrameRect(&r); |
310 | |
311 | TEUpdate(&(*(chatter->messages_te))->viewRect, |
312 | chatter->messages_te); |
313 | TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te); |
314 | |
315 | DrawControls(chatter->win); |
316 | break; |
317 | case activateEvt: |
318 | if (event->modifiers & activeFlag) { |
319 | LActivate(true, chatter->nick_list); |
320 | TEActivate(chatter->messages_te); |
321 | TEActivate(chatter->input_te); |
322 | } else { |
323 | LActivate(false, chatter->nick_list); |
324 | TEDeactivate(chatter->messages_te); |
325 | TEDeactivate(chatter->input_te); |
326 | } |
327 | break; |
328 | } |
329 | } |
330 | |
331 | void |
332 | chatter_mouse_down(struct focusable *focusable, EventRecord *event) |
333 | { |
334 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
335 | Point p; |
336 | Cell selected = { 0 }, now = { 0 }, t = { 0 }; |
337 | ControlHandle control; |
338 | Rect r; |
339 | int val, adj, page, ret; |
340 | short part; |
341 | |
342 | p = event->where; |
343 | GlobalToLocal(&p); |
344 | |
345 | r = (*(chatter->nick_list))->rView; |
346 | r.right += SCROLLBAR_WIDTH; |
347 | if (PtInRect(p, &r)) { |
348 | /* store what is selected now */ |
349 | ret = LGetSelect(true, &selected, chatter->nick_list); |
350 | |
351 | /* possibly highlight a new cell */ |
352 | LClick(p, event->modifiers, chatter->nick_list); |
353 | |
354 | if (selected.v != now.v) { |
355 | LSetSelect(false, selected, chatter->nick_list); |
356 | /* TODO */ |
357 | } |
358 | |
359 | return; |
360 | } |
361 | |
362 | r = (*(chatter->messages_te))->viewRect; |
363 | if (PtInRect(p, &r)) { |
364 | TEClick(p, ((event->modifiers & shiftKey) != 0), |
365 | chatter->messages_te); |
366 | HLock(chatter->messages_te); |
367 | if ((*(chatter->messages_te))->selStart != |
368 | (*(chatter->messages_te))->selEnd) |
369 | TESetSelect(0, 0, chatter->input_te); |
370 | HUnlock(chatter->messages_te); |
371 | return; |
372 | } |
373 | |
374 | r = (*(chatter->input_te))->viewRect; |
375 | if (PtInRect(p, &r)) { |
376 | TEClick(p, ((event->modifiers & shiftKey) != 0), |
377 | chatter->input_te); |
378 | HLock(chatter->input_te); |
379 | if ((*(chatter->input_te))->selStart != |
380 | (*(chatter->input_te))->selEnd) |
381 | TESetSelect(0, 0, chatter->messages_te); |
382 | HUnlock(chatter->input_te); |
383 | return; |
384 | } |
385 | |
386 | switch (part = FindControl(p, chatter->win, &control)) { |
387 | case inUpButton: |
388 | case inDownButton: |
389 | case inPageUp: |
390 | case inPageDown: |
391 | if (control != chatter->messages_scroller) |
392 | break; |
393 | SetTrackControlTE(chatter->messages_te); |
394 | TrackControl(control, p, TrackMouseDownInControl); |
395 | break; |
396 | case inThumb: |
397 | val = GetCtlValue(control); |
398 | if (TrackControl(control, p, 0L) == 0) |
399 | break; |
400 | adj = val - GetCtlValue(control); |
401 | if (adj != 0) { |
402 | val -= adj; |
403 | if (control == chatter->messages_scroller) |
404 | TEScroll(0, adj * TEGetHeight(0, 0, chatter->messages_te), |
405 | chatter->messages_te); |
406 | SetCtlValue(control, val); |
407 | } |
408 | break; |
409 | } |
410 | } |
411 | |
412 | bool |
413 | chatter_menu(struct focusable *focusable, short menu, short item) |
414 | { |
415 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
416 | |
417 | switch (menu) { |
418 | case EDIT_MENU_ID: |
419 | switch (item) { |
420 | case EDIT_MENU_CUT_ID: |
421 | TECut(chatter->input_te); |
422 | return true; |
423 | case EDIT_MENU_COPY_ID: |
424 | HLock(chatter->input_te); |
425 | HLock(chatter->messages_te); |
426 | if ((*(chatter->input_te))->selStart != |
427 | (*(chatter->input_te))->selEnd) |
428 | TECopy(chatter->input_te); |
429 | else if ((*(chatter->messages_te))->selStart != |
430 | (*(chatter->messages_te))->selEnd) |
431 | TECopy(chatter->messages_te); |
432 | HUnlock(chatter->messages_te); |
433 | HUnlock(chatter->input_te); |
434 | return true; |
435 | case EDIT_MENU_PASTE_ID: |
436 | TEPaste(chatter->input_te); |
437 | return true; |
438 | } |
439 | } |
440 | |
441 | return false; |
442 | } |
443 | |
444 | #if 0 |
445 | void |
446 | chatter_grow(Point p) |
447 | { |
448 | GrafPtr old_port; |
449 | long res; |
450 | Rect r; |
451 | |
452 | GetPort(&old_port); |
453 | |
454 | SetPort(chatter_win); |
455 | SetRect(&r, 80, 80, screenBits.bounds.right, screenBits.bounds.bottom); |
456 | res = GrowWindow(chatter_win, p, &r); |
457 | if (res != 0) { |
458 | SizeWindow(chatter_win, LoWord(res), HiWord(res), false); |
459 | InvalRect(&chatter_win->portRect); |
460 | chatter_layout(); |
461 | } |
462 | |
463 | SetPort(old_port); |
464 | } |
465 | #endif |
466 | |
467 | void |
468 | chatter_key_down(struct focusable *focusable, EventRecord *event) |
469 | { |
470 | struct chatter *chatter = (struct chatter *)(focusable->cookie); |
471 | TERec *te; |
472 | char *input, k; |
473 | |
474 | k = (event->message & charCodeMask); |
475 | if (k == '\r') { |
476 | HLock(chatter->input_te); |
477 | te = *(chatter->input_te); |
478 | HLock(te->hText); |
479 | (*(te->hText))[te->teLength] = '\0'; |
480 | input = xstrdup(*(te->hText), "input"); |
481 | TESetText(&k, 0, chatter->input_te); |
482 | EraseRect(&te->viewRect); |
483 | ValidRect(&te->viewRect); |
484 | TEIdle(chatter->input_te); |
485 | HUnlock(te->hText); |
486 | HUnlock(chatter->input_te); |
487 | irc_process_input(chatter, input); |
488 | xfree(&input); |
489 | } else { |
490 | TEKey(k, chatter->input_te); |
491 | TESelView(chatter->input_te); |
492 | } |
493 | } |
494 | |
495 | size_t |
496 | chatter_printf(struct chatter *chatter, const char *format, ...) |
497 | { |
498 | static char buf[600], buf_out[600]; |
499 | StScrpRec *scrp_rec; |
500 | ScrpSTElement *scrp_ele, *prev_scrp_ele; |
501 | RgnHandle savergn; |
502 | Rect zerorect = { 0, 0, 0, 0 }; |
503 | va_list argptr; |
504 | size_t len, n, buf_out_len, in_this_style; |
505 | time_t now = Time; |
506 | short line_height = 0; |
507 | bool stop_formatting = false; |
508 | |
509 | len = 0; |
510 | |
511 | if ((*(chatter->messages_te))->teLength > 0) { |
512 | buf[0] = '\r'; |
513 | len++; |
514 | } |
515 | |
516 | len += strftime(buf + len, sizeof(buf) - len, "$B[%H:%M]$0 ", |
517 | localtime(&now)); |
518 | |
519 | va_start(argptr, format); |
520 | len += vsnprintf(buf + len, sizeof(buf) - len, format, argptr); |
521 | va_end(argptr); |
522 | |
523 | if (scrp_rec_h == NULL) { |
524 | scrp_rec_h = xNewHandle(4 + (20 * CHATTER_SCRAP_ELEMENTS)); |
525 | HLock(scrp_rec_h); |
526 | memset(*scrp_rec_h, 0, (4 + (20 * CHATTER_SCRAP_ELEMENTS))); |
527 | } else |
528 | HLock(scrp_rec_h); |
529 | |
530 | line_height = CHATTER_FONT_SIZE + 3; |
531 | |
532 | scrp_rec = (StScrpRec *)(*scrp_rec_h); |
533 | scrp_rec->scrpNStyles = 1; |
534 | scrp_ele = &scrp_rec->scrpStyleTab[scrp_rec->scrpNStyles - 1]; |
535 | scrp_ele->scrpStartChar = 0; |
536 | scrp_ele->scrpHeight = line_height; |
537 | scrp_ele->scrpAscent = CHATTER_FONT_SIZE; |
538 | scrp_ele->scrpFont = CHATTER_FONT; |
539 | scrp_ele->scrpSize = CHATTER_FONT_SIZE; |
540 | scrp_ele->scrpFace = 0; |
541 | |
542 | for (n = 0, buf_out_len = 0, in_this_style = 0; n < len; n++) { |
543 | if (!stop_formatting && buf[n] == '$') { |
544 | if (in_this_style > 0) { |
545 | scrp_rec->scrpNStyles++; |
546 | if (scrp_rec->scrpNStyles >= CHATTER_SCRAP_ELEMENTS) |
547 | panic("chatter_printf: too many elements"); |
548 | prev_scrp_ele = scrp_ele; |
549 | scrp_ele = &scrp_rec->scrpStyleTab[ |
550 | scrp_rec->scrpNStyles - 1]; |
551 | /* carry style forward */ |
552 | memcpy(scrp_ele, prev_scrp_ele, sizeof(ScrpSTElement)); |
553 | } |
554 | scrp_ele->scrpStartChar = buf_out_len; |
555 | |
556 | switch (buf[n + 1]) { |
557 | case 'B': |
558 | scrp_ele->scrpFace |= bold | condense; |
559 | break; |
560 | case 'U': |
561 | scrp_ele->scrpFace |= underline; |
562 | break; |
563 | case 's': |
564 | scrp_ele->scrpSize--; |
565 | break; |
566 | case 'S': |
567 | scrp_ele->scrpSize++; |
568 | break; |
569 | case '/': |
570 | stop_formatting = true; |
571 | /* FALLTHROUGH */ |
572 | case '0': |
573 | scrp_ele->scrpHeight = line_height; |
574 | scrp_ele->scrpAscent = CHATTER_FONT_SIZE; |
575 | scrp_ele->scrpFont = CHATTER_FONT; |
576 | scrp_ele->scrpSize = CHATTER_FONT_SIZE; |
577 | scrp_ele->scrpFace = 0; |
578 | break; |
579 | } |
580 | n++; |
581 | continue; |
582 | } |
583 | buf_out[buf_out_len++] = buf[n]; |
584 | in_this_style++; |
585 | } |
586 | |
587 | if (!buf_out_len) { |
588 | HUnlock(scrp_rec_h); |
589 | return 0; |
590 | } |
591 | |
592 | HLock(chatter->messages_te); |
593 | |
594 | /* check for TE overflow */ |
595 | |
596 | /* too many lines */ |
597 | if ((*(chatter->messages_te))->nLines >= |
598 | (nitems((*(chatter->messages_te))->lineStarts) - 10)) |
599 | goto te_overflow; |
600 | |
601 | /* too many characters */ |
602 | if ((*(chatter->messages_te))->teLength >= (SHRT_MAX - 500)) |
603 | goto te_overflow; |
604 | |
605 | /* rect of all lines is too tall */ |
606 | if ((*(chatter->messages_te))->nLines * line_height >= (SHRT_MAX - 100)) |
607 | goto te_overflow; |
608 | |
609 | goto no_overflow; |
610 | |
611 | te_overflow: |
612 | savergn = NewRgn(); |
613 | GetClip(savergn); |
614 | /* create an empty clip region so all TE updates are hidden */ |
615 | ClipRect(&zerorect); |
616 | |
617 | /* select some lines at the start, delete them */ |
618 | TESetSelect(0, (*(chatter->messages_te))->lineStarts[5], |
619 | chatter->messages_te); |
620 | TEDelete(chatter->messages_te); |
621 | |
622 | /* scroll up, causing a repaint */ |
623 | TEPinScroll(0, INT_MAX, chatter->messages_te); |
624 | |
625 | /* then scroll back down to what it looked like before we did anything */ |
626 | TEPinScroll(0, -INT_MAX, chatter->messages_te); |
627 | |
628 | /* resume normal drawing */ |
629 | SetClip(savergn); |
630 | DisposeRgn(savergn); |
631 | |
632 | no_overflow: |
633 | TESetSelect(SHRT_MAX, SHRT_MAX, chatter->messages_te); |
634 | TEStylInsert(buf_out, buf_out_len, scrp_rec_h, chatter->messages_te); |
635 | HUnlock(scrp_rec_h); |
636 | |
637 | TEPinScroll(0, -INT_MAX, chatter->messages_te); |
638 | SetCtlValue(chatter->messages_scroller, |
639 | GetCtlMax(chatter->messages_scroller)); |
640 | UpdateScrollbarForTE(chatter->win, chatter->messages_scroller, |
641 | chatter->messages_te, false); |
642 | HUnlock(chatter->messages_te); |
643 | |
644 | return buf_out_len; |
645 | } |
646 | |
647 | void |
648 | chatter_sync_nick_list(struct chatter *chatter, bool just_summary) |
649 | { |
650 | size_t n, i, j, tj, ops, voices; |
651 | short ret, cellv; |
652 | struct irc_channel_nick *nick = NULL; |
653 | |
654 | if (!just_summary) { |
655 | LDoDraw(false, chatter->nick_list); |
656 | LDelRow(0, 0, chatter->nick_list); |
657 | } |
658 | |
659 | if (chatter->irc_channel) { |
660 | cellv = 0; |
661 | ops = voices = 0; |
662 | nick = &chatter->irc_channel->nicks[ |
663 | chatter->irc_channel->first_nick]; |
664 | while (nick) { |
665 | if (nick->flags & IRC_NICK_FLAG_OP) |
666 | ops++; |
667 | else if (nick->flags & IRC_NICK_FLAG_VOICE) |
668 | voices++; |
669 | |
670 | if (!just_summary) |
671 | chatter_insert_to_nick_list(chatter, nick, cellv); |
672 | |
673 | cellv++; |
674 | |
675 | if (nick->next_nick == -1) |
676 | break; |
677 | nick = &chatter->irc_channel->nicks[nick->next_nick]; |
678 | } |
679 | |
680 | if (just_summary) |
681 | chatter_printf(chatter, "$B%s$0: Total of $B%ld$0 nick%s $B(" |
682 | "%ld$0 op%s, $B%ld$0 voice%s$B)$0", |
683 | chatter->irc_channel->name, chatter->irc_channel->nnicks, |
684 | chatter->irc_channel->nnicks == 1 ? "" : "s", |
685 | ops, ops == 1 ? "" : "s", |
686 | voices, voices == 1 ? "" : "s"); |
687 | } |
688 | |
689 | if (!just_summary) { |
690 | LDoDraw(true, chatter->nick_list); |
691 | InvalRect(&(*(chatter->nick_list))->rView); |
692 | } |
693 | } |
694 | |
695 | void |
696 | chatter_insert_to_nick_list(struct chatter *chatter, |
697 | struct irc_channel_nick *nick, short pos) |
698 | { |
699 | Cell cell = { 0, 0 }; |
700 | struct irc_channel_nick tnick; |
701 | short j = 0; |
702 | |
703 | if (nick->flags & IRC_NICK_FLAG_OP) { |
704 | tnick.nick[0] = '@'; |
705 | j++; |
706 | } else if (nick->flags & IRC_NICK_FLAG_VOICE) { |
707 | tnick.nick[0] = '+'; |
708 | j++; |
709 | } |
710 | |
711 | cell.v = pos; |
712 | j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j); |
713 | LAddRow(1, cell.v, chatter->nick_list); |
714 | LSetCell(&tnick.nick, j, cell, chatter->nick_list); |
715 | } |