1 /* SCCS Id: @(#)winmisc.c 3.4 2000/05/21 */
2 /* Copyright (c) Dean Luick, 1992 */
3 /* NetHack may be freely redistributed. See license for details. */
6 * Misc. popup windows: player selection and extended commands.
8 * + Global functions: player_selection() and get_ext_cmd().
12 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
15 #include <X11/Intrinsic.h>
16 #include <X11/StringDefs.h>
17 #include <X11/Shell.h>
18 #include <X11/Xaw/Command.h>
19 #include <X11/Xaw/Form.h>
20 #include <X11/Xaw/Label.h>
21 #include <X11/Xaw/Cardinals.h>
22 #include <X11/Xos.h> /* for index() */
23 #include <X11/Xatom.h>
25 #ifdef PRESERVE_NO_SYSV
29 # undef PRESERVE_NO_SYSV
37 static Widget extended_command_popup = 0;
38 static Widget extended_command_form;
39 static Widget *extended_commands = 0;
40 static int extended_command_selected; /* index of the selected command; */
41 static int ps_selected; /* index of selected role */
42 #define PS_RANDOM (-50)
44 static const char ps_randchars[] = "*@";
45 static const char ps_quitchars[] = "\033qQ";
48 static boolean ec_active = FALSE;
49 static int ec_nchars = 0;
50 static char ec_chars[EC_NCHARS];
53 static const char extended_command_translations[] =
57 static const char player_select_translations[] =
60 static const char race_select_translations[] =
63 static const char gend_select_translations[] =
66 static const char algn_select_translations[] =
70 static void FDECL(popup_delete, (Widget, XEvent*, String*, Cardinal*));
71 static void NDECL(ec_dismiss);
72 static Widget FDECL(make_menu, (const char *,const char *,const char *,
73 const char *,XtCallbackProc,
74 const char *,XtCallbackProc,
75 int,const char **, Widget **,
76 XtCallbackProc,Widget *));
77 static void NDECL(init_extended_commands_popup);
78 static void FDECL(ps_quit, (Widget,XtPointer,XtPointer));
79 static void FDECL(ps_random, (Widget,XtPointer,XtPointer));
80 static void FDECL(ps_select, (Widget,XtPointer,XtPointer));
83 /* Player Selection -------------------------------------------------------- */
86 ps_quit(w, client_data, call_data)
88 XtPointer client_data, call_data;
90 ps_selected = PS_QUIT;
91 exit_x_event = TRUE; /* leave event loop */
96 ps_random(w, client_data, call_data)
98 XtPointer client_data, call_data;
100 ps_selected = PS_RANDOM;
101 exit_x_event = TRUE; /* leave event loop */
106 ps_select(w, client_data, call_data)
108 XtPointer client_data, call_data;
110 ps_selected = (int) client_data;
111 exit_x_event = TRUE; /* leave event loop */
116 ps_key(w, event, params, num_params)
120 Cardinal *num_params;
123 char rolechars[QBUFSZ];
126 (void)memset(rolechars, '\0', sizeof rolechars); /* for index() */
127 for (i = 0; roles[i].name.m; ++i) {
128 ch = lowc(*roles[i].name.m);
129 /* if (flags.female && roles[i].name.f) ch = lowc(*roles[i].name.f); */
130 /* this supports at most two roles with the same first letter */
131 if (index(rolechars, ch)) ch = highc(ch);
134 ch = key_event_to_char((XKeyEvent *) event);
135 if (ch == '\0') { /* don't accept nul char/modifier event */
139 mark = index(rolechars, ch);
140 if (!mark) mark = index(rolechars, lowc(ch));
141 if (!mark) mark = index(rolechars, highc(ch));
143 if (index(ps_randchars, ch))
144 ps_selected = PS_RANDOM;
145 else if (index(ps_quitchars, ch))
146 ps_selected = PS_QUIT;
148 X11_nhbell(); /* no such class */
152 ps_selected = (int)(mark - rolechars);
158 race_key(w, event, params, num_params)
162 Cardinal *num_params;
165 char racechars[QBUFSZ];
168 (void)memset(racechars, '\0', sizeof racechars); /* for index() */
169 for (i = 0; races[i].noun; ++i) {
170 ch = lowc(*races[i].noun);
171 /* this supports at most two races with the same first letter */
172 if (index(racechars, ch)) ch = highc(ch);
175 ch = key_event_to_char((XKeyEvent *) event);
176 if (ch == '\0') { /* don't accept nul char/modifier event */
180 mark = index(racechars, ch);
181 if (!mark) mark = index(racechars, lowc(ch));
182 if (!mark) mark = index(racechars, highc(ch));
184 if (index(ps_randchars, ch))
185 ps_selected = PS_RANDOM;
186 else if (index(ps_quitchars, ch))
187 ps_selected = PS_QUIT;
189 X11_nhbell(); /* no such race */
193 ps_selected = (int)(mark - racechars);
199 gend_key(w, event, params, num_params)
203 Cardinal *num_params;
206 static char gendchars[] = "mf";
208 ch = key_event_to_char((XKeyEvent *) event);
209 if (ch == '\0') { /* don't accept nul char/modifier event */
213 mark = index(gendchars, ch);
214 if (!mark) mark = index(gendchars, lowc(ch));
216 if (index(ps_randchars, ch))
217 ps_selected = PS_RANDOM;
218 else if (index(ps_quitchars, ch))
219 ps_selected = PS_QUIT;
221 X11_nhbell(); /* no such gender */
225 ps_selected = (int)(mark - gendchars);
231 algn_key(w, event, params, num_params)
235 Cardinal *num_params;
238 static char algnchars[] = "LNC";
240 ch = key_event_to_char((XKeyEvent *) event);
241 if (ch == '\0') { /* don't accept nul char/modifier event */
245 mark = index(algnchars, ch);
246 if (!mark) mark = index(algnchars, highc(ch));
248 if (index(ps_randchars, ch))
249 ps_selected = PS_RANDOM;
250 else if (index(ps_quitchars, ch))
251 ps_selected = PS_QUIT;
253 X11_nhbell(); /* no such alignment */
257 ps_selected = (int)(mark - algnchars);
261 /* Global functions ========================================================= */
263 X11_player_selection()
265 int num_roles, num_races, num_gends, num_algns,
266 i, availcount, availindex;
267 Widget popup, player_form;
268 const char **choices;
269 char qbuf[QBUFSZ], plbuf[QBUFSZ];
271 /* avoid unnecessary prompts further down */
274 (void) root_plselection_prompt(plbuf, QBUFSZ - 1,
275 flags.initrole, flags.initrace, flags.initgend, flags.initalign);
277 while (flags.initrole < 0) {
278 if (flags.initrole == ROLE_RANDOM || flags.randomall) {
279 flags.initrole = pick_role(flags.initrace,
280 flags.initgend, flags.initalign, PICK_RANDOM);
285 for (num_roles = 0; roles[num_roles].name.m; ++num_roles) continue;
286 choices = (const char **)alloc(sizeof(char *) * num_roles);
289 for (i = 0; i < num_roles; i++) {
291 if (ok_role(i, flags.initrace,
292 flags.initgend, flags.initalign)) {
293 choices[i] = roles[i].name.m;
294 if (flags.initgend >= 0 && flags.female && roles[i].name.f)
295 choices[i] = roles[i].name.f;
299 if (availcount > 0) break;
300 else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
301 else if (flags.initgend >= 0) flags.initgend = -1;
302 else if (flags.initrace >= 0) flags.initrace = -1;
303 else panic("no available ROLE+race+gender+alignment combinations");
305 Sprintf(qbuf, "Choose your %s Role", s_suffix(plbuf));
306 popup = make_menu("player_selection", qbuf,
307 player_select_translations,
310 num_roles, choices, (Widget **)0, ps_select, &player_form);
313 positionpopup(popup, FALSE);
314 nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
316 /* The callbacks will enable the event loop exit. */
317 (void) x_event(EXIT_ON_EXIT);
320 XtDestroyWidget(popup);
321 free((genericptr_t)choices), choices = 0;
323 if (ps_selected == PS_QUIT) {
325 X11_exit_nhwindows((char *)0);
327 } else if (ps_selected == PS_RANDOM) {
328 flags.initrole = ROLE_RANDOM;
329 } else if (ps_selected < 0 || ps_selected >= num_roles) {
330 panic("player_selection: bad role select value %d", ps_selected);
332 flags.initrole = ps_selected;
336 (void) root_plselection_prompt(plbuf, QBUFSZ - 1,
337 flags.initrole, flags.initrace, flags.initgend, flags.initalign);
339 while (!validrace(flags.initrole, flags.initrace)) {
340 if (flags.initrace == ROLE_RANDOM || flags.randomall) {
341 flags.initrace = pick_race(flags.initrole,
342 flags.initgend, flags.initalign, PICK_RANDOM);
346 for (num_races = 0; races[num_races].noun; ++num_races) continue;
347 choices = (const char **)alloc(sizeof(char *) * num_races);
349 availcount = availindex = 0;
350 for (i = 0; i < num_races; i++) {
352 if (ok_race(flags.initrole, i,
353 flags.initgend, flags.initalign)) {
354 choices[i] = races[i].noun;
356 availindex = i; /* used iff only one */
359 if (availcount > 0) break;
360 else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
361 else if (flags.initgend >= 0) flags.initgend = -1;
362 else panic("no available role+RACE+gender+alignment combinations");
365 if (availcount == 1) {
366 flags.initrace = availindex;
367 free((genericptr_t)choices), choices = 0;
369 Sprintf(qbuf, "Pick your %s race", s_suffix(plbuf));
370 popup = make_menu("race_selection", qbuf,
371 race_select_translations,
374 num_races, choices, (Widget **)0,
375 ps_select, &player_form);
378 positionpopup(popup, FALSE);
379 nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
381 /* The callbacks will enable the event loop exit. */
382 (void) x_event(EXIT_ON_EXIT);
385 XtDestroyWidget(popup);
386 free((genericptr_t)choices), choices = 0;
388 if (ps_selected == PS_QUIT) {
390 X11_exit_nhwindows((char *)0);
392 } else if (ps_selected == PS_RANDOM) {
393 flags.initrace = ROLE_RANDOM;
394 } else if (ps_selected < 0 || ps_selected >= num_races) {
395 panic("player_selection: bad race select value %d", ps_selected);
397 flags.initrace = ps_selected;
399 } /* more than one race choice available */
402 (void) root_plselection_prompt(plbuf, QBUFSZ - 1,
403 flags.initrole, flags.initrace, flags.initgend, flags.initalign);
405 while (!validgend(flags.initrole, flags.initrace, flags.initgend)) {
406 if (flags.initgend == ROLE_RANDOM || flags.randomall) {
407 flags.initgend = pick_gend(flags.initrole, flags.initrace,
408 flags.initalign, PICK_RANDOM);
411 /* select a gender */
412 num_gends = 2; /* genders[2] isn't allowed */
413 choices = (const char **)alloc(sizeof(char *) * num_gends);
415 availcount = availindex = 0;
416 for (i = 0; i < num_gends; i++) {
418 if (ok_gend(flags.initrole, flags.initrace,
419 i, flags.initalign)) {
420 choices[i] = genders[i].adj;
422 availindex = i; /* used iff only one */
425 if (availcount > 0) break;
426 else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
427 else panic("no available role+race+GENDER+alignment combinations");
430 if (availcount == 1) {
431 flags.initgend = availindex;
432 free((genericptr_t)choices), choices = 0;
434 Sprintf(qbuf, "Your %s gender?", s_suffix(plbuf));
435 popup = make_menu("gender_selection", qbuf,
436 gend_select_translations,
439 num_gends, choices, (Widget **)0,
440 ps_select, &player_form);
443 positionpopup(popup, FALSE);
444 nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
446 /* The callbacks will enable the event loop exit. */
447 (void) x_event(EXIT_ON_EXIT);
450 XtDestroyWidget(popup);
451 free((genericptr_t)choices), choices = 0;
453 if (ps_selected == PS_QUIT) {
455 X11_exit_nhwindows((char *)0);
457 } else if (ps_selected == PS_RANDOM) {
458 flags.initgend = ROLE_RANDOM;
459 } else if (ps_selected < 0 || ps_selected >= num_gends) {
460 panic("player_selection: bad gender select value %d", ps_selected);
462 flags.initgend = ps_selected;
464 } /* more than one gender choice available */
467 (void) root_plselection_prompt(plbuf, QBUFSZ - 1,
468 flags.initrole, flags.initrace, flags.initgend, flags.initalign);
470 while (!validalign(flags.initrole, flags.initrace, flags.initalign)) {
471 if (flags.initalign == ROLE_RANDOM || flags.randomall) {
472 flags.initalign = pick_align(flags.initrole, flags.initrace,
473 flags.initgend, PICK_RANDOM);
476 /* select an alignment */
477 num_algns = 3; /* aligns[3] isn't allowed */
478 choices = (const char **)alloc(sizeof(char *) * num_algns);
480 availcount = availindex = 0;
481 for (i = 0; i < num_algns; i++) {
483 if (ok_align(flags.initrole, flags.initrace,
484 flags.initgend, i)) {
485 choices[i] = aligns[i].adj;
487 availindex = i; /* used iff only one */
490 if (availcount > 0) break;
491 else panic("no available role+race+gender+ALIGNMENT combinations");
494 if (availcount == 1) {
495 flags.initalign = availindex;
496 free((genericptr_t)choices), choices = 0;
498 Sprintf(qbuf, "Your %s alignment?", s_suffix(plbuf));
499 popup = make_menu("alignment_selection", qbuf,
500 algn_select_translations,
503 num_algns, choices, (Widget **)0,
504 ps_select, &player_form);
507 positionpopup(popup, FALSE);
508 nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
510 /* The callbacks will enable the event loop exit. */
511 (void) x_event(EXIT_ON_EXIT);
514 XtDestroyWidget(popup);
515 free((genericptr_t)choices), choices = 0;
517 if (ps_selected == PS_QUIT) {
519 X11_exit_nhwindows((char *)0);
521 } else if (ps_selected == PS_RANDOM) {
522 flags.initalign = ROLE_RANDOM;
523 } else if (ps_selected < 0 || ps_selected >= num_algns) {
524 panic("player_selection: bad alignment select value %d", ps_selected);
526 flags.initalign = ps_selected;
528 } /* more than one alignment choice available */
536 static Boolean initialized = False;
539 init_extended_commands_popup();
543 extended_command_selected = -1; /* reset selected value */
545 positionpopup(extended_command_popup, FALSE); /* center on cursor */
546 nh_XtPopup(extended_command_popup, (int)XtGrabExclusive,
547 extended_command_form);
549 /* The callbacks will enable the event loop exit. */
550 (void) x_event(EXIT_ON_EXIT);
552 return extended_command_selected;
555 /* End global functions ===================================================== */
557 /* Extended Command -------------------------------------------------------- */
560 extend_select(w, client_data, call_data)
562 XtPointer client_data, call_data;
564 int selected = (int) client_data;
566 if (extended_command_selected != selected) {
567 /* visibly deselect old one */
568 if (extended_command_selected >= 0)
569 swap_fg_bg(extended_commands[extended_command_selected]);
572 swap_fg_bg(extended_commands[selected]);
573 extended_command_selected = selected;
576 nh_XtPopdown(extended_command_popup);
577 /* reset colors while popped down */
578 swap_fg_bg(extended_commands[extended_command_selected]);
580 exit_x_event = TRUE; /* leave event loop */
585 extend_dismiss(w, client_data, call_data)
587 XtPointer client_data, call_data;
594 extend_help(w, client_data, call_data)
596 XtPointer client_data, call_data;
598 /* We might need to make it known that we already have one listed. */
604 ec_delete(w, event, params, num_params)
608 Cardinal *num_params;
610 if (w == extended_command_popup) {
613 popup_delete(w, event, params, num_params);
619 popup_delete(w, event, params, num_params)
623 Cardinal *num_params;
625 ps_selected = PS_QUIT;
627 exit_x_event = TRUE; /* leave event loop */
633 /* unselect while still visible */
634 if (extended_command_selected >= 0)
635 swap_fg_bg(extended_commands[extended_command_selected]);
636 extended_command_selected = -1; /* dismiss */
637 nh_XtPopdown(extended_command_popup);
639 exit_x_event = TRUE; /* leave event loop */
644 ec_key(w, event, params, num_params)
648 Cardinal *num_params;
652 XKeyEvent *xkey = (XKeyEvent *) event;
654 ch = key_event_to_char(xkey);
656 if (ch == '\0') { /* don't accept nul char/modifier event */
659 } else if (index("\033\n\r", ch)) {
661 /* unselect while still visible */
662 if (extended_command_selected >= 0)
663 swap_fg_bg(extended_commands[extended_command_selected]);
664 extended_command_selected = -1; /* dismiss */
667 nh_XtPopdown(extended_command_popup);
668 /* unselect while invisible */
669 if (extended_command_selected >= 0)
670 swap_fg_bg(extended_commands[extended_command_selected]);
672 exit_x_event = TRUE; /* leave event loop */
677 /* too much time has elapsed */
678 if ((xkey->time - ec_time) > 500)
686 ec_time = xkey->time;
687 ec_chars[ec_nchars++] = ch;
688 if (ec_nchars >= EC_NCHARS)
689 ec_nchars = EC_NCHARS-1; /* don't overflow */
691 for (i = 0; extcmdlist[i].ef_txt; i++) {
692 if (extcmdlist[i].ef_txt[0] == '?') continue;
694 if (!strncmp(ec_chars, extcmdlist[i].ef_txt, ec_nchars)) {
695 if (extended_command_selected != i) {
696 /* I should use set() and unset() actions, but how do */
697 /* I send the an action to the widget? */
698 if (extended_command_selected >= 0)
699 swap_fg_bg(extended_commands[extended_command_selected]);
700 extended_command_selected = i;
701 swap_fg_bg(extended_commands[extended_command_selected]);
709 * Use our own home-brewed version menu because simpleMenu is designed to
710 * be used from a menubox.
713 init_extended_commands_popup()
716 const char **command_list;
719 for (num_commands = 0; extcmdlist[num_commands].ef_txt; num_commands++)
722 /* If the last entry is "help", don't use it. */
723 if (strcmp(extcmdlist[num_commands-1].ef_txt, "?") == 0)
727 (const char **) alloc((unsigned)num_commands * sizeof(char *));
729 for (i = 0; i < num_commands; i++)
730 command_list[i] = extcmdlist[i].ef_txt;
732 extended_command_popup = make_menu("extended_commands",
734 extended_command_translations,
735 "dismiss", extend_dismiss,
737 num_commands, command_list, &extended_commands,
738 extend_select, &extended_command_form);
740 free((char *)command_list);
743 /* ------------------------------------------------------------------------- */
746 * Create a popup widget of the following form:
749 * ----------- ------------
750 * |left_name| |right_name|
751 * ----------- ------------
752 * ------------------------
754 * ------------------------
755 * ------------------------
757 * ------------------------
760 * ------------------------
762 * ------------------------
765 make_menu(popup_name, popup_label, popup_translations,
766 left_name, left_callback,
767 right_name, right_callback,
768 num_names, widget_names, command_widgets, name_callback, formp)
769 const char *popup_name;
770 const char *popup_label;
771 const char *popup_translations;
772 const char *left_name;
773 XtCallbackProc left_callback;
774 const char *right_name;
775 XtCallbackProc right_callback;
777 const char **widget_names; /* return array of command widgets */
778 Widget **command_widgets;
779 XtCallbackProc name_callback;
780 Widget *formp; /* return */
782 Widget popup, form, label, above, left, right;
783 Widget *commands, *curr;
787 Dimension width, max_width;
791 commands = (Widget *) alloc((unsigned)num_names * sizeof(Widget));
795 XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
797 popup = XtCreatePopupShell(popup_name,
798 transientShellWidgetClass,
799 toplevel, args, num_args);
800 XtOverrideTranslations(popup,
801 XtParseTranslationTable("<Message>WM_PROTOCOLS: ec_delete()"));
804 XtSetArg(args[num_args], XtNtranslations,
805 XtParseTranslationTable(popup_translations)); num_args++;
806 *formp = form = XtCreateManagedWidget("menuform",
811 /* Get the default distance between objects in the form widget. */
813 XtSetArg(args[num_args], XtNdefaultDistance, &distance); num_args++;
814 XtGetValues(form, args, num_args);
820 XtSetArg(args[num_args], XtNborderWidth, 0); num_args++;
821 label = XtCreateManagedWidget(popup_label,
827 * Create the left button.
830 XtSetArg(args[num_args], XtNfromVert, label); num_args++;
832 XtSetArg(args[num_args], XtNshapeStyle,
833 XmuShapeRoundedRectangle); num_args++;
835 left = XtCreateManagedWidget(left_name,
839 XtAddCallback(left, XtNcallback, left_callback, (XtPointer) 0);
840 skip = 3*distance; /* triple the spacing */
844 * Create right button.
847 XtSetArg(args[num_args], XtNfromHoriz, left); num_args++;
848 XtSetArg(args[num_args], XtNfromVert, label); num_args++;
850 XtSetArg(args[num_args], XtNshapeStyle,
851 XmuShapeRoundedRectangle); num_args++;
853 right = XtCreateManagedWidget(right_name,
857 XtAddCallback(right, XtNcallback, right_callback, (XtPointer) 0);
859 XtInstallAccelerators(form, left);
860 XtInstallAccelerators(form, right);
863 * Create and place the command widgets.
865 for (i = 0, above = left, curr = commands; i < num_names; i++) {
866 if (!widget_names[i]) continue;
868 XtSetArg(args[num_args], XtNfromVert, above); num_args++;
870 /* if first, we are farther apart */
871 XtSetArg(args[num_args], XtNvertDistance, skip); num_args++;
874 *curr = XtCreateManagedWidget(widget_names[i],
878 XtAddCallback(*curr, XtNcallback, name_callback, (XtPointer) i);
883 * Now find the largest width. Start with the width dismiss + help
884 * buttons, since they are adjacent.
886 XtSetArg(args[0], XtNwidth, &max_width);
887 XtGetValues(left, args, ONE);
888 XtSetArg(args[0], XtNwidth, &width);
889 XtGetValues(right, args, ONE);
890 max_width = max_width + width + distance;
892 /* Next, the title. */
893 XtSetArg(args[0], XtNwidth, &width);
894 XtGetValues(label, args, ONE);
895 if (width > max_width) max_width = width;
897 /* Finally, the commands. */
898 for (i = 0, curr = commands; i < num_names; i++) {
899 if (!widget_names[i]) continue;
900 XtSetArg(args[0], XtNwidth, &width);
901 XtGetValues(*curr, args, ONE);
902 if (width > max_width) max_width = width;
907 * Finally, set all of the single line widgets to the largest width.
909 XtSetArg(args[0], XtNwidth, max_width);
910 XtSetValues(label, args, ONE);
912 for (i = 0, curr = commands; i < num_names; i++) {
913 if (!widget_names[i]) continue;
914 XtSetArg(args[0], XtNwidth, max_width);
915 XtSetValues(*curr, args, ONE);
920 *command_widgets = commands;
922 free((char *) commands);
924 XtRealizeWidget(popup);
925 XSetWMProtocols(XtDisplay(popup), XtWindow(popup), &wm_delete_window, 1);