jcs
/subtext
/amendments
/56
settings: Start on settings windows
jcs made amendment 56 over 2 years ago
--- settings.c Tue Jan 18 17:05:20 2022
+++ settings.c Tue Jan 18 17:05:20 2022
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "subtext.h"
+#include "db.h"
+#include "settings.h"
+#include "tetab.h"
+#include "util.h"
+
+enum {
+ CONFIG_TYPE_STRING,
+ CONFIG_TYPE_SHORT
+};
+
+struct config_field {
+ char name[32];
+ short type;
+ unsigned short off;
+ unsigned short min;
+ unsigned short max;
+ short ditl_id;
+} config_fields[] = {
+ { "BBS Name", CONFIG_TYPE_STRING,
+ offsetof(struct config, name), 1, 32, SETTINGS_BBS_NAME_ID },
+ { "Phone Number", CONFIG_TYPE_STRING,
+ offsetof(struct config, phone_number), 1, 16, SETTINGS_PHONE_ID },
+ { "Location", CONFIG_TYPE_STRING,
+ offsetof(struct config, location), 1, 64, SETTINGS_LOCATION_ID },
+ { "Hostname", CONFIG_TYPE_STRING,
+ offsetof(struct config, hostname), 1, 32, SETTINGS_HOSTNAME_ID },
+ { "Telnet Port", CONFIG_TYPE_SHORT,
+ offsetof(struct config, telnet_port), 1, 65535, SETTINGS_TELNET_PORT_ID }
+};
+
+struct view_editor {
+ WindowPtr win;
+ size_t view_id;
+ TEHandle te;
+ ControlHandle scroller;
+};
+
+void view_editor_key_down(struct focusable *focusable, EventRecord *event);
+void view_editor_mouse_down(struct focusable *focusable,
+ EventRecord *event);
+void view_editor_update(struct focusable *focusable, EventRecord *event);
+void view_editor_close(struct focusable *focusable, EventRecord *event);
+
+void
+settings_edit(void)
+{
+ Str255 txt;
+ Handle ihandle;
+ struct config new_config;
+ size_t len;
+ DialogPtr dlg;
+ Rect irect;
+ short done = 0, itype, hit, n;
+
+ new_config = db->config;
+
+ if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL)
+ panic("Can't find settings DLOG %d", SETTINGS_DLOG_ID);
+
+ for (n = 0; n < nitems(config_fields); n++) {
+ struct config_field *cf = &config_fields[n];
+ short sval;
+
+ GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect);
+
+ switch (cf->type) {
+ case CONFIG_TYPE_STRING:
+ strlcpy((char *)&txt, (char *)&new_config + cf->off,
+ sizeof(txt));
+ break;
+ case CONFIG_TYPE_SHORT:
+ sval = (((char *)&new_config)[cf->off] << 8) |
+ ((char *)&new_config)[cf->off + 1];
+ snprintf((char *)&txt, sizeof(txt), "%d", sval);
+ break;
+ }
+
+ CtoPstr(txt);
+ SetIText(ihandle, txt);
+ }
+
+ ShowWindow(dlg);
+
+ while (!done) {
+get_input:
+ ModalDialog(nil, &hit);
+ switch (hit) {
+ case OK:
+ done = 1;
+ break;
+ case Cancel:
+ goto cancel;
+ }
+
+ if (!done)
+ continue;
+
+ for (n = 0; n < nitems(config_fields); n++) {
+ struct config_field *cf = &config_fields[n];
+ long lval;
+ short sval;
+
+ GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect);
+ GetIText(ihandle, txt);
+ PtoCstr(txt);
+
+ switch (cf->type) {
+ case CONFIG_TYPE_STRING:
+ if (strlen((char *)txt) >= cf->max) {
+ warn("%s is too long (%d max)", cf->name, cf->max - 1);
+ done = 0;
+ goto get_input;
+ }
+ strlcpy((char *)&new_config + cf->off, (char *)&txt,
+ cf->max);
+ break;
+ case CONFIG_TYPE_SHORT:
+ lval = atol((char *)txt);
+ if (lval < cf->min) {
+ warn("%s must be at least %d", cf->name, cf->min);
+ done = 0;
+ goto get_input;
+ }
+ if (lval > cf->max) {
+ warn("%s must be less than %d", cf->name, cf->max);
+ done = 0;
+ goto get_input;
+ }
+ sval = lval;
+ memcpy(&new_config + cf->off, &sval, sizeof(short));
+ break;
+ }
+ }
+ }
+
+ db->config = new_config;
+ db_config_save(db);
+
+cancel:
+ DisposeDialog(dlg);
+}
+
+void
+view_editor_show(size_t id, char *title)
+{
+ struct focusable *focusable;
+ struct view_editor *view_editor;
+ Rect bounds, te_bounds;
+ TextStyle style;
+ short width, height, fh, hit;
+ size_t vsize;
+ char *view = NULL;
+ TEHandle te;
+ short padding = 10;
+ bool done;
+
+ vsize = bile_read_alloc(db->bile, DB_TEXT_TYPE, id, &view);
+// if (vsize == 0)
+// return;
+
+ width = 480;
+ height = 250;
+
+ bounds.left = (screenBits.bounds.right - screenBits.bounds.left -
+ width) / 2;
+ bounds.right = bounds.left + width;
+ bounds.top = GetMBarHeight() +
+ ((screenBits.bounds.bottom - height) / 2);
+ bounds.bottom = bounds.top + height;
+
+ view_editor = xmalloczero(sizeof(struct view_editor));
+ view_editor->view_id = id;
+
+ CtoPstr(title);
+ view_editor->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
+ (WindowPtr)-1L, true, 0);
+ PtoCstr(title);
+ if (!view_editor->win)
+ panic("Can't create window");
+ SetPort(view_editor->win);
+
+ bounds.top = padding;
+ bounds.left = padding;
+ fh = FontHeight(monaco, 9);
+ bounds.bottom = view_editor->win->portRect.bottom - padding;
+ bounds.right = view_editor->win->portRect.right - SCROLLBAR_WIDTH -
+ padding;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ TextFont(monaco);
+ TextSize(9);
+ view_editor->te = TEStylNew(&te_bounds, &bounds);
+ style.tsFont = monaco;
+ style.tsSize = 9;
+ TESetStyle(doFont | doSize, &style, false, view_editor->te);
+ TEAutoView(true, view_editor->te);
+ TETabEnable(view_editor->te);
+
+ if (view)
+ free(view);
+
+ bounds.left = bounds.right;
+ bounds.right += SCROLLBAR_WIDTH;
+ bounds.bottom++;
+ bounds.top--;
+ view_editor->scroller = NewControl(view_editor->win, &bounds, "\p",
+ true, 1, 1, 1, scrollBarProc, 0L);
+
+ focusable = xmalloczero(sizeof(struct focusable));
+ focusable->win = view_editor->win;
+ focusable->cookie = view_editor;
+ focusable->key_down = view_editor_key_down;
+ focusable->mouse_down = view_editor_mouse_down;
+ focusable->update = view_editor_update;
+ focusable->close = view_editor_close;
+ show_focusable(focusable);
+}
+
+void
+view_editor_key_down(struct focusable *focusable, EventRecord *event)
+{
+ struct view_editor *view_editor =
+ (struct view_editor *)focusable->cookie;
+ char k;
+
+ k = (event->message & charCodeMask);
+ TEKey(k, view_editor->te);
+ UpdateScrollbarForTE(view_editor->scroller, view_editor->te,
+ false);
+}
+
+void
+view_editor_mouse_down(struct focusable *focusable, EventRecord *event)
+{
+ struct view_editor *view_editor =
+ (struct view_editor *)focusable->cookie;
+ Point p;
+ ControlHandle control;
+ Rect r;
+ short val, adj, page, was_selected, part, i;
+
+ p = event->where;
+ GlobalToLocal(&p);
+
+ r = (*(view_editor->te))->viewRect;
+ if (PtInRect(p, &r)) {
+ TEClick(p, ((event->modifiers & shiftKey) != 0),
+ view_editor->te);
+ return;
+ }
+
+ switch (part = FindControl(p, view_editor->win, &control)) {
+ case inButton:
+ TextFont(applFont);
+ TextSize(11);
+ if (TrackControl(control, p, 0L))
+ ;
+ break;
+ case inUpButton:
+ case inDownButton:
+ case inPageUp:
+ case inPageDown:
+ if (control == view_editor->scroller)
+ SetTrackControlTE(view_editor->te);
+ else
+ break;
+ TrackControl(control, p, TrackMouseDownInControl);
+ break;
+ case inThumb:
+ val = GetCtlValue(control);
+ if (TrackControl(control, p, 0L) == 0)
+ break;
+ adj = val - GetCtlValue(control);
+ if (adj != 0) {
+ val -= adj;
+ if (control == view_editor->scroller)
+ TEScroll(0, adj * TEGetHeight(0, 0, view_editor->te),
+ view_editor->te);
+ SetCtlValue(control, val);
+ }
+ break;
+ }
+}
+
+void
+view_editor_update(struct focusable *focusable, EventRecord *event)
+{
+ struct view_editor *view_editor =
+ (struct view_editor *)focusable->cookie;
+ Rect r;
+
+ r = (*(view_editor->te))->viewRect;
+ FillRect(&r, white);
+ TEUpdate(&r, view_editor->te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ UpdtControl(view_editor->win, view_editor->win->visRgn);
+}
+
+void
+view_editor_close(struct focusable *focusable, EventRecord *event)
+{
+ struct view_editor *view_editor =
+ (struct view_editor *)focusable->cookie;
+
+ close_focusable(focusable);
+ DisposeWindow(view_editor->win);
+ TEDispose(view_editor->te);
+ DisposHandle(view_editor->scroller);
+ free(focusable);
+ free(view_editor);
+}
--- settings.h Tue Jan 18 08:07:32 2022
+++ settings.h Tue Jan 18 08:07:32 2022
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __SETTINGS_H__
+#define __SETTINGS_H__
+
+void settings_edit(void);
+void view_editor_show(size_t id, char *title);
+
+#endif
--- tetab.c Fri Oct 29 17:17:44 2021
+++ tetab.c Fri Oct 29 17:17:44 2021
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Implements tab spacing in TextEdit controls with a fixed-width font.
+ * Only works on styled TextEdit controls (created with TEStylNew) and
+ * assumes that functions get called with a text buffer that starts at the
+ * beginning of the line.
+ *
+ * Usage is per-TextEdit control, so these hooks need to be established
+ * on all controls where tabs should be expanded:
+ *
+ * TEHandle te;
+ * ProcPtr teproc;
+ *
+ * [..]
+ * te = TEStylNew(&bounds, &bounds);
+ * TEAutoView(true, te);
+ * TETabEnable(te);
+ *
+ * The tab width is set to 4 by default in the TETabWidth global.
+ *
+ * Thanks to Ari Halberstadt for their 1989 Usenet post on
+ * comp.sys.mac.programmer with HitTestHook assembly.
+ */
+
+#include <Script.h>
+#include <SetUpA4.h>
+
+#include "tetab.h"
+
+pascal void TETabDrawHook(void);
+pascal void TETabWidthHook(void);
+pascal void TETabHitTestHook(void);
+
+short TETabWidth = 4;
+
+void
+TETabEnable(TEHandle te)
+{
+ ProcPtr teproc;
+
+ TEAutoView(true, te);
+ teproc = StripAddress(TETabWidthHook);
+ TECustomHook(intWidthHook, &teproc, te);
+ teproc = StripAddress(TETabDrawHook);
+ TECustomHook(intDrawHook, &teproc, te);
+ teproc = StripAddress(TETabHitTestHook);
+ TECustomHook(intHitTestHook, &teproc, te);
+}
+
+pascal void
+TETabDrawHook(void)
+{
+ /* entry registers, in order */
+ short offset;
+ short length;
+ Ptr text;
+
+ /* locals */
+ short i, cwidth, cpos, space, startdraw;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, offset
+ move.w d1, length
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ /*
+ * This startdraw stuff is to try to draw long blocks of non-tab text
+ * with one call to DrawText() instead of DrawChar()ing every character
+ * one at a time, for speed.
+ */
+ for (i = 0, cpos = 0, startdraw = 0; i < length; i++) {
+ c = text[offset + i];
+
+ if (c == '\t') {
+ if (i - startdraw)
+ DrawText(text, startdraw, i - startdraw);
+
+ space = (TETabWidth - (cpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ cpos += space;
+ Move(cwidth * space, 0);
+ startdraw = i + 1;
+ } else {
+ cpos++;
+ }
+ }
+
+ if (startdraw != i)
+ DrawText(text, startdraw, i - startdraw);
+
+ /* return nothing */
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
+
+pascal void
+TETabWidthHook(void)
+{
+ /* entry registers, in order */
+ short length;
+ short offset;
+ Ptr text;
+
+ /* exit */
+ short width;
+
+ short i, cwidth, cpos, space;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, length
+ move.w d1, offset
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ for (i = 0, cpos = 0; i < length; i++) {
+ c = text[offset + i];
+
+ if (c == '\t') {
+ space = (TETabWidth - (cpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ cpos += space;
+ } else {
+ cpos++;
+ }
+ }
+
+ width = cwidth * cpos;
+
+ /* return length */
+ asm {
+ move.w width, d1
+ }
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
+
+pascal void
+TETabHitTestHook(void)
+{
+ /* entry registers, in order */
+ short length;
+ short poffset;
+ Ptr text;
+
+ /* exit */
+ short width;
+ short offset = 0;
+ short leftside = 0;
+
+ /* locals */
+ short i, cwidth, cpos, wpos, space, found;
+ char c;
+
+ asm {
+ movem.l d3-d7/a0-a3, -(a7)
+ move.w d0, length
+ move.w d1, poffset
+ move.l a0, text
+ }
+ SetUpA4();
+
+ cwidth = CharWidth(' ');
+
+ for (i = 0, wpos = 0, cpos = 0, found = 0; i < length; i++) {
+ c = text[i];
+
+ if (c == '\t') {
+ space = (TETabWidth - (wpos % TETabWidth));
+ if (!space)
+ space = TETabWidth;
+ wpos += space;
+ } else if (c == '\r')
+ break;
+ else
+ wpos++;
+
+ cpos++;
+
+ if (poffset <= (wpos * cwidth)) {
+ found = 1;
+ break;
+ }
+ }
+
+ /* put found in hi word */
+ width = (wpos * cwidth) | (found << 8);
+ offset = cpos;
+ leftside = (poffset < (width - (cwidth / 2)));
+
+ asm {
+ move.w width, d0
+ move.w offset, d1
+ move.w leftside, d2
+ }
+
+ /* restore registers */
+ RestoreA4();
+ asm {
+ movem.l (a7)+, d3-d7/a0-a3
+ }
+}
--- tetab.h Thu Oct 21 14:04:42 2021
+++ tetab.h Thu Oct 21 14:04:42 2021
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+void TETabEnable(TEHandle te);
+extern short TETabWidth;