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