1 /* NetHack 3.6 winmisc.c $NHDT-Date: 1457079197 2016/03/04 08:13:17 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.25 $ */
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/Scrollbar.h>
22 #include <X11/Xaw/Viewport.h>
23 #include <X11/Xaw/Cardinals.h>
24 #include <X11/Xos.h> /* for index() */
25 #include <X11/Xatom.h>
27 #ifdef PRESERVE_NO_SYSV
31 #undef PRESERVE_NO_SYSV
38 static Widget extended_command_popup
= 0;
39 static Widget extended_command_form
;
40 static Widget
*extended_commands
= 0;
41 static int extended_cmd_selected
; /* index of the selected command; */
42 static int ps_selected
; /* index of selected role */
43 #define PS_RANDOM (-50)
45 /* 'r' for random won't work for role but will for race, gender, alignment */
46 static const char ps_randchars
[] = "*@\n\rrR";
47 static const char ps_quitchars
[] = "\033qQ";
50 static boolean ec_active
= FALSE
;
51 static int ec_nchars
= 0;
52 static char ec_chars
[EC_NCHARS
];
55 static const char extended_command_translations
[] = "#override\n\
56 <Key>Left: scroll(4)\n\
57 <Key>Right: scroll(6)\n\
59 <Key>Down: scroll(2)\n\
62 static const char player_select_translations
[] = "#override\n\
64 static const char race_select_translations
[] = "#override\n\
66 static const char gend_select_translations
[] = "#override\n\
68 static const char algn_select_translations
[] = "#override\n\
71 static void FDECL(ps_quit
, (Widget
, XtPointer
, XtPointer
));
72 static void FDECL(ps_random
, (Widget
, XtPointer
, XtPointer
));
73 static void FDECL(ps_select
, (Widget
, XtPointer
, XtPointer
));
74 static void FDECL(extend_select
, (Widget
, XtPointer
, XtPointer
));
75 static void FDECL(extend_dismiss
, (Widget
, XtPointer
, XtPointer
));
76 static void FDECL(extend_help
, (Widget
, XtPointer
, XtPointer
));
77 static void FDECL(popup_delete
, (Widget
, XEvent
*, String
*, Cardinal
*));
78 static void NDECL(ec_dismiss
);
79 static void FDECL(ec_scroll_to_view
, (int));
80 static void NDECL(init_extended_commands_popup
);
81 static Widget
FDECL(make_menu
, (const char *, const char *, const char *,
82 const char *, XtCallbackProc
, const char *,
83 XtCallbackProc
, int, const char **,
84 Widget
**, XtCallbackProc
, Widget
*));
86 /* Player Selection --------------------------------------------------------
90 ps_quit(w
, client_data
, call_data
)
92 XtPointer client_data
, call_data
;
98 ps_selected
= PS_QUIT
;
99 exit_x_event
= TRUE
; /* leave event loop */
104 ps_random(w
, client_data
, call_data
)
106 XtPointer client_data
, call_data
;
112 ps_selected
= PS_RANDOM
;
113 exit_x_event
= TRUE
; /* leave event loop */
118 ps_select(w
, client_data
, call_data
)
120 XtPointer client_data
, call_data
;
125 ps_selected
= (int) (ptrdiff_t) client_data
;
126 exit_x_event
= TRUE
; /* leave event loop */
131 ps_key(w
, event
, params
, num_params
)
135 Cardinal
*num_params
;
138 char rolechars
[QBUFSZ
];
145 (void) memset(rolechars
, '\0', sizeof rolechars
); /* for index() */
146 for (i
= 0; roles
[i
].name
.m
; ++i
) {
147 ch
= lowc(*roles
[i
].name
.m
);
148 /* if (flags.female && roles[i].name.f) ch = lowc(*roles[i].name.f);
150 /* this supports at most two roles with the same first letter */
151 if (index(rolechars
, ch
))
155 ch
= key_event_to_char((XKeyEvent
*) event
);
156 if (ch
== '\0') { /* don't accept nul char/modifier event */
160 mark
= index(rolechars
, ch
);
162 mark
= index(rolechars
, lowc(ch
));
164 mark
= index(rolechars
, highc(ch
));
166 if (index(ps_randchars
, ch
))
167 ps_selected
= PS_RANDOM
;
168 else if (index(ps_quitchars
, ch
))
169 ps_selected
= PS_QUIT
;
171 X11_nhbell(); /* no such class */
175 ps_selected
= (int) (mark
- rolechars
);
181 race_key(w
, event
, params
, num_params
)
185 Cardinal
*num_params
;
188 char racechars
[QBUFSZ
];
195 (void) memset(racechars
, '\0', sizeof racechars
); /* for index() */
196 for (i
= 0; races
[i
].noun
; ++i
) {
197 ch
= lowc(*races
[i
].noun
);
198 /* this supports at most two races with the same first letter */
199 if (index(racechars
, ch
))
203 ch
= key_event_to_char((XKeyEvent
*) event
);
204 if (ch
== '\0') { /* don't accept nul char/modifier event */
208 mark
= index(racechars
, ch
);
210 mark
= index(racechars
, lowc(ch
));
212 mark
= index(racechars
, highc(ch
));
214 if (index(ps_randchars
, ch
))
215 ps_selected
= PS_RANDOM
;
216 else if (index(ps_quitchars
, ch
))
217 ps_selected
= PS_QUIT
;
219 X11_nhbell(); /* no such race */
223 ps_selected
= (int) (mark
- racechars
);
229 gend_key(w
, event
, params
, num_params
)
233 Cardinal
*num_params
;
236 static char gendchars
[] = "mf";
242 ch
= key_event_to_char((XKeyEvent
*) event
);
243 if (ch
== '\0') { /* don't accept nul char/modifier event */
247 mark
= index(gendchars
, ch
);
249 mark
= index(gendchars
, lowc(ch
));
251 if (index(ps_randchars
, ch
))
252 ps_selected
= PS_RANDOM
;
253 else if (index(ps_quitchars
, ch
))
254 ps_selected
= PS_QUIT
;
256 X11_nhbell(); /* no such gender */
260 ps_selected
= (int) (mark
- gendchars
);
266 algn_key(w
, event
, params
, num_params
)
270 Cardinal
*num_params
;
273 static char algnchars
[] = "LNC";
279 ch
= key_event_to_char((XKeyEvent
*) event
);
280 if (ch
== '\0') { /* don't accept nul char/modifier event */
284 mark
= index(algnchars
, ch
);
286 mark
= index(algnchars
, highc(ch
));
288 if (index(ps_randchars
, ch
))
289 ps_selected
= PS_RANDOM
;
290 else if (index(ps_quitchars
, ch
))
291 ps_selected
= PS_QUIT
;
293 X11_nhbell(); /* no such alignment */
297 ps_selected
= (int) (mark
- algnchars
);
301 /* Global functions =========================================================
304 X11_player_selection()
306 int num_roles
, num_races
, num_gends
, num_algns
, i
, availcount
, availindex
;
307 Widget popup
, player_form
;
308 const char **choices
;
309 char qbuf
[QBUFSZ
], plbuf
[QBUFSZ
];
311 /* avoid unnecessary prompts further down */
314 (void) root_plselection_prompt(plbuf
, QBUFSZ
- 1, flags
.initrole
,
315 flags
.initrace
, flags
.initgend
,
318 while (flags
.initrole
< 0) {
319 if (flags
.initrole
== ROLE_RANDOM
|| flags
.randomall
) {
320 flags
.initrole
= pick_role(flags
.initrace
, flags
.initgend
,
321 flags
.initalign
, PICK_RANDOM
);
326 for (num_roles
= 0; roles
[num_roles
].name
.m
; ++num_roles
)
328 choices
= (const char **) alloc(sizeof(char *) * num_roles
);
331 for (i
= 0; i
< num_roles
; i
++) {
333 if (ok_role(i
, flags
.initrace
, flags
.initgend
,
335 choices
[i
] = roles
[i
].name
.m
;
336 if (flags
.initgend
>= 0 && flags
.female
338 choices
[i
] = roles
[i
].name
.f
;
344 else if (flags
.initalign
>= 0)
345 flags
.initalign
= -1; /* reset */
346 else if (flags
.initgend
>= 0)
348 else if (flags
.initrace
>= 0)
351 panic("no available ROLE+race+gender+alignment combinations");
353 Sprintf(qbuf
, "Choose your %s Role", s_suffix(plbuf
));
355 make_menu("player_selection", qbuf
, player_select_translations
,
356 "quit", ps_quit
, "random", ps_random
, num_roles
,
357 choices
, (Widget
**) 0, ps_select
, &player_form
);
360 positionpopup(popup
, FALSE
);
361 nh_XtPopup(popup
, (int) XtGrabExclusive
, player_form
);
363 /* The callbacks will enable the event loop exit. */
364 (void) x_event(EXIT_ON_EXIT
);
367 XtDestroyWidget(popup
);
368 free((genericptr_t
) choices
), choices
= 0;
370 if (ps_selected
== PS_QUIT
|| program_state
.done_hup
) {
372 X11_exit_nhwindows((char *) 0);
374 } else if (ps_selected
== PS_RANDOM
) {
375 flags
.initrole
= ROLE_RANDOM
;
376 } else if (ps_selected
< 0 || ps_selected
>= num_roles
) {
377 panic("player_selection: bad role select value %d", ps_selected
);
379 flags
.initrole
= ps_selected
;
383 (void) root_plselection_prompt(plbuf
, QBUFSZ
- 1, flags
.initrole
,
384 flags
.initrace
, flags
.initgend
,
387 while (!validrace(flags
.initrole
, flags
.initrace
)) {
388 if (flags
.initrace
== ROLE_RANDOM
|| flags
.randomall
) {
389 flags
.initrace
= pick_race(flags
.initrole
, flags
.initgend
,
390 flags
.initalign
, PICK_RANDOM
);
394 for (num_races
= 0; races
[num_races
].noun
; ++num_races
)
396 choices
= (const char **) alloc(sizeof(char *) * num_races
);
398 availcount
= availindex
= 0;
399 for (i
= 0; i
< num_races
; i
++) {
401 if (ok_race(flags
.initrole
, i
, flags
.initgend
,
403 choices
[i
] = races
[i
].noun
;
405 availindex
= i
; /* used iff only one */
410 else if (flags
.initalign
>= 0)
411 flags
.initalign
= -1; /* reset */
412 else if (flags
.initgend
>= 0)
415 panic("no available role+RACE+gender+alignment combinations");
418 if (availcount
== 1) {
419 flags
.initrace
= availindex
;
420 free((genericptr_t
) choices
), choices
= 0;
422 Sprintf(qbuf
, "Pick your %s race", s_suffix(plbuf
));
424 make_menu("race_selection", qbuf
, race_select_translations
,
425 "quit", ps_quit
, "random", ps_random
, num_races
,
426 choices
, (Widget
**) 0, ps_select
, &player_form
);
429 positionpopup(popup
, FALSE
);
430 nh_XtPopup(popup
, (int) XtGrabExclusive
, player_form
);
432 /* The callbacks will enable the event loop exit. */
433 (void) x_event(EXIT_ON_EXIT
);
436 XtDestroyWidget(popup
);
437 free((genericptr_t
) choices
), choices
= 0;
439 if (ps_selected
== PS_QUIT
|| program_state
.done_hup
) {
441 X11_exit_nhwindows((char *) 0);
443 } else if (ps_selected
== PS_RANDOM
) {
444 flags
.initrace
= ROLE_RANDOM
;
445 } else if (ps_selected
< 0 || ps_selected
>= num_races
) {
446 panic("player_selection: bad race select value %d",
449 flags
.initrace
= ps_selected
;
451 } /* more than one race choice available */
454 (void) root_plselection_prompt(plbuf
, QBUFSZ
- 1, flags
.initrole
,
455 flags
.initrace
, flags
.initgend
,
458 while (!validgend(flags
.initrole
, flags
.initrace
, flags
.initgend
)) {
459 if (flags
.initgend
== ROLE_RANDOM
|| flags
.randomall
) {
460 flags
.initgend
= pick_gend(flags
.initrole
, flags
.initrace
,
461 flags
.initalign
, PICK_RANDOM
);
464 /* select a gender */
465 num_gends
= 2; /* genders[2] isn't allowed */
466 choices
= (const char **) alloc(sizeof(char *) * num_gends
);
468 availcount
= availindex
= 0;
469 for (i
= 0; i
< num_gends
; i
++) {
471 if (ok_gend(flags
.initrole
, flags
.initrace
, i
,
473 choices
[i
] = genders
[i
].adj
;
475 availindex
= i
; /* used iff only one */
480 else if (flags
.initalign
>= 0)
481 flags
.initalign
= -1; /* reset */
483 panic("no available role+race+GENDER+alignment combinations");
486 if (availcount
== 1) {
487 flags
.initgend
= availindex
;
488 free((genericptr_t
) choices
), choices
= 0;
490 Sprintf(qbuf
, "Your %s gender?", s_suffix(plbuf
));
492 make_menu("gender_selection", qbuf
, gend_select_translations
,
493 "quit", ps_quit
, "random", ps_random
, num_gends
,
494 choices
, (Widget
**) 0, ps_select
, &player_form
);
497 positionpopup(popup
, FALSE
);
498 nh_XtPopup(popup
, (int) XtGrabExclusive
, player_form
);
500 /* The callbacks will enable the event loop exit. */
501 (void) x_event(EXIT_ON_EXIT
);
504 XtDestroyWidget(popup
);
505 free((genericptr_t
) choices
), choices
= 0;
507 if (ps_selected
== PS_QUIT
|| program_state
.done_hup
) {
509 X11_exit_nhwindows((char *) 0);
511 } else if (ps_selected
== PS_RANDOM
) {
512 flags
.initgend
= ROLE_RANDOM
;
513 } else if (ps_selected
< 0 || ps_selected
>= num_gends
) {
514 panic("player_selection: bad gender select value %d",
517 flags
.initgend
= ps_selected
;
519 } /* more than one gender choice available */
522 (void) root_plselection_prompt(plbuf
, QBUFSZ
- 1, flags
.initrole
,
523 flags
.initrace
, flags
.initgend
,
526 while (!validalign(flags
.initrole
, flags
.initrace
, flags
.initalign
)) {
527 if (flags
.initalign
== ROLE_RANDOM
|| flags
.randomall
) {
528 flags
.initalign
= pick_align(flags
.initrole
, flags
.initrace
,
529 flags
.initgend
, PICK_RANDOM
);
532 /* select an alignment */
533 num_algns
= 3; /* aligns[3] isn't allowed */
534 choices
= (const char **) alloc(sizeof(char *) * num_algns
);
536 availcount
= availindex
= 0;
537 for (i
= 0; i
< num_algns
; i
++) {
539 if (ok_align(flags
.initrole
, flags
.initrace
, flags
.initgend
,
541 choices
[i
] = aligns
[i
].adj
;
543 availindex
= i
; /* used iff only one */
549 panic("no available role+race+gender+ALIGNMENT combinations");
552 if (availcount
== 1) {
553 flags
.initalign
= availindex
;
554 free((genericptr_t
) choices
), choices
= 0;
556 Sprintf(qbuf
, "Your %s alignment?", s_suffix(plbuf
));
557 popup
= make_menu("alignment_selection", qbuf
,
558 algn_select_translations
, "quit", ps_quit
,
559 "random", ps_random
, num_algns
, choices
,
560 (Widget
**) 0, ps_select
, &player_form
);
563 positionpopup(popup
, FALSE
);
564 nh_XtPopup(popup
, (int) XtGrabExclusive
, player_form
);
566 /* The callbacks will enable the event loop exit. */
567 (void) x_event(EXIT_ON_EXIT
);
570 XtDestroyWidget(popup
);
571 free((genericptr_t
) choices
), choices
= 0;
573 if (ps_selected
== PS_QUIT
|| program_state
.done_hup
) {
575 X11_exit_nhwindows((char *) 0);
577 } else if (ps_selected
== PS_RANDOM
) {
578 flags
.initalign
= ROLE_RANDOM
;
579 } else if (ps_selected
< 0 || ps_selected
>= num_algns
) {
580 panic("player_selection: bad alignment select value %d",
583 flags
.initalign
= ps_selected
;
585 } /* more than one alignment choice available */
592 if (!extended_commands
)
593 init_extended_commands_popup();
595 extended_cmd_selected
= -1; /* reset selected value */
596 ec_scroll_to_view(-1); /* force scroll bar to top */
598 positionpopup(extended_command_popup
, FALSE
); /* center on cursor */
599 nh_XtPopup(extended_command_popup
, (int) XtGrabExclusive
,
600 extended_command_form
);
602 /* The callbacks will enable the event loop exit. */
603 (void) x_event(EXIT_ON_EXIT
);
605 return extended_cmd_selected
;
609 release_extended_cmds()
611 if (extended_commands
) {
612 XtDestroyWidget(extended_command_popup
);
613 free((genericptr_t
) extended_commands
), extended_commands
= 0;
617 /* End global functions =====================================================
620 /* Extended Command --------------------------------------------------------
624 extend_select(w
, client_data
, call_data
)
626 XtPointer client_data
, call_data
;
628 int selected
= (int) (ptrdiff_t) client_data
;
633 if (extended_cmd_selected
!= selected
) {
634 /* visibly deselect old one */
635 if (extended_cmd_selected
>= 0)
636 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
639 swap_fg_bg(extended_commands
[selected
]);
640 extended_cmd_selected
= selected
;
643 nh_XtPopdown(extended_command_popup
);
644 /* reset colors while popped down */
645 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
647 exit_x_event
= TRUE
; /* leave event loop */
652 extend_dismiss(w
, client_data
, call_data
)
654 XtPointer client_data
, call_data
;
665 extend_help(w
, client_data
, call_data
)
667 XtPointer client_data
, call_data
;
673 /* We might need to make it known that we already have one listed. */
679 ec_delete(w
, event
, params
, num_params
)
683 Cardinal
*num_params
;
685 if (w
== extended_command_popup
) {
688 popup_delete(w
, event
, params
, num_params
);
694 popup_delete(w
, event
, params
, num_params
)
698 Cardinal
*num_params
;
704 ps_selected
= PS_QUIT
;
706 exit_x_event
= TRUE
; /* leave event loop */
712 /* unselect while still visible */
713 if (extended_cmd_selected
>= 0)
714 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
715 extended_cmd_selected
= -1; /* dismiss */
716 nh_XtPopdown(extended_command_popup
);
718 exit_x_event
= TRUE
; /* leave event loop */
721 /* scroll the extended command menu if necessary
722 so that choices extended_cmd_selected through ec_indx will be visible */
724 ec_scroll_to_view(ec_indx
)
725 int ec_indx
; /* might be greater than extended_cmd_selected */
727 Widget viewport
, scrollbar
, tmpw
;
730 Position lo_y
, hi_y
; /* ext cmd label y */
731 float s_shown
, s_top
; /* scrollbar pos */
733 Dimension h
, hh
, wh
, vh
; /* widget and viewport heights */
734 Dimension border_width
;
736 boolean force_top
= (ec_indx
< 0);
739 * If the extended command menu needs to be scrolled in order to move
740 * either the highlighted entry (extended_cmd_selected) or the target
741 * entry (ec_indx) into view, we want to make both end up visible.
742 * [Highligthed one is the first matching entry when the user types
743 * something, such as "adjust" after typing 'a', and will be chosen
744 * by pressing <return>. Target entry is one past the last matching
745 * entry (or last matching entry itself if at end of command list),
746 * showing the user the other potential matches so far.]
748 * If that's not possible (maybe menu has been resized so that it's
749 * too small), the highlighted entry takes precedence and the target
750 * will be decremented until close enough to fit.
756 /* get viewport and scrollbar widgets */
757 tmpw
= extended_commands
[ec_indx
];
758 viewport
= XtParent(tmpw
);
760 scrollbar
= XtNameToWidget(tmpw
, "*vertical");
763 tmpw
= XtParent(tmpw
);
766 if (scrollbar
&& viewport
) {
767 /* get selected ext command label y position and height */
769 XtSetArg(args
[num_args
], XtNy
, &hi_y
); num_args
++;
770 XtSetArg(args
[num_args
], XtNheight
, &h
); num_args
++;
771 XtSetArg(args
[num_args
], nhStr(XtNborderWidth
), &border_width
);
773 XtSetArg(args
[num_args
], nhStr(XtNdefaultDistance
), &distance
);
775 XtGetValues(extended_commands
[ec_indx
], args
, num_args
);
776 if (distance
< 1 || distance
> 32766) /* defaultDistance is weird */
778 /* vertical distance between top of one command widget and the next */
779 hh
= h
+ distance
+ 2 * border_width
;
780 /* location of the highlighted entry, if any */
781 if (extended_cmd_selected
>= 0) {
782 XtSetArg(args
[0], XtNy
, &lo_y
);
783 XtGetValues(extended_commands
[extended_cmd_selected
], args
, ONE
);
787 /* get menu widget and viewport heights */
788 XtSetArg(args
[0], XtNheight
, &wh
);
789 XtGetValues(tmpw
, args
, ONE
);
790 XtSetArg(args
[0], XtNheight
, &vh
);
791 XtGetValues(viewport
, args
, ONE
);
793 /* widget might be too small if it has been resized or
794 there are a very large number of ambiguous choices */
795 if (hi_y
- lo_y
> wh
) {
796 ec_indx
= extended_cmd_selected
;
798 ec_indx
+= (wh
/ hh
);
799 XtSetArg(args
[0], XtNy
, &hi_y
);
800 XtGetValues(extended_commands
[ec_indx
], args
, num_args
);
803 /* get scrollbar "height" and "top" position; floats between 0-1 */
805 XtSetArg(args
[num_args
], XtNshown
, &s_shown
); num_args
++;
806 XtSetArg(args
[num_args
], nhStr(XtNtopOfThumb
), &s_top
); num_args
++;
807 XtGetValues(scrollbar
, args
, num_args
);
810 s_max
= (s_top
+ s_shown
) * vh
;
812 /* scroll if outside the view */
815 XtCallCallbacks(scrollbar
, XtNjumpProc
, &s_min
);
816 } else if ((int) lo_y
<= (int) s_min
) {
817 s_min
= (float) (lo_y
/ (float) vh
);
818 XtCallCallbacks(scrollbar
, XtNjumpProc
, &s_min
);
819 } else if ((int) (hi_y
+ h
) >= (int) s_max
) {
820 s_min
= (float) ((hi_y
+ h
) / (float) vh
) - s_shown
;
821 XtCallCallbacks(scrollbar
, XtNjumpProc
, &s_min
);
828 ec_key(w
, event
, params
, num_params
)
832 Cardinal
*num_params
;
837 XKeyEvent
*xkey
= (XKeyEvent
*) event
;
843 ch
= key_event_to_char(xkey
);
845 if (ch
== '\0') { /* don't accept nul char/modifier event */
848 } else if (ch
== '?') {
849 extend_help((Widget
) 0, (XtPointer
) 0, (XtPointer
) 0);
851 } else if (index("\033\n\r", ch
)) {
853 /* unselect while still visible */
854 if (extended_cmd_selected
>= 0)
855 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
856 extended_cmd_selected
= -1; /* dismiss */
859 nh_XtPopdown(extended_command_popup
);
860 /* unselect while invisible */
861 if (extended_cmd_selected
>= 0)
862 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
864 exit_x_event
= TRUE
; /* leave event loop */
870 * If too much time has elapsed, treat current key as starting a new
871 * choice, otherwise it is a continuation of the choice in progress.
872 * Extra letters might be needed to disambiguate between choices
873 * ("ride" vs "rub", for instance), or player may just be typing in
876 if (ec_active
&& (xkey
->time
- ec_time
) > 2500) /* 2.5 seconds */
884 ec_time
= xkey
->time
;
885 ec_chars
[ec_nchars
++] = ch
;
886 if (ec_nchars
>= EC_NCHARS
)
887 ec_nchars
= EC_NCHARS
- 1; /* don't overflow */
889 for (pass
= 0; pass
< 2; pass
++) {
891 /* first pass finished, but no matching command was found */
892 /* start a new one with the last char entered */
893 if (extended_cmd_selected
>= 0)
894 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
895 extended_cmd_selected
= -1; /* dismiss */
896 ec_chars
[0] = ec_chars
[ec_nchars
-1];
899 for (i
= 0; extcmdlist
[i
].ef_txt
; i
++) {
900 if (extcmdlist
[i
].ef_txt
[0] == '?')
903 if (!strncmp(ec_chars
, extcmdlist
[i
].ef_txt
, ec_nchars
)) {
904 if (extended_cmd_selected
!= i
) {
905 /* I should use set() and unset() actions, but how do */
906 /* I send the an action to the widget? */
907 if (extended_cmd_selected
>= 0)
908 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
909 extended_cmd_selected
= i
;
910 swap_fg_bg(extended_commands
[extended_cmd_selected
]);
912 /* advance to one past last matching entry, so that all
913 ambiguous choices, plus one to show thare aren't any
914 more such, will scroll into view */
916 if (!extcmdlist
[i
+ 1].ef_txt
917 || *extcmdlist
[i
+ 1].ef_txt
== '?')
918 break; /* end of list */
920 } while (!strncmp(ec_chars
, extcmdlist
[i
].ef_txt
, ec_nchars
));
922 ec_scroll_to_view(i
);
930 * Use our own home-brewed version menu because simpleMenu is designed to
931 * be used from a menubox.
934 init_extended_commands_popup()
937 const char **command_list
;
940 for (num_commands
= 0; extcmdlist
[num_commands
].ef_txt
; num_commands
++)
943 /* If the last entry is "help", don't use it. */
944 if (strcmp(extcmdlist
[num_commands
- 1].ef_txt
, "?") == 0)
948 (const char **) alloc((unsigned) num_commands
* sizeof(char *));
950 for (i
= 0; i
< num_commands
; i
++)
951 command_list
[i
] = extcmdlist
[i
].ef_txt
;
953 extended_command_popup
=
954 make_menu("extended_commands", "Extended Commands",
955 extended_command_translations
, "dismiss", extend_dismiss
,
956 "help", extend_help
, num_commands
, command_list
,
957 &extended_commands
, extend_select
, &extended_command_form
);
959 free((char *) command_list
);
962 /* -------------------------------------------------------------------------
966 * Create a popup widget of the following form:
969 * ----------- ------------
970 * |left_name| |right_name|
971 * ----------- ------------
972 * ------------------------
974 * ------------------------
975 * ------------------------
977 * ------------------------
980 * ------------------------
982 * ------------------------
985 make_menu(popup_name
, popup_label
, popup_translations
, left_name
,
986 left_callback
, right_name
, right_callback
, num_names
, widget_names
,
987 command_widgets
, name_callback
, formp
)
988 const char *popup_name
;
989 const char *popup_label
;
990 const char *popup_translations
;
991 const char *left_name
;
992 XtCallbackProc left_callback
;
993 const char *right_name
;
994 XtCallbackProc right_callback
;
996 const char **widget_names
; /* return array of command widgets */
997 Widget
**command_widgets
;
998 XtCallbackProc name_callback
;
999 Widget
*formp
; /* return */
1001 Widget popup
, form
, label
, above
, left
, right
, view
;
1002 Widget
*commands
, *curr
;
1006 Dimension width
, other_width
, max_width
, border_width
,
1007 height
, cumulative_height
, screen_height
;
1010 commands
= (Widget
*) alloc((unsigned) num_names
* sizeof (Widget
));
1013 XtSetArg(args
[num_args
], XtNallowShellResize
, True
); num_args
++;
1014 popup
= XtCreatePopupShell(popup_name
, transientShellWidgetClass
,
1015 toplevel
, args
, num_args
);
1016 XtOverrideTranslations(
1017 popup
, XtParseTranslationTable("<Message>WM_PROTOCOLS: ec_delete()"));
1020 XtSetArg(args
[num_args
], XtNforceBars
, False
); num_args
++;
1021 XtSetArg(args
[num_args
], XtNallowVert
, True
); num_args
++;
1022 XtSetArg(args
[num_args
], XtNtranslations
,
1023 XtParseTranslationTable(popup_translations
)); num_args
++;
1024 view
= XtCreateManagedWidget("menuformview", viewportWidgetClass
, popup
,
1028 XtSetArg(args
[num_args
], XtNtranslations
,
1029 XtParseTranslationTable(popup_translations
)); num_args
++;
1030 *formp
= form
= XtCreateManagedWidget("menuform", formWidgetClass
, view
,
1034 * Get the default distance between objects in the viewport widget.
1035 * (Something is fishy here: 'distance' ends up being 0 but there
1036 * is a non-zero gap between the borders of the internal widgets.
1037 * It matches exactly the default value of 4 for defaultDistance.)
1040 XtSetArg(args
[num_args
], nhStr(XtNdefaultDistance
), &distance
); num_args
++;
1041 XtSetArg(args
[num_args
], nhStr(XtNborderWidth
), &border_width
); num_args
++;
1042 XtGetValues(view
, args
, num_args
);
1043 if (distance
< 1 || distance
> 32766)
1050 XtSetArg(args
[num_args
], XtNborderWidth
, 0); num_args
++;
1051 label
= XtCreateManagedWidget(popup_label
, labelWidgetClass
, form
, args
,
1054 cumulative_height
= 0;
1055 XtSetArg(args
[0], XtNheight
, &height
);
1056 XtGetValues(label
, args
, ONE
);
1057 cumulative_height
+= distance
+ height
; /* no border for label */
1060 * Create the left button.
1063 XtSetArg(args
[num_args
], nhStr(XtNfromVert
), label
); num_args
++;
1065 XtSetArg(args
[num_args
], nhStr(XtNshapeStyle
),
1066 XmuShapeRoundedRectangle
); num_args
++;
1068 left
= XtCreateManagedWidget(left_name
, commandWidgetClass
, form
, args
,
1070 XtAddCallback(left
, XtNcallback
, left_callback
, (XtPointer
) 0);
1071 skip
= (distance
< 4) ? 8 : 2 * distance
;
1074 XtSetArg(args
[0], XtNheight
, &height
);
1075 XtGetValues(left
, args
, ONE
);
1076 cumulative_height
+= distance
+ height
+ 2 * border_width
;
1079 * Create right button.
1082 XtSetArg(args
[num_args
], nhStr(XtNfromHoriz
), left
); num_args
++;
1083 XtSetArg(args
[num_args
], nhStr(XtNhorizDistance
), skip
); num_args
++;
1084 XtSetArg(args
[num_args
], nhStr(XtNfromVert
), label
); num_args
++;
1086 XtSetArg(args
[num_args
], nhStr(XtNshapeStyle
),
1087 XmuShapeRoundedRectangle
); num_args
++;
1089 right
= XtCreateManagedWidget(right_name
, commandWidgetClass
, form
, args
,
1091 XtAddCallback(right
, XtNcallback
, right_callback
, (XtPointer
) 0);
1093 XtInstallAccelerators(form
, left
);
1094 XtInstallAccelerators(form
, right
);
1097 * Create and place the command widgets.
1099 for (i
= 0, above
= left
, curr
= commands
; i
< num_names
; i
++) {
1100 if (!widget_names
[i
])
1103 XtSetArg(args
[num_args
], nhStr(XtNfromVert
), above
); num_args
++;
1104 if (above
== left
) {
1105 /* if first, we are farther apart */
1106 XtSetArg(args
[num_args
], nhStr(XtNvertDistance
), skip
); num_args
++;
1107 cumulative_height
+= skip
;
1109 cumulative_height
+= distance
;
1110 cumulative_height
+= height
+ 2 * border_width
;
1112 *curr
= XtCreateManagedWidget(widget_names
[i
], commandWidgetClass
,
1113 form
, args
, num_args
);
1114 XtAddCallback(*curr
, XtNcallback
, name_callback
,
1115 (XtPointer
) (ptrdiff_t) i
);
1118 cumulative_height
+= distance
; /* space at bottom of form */
1121 * Now find the largest width. Start with width of left + right buttons
1122 * ('dismiss' + 'help' or 'quit' + 'random'), since they are adjacent.
1124 XtSetArg(args
[0], XtNwidth
, &max_width
);
1125 XtGetValues(left
, args
, ONE
);
1126 XtSetArg(args
[0], XtNwidth
, &width
);
1127 XtGetValues(right
, args
, ONE
);
1128 /* doesn't count leftmost 'distance + border_width' and
1129 rightmost 'border_width + distance' since all entries have those */
1130 max_width
= max_width
+ border_width
+ skip
+ border_width
+ width
;
1132 /* Next, the title. */
1133 XtSetArg(args
[0], XtNwidth
, &width
);
1134 XtGetValues(label
, args
, ONE
);
1135 if (width
> max_width
)
1138 /* Finally, the commands. */
1139 for (i
= 0, curr
= commands
; i
< num_names
; i
++) {
1140 if (!widget_names
[i
])
1142 XtSetArg(args
[0], XtNwidth
, &width
);
1143 XtGetValues(*curr
, args
, ONE
);
1144 if (width
> max_width
)
1150 * Re-do the two side-by-side widgets to take up half the width each.
1152 * With max_width and skip both having even values, we never have to
1153 * tweak left or right to maybe be one pixel wider than the other.
1157 XtSetArg(args
[0], XtNwidth
, &width
);
1158 XtGetValues(left
, args
, ONE
);
1159 XtSetArg(args
[0], XtNwidth
, &other_width
);
1160 XtGetValues(right
, args
, ONE
);
1161 if (width
+ border_width
+ skip
/ 2 < max_width
/ 2
1162 && other_width
+ border_width
+ skip
/ 2 < max_width
/ 2) {
1163 /* both are narrower than half */
1164 width
= other_width
= max_width
/ 2 - border_width
- skip
/ 2;
1165 XtSetArg(args
[0], XtNwidth
, width
);
1166 XtSetValues(left
, args
, ONE
);
1167 XtSetArg(args
[0], XtNwidth
, other_width
);
1168 XtSetValues(right
, args
, ONE
);
1169 } else if (width
+ border_width
+ skip
/ 2 < max_width
/ 2) {
1170 /* 'other_width' (right) is half or more */
1171 width
= max_width
- other_width
- 2 * border_width
- skip
;
1172 XtSetArg(args
[0], XtNwidth
, width
);
1173 XtSetValues(left
, args
, ONE
);
1174 } else if (other_width
+ border_width
+ skip
/ 2 < max_width
/ 2) {
1175 /* 'width' (left) is half or more */
1176 other_width
= max_width
- width
- 2 * border_width
- skip
;
1177 XtSetArg(args
[0], XtNwidth
, other_width
);
1178 XtSetValues(right
, args
, ONE
);
1180 ; /* both are exactly half... */
1184 * Finally, set all of the single line widgets to the largest width.
1186 XtSetArg(args
[0], XtNwidth
, max_width
);
1187 XtSetValues(label
, args
, ONE
);
1189 for (i
= 0, curr
= commands
; i
< num_names
; i
++) {
1190 if (!widget_names
[i
])
1192 XtSetArg(args
[0], XtNwidth
, max_width
);
1193 XtSetValues(*curr
, args
, ONE
);
1197 if (command_widgets
)
1198 *command_widgets
= commands
;
1200 free((char *) commands
);
1203 * If the menu's complete height is too big for the display,
1204 * forcing the height to be smaller will cause the vertical
1205 * scroll bar (enabled but not forced above) to be included.
1207 screen_height
= XHeightOfScreen(XtScreen(popup
));
1208 screen_height
-= appResources
.extcmd_height_delta
; /* NetHack.ad */
1209 if (cumulative_height
>= screen_height
) {
1210 /* 25 is a guesstimate for scrollbar width;
1211 window manager might override the request for y==1 */
1213 XtSetArg(args
[num_args
], XtNy
, 1); num_args
++;
1214 XtSetArg(args
[num_args
], XtNwidth
, max_width
+ 25); num_args
++;
1215 XtSetArg(args
[num_args
], XtNheight
, screen_height
- 1); num_args
++;
1216 XtSetValues(popup
, args
, num_args
);
1218 XtRealizeWidget(popup
);
1219 XSetWMProtocols(XtDisplay(popup
), XtWindow(popup
), &wm_delete_window
, 1);
1221 /* during role selection, highlight "random" as pre-selected choice */
1222 if (right_callback
== ps_random
&& index(ps_randchars
, '\n'))