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  | }