1    | /*	SCCS Id: @(#)winmenu.c	3.3	96/08/15	*/
2    | /* Copyright (c) Dean Luick, 1992				  */
3    | /* NetHack may be freely redistributed.  See license for details. */
4    | 
5    | /*
6    |  * File for creating menus.
7    |  *
8    |  *	+ Global functions: start_menu, add_menu, end_menu, select_menu
9    |  */
10   | /*#define USE_FWF*/		/* use FWF's list widget */
11   | 
12   | #ifndef SYSV
13   | #define PRESERVE_NO_SYSV	/* X11 include files may define SYSV */
14   | #endif
15   | 
16   | #include <X11/Intrinsic.h>
17   | #include <X11/StringDefs.h>
18   | #include <X11/Shell.h>
19   | #include <X11/Xatom.h>
20   | #include <X11/Xaw/Label.h>
21   | #include <X11/Xaw/Command.h>
22   | #include <X11/Xaw/Viewport.h>
23   | #include <X11/Xaw/Cardinals.h>
24   | #include <X11/Xaw/Box.h>
25   | #ifdef USE_FWF
26   | #include <X11/Xfwf/MultiList.h>
27   | #else
28   | #include <X11/Xaw/List.h>
29   | #endif
30   | #include <X11/Xos.h>
31   | 
32   | #ifdef PRESERVE_NO_SYSV
33   | # ifdef SYSV
34   | #  undef SYSV
35   | # endif
36   | # undef PRESERVE_NO_SYSV
37   | #endif
38   | 
39   | #include "hack.h"
40   | #include "winX.h"
41   | #include <ctype.h>
42   | 
43   | 
44   | static void FDECL(menu_select, (Widget, XtPointer, XtPointer));
45   | static void FDECL(invert_line, (struct xwindow *,x11_menu_item *,int,long));
46   | static void FDECL(menu_ok, (Widget, XtPointer, XtPointer));
47   | static void FDECL(menu_cancel, (Widget, XtPointer, XtPointer));
48   | static void FDECL(menu_all, (Widget, XtPointer, XtPointer));
49   | static void FDECL(menu_none, (Widget, XtPointer, XtPointer));
50   | static void FDECL(menu_invert, (Widget, XtPointer, XtPointer));
51   | static void FDECL(menu_search, (Widget, XtPointer, XtPointer));
52   | static void FDECL(select_all, (struct xwindow *));
53   | static void FDECL(select_none, (struct xwindow *));
54   | static void FDECL(select_match, (struct xwindow *, char*));
55   | static void FDECL(invert_all, (struct xwindow *));
56   | static void FDECL(invert_match, (struct xwindow *, char*));
57   | static void FDECL(menu_popdown, (struct xwindow *));
58   | #ifdef USE_FWF
59   | static void FDECL(sync_selected, (struct menu_info_t *, int, int *));
60   | #endif
61   | 
62   | static void FDECL(move_menu, (struct menu *, struct menu *));
63   | static void FDECL(free_menu, (struct menu *));
64   | static void FDECL(reset_menu_to_default, (struct menu *));
65   | static void FDECL(clear_old_menu, (struct xwindow *));
66   | static char *FDECL(copy_of, (const char *));
67   | 
68   | #define reset_menu_count(mi)	((mi)->counting = FALSE, (mi)->menu_count = 0L)
69   | 
70   | 
71   | static const char menu_translations[] =
72   |     "#override\n\
73   |      <Key>Left: scroll(4)\n\
74   |      <Key>Right: scroll(6)\n\
75   |      <Key>Up: scroll(8)\n\
76   |      <Key>Down: scroll(2)\n\
77   |      <Key>: menu_key()";
78   | 
79   | /*
80   |  * Menu callback.
81   |  */
82   | /* ARGSUSED */
83   | static void
84   | menu_select(w, client_data, call_data)
85   |     Widget w;
86   |     XtPointer client_data, call_data;
87   | {
88   |     struct xwindow *wp;
89   |     struct menu_info_t *menu_info;
90   | #ifdef USE_FWF
91   |     XfwfMultiListReturnStruct *lrs = (XfwfMultiListReturnStruct *) call_data;
92   | #else
93   |     XawListReturnStruct *lrs = (XawListReturnStruct *) call_data;
94   |     int i;
95   |     x11_menu_item *curr;
96   | #endif
97   |     long how_many;
98   | 
99   |     wp = find_widget(w);
100  |     menu_info  = wp->menu_information;
101  |     how_many = menu_info->counting ? menu_info->menu_count : -1L;
102  |     reset_menu_count(menu_info);
103  | 
104  | #ifdef USE_FWF
105  |     /* if we've reached here, we've found our selected item */
106  |     switch (lrs->action) {
107  | 	case XfwfMultiListActionNothing:
108  | 		pline("menu_select: nothing action?");
109  | 		break;
110  | 	case XfwfMultiListActionStatus:
111  | 		pline("menu_select: status action?");
112  | 		break;
113  | 	case XfwfMultiListActionHighlight:
114  | 	case XfwfMultiListActionUnhighlight:
115  | 		sync_selected(menu_info,lrs->num_selected,lrs->selected_items);
116  | 		break;
117  |     }
118  | #else
119  |     for (i = 0, curr = menu_info->curr_menu.base; i < lrs->list_index; i++) {
120  | 	if (!curr) panic("menu_select: out of menu items!");
121  | 	curr = curr->next;
122  |     }
123  |     XawListUnhighlight(w);	/* unhilight item */
124  | 
125  |     /* if the menu is not active or don't have an identifier, try again */
126  |     if (!menu_info->is_active || curr->identifier.a_void == 0) {
127  | 	X11_nhbell();
128  | 	return;
129  |     }
130  | 
131  |     /* if we've reached here, we've found our selected item */
132  |     curr->selected = !curr->selected;
133  |     if (curr->selected) {
134  | 	curr->str[2] = (how_many != -1L) ? '#' : '+';
135  | 	curr->pick_count = how_many;
136  |     } else {
137  | 	curr->str[2] = '-';
138  | 	curr->pick_count = -1L;
139  |     }
140  |     XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
141  | #endif
142  | 
143  |     if (menu_info->how == PICK_ONE)
144  | 	menu_popdown(wp);
145  | }
146  | 
147  | /*
148  |  * Called when menu window is deleted.
149  |  */
150  | /* ARGSUSED */
151  | void
152  | menu_delete(w, event, params, num_params)
153  |     Widget w;
154  |     XEvent *event;
155  |     String *params;
156  |     Cardinal *num_params;
157  | {
158  |     menu_cancel((Widget)None, (XtPointer) find_widget(w), (XtPointer) 0);
159  | }
160  | 
161  | /*
162  |  * Invert the count'th line (curr) in the given window.
163  |  */
164  | /*ARGSUSED*/
165  | static void
166  | invert_line(wp, curr, which, how_many)
167  |     struct xwindow *wp;
168  |     x11_menu_item *curr;
169  |     int which;
170  |     long how_many;
171  | {
172  |     reset_menu_count(wp->menu_information);
173  |     curr->selected = !curr->selected;
174  |     if (curr->selected) {
175  | #ifdef USE_FWF
176  | 	XfwfMultiListHighlightItem((XfwfMultiListWidget)wp->w, which);
177  | #else
178  | 	curr->str[2] = (how_many != -1) ? '#' : '+';
179  | #endif
180  | 	curr->pick_count = how_many;
181  |     } else {
182  | #ifdef USE_FWF
183  | 	XfwfMultiListUnhighlightItem((XfwfMultiListWidget)wp->w, which);
184  | #else
185  | 	curr->str[2] = '-';
186  | #endif
187  | 	curr->pick_count = -1L;
188  |     }
189  | }
190  | 
191  | /*
192  |  * Called when we get a key press event on a menu window.
193  |  */
194  | /* ARGSUSED */
195  | void
196  | menu_key(w, event, params, num_params)
197  |     Widget w;
198  |     XEvent *event;
199  |     String *params;
200  |     Cardinal *num_params;
201  | {
202  |     struct menu_info_t *menu_info;
203  |     x11_menu_item *curr;
204  |     struct xwindow *wp;
205  |     char ch;
206  |     int count;
207  | 
208  |     wp = find_widget(w);
209  |     menu_info = wp->menu_information;
210  | 
211  |     ch = key_event_to_char((XKeyEvent *) event);
212  | 
213  |     if (ch == '\0') {	/* don't accept nul char/modifier event */
214  | 	/* don't beep */
215  | 	return;
216  |     }
217  | 
218  |     if (menu_info->is_active) {		/* waiting for input */
219  | 	ch = map_menu_cmd(ch);
220  | 	if (ch == '\033') {		/* quit */
221  | 	    if (menu_info->counting) {
222  | 		/* when there's a count in progress, ESC discards it
223  | 		   rather than dismissing the whole menu */
224  | 		reset_menu_count(menu_info);
225  | 		return;
226  | 	    }
227  | 	    select_none(wp);
228  | 	} else if (ch == '\n' || ch == '\r') {
229  | 	    ;	/* accept */
230  | 	} else if (isdigit(ch)) {
231  | 	    /* special case: '0' is also the default ball class */
232  | 	    if (ch == '0' && !menu_info->counting &&
233  | 		    index(menu_info->curr_menu.gacc, ch))
234  | 		goto group_accel;
235  | 	    menu_info->menu_count *= 10L;
236  | 	    menu_info->menu_count += (long)(ch - '0');
237  | 	    if (menu_info->menu_count != 0L)	/* ignore leading zeros */
238  | 		menu_info->counting = TRUE;
239  | 	    return;
240  | 	} else if (ch == MENU_SEARCH) {		/* search */
241  | 	    if (menu_info->how == PICK_ANY || menu_info->how == PICK_ONE) {
242  | 		char buf[BUFSZ];
243  | 		X11_getlin("Search for:", buf);
244  | 		if (!*buf || *buf == '\033') return;
245  | 		if (menu_info->how == PICK_ANY) {
246  | 		    invert_match(wp, buf);
247  | 		    return;
248  | 		} else {
249  | 		    select_match(wp, buf);
250  | 		}
251  | 	    } else {
252  | 		X11_nhbell();
253  | 		return;
254  | 	    }
255  | 	} else if (ch == MENU_SELECT_ALL) {		/* select all */
256  | 	    if (menu_info->how == PICK_ANY)
257  | 		select_all(wp);
258  | 	    else
259  | 		X11_nhbell();
260  | 	    return;
261  | 	} else if (ch == MENU_UNSELECT_ALL) {		/* unselect all */
262  | 	    if (menu_info->how == PICK_ANY)
263  | 		select_none(wp);
264  | 	    else
265  | 		X11_nhbell();
266  | 	    return;
267  | 	} else if (ch == MENU_INVERT_ALL) {		/* invert all */
268  | 	    if (menu_info->how == PICK_ANY)
269  | 		invert_all(wp);
270  | 	    else
271  | 		X11_nhbell();
272  | 	    return;
273  | 	} else if (index(menu_info->curr_menu.gacc, ch)) {
274  |  group_accel:
275  | 	    /* matched a group accelerator */
276  | 	    if (menu_info->how == PICK_ANY || menu_info->how == PICK_ONE) {
277  | 		for (count = 0, curr = menu_info->curr_menu.base; curr;
278  | 						curr = curr->next, count++) {
279  | 		    if (curr->identifier.a_void != 0 && curr->gselector == ch) {
280  | 			invert_line(wp, curr, count, -1L);
281  | 			/* for PICK_ONE, a group accelerator will
282  | 			   only be included in gacc[] if it matches
283  | 			   exactly one entry, so this must be it... */
284  | 			if (menu_info->how == PICK_ONE)
285  | 			    goto menu_done;	/* pop down */
286  | 		    }
287  | 		}
288  | #ifndef USE_FWF
289  | 		XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
290  | #endif
291  | 	    } else
292  | 		X11_nhbell();
293  | 	    return;
294  | 	} else {
295  | 	    boolean selected_something = FALSE;
296  | 	    for (count = 0, curr = menu_info->curr_menu.base; curr;
297  | 						    curr = curr->next, count++)
298  | 		if (curr->identifier.a_void != 0 && curr->selector == ch) break;
299  | 
300  | 	    if (curr) {
301  | 		invert_line(wp, curr, count,
302  | 			    menu_info->counting ? menu_info->menu_count : -1L);
303  | #ifndef USE_FWF
304  | 		XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, True);
305  | #endif
306  | 		selected_something = curr->selected;
307  | 	    } else {
308  | 		X11_nhbell();		/* no match */
309  | 	    }
310  | 	    if (!(selected_something && menu_info->how == PICK_ONE))
311  | 		return;		/* keep going */
312  | 	}
313  | 	/* pop down */
314  |     } else {			/* permanent inventory window */
315  | 	if (ch != '\033') {
316  | 	    X11_nhbell();
317  | 	    return;
318  | 	}
319  | 	/* pop down on ESC */
320  |     }
321  | 
322  |  menu_done:
323  |     menu_popdown(wp);
324  | }
325  | 
326  | /* ARGSUSED */
327  | static void
328  | menu_ok(w, client_data, call_data)
329  |     Widget w;
330  |     XtPointer client_data, call_data;
331  | {
332  |     struct xwindow *wp = (struct xwindow *) client_data;
333  | 
334  |     menu_popdown(wp);
335  | }
336  | 
337  | /* ARGSUSED */
338  | static void
339  | menu_cancel(w, client_data, call_data)
340  |     Widget w;				/* don't use - may be None */
341  |     XtPointer client_data, call_data;
342  | {
343  |     struct xwindow *wp = (struct xwindow *) client_data;
344  | 
345  |     if (wp->menu_information->is_active) {
346  | 	select_none(wp);
347  | 	wp->menu_information->cancelled = TRUE;
348  |     }
349  |     menu_popdown(wp);
350  | }
351  | 
352  | /* ARGSUSED */
353  | static void
354  | menu_all(w, client_data, call_data)
355  |     Widget w;
356  |     XtPointer client_data, call_data;
357  | {
358  |     select_all((struct xwindow *) client_data);
359  | }
360  | 
361  | /* ARGSUSED */
362  | static void
363  | menu_none(w, client_data, call_data)
364  |     Widget w;
365  |     XtPointer client_data, call_data;
366  | {
367  |     select_none((struct xwindow *) client_data);
368  | }
369  | 
370  | /* ARGSUSED */
371  | static void
372  | menu_invert(w, client_data, call_data)
373  |     Widget w;
374  |     XtPointer client_data, call_data;
375  | {
376  |     invert_all((struct xwindow *) client_data);
377  | }
378  | 
379  | /* ARGSUSED */
380  | static void
381  | menu_search(w, client_data, call_data)
382  |     Widget w;
383  |     XtPointer client_data, call_data;
384  | {
385  |     struct xwindow *wp = (struct xwindow *) client_data;
386  |     struct menu_info_t *menu_info = wp->menu_information;
387  | 
388  |     char buf[BUFSZ];
389  |     X11_getlin("Search for:", buf);
390  |     if (!*buf || *buf == '\033') return;
391  | 
392  |     if (menu_info->how == PICK_ANY)
393  | 	invert_match(wp, buf);
394  |     else
395  | 	select_match(wp, buf);
396  | 
397  |     if (menu_info->how == PICK_ONE)
398  | 	menu_popdown(wp);
399  | }
400  | 
401  | static void
402  | select_all(wp)
403  |     struct xwindow *wp;
404  | {
405  |     x11_menu_item *curr;
406  |     int count;
407  |     boolean changed = FALSE;
408  | 
409  |     reset_menu_count(wp->menu_information);
410  |     for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
411  | 					curr = curr->next, count++)
412  | 	if (curr->identifier.a_void != 0)
413  | 	    if (!curr->selected) {
414  | 		invert_line(wp, curr, count, -1L);
415  | 		changed = TRUE;
416  | 	    }
417  | 
418  | #ifndef USE_FWF
419  |     if (changed)
420  | 	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
421  | 		      0, 0, True);
422  | #endif
423  | }
424  | 
425  | static void
426  | select_none(wp)
427  |     struct xwindow *wp;
428  | {
429  |     x11_menu_item *curr;
430  |     int count;
431  |     boolean changed = FALSE;
432  | 
433  |     reset_menu_count(wp->menu_information);
434  |     for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
435  | 					curr = curr->next, count++)
436  | 	if (curr->identifier.a_void != 0)
437  | 	    if (curr->selected) {
438  | 		invert_line(wp, curr, count, -1L);
439  | 		changed = TRUE;
440  | 	    }
441  | 
442  | #ifndef USE_FWF
443  |     if (changed)
444  | 	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
445  | 		      0, 0, True);
446  | #endif
447  | }
448  | 
449  | static void
450  | invert_all(wp)
451  |     struct xwindow *wp;
452  | {
453  |     x11_menu_item *curr;
454  |     int count;
455  | 
456  |     reset_menu_count(wp->menu_information);
457  |     for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
458  | 					curr = curr->next, count++)
459  | 	if (curr->identifier.a_void != 0)
460  | 	    invert_line(wp, curr, count, -1L);
461  | 
462  | #ifndef USE_FWF
463  |     XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
464  | 		  0, 0, True);
465  | #endif
466  | }
467  | 
468  | static void
469  | invert_match(wp, match)
470  |     struct xwindow *wp;
471  |     char *match;
472  | {
473  |     x11_menu_item *curr;
474  |     int count;
475  |     boolean changed = FALSE;
476  | 
477  |     reset_menu_count(wp->menu_information);
478  |     for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
479  | 						curr = curr->next, count++)
480  | 	if (curr->identifier.a_void != 0 && strstri(curr->str, match)) {
481  | 	    invert_line(wp, curr, count, -1L);
482  | 	    changed = TRUE;
483  | 	}
484  | 
485  | #ifndef USE_FWF
486  |     if (changed)
487  | 	XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
488  | 		      0, 0, True);
489  | #endif
490  | }
491  | 
492  | static void
493  | select_match(wp, match)
494  |     struct xwindow *wp;
495  |     char *match;
496  | {
497  |     x11_menu_item *curr;
498  |     int count;
499  | 
500  |     reset_menu_count(wp->menu_information);
501  |     for (count = 0, curr = wp->menu_information->curr_menu.base; curr;
502  | 						curr = curr->next, count++)
503  | 	if (curr->identifier.a_void != 0 && strstri(curr->str, match)) {
504  | 	    if (!curr->selected) {
505  | 		invert_line(wp, curr, count, -1L);
506  | #ifndef USE_FWF
507  | 		XawListChange(wp->w, wp->menu_information->curr_menu.list_pointer,
508  | 			      0, 0, True);
509  | #endif
510  | 	    }
511  | 	    return;
512  | 	}
513  | 
514  |     /* no match */
515  |     X11_nhbell();
516  | }
517  | 
518  | static void
519  | menu_popdown(wp)
520  |     struct xwindow *wp;
521  | {
522  |     nh_XtPopdown(wp->popup);			/* remove the event grab */
523  |     if (wp->menu_information->is_active)
524  | 	exit_x_event = TRUE;			/* exit our event handler */
525  |     wp->menu_information->is_up = FALSE;	/* menu is down */
526  | }
527  | 
528  | #ifdef USE_FWF
529  | /*
530  |  * Make sure our idea of selected matches the FWF Multilist's idea of what
531  |  * is currently selected.  The MultiList's selected list can change without
532  |  * notifying us if one or more items are selected and then another is
533  |  * selected (not toggled).  Then the items that were selected are deselected
534  |  * but we are not notified.
535  |  */
536  | static void
537  | sync_selected(menu_info, num_selected, items)
538  |     struct menu_info_t *menu_info;
539  |     int num_selected;
540  |     int *items;
541  | {
542  |     int i, j, *ip;
543  |     x11_menu_item *curr;
544  |     Boolean found;
545  | 
546  |     for (i=0, curr = menu_info->curr_menu.base; curr; i++, curr = curr->next) {
547  | 	found = False;
548  | 	for (j = 0, ip = items; j < num_selected; j++, ip++)
549  | 	    if (*ip == i) {
550  | 		found = True;
551  | 		break;
552  | 	    }
553  | #if 0
554  | 	if (curr->selected && !found)
555  | 	    printf("sync: deselecting %s\n", curr->str);
556  | 	else if (!curr->selected && found)
557  | 	    printf("sync: selecting %s\n", curr->str);
558  | #endif
559  | 	curr->selected = found ? TRUE : FALSE;
560  |     }
561  | }
562  | #endif /* USE_FWF */
563  | 
564  | 
565  | /* Global functions ======================================================== */
566  | 
567  | void
568  | X11_start_menu(window)
569  |     winid window;
570  | {
571  |     struct xwindow *wp;
572  |     check_winid(window);
573  | 
574  |     wp = &window_list[window];
575  | 
576  |     if (wp->menu_information->is_menu) {
577  | 	/* make sure we'ere starting with a clean slate */
578  | 	free_menu(&wp->menu_information->new_menu);
579  |     } else {
580  | 	wp->menu_information->is_menu = TRUE;
581  |     }
582  | }
583  | 
584  | /*ARGSUSED*/
585  | void
586  | X11_add_menu(window, glyph, identifier, ch, gch, attr, str, preselected)
587  |     winid window;
588  |     int glyph;			/* unused (for now) */
589  |     const anything *identifier;
590  |     char ch;
591  |     char gch;			/* group accelerator (0 = no group) */
592  |     int attr;
593  |     const char *str;
594  |     boolean preselected;
595  | {
596  |     x11_menu_item *item;
597  |     struct menu_info_t *menu_info;
598  | 
599  |     check_winid(window);
600  |     menu_info = window_list[window].menu_information;
601  |     if (!menu_info->is_menu) {
602  | 	impossible("add_menu:  called before start_menu");
603  | 	return;
604  |     }
605  | 
606  |     item = (x11_menu_item *) alloc((unsigned)sizeof(x11_menu_item));
607  |     item->next = (x11_menu_item *) 0;
608  |     item->identifier = *identifier;
609  |     item->attr = attr;
610  | /*    item->selected = preselected; */
611  |     item->selected = FALSE;
612  |     item->pick_count = -1L;
613  | 
614  |     if (identifier->a_void) {
615  | 	char buf[4+BUFSZ];
616  | 	int len = strlen(str);
617  | 
618  | 	if (!ch) {
619  | 	    /* Supply a keyboard accelerator.  Only the first 52 get one. */
620  | 
621  | 	    if (menu_info->new_menu.curr_selector) {
622  | 		ch = menu_info->new_menu.curr_selector++;
623  | 		if (ch == 'z')
624  | 		    menu_info->new_menu.curr_selector = 'A';
625  | 		else if (ch == 'Z')
626  | 		    menu_info->new_menu.curr_selector = 0;	/* out */
627  | 	    }
628  | 	}
629  | 
630  | 	if (len >= BUFSZ) {
631  | 	    /* We *think* everything's coming in off at most BUFSZ bufs... */
632  | 	    impossible("Menu item too long (%d).", len);
633  | 	    len = BUFSZ - 1;
634  | 	}
635  | 	Sprintf(buf, "%c - ", ch ? ch : ' ');
636  | 	(void) strncpy(buf+4, str, len);
637  | 	buf[4+len] = '\0';
638  | 	item->str = copy_of(buf);
639  |     } else {
640  | 	/* no keyboard accelerator */
641  | 	item->str = copy_of(str);
642  | 	ch = 0;
643  |     }
644  | 
645  |     item->selector = ch;
646  |     item->gselector = gch;
647  | 
648  |     if (menu_info->new_menu.last) {
649  | 	menu_info->new_menu.last->next = item;
650  |     } else {
651  | 	menu_info->new_menu.base = item;
652  |     }
653  |     menu_info->new_menu.last = item;
654  |     menu_info->new_menu.count++;
655  | }
656  | 
657  | void
658  | X11_end_menu(window, query)
659  |     winid window;
660  |     const char *query;
661  | {
662  |     struct menu_info_t *menu_info;
663  | 
664  |     check_winid(window);
665  |     menu_info = window_list[window].menu_information;
666  |     if (!menu_info->is_menu) {
667  | 	impossible("end_menu:  called before start_menu");
668  | 	return;
669  |     }
670  |     menu_info->new_menu.query = copy_of(query);
671  | }
672  | 
673  | int
674  | X11_select_menu(window, how, menu_list)
675  |     winid window;
676  |     int how;
677  |     menu_item **menu_list;
678  | {
679  |     x11_menu_item *curr;
680  |     struct xwindow *wp;
681  |     struct menu_info_t *menu_info;
682  |     Arg args[10];
683  |     Cardinal num_args;
684  |     String *ptr;
685  |     int retval;
686  |     Dimension v_pixel_width, v_pixel_height;
687  |     boolean labeled;
688  |     Widget viewport_widget, form, label, ok, cancel, all, none, invert, search;
689  |     Boolean sens;
690  | #ifdef USE_FWF
691  |     Boolean *boolp;
692  | #endif
693  |     char gacc[QBUFSZ], *ap;
694  | 
695  |     *menu_list = (menu_item *) 0;
696  |     check_winid(window);
697  |     wp = &window_list[window];
698  |     menu_info = wp->menu_information;
699  |     if (!menu_info->is_menu) {
700  | 	impossible("select_menu:  called before start_menu");
701  | 	return 0;
702  |     }
703  | 
704  |     menu_info->how = (short) how;
705  | 
706  |     /* collect group accelerators; for PICK_NONE, they're ignored;
707  |        for PICK_ONE, only those which match exactly one entry will be
708  |        accepted; for PICK_ANY, those which match any entry are okay */
709  |     gacc[0] = '\0';
710  |     if (menu_info->how != PICK_NONE) {
711  | 	int i, n, gcnt[128];
712  | #define GSELIDX(c) ((c) & 127)	/* guard against `signed char' */
713  | 
714  | 	for (i = 0; i < SIZE(gcnt); i++) gcnt[i] = 0;
715  | 	for (n = 0, curr = menu_info->new_menu.base; curr; curr = curr->next)
716  | 	    if (curr->gselector) ++n,  ++gcnt[GSELIDX(curr->gselector)];
717  | 
718  | 	if (n > 0)	/* at least one group accelerator found */
719  | 	    for (ap = gacc, curr = menu_info->new_menu.base;
720  | 		    curr; curr = curr->next)
721  | 		if (curr->gselector && !index(gacc, curr->gselector) &&
722  | 			(menu_info->how == PICK_ANY ||
723  | 			    gcnt[GSELIDX(curr->gselector)] == 1)) {
724  | 		    *ap++ = curr->gselector;
725  | 		    *ap = '\0'; /* re-terminate for index() */
726  | 		}
727  |     }
728  |     menu_info->new_menu.gacc = copy_of(gacc);
729  |     reset_menu_count(menu_info);
730  | 
731  |     /*
732  |      * Create a string and sensitive list for the new menu.
733  |      */
734  |     menu_info->new_menu.list_pointer = ptr = (String *)
735  | 	    alloc((unsigned) (sizeof(String) * (menu_info->new_menu.count+1)));
736  |     for (curr = menu_info->new_menu.base; curr; ptr++, curr = curr->next)
737  | 	*ptr = (String) curr->str;
738  |     *ptr = 0;		/* terminate list with null */
739  | 
740  | #ifdef USE_FWF
741  |     menu_info->new_menu.sensitive = boolp = (Boolean *)
742  | 	    alloc((unsigned) (sizeof(Boolean) * (menu_info->new_menu.count)));
743  |     for (curr = menu_info->new_menu.base; curr; boolp++, curr = curr->next)
744  | 	*boolp = (curr->identifier.a_void != 0);
745  | #else
746  |     menu_info->new_menu.sensitive = (Boolean *) 0;
747  | #endif
748  |     labeled = (menu_info->new_menu.query && *(menu_info->new_menu.query))
749  | 	? TRUE : FALSE;
750  | 
751  |     /*
752  |      * Menus don't appear to size components correctly, except
753  |      * when first created.  For 3.2.0 release, just recreate
754  |      * each time.
755  |      */
756  |     if (menu_info->valid_widgets
757  | 			&& (window != WIN_INVEN || !flags.perm_invent)) {
758  | 	XtDestroyWidget(wp->popup);
759  | 	menu_info->valid_widgets = FALSE;
760  | 	menu_info->is_up = FALSE;
761  |     }
762  | 
763  |     if (!menu_info->valid_widgets) {
764  | 	Dimension row_spacing;
765  | 
766  | 	num_args = 0;
767  | 	XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
768  | 	wp->popup = XtCreatePopupShell(
769  | 			window == WIN_INVEN ? "inventory" : "menu",
770  | 			how == PICK_NONE ? topLevelShellWidgetClass:
771  | 					transientShellWidgetClass,
772  | 			toplevel, args, num_args);
773  | 	XtOverrideTranslations(wp->popup,
774  | 	    XtParseTranslationTable("<Message>WM_PROTOCOLS: menu_delete()"));
775  | 
776  | 
777  | 	num_args = 0;
778  | 	XtSetArg(args[num_args], XtNtranslations,
779  | 		XtParseTranslationTable(menu_translations));	num_args++;
780  | 	form = XtCreateManagedWidget("mform",
781  | 				    formWidgetClass,
782  | 				    wp->popup,
783  | 				    args, num_args);
784  | 
785  | 	num_args = 0;
786  | 	XtSetArg(args[num_args], XtNborderWidth, 0);		num_args++;
787  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
788  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
789  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
790  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
791  | 
792  | 	if (labeled)
793  | 	    label = XtCreateManagedWidget(menu_info->new_menu.query,
794  | 				    labelWidgetClass,
795  | 				    form,
796  | 				    args, num_args);
797  | 	else label = NULL;
798  | 
799  | 	/*
800  | 	 * Create ok, cancel, all, none, invert, and search buttons..
801  | 	 */
802  | 	num_args = 0;
803  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
804  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
805  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
806  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
807  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
808  | 	ok = XtCreateManagedWidget("OK",
809  | 			commandWidgetClass,
810  | 			form,
811  | 			args, num_args);
812  | 	XtAddCallback(ok, XtNcallback, menu_ok, (XtPointer) wp);
813  | 
814  | 	num_args = 0;
815  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
816  | 	XtSetArg(args[num_args], XtNfromHoriz, ok);		num_args++;
817  | 	XtSetArg(args[num_args], XtNsensitive, how!=PICK_NONE);	num_args++;
818  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
819  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
820  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
821  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
822  | 	cancel = XtCreateManagedWidget("cancel",
823  | 			commandWidgetClass,
824  | 			form,
825  | 			args, num_args);
826  | 	XtAddCallback(cancel, XtNcallback, menu_cancel, (XtPointer) wp);
827  | 
828  | 	sens = (how == PICK_ANY);
829  | 	num_args = 0;
830  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
831  | 	XtSetArg(args[num_args], XtNfromHoriz, cancel);		num_args++;
832  | 	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
833  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
834  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
835  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
836  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
837  | 	all = XtCreateManagedWidget("all",
838  | 			commandWidgetClass,
839  | 			form,
840  | 			args, num_args);
841  | 	XtAddCallback(all, XtNcallback, menu_all, (XtPointer) wp);
842  | 
843  | 	num_args = 0;
844  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
845  | 	XtSetArg(args[num_args], XtNfromHoriz, all);		num_args++;
846  | 	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
847  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
848  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
849  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
850  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
851  | 	none = XtCreateManagedWidget("none",
852  | 			commandWidgetClass,
853  | 			form,
854  | 			args, num_args);
855  | 	XtAddCallback(none, XtNcallback, menu_none, (XtPointer) wp);
856  | 
857  | 	num_args = 0;
858  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
859  | 	XtSetArg(args[num_args], XtNfromHoriz, none);		num_args++;
860  | 	XtSetArg(args[num_args], XtNsensitive, sens);		num_args++;
861  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
862  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
863  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
864  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
865  | 	invert = XtCreateManagedWidget("invert",
866  | 			commandWidgetClass,
867  | 			form,
868  | 			args, num_args);
869  | 	XtAddCallback(invert, XtNcallback, menu_invert, (XtPointer) wp);
870  | 
871  | 	num_args = 0;
872  | 	XtSetArg(args[num_args], XtNfromVert, label);		num_args++;
873  | 	XtSetArg(args[num_args], XtNfromHoriz, invert);		num_args++;
874  | 	XtSetArg(args[num_args], XtNsensitive, how!=PICK_NONE);	num_args++;
875  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
876  | 	XtSetArg(args[num_args], XtNbottom, XtChainTop);	num_args++;
877  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
878  | 	XtSetArg(args[num_args], XtNright, XtChainLeft);	num_args++;
879  | 	search = XtCreateManagedWidget("search",
880  | 			commandWidgetClass,
881  | 			form,
882  | 			args, num_args);
883  | 	XtAddCallback(search, XtNcallback, menu_search, (XtPointer) wp);
884  | 
885  | 	num_args = 0;
886  | 	XtSetArg(args[num_args], XtNallowVert,  True);		num_args++;
887  | 	XtSetArg(args[num_args], XtNallowHoriz, False);		num_args++;
888  | 	XtSetArg(args[num_args], XtNuseBottom, True);		num_args++;
889  | 	XtSetArg(args[num_args], XtNuseRight, True);		num_args++;
890  | /*
891  | 	XtSetArg(args[num_args], XtNforceBars, True);		num_args++;
892  | */
893  | 	XtSetArg(args[num_args], XtNfromVert, all);		num_args++;
894  | 	XtSetArg(args[num_args], XtNtop, XtChainTop);		num_args++;
895  | 	XtSetArg(args[num_args], XtNbottom, XtChainBottom);	num_args++;
896  | 	XtSetArg(args[num_args], XtNleft, XtChainLeft);		num_args++;
897  | 	XtSetArg(args[num_args], XtNright, XtChainRight);	num_args++;
898  | 	viewport_widget = XtCreateManagedWidget(
899  | 		    "menu_viewport",	/* name */
900  | 		    viewportWidgetClass,
901  | 		    form,		/* parent widget */
902  | 		    args, num_args);	/* values, and number of values */
903  | 
904  | 	/* make new menu the current menu */
905  | 	move_menu(&menu_info->new_menu, &menu_info->curr_menu);
906  | 
907  | 	num_args = 0;
908  | 	XtSetArg(args[num_args], XtNforceColumns, True);	num_args++;
909  | 	XtSetArg(args[num_args], XtNcolumnSpacing, 1);		num_args++;
910  | 	XtSetArg(args[num_args], XtNdefaultColumns, 1);		num_args++;
911  | 	XtSetArg(args[num_args], XtNlist,
912  | 			menu_info->curr_menu.list_pointer);	num_args++;
913  | #ifdef USE_FWF
914  | 	XtSetArg(args[num_args], XtNsensitiveArray,
915  | 			menu_info->curr_menu.sensitive);	num_args++;
916  | 	XtSetArg(args[num_args], XtNmaxSelectable,
917  | 			menu_info->curr_menu.count);		num_args++;
918  | #endif
919  | 	wp->w = XtCreateManagedWidget(
920  | 		    "menu_list",		/* name */
921  | #ifdef USE_FWF
922  | 		    xfwfMultiListWidgetClass,
923  | #else
924  | 		    listWidgetClass,
925  | #endif
926  | 		    viewport_widget,		/* parent widget */
927  | 		    args,			/* set some values */
928  | 		    num_args);			/* number of values to set */
929  | 
930  | 	XtAddCallback(wp->w, XtNcallback, menu_select, (XtPointer) 0);
931  | 
932  | 	/* Get the font and margin information. */
933  | 	num_args = 0;
934  | 	XtSetArg(args[num_args], XtNfont, &menu_info->fs);	num_args++;
935  | 	XtSetArg(args[num_args], XtNinternalHeight,
936  | 				&menu_info->internal_height);	num_args++;
937  | 	XtSetArg(args[num_args], XtNinternalWidth,
938  | 				&menu_info->internal_width);	num_args++;
939  | 	XtSetArg(args[num_args], XtNrowSpacing, &row_spacing);	num_args++;
940  | 	XtGetValues(wp->w, args, num_args);
941  | 
942  | 	/* font height is ascent + descent */
943  | 	menu_info->line_height =
944  | 		menu_info->fs->max_bounds.ascent +
945  | 		menu_info->fs->max_bounds.descent + row_spacing;
946  | 
947  | 	menu_info->valid_widgets = TRUE;
948  | 
949  | 	num_args = 0;
950  | 	XtSetArg(args[num_args], XtNwidth, &v_pixel_width);	num_args++;
951  | 	XtSetArg(args[num_args], XtNheight, &v_pixel_height);	num_args++;
952  | 	XtGetValues(wp->w, args, num_args);
953  |     } else {
954  | 	Dimension len;
955  | 
956  | 	viewport_widget = XtParent(wp->w);
957  | 
958  | 	/* get the longest string on new menu */
959  | 	v_pixel_width = 0;
960  | 	for (ptr = menu_info->new_menu.list_pointer; *ptr; ptr++) {
961  | 	    len = XTextWidth(menu_info->fs, *ptr, strlen(*ptr));
962  | 	    if (len > v_pixel_width) v_pixel_width = len;
963  | 	}
964  | 
965  | 	/* add viewport internal border */
966  | 	v_pixel_width += 2 * menu_info->internal_width;
967  | 	v_pixel_height = (2 * menu_info->internal_height) +
968  | 	    (menu_info->new_menu.count * menu_info->line_height);
969  | 
970  | 	/* make new menu the current menu */
971  | 	move_menu(&menu_info->new_menu, &menu_info->curr_menu);
972  | #ifdef USE_FWF
973  | 	XfwfMultiListSetNewData((XfwfMultiListWidget)wp->w,
974  | 		menu_info->curr_menu.list_pointer, 0, 0, TRUE,
975  | 		menu_info->curr_menu.sensitive);
976  | #else
977  | 	XawListChange(wp->w, menu_info->curr_menu.list_pointer, 0, 0, TRUE);
978  | #endif
979  |     }
980  | 
981  |     /* if viewport will be bigger than the screen, limit its height */
982  |     num_args = 0;
983  |     XtSetArg(args[num_args], XtNwidth, &v_pixel_width);	num_args++;
984  |     XtSetArg(args[num_args], XtNheight, &v_pixel_height);	num_args++;
985  |     XtGetValues(wp->w, args, num_args);
986  |     if ((Dimension) XtScreen(wp->w)->height * 5 / 6 < v_pixel_height) {
987  | 	/* scrollbar is 14 pixels wide.  Widen the form to accommodate it. */
988  | 	v_pixel_width += 14;
989  | 
990  | 	/* shrink to fit vertically */
991  | 	v_pixel_height = XtScreen(wp->w)->height * 5 / 6;
992  | 
993  | 	num_args = 0;
994  | 	XtSetArg(args[num_args], XtNwidth, v_pixel_width); num_args++;
995  | 	XtSetArg(args[num_args], XtNheight, v_pixel_height); num_args++;
996  | 	XtSetValues(wp->w, args, num_args);
997  |     }
998  |     XtRealizeWidget(wp->popup);	/* need to realize before we position */
999  | 
1000 |     /* if menu is not up, position it */
1001 |     if (!menu_info->is_up) positionpopup(wp->popup, FALSE);
1002 | 
1003 |     menu_info->is_up = TRUE;
1004 |     if (window == WIN_INVEN && how == PICK_NONE) {
1005 | 	/* cant use nh_XtPopup() because it may try to grab the focus */
1006 | 	XtPopup(wp->popup, (int)XtGrabNone);
1007 | 	if (!updated_inventory)
1008 | 	    XMapRaised(XtDisplay(wp->popup), XtWindow(wp->popup));
1009 | 	XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
1010 | 							&wm_delete_window, 1);
1011 | 	retval = 0;
1012 |     } else {
1013 | 	menu_info->is_active = TRUE;	/* waiting for user response */
1014 | 	menu_info->cancelled = FALSE;
1015 | 	nh_XtPopup(wp->popup, (int)XtGrabExclusive, wp->w);
1016 | 	(void) x_event(EXIT_ON_EXIT);
1017 | 	menu_info->is_active = FALSE;
1018 | 	if (menu_info->cancelled)
1019 | 	    return -1;
1020 | 
1021 | 	retval = 0;
1022 | 	for (curr = menu_info->curr_menu.base; curr; curr = curr->next)
1023 | 	    if (curr->selected) retval++;
1024 | 
1025 | 	if (retval) {
1026 | 	    menu_item *mi;
1027 | 
1028 | 	    *menu_list = mi = (menu_item *) alloc(retval * sizeof(menu_item));
1029 | 	    for (curr = menu_info->curr_menu.base; curr; curr = curr->next)
1030 | 		if (curr->selected) {
1031 | 		    mi->item = curr->identifier;
1032 | 		    mi->count = curr->pick_count;
1033 | 		    mi++;
1034 | 		}
1035 | 	}
1036 |     }
1037 | 
1038 |     return retval;
1039 | }
1040 | 
1041 | /* End global functions ==================================================== */
1042 | 
1043 | /*
1044 |  * Allocate a copy of the given string.  If null, return a string of
1045 |  * zero length.
1046 |  *
1047 |  * This is an exact duplicate of copy_of() in tty/wintty.c.
1048 |  */
1049 | static char *
1050 | copy_of(s)
1051 |     const char *s;
1052 | {
1053 |     if (!s) s = "";
1054 |     return strcpy((char *) alloc((unsigned) (strlen(s) + 1)), s);
1055 | }
1056 | 
1057 | 
1058 | static void
1059 | move_menu(src_menu, dest_menu)
1060 |     struct menu *src_menu, *dest_menu;
1061 | {
1062 |     free_menu(dest_menu);	/* toss old menu */
1063 |     *dest_menu = *src_menu;	/* make new menu current */
1064 | 				/* leave no dangling ptrs */
1065 |     reset_menu_to_default(src_menu);
1066 | }
1067 | 
1068 | 
1069 | static void
1070 | free_menu(mp)
1071 |     struct menu *mp;
1072 | {
1073 |     while (mp->base) {
1074 | 	mp->last = mp->base;
1075 | 	mp->base = mp->base->next;
1076 | 
1077 | 	free((genericptr_t)mp->last->str);
1078 | 	free((genericptr_t)mp->last);
1079 |     }
1080 |     if (mp->query) free((genericptr_t) mp->query);
1081 |     if (mp->gacc) free((genericptr_t) mp->gacc);
1082 |     if (mp->list_pointer) free((genericptr_t) mp->list_pointer);
1083 |     if (mp->sensitive) free((genericptr_t) mp->sensitive);
1084 |     reset_menu_to_default(mp);
1085 | }
1086 | 
1087 | static void
1088 | reset_menu_to_default(mp)
1089 |     struct menu *mp;
1090 | {
1091 |     mp->base = mp->last = (x11_menu_item *)0;
1092 |     mp->query = (const char *)0;
1093 |     mp->gacc = (const char *)0;
1094 |     mp->count = 0;
1095 |     mp->list_pointer = (String *)0;
1096 |     mp->sensitive = (Boolean *)0;
1097 |     mp->curr_selector = 'a';	/* first accelerator */
1098 | }
1099 | 
1100 | static void
1101 | clear_old_menu(wp)
1102 |     struct xwindow *wp;
1103 | {
1104 |     struct menu_info_t *menu_info = wp->menu_information;
1105 | 
1106 |     free_menu(&menu_info->curr_menu);
1107 |     free_menu(&menu_info->new_menu);
1108 | 
1109 |     if (menu_info->valid_widgets) {
1110 | 	nh_XtPopdown(wp->popup);
1111 | 	menu_info->is_up = FALSE;
1112 | 	XtDestroyWidget(wp->popup);
1113 | 	menu_info->valid_widgets = FALSE;
1114 | 	wp->w = wp->popup = (Widget) 0;
1115 |     }
1116 | }
1117 | 
1118 | void
1119 | create_menu_window(wp)
1120 |     struct xwindow *wp;
1121 | {
1122 |     wp->type = NHW_MENU;
1123 |     wp->menu_information =
1124 | 		(struct menu_info_t *) alloc(sizeof(struct menu_info_t));
1125 |     (void) memset((genericptr_t) wp->menu_information, '\0',
1126 | 						sizeof(struct menu_info_t));
1127 |     reset_menu_to_default(&wp->menu_information->curr_menu);
1128 |     reset_menu_to_default(&wp->menu_information->new_menu);
1129 |     reset_menu_count(wp->menu_information);
1130 |     wp->w = wp->popup = (Widget) 0;
1131 | }
1132 | 
1133 | void
1134 | destroy_menu_window(wp)
1135 |     struct xwindow *wp;
1136 | {
1137 |     clear_old_menu(wp);		/* this will also destroy the widgets */
1138 |     free((genericptr_t) wp->menu_information);
1139 |     wp->menu_information = (struct menu_info_t *) 0;
1140 |     wp->type = NHW_NONE;	/* allow re-use */
1141 | }
1142 | 
1143 | /*winmenu.c*/