Download
jcs
/wallops
/main.c
(View History)
jcs main: Center connection dialog on the screen | Latest amendment: 36 on 2022-09-06 |
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 <string.h> |
18 | #include "chatter.h" |
19 | #include "irc.h" |
20 | #include "tcp.h" |
21 | #include "util.h" |
22 | |
23 | MenuHandle apple_menu, file_menu; |
24 | struct focusable **focusables = NULL; |
25 | short nfocusables = 0; |
26 | NMRec notification = { 0 }; |
27 | |
28 | enum { |
29 | CONFIG_TYPE_STRING, |
30 | CONFIG_TYPE_SHORT, |
31 | CONFIG_TYPE_PASSWORD |
32 | }; |
33 | |
34 | struct config_field { |
35 | char name[32]; |
36 | short type; |
37 | unsigned short min; |
38 | unsigned short max; |
39 | short ditl_id; |
40 | short res_id; |
41 | char sdefault[32]; |
42 | char password_storage[256]; |
43 | } config_fields[] = { |
44 | { "Server Name", CONFIG_TYPE_STRING, 1, 0, |
45 | CONNECT_SERVER_ID, STR_SERVER_ID, DEFAULT_SERVER_NAME }, |
46 | { "Server Port", CONFIG_TYPE_SHORT, 1, 65535, |
47 | CONNECT_PORT_ID, STR_PORT_ID, DEFAULT_PORT }, |
48 | { "Server Password", CONFIG_TYPE_PASSWORD, 0, 0, |
49 | CONNECT_PASSWORD_ID, STR_PASSWORD_ID, "" }, |
50 | { "Nickname", CONFIG_TYPE_STRING, 1, 0, |
51 | CONNECT_NICK_ID, STR_NICK_ID, "" }, |
52 | { "Ident", CONFIG_TYPE_STRING, 1, 0, |
53 | CONNECT_IDENT_ID, STR_IDENT_ID, DEFAULT_IDENT }, |
54 | { "Realname", CONFIG_TYPE_STRING, 1, 0, |
55 | CONNECT_REALNAME_ID, STR_REALNAME_ID, DEFAULT_REALNAME }, |
56 | { "Channel", CONFIG_TYPE_STRING, 0, 0, |
57 | CONNECT_CHANNEL_ID, STR_CHANNEL_ID, DEFAULT_CHANNEL }, |
58 | }; |
59 | |
60 | void update_menu(void); |
61 | void handle_exit(void); |
62 | bool handle_menu(long menu_id); |
63 | short show_connect_dialog(void); |
64 | |
65 | int |
66 | main(void) |
67 | { |
68 | Handle mbar; |
69 | EventRecord event; |
70 | WindowPtr event_win; |
71 | GrafPtr old_port; |
72 | AppFile finder_file; |
73 | struct focusable *found_focusable; |
74 | short event_in, n; |
75 | char key; |
76 | long wait_ticks; |
77 | short wait_type; |
78 | |
79 | SetApplLimit(GetApplLimit() - (1024 * 8)); |
80 | |
81 | InitGraf(&thePort); |
82 | InitFonts(); |
83 | FlushEvents(everyEvent, 0); |
84 | InitWindows(); |
85 | InitMenus(); |
86 | TEInit(); |
87 | InitDialogs(0); |
88 | InitCursor(); |
89 | MaxApplZone(); |
90 | |
91 | _atexit(handle_exit); |
92 | |
93 | if (!(mbar = GetNewMBar(MBAR_ID))) |
94 | panic("no mbar"); |
95 | SetMenuBar(mbar); |
96 | if (!(apple_menu = GetMHandle(APPLE_MENU_ID))) |
97 | panic("no apple menu"); |
98 | AddResMenu(apple_menu, 'DRVR'); |
99 | if (!(file_menu = GetMHandle(FILE_MENU_ID))) |
100 | panic("no file menu"); |
101 | update_menu(); |
102 | DrawMenuBar(); |
103 | |
104 | show_connect_dialog(); |
105 | |
106 | for (;;) { |
107 | wait_type = WAIT_TYPE_BACKGROUND; |
108 | for (n = 0; n < nfocusables; n++) { |
109 | if (focusables[n]->wait_type) |
110 | wait_type |= focusables[n]->wait_type(focusables[n]); |
111 | else if (focusables[n]->visible) |
112 | wait_type |= WAIT_TYPE_FOREGROUND; |
113 | } |
114 | |
115 | if (wait_type & WAIT_TYPE_URGENT) |
116 | wait_ticks = 0; |
117 | else if (wait_type & WAIT_TYPE_FOREGROUND) |
118 | wait_ticks = 5L; |
119 | else |
120 | wait_ticks = 60L; |
121 | |
122 | WaitNextEvent(everyEvent, &event, wait_ticks, 0L); |
123 | |
124 | if (event.what != nullEvent) { |
125 | event_in = FindWindow(event.where, &event_win); |
126 | found_focusable = NULL; |
127 | for (n = 0; n < nfocusables; n++) { |
128 | if (focusables[n]->win == event_win) { |
129 | found_focusable = focusables[n]; |
130 | break; |
131 | } |
132 | } |
133 | } |
134 | |
135 | switch (event.what) { |
136 | case nullEvent: |
137 | for (n = 0; n < nfocusables; n++) { |
138 | if (focusables[n]->idle) |
139 | focusables[n]->idle(focusables[n], &event); |
140 | } |
141 | break; |
142 | case keyDown: |
143 | case autoKey: |
144 | key = event.message & charCodeMask; |
145 | if ((event.modifiers & cmdKey) != 0 && |
146 | handle_menu(MenuKey(key)) == true) |
147 | break; |
148 | if (nfocusables && focusables[0]->visible && |
149 | focusables[0]->key_down) |
150 | focusables[0]->key_down(focusables[0], &event); |
151 | break; |
152 | case mouseDown: |
153 | switch (event_in) { |
154 | case inMenuBar: |
155 | handle_menu(MenuSelect(event.where)); |
156 | break; |
157 | case inSysWindow: |
158 | SystemClick(&event, event_win); |
159 | break; |
160 | case inDrag: |
161 | DragWindow(event_win, event.where, &screenBits.bounds); |
162 | break; |
163 | case inGoAway: |
164 | if (TrackGoAway(event_win, event.where) && |
165 | found_focusable && found_focusable->close) |
166 | found_focusable->close(found_focusable, &event); |
167 | break; |
168 | case inContent: |
169 | if (event_win != FrontWindow()) { |
170 | if (found_focusable) |
171 | show_focusable(found_focusable); |
172 | } |
173 | if (found_focusable && found_focusable->mouse_down) |
174 | found_focusable->mouse_down(found_focusable, &event); |
175 | break; |
176 | } |
177 | break; |
178 | case updateEvt: |
179 | case activateEvt: |
180 | event_win = (WindowPtr)event.message; |
181 | |
182 | if (event.what == updateEvt) { |
183 | GetPort(&old_port); |
184 | SetPort(event_win); |
185 | BeginUpdate(event_win); |
186 | } |
187 | |
188 | if (found_focusable && found_focusable->update) |
189 | found_focusable->update(found_focusable, &event); |
190 | |
191 | if (event.what == updateEvt) { |
192 | EndUpdate(event_win); |
193 | SetPort(old_port); |
194 | } |
195 | break; |
196 | case app4Evt: |
197 | if (HiWord(event.message) & (1 << 8)) { |
198 | /* multifinder suspend/resume */ |
199 | switch (event.message & (1 << 0)) { |
200 | case 0: |
201 | /* suspend */ |
202 | for (n = 0; n < nfocusables; n++) { |
203 | if (focusables[n]->suspend) |
204 | focusables[n]->suspend(focusables[n], &event); |
205 | } |
206 | break; |
207 | case 1: |
208 | /* resume */ |
209 | for (n = 0; n < nfocusables; n++) { |
210 | if (focusables[n]->resume) |
211 | focusables[n]->resume(focusables[n], &event); |
212 | } |
213 | break; |
214 | } |
215 | } |
216 | break; |
217 | } |
218 | } |
219 | |
220 | return 0; |
221 | } |
222 | |
223 | short |
224 | show_connect_dialog(void) |
225 | { |
226 | Str255 txt, server, ports, nick, ident, realname, channel, password; |
227 | StringHandle h; |
228 | DialogTHndl dlgh; |
229 | DialogPtr dlg; |
230 | Handle ihandle; |
231 | Rect irect; |
232 | size_t size, n, m; |
233 | long port; |
234 | short hit, itype, ret; |
235 | |
236 | /* center dialog in screen */ |
237 | dlgh = (DialogTHndl)xGetResource('DLOG', CONNECT_DLOG_ID); |
238 | HLock(dlgh); |
239 | center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, |
240 | (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, |
241 | true, &(**dlgh).boundsRect); |
242 | HUnlock(dlgh); |
243 | |
244 | if ((dlg = GetNewDialog(CONNECT_DLOG_ID, nil, (WindowPtr)-1)) == NULL) |
245 | panic("Can't find connection DLOG %d", CONNECT_DLOG_ID); |
246 | |
247 | for (n = 0; n < nitems(config_fields); n++) { |
248 | struct config_field *cf = &config_fields[n]; |
249 | short sval; |
250 | |
251 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
252 | |
253 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
254 | PasswordDialogFieldFilterSetup(cf->ditl_id, |
255 | cf->password_storage, sizeof(cf->password_storage)); |
256 | } |
257 | |
258 | if (cf->res_id && (h = GetString(cf->res_id))) { |
259 | HLock(h); |
260 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
261 | size = (*h)[0]; |
262 | for (m = 0; m < size; m++) |
263 | cf->password_storage[m] = '•'; |
264 | cf->password_storage[m] = '\0'; |
265 | CtoPstr(cf->password_storage); |
266 | SetIText(ihandle, cf->password_storage); |
267 | strlcpy(cf->password_storage, PtoCstr(*h), |
268 | sizeof(cf->password_storage)); |
269 | } else { |
270 | SetIText(ihandle, *h); |
271 | } |
272 | HUnlock(h); |
273 | ReleaseResource(h); |
274 | } else if (cf->sdefault[0] != '\0') { |
275 | strlcpy((char *)&txt, cf->sdefault, sizeof(txt)); |
276 | CtoPstr(txt); |
277 | SetIText(ihandle, txt); |
278 | } |
279 | } |
280 | |
281 | ShowWindow(dlg); |
282 | |
283 | get_input: |
284 | ModalDialog(PasswordDialogFieldFilter, &hit); |
285 | switch (hit) { |
286 | case -1: |
287 | DisposeDialog(dlg); |
288 | ReleaseResource(dlgh); |
289 | return; |
290 | case OK: |
291 | goto verify; |
292 | default: |
293 | goto get_input; |
294 | } |
295 | |
296 | verify: |
297 | for (n = 0; n < nitems(config_fields); n++) { |
298 | struct config_field *cf = &config_fields[n]; |
299 | long lval; |
300 | short sval; |
301 | |
302 | if (cf->type == CONFIG_TYPE_PASSWORD) { |
303 | memcpy((char *)&txt, cf->password_storage, sizeof(txt)); |
304 | } else { |
305 | GetDItem(dlg, cf->ditl_id, &itype, &ihandle, &irect); |
306 | GetIText(ihandle, txt); |
307 | PtoCstr(txt); |
308 | } |
309 | |
310 | switch (cf->type) { |
311 | case CONFIG_TYPE_STRING: |
312 | case CONFIG_TYPE_PASSWORD: |
313 | if (cf->min && strlen((char *)txt) < cf->min) { |
314 | warn("%s is too short (minimum %d)", cf->name, cf->min); |
315 | goto get_input; |
316 | } |
317 | if (cf->max && strlen((char *)txt) > cf->max) { |
318 | warn("%s is too long (maximum %d)", cf->name, cf->max); |
319 | goto get_input; |
320 | } |
321 | break; |
322 | case CONFIG_TYPE_SHORT: |
323 | lval = atol((char *)txt); |
324 | if (lval < cf->min) { |
325 | warn("%s must be at least %d", cf->name, cf->min); |
326 | goto get_input; |
327 | } |
328 | if (lval > cf->max) { |
329 | warn("%s must be less than %d", cf->name, cf->max); |
330 | goto get_input; |
331 | } |
332 | break; |
333 | } |
334 | |
335 | switch (cf->ditl_id) { |
336 | case CONNECT_SERVER_ID: |
337 | strlcpy((char *)&server, (char *)txt, sizeof(server)); |
338 | break; |
339 | case CONNECT_PORT_ID: |
340 | port = atol((char *)txt); |
341 | break; |
342 | case CONNECT_PASSWORD_ID: |
343 | strlcpy((char *)&password, (char *)txt, sizeof(password)); |
344 | break; |
345 | case CONNECT_NICK_ID: |
346 | strlcpy((char *)&nick, (char *)txt, sizeof(nick)); |
347 | break; |
348 | case CONNECT_IDENT_ID: |
349 | strlcpy((char *)&ident, (char *)txt, sizeof(ident)); |
350 | break; |
351 | case CONNECT_REALNAME_ID: |
352 | strlcpy((char *)&realname, (char *)txt, sizeof(realname)); |
353 | break; |
354 | case CONNECT_CHANNEL_ID: |
355 | strlcpy((char *)&channel, (char *)txt, sizeof(channel)); |
356 | break; |
357 | } |
358 | |
359 | if ((h = GetString(cf->res_id)) != NULL) { |
360 | RmveResource(h); |
361 | ReleaseResource(h); |
362 | } |
363 | size = strlen((char *)txt) + 1; |
364 | h = (StringHandle)xNewHandle(size); |
365 | strlcpy((char *)*h, (char *)txt, size); |
366 | CtoPstr(*h); |
367 | strlcpy((char *)txt, cf->name, sizeof(txt)); |
368 | CtoPstr(txt); |
369 | AddResource(h, 'STR ', cf->res_id, txt); |
370 | ReleaseResource(h); |
371 | } |
372 | |
373 | PasswordDialogFieldFinish(); |
374 | DisposeDialog(dlg); |
375 | ReleaseResource(dlgh); |
376 | |
377 | chatter_init((char *)&server, port, (char *)&password, (char *)&nick, |
378 | (char *)&ident, (char *)&realname, (char *)&channel); |
379 | } |
380 | |
381 | bool |
382 | handle_menu(long menu_id) |
383 | { |
384 | size_t n; |
385 | bool ret = false; |
386 | bool quit = true; |
387 | |
388 | switch (HiWord(menu_id)) { |
389 | case APPLE_MENU_ID: |
390 | switch (LoWord(menu_id)) { |
391 | case APPLE_MENU_ABOUT_ID: { |
392 | char vers_s[255]; |
393 | |
394 | sprintf(vers_s, "%s %s", PROGRAM_NAME, get_version(true)); |
395 | note("%s", vers_s); |
396 | |
397 | ret = true; |
398 | break; |
399 | } |
400 | default: { |
401 | Str255 da; |
402 | GrafPtr save_port; |
403 | |
404 | GetItem(apple_menu, LoWord(menu_id), &da); |
405 | GetPort(&save_port); |
406 | OpenDeskAcc(da); |
407 | SetPort(save_port); |
408 | |
409 | ret = true; |
410 | break; |
411 | } |
412 | } |
413 | break; |
414 | case FILE_MENU_ID: |
415 | switch (LoWord(menu_id)) { |
416 | case FILE_MENU_CONNECT_ID: |
417 | show_connect_dialog(); |
418 | ret = true; |
419 | break; |
420 | case FILE_MENU_QUIT_ID: |
421 | ret = true; |
422 | quit = true; |
423 | for (n = 0; n < nfocusables; n++) { |
424 | if (focusables[n] && focusables[n]->quit && |
425 | !focusables[n]->quit(focusables[n])) { |
426 | quit = false; |
427 | break; |
428 | } |
429 | } |
430 | if (quit) |
431 | ExitToShell(); |
432 | break; |
433 | } |
434 | break; |
435 | default: |
436 | if (nfocusables && focusables[0]->visible && focusables[0]->menu) |
437 | ret = focusables[0]->menu(focusables[0], HiWord(menu_id), |
438 | LoWord(menu_id)); |
439 | } |
440 | |
441 | HiliteMenu(0); |
442 | return ret; |
443 | } |
444 | |
445 | void |
446 | update_menu(void) |
447 | { |
448 | } |
449 | |
450 | void |
451 | handle_exit(void) |
452 | { |
453 | short n; |
454 | |
455 | for (n = 0; n < nfocusables; n++) { |
456 | if (focusables[n]->atexit) |
457 | focusables[n]->atexit(focusables[n]); |
458 | } |
459 | } |
460 | |
461 | void |
462 | add_focusable(struct focusable *focusable) |
463 | { |
464 | nfocusables++; |
465 | focusables = xreallocarray(focusables, sizeof(Ptr), nfocusables); |
466 | |
467 | if (nfocusables > 1) |
468 | memmove(focusables + sizeof(Ptr), focusables, |
469 | sizeof(Ptr) * (nfocusables - 1)); |
470 | |
471 | focusables[0] = focusable; |
472 | |
473 | show_focusable(focusable); |
474 | } |
475 | |
476 | void |
477 | show_focusable(struct focusable *focusable) |
478 | { |
479 | struct focusable *last, *tmp; |
480 | short n; |
481 | |
482 | if (nfocusables > 1 && focusables[0] != focusable) { |
483 | last = focusables[0]; |
484 | focusables[0] = focusable; |
485 | for (n = 1; n < nfocusables; n++) { |
486 | tmp = focusables[n]; |
487 | focusables[n] = last; |
488 | last = tmp; |
489 | if (last == focusable) |
490 | break; |
491 | } |
492 | } |
493 | |
494 | focusable->visible = true; |
495 | ShowWindow(focusable->win); |
496 | SelectWindow(focusable->win); |
497 | |
498 | cancel_notification(); |
499 | } |
500 | |
501 | void |
502 | close_focusable(struct focusable *focusable) |
503 | { |
504 | short n; |
505 | |
506 | if (focusable->visible) |
507 | CloseWindow(focusable->win); |
508 | |
509 | for (n = 0; n < nfocusables; n++) { |
510 | if (focusables[n] == focusable) { |
511 | for (; n < nfocusables - 1; n++) |
512 | focusables[n] = focusables[n + 1]; |
513 | break; |
514 | } |
515 | } |
516 | |
517 | nfocusables--; |
518 | if (nfocusables) |
519 | focusables = xreallocarray(focusables, sizeof(Ptr), nfocusables); |
520 | else |
521 | xfree(&focusables); |
522 | |
523 | if (nfocusables && focusables[0]->visible) |
524 | SelectWindow(focusables[0]->win); |
525 | } |
526 | |
527 | void |
528 | hide_focusable(struct focusable *focusable) |
529 | { |
530 | short n; |
531 | |
532 | HideWindow(focusable->win); |
533 | focusable->visible = false; |
534 | |
535 | for (n = 0; n < nfocusables; n++) { |
536 | if (focusables[n] == focusable) { |
537 | for (; n < nfocusables - 1; n++) |
538 | focusables[n] = focusables[n + 1]; |
539 | break; |
540 | } |
541 | } |
542 | focusables[nfocusables - 1] = focusable; |
543 | } |
544 | |
545 | void |
546 | notify(void) |
547 | { |
548 | memset(¬ification, 0, sizeof(notification)); |
549 | notification.qType = nmType; |
550 | notification.nmMark = 1; |
551 | notification.nmSound = (Handle)-1; |
552 | notification.nmIcon = GetResource('SICN', NOTIFICATION_ICON_ID); |
553 | NMInstall(¬ification); |
554 | } |
555 | |
556 | void |
557 | cancel_notification(void) |
558 | { |
559 | if (notification.qType) |
560 | NMRemove(¬ification); |
561 | memset(¬ification, 0, sizeof(notification)); |
562 | } |