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
ps_quit(w,client_data,call_data)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
ps_random(w,client_data,call_data)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
ps_select(w,client_data,call_data)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
ps_key(w,event,params,num_params)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
race_key(w,event,params,num_params)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
gend_key(w,event,params,num_params)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
algn_key(w,event,params,num_params)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
X11_player_selection()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
X11_get_ext_cmd()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
extend_select(w,client_data,call_data)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
extend_dismiss(w,client_data,call_data)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
extend_help(w,client_data,call_data)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
ec_delete(w,event,params,num_params)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
popup_delete(w,event,params,num_params)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
ec_dismiss()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
ec_key(w,event,params,num_params)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
init_extended_commands_popup()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
make_menu(popup_name,popup_label,popup_translations,left_name,left_callback,right_name,right_callback,num_names,widget_names,command_widgets,name_callback,formp)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 }
929