1 | /* SCCS Id: @(#)winmisc.c 3.3 2000/05/21 */
2 | /* Copyright (c) Dean Luick, 1992 */
3 | /* NetHack may be freely redistributed. See license for details. */
4 |
5 | /*
6 | * Misc. popup windows: player selection and extended commands.
7 | *
8 | * + Global functions: player_selection() and get_ext_cmd().
9 | */
10 |
11 | #ifndef SYSV
12 | #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
13 | #endif
14 |
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>
24 |
25 | #ifdef PRESERVE_NO_SYSV
26 | # ifdef SYSV
27 | # undef SYSV
28 | # endif
29 | # undef PRESERVE_NO_SYSV
30 | #endif
31 |
32 | #include "hack.h"
33 | #include "func_tab.h"
34 | #include "winX.h"
35 |
36 |
37 | static Widget extended_command_popup;
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)
43 | #define PS_QUIT (-75)
44 | static const char ps_randchars[] = "*@";
45 | static const char ps_quitchars[] = "\033qQ";
46 |
47 | #define EC_NCHARS 32
48 | static boolean ec_active = FALSE;
49 | static int ec_nchars = 0;
50 | static char ec_chars[EC_NCHARS];
51 | static Time ec_time;
52 |
53 | static const char extended_command_translations[] =
54 | "#override\n\
55 | <Key>: ec_key()";
56 |
57 | static const char player_select_translations[] =
58 | "#override\n\
59 | <Key>: ps_key()";
60 | static const char race_select_translations[] =
61 | "#override\n\
62 | <Key>: race_key()";
63 | static const char gend_select_translations[] =
64 | "#override\n\
65 | <Key>: gend_key()";
66 | static const char algn_select_translations[] =
67 | "#override\n\
68 | <Key>: algn_key()";
69 |
70 | static void NDECL(ec_dismiss);
71 | static Widget FDECL(make_menu, (const char *,const char *,const char *,
72 | const char *,XtCallbackProc,
73 | const char *,XtCallbackProc,
74 | int,const char **, Widget **,
75 | XtCallbackProc,Widget *));
76 | static void NDECL(init_extended_commands_popup);
77 | static void FDECL(ps_quit, (Widget,XtPointer,XtPointer));
78 | static void FDECL(ps_random, (Widget,XtPointer,XtPointer));
79 | static void FDECL(ps_select, (Widget,XtPointer,XtPointer));
80 |
81 |
82 | /* Player Selection -------------------------------------------------------- */
83 | /* ARGSUSED */
84 | static void
85 | ps_quit(w, client_data, call_data)
86 | Widget w;
87 | XtPointer client_data, call_data;
88 | {
89 | ps_selected = PS_QUIT;
90 | exit_x_event = TRUE; /* leave event loop */
91 | }
92 |
93 | /* ARGSUSED */
94 | static void
95 | ps_random(w, client_data, call_data)
96 | Widget w;
97 | XtPointer client_data, call_data;
98 | {
99 | ps_selected = PS_RANDOM;
100 | exit_x_event = TRUE; /* leave event loop */
101 | }
102 |
103 | /* ARGSUSED */
104 | static void
105 | ps_select(w, client_data, call_data)
106 | Widget w;
107 | XtPointer client_data, call_data;
108 | {
109 | ps_selected = (int) client_data;
110 | exit_x_event = TRUE; /* leave event loop */
111 | }
112 |
113 | /* ARGSUSED */
114 | void
115 | ps_key(w, event, params, num_params)
116 | Widget w;
117 | XEvent *event;
118 | String *params;
119 | Cardinal *num_params;
120 | {
121 | char ch, *mark;
122 | char rolechars[QBUFSZ];
123 | int i;
124 |
125 | (void)memset(rolechars, '\0', sizeof rolechars); /* for index() */
126 | for (i = 0; roles[i].name.m; ++i) {
127 | ch = lowc(*roles[i].name.m);
128 | /* if (flags.female && roles[i].name.f) ch = lowc(*roles[i].name.f); */
129 | /* this supports at most two roles with the same first letter */
130 | if (index(rolechars, ch)) ch = highc(ch);
131 | rolechars[i] = ch;
132 | }
133 | ch = key_event_to_char((XKeyEvent *) event);
134 | if (ch == '\0') { /* don't accept nul char/modifier event */
135 | /* don't beep */
136 | return;
137 | }
138 | mark = index(rolechars, ch);
139 | if (!mark) mark = index(rolechars, lowc(ch));
140 | if (!mark) mark = index(rolechars, highc(ch));
141 | if (!mark) {
142 | if (index(ps_randchars, ch))
143 | ps_selected = PS_RANDOM;
144 | else if (index(ps_quitchars, ch))
145 | ps_selected = PS_QUIT;
146 | else {
147 | X11_nhbell(); /* no such class */
148 | return;
149 | }
150 | } else
151 | ps_selected = (int)(mark - rolechars);
152 | exit_x_event = TRUE;
153 | }
154 |
155 | /* ARGSUSED */
156 | void
157 | race_key(w, event, params, num_params)
158 | Widget w;
159 | XEvent *event;
160 | String *params;
161 | Cardinal *num_params;
162 | {
163 | char ch, *mark;
164 | char racechars[QBUFSZ];
165 | int i;
166 |
167 | (void)memset(racechars, '\0', sizeof racechars); /* for index() */
168 | for (i = 0; races[i].noun; ++i) {
169 | ch = lowc(*races[i].noun);
170 | /* this supports at most two races with the same first letter */
171 | if (index(racechars, ch)) ch = highc(ch);
172 | racechars[i] = ch;
173 | }
174 | ch = key_event_to_char((XKeyEvent *) event);
175 | if (ch == '\0') { /* don't accept nul char/modifier event */
176 | /* don't beep */
177 | return;
178 | }
179 | mark = index(racechars, ch);
180 | if (!mark) mark = index(racechars, lowc(ch));
181 | if (!mark) mark = index(racechars, highc(ch));
182 | if (!mark) {
183 | if (index(ps_randchars, ch))
184 | ps_selected = PS_RANDOM;
185 | else if (index(ps_quitchars, ch))
186 | ps_selected = PS_QUIT;
187 | else {
188 | X11_nhbell(); /* no such race */
189 | return;
190 | }
191 | } else
192 | ps_selected = (int)(mark - racechars);
193 | exit_x_event = TRUE;
194 | }
195 |
196 | /* ARGSUSED */
197 | void
198 | gend_key(w, event, params, num_params)
199 | Widget w;
200 | XEvent *event;
201 | String *params;
202 | Cardinal *num_params;
203 | {
204 | char ch, *mark;
205 | static char gendchars[] = "mf";
206 |
207 | ch = key_event_to_char((XKeyEvent *) event);
208 | if (ch == '\0') { /* don't accept nul char/modifier event */
209 | /* don't beep */
210 | return;
211 | }
212 | mark = index(gendchars, ch);
213 | if (!mark) mark = index(gendchars, lowc(ch));
214 | if (!mark) {
215 | if (index(ps_randchars, ch))
216 | ps_selected = PS_RANDOM;
217 | else if (index(ps_quitchars, ch))
218 | ps_selected = PS_QUIT;
219 | else {
220 | X11_nhbell(); /* no such gender */
221 | return;
222 | }
223 | } else
224 | ps_selected = (int)(mark - gendchars);
225 | exit_x_event = TRUE;
226 | }
227 |
228 | /* ARGSUSED */
229 | void
230 | algn_key(w, event, params, num_params)
231 | Widget w;
232 | XEvent *event;
233 | String *params;
234 | Cardinal *num_params;
235 | {
236 | char ch, *mark;
237 | static char algnchars[] = "LNC";
238 |
239 | ch = key_event_to_char((XKeyEvent *) event);
240 | if (ch == '\0') { /* don't accept nul char/modifier event */
241 | /* don't beep */
242 | return;
243 | }
244 | mark = index(algnchars, ch);
245 | if (!mark) mark = index(algnchars, highc(ch));
246 | if (!mark) {
247 | if (index(ps_randchars, ch))
248 | ps_selected = PS_RANDOM;
249 | else if (index(ps_quitchars, ch))
250 | ps_selected = PS_QUIT;
251 | else {
252 | X11_nhbell(); /* no such alignment */
253 | return;
254 | }
255 | } else
256 | ps_selected = (int)(mark - algnchars);
257 | exit_x_event = TRUE;
258 | }
259 |
260 | /* Global functions ========================================================= */
261 | void
262 | X11_player_selection()
263 | {
264 | int num_roles, num_races, num_gends, num_algns,
265 | i, availcount, availindex;
266 | Widget popup, player_form;
267 | const char **choices;
268 | const char *namep;
269 | char qbuf[QBUFSZ];
270 |
271 | while (flags.initrole < 0) {
272 | if (flags.initrole == ROLE_RANDOM) {
273 | flags.initrole = pick_role(flags.initrace,
274 | flags.initgend, flags.initalign);
275 | break;
276 | }
277 | /* select a role */
278 | for (num_roles = 0; roles[num_roles].name.m; ++num_roles) continue;
279 | choices = (const char **)alloc(sizeof(char *) * num_roles);
280 | for (;;) {
281 | availcount = 0;
282 | for (i = 0; i < num_roles; i++) {
283 | choices[i] = 0;
284 | if (ok_role(i, flags.initrace,
285 | flags.initgend, flags.initalign)) {
286 | choices[i] = roles[i].name.m;
287 | if (flags.initgend >= 0 && flags.female && roles[i].name.f)
288 | choices[i] = roles[i].name.f;
289 | ++availcount;
290 | }
291 | }
292 | if (availcount > 0) break;
293 | else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
294 | else if (flags.initgend >= 0) flags.initgend = -1;
295 | else if (flags.initrace >= 0) flags.initrace = -1;
296 | else panic("no available ROLE+race+gender+alignment combinations");
297 | }
298 | popup = make_menu("player_selection", "Choose a Role",
299 | player_select_translations,
300 | "quit", ps_quit,
301 | "random", ps_random,
302 | num_roles, choices, (Widget **)0, ps_select, &player_form);
303 |
304 | ps_selected = -1;
305 | positionpopup(popup, FALSE);
306 | nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
307 |
308 | /* The callbacks will enable the event loop exit. */
309 | (void) x_event(EXIT_ON_EXIT);
310 |
311 | nh_XtPopdown(popup);
312 | XtDestroyWidget(popup);
313 | free((genericptr_t)choices), choices = 0;
314 |
315 | if (ps_selected == PS_QUIT) {
316 | clearlocks();
317 | X11_exit_nhwindows((char *)0);
318 | terminate(0);
319 | } else if (ps_selected == PS_RANDOM) {
320 | flags.initrole = ROLE_RANDOM;
321 | } else if (ps_selected < 0 || ps_selected >= num_roles) {
322 | panic("player_selection: bad role select value %d\n", ps_selected);
323 | } else {
324 | flags.initrole = ps_selected;
325 | }
326 | }
327 |
328 | while (!validrace(flags.initrole, flags.initrace)) {
329 | if (flags.initrace == ROLE_RANDOM) {
330 | flags.initrace = pick_race(flags.initrole,
331 | flags.initgend, flags.initalign);
332 | break;
333 | }
334 | /* select a race */
335 | for (num_races = 0; races[num_races].noun; ++num_races) continue;
336 | choices = (const char **)alloc(sizeof(char *) * num_races);
337 | for (;;) {
338 | availcount = availindex = 0;
339 | for (i = 0; i < num_races; i++) {
340 | choices[i] = 0;
341 | if (ok_race(flags.initrole, i,
342 | flags.initgend, flags.initalign)) {
343 | choices[i] = races[i].noun;
344 | ++availcount;
345 | availindex = i; /* used iff only one */
346 | }
347 | }
348 | if (availcount > 0) break;
349 | else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
350 | else if (flags.initgend >= 0) flags.initgend = -1;
351 | else panic("no available role+RACE+gender+alignment combinations");
352 | }
353 |
354 | if (availcount == 1) {
355 | flags.initrace = availindex;
356 | free((genericptr_t)choices), choices = 0;
357 | } else {
358 | namep = roles[flags.initrole].name.m;
359 | if (flags.initgend >= 0 && flags.female &&
360 | roles[flags.initrole].name.f)
361 | namep = roles[flags.initrole].name.f;
362 | Sprintf(qbuf, "Pick your %s race", s_suffix(namep));
363 | popup = make_menu("race_selection", qbuf,
364 | race_select_translations,
365 | "quit", ps_quit,
366 | "random", ps_random,
367 | num_races, choices, (Widget **)0,
368 | ps_select, &player_form);
369 |
370 | ps_selected = -1;
371 | positionpopup(popup, FALSE);
372 | nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
373 |
374 | /* The callbacks will enable the event loop exit. */
375 | (void) x_event(EXIT_ON_EXIT);
376 |
377 | nh_XtPopdown(popup);
378 | XtDestroyWidget(popup);
379 | free((genericptr_t)choices), choices = 0;
380 |
381 | if (ps_selected == PS_QUIT) {
382 | clearlocks();
383 | X11_exit_nhwindows((char *)0);
384 | terminate(0);
385 | } else if (ps_selected == PS_RANDOM) {
386 | flags.initrace = ROLE_RANDOM;
387 | } else if (ps_selected < 0 || ps_selected >= num_races) {
388 | panic("player_selection: bad race select value %d\n", ps_selected);
389 | } else {
390 | flags.initrace = ps_selected;
391 | }
392 | } /* more than one race choice available */
393 | }
394 |
395 | while (!validgend(flags.initrole, flags.initrace, flags.initgend)) {
396 | if (flags.initgend == ROLE_RANDOM) {
397 | flags.initgend = pick_gend(flags.initrole, flags.initrace,
398 | flags.initalign);
399 | break;
400 | }
401 | /* select a gender */
402 | num_gends = 2; /* genders[2] isn't allowed */
403 | choices = (const char **)alloc(sizeof(char *) * num_gends);
404 | for (;;) {
405 | availcount = availindex = 0;
406 | for (i = 0; i < num_gends; i++) {
407 | choices[i] = 0;
408 | if (ok_gend(flags.initrole, flags.initrace,
409 | i, flags.initalign)) {
410 | choices[i] = genders[i].adj;
411 | ++availcount;
412 | availindex = i; /* used iff only one */
413 | }
414 | }
415 | if (availcount > 0) break;
416 | else if (flags.initalign >= 0) flags.initalign = -1; /* reset */
417 | else panic("no available role+race+GENDER+alignment combinations");
418 | }
419 |
420 | if (availcount == 1) {
421 | flags.initgend = availindex;
422 | free((genericptr_t)choices), choices = 0;
423 | } else {
424 | namep = roles[flags.initrole].name.m;
425 | if (flags.initgend >= 0 && flags.female &&
426 | roles[flags.initrole].name.f)
427 | namep = roles[flags.initrole].name.f;
428 | Sprintf(qbuf, "Your %s %s gender?",
429 | races[flags.initrace].adj, s_suffix(namep));
430 | popup = make_menu("gender_selection", qbuf,
431 | gend_select_translations,
432 | "quit", ps_quit,
433 | "random", ps_random,
434 | num_gends, choices, (Widget **)0,
435 | ps_select, &player_form);
436 |
437 | ps_selected = -1;
438 | positionpopup(popup, FALSE);
439 | nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
440 |
441 | /* The callbacks will enable the event loop exit. */
442 | (void) x_event(EXIT_ON_EXIT);
443 |
444 | nh_XtPopdown(popup);
445 | XtDestroyWidget(popup);
446 | free((genericptr_t)choices), choices = 0;
447 |
448 | if (ps_selected == PS_QUIT) {
449 | clearlocks();
450 | X11_exit_nhwindows((char *)0);
451 | terminate(0);
452 | } else if (ps_selected == PS_RANDOM) {
453 | flags.initgend = ROLE_RANDOM;
454 | } else if (ps_selected < 0 || ps_selected >= num_gends) {
455 | panic("player_selection: bad gender select value %d\n", ps_selected);
456 | } else {
457 | flags.initgend = ps_selected;
458 | }
459 | } /* more than one gender choice available */
460 | }
461 |
462 | while (!validalign(flags.initrole, flags.initrace, flags.initalign)) {
463 | if (flags.initalign == ROLE_RANDOM) {
464 | flags.initalign = pick_align(flags.initrole, flags.initrace,
465 | flags.initgend);
466 | break;
467 | }
468 | /* select an alignment */
469 | num_algns = 3; /* aligns[3] isn't allowed */
470 | choices = (const char **)alloc(sizeof(char *) * num_algns);
471 | for (;;) {
472 | availcount = availindex = 0;
473 | for (i = 0; i < num_algns; i++) {
474 | choices[i] = 0;
475 | if (ok_align(flags.initrole, flags.initrace,
476 | flags.initgend, i)) {
477 | choices[i] = aligns[i].adj;
478 | ++availcount;
479 | availindex = i; /* used iff only one */
480 | }
481 | }
482 | if (availcount > 0) break;
483 | else panic("no available role+race+gender+ALIGNMENT combinations");
484 | }
485 |
486 | if (availcount == 1) {
487 | flags.initalign = availindex;
488 | free((genericptr_t)choices), choices = 0;
489 | } else {
490 | namep = roles[flags.initrole].name.m;
491 | if (flags.initgend >= 0 && flags.female &&
492 | roles[flags.initrole].name.f)
493 | namep = roles[flags.initrole].name.f;
494 | Sprintf(qbuf, "%s %s %s alignment?",
495 | genders[flags.initgend].adj,
496 | races[flags.initrace].adj, s_suffix(namep));
497 | qbuf[0] = highc(qbuf[0]);
498 | popup = make_menu("alignment_selection", qbuf,
499 | algn_select_translations,
500 | "quit", ps_quit,
501 | "random", ps_random,
502 | num_algns, choices, (Widget **)0,
503 | ps_select, &player_form);
504 |
505 | ps_selected = -1;
506 | positionpopup(popup, FALSE);
507 | nh_XtPopup(popup, (int)XtGrabExclusive, player_form);
508 |
509 | /* The callbacks will enable the event loop exit. */
510 | (void) x_event(EXIT_ON_EXIT);
511 |
512 | nh_XtPopdown(popup);
513 | XtDestroyWidget(popup);
514 | free((genericptr_t)choices), choices = 0;
515 |
516 | if (ps_selected == PS_QUIT) {
517 | clearlocks();
518 | X11_exit_nhwindows((char *)0);
519 | terminate(0);
520 | } else if (ps_selected == PS_RANDOM) {
521 | flags.initalign = ROLE_RANDOM;
522 | } else if (ps_selected < 0 || ps_selected >= num_algns) {
523 | panic("player_selection: bad alignment select value %d\n", ps_selected);
524 | } else {
525 | flags.initalign = ps_selected;
526 | }
527 | } /* more than one alignment choice available */
528 | }
529 | }
530 |
531 |
532 | int
533 | X11_get_ext_cmd()
534 | {
535 | static Boolean initialized = False;
536 |
537 | if (!initialized) {
538 | init_extended_commands_popup();
539 | initialized = True;
540 | }
541 |
542 | extended_command_selected = -1; /* reset selected value */
543 |
544 | positionpopup(extended_command_popup, FALSE); /* center on cursor */
545 | nh_XtPopup(extended_command_popup, (int)XtGrabExclusive,
546 | extended_command_form);
547 |
548 | /* The callbacks will enable the event loop exit. */
549 | (void) x_event(EXIT_ON_EXIT);
550 |
551 | return extended_command_selected;
552 | }
553 |
554 | /* End global functions ===================================================== */
555 |
556 | /* Extended Command -------------------------------------------------------- */
557 | /* ARGSUSED */
558 | static void
559 | extend_select(w, client_data, call_data)
560 | Widget w;
561 | XtPointer client_data, call_data;
562 | {
563 | int selected = (int) client_data;
564 |
565 | if (extended_command_selected != selected) {
566 | /* visibly deselect old one */
567 | if (extended_command_selected >= 0)
568 | swap_fg_bg(extended_commands[extended_command_selected]);
569 |
570 | /* select new one */
571 | swap_fg_bg(extended_commands[selected]);
572 | extended_command_selected = selected;
573 | }
574 |
575 | nh_XtPopdown(extended_command_popup);
576 | /* reset colors while popped down */
577 | swap_fg_bg(extended_commands[extended_command_selected]);
578 | ec_active = FALSE;
579 | exit_x_event = TRUE; /* leave event loop */
580 | }
581 |
582 | /* ARGSUSED */
583 | static void
584 | extend_dismiss(w, client_data, call_data)
585 | Widget w;
586 | XtPointer client_data, call_data;
587 | {
588 | ec_dismiss();
589 | }
590 |
591 | /* ARGSUSED */
592 | static void
593 | extend_help(w, client_data, call_data)
594 | Widget w;
595 | XtPointer client_data, call_data;
596 | {
597 | /* We might need to make it known that we already have one listed. */
598 | (void) doextlist();
599 | }
600 |
601 | /* ARGSUSED */
602 | void
603 | ec_delete(w, event, params, num_params)
604 | Widget w;
605 | XEvent *event;
606 | String *params;
607 | Cardinal *num_params;
608 | {
609 | ec_dismiss();
610 | }
611 |
612 | static void
613 | ec_dismiss()
614 | {
615 | /* unselect while still visible */
616 | if (extended_command_selected >= 0)
617 | swap_fg_bg(extended_commands[extended_command_selected]);
618 | extended_command_selected = -1; /* dismiss */
619 | nh_XtPopdown(extended_command_popup);
620 | ec_active = FALSE;
621 | exit_x_event = TRUE; /* leave event loop */
622 | }
623 |
624 | /* ARGSUSED */
625 | void
626 | ec_key(w, event, params, num_params)
627 | Widget w;
628 | XEvent *event;
629 | String *params;
630 | Cardinal *num_params;
631 | {
632 | char ch;
633 | int i;
634 | XKeyEvent *xkey = (XKeyEvent *) event;
635 |
636 | ch = key_event_to_char(xkey);
637 |
638 | if (ch == '\0') { /* don't accept nul char/modifier event */
639 | /* don't beep */
640 | return;
641 | } else if (index("\033\n\r", ch)) {
642 | if (ch == '\033') {
643 | /* unselect while still visible */
644 | if (extended_command_selected >= 0)
645 | swap_fg_bg(extended_commands[extended_command_selected]);
646 | extended_command_selected = -1; /* dismiss */
647 | }
648 |
649 | nh_XtPopdown(extended_command_popup);
650 | /* unselect while invisible */
651 | if (extended_command_selected >= 0)
652 | swap_fg_bg(extended_commands[extended_command_selected]);
653 |
654 | exit_x_event = TRUE; /* leave event loop */
655 | ec_active = FALSE;
656 | return;
657 | }
658 |
659 | /* too much time has elapsed */
660 | if ((xkey->time - ec_time) > 500)
661 | ec_active = FALSE;
662 |
663 | if (!ec_active) {
664 | ec_nchars = 0;
665 | ec_active = TRUE;
666 | }
667 |
668 | ec_time = xkey->time;
669 | ec_chars[ec_nchars++] = ch;
670 | if (ec_nchars >= EC_NCHARS)
671 | ec_nchars = EC_NCHARS-1; /* don't overflow */
672 |
673 | for (i = 0; extcmdlist[i].ef_txt; i++) {
674 | if (extcmdlist[i].ef_txt[0] == '?') continue;
675 |
676 | if (!strncmp(ec_chars, extcmdlist[i].ef_txt, ec_nchars)) {
677 | if (extended_command_selected != i) {
678 | /* I should use set() and unset() actions, but how do */
679 | /* I send the an action to the widget? */
680 | if (extended_command_selected >= 0)
681 | swap_fg_bg(extended_commands[extended_command_selected]);
682 | extended_command_selected = i;
683 | swap_fg_bg(extended_commands[extended_command_selected]);
684 | }
685 | break;
686 | }
687 | }
688 | }
689 |
690 | /*
691 | * Use our own home-brewed version menu because simpleMenu is designed to
692 | * be used from a menubox.
693 | */
694 | static void
695 | init_extended_commands_popup()
696 | {
697 | int i, num_commands;
698 | const char **command_list;
699 |
700 | /* count commands */
701 | for (num_commands = 0; extcmdlist[num_commands].ef_txt; num_commands++)
702 | ; /* do nothing */
703 |
704 | /* If the last entry is "help", don't use it. */
705 | if (strcmp(extcmdlist[num_commands-1].ef_txt, "?") == 0)
706 | --num_commands;
707 |
708 | command_list =
709 | (const char **) alloc((unsigned)num_commands * sizeof(char *));
710 |
711 | for (i = 0; i < num_commands; i++)
712 | command_list[i] = extcmdlist[i].ef_txt;
713 |
714 | extended_command_popup = make_menu("extended_commands",
715 | "Extended Commands",
716 | extended_command_translations,
717 | "dismiss", extend_dismiss,
718 | "help", extend_help,
719 | num_commands, command_list, &extended_commands,
720 | extend_select, &extended_command_form);
721 |
722 | free((char *)command_list);
723 | }
724 |
725 | /* ------------------------------------------------------------------------- */
726 |
727 | /*
728 | * Create a popup widget of the following form:
729 | *
730 | * popup_label
731 | * ----------- ------------
732 | * |left_name| |right_name|
733 | * ----------- ------------
734 | * ------------------------
735 | * | name1 |
736 | * ------------------------
737 | * ------------------------
738 | * | name2 |
739 | * ------------------------
740 | * .
741 | * .
742 | * ------------------------
743 | * | nameN |
744 | * ------------------------
745 | */
746 | static Widget
747 | make_menu(popup_name, popup_label, popup_translations,
748 | left_name, left_callback,
749 | right_name, right_callback,
750 | num_names, widget_names, command_widgets, name_callback, formp)
751 | const char *popup_name;
752 | const char *popup_label;
753 | const char *popup_translations;
754 | const char *left_name;
755 | XtCallbackProc left_callback;
756 | const char *right_name;
757 | XtCallbackProc right_callback;
758 | int num_names;
759 | const char **widget_names; /* return array of command widgets */
760 | Widget **command_widgets;
761 | XtCallbackProc name_callback;
762 | Widget *formp; /* return */
763 | {
764 | Widget popup, form, label, above, left, right;
765 | Widget *commands, *curr;
766 | int i;
767 | Arg args[8];
768 | Cardinal num_args;
769 | Dimension width, max_width;
770 | int distance, skip;
771 |
772 |
773 | commands = (Widget *) alloc((unsigned)num_names * sizeof(Widget));
774 |
775 |
776 | num_args = 0;
777 | XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
778 |
779 | popup = XtCreatePopupShell(popup_name,
780 | transientShellWidgetClass,
781 | toplevel, args, num_args);
782 | XtOverrideTranslations(popup,
783 | XtParseTranslationTable("<Message>WM_PROTOCOLS: ec_delete()"));
784 |
785 | num_args = 0;
786 | XtSetArg(args[num_args], XtNtranslations,
787 | XtParseTranslationTable(popup_translations)); num_args++;
788 | *formp = form = XtCreateManagedWidget("menuform",
789 | formWidgetClass,
790 | popup,
791 | args, num_args);
792 |
793 | /* Get the default distance between objects in the form widget. */
794 | num_args = 0;
795 | XtSetArg(args[num_args], XtNdefaultDistance, &distance); num_args++;
796 | XtGetValues(form, args, num_args);
797 |
798 | /*
799 | * Create the label.
800 | */
801 | num_args = 0;
802 | XtSetArg(args[num_args], XtNborderWidth, 0); num_args++;
803 | label = XtCreateManagedWidget(popup_label,
804 | labelWidgetClass,
805 | form,
806 | args, num_args);
807 |
808 | /*
809 | * Create the left button.
810 | */
811 | num_args = 0;
812 | XtSetArg(args[num_args], XtNfromVert, label); num_args++;
813 | /*
814 | XtSetArg(args[num_args], XtNshapeStyle,
815 | XmuShapeRoundedRectangle); num_args++;
816 | */
817 | left = XtCreateManagedWidget(left_name,
818 | commandWidgetClass,
819 | form,
820 | args, num_args);
821 | XtAddCallback(left, XtNcallback, left_callback, (XtPointer) 0);
822 | skip = 3*distance; /* triple the spacing */
823 | if(!skip) skip = 3;
824 |
825 | /*
826 | * Create right button.
827 | */
828 | num_args = 0;
829 | XtSetArg(args[num_args], XtNfromHoriz, left); num_args++;
830 | XtSetArg(args[num_args], XtNfromVert, label); num_args++;
831 | /*
832 | XtSetArg(args[num_args], XtNshapeStyle,
833 | XmuShapeRoundedRectangle); num_args++;
834 | */
835 | right = XtCreateManagedWidget(right_name,
836 | commandWidgetClass,
837 | form,
838 | args, num_args);
839 | XtAddCallback(right, XtNcallback, right_callback, (XtPointer) 0);
840 |
841 | XtInstallAccelerators(form, left);
842 | XtInstallAccelerators(form, right);
843 |
844 | /*
845 | * Create and place the command widgets.
846 | */
847 | for (i = 0, above = left, curr = commands; i < num_names; i++) {
848 | if (!widget_names[i]) continue;
849 | num_args = 0;
850 | XtSetArg(args[num_args], XtNfromVert, above); num_args++;
851 | if (above == left) {
852 | /* if first, we are farther apart */
853 | XtSetArg(args[num_args], XtNvertDistance, skip); num_args++;
854 | }
855 |
856 | *curr = XtCreateManagedWidget(widget_names[i],
857 | commandWidgetClass,
858 | form,
859 | args, num_args);
860 | XtAddCallback(*curr, XtNcallback, name_callback, (XtPointer) i);
861 | above = *curr++;
862 | }
863 |
864 | /*
865 | * Now find the largest width. Start with the width dismiss + help
866 | * buttons, since they are adjacent.
867 | */
868 | XtSetArg(args[0], XtNwidth, &max_width);
869 | XtGetValues(left, args, ONE);
870 | XtSetArg(args[0], XtNwidth, &width);
871 | XtGetValues(right, args, ONE);
872 | max_width = max_width + width + distance;
873 |
874 | /* Next, the title. */
875 | XtSetArg(args[0], XtNwidth, &width);
876 | XtGetValues(label, args, ONE);
877 | if (width > max_width) max_width = width;
878 |
879 | /* Finally, the commands. */
880 | for (i = 0, curr = commands; i < num_names; i++) {
881 | if (!widget_names[i]) continue;
882 | XtSetArg(args[0], XtNwidth, &width);
883 | XtGetValues(*curr, args, ONE);
884 | if (width > max_width) max_width = width;
885 | curr++;
886 | }
887 |
888 | /*
889 | * Finally, set all of the single line widgets to the largest width.
890 | */
891 | XtSetArg(args[0], XtNwidth, max_width);
892 | XtSetValues(label, args, ONE);
893 |
894 | for (i = 0, curr = commands; i < num_names; i++) {
895 | if (!widget_names[i]) continue;
896 | XtSetArg(args[0], XtNwidth, max_width);
897 | XtSetValues(*curr, args, ONE);
898 | curr++;
899 | }
900 |
901 | if (command_widgets)
902 | *command_widgets = commands;
903 | else
904 | free((char *) commands);
905 |
906 | XtRealizeWidget(popup);
907 | XSetWMProtocols(XtDisplay(popup), XtWindow(popup), &wm_delete_window, 1);
908 |
909 | return popup;
910 | }