AmendHub

Download

jcs

/

subtext

/

logger.c

 

(View History)

jcs   logger: Fix text selection and copying Latest amendment: 529 on 2023-11-08

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 "focusable.h"
21 #include "logger.h"
22 #include "subtext.h"
23 #include "user.h"
24 #include "util.h"
25
26 struct logger *logger = NULL;
27
28 void logger_layout(Rect *init_bounds);
29 void logger_key_down(struct focusable *focusable, EventRecord *event);
30 void logger_mouse_down(struct focusable *focusable, EventRecord *event);
31 bool logger_menu(struct focusable *focusable, short menu, short item);
32 void logger_resize(struct focusable *focusable, EventRecord *event);
33 void logger_update(struct focusable *focusable, EventRecord *event);
34 void logger_resume(struct focusable *focusable, EventRecord *event);
35 bool logger_quit(struct focusable *focusable);
36 void logger_flush_buffer(bool force);
37
38 void
39 logger_init(void)
40 {
41 struct focusable *focusable;
42 Rect bounds = { 0 };
43 short padding = 5;
44
45 logger = xmalloczero(sizeof(struct logger));
46 if (!logger)
47 panic("Can't allocate logger");
48 logger->buffered_logs = xmalloc(LOGGER_BUFFERED_LOG_SIZE);
49 if (!logger->buffered_logs)
50 panic("Can't allocate logger buffer");
51 logger->buffered_logs[0] = '\0';
52 logger->autoflush = true;
53
54 bounds.left = padding;
55 bounds.top = ((screenBits.bounds.bottom -
56 screenBits.bounds.top) / 3);
57 bounds.right = screenBits.bounds.right - padding - 1;
58 bounds.bottom = screenBits.bounds.bottom - padding - 1;
59
60 logger->win = NewWindow(0L, &bounds, "\p", false,
61 documentProc, (WindowPtr)-1L, false, 0);
62 if (!logger->win)
63 panic("Can't create logger window");
64 logger_update_title();
65
66 SetPort(logger->win);
67
68 TextFont(LOGGER_FONT);
69 TextSize(LOGGER_FONT_SIZE);
70
71 bounds.right -= bounds.left;
72 bounds.bottom -= bounds.top;
73 bounds.top = bounds.left = 0;
74 logger_layout(&bounds);
75
76 focusable = xmalloczero(sizeof(struct focusable));
77 if (!focusable)
78 panic("Can't create focusable");
79 focusable->win = logger->win;
80 focusable->cookie = logger;
81 focusable->mouse_down = logger_mouse_down;
82 focusable->menu = logger_menu;
83 focusable->resize = logger_resize;
84 focusable->update = logger_update;
85 focusable->quit = logger_quit;
86 focusable->resume = logger_resume;
87 if (!add_focusable(focusable))
88 panic("Can't add focusable");
89 logger->focusable = focusable;
90
91 DrawControls(logger->win);
92 DrawGrowIconOnly(logger->win);
93 }
94
95 void
96 logger_layout(Rect *init_bounds)
97 {
98 Rect bounds, inset_bounds, win_bounds;
99 bool init = (init_bounds != NULL);
100
101 if (init)
102 win_bounds = *init_bounds;
103 else
104 win_bounds = logger->win->portRect;
105
106 /* messages scrollbar */
107 bounds.top = -1;
108 bounds.right = win_bounds.right - win_bounds.left + 1;
109 bounds.bottom = win_bounds.bottom - win_bounds.top - 14;
110 bounds.left = bounds.right - SCROLLBAR_WIDTH;
111 if (init) {
112 logger->messages_scroller = NewControl(logger->win, &bounds,
113 "\p", true, 1, 1, 1, scrollBarProc, 0L);
114 if (!logger->messages_scroller)
115 panic("Can't create messages scroller");
116 } else
117 (*(logger->messages_scroller))->contrlRect = bounds;
118
119 /* messages */
120 bounds.right = (*(logger->messages_scroller))->contrlRect.left;
121 bounds.left = 0;
122 bounds.top = 0;
123 bounds.bottom = win_bounds.bottom - win_bounds.top;
124 if (init) {
125 inset_bounds = bounds;
126 InsetRect(&inset_bounds, 4, 4);
127 logger->messages_te = TEStylNew(&inset_bounds, &bounds);
128 if (logger->messages_te == NULL)
129 panic("Can't create logger TE");
130 (*(logger->messages_te))->caretHook = NullCaretHook;
131 TEActivate(logger->messages_te);
132 } else {
133 (*(logger->messages_te))->viewRect = bounds;
134 InsetRect(&bounds, 4, 4);
135 (*(logger->messages_te))->destRect = bounds;
136 TECalText(logger->messages_te);
137 TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te);
138 }
139
140 DrawControls(logger->win);
141 DrawGrowIconOnly(logger->win);
142
143 if (!init)
144 logger_flush_buffer(true);
145 }
146
147 void
148 logger_resume(struct focusable *focusable, EventRecord *event)
149 {
150 show_focusable(focusable);
151 InvalRect(logger->win->visRgn);
152 }
153
154 bool
155 logger_quit(struct focusable *focusable)
156 {
157 destroy_focusable(focusable);
158 xfree(&logger->buffered_logs);
159 xfree(&logger);
160
161 return true;
162 }
163
164 void
165 logger_update(struct focusable *focusable, EventRecord *event)
166 {
167 GrafPtr old_port;
168 Rect r;
169 short what = -1;
170
171 GetPort(&old_port);
172 SetPort(logger->win);
173
174 if (event != NULL)
175 what = event->what;
176
177 switch (what) {
178 case -1:
179 case updateEvt:
180 TextFont(applFont);
181 TextSize(10);
182
183 EraseRect(&logger->win->portRect);
184
185 r = (*(logger->messages_te))->viewRect;
186 InsetRect(&r, -1, -1);
187 FrameRect(&r);
188
189 TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te);
190 DrawControls(logger->win);
191 DrawGrowIconOnly(logger->win);
192 break;
193 case activateEvt:
194 if (event->modifiers & activeFlag) {
195 TEActivate(logger->messages_te);
196 } else {
197 TEDeactivate(logger->messages_te);
198 }
199 break;
200 }
201
202 SetPort(old_port);
203 }
204
205 void
206 logger_update_title(void)
207 {
208 static char logger_title[64];
209 short n, uprinted = 0;
210
211 snprintf(logger_title, sizeof(logger_title),
212 "%s | %luKB Free | %lu Call%s | ", db->config.name,
213 (FreeMem() / 1024),
214 session_today_tally.calls, session_today_tally.calls == 1 ? "" : "s");
215
216 for (n = 0; n < MAX_SESSIONS; n++) {
217 if (sessions[n] == NULL || !sessions[n]->logged_in)
218 continue;
219
220 if (uprinted++)
221 strlcat(logger_title, ", ", sizeof(logger_title));
222 else
223 strlcat(logger_title, "Logged in: ", sizeof(logger_title));
224
225 strlcat(logger_title,
226 sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME,
227 sizeof(logger_title));
228 }
229
230 if (uprinted == 0)
231 strlcat(logger_title, "0 connected", sizeof(logger_title));
232
233 SetWTitle(logger->win, CtoPstr(logger_title));
234 }
235
236 void
237 logger_mouse_down(struct focusable *focusable, EventRecord *event)
238 {
239 Point p;
240 ControlHandle control;
241 Rect r;
242 int val, adj;
243
244 p = event->where;
245 GlobalToLocal(&p);
246
247 r = (*(logger->messages_te))->viewRect;
248 if (PtInRect(p, &r)) {
249 TEClick(p, ((event->modifiers & shiftKey) != 0),
250 logger->messages_te);
251 return;
252 }
253
254 switch (FindControl(p, logger->win, &control)) {
255 case inUpButton:
256 case inDownButton:
257 case inPageUp:
258 case inPageDown:
259 if (control != logger->messages_scroller)
260 break;
261 SetTrackControlTE(logger->messages_te);
262 TrackControl(control, p, TrackMouseDownInControl);
263 break;
264 case inThumb:
265 val = GetCtlValue(control);
266 if (TrackControl(control, p, 0L) == 0)
267 break;
268 adj = val - GetCtlValue(control);
269 if (adj != 0) {
270 val -= adj;
271 if (control == logger->messages_scroller)
272 TEScroll(0, adj * TEGetHeight(0, 0, logger->messages_te),
273 logger->messages_te);
274 SetCtlValue(control, val);
275 }
276 break;
277 }
278 }
279
280 bool
281 logger_menu(struct focusable *focusable, short menu, short item)
282 {
283 switch (menu) {
284 case EDIT_MENU_ID:
285 switch (item) {
286 case EDIT_MENU_COPY_ID:
287 HLock(logger->messages_te);
288 if ((*(logger->messages_te))->selStart ==
289 (*(logger->messages_te))->selEnd)
290 SysBeep(10);
291 else
292 TECopy(logger->messages_te);
293 HUnlock(logger->messages_te);
294 break;
295 default:
296 SysBeep(10);
297 break;
298 }
299 return true;
300 }
301
302 return false;
303 }
304
305 void
306 logger_resize(struct focusable *focusable, EventRecord *event)
307 {
308 Rect bounds;
309 long newsize, width, height;
310
311 bounds.left = 100;
312 bounds.top = 100;
313 bounds.right = screenBits.bounds.right;
314 bounds.bottom = screenBits.bounds.bottom;
315
316 newsize = GrowWindow(focusable->win, event->where, &bounds);
317
318 height = HiWord(newsize);
319 width = LoWord(newsize);
320 SizeWindow(focusable->win, width, height, true);
321 logger_layout(NULL);
322 }
323
324 size_t
325 logger_printf(const char *format, ...)
326 {
327 va_list va;
328 size_t len;
329
330 if (!logger)
331 return 0;
332
333 va_start(va, format);
334 len = logger_vprintf(format, va);
335 va_end(va);
336
337 return len;
338 }
339
340 size_t
341 logger_vprintf(const char *format, va_list ap)
342 {
343 static char buf[600];
344 ssize_t len;
345 time_t now = Time;
346
347 if (!logger)
348 return 0;
349
350 blanker_unblank();
351
352 len = strftime(buf, sizeof(buf), "\r[%H:%M:%S] ", localtime(&now));
353 len += vsnprintf(buf + len, sizeof(buf) - len, format, ap);
354 if (len > sizeof(buf)) {
355 buf[sizeof(buf) - 1] = ']';
356 buf[sizeof(buf) - 2] = '…';
357 buf[sizeof(buf) - 3] = '[';
358 len = sizeof(buf);
359 }
360
361 while (buf[len - 1] == '\r') {
362 buf[len - 1] = '\0';
363 len--;
364 }
365
366 if (logger->buffered_logs_len + len > LOGGER_BUFFERED_LOG_SIZE)
367 logger_flush_buffer(false);
368
369 len = strlcat(logger->buffered_logs, buf, LOGGER_BUFFERED_LOG_SIZE);
370 logger->buffered_logs_len = MIN(len, LOGGER_BUFFERED_LOG_SIZE);
371
372 if (logger->autoflush)
373 logger_flush_buffer(false);
374
375 return len;
376 }
377
378 void
379 logger_flush_buffer(bool force)
380 {
381 RgnHandle savergn;
382 Rect zerorect = { 0, 0, 0, 0 };
383 GrafPtr old_port;
384 short line_height = 0, new_lines = 1, n;
385 char *buf;
386
387 if (!logger || (logger->buffered_logs_len == 0 && !force))
388 return;
389
390 for (n = 0; n < logger->buffered_logs_len; n++) {
391 if (logger->buffered_logs[n] == '\r')
392 new_lines++;
393 }
394
395 line_height = LOGGER_FONT_SIZE + 3;
396
397 GetPort(&old_port);
398 SetPort(logger->win);
399
400 /* check for TE overflow */
401
402 HLock(logger->messages_te);
403
404 /* too many lines */
405 if ((unsigned long)(*(logger->messages_te))->nLines >=
406 (nitems((*(logger->messages_te))->lineStarts) - new_lines))
407 goto te_overflow;
408
409 /* too many characters */
410 if ((unsigned long)(*(logger->messages_te))->teLength >=
411 (SHRT_MAX - logger->buffered_logs_len))
412 goto te_overflow;
413
414 /* destRect of all lines is too tall */
415 if ((unsigned long)(*(logger->messages_te))->nLines * line_height >=
416 (SHRT_MAX - new_lines))
417 goto te_overflow;
418
419 goto no_overflow;
420
421 te_overflow:
422 savergn = NewRgn();
423 GetClip(savergn);
424 /* create an empty clip region so all TE updates are hidden */
425 ClipRect(&zerorect);
426
427 /* select some lines at the start, delete them */
428 TESetSelect(0, (*(logger->messages_te))->lineStarts[new_lines * 2],
429 logger->messages_te);
430 TEDelete(logger->messages_te);
431
432 /* scroll up, causing a repaint */
433 TEPinScroll(0, SHRT_MAX, logger->messages_te);
434
435 /* then scroll back down to what it looked like before we did anything */
436 TEPinScroll(0, -SHRT_MAX, logger->messages_te);
437
438 /* resume normal drawing */
439 SetClip(savergn);
440 DisposeRgn(savergn);
441
442 no_overflow:
443 TESetSelect(SHRT_MAX, SHRT_MAX, logger->messages_te);
444
445 buf = logger->buffered_logs;
446 if ((*(logger->messages_te))->teLength == 0) {
447 /* skip leading \r */
448 logger->buffered_logs_len--;
449 buf++;
450 }
451
452 TEInsert(buf, logger->buffered_logs_len, logger->messages_te);
453
454 if ((unsigned long)(*(logger->messages_te))->nLines >=
455 nitems((*(logger->messages_te))->lineStarts))
456 warn("te overflow!");
457
458 TEPinScroll(0, -SHRT_MAX, logger->messages_te);
459 SetCtlValue(logger->messages_scroller,
460 GetCtlMax(logger->messages_scroller));
461 UpdateScrollbarForTE(logger->win, logger->messages_scroller,
462 logger->messages_te, false);
463 HUnlock(logger->messages_te);
464
465 SetPort(old_port);
466
467 logger->buffered_logs_len = 0;
468 logger->buffered_logs[0] = '\0';
469 }