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 | } |