/* * Copyright (c) 2022 joshua stein * * 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 #include #include "wikipedia.h" #include "browser.h" #include "focusable.h" #include "util.h" enum { CONFIG_TYPE_STRING, CONFIG_TYPE_SHORT, CONFIG_TYPE_PASSWORD, CONFIG_TYPE_BOOL }; struct config_field { char name[32]; short type; unsigned short min; unsigned short max; short ditl_id; short res_id; char sdefault[64]; char password_storage[256]; } config_fields[] = { { "Hostname", CONFIG_TYPE_STRING, 1, 255, SETTINGS_HOSTNAME_ID, STR_HOSTNAME_ID, DEFAULT_HOSTNAME }, }; MenuHandle apple_menu, file_menu, edit_menu, view_menu; bool quitting = false; void handle_menu(long menu_id); void settings_apply_defaults(void); void settings_show(void); int main(void) { Handle mbar; EventRecord event; WindowPtr event_win; GrafPtr old_port; struct focusable *focusable; short event_in, n; char key; InitGraf(&thePort); InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); InitMenus(); TEInit(); InitDialogs(0); InitCursor(); MaxApplZone(); util_init(); _atexit(focusables_atexit); mbar = GetNewMBar(MBAR_ID); SetMenuBar(mbar); apple_menu = GetMHandle(APPLE_MENU_ID); AddResMenu(apple_menu, 'DRVR'); file_menu = GetMHandle(FILE_MENU_ID); edit_menu = GetMHandle(EDIT_MENU_ID); view_menu = GetMHandle(VIEW_MENU_ID); menu_defaults(); DrawMenuBar(); settings_apply_defaults(); if (_TCPInit() != 0) panic("Failed initializing MacTCP"); browser_init(NULL); while (!quitting) { WaitNextEvent(everyEvent, &event, 0L, 0L); switch (event.what) { case nullEvent: for (n = 0; n < nfocusables; n++) { if (focusables[n]->idle) focusables[n]->idle(focusables[n], &event); } break; case keyDown: case autoKey: key = event.message & charCodeMask; if ((event.modifiers & cmdKey) != 0) handle_menu(MenuKey(key)); else if ((focusable = focusable_focused()) && focusable->key_down) focusable->key_down(focusable, &event); break; case mouseDown: event_in = FindWindow(event.where, &event_win); switch (event_in) { case inMenuBar: handle_menu(MenuSelect(event.where)); break; case inSysWindow: SystemClick(&event, event_win); break; case inDrag: if ((focusable = focusable_find(event_win)) != NULL) { if (!focusable_show(focusable)) break; DragWindow(event_win, event.where, &screenBits.bounds); } break; case inGoAway: if (TrackGoAway(event_win, event.where)) { if ((focusable = focusable_find(event_win)) != NULL) focusable_close(focusable); } break; case inContent: if ((focusable = focusable_find(event_win)) != NULL) { if (!focusable_show(focusable)) break; if (focusable->mouse_down) focusable->mouse_down(focusable, &event); } break; } break; case updateEvt: event_win = (WindowPtr)event.message; GetPort(&old_port); SetPort(event_win); BeginUpdate(event_win); focusable = focusable_find(event_win); if (focusable && focusable->update) focusable->update(focusable, &event); EndUpdate(event_win); SetPort(old_port); break; case activateEvt: break; case app4Evt: if (HiWord(event.message) & (1 << 8)) { /* multifinder suspend/resume */ switch (event.message & (1 << 0)) { case 0: /* suspend */ for (n = 0; n < nfocusables; n++) { if (focusables[n]->suspend) focusables[n]->suspend(focusables[n], &event); } break; case 1: /* resume */ for (n = 0; n < nfocusables; n++) { if (focusables[n]->resume) focusables[n]->resume(focusables[n], &event); } break; } } break; } } return 0; } void handle_menu(long menu_id) { struct focusable *focused; short menu, item; menu = HiWord(menu_id); item = LoWord(menu_id); if ((focused = focusable_focused()) && focused->menu && focused->menu(focused, menu, item)) goto handled; switch (menu) { case APPLE_MENU_ID: switch (item) { case APPLE_MENU_ABOUT_ID: about(PROGRAM_NAME); break; default: { Str255 da; GrafPtr save_port; GetItem(apple_menu, LoWord(menu_id), &da); GetPort(&save_port); OpenDeskAcc(da); SetPort(save_port); break; } } break; case FILE_MENU_ID: switch (item) { case FILE_MENU_NEW_ID: browser_init(NULL); break; case FILE_MENU_SETTINGS_ID: settings_show(); break; case FILE_MENU_QUIT_ID: if (focusables_quit()) quitting = true; break; } break; } handled: HiliteMenu(0); } void menu_defaults(void) { DisableItem(edit_menu, EDIT_MENU_CUT_ID); DisableItem(edit_menu, EDIT_MENU_COPY_ID); DisableItem(edit_menu, EDIT_MENU_PASTE_ID); DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); } void settings_apply_defaults(void) { Str255 txt; StringHandle h; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m; short hit, itype, ret; for (n = 0; n < nitems(config_fields); n++) { struct config_field *cf = &config_fields[n]; long lval; short sval; if (cf->sdefault[0] == '\0') continue; h = GetString(cf->res_id); if (h != NULL) { HLock(h); if ((*h)[0] > 0) continue; RmveResource(h); ReleaseResource(h); } size = strlen(cf->sdefault); h = (StringHandle)xNewHandle(size + 1); HLock(h); strlcpy(((char *)*h) + 1, cf->sdefault, size + 1); (*h)[0] = size; strlcpy((char *)txt, cf->name, sizeof(txt)); CtoPstr(txt); AddResource(h, 'STR ', cf->res_id, txt); ReleaseResource(h); } } void settings_show(void) { Str255 txt, hostname; GrafPtr old_port; StringHandle h; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m; short hit, itype, ret; /* center dialog in screen */ dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_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(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL) panic("Can't find connection 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); if (cf->type == CONFIG_TYPE_PASSWORD) { PasswordDialogFieldFilterSetup(cf->ditl_id, cf->password_storage, sizeof(cf->password_storage)); } if (cf->res_id && (h = GetString(cf->res_id))) { HLock(h); if (cf->type == CONFIG_TYPE_PASSWORD) { size = (*h)[0]; if (size >= sizeof(cf->password_storage) - 1) size = 0; for (m = 0; m < size; m++) cf->password_storage[m] = '¥'; cf->password_storage[m] = '\0'; CtoPstr(cf->password_storage); SetIText(ihandle, cf->password_storage); memcpy(cf->password_storage, *h, size); cf->password_storage[size] = '\0'; PtoCstr(cf->password_storage); } else if (cf->type == CONFIG_TYPE_BOOL) { SetCtlValue(ihandle, ((*h)[1] == '1')); } else { SetIText(ihandle, *h); } HUnlock(h); ReleaseResource(h); } else if (cf->type == CONFIG_TYPE_BOOL) { SetCtlValue(ihandle, (cf->sdefault[0] == '1')); } else if (cf->sdefault[0] != '\0') { strlcpy((char *)&txt, cf->sdefault, sizeof(txt)); CtoPstr(txt); SetIText(ihandle, txt); } } ShowWindow(dlg); get_input: /* outline ok button */ GetPort(&old_port); SetPort(dlg); GetDItem(dlg, ok, &itype, &ihandle, &irect); PenSize(3, 3); InsetRect(&irect, -4, -4); FrameRoundRect(&irect, 16, 16); PenNormal(); ModalDialog(PasswordDialogFieldFilter, &hit); SetPort(old_port); switch (hit) { case -1: case cancel: DisposeDialog(dlg); ReleaseResource(dlgh); return; case OK: goto verify; default: GetDItem(dlg, hit, &itype, &ihandle, &irect); if (itype == (ctrlItem + chkCtrl)) /* flip checkboxes */ SetCtlValue(ihandle, 1 - GetCtlValue(ihandle)); goto get_input; } verify: 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); if (cf->type == CONFIG_TYPE_PASSWORD) { memcpy((char *)&txt, cf->password_storage, sizeof(txt)); } else if (cf->type == CONFIG_TYPE_BOOL) { snprintf((char *)&txt, sizeof(txt), "%d", GetCtlValue(ihandle)); } else { GetIText(ihandle, txt); PtoCstr(txt); } switch (cf->type) { case CONFIG_TYPE_STRING: case CONFIG_TYPE_PASSWORD: if (cf->min && strlen((char *)txt) < cf->min) { warn("%s is too short (minimum %d)", cf->name, cf->min); goto get_input; } if (cf->max && strlen((char *)txt) > cf->max) { warn("%s is too long (maximum %d)", cf->name, cf->max); goto get_input; } break; case CONFIG_TYPE_SHORT: lval = atol((char *)txt); if (lval < cf->min) { warn("%s must be at least %d", cf->name, cf->min); goto get_input; } if (lval > cf->max) { warn("%s must be less than %d", cf->name, cf->max); goto get_input; } break; case CONFIG_TYPE_BOOL: break; } switch (cf->ditl_id) { case SETTINGS_HOSTNAME_ID: strlcpy((char *)&hostname, (char *)txt, sizeof(hostname)); break; } if ((h = GetString(cf->res_id)) != NULL) { RmveResource(h); ReleaseResource(h); } size = strlen((char *)txt) + 1; h = (StringHandle)xNewHandle(size); strlcpy((char *)*h, (char *)txt, size); CtoPstr(*h); strlcpy((char *)txt, cf->name, sizeof(txt)); CtoPstr(txt); AddResource(h, 'STR ', cf->res_id, txt); ReleaseResource(h); } PasswordDialogFieldFinish(); DisposeDialog(dlg); ReleaseResource(dlgh); }