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