1 /* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
2 /* NetHack 3.6 cursdial.c */
3 /* Copyright (c) Karl Garrison, 2010. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "curses.h"
7 #include "hack.h"
8 #include "wincurs.h"
9 #include "cursdial.h"
10 #include "func_tab.h"
11 #include <ctype.h>
12 
13 #if defined(FILENAME_CMP) && !defined(strcasecmp)
14 #define strcasecmp FILENAME_CMP
15 #endif
16 #if defined(STRNCMPI) && !defined(strncasecmp)
17 #define strncasecmp strncmpi
18 #endif
19 
20 /* defined in sys/<foo>/<foo>tty.c or cursmain.c as last resort;
21    set up by curses_init_nhwindows() */
22 extern char erase_char, kill_char;
23 
24 /*
25  * Note:
26  *
27  *  Prototypes need to use the widened/unwidened type macros (CHAR_P, &c)
28  *  in order to match fields of the window_procs struct (see winprocs.h).
29  *  But for a standard-conforming compiler, we'll end up with the widened
30  *  types necessary to match the mixed prototype/old-style function
31  *  definition environment as used by nethack's core.  Prototype
32 int func(CHAR_P);
33  *  becomes
34 int func(int);
35  *  after expansion, which matches the definition
36 int func(arg) char arg; { ... }
37  *  according to the rules of the C standard.  But the use of new-style
38  *  definitions
39 int func(char arg) { ... }
40  *  by the curses interface turns that into a conflict.  No widening takes
41  *  place so it ought to be 'int func(char);' instead.  Unfortunately that
42  *  would be incompatible for functions assigned to window_procs.
43  *
44  *  So, the code here (also cursmain.c and cursinvt.c) is mis-using the
45  *  widening macros for variable types
46 int func(CHAR_P arg) { ... }
47  *  (no doubt modelling it after the C++ code in win/Qt where the option
48  *  to switch the applicable definitions to old-style isn't available).
49  *  Other alternatives aren't significantly better so just live with it.
50  *  [Redoing the windowing interface to avoid narrow arguments would be
51  *  better since that would fix Qt's usage too.]
52  */
53 
54 /* Dialog windows for curses interface */
55 
56 
57 /* Private declarations */
58 
59 typedef struct nhmi {
60     winid wid;                  /* NetHack window id */
61     int glyph;                  /* Menu glyphs */
62     anything identifier;        /* Value returned if item selected */
63     CHAR_P accelerator;         /* Character used to select item from menu */
64     CHAR_P group_accel;         /* Group accelerator for menu item, if any */
65     int attr;                   /* Text attributes for item */
66     const char *str;            /* Text of menu item */
67     BOOLEAN_P presel;           /* Whether menu item should be preselected */
68     boolean selected;           /* Whether item is currently selected */
69     int page_num;               /* Display page number for entry */
70     int line_num;               /* Line number on page where entry begins */
71     int num_lines;              /* Number of lines entry uses on page */
72     int count;                  /* Count for selected item */
73     struct nhmi *prev_item;     /* Pointer to previous entry */
74     struct nhmi *next_item;     /* Pointer to next entry */
75 } nhmenu_item;
76 
77 typedef struct nhm {
78     winid wid;                  /* NetHack window id */
79     const char *prompt;         /* Menu prompt text */
80     nhmenu_item *entries;       /* Menu entries */
81     int num_entries;            /* Number of menu entries */
82     int num_pages;              /* Number of display pages for entry */
83     int height;                 /* Window height of menu */
84     int width;                  /* Window width of menu */
85     boolean reuse_accels;       /* Non-unique accelerators per page */
86     boolean bottom_heavy;       /* display multi-page menu starting at end */
87     struct nhm *prev_menu;      /* Pointer to previous entry */
88     struct nhm *next_menu;      /* Pointer to next entry */
89 } nhmenu;
90 
91 typedef enum menu_op_type {
92     SELECT,
93     DESELECT,
94     INVERT
95 } menu_op;
96 
97 static nhmenu_item *curs_new_menu_item(winid, const char *);
98 static void curs_pad_menu(nhmenu *, boolean);
99 static nhmenu *get_menu(winid wid);
100 static char menu_get_accel(boolean first);
101 static void menu_determine_pages(nhmenu *menu);
102 static boolean menu_is_multipage(nhmenu *menu, int width, int height);
103 static void menu_win_size(nhmenu *menu);
104 static void menu_display_page(nhmenu *menu, WINDOW * win, int page_num,
105                               char *);
106 static int menu_get_selections(WINDOW * win, nhmenu *menu, int how);
107 static void menu_select_deselect(WINDOW * win, nhmenu_item *item,
108                                  menu_op operation, int);
109 static int menu_operation(WINDOW * win, nhmenu *menu, menu_op operation,
110                           int page_num);
111 static void menu_clear_selections(nhmenu *menu);
112 static int menu_max_height(void);
113 
114 static nhmenu *nhmenus = NULL;  /* NetHack menu array */
115 
116 /* maximum number of selector entries per page; if '$' is present
117    it isn't counted toward maximum, so actual max per page is 53 */
118 #define MAX_ACCEL_PER_PAGE 52 /* 'a'..'z' + 'A'..'Z' */
119 /* TODO?  limit per page should be ignored for perm_invent, which might
120    have some '#' overflow entries and isn't used to select items */
121 
122 
123 /* Get a line of text from the player, such as asking for a character name
124    or a wish.  Note: EDIT_GETLIN not supported for popup prompting. */
125 
126 void
curses_line_input_dialog(const char * prompt,char * answer,int buffer)127 curses_line_input_dialog(const char *prompt, char *answer, int buffer)
128 {
129     int map_height, map_width, maxwidth, remaining_buf, winx, winy, count;
130     WINDOW *askwin, *bwin;
131     char *tmpstr;
132     int prompt_width, prompt_height = 1, height = prompt_height;
133     char input[BUFSZ];
134 
135     /* if messages were being suppressed for the remainder of the turn,
136        re-activate them now that input is being requested */
137     curses_got_input();
138 
139     if (buffer > (int) sizeof input)
140         buffer = (int) sizeof input;
141     /* +1: space between prompt and answer; buffer already accounts for \0 */
142     prompt_width = (int) strlen(prompt) + 1 + buffer;
143     maxwidth = term_cols - 2;
144 
145     if (iflags.window_inited) {
146         if (!iflags.wc_popup_dialog) {
147             curses_message_win_getline(prompt, answer, buffer);
148             return;
149         }
150         curses_get_window_size(MAP_WIN, &map_height, &map_width);
151         if ((prompt_width + 2) > map_width)
152             maxwidth = map_width - 2;
153     }
154 
155     if (prompt_width > maxwidth) {
156         prompt_height = curses_num_lines(prompt, maxwidth);
157         height = prompt_height;
158         prompt_width = maxwidth;
159         tmpstr = curses_break_str(prompt, maxwidth, prompt_height);
160         remaining_buf = buffer - ((int) strlen(tmpstr) - 1);
161         if (remaining_buf > 0) {
162             height += (remaining_buf / prompt_width);
163             if ((remaining_buf % prompt_width) > 0) {
164                 height++;
165             }
166         }
167         free(tmpstr);
168     }
169 
170     bwin = curses_create_window(prompt_width, height,
171                                 iflags.window_inited ? UP : CENTER);
172     wrefresh(bwin);
173     getbegyx(bwin, winy, winx);
174     askwin = newwin(height, prompt_width, winy + 1, winx + 1);
175 
176     for (count = 0; count < prompt_height; count++) {
177         tmpstr = curses_break_str(prompt, maxwidth, count + 1);
178         mvwaddstr(askwin, count, 0, tmpstr);
179         if (count == prompt_height - 1) { /* Last line */
180             if ((int) strlen(tmpstr) < maxwidth)
181                 waddch(askwin, ' ');
182             else
183                 wmove(askwin, count + 1, 0);
184         }
185         free(tmpstr);
186     }
187 
188     echo();
189     curs_set(1);
190     wgetnstr(askwin, input, buffer - 1);
191     curs_set(0);
192     Strcpy(answer, input);
193     werase(bwin);
194     delwin(bwin);
195     curses_destroy_win(askwin);
196     noecho();
197 }
198 
199 
200 /* Get a single character response from the player, such as a y/n prompt */
201 
202 int
curses_character_input_dialog(const char * prompt,const char * choices,CHAR_P def)203 curses_character_input_dialog(const char *prompt, const char *choices,
204                               CHAR_P def)
205 {
206     WINDOW *askwin = NULL;
207 #ifdef PDCURSES
208     WINDOW *message_window;
209 #endif
210     int answer, count, maxwidth, map_height, map_width;
211     char *linestr;
212     char askstr[BUFSZ + QBUFSZ];
213     char choicestr[QBUFSZ];
214     int prompt_width;
215     int prompt_height = 1;
216     boolean any_choice = FALSE;
217     boolean accept_count = FALSE;
218 
219     /* if messages were being suppressed for the remainder of the turn,
220        re-activate them now that input is being requested */
221     curses_got_input();
222 
223     if (invent || (moves > 1)) {
224         curses_get_window_size(MAP_WIN, &map_height, &map_width);
225     } else {
226         map_height = term_rows;
227         map_width = term_cols;
228     }
229 
230 #ifdef PDCURSES
231     message_window = curses_get_nhwin(MESSAGE_WIN);
232 #endif
233     maxwidth = map_width - 2;
234 
235     if (choices != NULL) {
236         for (count = 0; choices[count] != '\0'; count++) {
237             if (choices[count] == '#') { /* Accept a count */
238                 accept_count = TRUE;
239             }
240         }
241         choicestr[0] = ' ';
242         choicestr[1] = '[';
243         for (count = 0; choices[count] != '\0'; count++) {
244             if (choices[count] == '\033') { /* Escape */
245                 break;
246             }
247             choicestr[count + 2] = choices[count];
248         }
249         choicestr[count + 2] = ']';
250         if (((def >= 'A') && (def <= 'Z')) || ((def >= 'a') && (def <= 'z'))) {
251             choicestr[count + 3] = ' ';
252             choicestr[count + 4] = '(';
253             choicestr[count + 5] = def;
254             choicestr[count + 6] = ')';
255             choicestr[count + 7] = '\0';
256         } else {                /* No usable default choice */
257 
258             choicestr[count + 3] = '\0';
259             def = '\0';         /* Mark as no default */
260         }
261         strcpy(askstr, prompt);
262         strcat(askstr, choicestr);
263     } else {
264         strcpy(askstr, prompt);
265         any_choice = TRUE;
266     }
267 
268     /* +1: room for a trailing space where the cursor will rest */
269     prompt_width = (int) strlen(askstr) + 1;
270 
271     if ((prompt_width + 2) > maxwidth) {
272         prompt_height = curses_num_lines(askstr, maxwidth);
273         prompt_width = map_width - 2;
274     }
275 
276     if (iflags.wc_popup_dialog /*|| curses_stupid_hack*/) {
277         askwin = curses_create_window(prompt_width, prompt_height, UP);
278         for (count = 0; count < prompt_height; count++) {
279             linestr = curses_break_str(askstr, maxwidth, count + 1);
280             mvwaddstr(askwin, count + 1, 1, linestr);
281             free(linestr);
282         }
283 
284         wrefresh(askwin);
285     } else {
286         /* TODO: add SUPPRESS_HISTORY flag, then after getting a response,
287            append it and use put_msghistory() on combined prompt+answer */
288         custompline(OVERRIDE_MSGTYPE, "%s", askstr);
289     }
290 
291     /*curses_stupid_hack = 0; */
292 
293     curs_set(1);
294     while (1) {
295 #ifdef PDCURSES
296         answer = wgetch(message_window);
297 #else
298         answer = getch();
299 #endif
300         if (answer == ERR) {
301             answer = def;
302             break;
303         }
304 
305         answer = curses_convert_keys(answer);
306 
307         if (answer == KEY_ESC) {
308             if (choices == NULL) {
309                 break;
310             }
311             answer = def;
312             for (count = 0; choices[count] != '\0'; count++) {
313                 if (choices[count] == 'q') {    /* q is preferred over n */
314                     answer = 'q';
315                 } else if ((choices[count] == 'n') && answer != 'q') {
316                     answer = 'n';
317                 }
318             }
319             break;
320         } else if ((answer == '\n') || (answer == '\r') || (answer == ' ')) {
321             if ((choices != NULL) && (def != '\0')) {
322                 answer = def;
323             }
324             break;
325         }
326 
327         if (digit(answer)) {
328             if (accept_count) {
329                 if (answer != '0') {
330                     yn_number = curses_get_count(answer - '0');
331                     touchwin(askwin);
332                     refresh();
333                 }
334 
335                 answer = '#';
336                 break;
337             }
338         }
339 
340         if (any_choice) {
341             break;
342         }
343 
344         if (choices != NULL && answer != '\0' && index(choices, answer))
345             break;
346     }
347     curs_set(0);
348 
349     if (iflags.wc_popup_dialog) {
350         /* Kludge to make prompt visible after window is dismissed
351            when inputting a number */
352         if (digit(answer)) {
353             custompline(OVERRIDE_MSGTYPE, "%s", askstr);
354             curs_set(1);
355         }
356 
357         curses_destroy_win(askwin);
358     } else {
359         curses_clear_unhighlight_message_window();
360     }
361 
362     return answer;
363 }
364 
365 
366 /* Return an extended command from the user */
367 
368 int
curses_ext_cmd()369 curses_ext_cmd()
370 {
371     int count, letter, prompt_width, startx, starty, winx, winy;
372     int messageh, messagew, maxlen = BUFSZ - 1;
373     int ret = -1;
374     char cur_choice[BUFSZ];
375     int matches = 0;
376     WINDOW *extwin = NULL, *extwin2 = NULL;
377 
378     if (iflags.extmenu) {
379         return extcmd_via_menu();
380     }
381 
382     startx = 0;
383     starty = 0;
384     if (iflags.wc_popup_dialog) { /* Prompt in popup window */
385         int x0, y0, w, h; /* bounding coords of popup */
386 
387         extwin2 = curses_create_window(25, 1, UP);
388         wrefresh(extwin2);
389         /* create window inside window to prevent overwriting of border */
390         getbegyx(extwin2, y0, x0);
391         getmaxyx(extwin2, h, w);
392         extwin = newwin(1, w - 2, y0 + 1, x0 + 1);
393         if (w - 4 < maxlen)
394             maxlen = w - 4;
395         nhUse(h); /* needed only to give getmaxyx three arguments */
396     } else {
397         curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
398         curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);
399 
400         if (curses_window_has_border(MESSAGE_WIN)) {
401             winx++;
402             winy++;
403         }
404 
405         winy += messageh - 1;
406         extwin = newwin(1, messagew - 2, winy, winx);
407         if (messagew - 4 < maxlen)
408             maxlen = messagew - 4;
409         custompline(OVERRIDE_MSGTYPE, "#");
410     }
411 
412     cur_choice[0] = '\0';
413 
414     while (1) {
415         wmove(extwin, starty, startx);
416         waddstr(extwin, "# ");
417         wmove(extwin, starty, startx + 2);
418         waddstr(extwin, cur_choice);
419         wmove(extwin, starty, (int) strlen(cur_choice) + startx + 2);
420         wclrtoeol(extwin);
421 
422         /* if we have an autocomplete command, AND it matches uniquely */
423         if (matches == 1) {
424             curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, ON);
425             wmove(extwin, starty, (int) strlen(cur_choice) + startx + 2);
426             wprintw(extwin, "%s",
427                     extcmdlist[ret].ef_txt + (int) strlen(cur_choice));
428             curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, OFF);
429         }
430 
431         curs_set(1);
432         wrefresh(extwin);
433         letter = getch();
434         curs_set(0);
435         prompt_width = (int) strlen(cur_choice);
436         matches = 0;
437 
438         if (letter == '\033' || letter == ERR) {
439             ret = -1;
440             break;
441         }
442 
443         if (letter == '\r' || letter == '\n') {
444             if (ret == -1) {
445                 for (count = 0; extcmdlist[count].ef_txt; count++) {
446                     if (!strcasecmp(cur_choice, extcmdlist[count].ef_txt)) {
447                         ret = count;
448                         break;
449                     }
450                 }
451             }
452             break;
453         }
454 
455         if (letter == '\177') /* DEL/Rubout */
456             letter = '\b';
457         if (letter == '\b' || letter == KEY_BACKSPACE
458             || (erase_char && letter == (int) (uchar) erase_char)) {
459             if (prompt_width == 0) {
460                 ret = -1;
461                 break;
462             } else {
463                 cur_choice[prompt_width - 1] = '\0';
464                 letter = '*';
465                 prompt_width--;
466             }
467 
468         /* honor kill_char if it's ^U or similar, but not if it's '@' */
469         } else if (kill_char && letter == (int) (uchar) kill_char
470                    && (letter < ' ' || letter >= '\177')) { /*ASCII*/
471             cur_choice[0] = '\0';
472             letter = '*';
473             prompt_width = 0;
474         }
475 
476         if (letter != '*' && prompt_width < maxlen) {
477             cur_choice[prompt_width] = letter;
478             cur_choice[prompt_width + 1] = '\0';
479             ret = -1;
480         }
481         for (count = 0; extcmdlist[count].ef_txt; count++) {
482             if (!wizard && (extcmdlist[count].flags & WIZMODECMD))
483                 continue;
484             if (!(extcmdlist[count].flags & AUTOCOMPLETE))
485                 continue;
486             if ((int) strlen(extcmdlist[count].ef_txt) > prompt_width) {
487                 if (strncasecmp(cur_choice, extcmdlist[count].ef_txt,
488                                 prompt_width) == 0) {
489                     if ((extcmdlist[count].ef_txt[prompt_width] ==
490                          lowc(letter)) || letter == '*') {
491                         if (matches == 0) {
492                             ret = count;
493                         }
494 
495                         matches++;
496                     }
497                 }
498             }
499         }
500     }
501 
502     curses_destroy_win(extwin);
503     if (extwin2)
504         curses_destroy_win(extwin2);
505     return ret;
506 }
507 
508 
509 /* Initialize a menu from given NetHack winid */
510 
511 void
curses_create_nhmenu(winid wid)512 curses_create_nhmenu(winid wid)
513 {
514     nhmenu *new_menu = NULL;
515     nhmenu *menuptr = nhmenus;
516     nhmenu_item *menu_item_ptr = NULL;
517     nhmenu_item *tmp_menu_item = NULL;
518 
519     new_menu = get_menu(wid);
520 
521     if (new_menu != NULL) {
522         /* Reuse existing menu, clearing out current entries */
523         menu_item_ptr = new_menu->entries;
524 
525         if (menu_item_ptr != NULL) {
526             while (menu_item_ptr->next_item != NULL) {
527                 tmp_menu_item = menu_item_ptr->next_item;
528                 free((genericptr_t) menu_item_ptr->str);
529                 free((genericptr_t) menu_item_ptr);
530                 menu_item_ptr = tmp_menu_item;
531             }
532             free((genericptr_t) menu_item_ptr->str);
533             free((genericptr_t) menu_item_ptr); /* Last entry */
534             new_menu->entries = NULL;
535         }
536         if (new_menu->prompt != NULL) { /* Reusing existing menu */
537             free((genericptr_t) new_menu->prompt);
538             new_menu->prompt = NULL;
539         }
540         new_menu->num_pages = 0;
541         new_menu->height = 0;
542         new_menu->width = 0;
543         new_menu->reuse_accels = FALSE;
544         new_menu->bottom_heavy = FALSE;
545         return;
546     }
547 
548     new_menu = (nhmenu *) alloc((signed) sizeof (nhmenu));
549     new_menu->wid = wid;
550     new_menu->prompt = NULL;
551     new_menu->entries = NULL;
552     new_menu->num_pages = 0;
553     new_menu->height = 0;
554     new_menu->width = 0;
555     new_menu->reuse_accels = FALSE;
556     new_menu->bottom_heavy = FALSE;
557     new_menu->next_menu = NULL;
558 
559     if (nhmenus == NULL) {      /* no menus in memory yet */
560         new_menu->prev_menu = NULL;
561         nhmenus = new_menu;
562     } else {
563         while (menuptr->next_menu != NULL) {
564             menuptr = menuptr->next_menu;
565         }
566         new_menu->prev_menu = menuptr;
567         menuptr->next_menu = new_menu;
568     }
569 }
570 
571 static nhmenu_item *
curs_new_menu_item(winid wid,const char * str)572 curs_new_menu_item(winid wid, const char *str)
573 {
574     char *new_str;
575     nhmenu_item *new_item;
576 
577     new_str = curses_copy_of(str);
578     curses_rtrim(new_str);
579     new_item = (nhmenu_item *) alloc((unsigned) sizeof (nhmenu_item));
580     new_item->wid = wid;
581     new_item->glyph = NO_GLYPH;
582     new_item->identifier = zeroany;
583     new_item->accelerator = '\0';
584     new_item->group_accel = '\0';
585     new_item->attr = 0;
586     new_item->str = new_str;
587     new_item->presel = FALSE;
588     new_item->selected = FALSE;
589     new_item->page_num = 0;
590     new_item->line_num = 0;
591     new_item->num_lines = 0;
592     new_item->count = -1;
593     new_item->next_item = NULL;
594     return new_item;
595 }
596 /* Add a menu item to the given menu window */
597 
598 void
curses_add_nhmenu_item(winid wid,int glyph,const ANY_P * identifier,CHAR_P accelerator,CHAR_P group_accel,int attr,const char * str,BOOLEAN_P presel)599 curses_add_nhmenu_item(winid wid, int glyph, const ANY_P *identifier,
600                        CHAR_P accelerator, CHAR_P group_accel, int attr,
601                        const char *str, BOOLEAN_P presel)
602 {
603     nhmenu_item *new_item, *current_items, *menu_item_ptr;
604     nhmenu *current_menu = get_menu(wid);
605 
606     if (current_menu == NULL) {
607         impossible(
608            "curses_add_nhmenu_item: attempt to add item to nonexistent menu");
609         return;
610     }
611 
612     if (str == NULL) {
613         return;
614     }
615 
616     new_item = curs_new_menu_item(wid, str);
617     new_item->glyph = glyph;
618     new_item->identifier = *identifier;
619     new_item->accelerator = accelerator;
620     new_item->group_accel = group_accel;
621     new_item->attr = attr;
622     new_item->presel = presel;
623 
624     current_items = current_menu->entries;
625     menu_item_ptr = current_items;
626 
627     if (current_items == NULL) {
628         new_item->prev_item = NULL;
629         current_menu->entries = new_item;
630     } else {
631         while (menu_item_ptr->next_item != NULL) {
632             menu_item_ptr = menu_item_ptr->next_item;
633         }
634         new_item->prev_item = menu_item_ptr;
635         menu_item_ptr->next_item = new_item;
636     }
637 }
638 
639 /* for menu->bottom_heavy -- insert enough blank lines at top of
640    first page to make the last page become a full one */
641 static void
curs_pad_menu(nhmenu * current_menu,boolean do_pad UNUSED)642 curs_pad_menu(nhmenu *current_menu, boolean do_pad UNUSED)
643 {
644     nhmenu_item *menu_item_ptr;
645     int numpages = current_menu->num_pages;
646 
647     /* caller has already called menu_win_size() */
648     menu_determine_pages(current_menu); /* sets 'menu->num_pages' */
649     numpages = current_menu->num_pages;
650     /* pad beginning of menu so that partial last page becomes full;
651        might be slightly less than full if any entries take multiple
652        lines and the padding would force those to span page boundary
653        and that gets prevented; so we re-count the number of pages
654        with every insertion instead of trying to calculate the number
655        of them to add */
656     do {
657         menu_item_ptr = curs_new_menu_item(current_menu->wid, "");
658         menu_item_ptr->next_item = current_menu->entries;
659         current_menu->entries->prev_item = menu_item_ptr;
660         current_menu->entries = menu_item_ptr;
661         current_menu->num_entries += 1;
662 
663         menu_determine_pages(current_menu);
664     } while (current_menu->num_pages == numpages);
665 
666     /* we inserted blank lines at beginning until final entry spilled
667        over to another page; take the most recent blank one back out */
668     current_menu->num_entries -= 1;
669     current_menu->entries = menu_item_ptr->next_item;
670     current_menu->entries->prev_item = (nhmenu_item *) 0;
671     free((genericptr_t) menu_item_ptr->str);
672     free((genericptr_t) menu_item_ptr);
673 
674     /* reset page count; shouldn't need to re-count */
675     current_menu->num_pages = numpages;
676     return;
677 }
678 
679 /* mark ^P message recall menu, for msg_window:full (FIFO), where we'll
680    start viewing on the last page so be able to see most recent immediately */
681 void
curs_menu_set_bottom_heavy(winid wid)682 curs_menu_set_bottom_heavy(winid wid)
683 {
684     nhmenu *menu = get_menu(wid);
685 
686     /*
687      * Called after end_menu + finalize_nhmenu,
688      * before select_menu + display_nhmenu.
689      */
690     menu_win_size(menu); /* (normally not done until display_nhmenu) */
691     if (menu_is_multipage(menu, menu->width, menu->height)) {
692         menu->bottom_heavy = TRUE;
693 
694         /* insert enough blank lines at top of first page to make the
695            last page become a full one */
696         curs_pad_menu(menu, TRUE);
697     }
698     return;
699 }
700 
701 /* No more entries are to be added to menu, so details of the menu can be
702  finalized in memory */
703 
704 void
curses_finalize_nhmenu(winid wid,const char * prompt)705 curses_finalize_nhmenu(winid wid, const char *prompt)
706 {
707     int count = 0;
708     nhmenu_item *menu_item_ptr;
709     nhmenu *current_menu = get_menu(wid);
710 
711     if (current_menu == NULL) {
712         impossible(
713               "curses_finalize_nhmenu: attempt to finalize nonexistent menu");
714         return;
715     }
716     for (menu_item_ptr = current_menu->entries;
717          menu_item_ptr != NULL; menu_item_ptr = menu_item_ptr->next_item)
718         count++;
719 
720     current_menu->num_entries = count;
721     current_menu->prompt = curses_copy_of(prompt);
722 }
723 
724 
725 /* Display a nethack menu, and return a selection, if applicable */
726 
727 int
curses_display_nhmenu(winid wid,int how,MENU_ITEM_P ** _selected)728 curses_display_nhmenu(winid wid, int how, MENU_ITEM_P ** _selected)
729 {
730     nhmenu *current_menu = get_menu(wid);
731     nhmenu_item *menu_item_ptr;
732     int num_chosen, count;
733     WINDOW *win;
734     MENU_ITEM_P *selected = NULL;
735 
736     *_selected = NULL;
737 
738     if (current_menu == NULL) {
739         impossible(
740                 "curses_display_nhmenu: attempt to display nonexistent menu");
741         return '\033';
742     }
743 
744     menu_item_ptr = current_menu->entries;
745 
746     if (menu_item_ptr == NULL) {
747         impossible("curses_display_nhmenu: attempt to display empty menu");
748         return '\033';
749     }
750 
751     /* Reset items to unselected to clear out selections from previous
752        invocations of this menu, and preselect appropriate items */
753     while (menu_item_ptr != NULL) {
754         menu_item_ptr->selected = menu_item_ptr->presel;
755         menu_item_ptr = menu_item_ptr->next_item;
756     }
757 
758     menu_win_size(current_menu);
759     menu_determine_pages(current_menu);
760 
761     /* Display pre and post-game menus centered */
762     if ((moves <= 1 && !invent) || program_state.gameover) {
763         win = curses_create_window(current_menu->width,
764                                    current_menu->height, CENTER);
765     } else { /* Display during-game menus on the right out of the way */
766         win = curses_create_window(current_menu->width,
767                                    current_menu->height, RIGHT);
768     }
769 
770     num_chosen = menu_get_selections(win, current_menu, how);
771     curses_destroy_win(win);
772 
773     if (num_chosen > 0) {
774         selected = (MENU_ITEM_P *) alloc((unsigned)
775                                          (num_chosen * sizeof (MENU_ITEM_P)));
776         count = 0;
777 
778         menu_item_ptr = current_menu->entries;
779 
780         while (menu_item_ptr != NULL) {
781             if (menu_item_ptr->selected) {
782                 if (count == num_chosen) {
783                     impossible("curses_display_nhmenu: Selected items "
784                           "exceeds expected number");
785                      break;
786                 }
787                 selected[count].item = menu_item_ptr->identifier;
788                 selected[count].count = menu_item_ptr->count;
789                 count++;
790             }
791             menu_item_ptr = menu_item_ptr->next_item;
792         }
793 
794         if (count != num_chosen) {
795             impossible(
796            "curses_display_nhmenu: Selected items less than expected number");
797         }
798     }
799 
800     *_selected = selected;
801 
802     return num_chosen;
803 }
804 
805 
806 boolean
curses_menu_exists(winid wid)807 curses_menu_exists(winid wid)
808 {
809     if (get_menu(wid) != NULL) {
810         return TRUE;
811     } else {
812         return FALSE;
813     }
814 }
815 
816 /* Delete the menu associated with the given NetHack winid from memory */
817 
818 void
curses_del_menu(winid wid,boolean del_wid_too)819 curses_del_menu(winid wid, boolean del_wid_too)
820 {
821     nhmenu_item *tmp_menu_item;
822     nhmenu_item *menu_item_ptr;
823     nhmenu *tmpmenu;
824     nhmenu *current_menu = get_menu(wid);
825 
826     if (current_menu == NULL) {
827         return;
828     }
829 
830     menu_item_ptr = current_menu->entries;
831 
832     /* First free entries associated with this menu from memory */
833     if (menu_item_ptr != NULL) {
834         while (menu_item_ptr->next_item != NULL) {
835             tmp_menu_item = menu_item_ptr->next_item;
836             free((genericptr_t) menu_item_ptr->str);
837             free(menu_item_ptr);
838             menu_item_ptr = tmp_menu_item;
839         }
840         free((genericptr_t) menu_item_ptr->str);
841         free(menu_item_ptr);    /* Last entry */
842         current_menu->entries = NULL;
843     }
844 
845     /* Now unlink the menu from the list and free it as well */
846     if ((tmpmenu = current_menu->prev_menu) != NULL) {
847         tmpmenu->next_menu = current_menu->next_menu;
848     } else {
849         nhmenus = current_menu->next_menu;      /* New head node or NULL */
850     }
851     if ((tmpmenu = current_menu->next_menu) != NULL) {
852         tmpmenu->prev_menu = current_menu->prev_menu;
853     }
854 
855     if (current_menu->prompt)
856         free((genericptr_t) current_menu->prompt);
857     free((genericptr_t) current_menu);
858 
859     if (del_wid_too)
860         curses_del_wid(wid);
861 }
862 
863 
864 /* return a pointer to the menu associated with the given NetHack winid */
865 
866 static nhmenu *
get_menu(winid wid)867 get_menu(winid wid)
868 {
869     nhmenu *menuptr = nhmenus;
870 
871     while (menuptr != NULL) {
872         if (menuptr->wid == wid) {
873             return menuptr;
874         }
875         menuptr = menuptr->next_menu;
876     }
877 
878     return NULL;                /* Not found */
879 }
880 
881 
882 static char
menu_get_accel(boolean first)883 menu_get_accel(boolean first)
884 {
885     static char next_letter;
886     char ret;
887 
888     if (first) {
889         next_letter = 'a';
890     }
891 
892     ret = next_letter;
893 
894     if ((next_letter < 'z' && next_letter >= 'a')
895         || (next_letter < 'Z' && next_letter >= 'A')) {
896         next_letter++;
897     } else if (next_letter == 'z') {
898         next_letter = 'A';
899     } else if (next_letter == 'Z') {
900         next_letter = 'a'; /* wrap to beginning */
901     }
902 
903     return ret;
904 }
905 
906 
907 /* Determine if menu will require multiple pages to display */
908 
909 static boolean
menu_is_multipage(nhmenu * menu,int width,int height)910 menu_is_multipage(nhmenu *menu, int width, int height)
911 {
912     int num_lines;
913     int curline = 0, accel_per_page = 0;
914     nhmenu_item *menu_item_ptr = menu->entries;
915 
916     if (*menu->prompt) {
917         curline += curses_num_lines(menu->prompt, width) + 1;
918     }
919 
920     while (menu_item_ptr != NULL) {
921         menu_item_ptr->line_num = curline;
922         if (menu_item_ptr->identifier.a_void == NULL) {
923             num_lines = curses_num_lines(menu_item_ptr->str, width);
924         } else {
925             if (menu_item_ptr->accelerator != GOLD_SYM) {
926                 if (++accel_per_page > MAX_ACCEL_PER_PAGE)
927                     break;
928             }
929             /* Add space for accelerator */
930             num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
931         }
932         menu_item_ptr->num_lines = num_lines;
933         curline += num_lines;
934         menu_item_ptr = menu_item_ptr->next_item;
935         if (curline > height
936             || (curline > height - 2 && height == menu_max_height())) {
937             break;
938         }
939     }
940     return (menu_item_ptr != NULL) ? TRUE : FALSE;
941 }
942 
943 
944 /* Determine which entries go on which page, and total number of pages */
945 
946 static void
menu_determine_pages(nhmenu * menu)947 menu_determine_pages(nhmenu *menu)
948 {
949     int tmpline, num_lines, accel_per_page;
950     int curline = 0;
951     int page_num = 1;
952     nhmenu_item *menu_item_ptr = menu->entries;
953     int width = menu->width;
954     int height = menu->height;
955     int page_end = height;
956 
957 
958     if (*menu->prompt) {
959         curline += curses_num_lines(menu->prompt, width) + 1;
960     }
961     tmpline = curline;
962 
963     if (menu_is_multipage(menu, width, height)) {
964         page_end -= 2;          /* Room to display current page number */
965     }
966     accel_per_page = 0;
967 
968     /* Determine what entries belong on which page */
969     for (menu_item_ptr = menu->entries; menu_item_ptr != NULL;
970          menu_item_ptr = menu_item_ptr->next_item) {
971         menu_item_ptr->page_num = page_num;
972         menu_item_ptr->line_num = curline;
973         if (menu_item_ptr->identifier.a_void == NULL) {
974             num_lines = curses_num_lines(menu_item_ptr->str, width);
975         } else {
976             if (menu_item_ptr->accelerator != GOLD_SYM)
977                 ++accel_per_page;
978             /* Add space for accelerator */
979             num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
980         }
981         menu_item_ptr->num_lines = num_lines;
982         curline += num_lines;
983         if (curline > page_end || accel_per_page > MAX_ACCEL_PER_PAGE) {
984             ++page_num;
985             accel_per_page = 0; /* reset */
986             curline = tmpline;
987             /* Move ptr back so entry will be reprocessed on new page */
988             menu_item_ptr = menu_item_ptr->prev_item;
989         }
990     }
991 
992     menu->num_pages = page_num;
993 }
994 
995 
996 /* Determine dimensions of menu window based on term size and entries */
997 
998 static void
menu_win_size(nhmenu * menu)999 menu_win_size(nhmenu *menu)
1000 {
1001     int maxwidth, maxheight, curentrywidth, lastline;
1002     int maxentrywidth = 0;
1003     int maxheaderwidth = menu->prompt ? (int) strlen(menu->prompt) : 0;
1004     nhmenu_item *menu_item_ptr;
1005 
1006     if (program_state.gameover) {
1007         /* for final inventory disclosure, use full width */
1008         maxwidth = term_cols - 2; /* +2: borders assumed */
1009     } else {
1010         /* this used to be 38, which is 80/2 - 2 (half a 'normal' sized
1011            screen minus room for a border box), but some data files
1012            have been manually formatted for 80 columns (usually limited
1013            to 78 but sometimes 79, rarely 80 itself) and using a value
1014            less that 40 meant that a full line would wrap twice:
1015            1..38, 39..76, and 77..80 */
1016         maxwidth = 40; /* Reasonable minimum usable width */
1017         if ((term_cols / 2) > maxwidth)
1018             maxwidth = (term_cols / 2); /* Half the screen */
1019     }
1020     maxheight = menu_max_height();
1021 
1022     /* First, determine the width of the longest menu entry */
1023     for (menu_item_ptr = menu->entries; menu_item_ptr != NULL;
1024          menu_item_ptr = menu_item_ptr->next_item) {
1025         curentrywidth = (int) strlen(menu_item_ptr->str);
1026         if (menu_item_ptr->identifier.a_void == NULL) {
1027             if (curentrywidth > maxheaderwidth) {
1028                 maxheaderwidth = curentrywidth;
1029             }
1030         } else {
1031             /* Add space for accelerator (selector letter) */
1032             curentrywidth += 4;
1033 #if 0 /* FIXME: menu glyphs */
1034             if (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs)
1035                 curentrywidth += 2;
1036 #endif
1037         }
1038         if (curentrywidth > maxentrywidth) {
1039             maxentrywidth = curentrywidth;
1040         }
1041     }
1042 
1043     /*
1044      * 3.6.3: This used to set maxwidth to maxheaderwidth when that was
1045      * bigger but only set it to maxentrywidth if the latter was smaller,
1046      * so entries wider than the default would always wrap unless at
1047      * least one header or separator line was long, even when there was
1048      * lots of space available to display them without wrapping.  The
1049      * reason to force narrow menus isn't known.  It may have been to
1050      * reduce the amount of map rewriting when menu is dismissed, but if
1051      * so, that was an issue due to excessive screen writing for the map
1052      * (output was flushed for each character) which was fixed long ago.
1053      */
1054     maxwidth = max(maxentrywidth, maxheaderwidth);
1055     if (maxwidth > term_cols - 2) { /* -2: space for left and right borders */
1056         maxwidth = term_cols - 2;
1057     }
1058 
1059     /* Possibly reduce height if only 1 page */
1060     if (!menu_is_multipage(menu, maxwidth, maxheight)) {
1061         menu_item_ptr = menu->entries;
1062 
1063         while (menu_item_ptr->next_item != NULL) {
1064             menu_item_ptr = menu_item_ptr->next_item;
1065         }
1066 
1067         lastline = (menu_item_ptr->line_num) + menu_item_ptr->num_lines;
1068 
1069         if (lastline < maxheight) {
1070             maxheight = lastline;
1071         }
1072     }
1073 
1074     /* avoid a tiny popup window; when it's shown over the endings of
1075        old messsages rather than over the map, it is fairly easy for
1076        the player to overlook it, particularly when walking around and
1077        stepping on a pile of 2 items; also, multi-page menus need enough
1078        room for "(Page M of N) => " even if all entries are narrower
1079        than that; we specify same minimum width even when single page */
1080     menu->width = max(maxwidth, 25);
1081     menu->height = max(maxheight, 5);
1082 }
1083 
1084 
1085 /* Displays menu selections in the given window */
1086 
1087 static void
menu_display_page(nhmenu * menu,WINDOW * win,int page_num,char * selectors)1088 menu_display_page(nhmenu *menu, WINDOW * win, int page_num, char *selectors)
1089 {
1090     nhmenu_item *menu_item_ptr;
1091     int count, curletter, entry_cols, start_col, num_lines;
1092     char *tmpstr;
1093     boolean first_accel = TRUE;
1094     int color = NO_COLOR, attr = A_NORMAL;
1095     boolean menu_color = FALSE;
1096 
1097     /* letters assigned to entries on current page */
1098     if (selectors)
1099         (void) memset((genericptr_t) selectors, 0, 256);
1100 
1101     /* Cycle through entries until we are on the correct page */
1102 
1103     menu_item_ptr = menu->entries;
1104 
1105     while (menu_item_ptr != NULL) {
1106         if (menu_item_ptr->page_num == page_num) {
1107             break;
1108         }
1109         menu_item_ptr = menu_item_ptr->next_item;
1110     }
1111 
1112     if (menu_item_ptr == NULL) {        /* Page not found */
1113         impossible("menu_display_page: attempt to display nonexistent page");
1114         return;
1115     }
1116 
1117     werase(win);
1118 
1119     if (menu->prompt && *menu->prompt) {
1120         num_lines = curses_num_lines(menu->prompt, menu->width);
1121 
1122         for (count = 0; count < num_lines; count++) {
1123             tmpstr = curses_break_str(menu->prompt, menu->width, count + 1);
1124             mvwprintw(win, count + 1, 1, "%s", tmpstr);
1125             free(tmpstr);
1126         }
1127     }
1128 
1129     /* Display items for current page */
1130 
1131     while (menu_item_ptr != NULL) {
1132         if (menu_item_ptr->page_num != page_num) {
1133             break;
1134         }
1135         if (menu_item_ptr->identifier.a_void != NULL) {
1136             if (menu_item_ptr->accelerator != 0) {
1137                 curletter = menu_item_ptr->accelerator;
1138             } else {
1139                 if (first_accel) {
1140                     curletter = menu_get_accel(TRUE);
1141                     first_accel = FALSE;
1142                     if (!menu->reuse_accels && (menu->num_pages > 1)) {
1143                         menu->reuse_accels = TRUE;
1144                     }
1145                 } else {
1146                     curletter = menu_get_accel(FALSE);
1147                 }
1148                 menu_item_ptr->accelerator = curletter;
1149             }
1150             /* we have a selector letter; tell caller about it */
1151             if (selectors)
1152                 selectors[(unsigned) (curletter & 0xFF)]++;
1153 
1154             if (menu_item_ptr->selected) {
1155                 curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
1156                 mvwaddch(win, menu_item_ptr->line_num + 1, 1, '<');
1157                 mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
1158                 mvwaddch(win, menu_item_ptr->line_num + 1, 3, '>');
1159                 curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
1160             } else {
1161                 curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
1162                 mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
1163                 curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
1164                 mvwprintw(win, menu_item_ptr->line_num + 1, 3, ") ");
1165             }
1166         }
1167         entry_cols = menu->width;
1168         start_col = 1;
1169 
1170         if (menu_item_ptr->identifier.a_void != NULL) {
1171             entry_cols -= 4;
1172             start_col += 4;
1173         }
1174 #if 0
1175         /* FIXME: menuglyphs not implemented yet */
1176         if (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs) {
1177             unsigned special;  /*notused */
1178 
1179             mapglyph(menu_item_ptr->glyph, &curletter, &color, &special, 0, 0, 0);
1180             curses_toggle_color_attr(win, color, NONE, ON);
1181             mvwaddch(win, menu_item_ptr->line_num + 1, start_col, curletter);
1182             curses_toggle_color_attr(win, color, NONE, OFF);
1183             mvwaddch(win, menu_item_ptr->line_num + 1, start_col + 1, ' ');
1184             entry_cols -= 2;
1185             start_col += 2;
1186         }
1187 #endif
1188         color = NO_COLOR;
1189         menu_color = iflags.use_menu_color
1190                      && get_menu_coloring(menu_item_ptr->str, &color, &attr);
1191         if (menu_color) {
1192             attr = curses_convert_attr(attr);
1193             if (color != NO_COLOR || attr != A_NORMAL)
1194                 curses_menu_color_attr(win, color, attr, ON);
1195         } else {
1196             attr = menu_item_ptr->attr;
1197             if (color != NO_COLOR || attr != A_NORMAL)
1198                 curses_toggle_color_attr(win, color, attr, ON);
1199         }
1200 
1201         num_lines = curses_num_lines(menu_item_ptr->str, entry_cols);
1202         for (count = 0; count < num_lines; count++) {
1203             if (menu_item_ptr->str && *menu_item_ptr->str) {
1204                 tmpstr = curses_break_str(menu_item_ptr->str,
1205                                           entry_cols, count + 1);
1206                 mvwprintw(win, menu_item_ptr->line_num + count + 1, start_col,
1207                           "%s", tmpstr);
1208                 free(tmpstr);
1209             }
1210         }
1211         if (color != NO_COLOR || attr != A_NORMAL) {
1212             if (menu_color)
1213                 curses_menu_color_attr(win, color, attr, OFF);
1214             else
1215                 curses_toggle_color_attr(win, color, attr, OFF);
1216         }
1217 
1218         menu_item_ptr = menu_item_ptr->next_item;
1219     }
1220 
1221     if (menu->num_pages > 1) {
1222         int footer_x, footwidth, shoesize = menu->num_pages;
1223 
1224         footwidth = (int) (sizeof "<- (Page X of Y) ->" - sizeof "");
1225         while (shoesize >= 10) { /* possible for pickup from big piles... */
1226              /* room for wider feet; extra digit for both X and Y */
1227             footwidth += 2;
1228             shoesize /= 10;
1229         }
1230         footer_x = !menu->bottom_heavy ? (menu->width - footwidth) : 2;
1231         if (page_num != 1) {
1232             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
1233             mvwaddstr(win, menu->height, footer_x, "<=");
1234             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
1235         }
1236         mvwprintw(win, menu->height, footer_x + 2, " (Page %d of %d) ",
1237                   page_num, menu->num_pages);
1238         if (page_num != menu->num_pages) {
1239             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
1240             mvwaddstr(win, menu->height, footer_x + footwidth - 2, "=>");
1241             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
1242         }
1243     }
1244     curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
1245     box(win, 0, 0);
1246     curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
1247     wrefresh(win);
1248 }
1249 
1250 
1251 static int
menu_get_selections(WINDOW * win,nhmenu * menu,int how)1252 menu_get_selections(WINDOW * win, nhmenu *menu, int how)
1253 {
1254     int curletter;
1255     int count = -1;
1256     int count_letter = '\0';
1257     int curpage = !menu->bottom_heavy ? 1 : menu->num_pages;
1258     int num_selected = 0;
1259     boolean dismiss = FALSE;
1260     char search_key[BUFSZ], selectors[256];
1261     nhmenu_item *menu_item_ptr = menu->entries;
1262 
1263     menu_display_page(menu, win, curpage, selectors);
1264 
1265     while (!dismiss) {
1266         curletter = getch();
1267 
1268         if (curletter == ERR) {
1269             num_selected = -1;
1270             dismiss = TRUE;
1271         }
1272 
1273         if (curletter == '\033') {
1274             curletter = curses_convert_keys(curletter);
1275         }
1276 
1277         switch (how) {
1278         case PICK_NONE:
1279             if (menu->num_pages == 1) {
1280                 if (curletter == KEY_ESC) {
1281                     num_selected = -1;
1282                 } else {
1283                     num_selected = 0;
1284                 }
1285                 dismiss = TRUE;
1286                 break;
1287             }
1288             break;
1289         case PICK_ANY:
1290             if (curletter <= 0 || curletter >= 256 || !selectors[curletter]) {
1291                 switch (curletter) {
1292                 case MENU_SELECT_PAGE:
1293                     (void) menu_operation(win, menu, SELECT, curpage);
1294                     break;
1295                 case MENU_SELECT_ALL:
1296                     curpage = menu_operation(win, menu, SELECT, 0);
1297                     break;
1298                 case MENU_UNSELECT_PAGE:
1299                     (void) menu_operation(win, menu, DESELECT, curpage);
1300                     break;
1301                 case MENU_UNSELECT_ALL:
1302                     curpage = menu_operation(win, menu, DESELECT, 0);
1303                     break;
1304                 case MENU_INVERT_PAGE:
1305                     (void) menu_operation(win, menu, INVERT, curpage);
1306                     break;
1307                 case MENU_INVERT_ALL:
1308                     curpage = menu_operation(win, menu, INVERT, 0);
1309                     break;
1310                 }
1311             }
1312             /*FALLTHRU*/
1313         default:
1314             if (isdigit(curletter)) {
1315                 count = curses_get_count(curletter - '0');
1316                 touchwin(win);
1317                 refresh();
1318                 curletter = getch();
1319                 if (count > 0) {
1320                     count_letter = curletter;
1321                 }
1322             }
1323         }
1324 
1325         if (curletter <= 0 || curletter >= 256 || !selectors[curletter]) {
1326             switch (curletter) {
1327             case KEY_ESC:
1328                 num_selected = -1;
1329                 dismiss = TRUE;
1330                 break;
1331             case '\n':
1332             case '\r':
1333                 dismiss = TRUE;
1334                 break;
1335             case KEY_RIGHT:
1336             case KEY_NPAGE:
1337             case MENU_NEXT_PAGE:
1338             case ' ':
1339                 if (curpage < menu->num_pages) {
1340                     curpage++;
1341                     menu_display_page(menu, win, curpage, selectors);
1342                 } else if (curletter == ' ') {
1343                     dismiss = TRUE;
1344                     break;
1345                 }
1346                 break;
1347             case KEY_LEFT:
1348             case KEY_PPAGE:
1349             case MENU_PREVIOUS_PAGE:
1350                 if (curpage > 1) {
1351                      curpage--;
1352                      menu_display_page(menu, win, curpage, selectors);
1353                 }
1354                 break;
1355             case KEY_END:
1356             case MENU_LAST_PAGE:
1357                 if (curpage != menu->num_pages) {
1358                     curpage = menu->num_pages;
1359                     menu_display_page(menu, win, curpage, selectors);
1360                 }
1361                 break;
1362             case KEY_HOME:
1363             case MENU_FIRST_PAGE:
1364                 if (curpage != 1) {
1365                     curpage = 1;
1366                     menu_display_page(menu, win, curpage, selectors);
1367                 }
1368                 break;
1369             case MENU_SEARCH:
1370                 search_key[0] = '\0';
1371                 curses_line_input_dialog("Search for:", search_key, BUFSZ);
1372 
1373                 refresh();
1374                 touchwin(win);
1375                 wrefresh(win);
1376 
1377                 if (!*search_key)
1378                     break;
1379 
1380                 menu_item_ptr = menu->entries;
1381 
1382                 while (menu_item_ptr != NULL) {
1383                     if (menu_item_ptr->identifier.a_void != NULL
1384                         && strstri(menu_item_ptr->str, search_key)) {
1385                         if (how == PICK_ONE) {
1386                             menu_clear_selections(menu);
1387                             menu_select_deselect(win, menu_item_ptr,
1388                                                  SELECT, curpage);
1389                             num_selected = 1;
1390                             dismiss = TRUE;
1391                             break;
1392                         } else {
1393                             menu_select_deselect(win, menu_item_ptr,
1394                                                  INVERT, curpage);
1395                         }
1396                     }
1397 
1398                     menu_item_ptr = menu_item_ptr->next_item;
1399                 }
1400 
1401                 menu_item_ptr = menu->entries;
1402                 break;
1403             default:
1404                 if (how == PICK_NONE) {
1405                     num_selected = 0;
1406                     dismiss = TRUE;
1407                     break;
1408                 }
1409             }
1410         }
1411 
1412         menu_item_ptr = menu->entries;
1413         while (menu_item_ptr != NULL) {
1414             if (menu_item_ptr->identifier.a_void != NULL) {
1415                 if ((curletter == menu_item_ptr->accelerator
1416                      && (curpage == menu_item_ptr->page_num
1417                          || !menu->reuse_accels))
1418                     || (menu_item_ptr->group_accel
1419                         && curletter == menu_item_ptr->group_accel)) {
1420                     if (curpage != menu_item_ptr->page_num) {
1421                         curpage = menu_item_ptr->page_num;
1422                         menu_display_page(menu, win, curpage, selectors);
1423                     }
1424 
1425                     if (how == PICK_ONE) {
1426                         menu_clear_selections(menu);
1427                         menu_select_deselect(win, menu_item_ptr,
1428                                              SELECT, curpage);
1429                         if (count)
1430                             menu_item_ptr->count = count;
1431                         num_selected = 1;
1432                         dismiss = TRUE;
1433                         break;
1434                     } else if (how == PICK_ANY && curletter == count_letter) {
1435                         menu_select_deselect(win, menu_item_ptr,
1436                                              SELECT, curpage);
1437                         menu_item_ptr->count = count;
1438                         count = 0;
1439                         count_letter = '\0';
1440                     } else {
1441                         menu_select_deselect(win, menu_item_ptr,
1442                                              INVERT, curpage);
1443                     }
1444                 }
1445             }
1446             menu_item_ptr = menu_item_ptr->next_item;
1447         }
1448     }
1449 
1450     if (how == PICK_ANY && num_selected != -1) {
1451         num_selected = 0;
1452         menu_item_ptr = menu->entries;
1453 
1454         while (menu_item_ptr != NULL) {
1455             if (menu_item_ptr->identifier.a_void != NULL) {
1456                 if (menu_item_ptr->selected) {
1457                     num_selected++;
1458                 }
1459             }
1460             menu_item_ptr = menu_item_ptr->next_item;
1461         }
1462     }
1463 
1464     return num_selected;
1465 }
1466 
1467 
1468 /* Select, deselect, or toggle selected for the given menu entry.
1469    For search operations, the toggled entry might be on a different
1470    page than the one currently shown. */
1471 
1472 static void
menu_select_deselect(WINDOW * win,nhmenu_item * item,menu_op operation,int current_page)1473 menu_select_deselect(WINDOW *win, nhmenu_item *item,
1474                      menu_op operation, int current_page)
1475 {
1476     int curletter = item->accelerator;
1477     boolean visible = (item->page_num == current_page);
1478 
1479     if (operation == DESELECT || (item->selected && operation == INVERT)) {
1480         item->selected = FALSE;
1481         if (visible) {
1482             mvwaddch(win, item->line_num + 1, 1, ' ');
1483             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
1484             mvwaddch(win, item->line_num + 1, 2, curletter);
1485             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
1486             mvwaddch(win, item->line_num + 1, 3, ')');
1487         }
1488     } else {
1489         item->selected = TRUE;
1490         if (visible) {
1491             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
1492             mvwaddch(win, item->line_num + 1, 1, '<');
1493             mvwaddch(win, item->line_num + 1, 2, curletter);
1494             mvwaddch(win, item->line_num + 1, 3, '>');
1495             curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
1496         }
1497     }
1498     if (visible)
1499         wrefresh(win);
1500 }
1501 
1502 
1503 /* Perform the selected operation (select, unselect, invert selection)
1504 on the given menu page.  If menu_page is 0, then perform opetation on
1505 all pages in menu.  Returns last page displayed.  */
1506 
1507 static int
menu_operation(WINDOW * win,nhmenu * menu,menu_op operation,int page_num)1508 menu_operation(WINDOW * win, nhmenu *menu, menu_op
1509                operation, int page_num)
1510 {
1511     int first_page, last_page, current_page;
1512     nhmenu_item *menu_item_ptr = menu->entries;
1513 
1514     if (page_num == 0) {        /* Operation to occur on all pages */
1515         first_page = 1;
1516         last_page = menu->num_pages;
1517     } else {
1518         first_page = page_num;
1519         last_page = page_num;
1520     }
1521 
1522     /* Cycle through entries until we are on the correct page */
1523     while (menu_item_ptr != NULL) {
1524         if (menu_item_ptr->page_num == first_page) {
1525             break;
1526         }
1527         menu_item_ptr = menu_item_ptr->next_item;
1528     }
1529 
1530     current_page = first_page;
1531 
1532     if (page_num == 0) {
1533         menu_display_page(menu, win, current_page, (char *) 0);
1534     }
1535 
1536     if (menu_item_ptr == NULL) {        /* Page not found */
1537         impossible("menu_display_page: attempt to display nonexistent page");
1538         return 0;
1539     }
1540 
1541     while (menu_item_ptr != NULL) {
1542         if (menu_item_ptr->page_num != current_page) {
1543             if (menu_item_ptr->page_num > last_page) {
1544                 break;
1545             }
1546 
1547             current_page = menu_item_ptr->page_num;
1548             menu_display_page(menu, win, current_page, (char *) 0);
1549         }
1550 
1551         if (menu_item_ptr->identifier.a_void != NULL) {
1552             menu_select_deselect(win, menu_item_ptr, operation, current_page);
1553         }
1554 
1555         menu_item_ptr = menu_item_ptr->next_item;
1556     }
1557 
1558     return current_page;
1559 }
1560 
1561 
1562 /* Set all menu items to unselected in menu */
1563 
1564 static void
menu_clear_selections(nhmenu * menu)1565 menu_clear_selections(nhmenu *menu)
1566 {
1567     nhmenu_item *menu_item_ptr = menu->entries;
1568 
1569     while (menu_item_ptr != NULL) {
1570         menu_item_ptr->selected = FALSE;
1571         menu_item_ptr = menu_item_ptr->next_item;
1572     }
1573 }
1574 
1575 
1576 /* Get the maximum height for a menu */
1577 
1578 static int
menu_max_height(void)1579 menu_max_height(void)
1580 {
1581     return term_rows - 2;
1582 }
1583