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*/