AmendHub

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 }