I've noticed many instances of the game pausing and not being sure why, then pressing <space> and having it resume. The curses interface had a tendency to put its equivalent of the --More-- prompt, >>, somewhere where that wasn't visible, either off the right hand edge (possibly) or underneath the window borders if those were enabled. Especially the very last one it issues prior to exit. (An extra one compared to tty behavior.) This ended up being a pretty substantial overhaul of message window handling. I wouldn't be surprised if it has off-by-one errors which happen to be paired up and cancel each other out. ">>" is still drawn in orange if guicolor is on, now in inverse video when that is off. If it happens to be drawn at the same screen location in consecutive instances, the first ">" will toggle between blink and not blink so that there'll be no doubt as to whether the keypress registered when dismissing it (moot if the text preceding it is different but there's no attempt to be smart enough to check that, just screen placement).
1540 lines
47 KiB
C
1540 lines
47 KiB
C
/* 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" -*-*/
|
|
/* NetHack 3.6 cursdial.c */
|
|
/* Copyright (c) Karl Garrison, 2010. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "curses.h"
|
|
#include "hack.h"
|
|
#include "wincurs.h"
|
|
#include "cursdial.h"
|
|
#include "func_tab.h"
|
|
#include <ctype.h>
|
|
|
|
#if defined(FILENAME_CMP) && !defined(strcasecmp)
|
|
#define strcasecmp FILENAME_CMP
|
|
#endif
|
|
#if defined(STRNCMPI) && !defined(strncasecmp)
|
|
#define strncasecmp strncmpi
|
|
#endif
|
|
|
|
/*
|
|
* Note:
|
|
*
|
|
* Prototypes need to use the widened/unwidened type macros (CHAR_P, &c)
|
|
* in order to match fields of the window_procs struct (see winprocs.h).
|
|
* But for a standard-conforming compiler, we'll end up with the widened
|
|
* types necessary to match the mixed prototype/old-style function
|
|
* definition environment as used by nethack's core. Prototype
|
|
int func(CHAR_P);
|
|
* becomes
|
|
int func(int);
|
|
* after expansion, which matches the definition
|
|
int func(arg) char arg; { ... }
|
|
* according to the rules of the C standard. But the use of new-style
|
|
* definitions
|
|
int func(char arg) { ... }
|
|
* by the curses interface turns that into a conflict. No widening takes
|
|
* place so it ought to be 'int func(char);' instead. Unfortunately that
|
|
* would be incompatible for functions assigned to window_procs.
|
|
*
|
|
* So, the code here (also cursmain.c and cursinvt.c) is mis-using the
|
|
* widening macros for variable types
|
|
int func(CHAR_P arg) { ... }
|
|
* (no doubt modelling it after the C++ code in win/Qt where the option
|
|
* to switch the applicable definitions to old-style isn't available).
|
|
* Other alternatives aren't significantly better so just live with it.
|
|
* [Redoing the windowing interface to avoid narrow arguments would be
|
|
* better since that would fix Qt's usage too.]
|
|
*/
|
|
|
|
/* Dialog windows for curses interface */
|
|
|
|
|
|
/* Private declarations */
|
|
|
|
typedef struct nhmi {
|
|
winid wid; /* NetHack window id */
|
|
int glyph; /* Menu glyphs */
|
|
anything identifier; /* Value returned if item selected */
|
|
CHAR_P accelerator; /* Character used to select item from menu */
|
|
CHAR_P group_accel; /* Group accelerator for menu item, if any */
|
|
int attr; /* Text attributes for item */
|
|
const char *str; /* Text of menu item */
|
|
BOOLEAN_P presel; /* Whether menu item should be preselected */
|
|
boolean selected; /* Whether item is currently selected */
|
|
int page_num; /* Display page number for entry */
|
|
int line_num; /* Line number on page where entry begins */
|
|
int num_lines; /* Number of lines entry uses on page */
|
|
int count; /* Count for selected item */
|
|
struct nhmi *prev_item; /* Pointer to previous entry */
|
|
struct nhmi *next_item; /* Pointer to next entry */
|
|
} nhmenu_item;
|
|
|
|
typedef struct nhm {
|
|
winid wid; /* NetHack window id */
|
|
const char *prompt; /* Menu prompt text */
|
|
nhmenu_item *entries; /* Menu entries */
|
|
int num_entries; /* Number of menu entries */
|
|
int num_pages; /* Number of display pages for entry */
|
|
int height; /* Window height of menu */
|
|
int width; /* Window width of menu */
|
|
boolean reuse_accels; /* Non-unique accelerators per page */
|
|
boolean bottom_heavy; /* display multi-page menu starting at end */
|
|
struct nhm *prev_menu; /* Pointer to previous entry */
|
|
struct nhm *next_menu; /* Pointer to next entry */
|
|
} nhmenu;
|
|
|
|
typedef enum menu_op_type {
|
|
SELECT,
|
|
DESELECT,
|
|
INVERT
|
|
} menu_op;
|
|
|
|
static nhmenu_item *curs_new_menu_item(winid, const char *);
|
|
static void curs_pad_menu(nhmenu *, boolean);
|
|
static nhmenu *get_menu(winid wid);
|
|
static char menu_get_accel(boolean first);
|
|
static void menu_determine_pages(nhmenu *menu);
|
|
static boolean menu_is_multipage(nhmenu *menu, int width, int height);
|
|
static void menu_win_size(nhmenu *menu);
|
|
static void menu_display_page(nhmenu *menu, WINDOW * win, int page_num);
|
|
static int menu_get_selections(WINDOW * win, nhmenu *menu, int how);
|
|
static void menu_select_deselect(WINDOW * win, nhmenu_item *item,
|
|
menu_op operation);
|
|
static int menu_operation(WINDOW * win, nhmenu *menu, menu_op operation,
|
|
int page_num);
|
|
static void menu_clear_selections(nhmenu *menu);
|
|
static int menu_max_height(void);
|
|
|
|
static nhmenu *nhmenus = NULL; /* NetHack menu array */
|
|
|
|
|
|
/* Get a line of text from the player, such as asking for a character name
|
|
or a wish */
|
|
|
|
void
|
|
curses_line_input_dialog(const char *prompt, char *answer, int buffer)
|
|
{
|
|
int map_height, map_width, maxwidth, remaining_buf, winx, winy, count;
|
|
WINDOW *askwin, *bwin;
|
|
char *tmpstr;
|
|
int prompt_width = strlen(prompt) + buffer + 1;
|
|
int prompt_height = 1;
|
|
int height = prompt_height;
|
|
char input[BUFSZ];
|
|
|
|
/* if messages were being suppressed for the remainder of the turn,
|
|
re-activate them now that input is being requested */
|
|
curses_got_input();
|
|
|
|
if (buffer >= (int) sizeof input)
|
|
buffer = (int) sizeof input - 1;
|
|
maxwidth = term_cols - 2;
|
|
|
|
if (iflags.window_inited) {
|
|
if (!iflags.wc_popup_dialog) {
|
|
curses_message_win_getline(prompt, answer, buffer);
|
|
return;
|
|
}
|
|
curses_get_window_size(MAP_WIN, &map_height, &map_width);
|
|
if ((prompt_width + 2) > map_width)
|
|
maxwidth = map_width - 2;
|
|
}
|
|
|
|
if (prompt_width > maxwidth) {
|
|
prompt_height = curses_num_lines(prompt, maxwidth);
|
|
height = prompt_height;
|
|
prompt_width = maxwidth;
|
|
tmpstr = curses_break_str(prompt, maxwidth, prompt_height);
|
|
remaining_buf = buffer - (strlen(tmpstr) - 1);
|
|
if (remaining_buf > 0) {
|
|
height += (remaining_buf / prompt_width);
|
|
if ((remaining_buf % prompt_width) > 0) {
|
|
height++;
|
|
}
|
|
}
|
|
free(tmpstr);
|
|
}
|
|
|
|
if (iflags.window_inited) {
|
|
bwin = curses_create_window(prompt_width, height, UP);
|
|
wrefresh(bwin);
|
|
getbegyx(bwin, winy, winx);
|
|
askwin = newwin(height, prompt_width, winy + 1, winx + 1);
|
|
} else {
|
|
bwin = curses_create_window(prompt_width, height, CENTER);
|
|
wrefresh(bwin);
|
|
getbegyx(bwin, winy, winx);
|
|
askwin = newwin(height, prompt_width, winy + 1, winx + 1);
|
|
}
|
|
for (count = 0; count < prompt_height; count++) {
|
|
tmpstr = curses_break_str(prompt, maxwidth, count + 1);
|
|
if (count == (prompt_height - 1)) { /* Last line */
|
|
mvwprintw(askwin, count, 0, "%s ", tmpstr);
|
|
} else {
|
|
mvwaddstr(askwin, count, 0, tmpstr);
|
|
}
|
|
free(tmpstr);
|
|
}
|
|
|
|
echo();
|
|
curs_set(1);
|
|
wgetnstr(askwin, input, buffer - 1);
|
|
curs_set(0);
|
|
strcpy(answer, input);
|
|
werase(bwin);
|
|
delwin(bwin);
|
|
curses_destroy_win(askwin);
|
|
noecho();
|
|
}
|
|
|
|
|
|
/* Get a single character response from the player, such as a y/n prompt */
|
|
|
|
int
|
|
curses_character_input_dialog(const char *prompt, const char *choices,
|
|
CHAR_P def)
|
|
{
|
|
WINDOW *askwin = NULL;
|
|
#ifdef PDCURSES
|
|
WINDOW *message_window;
|
|
#endif
|
|
int answer, count, maxwidth, map_height, map_width;
|
|
char *linestr;
|
|
char askstr[BUFSZ + QBUFSZ];
|
|
char choicestr[QBUFSZ];
|
|
int prompt_width = strlen(prompt);
|
|
int prompt_height = 1;
|
|
boolean any_choice = FALSE;
|
|
boolean accept_count = FALSE;
|
|
|
|
/* if messages were being suppressed for the remainder of the turn,
|
|
re-activate them now that input is being requested */
|
|
curses_got_input();
|
|
|
|
if (invent || (moves > 1)) {
|
|
curses_get_window_size(MAP_WIN, &map_height, &map_width);
|
|
} else {
|
|
map_height = term_rows;
|
|
map_width = term_cols;
|
|
}
|
|
|
|
#ifdef PDCURSES
|
|
message_window = curses_get_nhwin(MESSAGE_WIN);
|
|
#endif
|
|
maxwidth = map_width - 2;
|
|
|
|
if (choices != NULL) {
|
|
for (count = 0; choices[count] != '\0'; count++) {
|
|
if (choices[count] == '#') { /* Accept a count */
|
|
accept_count = TRUE;
|
|
}
|
|
}
|
|
choicestr[0] = ' ';
|
|
choicestr[1] = '[';
|
|
for (count = 0; choices[count] != '\0'; count++) {
|
|
if (choices[count] == '\033') { /* Escape */
|
|
break;
|
|
}
|
|
choicestr[count + 2] = choices[count];
|
|
}
|
|
choicestr[count + 2] = ']';
|
|
if (((def >= 'A') && (def <= 'Z')) || ((def >= 'a') && (def <= 'z'))) {
|
|
choicestr[count + 3] = ' ';
|
|
choicestr[count + 4] = '(';
|
|
choicestr[count + 5] = def;
|
|
choicestr[count + 6] = ')';
|
|
choicestr[count + 7] = '\0';
|
|
} else { /* No usable default choice */
|
|
|
|
choicestr[count + 3] = '\0';
|
|
def = '\0'; /* Mark as no default */
|
|
}
|
|
strcpy(askstr, prompt);
|
|
strcat(askstr, choicestr);
|
|
} else {
|
|
strcpy(askstr, prompt);
|
|
any_choice = TRUE;
|
|
}
|
|
|
|
prompt_width = strlen(askstr);
|
|
|
|
if ((prompt_width + 2) > maxwidth) {
|
|
prompt_height = curses_num_lines(askstr, maxwidth);
|
|
prompt_width = map_width - 2;
|
|
}
|
|
|
|
if (iflags.wc_popup_dialog /*|| curses_stupid_hack*/) {
|
|
askwin = curses_create_window(prompt_width, prompt_height, UP);
|
|
for (count = 0; count < prompt_height; count++) {
|
|
linestr = curses_break_str(askstr, maxwidth, count + 1);
|
|
mvwaddstr(askwin, count + 1, 1, linestr);
|
|
free(linestr);
|
|
}
|
|
|
|
wrefresh(askwin);
|
|
} else {
|
|
/* TODO: add SUPPRESS_HISTORY flag, then after getting a response,
|
|
append it and use put_msghistory() on combined prompt+answer */
|
|
custompline(OVERRIDE_MSGTYPE, "%s", askstr);
|
|
curs_set(1);
|
|
}
|
|
|
|
/*curses_stupid_hack = 0; */
|
|
|
|
while (1) {
|
|
#ifdef PDCURSES
|
|
answer = wgetch(message_window);
|
|
#else
|
|
answer = getch();
|
|
#endif
|
|
if (answer == ERR) {
|
|
answer = def;
|
|
break;
|
|
}
|
|
|
|
answer = curses_convert_keys(answer);
|
|
|
|
if (answer == KEY_ESC) {
|
|
if (choices == NULL) {
|
|
break;
|
|
}
|
|
answer = def;
|
|
for (count = 0; choices[count] != '\0'; count++) {
|
|
if (choices[count] == 'q') { /* q is preferred over n */
|
|
answer = 'q';
|
|
} else if ((choices[count] == 'n') && answer != 'q') {
|
|
answer = 'n';
|
|
}
|
|
}
|
|
break;
|
|
} else if ((answer == '\n') || (answer == '\r') || (answer == ' ')) {
|
|
if ((choices != NULL) && (def != '\0')) {
|
|
answer = def;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (digit(answer)) {
|
|
if (accept_count) {
|
|
if (answer != '0') {
|
|
yn_number = curses_get_count(answer - '0');
|
|
touchwin(askwin);
|
|
refresh();
|
|
}
|
|
|
|
answer = '#';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (any_choice) {
|
|
break;
|
|
}
|
|
|
|
if (choices != NULL) {
|
|
for (count = 0; (size_t) count < strlen(choices); count++) {
|
|
if (choices[count] == answer) {
|
|
break;
|
|
}
|
|
}
|
|
if (choices[count] == answer) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iflags.wc_popup_dialog) {
|
|
/* Kludge to make prompt visible after window is dismissed
|
|
when inputting a number */
|
|
if (digit(answer)) {
|
|
custompline(OVERRIDE_MSGTYPE, "%s", askstr);
|
|
curs_set(1);
|
|
}
|
|
|
|
curses_destroy_win(askwin);
|
|
} else {
|
|
curses_clear_unhighlight_message_window();
|
|
curs_set(0);
|
|
}
|
|
|
|
return answer;
|
|
}
|
|
|
|
|
|
/* Return an extended command from the user */
|
|
|
|
int
|
|
curses_ext_cmd()
|
|
{
|
|
int count, letter, prompt_width, startx, starty, winx, winy;
|
|
int messageh, messagew, maxlen = BUFSZ - 1;
|
|
int ret = -1;
|
|
char cur_choice[BUFSZ];
|
|
int matches = 0;
|
|
WINDOW *extwin = NULL, *extwin2 = NULL;
|
|
|
|
if (iflags.extmenu) {
|
|
return extcmd_via_menu();
|
|
}
|
|
|
|
startx = 0;
|
|
starty = 0;
|
|
if (iflags.wc_popup_dialog) { /* Prompt in popup window */
|
|
int x0, y0, w, h; /* bounding coords of popup */
|
|
|
|
extwin2 = curses_create_window(25, 1, UP);
|
|
wrefresh(extwin2);
|
|
/* create window inside window to prevent overwriting of border */
|
|
getbegyx(extwin2, y0, x0);
|
|
getmaxyx(extwin2, h, w);
|
|
extwin = newwin(1, w - 2, y0 + 1, x0 + 1);
|
|
if (w - 4 < maxlen) maxlen = w - 4;
|
|
} else {
|
|
curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
|
|
curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);
|
|
|
|
if (curses_window_has_border(MESSAGE_WIN)) {
|
|
winx++;
|
|
winy++;
|
|
}
|
|
|
|
winy += messageh - 1;
|
|
extwin = newwin(1, messagew - 2, winy, winx);
|
|
if (messagew - 4 < maxlen)
|
|
maxlen = messagew - 4;
|
|
custompline(OVERRIDE_MSGTYPE, "#");
|
|
}
|
|
|
|
cur_choice[0] = '\0';
|
|
|
|
while (1) {
|
|
wmove(extwin, starty, startx);
|
|
waddstr(extwin, "# ");
|
|
wmove(extwin, starty, startx + 2);
|
|
waddstr(extwin, cur_choice);
|
|
wmove(extwin, starty, strlen(cur_choice) + startx + 2);
|
|
wprintw(extwin, " ");
|
|
|
|
/* if we have an autocomplete command, AND it matches uniquely */
|
|
if (matches == 1) {
|
|
curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, ON);
|
|
wmove(extwin, starty, strlen(cur_choice) + startx + 2);
|
|
wprintw(extwin, "%s", extcmdlist[ret].ef_txt + strlen(cur_choice));
|
|
curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, OFF);
|
|
mvwprintw(extwin, starty,
|
|
strlen(extcmdlist[ret].ef_txt) + 2, " ");
|
|
}
|
|
|
|
wrefresh(extwin);
|
|
letter = getch();
|
|
prompt_width = strlen(cur_choice);
|
|
matches = 0;
|
|
|
|
if (letter == '\033' || letter == ERR) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (letter == '\r' || letter == '\n') {
|
|
if (ret == -1) {
|
|
for (count = 0; extcmdlist[count].ef_txt; count++) {
|
|
if (!strcasecmp(cur_choice, extcmdlist[count].ef_txt)) {
|
|
ret = count;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (letter == '\177') /* DEL/Rubout */
|
|
letter = '\b';
|
|
if (letter == '\b' || letter == KEY_BACKSPACE) {
|
|
if (prompt_width == 0) {
|
|
ret = -1;
|
|
break;
|
|
} else {
|
|
cur_choice[prompt_width - 1] = '\0';
|
|
letter = '*';
|
|
prompt_width--;
|
|
}
|
|
}
|
|
if (letter != '*' && prompt_width < maxlen) {
|
|
cur_choice[prompt_width] = letter;
|
|
cur_choice[prompt_width + 1] = '\0';
|
|
ret = -1;
|
|
}
|
|
for (count = 0; extcmdlist[count].ef_txt; count++) {
|
|
if (!wizard && (extcmdlist[count].flags & WIZMODECMD))
|
|
continue;
|
|
if (!(extcmdlist[count].flags & AUTOCOMPLETE))
|
|
continue;
|
|
if (strlen(extcmdlist[count].ef_txt) > (size_t) prompt_width) {
|
|
if (strncasecmp(cur_choice, extcmdlist[count].ef_txt,
|
|
prompt_width) == 0) {
|
|
if ((extcmdlist[count].ef_txt[prompt_width] ==
|
|
lowc(letter)) || letter == '*') {
|
|
if (matches == 0) {
|
|
ret = count;
|
|
}
|
|
|
|
matches++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
curses_destroy_win(extwin);
|
|
if (extwin2) curses_destroy_win(extwin2);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Initialize a menu from given NetHack winid */
|
|
|
|
void
|
|
curses_create_nhmenu(winid wid)
|
|
{
|
|
nhmenu *new_menu = NULL;
|
|
nhmenu *menuptr = nhmenus;
|
|
nhmenu_item *menu_item_ptr = NULL;
|
|
nhmenu_item *tmp_menu_item = NULL;
|
|
|
|
new_menu = get_menu(wid);
|
|
|
|
if (new_menu != NULL) {
|
|
/* Reuse existing menu, clearing out current entries */
|
|
menu_item_ptr = new_menu->entries;
|
|
|
|
if (menu_item_ptr != NULL) {
|
|
while (menu_item_ptr->next_item != NULL) {
|
|
tmp_menu_item = menu_item_ptr->next_item;
|
|
free((genericptr_t) menu_item_ptr->str);
|
|
free((genericptr_t) menu_item_ptr);
|
|
menu_item_ptr = tmp_menu_item;
|
|
}
|
|
free((genericptr_t) menu_item_ptr->str);
|
|
free((genericptr_t) menu_item_ptr); /* Last entry */
|
|
new_menu->entries = NULL;
|
|
}
|
|
if (new_menu->prompt != NULL) { /* Reusing existing menu */
|
|
free((genericptr_t) new_menu->prompt);
|
|
new_menu->prompt = NULL;
|
|
}
|
|
new_menu->num_pages = 0;
|
|
new_menu->height = 0;
|
|
new_menu->width = 0;
|
|
new_menu->reuse_accels = FALSE;
|
|
new_menu->bottom_heavy = FALSE;
|
|
return;
|
|
}
|
|
|
|
new_menu = (nhmenu *) alloc((signed) sizeof (nhmenu));
|
|
new_menu->wid = wid;
|
|
new_menu->prompt = NULL;
|
|
new_menu->entries = NULL;
|
|
new_menu->num_pages = 0;
|
|
new_menu->height = 0;
|
|
new_menu->width = 0;
|
|
new_menu->reuse_accels = FALSE;
|
|
new_menu->bottom_heavy = FALSE;
|
|
new_menu->next_menu = NULL;
|
|
|
|
if (nhmenus == NULL) { /* no menus in memory yet */
|
|
new_menu->prev_menu = NULL;
|
|
nhmenus = new_menu;
|
|
} else {
|
|
while (menuptr->next_menu != NULL) {
|
|
menuptr = menuptr->next_menu;
|
|
}
|
|
new_menu->prev_menu = menuptr;
|
|
menuptr->next_menu = new_menu;
|
|
}
|
|
}
|
|
|
|
static nhmenu_item *
|
|
curs_new_menu_item(winid wid, const char *str)
|
|
{
|
|
char *new_str;
|
|
nhmenu_item *new_item;
|
|
|
|
new_str = curses_copy_of(str);
|
|
curses_rtrim(new_str);
|
|
new_item = (nhmenu_item *) alloc((unsigned) sizeof (nhmenu_item));
|
|
new_item->wid = wid;
|
|
new_item->glyph = NO_GLYPH;
|
|
new_item->identifier = zeroany;
|
|
new_item->accelerator = '\0';;
|
|
new_item->group_accel = '\0';
|
|
new_item->attr = 0;
|
|
new_item->str = new_str;
|
|
new_item->presel = FALSE;
|
|
new_item->selected = FALSE;
|
|
new_item->page_num = 0;
|
|
new_item->line_num = 0;
|
|
new_item->num_lines = 0;
|
|
new_item->count = -1;
|
|
new_item->next_item = NULL;
|
|
return new_item;
|
|
}
|
|
/* Add a menu item to the given menu window */
|
|
|
|
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)
|
|
{
|
|
nhmenu_item *new_item, *current_items, *menu_item_ptr;
|
|
nhmenu *current_menu = get_menu(wid);
|
|
|
|
if (current_menu == NULL) {
|
|
impossible(
|
|
"curses_add_nhmenu_item: attempt to add item to nonexistent menu");
|
|
return;
|
|
}
|
|
|
|
if (str == NULL) {
|
|
return;
|
|
}
|
|
|
|
new_item = curs_new_menu_item(wid, str);
|
|
new_item->glyph = glyph;
|
|
new_item->identifier = *identifier;
|
|
new_item->accelerator = accelerator;
|
|
new_item->group_accel = group_accel;
|
|
new_item->attr = attr;
|
|
new_item->presel = presel;
|
|
|
|
current_items = current_menu->entries;
|
|
menu_item_ptr = current_items;
|
|
|
|
if (current_items == NULL) {
|
|
new_item->prev_item = NULL;
|
|
current_menu->entries = new_item;
|
|
} else {
|
|
while (menu_item_ptr->next_item != NULL) {
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
new_item->prev_item = menu_item_ptr;
|
|
menu_item_ptr->next_item = new_item;
|
|
}
|
|
}
|
|
|
|
/* for menu->bottom_heavy -- insert enough blank lines at top of
|
|
first page to make the last page become a full one */
|
|
static void
|
|
curs_pad_menu(nhmenu *current_menu, boolean do_pad UNUSED)
|
|
{
|
|
nhmenu_item *menu_item_ptr;
|
|
int numpages = current_menu->num_pages;
|
|
|
|
/* caller has already called menu_win_size() */
|
|
menu_determine_pages(current_menu); /* sets 'menu->num_pages' */
|
|
numpages = current_menu->num_pages;
|
|
/* pad beginning of menu so that partial last page becomes full;
|
|
might be slightly less than full if any entries take multiple
|
|
lines and the padding would force those to span page boundary
|
|
and that gets prevented; so we re-count the number of pages
|
|
with every insertion instead of trying to calculate the number
|
|
of them to add */
|
|
do {
|
|
menu_item_ptr = curs_new_menu_item(current_menu->wid, "");
|
|
menu_item_ptr->next_item = current_menu->entries;
|
|
current_menu->entries->prev_item = menu_item_ptr;
|
|
current_menu->entries = menu_item_ptr;
|
|
current_menu->num_entries += 1;
|
|
|
|
menu_determine_pages(current_menu);
|
|
} while (current_menu->num_pages == numpages);
|
|
|
|
/* we inserted blank lines at beginning until final entry spilled
|
|
over to another page; take the most recent blank one back out */
|
|
current_menu->num_entries -= 1;
|
|
current_menu->entries = menu_item_ptr->next_item;
|
|
current_menu->entries->prev_item = (nhmenu_item *) 0;
|
|
free((genericptr_t) menu_item_ptr->str);
|
|
free((genericptr_t) menu_item_ptr);
|
|
|
|
/* reset page count; shouldn't need to re-count */
|
|
current_menu->num_pages = numpages;
|
|
return;
|
|
}
|
|
|
|
/* mark ^P message recall menu, for msg_window:full (FIFO), where we'll
|
|
start viewing on the last page so be able to see most recent immediately */
|
|
void
|
|
curs_menu_set_bottom_heavy(winid wid)
|
|
{
|
|
nhmenu *menu = get_menu(wid);
|
|
|
|
/*
|
|
* Called after end_menu + finalize_nhmenu,
|
|
* before select_menu + display_nhmenu.
|
|
*/
|
|
menu_win_size(menu); /* (normally not done until display_nhmenu) */
|
|
if (menu_is_multipage(menu, menu->width, menu->height)) {
|
|
menu->bottom_heavy = TRUE;
|
|
|
|
/* insert enough blank lines at top of first page to make the
|
|
last page become a full one */
|
|
curs_pad_menu(menu, TRUE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* No more entries are to be added to menu, so details of the menu can be
|
|
finalized in memory */
|
|
|
|
void
|
|
curses_finalize_nhmenu(winid wid, const char *prompt)
|
|
{
|
|
int count = 0;
|
|
nhmenu_item *menu_item_ptr;
|
|
nhmenu *current_menu = get_menu(wid);
|
|
|
|
if (current_menu == NULL) {
|
|
impossible(
|
|
"curses_finalize_nhmenu: attempt to finalize nonexistent menu");
|
|
return;
|
|
}
|
|
for (menu_item_ptr = current_menu->entries;
|
|
menu_item_ptr != NULL; menu_item_ptr = menu_item_ptr->next_item)
|
|
count++;
|
|
|
|
current_menu->num_entries = count;
|
|
current_menu->prompt = curses_copy_of(prompt);
|
|
}
|
|
|
|
|
|
/* Display a nethack menu, and return a selection, if applicable */
|
|
|
|
int
|
|
curses_display_nhmenu(winid wid, int how, MENU_ITEM_P ** _selected)
|
|
{
|
|
nhmenu *current_menu = get_menu(wid);
|
|
nhmenu_item *menu_item_ptr;
|
|
int num_chosen, count;
|
|
WINDOW *win;
|
|
MENU_ITEM_P *selected = NULL;
|
|
|
|
*_selected = NULL;
|
|
|
|
if (current_menu == NULL) {
|
|
impossible(
|
|
"curses_display_nhmenu: attempt to display nonexistent menu");
|
|
return '\033';
|
|
}
|
|
|
|
menu_item_ptr = current_menu->entries;
|
|
|
|
if (menu_item_ptr == NULL) {
|
|
impossible("curses_display_nhmenu: attempt to display empty menu");
|
|
return '\033';
|
|
}
|
|
|
|
/* Reset items to unselected to clear out selections from previous
|
|
invocations of this menu, and preselect appropriate items */
|
|
while (menu_item_ptr != NULL) {
|
|
menu_item_ptr->selected = menu_item_ptr->presel;
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
menu_win_size(current_menu);
|
|
menu_determine_pages(current_menu);
|
|
|
|
/* Display pre and post-game menus centered */
|
|
if (((moves <= 1) && !invent) || program_state.gameover) {
|
|
win = curses_create_window(current_menu->width,
|
|
current_menu->height, CENTER);
|
|
} else { /* Display during-game menus on the right out of the way */
|
|
|
|
win = curses_create_window(current_menu->width,
|
|
current_menu->height, RIGHT);
|
|
}
|
|
|
|
num_chosen = menu_get_selections(win, current_menu, how);
|
|
curses_destroy_win(win);
|
|
|
|
if (num_chosen > 0) {
|
|
selected = (MENU_ITEM_P *) alloc((unsigned)
|
|
(num_chosen * sizeof (MENU_ITEM_P)));
|
|
count = 0;
|
|
|
|
menu_item_ptr = current_menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->selected) {
|
|
if (count == num_chosen) {
|
|
impossible("curses_display_nhmenu: Selected items "
|
|
"exceeds expected number");
|
|
break;
|
|
}
|
|
selected[count].item = menu_item_ptr->identifier;
|
|
selected[count].count = menu_item_ptr->count;
|
|
count++;
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
if (count != num_chosen) {
|
|
impossible(
|
|
"curses_display_nhmenu: Selected items less than expected number");
|
|
}
|
|
}
|
|
|
|
*_selected = selected;
|
|
|
|
return num_chosen;
|
|
}
|
|
|
|
|
|
boolean
|
|
curses_menu_exists(winid wid)
|
|
{
|
|
if (get_menu(wid) != NULL) {
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Delete the menu associated with the given NetHack winid from memory */
|
|
|
|
void
|
|
curses_del_menu(winid wid, boolean del_wid_too)
|
|
{
|
|
nhmenu_item *tmp_menu_item;
|
|
nhmenu_item *menu_item_ptr;
|
|
nhmenu *tmpmenu;
|
|
nhmenu *current_menu = get_menu(wid);
|
|
|
|
if (current_menu == NULL) {
|
|
return;
|
|
}
|
|
|
|
menu_item_ptr = current_menu->entries;
|
|
|
|
/* First free entries associated with this menu from memory */
|
|
if (menu_item_ptr != NULL) {
|
|
while (menu_item_ptr->next_item != NULL) {
|
|
tmp_menu_item = menu_item_ptr->next_item;
|
|
free((genericptr_t) menu_item_ptr->str);
|
|
free(menu_item_ptr);
|
|
menu_item_ptr = tmp_menu_item;
|
|
}
|
|
free((genericptr_t) menu_item_ptr->str);
|
|
free(menu_item_ptr); /* Last entry */
|
|
current_menu->entries = NULL;
|
|
}
|
|
|
|
/* Now unlink the menu from the list and free it as well */
|
|
if ((tmpmenu = current_menu->prev_menu) != NULL) {
|
|
tmpmenu->next_menu = current_menu->next_menu;
|
|
} else {
|
|
nhmenus = current_menu->next_menu; /* New head node or NULL */
|
|
}
|
|
if ((tmpmenu = current_menu->next_menu) != NULL) {
|
|
tmpmenu->prev_menu = current_menu->prev_menu;
|
|
}
|
|
|
|
if (current_menu->prompt)
|
|
free((genericptr_t) current_menu->prompt);
|
|
free((genericptr_t) current_menu);
|
|
|
|
if (del_wid_too)
|
|
curses_del_wid(wid);
|
|
}
|
|
|
|
|
|
/* return a pointer to the menu associated with the given NetHack winid */
|
|
|
|
static nhmenu *
|
|
get_menu(winid wid)
|
|
{
|
|
nhmenu *menuptr = nhmenus;
|
|
|
|
while (menuptr != NULL) {
|
|
if (menuptr->wid == wid) {
|
|
return menuptr;
|
|
}
|
|
menuptr = menuptr->next_menu;
|
|
}
|
|
|
|
return NULL; /* Not found */
|
|
}
|
|
|
|
|
|
static char
|
|
menu_get_accel(boolean first)
|
|
{
|
|
char ret;
|
|
static char next_letter = 'a';
|
|
|
|
if (first) {
|
|
next_letter = 'a';
|
|
}
|
|
|
|
ret = next_letter;
|
|
|
|
if ((next_letter < 'z' && next_letter >= 'a')
|
|
|| (next_letter < 'Z' && next_letter >= 'A')
|
|
|| (next_letter < '9' && next_letter >= '0')) {
|
|
next_letter++;
|
|
} else if (next_letter == 'z') {
|
|
next_letter = 'A';
|
|
} else if (next_letter == 'Z') {
|
|
next_letter = '0';
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Determine if menu will require multiple pages to display */
|
|
|
|
static boolean
|
|
menu_is_multipage(nhmenu *menu, int width, int height)
|
|
{
|
|
int num_lines;
|
|
int curline = 0;
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
|
|
if (*menu->prompt) {
|
|
curline += curses_num_lines(menu->prompt, width) + 1;
|
|
}
|
|
|
|
if (menu->num_entries <= (height - curline)) {
|
|
while (menu_item_ptr != NULL) {
|
|
menu_item_ptr->line_num = curline;
|
|
if (menu_item_ptr->identifier.a_void == NULL) {
|
|
num_lines = curses_num_lines(menu_item_ptr->str, width);
|
|
} else {
|
|
/* Add space for accelerator */
|
|
num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
|
|
}
|
|
menu_item_ptr->num_lines = num_lines;
|
|
curline += num_lines;
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
if (curline > height
|
|
|| (curline > height - 2 && height == menu_max_height())) {
|
|
break;
|
|
}
|
|
}
|
|
if (menu_item_ptr == NULL) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Determine which entries go on which page, and total number of pages */
|
|
|
|
static void
|
|
menu_determine_pages(nhmenu *menu)
|
|
{
|
|
int tmpline, num_lines;
|
|
int curline = 0;
|
|
int page_num = 1;
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
int width = menu->width;
|
|
int height = menu->height;
|
|
int page_end = height;
|
|
|
|
|
|
if (*menu->prompt) {
|
|
curline += curses_num_lines(menu->prompt, width) + 1;
|
|
}
|
|
|
|
tmpline = curline;
|
|
|
|
if (menu_is_multipage(menu, width, height)) {
|
|
page_end -= 2; /* Room to display current page number */
|
|
}
|
|
|
|
/* Determine what entries belong on which page */
|
|
menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
menu_item_ptr->page_num = page_num;
|
|
menu_item_ptr->line_num = curline;
|
|
if (menu_item_ptr->identifier.a_void == NULL) {
|
|
num_lines = curses_num_lines(menu_item_ptr->str, width);
|
|
} else {
|
|
/* Add space for accelerator */
|
|
num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
|
|
}
|
|
menu_item_ptr->num_lines = num_lines;
|
|
curline += num_lines;
|
|
if (curline > page_end) {
|
|
page_num++;
|
|
curline = tmpline;
|
|
/* Move ptr back so entry will be reprocessed on new page */
|
|
menu_item_ptr = menu_item_ptr->prev_item;
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
menu->num_pages = page_num;
|
|
}
|
|
|
|
|
|
/* Determine dimensions of menu window based on term size and entries */
|
|
|
|
static void
|
|
menu_win_size(nhmenu *menu)
|
|
{
|
|
int width, height, maxwidth, maxheight, curentrywidth, lastline;
|
|
int maxentrywidth = strlen(menu->prompt);
|
|
int maxheaderwidth = 0;
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
|
|
maxwidth = 38; /* Reasonable minimum usable width */
|
|
|
|
if ((term_cols / 2) > maxwidth) {
|
|
maxwidth = (term_cols / 2); /* Half the screen */
|
|
}
|
|
|
|
maxheight = menu_max_height();
|
|
|
|
/* First, determine the width of the longest menu entry */
|
|
while (menu_item_ptr != NULL)
|
|
{
|
|
if (menu_item_ptr->identifier.a_void == NULL) {
|
|
curentrywidth = strlen(menu_item_ptr->str);
|
|
|
|
if (curentrywidth > maxheaderwidth) {
|
|
maxheaderwidth = curentrywidth;
|
|
}
|
|
} else {
|
|
/* Add space for accelerator */
|
|
curentrywidth = strlen(menu_item_ptr->str) + 4;
|
|
#if 0 /* FIXME: menu glyphs */
|
|
if (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs)
|
|
curentrywidth += 2;
|
|
#endif
|
|
}
|
|
if (curentrywidth > maxentrywidth) {
|
|
maxentrywidth = curentrywidth;
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
/* If widest entry is smaller than maxwidth, reduce maxwidth accordingly */
|
|
if (maxentrywidth < maxwidth) {
|
|
maxwidth = maxentrywidth;
|
|
}
|
|
|
|
/* Try not to wrap headers/normal text lines if possible. We can
|
|
go wider than half the screen for this purpose if need be */
|
|
|
|
if ((maxheaderwidth > maxwidth) && (maxheaderwidth < (term_cols - 2))) {
|
|
maxwidth = maxheaderwidth;
|
|
}
|
|
|
|
width = maxwidth;
|
|
|
|
/* Possibly reduce height if only 1 page */
|
|
if (!menu_is_multipage(menu, maxwidth, maxheight)) {
|
|
menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr->next_item != NULL) {
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
lastline = (menu_item_ptr->line_num) + menu_item_ptr->num_lines;
|
|
|
|
if (lastline < maxheight) {
|
|
maxheight = lastline;
|
|
}
|
|
} else { /* If multipage, make sure we have enough width for page footer */
|
|
|
|
if (width < 20) {
|
|
width = 20;
|
|
}
|
|
}
|
|
|
|
height = maxheight;
|
|
menu->width = width;
|
|
menu->height = height;
|
|
}
|
|
|
|
|
|
/* Displays menu selections in the given window */
|
|
|
|
static void
|
|
menu_display_page(nhmenu *menu, WINDOW * win, int page_num)
|
|
{
|
|
nhmenu_item *menu_item_ptr;
|
|
int count, curletter, entry_cols, start_col, num_lines;
|
|
char *tmpstr;
|
|
boolean first_accel = TRUE;
|
|
int color = NO_COLOR;
|
|
int attr = A_NORMAL;
|
|
boolean menu_color = FALSE;
|
|
|
|
/* Cycle through entries until we are on the correct page */
|
|
|
|
menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->page_num == page_num) {
|
|
break;
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
if (menu_item_ptr == NULL) { /* Page not found */
|
|
impossible("menu_display_page: attempt to display nonexistent page");
|
|
return;
|
|
}
|
|
|
|
werase(win);
|
|
|
|
if (menu->prompt && *menu->prompt) {
|
|
num_lines = curses_num_lines(menu->prompt, menu->width);
|
|
|
|
for (count = 0; count < num_lines; count++) {
|
|
tmpstr = curses_break_str(menu->prompt, menu->width, count + 1);
|
|
mvwprintw(win, count + 1, 1, "%s", tmpstr);
|
|
free(tmpstr);
|
|
}
|
|
}
|
|
|
|
/* Display items for current page */
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->page_num != page_num) {
|
|
break;
|
|
}
|
|
if (menu_item_ptr->identifier.a_void != NULL) {
|
|
if (menu_item_ptr->accelerator != 0) {
|
|
curletter = menu_item_ptr->accelerator;
|
|
} else {
|
|
if (first_accel) {
|
|
curletter = menu_get_accel(TRUE);
|
|
first_accel = FALSE;
|
|
if (!menu->reuse_accels && (menu->num_pages > 1)) {
|
|
menu->reuse_accels = TRUE;
|
|
}
|
|
} else {
|
|
curletter = menu_get_accel(FALSE);
|
|
}
|
|
menu_item_ptr->accelerator = curletter;
|
|
}
|
|
|
|
if (menu_item_ptr->selected) {
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, 1, '<');
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, 3, '>');
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
|
|
} else {
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
|
|
mvwprintw(win, menu_item_ptr->line_num + 1, 3, ") ");
|
|
}
|
|
}
|
|
entry_cols = menu->width;
|
|
start_col = 1;
|
|
|
|
if (menu_item_ptr->identifier.a_void != NULL) {
|
|
entry_cols -= 4;
|
|
start_col += 4;
|
|
}
|
|
#if 0
|
|
/* FIXME: menuglyphs not implemented yet */
|
|
if (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs) {
|
|
unsigned special; /*notused */
|
|
|
|
mapglyph(menu_item_ptr->glyph, &curletter, &color, &special, 0, 0);
|
|
curses_toggle_color_attr(win, color, NONE, ON);
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, start_col, curletter);
|
|
curses_toggle_color_attr(win, color, NONE, OFF);
|
|
mvwaddch(win, menu_item_ptr->line_num + 1, start_col + 1, ' ');
|
|
entry_cols -= 2;
|
|
start_col += 2;
|
|
}
|
|
#endif
|
|
if (iflags.use_menu_color
|
|
&& (menu_color = get_menu_coloring(menu_item_ptr->str,
|
|
&color, &attr)) != 0) {
|
|
if (color != NO_COLOR) {
|
|
curses_toggle_color_attr(win, color, NONE, ON);
|
|
}
|
|
attr = curses_convert_attr(attr);
|
|
if (attr != A_NORMAL) {
|
|
menu_item_ptr->attr = menu_item_ptr->attr | attr;
|
|
}
|
|
}
|
|
curses_toggle_color_attr(win, NONE, menu_item_ptr->attr, ON);
|
|
|
|
num_lines = curses_num_lines(menu_item_ptr->str, entry_cols);
|
|
|
|
for (count = 0; count < num_lines; count++) {
|
|
if (menu_item_ptr->str && *menu_item_ptr->str) {
|
|
tmpstr = curses_break_str(menu_item_ptr->str,
|
|
entry_cols, count + 1);
|
|
mvwprintw(win, menu_item_ptr->line_num + count + 1, start_col,
|
|
"%s", tmpstr);
|
|
free(tmpstr);
|
|
}
|
|
}
|
|
if (menu_color && (color != NO_COLOR)) {
|
|
curses_toggle_color_attr(win, color, NONE, OFF);
|
|
}
|
|
curses_toggle_color_attr(win, NONE, menu_item_ptr->attr, OFF);
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
if (menu->num_pages > 1) {
|
|
int footer_x, footwidth, shoesize = menu->num_pages;
|
|
|
|
footwidth = (int) (sizeof "<- (Page X of Y) ->" - sizeof "");
|
|
while (shoesize >= 10) { /* possible for pickup from big piles... */
|
|
/* room for wider feet; extra digit for both X and Y */
|
|
footwidth += 2;
|
|
shoesize /= 10;
|
|
}
|
|
footer_x = !menu->bottom_heavy ? (menu->width - footwidth) : 2;
|
|
if (page_num != 1) {
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
|
|
mvwaddstr(win, menu->height, footer_x, "<=");
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
|
|
}
|
|
mvwprintw(win, menu->height, footer_x + 2, " (Page %d of %d) ",
|
|
page_num, menu->num_pages);
|
|
if (page_num != menu->num_pages) {
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
|
|
mvwaddstr(win, menu->height, footer_x + footwidth - 2, "=>");
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
|
|
}
|
|
}
|
|
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
|
|
box(win, 0, 0);
|
|
curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
|
|
wrefresh(win);
|
|
}
|
|
|
|
|
|
static int
|
|
menu_get_selections(WINDOW * win, nhmenu *menu, int how)
|
|
{
|
|
int curletter;
|
|
int count = -1;
|
|
int count_letter = '\0';
|
|
int curpage = !menu->bottom_heavy ? 1 : menu->num_pages;
|
|
int num_selected = 0;
|
|
boolean dismiss = FALSE;
|
|
char search_key[BUFSZ];
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
|
|
menu_display_page(menu, win, curpage);
|
|
|
|
while (!dismiss) {
|
|
curletter = getch();
|
|
|
|
if (curletter == ERR) {
|
|
num_selected = -1;
|
|
dismiss = TRUE;
|
|
}
|
|
|
|
if (curletter == '\033') {
|
|
curletter = curses_convert_keys(curletter);
|
|
}
|
|
|
|
switch (how) {
|
|
case PICK_NONE:
|
|
if (menu->num_pages == 1) {
|
|
if (curletter == KEY_ESC) {
|
|
num_selected = -1;
|
|
} else {
|
|
num_selected = 0;
|
|
|
|
}
|
|
dismiss = TRUE;
|
|
break;
|
|
}
|
|
break;
|
|
case PICK_ANY:
|
|
switch (curletter) {
|
|
case MENU_SELECT_PAGE:
|
|
(void) menu_operation(win, menu, SELECT, curpage);
|
|
break;
|
|
case MENU_SELECT_ALL:
|
|
curpage = menu_operation(win, menu, SELECT, 0);
|
|
break;
|
|
case MENU_UNSELECT_PAGE:
|
|
(void) menu_operation(win, menu, DESELECT, curpage);
|
|
break;
|
|
case MENU_UNSELECT_ALL:
|
|
curpage = menu_operation(win, menu, DESELECT, 0);
|
|
break;
|
|
case MENU_INVERT_PAGE:
|
|
(void) menu_operation(win, menu, INVERT, curpage);
|
|
break;
|
|
case MENU_INVERT_ALL:
|
|
curpage = menu_operation(win, menu, INVERT, 0);
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
default:
|
|
if (isdigit(curletter)) {
|
|
count = curses_get_count(curletter - '0');
|
|
touchwin(win);
|
|
refresh();
|
|
curletter = getch();
|
|
if (count > 0) {
|
|
count_letter = curletter;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (curletter) {
|
|
case KEY_ESC:
|
|
num_selected = -1;
|
|
dismiss = TRUE;
|
|
break;
|
|
case '\n':
|
|
case '\r':
|
|
dismiss = TRUE;
|
|
break;
|
|
case KEY_RIGHT:
|
|
case KEY_NPAGE:
|
|
case MENU_NEXT_PAGE:
|
|
case ' ':
|
|
if (curpage < menu->num_pages) {
|
|
curpage++;
|
|
menu_display_page(menu, win, curpage);
|
|
} else if (curletter == ' ') {
|
|
dismiss = TRUE;
|
|
break;
|
|
}
|
|
break;
|
|
case KEY_LEFT:
|
|
case KEY_PPAGE:
|
|
case MENU_PREVIOUS_PAGE:
|
|
if (curpage > 1) {
|
|
curpage--;
|
|
menu_display_page(menu, win, curpage);
|
|
}
|
|
break;
|
|
case KEY_END:
|
|
case MENU_LAST_PAGE:
|
|
if (curpage != menu->num_pages) {
|
|
curpage = menu->num_pages;
|
|
menu_display_page(menu, win, curpage);
|
|
}
|
|
break;
|
|
case KEY_HOME:
|
|
case MENU_FIRST_PAGE:
|
|
if (curpage != 1) {
|
|
curpage = 1;
|
|
menu_display_page(menu, win, curpage);
|
|
}
|
|
break;
|
|
case MENU_SEARCH:
|
|
curses_line_input_dialog("Search for:", search_key, BUFSZ);
|
|
|
|
refresh();
|
|
touchwin(win);
|
|
wrefresh(win);
|
|
|
|
if (strlen(search_key) == 0) {
|
|
break;
|
|
}
|
|
|
|
menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->identifier.a_void != NULL
|
|
&& strstri(menu_item_ptr->str, search_key)) {
|
|
if (how == PICK_ONE) {
|
|
menu_clear_selections(menu);
|
|
menu_select_deselect(win, menu_item_ptr, SELECT);
|
|
num_selected = 1;
|
|
dismiss = TRUE;
|
|
break;
|
|
} else {
|
|
menu_select_deselect(win, menu_item_ptr, INVERT);
|
|
}
|
|
}
|
|
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
menu_item_ptr = menu->entries;
|
|
break;
|
|
default:
|
|
if (how == PICK_NONE) {
|
|
num_selected = 0;
|
|
dismiss = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
menu_item_ptr = menu->entries;
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->identifier.a_void != NULL) {
|
|
if ((curletter == menu_item_ptr->accelerator
|
|
&& (curpage == menu_item_ptr->page_num
|
|
|| !menu->reuse_accels))
|
|
|| (menu_item_ptr->group_accel
|
|
&& curletter == menu_item_ptr->group_accel)) {
|
|
if (curpage != menu_item_ptr->page_num) {
|
|
curpage = menu_item_ptr->page_num;
|
|
menu_display_page(menu, win, curpage);
|
|
}
|
|
|
|
if (how == PICK_ONE) {
|
|
menu_clear_selections(menu);
|
|
menu_select_deselect(win, menu_item_ptr, SELECT);
|
|
if (count)
|
|
menu_item_ptr->count = count;
|
|
num_selected = 1;
|
|
dismiss = TRUE;
|
|
break;
|
|
} else if (how == PICK_ANY && curletter == count_letter) {
|
|
menu_select_deselect(win, menu_item_ptr, SELECT);
|
|
menu_item_ptr->count = count;
|
|
count = 0;
|
|
count_letter = '\0';
|
|
} else {
|
|
menu_select_deselect(win, menu_item_ptr, INVERT);
|
|
}
|
|
}
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
}
|
|
|
|
if (how == PICK_ANY && num_selected != -1) {
|
|
num_selected = 0;
|
|
menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->identifier.a_void != NULL) {
|
|
if (menu_item_ptr->selected) {
|
|
num_selected++;
|
|
}
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
}
|
|
|
|
return num_selected;
|
|
}
|
|
|
|
|
|
/* Select, deselect, or toggle selected for the given menu entry */
|
|
|
|
static void
|
|
menu_select_deselect(WINDOW * win, nhmenu_item *item, menu_op operation)
|
|
{
|
|
int curletter = item->accelerator;
|
|
|
|
if ((operation == DESELECT) || (item->selected && (operation == INVERT))) {
|
|
item->selected = FALSE;
|
|
mvwaddch(win, item->line_num + 1, 1, ' ');
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
|
|
mvwaddch(win, item->line_num + 1, 2, curletter);
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
|
|
mvwaddch(win, item->line_num + 1, 3, ')');
|
|
} else {
|
|
item->selected = TRUE;
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
|
|
mvwaddch(win, item->line_num + 1, 1, '<');
|
|
mvwaddch(win, item->line_num + 1, 2, curletter);
|
|
mvwaddch(win, item->line_num + 1, 3, '>');
|
|
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
|
|
}
|
|
|
|
wrefresh(win);
|
|
}
|
|
|
|
|
|
/* Perform the selected operation (select, unselect, invert selection)
|
|
on the given menu page. If menu_page is 0, then perform opetation on
|
|
all pages in menu. Returns last page displayed. */
|
|
|
|
static int
|
|
menu_operation(WINDOW * win, nhmenu *menu, menu_op
|
|
operation, int page_num)
|
|
{
|
|
int first_page, last_page, current_page;
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
|
|
if (page_num == 0) { /* Operation to occur on all pages */
|
|
first_page = 1;
|
|
last_page = menu->num_pages;
|
|
} else {
|
|
first_page = page_num;
|
|
last_page = page_num;
|
|
}
|
|
|
|
/* Cycle through entries until we are on the correct page */
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->page_num == first_page) {
|
|
break;
|
|
}
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
current_page = first_page;
|
|
|
|
if (page_num == 0) {
|
|
menu_display_page(menu, win, current_page);
|
|
}
|
|
|
|
if (menu_item_ptr == NULL) { /* Page not found */
|
|
impossible("menu_display_page: attempt to display nonexistent page");
|
|
return 0;
|
|
}
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
if (menu_item_ptr->page_num != current_page) {
|
|
if (menu_item_ptr->page_num > last_page) {
|
|
break;
|
|
}
|
|
|
|
current_page = menu_item_ptr->page_num;
|
|
menu_display_page(menu, win, current_page);
|
|
}
|
|
|
|
if (menu_item_ptr->identifier.a_void != NULL) {
|
|
menu_select_deselect(win, menu_item_ptr, operation);
|
|
}
|
|
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
|
|
return current_page;
|
|
}
|
|
|
|
|
|
/* Set all menu items to unselected in menu */
|
|
|
|
static void
|
|
menu_clear_selections(nhmenu *menu)
|
|
{
|
|
nhmenu_item *menu_item_ptr = menu->entries;
|
|
|
|
while (menu_item_ptr != NULL) {
|
|
menu_item_ptr->selected = FALSE;
|
|
menu_item_ptr = menu_item_ptr->next_item;
|
|
}
|
|
}
|
|
|
|
|
|
/* Get the maximum height for a menu */
|
|
|
|
static int
|
|
menu_max_height(void)
|
|
{
|
|
return term_rows - 2;
|
|
}
|