Document explicitly what m-prefix does to each command
[aNetHack.git] / win / X11 / winmisc.c
blobb96902588b36a25a278b77c7ea76a69a656d1ca2
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. */
5 /*
6 * Misc. popup windows: player selection and extended commands.
8 * + Global functions: player_selection() and get_ext_cmd().
9 */
11 #ifndef SYSV
12 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
13 #endif
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
28 #ifdef SYSV
29 #undef SYSV
30 #endif
31 #undef PRESERVE_NO_SYSV
32 #endif
34 #include "hack.h"
35 #include "func_tab.h"
36 #include "winX.h"
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)
44 #define PS_QUIT (-75)
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";
49 #define EC_NCHARS 32
50 static boolean ec_active = FALSE;
51 static int ec_nchars = 0;
52 static char ec_chars[EC_NCHARS];
53 static Time ec_time;
55 static const char extended_command_translations[] = "#override\n\
56 <Key>Left: scroll(4)\n\
57 <Key>Right: scroll(6)\n\
58 <Key>Up: scroll(8)\n\
59 <Key>Down: scroll(2)\n\
60 <Key>: ec_key()";
62 static const char player_select_translations[] = "#override\n\
63 <Key>: ps_key()";
64 static const char race_select_translations[] = "#override\n\
65 <Key>: race_key()";
66 static const char gend_select_translations[] = "#override\n\
67 <Key>: gend_key()";
68 static const char algn_select_translations[] = "#override\n\
69 <Key>: algn_key()";
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 --------------------------------------------------------
88 /* ARGSUSED */
89 static void
90 ps_quit(w, client_data, call_data)
91 Widget w;
92 XtPointer client_data, call_data;
94 nhUse(w);
95 nhUse(client_data);
96 nhUse(call_data);
98 ps_selected = PS_QUIT;
99 exit_x_event = TRUE; /* leave event loop */
102 /* ARGSUSED */
103 static void
104 ps_random(w, client_data, call_data)
105 Widget w;
106 XtPointer client_data, call_data;
108 nhUse(w);
109 nhUse(client_data);
110 nhUse(call_data);
112 ps_selected = PS_RANDOM;
113 exit_x_event = TRUE; /* leave event loop */
116 /* ARGSUSED */
117 static void
118 ps_select(w, client_data, call_data)
119 Widget w;
120 XtPointer client_data, call_data;
122 nhUse(w);
123 nhUse(call_data);
125 ps_selected = (int) (ptrdiff_t) client_data;
126 exit_x_event = TRUE; /* leave event loop */
129 /* ARGSUSED */
130 void
131 ps_key(w, event, params, num_params)
132 Widget w;
133 XEvent *event;
134 String *params;
135 Cardinal *num_params;
137 char ch, *mark;
138 char rolechars[QBUFSZ];
139 int i;
141 nhUse(w);
142 nhUse(params);
143 nhUse(num_params);
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))
152 ch = highc(ch);
153 rolechars[i] = ch;
155 ch = key_event_to_char((XKeyEvent *) event);
156 if (ch == '\0') { /* don't accept nul char/modifier event */
157 /* don't beep */
158 return;
160 mark = index(rolechars, ch);
161 if (!mark)
162 mark = index(rolechars, lowc(ch));
163 if (!mark)
164 mark = index(rolechars, highc(ch));
165 if (!mark) {
166 if (index(ps_randchars, ch))
167 ps_selected = PS_RANDOM;
168 else if (index(ps_quitchars, ch))
169 ps_selected = PS_QUIT;
170 else {
171 X11_nhbell(); /* no such class */
172 return;
174 } else
175 ps_selected = (int) (mark - rolechars);
176 exit_x_event = TRUE;
179 /* ARGSUSED */
180 void
181 race_key(w, event, params, num_params)
182 Widget w;
183 XEvent *event;
184 String *params;
185 Cardinal *num_params;
187 char ch, *mark;
188 char racechars[QBUFSZ];
189 int i;
191 nhUse(w);
192 nhUse(params);
193 nhUse(num_params);
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))
200 ch = highc(ch);
201 racechars[i] = ch;
203 ch = key_event_to_char((XKeyEvent *) event);
204 if (ch == '\0') { /* don't accept nul char/modifier event */
205 /* don't beep */
206 return;
208 mark = index(racechars, ch);
209 if (!mark)
210 mark = index(racechars, lowc(ch));
211 if (!mark)
212 mark = index(racechars, highc(ch));
213 if (!mark) {
214 if (index(ps_randchars, ch))
215 ps_selected = PS_RANDOM;
216 else if (index(ps_quitchars, ch))
217 ps_selected = PS_QUIT;
218 else {
219 X11_nhbell(); /* no such race */
220 return;
222 } else
223 ps_selected = (int) (mark - racechars);
224 exit_x_event = TRUE;
227 /* ARGSUSED */
228 void
229 gend_key(w, event, params, num_params)
230 Widget w;
231 XEvent *event;
232 String *params;
233 Cardinal *num_params;
235 char ch, *mark;
236 static char gendchars[] = "mf";
238 nhUse(w);
239 nhUse(params);
240 nhUse(num_params);
242 ch = key_event_to_char((XKeyEvent *) event);
243 if (ch == '\0') { /* don't accept nul char/modifier event */
244 /* don't beep */
245 return;
247 mark = index(gendchars, ch);
248 if (!mark)
249 mark = index(gendchars, lowc(ch));
250 if (!mark) {
251 if (index(ps_randchars, ch))
252 ps_selected = PS_RANDOM;
253 else if (index(ps_quitchars, ch))
254 ps_selected = PS_QUIT;
255 else {
256 X11_nhbell(); /* no such gender */
257 return;
259 } else
260 ps_selected = (int) (mark - gendchars);
261 exit_x_event = TRUE;
264 /* ARGSUSED */
265 void
266 algn_key(w, event, params, num_params)
267 Widget w;
268 XEvent *event;
269 String *params;
270 Cardinal *num_params;
272 char ch, *mark;
273 static char algnchars[] = "LNC";
275 nhUse(w);
276 nhUse(params);
277 nhUse(num_params);
279 ch = key_event_to_char((XKeyEvent *) event);
280 if (ch == '\0') { /* don't accept nul char/modifier event */
281 /* don't beep */
282 return;
284 mark = index(algnchars, ch);
285 if (!mark)
286 mark = index(algnchars, highc(ch));
287 if (!mark) {
288 if (index(ps_randchars, ch))
289 ps_selected = PS_RANDOM;
290 else if (index(ps_quitchars, ch))
291 ps_selected = PS_QUIT;
292 else {
293 X11_nhbell(); /* no such alignment */
294 return;
296 } else
297 ps_selected = (int) (mark - algnchars);
298 exit_x_event = TRUE;
301 /* Global functions =========================================================
303 void
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 */
312 rigid_role_checks();
314 (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
315 flags.initrace, flags.initgend,
316 flags.initalign);
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);
322 break;
325 /* select a role */
326 for (num_roles = 0; roles[num_roles].name.m; ++num_roles)
327 continue;
328 choices = (const char **) alloc(sizeof(char *) * num_roles);
329 for (;;) {
330 availcount = 0;
331 for (i = 0; i < num_roles; i++) {
332 choices[i] = 0;
333 if (ok_role(i, flags.initrace, flags.initgend,
334 flags.initalign)) {
335 choices[i] = roles[i].name.m;
336 if (flags.initgend >= 0 && flags.female
337 && roles[i].name.f)
338 choices[i] = roles[i].name.f;
339 ++availcount;
342 if (availcount > 0)
343 break;
344 else if (flags.initalign >= 0)
345 flags.initalign = -1; /* reset */
346 else if (flags.initgend >= 0)
347 flags.initgend = -1;
348 else if (flags.initrace >= 0)
349 flags.initrace = -1;
350 else
351 panic("no available ROLE+race+gender+alignment combinations");
353 Sprintf(qbuf, "Choose your %s Role", s_suffix(plbuf));
354 popup =
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);
359 ps_selected = -1;
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);
366 nh_XtPopdown(popup);
367 XtDestroyWidget(popup);
368 free((genericptr_t) choices), choices = 0;
370 if (ps_selected == PS_QUIT || program_state.done_hup) {
371 clearlocks();
372 X11_exit_nhwindows((char *) 0);
373 terminate(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);
378 } else {
379 flags.initrole = ps_selected;
383 (void) root_plselection_prompt(plbuf, QBUFSZ - 1, flags.initrole,
384 flags.initrace, flags.initgend,
385 flags.initalign);
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);
391 break;
393 /* select a race */
394 for (num_races = 0; races[num_races].noun; ++num_races)
395 continue;
396 choices = (const char **) alloc(sizeof(char *) * num_races);
397 for (;;) {
398 availcount = availindex = 0;
399 for (i = 0; i < num_races; i++) {
400 choices[i] = 0;
401 if (ok_race(flags.initrole, i, flags.initgend,
402 flags.initalign)) {
403 choices[i] = races[i].noun;
404 ++availcount;
405 availindex = i; /* used iff only one */
408 if (availcount > 0)
409 break;
410 else if (flags.initalign >= 0)
411 flags.initalign = -1; /* reset */
412 else if (flags.initgend >= 0)
413 flags.initgend = -1;
414 else
415 panic("no available role+RACE+gender+alignment combinations");
418 if (availcount == 1) {
419 flags.initrace = availindex;
420 free((genericptr_t) choices), choices = 0;
421 } else {
422 Sprintf(qbuf, "Pick your %s race", s_suffix(plbuf));
423 popup =
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);
428 ps_selected = -1;
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);
435 nh_XtPopdown(popup);
436 XtDestroyWidget(popup);
437 free((genericptr_t) choices), choices = 0;
439 if (ps_selected == PS_QUIT || program_state.done_hup) {
440 clearlocks();
441 X11_exit_nhwindows((char *) 0);
442 terminate(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",
447 ps_selected);
448 } else {
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,
456 flags.initalign);
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);
462 break;
464 /* select a gender */
465 num_gends = 2; /* genders[2] isn't allowed */
466 choices = (const char **) alloc(sizeof(char *) * num_gends);
467 for (;;) {
468 availcount = availindex = 0;
469 for (i = 0; i < num_gends; i++) {
470 choices[i] = 0;
471 if (ok_gend(flags.initrole, flags.initrace, i,
472 flags.initalign)) {
473 choices[i] = genders[i].adj;
474 ++availcount;
475 availindex = i; /* used iff only one */
478 if (availcount > 0)
479 break;
480 else if (flags.initalign >= 0)
481 flags.initalign = -1; /* reset */
482 else
483 panic("no available role+race+GENDER+alignment combinations");
486 if (availcount == 1) {
487 flags.initgend = availindex;
488 free((genericptr_t) choices), choices = 0;
489 } else {
490 Sprintf(qbuf, "Your %s gender?", s_suffix(plbuf));
491 popup =
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);
496 ps_selected = -1;
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);
503 nh_XtPopdown(popup);
504 XtDestroyWidget(popup);
505 free((genericptr_t) choices), choices = 0;
507 if (ps_selected == PS_QUIT || program_state.done_hup) {
508 clearlocks();
509 X11_exit_nhwindows((char *) 0);
510 terminate(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",
515 ps_selected);
516 } else {
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,
524 flags.initalign);
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);
530 break;
532 /* select an alignment */
533 num_algns = 3; /* aligns[3] isn't allowed */
534 choices = (const char **) alloc(sizeof(char *) * num_algns);
535 for (;;) {
536 availcount = availindex = 0;
537 for (i = 0; i < num_algns; i++) {
538 choices[i] = 0;
539 if (ok_align(flags.initrole, flags.initrace, flags.initgend,
540 i)) {
541 choices[i] = aligns[i].adj;
542 ++availcount;
543 availindex = i; /* used iff only one */
546 if (availcount > 0)
547 break;
548 else
549 panic("no available role+race+gender+ALIGNMENT combinations");
552 if (availcount == 1) {
553 flags.initalign = availindex;
554 free((genericptr_t) choices), choices = 0;
555 } else {
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);
562 ps_selected = -1;
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);
569 nh_XtPopdown(popup);
570 XtDestroyWidget(popup);
571 free((genericptr_t) choices), choices = 0;
573 if (ps_selected == PS_QUIT || program_state.done_hup) {
574 clearlocks();
575 X11_exit_nhwindows((char *) 0);
576 terminate(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",
581 ps_selected);
582 } else {
583 flags.initalign = ps_selected;
585 } /* more than one alignment choice available */
590 X11_get_ext_cmd()
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;
608 void
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 --------------------------------------------------------
622 /* ARGSUSED */
623 static void
624 extend_select(w, client_data, call_data)
625 Widget w;
626 XtPointer client_data, call_data;
628 int selected = (int) (ptrdiff_t) client_data;
630 nhUse(w);
631 nhUse(call_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]);
638 /* select new one */
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]);
646 ec_active = FALSE;
647 exit_x_event = TRUE; /* leave event loop */
650 /* ARGSUSED */
651 static void
652 extend_dismiss(w, client_data, call_data)
653 Widget w;
654 XtPointer client_data, call_data;
656 nhUse(w);
657 nhUse(client_data);
658 nhUse(call_data);
660 ec_dismiss();
663 /* ARGSUSED */
664 static void
665 extend_help(w, client_data, call_data)
666 Widget w;
667 XtPointer client_data, call_data;
669 nhUse(w);
670 nhUse(client_data);
671 nhUse(call_data);
673 /* We might need to make it known that we already have one listed. */
674 (void) doextlist();
677 /* ARGSUSED */
678 void
679 ec_delete(w, event, params, num_params)
680 Widget w;
681 XEvent *event;
682 String *params;
683 Cardinal *num_params;
685 if (w == extended_command_popup) {
686 ec_dismiss();
687 } else {
688 popup_delete(w, event, params, num_params);
692 /* ARGSUSED */
693 static void
694 popup_delete(w, event, params, num_params)
695 Widget w;
696 XEvent *event;
697 String *params;
698 Cardinal *num_params;
700 nhUse(event);
701 nhUse(params);
702 nhUse(num_params);
704 ps_selected = PS_QUIT;
705 nh_XtPopdown(w);
706 exit_x_event = TRUE; /* leave event loop */
709 static void
710 ec_dismiss()
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);
717 ec_active = FALSE;
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 */
723 static void
724 ec_scroll_to_view(ec_indx)
725 int ec_indx; /* might be greater than extended_cmd_selected */
727 Widget viewport, scrollbar, tmpw;
728 Arg args[5];
729 Cardinal num_args;
730 Position lo_y, hi_y; /* ext cmd label y */
731 float s_shown, s_top; /* scrollbar pos */
732 float s_min, s_max;
733 Dimension h, hh, wh, vh; /* widget and viewport heights */
734 Dimension border_width;
735 int distance = 0;
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.
753 if (force_top)
754 ec_indx = 0;
756 /* get viewport and scrollbar widgets */
757 tmpw = extended_commands[ec_indx];
758 viewport = XtParent(tmpw);
759 do {
760 scrollbar = XtNameToWidget(tmpw, "*vertical");
761 if (scrollbar)
762 break;
763 tmpw = XtParent(tmpw);
764 } while (tmpw);
766 if (scrollbar && viewport) {
767 /* get selected ext command label y position and height */
768 num_args = 0;
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);
772 num_args++;
773 XtSetArg(args[num_args], nhStr(XtNdefaultDistance), &distance);
774 num_args++;
775 XtGetValues(extended_commands[ec_indx], args, num_args);
776 if (distance < 1 || distance > 32766) /* defaultDistance is weird */
777 distance = 4;
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);
784 } else
785 lo_y = hi_y;
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;
797 if (wh > hh)
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 */
804 num_args = 0;
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);
809 s_min = s_top * vh;
810 s_max = (s_top + s_shown) * vh;
812 /* scroll if outside the view */
813 if (force_top) {
814 s_min = 0.0;
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);
826 /* ARGSUSED */
827 void
828 ec_key(w, event, params, num_params)
829 Widget w;
830 XEvent *event;
831 String *params;
832 Cardinal *num_params;
834 char ch;
835 int i;
836 int pass;
837 XKeyEvent *xkey = (XKeyEvent *) event;
839 nhUse(w);
840 nhUse(params);
841 nhUse(num_params);
843 ch = key_event_to_char(xkey);
845 if (ch == '\0') { /* don't accept nul char/modifier event */
846 /* don't beep */
847 return;
848 } else if (ch == '?') {
849 extend_help((Widget) 0, (XtPointer) 0, (XtPointer) 0);
850 return;
851 } else if (index("\033\n\r", ch)) {
852 if (ch == '\033') {
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 */
865 ec_active = FALSE;
866 return;
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
874 * the whole word.
876 if (ec_active && (xkey->time - ec_time) > 2500) /* 2.5 seconds */
877 ec_active = FALSE;
879 if (!ec_active) {
880 ec_nchars = 0;
881 ec_active = TRUE;
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++) {
890 if (pass == 1) {
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];
897 ec_nchars = 1;
899 for (i = 0; extcmdlist[i].ef_txt; i++) {
900 if (extcmdlist[i].ef_txt[0] == '?')
901 continue;
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 */
915 do {
916 if (!extcmdlist[i + 1].ef_txt
917 || *extcmdlist[i + 1].ef_txt == '?')
918 break; /* end of list */
919 ++i;
920 } while (!strncmp(ec_chars, extcmdlist[i].ef_txt, ec_nchars));
922 ec_scroll_to_view(i);
923 return;
930 * Use our own home-brewed version menu because simpleMenu is designed to
931 * be used from a menubox.
933 static void
934 init_extended_commands_popup()
936 int i, num_commands;
937 const char **command_list;
939 /* count commands */
940 for (num_commands = 0; extcmdlist[num_commands].ef_txt; num_commands++)
941 ; /* do nothing */
943 /* If the last entry is "help", don't use it. */
944 if (strcmp(extcmdlist[num_commands - 1].ef_txt, "?") == 0)
945 --num_commands;
947 command_list =
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:
968 * popup_label
969 * ----------- ------------
970 * |left_name| |right_name|
971 * ----------- ------------
972 * ------------------------
973 * | name1 |
974 * ------------------------
975 * ------------------------
976 * | name2 |
977 * ------------------------
980 * ------------------------
981 * | nameN |
982 * ------------------------
984 static Widget
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;
995 int num_names;
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;
1003 int i;
1004 Arg args[8];
1005 Cardinal num_args;
1006 Dimension width, other_width, max_width, border_width,
1007 height, cumulative_height, screen_height;
1008 int distance, skip;
1010 commands = (Widget *) alloc((unsigned) num_names * sizeof (Widget));
1012 num_args = 0;
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()"));
1019 num_args = 0;
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,
1025 args, num_args);
1027 num_args = 0;
1028 XtSetArg(args[num_args], XtNtranslations,
1029 XtParseTranslationTable(popup_translations)); num_args++;
1030 *formp = form = XtCreateManagedWidget("menuform", formWidgetClass, view,
1031 args, num_args);
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.)
1039 num_args = 0;
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)
1044 distance = 4;
1047 * Create the label.
1049 num_args = 0;
1050 XtSetArg(args[num_args], XtNborderWidth, 0); num_args++;
1051 label = XtCreateManagedWidget(popup_label, labelWidgetClass, form, args,
1052 num_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.
1062 num_args = 0;
1063 XtSetArg(args[num_args], nhStr(XtNfromVert), label); num_args++;
1064 #if 0
1065 XtSetArg(args[num_args], nhStr(XtNshapeStyle),
1066 XmuShapeRoundedRectangle); num_args++;
1067 #endif
1068 left = XtCreateManagedWidget(left_name, commandWidgetClass, form, args,
1069 num_args);
1070 XtAddCallback(left, XtNcallback, left_callback, (XtPointer) 0);
1071 skip = (distance < 4) ? 8 : 2 * distance;
1073 num_args = 0;
1074 XtSetArg(args[0], XtNheight, &height);
1075 XtGetValues(left, args, ONE);
1076 cumulative_height += distance + height + 2 * border_width;
1079 * Create right button.
1081 num_args = 0;
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++;
1085 #if 0
1086 XtSetArg(args[num_args], nhStr(XtNshapeStyle),
1087 XmuShapeRoundedRectangle); num_args++;
1088 #endif
1089 right = XtCreateManagedWidget(right_name, commandWidgetClass, form, args,
1090 num_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])
1101 continue;
1102 num_args = 0;
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;
1108 } else
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);
1116 above = *curr++;
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)
1136 max_width = width;
1138 /* Finally, the commands. */
1139 for (i = 0, curr = commands; i < num_names; i++) {
1140 if (!widget_names[i])
1141 continue;
1142 XtSetArg(args[0], XtNwidth, &width);
1143 XtGetValues(*curr, args, ONE);
1144 if (width > max_width)
1145 max_width = width;
1146 curr++;
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.
1155 if (max_width % 2)
1156 ++max_width;
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);
1179 } else {
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])
1191 continue;
1192 XtSetArg(args[0], XtNwidth, max_width);
1193 XtSetValues(*curr, args, ONE);
1194 curr++;
1197 if (command_widgets)
1198 *command_widgets = commands;
1199 else
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 */
1212 num_args = 0;
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'))
1223 swap_fg_bg(right);
1225 return popup;
1228 /*winmisc.c*/