OSDN Git Service

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