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