jcs
/detritus
/amendments
/42
settings: Add bookmark menu editor, saving of prefs file
jcs made amendment 42 about 1 year ago
--- detritus.π.r Fri Nov 15 15:26:43 2024
+++ detritus.π.r Mon Nov 18 21:16:57 2024
@@ -23,22 +23,15 @@ data 'MENU' (131) {
data 'MENU' (132) {
$"0084 0000 0000 0000 0000 FFFF FFFF 0942" /* .Ñ............∆B */
- $"6F6F 6B6D 6172 6B73 00" /* ookmarks. */
+ $"6F6F 6B6D 6172 6B73 1145 6469 7420 426F" /* ookmarks.Edit Bo */
+ $"6F6B 6D61 726B 732E 2E2E 0042 0000 00" /* okmarks....B... */
};
data 'MBAR' (128) {
$"0005 0080 0081 0082 0083 0084" /* ...Ä.Å.Ç.É.Ñ */
};
-data 'DITL' (130, "ASK") {
- $"0003 0000 0000 004E 00FA 0064 0134 0403" /* .......N...d.4.. */
- $"5965 7321 0000 0000 004E 00B4 0064 00EE" /* Yes!.....N.¥.d.. */
- $"0402 4E6F 0000 0000 000D 004E 0041 0136" /* ..No.....¬.N.A.6 */
- $"0802 5E30 0000 0000 000D 0017 002D 0037" /* ..^0.....¬...-.7 */
- $"A002 0001" /* †... */
-};
-
-data 'DITL' (128) {
+data 'DITL' (128, "input") {
$"0003 0000 0000 004B 00D2 005F 0113 0406" /* .......K.“._.... */
$"5375 626D 6974 0000 0000 004B 0082 005F" /* Submit.....K.Ç._ */
$"00C3 0406 4361 6E63 656C 0000 0000 000A" /* .√..Cancel...... */
@@ -46,6 +39,29 @@ data 'DITL' (128) {
$"3F75 0000 0000 002D 000F 003D 017A 1000" /* ?u.....-...=.z.. */
};
+data 'DITL' (129) {
+ $"0015 0000 0000 0106 0195 011A 01CF 0404" /* .........ï...œ.. */
+ $"5361 7665 0000 0000 0106 0154 011A 018E" /* Save.......T...é */
+ $"0406 4361 6E63 656C 0000 0000 000A 000A" /* ..Cancel........ */
+ $"001A 00A9 1000 0000 0000 000A 00B9 001A" /* ...©.........π.. */
+ $"01D0 1000 0000 0000 0023 000A 0033 00A9" /* .–.......#...3.© */
+ $"1000 0000 0000 0023 00B9 0033 01D0 1000" /* .......#.π.3.–.. */
+ $"0000 0000 003C 000A 004C 00A9 1000 0000" /* .....<...L.©.... */
+ $"0000 003C 00B9 004C 01D0 1000 0000 0000" /* ...<.π.L.–...... */
+ $"0055 000A 0065 00A9 1000 0000 0000 0055" /* .U...e.©.......U */
+ $"00B9 0065 01D0 1000 0000 0000 006E 000A" /* .π.e.–.......n.. */
+ $"007E 00A9 1000 0000 0000 006E 00B9 007E" /* .~.©.......n.π.~ */
+ $"01D0 1000 0000 0000 0087 000A 0097 00A9" /* .–.......á...ó.© */
+ $"1000 0000 0000 0087 00B9 0097 01D0 1000" /* .......á.π.ó.–.. */
+ $"0000 0000 00A0 000A 00B0 00A9 1000 0000" /* .....†...∞.©.... */
+ $"0000 00A0 00B9 00B0 01D0 1000 0000 0000" /* ...†.π.∞.–...... */
+ $"00B9 000A 00C9 00A9 1000 0000 0000 00B9" /* .π...….©.......π */
+ $"00B9 00C9 01D0 1000 0000 0000 00D2 000A" /* .π.….–.......“.. */
+ $"00E2 00A9 1000 0000 0000 00D2 00B9 00E2" /* ...©.......“.π.. */
+ $"01D0 1000 0000 0000 00EB 000A 00FB 00A9" /* .–.............© */
+ $"1000 0000 0000 00EB 00B9 00FB 01D0 1000" /* .........π...–.. */
+};
+
data 'vers' (1) {
$"0010 6000 0000 0330 2E31 2630 2E31 20A9" /* ..`....0.1&0.1 © */
$"2032 3032 342C 206A 6F73 6875 6120 7374" /* 2024, joshua st */
@@ -114,5 +130,11 @@ data 'DLOG' (128) {
$"0060 002C 00C8 01B4 0010 0000 0100 0000" /* .`.,.».¥........ */
$"0000 0080 0F49 6E70 7574 2052 6571 7565" /* ...Ä.Input Reque */
$"7374 6564" /* sted */
+};
+
+data 'DLOG' (129) {
+ $"002E 0010 0150 01EA 0010 0000 0100 0000" /* .....P.......... */
+ $"0000 0081 0E45 6469 7420 426F 6F6B 6D61" /* ...Å.Edit Bookma */
+ $"726B 73" /* rks */
};
--- detritus.h Thu Nov 14 20:54:52 2024
+++ detritus.h Mon Nov 18 17:17:02 2024
@@ -19,6 +19,7 @@
#include "browser.h"
#include "request.h"
+#include "settings.h"
#include "util.h"
#define PROGRAM_NAME "Detritus"
@@ -44,9 +45,18 @@
#define HISTORY_MENU_ID 131
#define BOOKMARKS_MENU_ID 132
+#define BOOKMARKS_MENU_EDIT_ID 1
+#define BOOKMARKS_MENU_SEP_ID 2
extern MenuHandle file_menu, edit_menu, bookmarks_menu;
+struct default_bookmark {
+ char name[64];
+ char uri[256];
+};
+extern const struct default_bookmark default_bookmarks[];
+
void menu_defaults(void);
+void menu_load_bookmarks(void);
#endif
\ No newline at end of file
--- main.c Mon Nov 11 22:21:29 2024
+++ main.c Mon Nov 18 21:13:16 2024
@@ -22,6 +22,14 @@
#include "tcp.h"
#include "util.h"
+/* be sure this is NULL terminated */
+const struct default_bookmark default_bookmarks[] = {
+ { "Floodgap Gopher", "gopher://gopher.floodgap.com/", },
+ { "SDF Gopher", "gopher://sdf.org/", },
+ { "Gemini Protocol", "gemini://geminiprotocol.net/", },
+ { 0 },
+};
+
MenuHandle apple_menu, file_menu, edit_menu, bookmarks_menu;
bool quitting = false;
@@ -60,19 +68,17 @@ main(void)
edit_menu = GetMHandle(EDIT_MENU_ID);
bookmarks_menu = GetMHandle(BOOKMARKS_MENU_ID);
menu_defaults();
-
-AppendMenu(bookmarks_menu, "\p.");
-SetItem(bookmarks_menu, 1, "\pgemini://geminiprotocol.net/history/");
-AppendMenu(bookmarks_menu, "\p.");
-SetItem(bookmarks_menu, 2, "\pgopher://gopher.floodgap.com/");
-
+
DrawMenuBar();
if (_TCPInit() != 0)
panic("Failed initializing MacTCP");
+
+ settings_load();
+ menu_load_bookmarks();
+ /* browser before scsi so the user can look at something while we poll */
browser_init();
-
scsi_find_tls();
while (!quitting) {
@@ -176,8 +182,9 @@ SetItem(bookmarks_menu, 2, "\pgopher://gopher.floodgap
bool
handle_menu(long menu_id)
{
+ struct browser *browser;
struct focusable *focused;
- short menu, item;
+ short menu, item, n;
menu = HiWord(menu_id);
item = LoWord(menu_id);
@@ -186,6 +193,8 @@ handle_menu(long menu_id)
focused->menu(focused, menu, item))
goto handled;
+ browser = (struct browser *)focused->cookie;
+
switch (menu) {
case APPLE_MENU_ID:
switch (item) {
@@ -212,16 +221,19 @@ handle_menu(long menu_id)
break;
}
break;
- case BOOKMARKS_MENU_ID: {
- Str255 uri;
- struct browser *browser = (struct browser *)focused->cookie;
-
- GetItem(bookmarks_menu, item, uri);
- PtoCstr(uri);
- browser_go_uri(browser,(char *)uri);
+ case BOOKMARKS_MENU_ID:
+ switch (item) {
+ case BOOKMARKS_MENU_EDIT_ID:
+ bookmarks_dialog();
+ break;
+ default:
+ n = item - BOOKMARKS_MENU_SEP_ID - 1;
+ if (settings.bookmark_uris[n][0] != '\0')
+ browser_go_uri(browser, settings.bookmark_uris[n]);
+ break;
+ }
break;
}
- }
handled:
HiliteMenu(0);
@@ -235,6 +247,41 @@ menu_defaults(void)
DisableItem(edit_menu, EDIT_MENU_COPY_ID);
DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+}
+
+void
+menu_load_bookmarks(void)
+{
+ Str255 title;
+ short count, n;
+
+ count = CountMItems(bookmarks_menu);
+ while (count >= BOOKMARKS_MENU_SEP_ID) {
+ DelMenuItem(bookmarks_menu, count);
+ count--;
+ }
+
+ for (n = 0; n < BOOKMARKS_COUNT; n++) {
+ if (settings.bookmark_uris[n][0] == '\0')
+ break;
+
+ if (count < BOOKMARKS_MENU_SEP_ID) {
+ AppendMenu(bookmarks_menu, "\p(-");
+ count++;
+ }
+
+ if (settings.bookmark_names[n][0] == '\0')
+ strlcpy((char *)&title, (char *)&settings.bookmark_uris[n],
+ sizeof(title));
+ else
+ strlcpy((char *)&title, (char *)&settings.bookmark_names[n],
+ sizeof(title));
+
+ CtoPstr(title);
+ AppendMenu(bookmarks_menu, "\p.");
+ count++;
+ SetItem(bookmarks_menu, count, title);
+ }
}
void
--- settings.c Mon Nov 18 21:08:28 2024
+++ settings.c Mon Nov 18 21:08:28 2024
@@ -0,0 +1,490 @@
+/*
+ * Copyright (c) 2021-2024 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 <OSUtils.h>
+#include <Files.h>
+#include <Folders.h>
+#include <GestaltEqu.h>
+#include <Traps.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "browser.h"
+#include "settings.h"
+#include "util.h"
+
+enum {
+ SETTING_STRING,
+ SETTING_SHORT,
+ SETTING_USHORT,
+ SETTING_LONG,
+ SETTING_PASSWORD,
+ SETTING_BOOL
+};
+
+enum {
+ SETTING_FLAG_ALLOW_BLANK = (1 << 0)
+};
+
+struct settings settings = { 0 };
+
+struct setting {
+ char name[32];
+ short type;
+ long flags;
+ long min;
+ long max;
+ char sdefault[32];
+ short ditl_id;
+ unsigned long offset;
+ unsigned long size;
+ char password_storage[256];
+};
+
+void settings_load_defaults(void);
+void settings_find_prefs_folder(short *vrefnum, long *dirid);
+bool settings_edit(struct setting *defs, size_t defs_count, short dlog_id,
+ bool use_defaults);
+
+void
+settings_load(void)
+{
+ HParamBlockRec pb = { 0 };
+ struct settings tsettings = { 0 };
+ char fn[] = SETTINGS_FILENAME;
+ char *res;
+ short error, vrefnum, prefrefnum;
+ long len, rlen, dirid;
+ Handle h;
+
+ GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid);
+
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.ioParam.ioPermssn = fsRdPerm;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHOpen(&pb, false);
+ if (error) {
+ if (error != fnfErr)
+ panic("Failed reading preferences file %s: %d", PtoCstr(fn),
+ error);
+
+ goto load_defaults;
+ }
+
+ rlen = len = sizeof(struct settings);
+ FSRead(pb.ioParam.ioRefNum, &rlen, &tsettings);
+
+ while (tsettings.version < SETTINGS_VERSION) {
+ switch (tsettings.version) {
+ default:
+ FSClose(pb.ioParam.ioRefNum);
+
+ if (ask("Unknown preferences version (%d), reset?",
+ tsettings.version))
+ goto load_defaults;
+
+ ExitToShell();
+ }
+
+ tsettings.version++;
+ }
+
+ FSClose(pb.ioParam.ioRefNum);
+
+ settings = tsettings;
+ return;
+
+load_defaults:
+ settings_load_defaults();
+}
+
+void
+settings_load_defaults(void)
+{
+ short n;
+
+ memset(&settings, 0, sizeof(settings));
+
+ for (n = 0; default_bookmarks[n].name[0] != '\0'; n++) {
+ strlcpy(settings.bookmark_names[n], default_bookmarks[n].name,
+ sizeof(settings.bookmark_names[0]));
+ strlcpy(settings.bookmark_uris[n], default_bookmarks[n].uri,
+ sizeof(settings.bookmark_uris[0]));
+ }
+
+ settings.version = SETTINGS_VERSION;
+}
+
+void
+settings_save(struct settings *tsettings)
+{
+ HParamBlockRec pb = { 0 };
+ char fn[] = SETTINGS_FILENAME;
+ short error, vrefnum, prefrefnum;
+ long dirid, len;
+ Handle h;
+
+ GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid);
+
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHCreate(&pb, false);
+ if (error && error != dupFNErr)
+ goto create_failed;
+ if (!error) {
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.fileParam.ioDirID = dirid;
+ if ((error = PBHGetFInfo(&pb, false)))
+ goto create_failed;
+
+ pb.fileParam.ioDirID = dirid; /* required after PBHGetFInfo */
+ pb.fileParam.ioFlFndrInfo.fdType = 'pref';
+ pb.fileParam.ioFlFndrInfo.fdCreator = SETTINGS_FILE_CREATOR;
+ if ((error = PBHSetFInfo(&pb, false)))
+ goto create_failed;
+
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioVRefNum = vrefnum;
+ PBFlushVol(&pb, false);
+ }
+
+ memset(&pb, 0, sizeof(pb));
+ pb.ioParam.ioNamePtr = (StringPtr)&fn;
+ pb.ioParam.ioVRefNum = vrefnum;
+ pb.ioParam.ioPermssn = fsWrPerm;
+ pb.fileParam.ioDirID = dirid;
+ error = PBHOpen(&pb, false);
+ if (error)
+ goto create_failed;
+
+ len = sizeof(struct settings);
+ tsettings->version = SETTINGS_VERSION;
+ FSWrite(pb.ioParam.ioRefNum, &len, tsettings);
+ FSClose(pb.ioParam.ioRefNum);
+
+ return;
+
+create_failed:
+ panic("Failed creating preferences file %s: %d", PtoCstr(fn), error);
+}
+
+bool
+bookmarks_dialog(void)
+{
+ Str255 txt;
+ WindowPtr win;
+ DialogPtr dlg;
+ struct settings tsettings;
+ struct URI *uri;
+ short name_id, uri_id, itype, n, hit, bookmarkn;
+ Handle ihandle;
+ Rect irect, dlgrect;
+
+ GetPort(&win);
+
+ if ((dlg = GetNewDialog(BOOKMARKS_DLOG_ID, NULL,
+ (WindowPtr)-1)) == NULL) {
+ warn("Can't find DLOG %d", BOOKMARKS_DLOG_ID);
+ return false;
+ }
+ center_in_screen(dlg->portRect.right - dlg->portRect.left,
+ dlg->portRect.bottom - dlg->portRect.top, true, &dlgrect);
+ MoveWindow(dlg, dlgrect.left, dlgrect.top, true);
+
+ for (n = 0; n < BOOKMARKS_COUNT; n++) {
+ if (settings.bookmark_uris[n][0] == '\0')
+ continue;
+
+ name_id = BOOKMARKS_NAME_ID_START + (n * 2);
+ GetDItem(dlg, name_id, &itype, &ihandle, &irect);
+ strlcpy((char *)&txt, settings.bookmark_names[n], sizeof(txt));
+ CtoPstr(txt);
+ SetIText(ihandle, txt);
+
+ uri_id = BOOKMARKS_URI_ID_START + (n * 2);
+ GetDItem(dlg, uri_id, &itype, &ihandle, &irect);
+ strlcpy((char *)&txt, settings.bookmark_uris[n], sizeof(txt));
+ CtoPstr(txt);
+ SetIText(ihandle, txt);
+ }
+
+ tsettings = settings;
+
+ ShowWindow(dlg);
+ for (;;) {
+modal:
+ ModalDialog(ModalDialogFilter, &hit);
+ if (hit == cancel)
+ break;
+ if (hit != ok)
+ continue;
+
+ memset(&tsettings.bookmark_names, 0,
+ sizeof(tsettings.bookmark_names));
+ memset(&tsettings.bookmark_uris, 0,
+ sizeof(tsettings.bookmark_uris));
+
+ for (n = 0, bookmarkn = 0; n < BOOKMARKS_COUNT; n++) {
+ uri_id = BOOKMARKS_URI_ID_START + (n * 2);
+ GetDItem(dlg, uri_id, &itype, &ihandle, &irect);
+ GetIText(ihandle, txt);
+
+ if (txt[0] != '\0') {
+ PtoCstr(txt);
+
+ uri = parse_uri((char *)&txt);
+ if (uri == NULL) {
+ warn("Could not parse URI \"%s\"", txt);
+ goto modal;
+ }
+ xfree(&uri);
+ strlcpy(tsettings.bookmark_uris[bookmarkn], (char *)&txt,
+ sizeof(tsettings.bookmark_uris[bookmarkn]));
+
+ name_id = BOOKMARKS_NAME_ID_START + (n * 2);
+ GetDItem(dlg, name_id, &itype, &ihandle, &irect);
+ GetIText(ihandle, txt);
+ PtoCstr(txt);
+ strlcpy(tsettings.bookmark_names[bookmarkn], (char *)&txt,
+ sizeof(tsettings.bookmark_names[bookmarkn]));
+
+ bookmarkn++;
+ }
+ }
+
+ settings_save(&tsettings);
+ menu_load_bookmarks();
+ break;
+ }
+
+ DisposDialog(dlg);
+ SetPort(win);
+}
+
+bool
+settings_edit(struct setting *defs, size_t defs_count, short dlog_id,
+ bool use_defaults)
+{
+ struct settings tsettings = settings;
+ struct setting *s;
+ DialogTHndl dlgh;
+ DialogPtr dlg;
+ Handle ihandle;
+ Rect irect;
+ size_t size, n, m, slen;
+ long lval;
+ bool bval, save;
+ short hit, itype, ret;
+ char *sdata, *strval;
+ Str255 stmp;
+
+ /* center dialog in screen */
+ dlgh = (DialogTHndl)xGetResource('DLOG', dlog_id);
+ HLock(dlgh);
+ center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left,
+ (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top,
+ true, &(**dlgh).boundsRect);
+ HUnlock(dlgh);
+
+ if ((dlg = GetNewDialog(dlog_id, nil, (WindowPtr)-1)) == NULL)
+ panic("Can't find settings DLOG %d", dlog_id);
+
+ for (n = 0; n < defs_count; n++) {
+ s = &defs[n];
+ sdata = (char *)&tsettings + s->offset;
+
+ GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
+
+ switch (s->type) {
+ case SETTING_PASSWORD:
+ PasswordDialogFieldFilterSetup(s->ditl_id,
+ (char *)s->password_storage, sizeof(s->password_storage));
+
+ strval = (use_defaults ? (char *)s->sdefault : sdata);
+ size = strlen(strval);
+ if (size >= sizeof(s->password_storage) - 1)
+ size = 0;
+
+ /* show a masked value */
+ stmp[0] = size;
+ for (m = 1; m <= size; m++)
+ stmp[m] = '•';
+ SetIText(ihandle, stmp);
+
+ /* but store the actual password */
+ memcpy(s->password_storage, strval, size);
+ s->password_storage[size] = '\0';
+ break;
+ case SETTING_STRING:
+ strval = (use_defaults ? s->sdefault : sdata);
+ memcpy(stmp, strval, sizeof(stmp));
+ stmp[sizeof(stmp) - 1] = '\0';
+ CtoPstr(stmp);
+ SetIText(ihandle, stmp);
+ break;
+ case SETTING_SHORT:
+ case SETTING_USHORT:
+ case SETTING_LONG:
+ if (use_defaults) {
+ memcpy(stmp, s->sdefault, sizeof(stmp));
+ stmp[sizeof(stmp) - 1] = '\0';
+ } else if (s->type == SETTING_SHORT) {
+ snprintf((char *)stmp, sizeof(stmp), "%d",
+ BYTES_TO_SHORT(sdata[0], sdata[1]));
+ } else if (s->type == SETTING_USHORT) {
+ snprintf((char *)stmp, sizeof(stmp), "%u",
+ BYTES_TO_SHORT(sdata[0], sdata[1]));
+ } else if (s->type == SETTING_LONG) {
+ snprintf((char *)stmp, sizeof(stmp), "%ld",
+ BYTES_TO_LONG(sdata[0], sdata[1], sdata[2], sdata[3]));
+ }
+ CtoPstr(stmp);
+ SetIText(ihandle, stmp);
+ break;
+ case SETTING_BOOL:
+ SetCtlValue(ihandle, (use_defaults ? s->sdefault[0] :
+ sdata[0]) == 1);
+ break;
+ default:
+ panic("Unknown setting type %d", s->type);
+ }
+ }
+
+ ret = false;
+ ShowWindow(dlg);
+ SelectWindow(dlg);
+
+get_input:
+ ModalDialog(PasswordDialogFieldFilter, &hit);
+ switch (hit) {
+ case -1:
+ goto settings_done;
+ case OK:
+ goto verify;
+ default:
+ GetDItem(dlg, hit, &itype, &ihandle, &irect);
+ if (itype == (ctrlItem + chkCtrl))
+ SetCtlValue(ihandle, !GetCtlValue(ihandle));
+ goto get_input;
+ }
+
+ save = false;
+verify:
+ for (n = 0; n < defs_count; n++) {
+ s = &defs[n];
+ sdata = (char *)&tsettings + s->offset;
+
+ GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
+
+ if (s->type == SETTING_PASSWORD) {
+ memcpy((char *)&stmp, s->password_storage, sizeof(stmp));
+ } else if (s->type == SETTING_BOOL) {
+ snprintf((char *)&stmp, sizeof(stmp), "%d",
+ GetCtlValue(ihandle));
+ } else {
+ GetIText(ihandle, stmp);
+ PtoCstr(stmp);
+ }
+
+ switch (s->type) {
+ case SETTING_STRING:
+ case SETTING_PASSWORD:
+ slen = strlen((char *)stmp);
+
+ if (slen == 0 && (s->flags & SETTING_FLAG_ALLOW_BLANK)) {
+ /* ok */
+ } else {
+ if (s->min && s->max && s->min == s->max &&
+ slen != s->min) {
+ warn("%s must be %ld character%s", s->name, s->min,
+ s->min == 1 ? "" : "s");
+ goto get_input;
+ }
+ if (s->min && slen < s->min) {
+ warn("%s is too short (minimum %ld)", s->name, s->min);
+ goto get_input;
+ }
+ if (s->max && slen > s->max) {
+ warn("%s is too long (maximum %ld)", s->name, s->max);
+ goto get_input;
+ }
+ }
+
+ if (save) {
+ memset(sdata, 0, s->size);
+ strlcpy(sdata, (char *)stmp, s->size);
+ }
+ break;
+ case SETTING_SHORT:
+ case SETTING_USHORT:
+ case SETTING_LONG:
+ lval = atol((char *)stmp);
+ if (lval < s->min) {
+ if (s->type == SETTING_USHORT)
+ warn("%s must be at least %ul", s->name, s->min);
+ else
+ warn("%s must be at least %ld", s->name, s->min);
+ goto get_input;
+ }
+ if (lval > s->max) {
+ if (s->type == SETTING_USHORT)
+ warn("%s must be less than %ul", s->name, s->max);
+ else
+ warn("%s must be less than %ld", s->name, s->max);
+ goto get_input;
+ }
+ if (save) {
+ if (s->type == SETTING_LONG) {
+ sdata[0] = (lval >> 24) & 0xff;
+ sdata[1] = (lval >> 16) & 0xff;
+ sdata[2] = (lval >> 8) & 0xff;
+ sdata[3] = lval & 0xff;
+ } else {
+ sdata[0] = (lval >> 8) & 0xff;
+ sdata[1] = lval & 0xff;
+ }
+ }
+ break;
+ case SETTING_BOOL:
+ if (save)
+ sdata[0] = (stmp[0] == '1');
+ break;
+ }
+ }
+
+ if (!save) {
+ save = true;
+ goto verify;
+ }
+
+ /* all validated ok and fields written */
+ settings_save(&tsettings);
+ settings = tsettings;
+ ret = true;
+
+settings_done:
+ PasswordDialogFieldFinish();
+ DisposeDialog(dlg);
+ ReleaseResource(dlgh);
+
+ return ret;
+}
--- settings.h Mon Nov 18 16:28:51 2024
+++ settings.h Mon Nov 18 16:28:51 2024
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef __SETTINGS_H__
+#define __SETTINGS_H__
+
+#include "stdint.h"
+
+#define SETTINGS_FILENAME "\pDetritus Preferences"
+#define SETTINGS_FILE_CREATOR 'DTRT'
+
+#define BOOKMARKS_DLOG_ID 129
+#define BOOKMARKS_COUNT 10
+#define BOOKMARKS_NAME_ID_START 3
+#define BOOKMARKS_URI_ID_START 4
+
+struct settings {
+ short version;
+#define SETTINGS_VERSION 1
+ char bookmark_names[BOOKMARKS_COUNT][64];
+ char bookmark_uris[BOOKMARKS_COUNT][256];
+};
+
+extern struct settings settings;
+
+void settings_load(void);
+void settings_save(struct settings *tsettings);
+bool bookmarks_dialog(void);
+
+#endif
\ No newline at end of file