AmendHub

Download

jcs

/

subtext

/

settings.c

 

(View History)

jcs   *: Add more prompt help strings Latest amendment: 444 on 2023-03-25

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 <string.h>
19
20 #include "ansi.h"
21 #include "subtext.h"
22 #include "db.h"
23 #include "focusable.h"
24 #include "main_menu.h"
25 #include "settings.h"
26 #include "tcp.h" /* for long2ip/ip2long */
27 #include "tetab.h"
28 #include "util.h"
29
30 struct view_editor {
31 WindowPtr win;
32 size_t view_id;
33 TEHandle te;
34 ControlHandle vert_scroller;
35 ControlHandle horiz_scroller;
36 ControlHandle save_button;
37 };
38
39 void view_editor_key_down(struct focusable *focusable, EventRecord *event);
40 void view_editor_mouse_down(struct focusable *focusable,
41 EventRecord *event);
42 void view_editor_update(struct focusable *focusable, EventRecord *event);
43 void view_editor_close(struct focusable *focusable, EventRecord *event);
44 void view_editor_save(struct focusable *focusable, EventRecord *event);
45
46 short
47 struct_editor(struct session *s, const struct struct_field *fields,
48 const size_t nfields, const struct session_menu_option *extra_opts,
49 size_t nextra_opts, void *data, size_t dsize, void **result,
50 char *title, char *prompt)
51 {
52 static const struct session_menu_option opts[] = {
53 { '#', "", "Edit setting [#]" },
54 { 's', "Ss", "Save and return to main menu" },
55 { 'q', "QqXx", "Discard changes and return to main menu" },
56 { '?', "?", "List menu options" },
57 };
58 struct session_menu_option *dopts = NULL, *opt;
59 const struct struct_field *sf;
60 long lval;
61 char co, initial[20];
62 char *input = NULL, *new_data;
63 size_t nopts;
64 short n, j, i, sval, on, ret;
65 bool any_changes, done, show_list;
66 unsigned short c;
67
68 new_data = xmalloc(dsize);
69 if (new_data == NULL)
70 goto done;
71 memcpy(new_data, data, dsize);
72
73 nopts = nitems(opts) + nextra_opts;
74 dopts = xcalloc(sizeof(struct session_menu_option), nopts);
75 if (dopts == NULL)
76 return;
77 if (extra_opts != NULL)
78 memcpy(dopts, extra_opts,
79 sizeof(struct session_menu_option) * nextra_opts);
80 memcpy(dopts + nextra_opts, opts, sizeof(opts));
81
82 session_printf(s, "{{B}}Editor: %s{{/B}}\r\n", title);
83 session_flush(s);
84
85 any_changes = false;
86 show_list = true;
87 done = false;
88 ret = -1;
89
90 while (!done && !s->ending) {
91 if (show_list) {
92 for (n = 0; n < nfields; n++) {
93 if ((n + 1) % (s->terminal_lines - 2) == 0) {
94 session_printf(s, "-- More --");
95 session_flush(s);
96 session_input_char(s);
97 if (s->vt100)
98 session_printf(s, "\r%s",
99 ansi(s, ANSI_ERASE_LINE, ANSI_END));
100 else
101 session_output(s, "\r", 1);
102 }
103
104 sf = &fields[n];
105
106 session_printf(s, "{{B}}%d{{/B}}: %s [", n + 1, sf->name);
107
108 switch (sf->type) {
109 case CONFIG_TYPE_STRING:
110 session_printf(s, "%s", new_data + sf->off);
111 break;
112 case CONFIG_TYPE_PASSWORD:
113 i = strlen(new_data + sf->off);
114 while (i) {
115 session_output(s, "*", 1);
116 i--;
117 }
118 break;
119 case CONFIG_TYPE_SHORT:
120 sval = CHARS_TO_SHORT(new_data[sf->off],
121 new_data[sf->off + 1]);
122 session_printf(s, "%d", sval);
123 break;
124 case CONFIG_TYPE_LONG:
125 lval = CHARS_TO_LONG(new_data[sf->off],
126 new_data[sf->off + 1], new_data[sf->off + 2],
127 new_data[sf->off + 3]);
128 session_printf(s, "%ld", lval);
129 break;
130 case CONFIG_TYPE_BOOLEAN:
131 session_printf(s, "%s",
132 new_data[sf->off] == 0 ? "false" : "true");
133 break;
134 case CONFIG_TYPE_IP: {
135 char ip_str[16];
136 lval = CHARS_TO_LONG(new_data[sf->off],
137 new_data[sf->off + 1], new_data[sf->off + 2],
138 new_data[sf->off + 3]);
139 if (lval == 0)
140 session_printf(s, "0.0.0.0");
141 else {
142 long2ip(lval, ip_str);
143 session_printf(s, "%s", ip_str);
144 }
145 break;
146 }
147 }
148
149 session_printf(s, "]\r\n");
150 }
151 }
152
153 c = session_menu(s, NULL, prompt, NULL, dopts, nopts, show_list,
154 "Option #", &on);
155 show_list = false;
156
157 switch (c) {
158 case 's':
159 goto done;
160 case 'q':
161 any_changes = false;
162 goto done;
163 case '?':
164 show_list = true;
165 break;
166 case '#':
167 if (on < 1 || on > nfields) {
168 session_printf(s, "Invalid option\r\n");
169 session_flush(s);
170 break;
171 }
172
173 sf = &fields[on - 1];
174
175 get_input:
176 session_printf(s, "{{B}}%s:{{/B}} ", sf->name);
177 session_flush(s);
178
179 switch (sf->type) {
180 case CONFIG_TYPE_STRING:
181 case CONFIG_TYPE_PASSWORD:
182 input = session_field_input(s, sf->max, 50,
183 new_data + sf->off, false,
184 (sf->type == CONFIG_TYPE_PASSWORD ? '*' : 0));
185 session_output(s, "\r\n", 2);
186 session_flush(s);
187 if (input == NULL || s->ending)
188 break;
189
190 if (strlen(input) >= sf->max) {
191 session_printf(s,
192 "%s is too long (%d max, ^C to cancel)", sf->name,
193 sf->max - 1);
194 xfree(&input);
195 goto get_input;
196 }
197 strlcpy(new_data + sf->off, input, sf->max);
198 any_changes = true;
199 break;
200 case CONFIG_TYPE_SHORT:
201 case CONFIG_TYPE_LONG:
202 if (sf->type == CONFIG_TYPE_LONG) {
203 lval = CHARS_TO_LONG(new_data[sf->off],
204 new_data[sf->off + 1], new_data[sf->off + 2],
205 new_data[sf->off + 3]);
206 snprintf(initial, sizeof(initial), "%ld", lval);
207 } else {
208 sval = CHARS_TO_SHORT(new_data[sf->off],
209 new_data[sf->off + 1]);
210 snprintf(initial, sizeof(initial), "%d", sval);
211 }
212 input = session_field_input(s, 10, 10, initial, false, 0);
213 session_output(s, "\r\n", 2);
214 session_flush(s);
215 if (input == NULL || s->ending)
216 break;
217
218 lval = atol(input);
219 if (lval < sf->min) {
220 session_printf(s,
221 "%s must be %ld or higher (^C to cancel)\r\n",
222 sf->name, sf->min);
223 xfree(&input);
224 goto get_input;
225 }
226 if (lval > sf->max) {
227 session_printf(s,
228 "%s must be %ld or less (^C to cancel)\r\n",
229 sf->name, sf->max);
230 xfree(&input);
231 goto get_input;
232 }
233 if (sf->type == CONFIG_TYPE_LONG) {
234 new_data[sf->off] = (lval >> 24) & 0xff;
235 new_data[sf->off + 1] = (lval >> 16) & 0xff;
236 new_data[sf->off + 2] = (lval >> 8) & 0xff;
237 new_data[sf->off + 3] = lval & 0xff;
238 } else {
239 sval = lval;
240 new_data[sf->off] = (sval >> 8) & 0xff;
241 new_data[sf->off + 1] = sval & 0xff;
242 }
243 any_changes = true;
244 break;
245 case CONFIG_TYPE_BOOLEAN:
246 if (new_data[sf->off])
247 session_printf(s, "[Y/n] ");
248 else
249 session_printf(s, "[y/N] ");
250 session_flush(s);
251
252 for (;;) {
253 c = session_input_char(s);
254 if (s->ending)
255 break;
256 if (c == '\r' && new_data[sf->off])
257 c = 'y';
258 else if (c == '\r' && new_data[sf->off] == 0)
259 c = 'n';
260 if (c == 'y' || c == 'Y') {
261 new_data[sf->off] = 1;
262 any_changes = true;
263 session_printf(s, "%c\r\n", c);
264 break;
265 }
266 if (c == 'n' || c == 'N') {
267 new_data[sf->off] = 0;
268 any_changes = true;
269 session_printf(s, "%c\r\n", c);
270 break;
271 }
272 }
273 break;
274 case CONFIG_TYPE_IP: {
275 char ip_str[16];
276 lval = CHARS_TO_LONG(new_data[sf->off],
277 new_data[sf->off + 1], new_data[sf->off + 2],
278 new_data[sf->off + 3]);
279 if (lval == 0)
280 snprintf(initial, sizeof(initial), "0.0.0.0");
281 else {
282 long2ip(lval, ip_str);
283 snprintf(initial, sizeof(initial), "%s", ip_str);
284 }
285
286 input = session_field_input(s, 16, 16, initial, false, 0);
287 session_output(s, "\r\n", 2);
288 session_flush(s);
289 if (input == NULL || s->ending)
290 break;
291
292 if (input[0] == '\0') {
293 lval = 0;
294 xfree(&input);
295 } else {
296 lval = ip2long(input);
297 xfree(&input);
298 if (lval == 0) {
299 session_printf(s,
300 "Invalid IP address (^C to cancel)\r\n");
301 goto get_input;
302 }
303 }
304 new_data[sf->off] = (lval >> 24) & 0xff;
305 new_data[sf->off + 1] = (lval >> 16) & 0xff;
306 new_data[sf->off + 2] = (lval >> 8) & 0xff;
307 new_data[sf->off + 3] = lval & 0xff;
308 any_changes = true;
309 break;
310 }
311 }
312 break;
313 default:
314 ret = c;
315 done = true;
316 break;
317 }
318
319 if (s->ending) {
320 any_changes = false;
321 goto done;
322 }
323 }
324
325 done:
326 xfree(&dopts);
327
328 if (any_changes) {
329 *result = new_data;
330 return 0;
331 } else {
332 xfree(&new_data);
333 *result = NULL;
334 return ret;
335 }
336 }
337
338 void
339 view_editor_show(size_t id, char *title)
340 {
341 struct focusable *focusable;
342 struct view_editor *view_editor;
343 Rect bounds, te_bounds;
344 TextStyle style;
345 short width, height;
346 size_t vsize;
347 char *view = NULL;
348 short padding = 10;
349
350 width = 480;
351 height = 250;
352
353 bounds.left = (screenBits.bounds.right - screenBits.bounds.left -
354 width) / 2;
355 bounds.right = bounds.left + width;
356 bounds.top = GetMBarHeight() +
357 ((screenBits.bounds.bottom - height) / 2);
358 bounds.bottom = bounds.top + height;
359
360 view_editor = xmalloczero(sizeof(struct view_editor));
361 if (view_editor == NULL)
362 return;
363 view_editor->view_id = id;
364
365 CtoPstr(title);
366 view_editor->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
367 (WindowPtr)-1L, true, 0);
368 PtoCstr(title);
369 if (!view_editor->win) {
370 warn("Can't create window");
371 xfree(&view_editor);
372 return;
373 }
374 SetPort(view_editor->win);
375
376 /* add save button */
377 TextFont(applFont);
378 TextSize(11);
379 bounds.left = view_editor->win->portRect.right - padding - 100;
380 bounds.right = bounds.left + 100;
381 bounds.bottom = view_editor->win->portRect.bottom - padding;
382 bounds.top = bounds.bottom - 20;
383 view_editor->save_button = NewControl(view_editor->win, &bounds,
384 "\pSave", true, 1, 1, 1, pushButProc, 0L);
385 if (view_editor->save_button == NULL) {
386 warn("Failed to create save button");
387 goto fail;
388 }
389
390 /* add text box */
391 bounds.bottom = bounds.top - padding - SCROLLBAR_WIDTH;
392 bounds.top = padding;
393 bounds.left = padding;
394 bounds.right = view_editor->win->portRect.right - SCROLLBAR_WIDTH -
395 padding;
396 te_bounds = bounds;
397 InsetRect(&te_bounds, 2, 2);
398 TextFont(monaco);
399 TextSize(9);
400 view_editor->te = TEStylNew(&te_bounds, &bounds);
401 if (view_editor->te == NULL) {
402 warn("Failed to create TE");
403 goto fail;
404 }
405 style.tsFont = monaco;
406 style.tsSize = 9;
407 TESetStyle(doFont | doSize, &style, false, view_editor->te);
408 TEAutoView(true, view_editor->te);
409 TETabWidth = 8;
410 TETabEnable(view_editor->te);
411 TEActivate(view_editor->te);
412
413 vsize = bile_read_alloc(db->bile, DB_TEXT_TYPE, id, &view);
414 if (bile_error(db->bile) == BILE_ERR_NO_MEMORY)
415 goto fail;
416 if (view) {
417 TESetText(view, vsize, view_editor->te);
418 HLock(view_editor->te);
419 InvalRect(&(*(view_editor->te))->viewRect);
420 HUnlock(view_editor->te);
421 xfree(&view);
422 }
423
424 bounds.left = bounds.right;
425 bounds.right += SCROLLBAR_WIDTH;
426 bounds.bottom++;
427 bounds.top--;
428 view_editor->vert_scroller = NewControl(view_editor->win, &bounds,
429 "\p", true, 1, 1, 1, scrollBarProc, 0L);
430 if (view_editor->vert_scroller == NULL) {
431 warn("Failed to create scroller");
432 goto fail;
433 }
434
435 bounds.left = (*(view_editor->te))->viewRect.left - 1;
436 bounds.right = (*(view_editor->te))->viewRect.right + 1;
437 bounds.top = (*(view_editor->te))->viewRect.bottom;
438 bounds.bottom = bounds.top + SCROLLBAR_WIDTH;
439 view_editor->horiz_scroller = NewControl(view_editor->win, &bounds,
440 "\p", true, 1, 1, 1, scrollBarProc, 0L);
441 if (view_editor->horiz_scroller == NULL) {
442 warn("Failed to create scroller");
443 goto fail;
444 }
445
446 focusable = xmalloczero(sizeof(struct focusable));
447 if (focusable == NULL)
448 goto fail;
449 focusable->win = view_editor->win;
450 focusable->cookie = view_editor;
451 focusable->key_down = view_editor_key_down;
452 focusable->mouse_down = view_editor_mouse_down;
453 focusable->update = view_editor_update;
454 focusable->close = view_editor_close;
455 if (!add_focusable(focusable))
456 goto fail;
457
458 UpdateScrollbarForTE(view_editor->win, view_editor->vert_scroller,
459 view_editor->te, false);
460 UpdateScrollbarForTE(view_editor->win, view_editor->horiz_scroller,
461 view_editor->te, false);
462
463 return;
464
465 fail:
466 if (focusable)
467 xfree(&focusable);
468 if (view_editor->te)
469 TEDispose(view_editor->te);
470 DisposeWindow(view_editor);
471 xfree(&view_editor);
472 }
473
474 void
475 view_editor_key_down(struct focusable *focusable, EventRecord *event)
476 {
477 struct view_editor *view_editor =
478 (struct view_editor *)focusable->cookie;
479 char k = (event->message & charCodeMask);
480
481 if ((event->modifiers & cmdKey) != 0) {
482 switch (k) {
483 case 'c':
484 TECopy(view_editor->te);
485 break;
486 case 'v':
487 TEPaste(view_editor->te);
488 break;
489 case 'x':
490 TECut(view_editor->te);
491 break;
492 }
493 } else
494 TEKey(k, view_editor->te);
495
496 UpdateScrollbarForTE(view_editor->win, view_editor->vert_scroller,
497 view_editor->te, false);
498 UpdateScrollbarForTE(view_editor->win, view_editor->horiz_scroller,
499 view_editor->te, false);
500 }
501
502 void
503 view_editor_mouse_down(struct focusable *focusable, EventRecord *event)
504 {
505 struct view_editor *view_editor =
506 (struct view_editor *)focusable->cookie;
507 Point p;
508 ControlHandle control;
509 Rect r;
510 short val, adj;
511
512 p = event->where;
513 GlobalToLocal(&p);
514
515 r = (*(view_editor->te))->viewRect;
516 if (PtInRect(p, &r)) {
517 TEClick(p, ((event->modifiers & shiftKey) != 0),
518 view_editor->te);
519 return;
520 }
521
522 switch (FindControl(p, view_editor->win, &control)) {
523 case inButton:
524 if (TrackControl(control, p, 0L)) {
525 if (control == view_editor->save_button)
526 view_editor_save(focusable, NULL);
527 }
528 break;
529 case inUpButton:
530 case inDownButton:
531 case inPageUp:
532 case inPageDown:
533 if (control != view_editor->vert_scroller &&
534 control != view_editor->horiz_scroller)
535 break;
536
537 SetTrackControlTE(view_editor->te);
538 TrackControl(control, p, TrackMouseDownInControl);
539 break;
540 case inThumb:
541 val = GetCtlValue(control);
542 if (TrackControl(control, p, 0L) == 0)
543 break;
544 adj = val - GetCtlValue(control);
545 if (adj != 0) {
546 val -= adj;
547 if (control == view_editor->vert_scroller)
548 TEScroll(0, adj * TEGetHeight(0, 0, view_editor->te),
549 view_editor->te);
550 else if (control == view_editor->horiz_scroller)
551 TEScroll(adj * TEGetWidth(0, view_editor->te), 0,
552 view_editor->te);
553 SetCtlValue(control, val);
554 }
555 break;
556 }
557 }
558
559 void
560 view_editor_update(struct focusable *focusable, EventRecord *event)
561 {
562 struct view_editor *view_editor =
563 (struct view_editor *)focusable->cookie;
564 Rect r;
565
566 r = (*(view_editor->te))->viewRect;
567 FillRect(&r, white);
568 TEUpdate(&r, view_editor->te);
569 InsetRect(&r, -1, -1);
570 FrameRect(&r);
571
572 UpdtControl(view_editor->win, view_editor->win->visRgn);
573 }
574
575 void
576 view_editor_save(struct focusable *focusable, EventRecord *event)
577 {
578 struct view_editor *view_editor =
579 (struct view_editor *)focusable->cookie;
580 size_t len;
581
582 HLock(view_editor->te);
583 HLock((*(view_editor->te))->hText);
584
585 len = (*(view_editor->te))->teLength;
586
587 if (view_editor->view_id == DB_TEXT_MENU_OPTIONS_ID) {
588 if (!main_menu_parse(*(*(view_editor->te))->hText, len)) {
589 HUnlock((*(view_editor->te))->hText);
590 HUnlock(view_editor->te);
591 return;
592 }
593 }
594
595 bile_write(db->bile, DB_TEXT_TYPE, view_editor->view_id,
596 *(*(view_editor->te))->hText, len);
597
598 HUnlock((*(view_editor->te))->hText);
599 HUnlock(view_editor->te);
600
601 if (view_editor->view_id == DB_TEXT_MENU_OPTIONS_ID)
602 main_menu_init();
603
604 view_editor_close(focusable, event);
605 }
606
607 void
608 view_editor_close(struct focusable *focusable, EventRecord *event)
609 {
610 struct view_editor *view_editor =
611 (struct view_editor *)focusable->cookie;
612
613 TEDispose(view_editor->te);
614 destroy_focusable(focusable);
615 xfree(&view_editor);
616 }