Download
jcs
/wikipedia
/main.c
(View History)
jcs main+browser: Make cmd+click on links open them in a new window | Latest amendment: 54 on 2023-10-30 |
1 | /* |
2 | * Copyright (c) 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 "wikipedia.h" |
21 | #include "browser.h" |
22 | #include "focusable.h" |
23 | #include "util.h" |
24 | |
25 | enum { |
26 | CONFIG_TYPE_STRING, |
27 | CONFIG_TYPE_SHORT, |
28 | CONFIG_TYPE_PASSWORD, |
29 | CONFIG_TYPE_BOOL |
30 | }; |
31 | |
32 | struct config_field { |
33 | char name[32]; |
34 | short type; |
35 | unsigned short min; |
36 | unsigned short max; |
37 | short ditl_id; |
38 | short res_id; |
39 | char sdefault[64]; |
40 | char password_storage[256]; |
41 | } config_fields[] = { |
42 | { "Hostname", CONFIG_TYPE_STRING, 1, 255, |
43 | SETTINGS_HOSTNAME_ID, STR_HOSTNAME_ID, DEFAULT_HOSTNAME }, |
44 | }; |
45 | |
46 | MenuHandle apple_menu, file_menu, edit_menu, view_menu; |
47 | bool quitting = false; |
48 | |
49 | void handle_menu(long menu_id); |
50 | void settings_apply_defaults(void); |
51 | void settings_show(void); |
52 | |
53 | int |
54 | main(void) |
55 | { |
56 | Handle mbar; |
57 | EventRecord event; |
58 | WindowPtr event_win; |
59 | GrafPtr old_port; |
60 | struct focusable *focusable; |
61 | short event_in, n; |
62 | char key; |
63 | |
64 | InitGraf(&thePort); |
65 | InitFonts(); |
66 | FlushEvents(everyEvent, 0); |
67 | InitWindows(); |
68 | InitMenus(); |
69 | TEInit(); |
70 | InitDialogs(0); |
71 | InitCursor(); |
72 | MaxApplZone(); |
73 | |
74 | util_init(); |
75 | _atexit(focusables_atexit); |
76 | |
77 | mbar = GetNewMBar(MBAR_ID); |
78 | SetMenuBar(mbar); |
79 | apple_menu = GetMHandle(APPLE_MENU_ID); |
80 | AddResMenu(apple_menu, 'DRVR'); |
81 | file_menu = GetMHandle(FILE_MENU_ID); |
82 | edit_menu = GetMHandle(EDIT_MENU_ID); |
83 | view_menu = GetMHandle(VIEW_MENU_ID); |
84 | menu_defaults(); |
85 | DrawMenuBar(); |
86 | |
87 | settings_apply_defaults(); |
88 | |
89 | if (_TCPInit() != 0) |
90 | panic("Failed initializing MacTCP"); |
91 | |
92 | browser_init(NULL); |
93 | |
94 | while (!quitting) { |
95 | WaitNextEvent(everyEvent, &event, 0L, 0L); |
96 | |
97 | switch (event.what) { |
98 | case nullEvent: |
99 | for (n = 0; n < nfocusables; n++) { |
100 | if (focusables[n]->idle) |
101 | focusables[n]->idle(focusables[n], &event); |
102 | } |
103 | break; |
104 | case keyDown: |
105 | case autoKey: |
106 | key = event.message & charCodeMask; |
107 | if ((event.modifiers & cmdKey) != 0) |
108 | handle_menu(MenuKey(key)); |
109 | else if ((focusable = focusable_focused()) && |
110 | focusable->key_down) |
111 | focusable->key_down(focusable, &event); |
112 | break; |
113 | case mouseDown: |
114 | event_in = FindWindow(event.where, &event_win); |
115 | |
116 | switch (event_in) { |
117 | case inMenuBar: |
118 | handle_menu(MenuSelect(event.where)); |
119 | break; |
120 | case inSysWindow: |
121 | SystemClick(&event, event_win); |
122 | break; |
123 | case inDrag: |
124 | if ((focusable = focusable_find(event_win)) != NULL) { |
125 | if (!focusable_show(focusable)) |
126 | break; |
127 | |
128 | DragWindow(event_win, event.where, &screenBits.bounds); |
129 | } |
130 | break; |
131 | case inGoAway: |
132 | if (TrackGoAway(event_win, event.where)) { |
133 | if ((focusable = focusable_find(event_win)) != NULL) |
134 | focusable_close(focusable); |
135 | } |
136 | break; |
137 | case inContent: |
138 | if ((focusable = focusable_find(event_win)) != NULL) { |
139 | if (!focusable_show(focusable)) |
140 | break; |
141 | if (focusable->mouse_down) |
142 | focusable->mouse_down(focusable, &event); |
143 | } |
144 | break; |
145 | } |
146 | break; |
147 | case updateEvt: |
148 | event_win = (WindowPtr)event.message; |
149 | GetPort(&old_port); |
150 | SetPort(event_win); |
151 | BeginUpdate(event_win); |
152 | focusable = focusable_find(event_win); |
153 | if (focusable && focusable->update) |
154 | focusable->update(focusable, &event); |
155 | EndUpdate(event_win); |
156 | SetPort(old_port); |
157 | break; |
158 | case activateEvt: |
159 | break; |
160 | case app4Evt: |
161 | if (HiWord(event.message) & (1 << 8)) { |
162 | /* multifinder suspend/resume */ |
163 | switch (event.message & (1 << 0)) { |
164 | case 0: |
165 | /* suspend */ |
166 | for (n = 0; n < nfocusables; n++) { |
167 | if (focusables[n]->suspend) |
168 | focusables[n]->suspend(focusables[n], &event); |
169 | } |
170 | break; |
171 | case 1: |
172 | /* resume */ |
173 | for (n = 0; n < nfocusables; n++) { |
174 | if (focusables[n]->resume) |
175 | focusables[n]->resume(focusables[n], &event); |
176 | } |
177 | break; |
178 | } |
179 | } |
180 | break; |
181 | } |
182 | } |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | void |
188 | handle_menu(long menu_id) |
189 | { |
190 | struct focusable *focused; |
191 | short menu, item; |
192 | |
193 | menu = HiWord(menu_id); |
194 | item = LoWord(menu_id); |
195 | |
196 | if ((focused = focusable_focused()) && focused->menu && |
197 | focused->menu(focused, menu, item)) |
198 | goto handled; |
199 | |
200 | switch (menu) { |
201 | case APPLE_MENU_ID: |
202 | switch (item) { |
203 | case APPLE_MENU_ABOUT_ID: |
204 | about(PROGRAM_NAME); |
205 | break; |
206 | default: { |
207 | Str255 da; |
208 | GrafPtr save_port; |
209 | |
210 | GetItem(apple_menu, LoWord(menu_id), &da); |
211 | GetPort(&save_port); |
212 | OpenDeskAcc(da); |
213 | SetPort(save_port); |
214 | break; |
215 | } |
216 | } |
217 | break; |
218 | case FILE_MENU_ID: |
219 | switch (item) { |
220 | case FILE_MENU_NEW_ID: |
221 | browser_init(NULL); |
222 | break; |
223 | case FILE_MENU_SETTINGS_ID: |
224 | settings_show(); |
225 | break; |
226 | case FILE_MENU_QUIT_ID: |
227 | if (focusables_quit()) |
228 | quitting = true; |
229 | break; |
230 | } |
231 | break; |
232 | } |
233 | |
234 | handled: |
235 | HiliteMenu(0); |
236 | } |
237 | |
238 | void |
239 | menu_defaults(void) |
240 | { |
241 | DisableItem(edit_menu, EDIT_MENU_CUT_ID); |
242 | DisableItem(edit_menu, EDIT_MENU_COPY_ID); |
243 | DisableItem(edit_menu, EDIT_MENU_PASTE_ID); |
244 | DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); |
245 | } |
246 | |
247 | void |
248 | settings_apply_defaults(void) |
249 | { |
250 | Str255 txt; |
251 | StringHandle h; |
252 | DialogTHndl dlgh; |
253 | DialogPtr dlg; |
254 | Handle ihandle; |
255 | Rect irect; |
256 | size_t size, n, m; |
257 | short hit, itype, ret; |
258 | |
259 | for (n = 0; n < nitems(config_fields); n++) { |
260 | struct config_field *cf = &config_fields[n]; |
261 | long lval; |
262 | short sval; |
263 | |
264 | if (cf->sdefault[0] == '\0') |
265 | continue; |
266 | |
267 | h = GetString(cf->res_id); |
268 | if (h != NULL) { |
269 | HLock(h); |
270 | if ((*h)[0] > 0) |
271 | continue; |
272 | |
273 | RmveResource(h); |
274 | ReleaseResource(h); |
275 | } |
276 | |
277 | size = strlen(cf->sdefault); |
278 | h = (StringHandle)xNewHandle(size + 1); |
279 | HLock(h); |
280 | strlcpy(((char *)*h) + 1, cf->sdefault, size + 1); |
281 | (*h)[0] = size; |
282 | strlcpy((char *)txt, cf->name, sizeof(txt)); |
283 | CtoPstr(txt); |
284 | AddResource(h, 'STR ', cf->res_id, txt); |
285 | ReleaseResource(h); |
286 | } |
287 | } |
288 | |
289 | void |
290 | settings_show(void) |
291 | { |
292 | Str255 txt, hostname; |
293 | GrafPtr old_port; |
294 | StringHandle h; |
295 | DialogTHndl dlgh; |
296 | DialogPtr dlg; |
297 | Handle ihandle; |
298 | Rect irect; |
299 | size_t size, n, m; |
300 | short hit, itype, ret; |
301 | |
302 | /* center dialog in screen */ |
303 | dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_DLOG_ID); |
304 | HLock(dlgh); |
305 | center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, |
306 | (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, |
307 | true, &(**dlgh).boundsRect); |
308 | HUnlock(dlgh); |
309 | |
310 | if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL) |
311 | panic("Can't find connection DLOG %d", SETTINGS_DLOG_ID); |
312 | |
313 | for (n = 0; n < nitems(config_fields); n++) { |
314 | struct config_field *cf = &config_fields[n]; |
315 | short sval; |
316 | |
317 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
318 | |
319 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
320 | PasswordDialogFieldFilterSetup(cf->ditl_id, |
321 | cf->password_storage, sizeof(cf->password_storage)); |
322 | } |
323 | |
324 | if (cf->res_id && (h = GetString(cf->res_id))) { |
325 | HLock(h); |
326 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
327 | size = (*h)[0]; |
328 | if (size >= sizeof(cf->password_storage) - 1) |
329 | size = 0; |
330 | for (m = 0; m < size; m++) |
331 | cf->password_storage[m] = '•'; |
332 | cf->password_storage[m] = '\0'; |
333 | CtoPstr(cf->password_storage); |
334 | SetIText(ihandle, cf->password_storage); |
335 | memcpy(cf->password_storage, *h, size); |
336 | cf->password_storage[size] = '\0'; |
337 | PtoCstr(cf->password_storage); |
338 | } else if (cf->type == CONFIG_TYPE_BOOL) { |
339 | SetCtlValue(ihandle, ((*h)[1] == '1')); |
340 | } else { |
341 | SetIText(ihandle, *h); |
342 | } |
343 | HUnlock(h); |
344 | ReleaseResource(h); |
345 | } else if (cf->type == CONFIG_TYPE_BOOL) { |
346 | SetCtlValue(ihandle, (cf->sdefault[0] == '1')); |
347 | } else if (cf->sdefault[0] != '\0') { |
348 | strlcpy((char *)&txt, cf->sdefault, sizeof(txt)); |
349 | CtoPstr(txt); |
350 | SetIText(ihandle, txt); |
351 | } |
352 | } |
353 | |
354 | ShowWindow(dlg); |
355 | |
356 | get_input: |
357 | /* outline ok button */ |
358 | GetPort(&old_port); |
359 | SetPort(dlg); |
360 | GetDItem(dlg, ok, &itype, &ihandle, &irect); |
361 | PenSize(3, 3); |
362 | InsetRect(&irect, -4, -4); |
363 | FrameRoundRect(&irect, 16, 16); |
364 | PenNormal(); |
365 | |
366 | ModalDialog(PasswordDialogFieldFilter, &hit); |
367 | SetPort(old_port); |
368 | |
369 | switch (hit) { |
370 | case -1: |
371 | case cancel: |
372 | DisposeDialog(dlg); |
373 | ReleaseResource(dlgh); |
374 | return; |
375 | case OK: |
376 | goto verify; |
377 | default: |
378 | GetDItem(dlg, hit, &itype, &ihandle, &irect); |
379 | if (itype == (ctrlItem + chkCtrl)) |
380 | /* flip checkboxes */ |
381 | SetCtlValue(ihandle, 1 - GetCtlValue(ihandle)); |
382 | goto get_input; |
383 | } |
384 | |
385 | verify: |
386 | for (n = 0; n < nitems(config_fields); n++) { |
387 | struct config_field *cf = &config_fields[n]; |
388 | long lval; |
389 | short sval; |
390 | |
391 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
392 | |
393 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
394 | memcpy((char *)&txt, cf->password_storage, sizeof(txt)); |
395 | } else if (cf->type == CONFIG_TYPE_BOOL) { |
396 | snprintf((char *)&txt, sizeof(txt), "%d", |
397 | GetCtlValue(ihandle)); |
398 | } else { |
399 | GetIText(ihandle, txt); |
400 | PtoCstr(txt); |
401 | } |
402 | |
403 | switch (cf->type) { |
404 | case CONFIG_TYPE_STRING: |
405 | case CONFIG_TYPE_PASSWORD: |
406 | if (cf->min && strlen((char *)txt) < cf->min) { |
407 | warn("%s is too short (minimum %d)", cf->name, cf->min); |
408 | goto get_input; |
409 | } |
410 | if (cf->max && strlen((char *)txt) > cf->max) { |
411 | warn("%s is too long (maximum %d)", cf->name, cf->max); |
412 | goto get_input; |
413 | } |
414 | break; |
415 | case CONFIG_TYPE_SHORT: |
416 | lval = atol((char *)txt); |
417 | if (lval < cf->min) { |
418 | warn("%s must be at least %d", cf->name, cf->min); |
419 | goto get_input; |
420 | } |
421 | if (lval > cf->max) { |
422 | warn("%s must be less than %d", cf->name, cf->max); |
423 | goto get_input; |
424 | } |
425 | break; |
426 | case CONFIG_TYPE_BOOL: |
427 | break; |
428 | } |
429 | |
430 | switch (cf->ditl_id) { |
431 | case SETTINGS_HOSTNAME_ID: |
432 | strlcpy((char *)&hostname, (char *)txt, sizeof(hostname)); |
433 | break; |
434 | } |
435 | |
436 | if ((h = GetString(cf->res_id)) != NULL) { |
437 | RmveResource(h); |
438 | ReleaseResource(h); |
439 | } |
440 | size = strlen((char *)txt) + 1; |
441 | h = (StringHandle)xNewHandle(size); |
442 | strlcpy((char *)*h, (char *)txt, size); |
443 | CtoPstr(*h); |
444 | strlcpy((char *)txt, cf->name, sizeof(txt)); |
445 | CtoPstr(txt); |
446 | AddResource(h, 'STR ', cf->res_id, txt); |
447 | ReleaseResource(h); |
448 | } |
449 | |
450 | PasswordDialogFieldFinish(); |
451 | DisposeDialog(dlg); |
452 | ReleaseResource(dlgh); |
453 | } |