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