/* 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

/* defined in sys/<foo>/<foo>tty.c or cursmain.c as last resort;
   set up by curses_init_nhwindows() */
extern char erase_char, kill_char;

/*
 * 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 */
    unsigned itemflags;
    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,
                              char *);
static int menu_get_selections(WINDOW * win, nhmenu *menu, int how);
static void menu_select_deselect(WINDOW * win, nhmenu_item *item,
                                 menu_op operation, int);
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 */

/* maximum number of selector entries per page; if '$' is present
   it isn't counted toward maximum, so actual max per page is 53 */
#define MAX_ACCEL_PER_PAGE 52 /* 'a'..'z' + 'A'..'Z' */
/* TODO?  limit per page should be ignored for perm_invent, which might
   have some '#' overflow entries and isn't used to select items */


/* Get a line of text from the player, such as asking for a character name
   or a wish.  Note: EDIT_GETLIN not supported for popup prompting. */

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, prompt_height = 1, 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: space between prompt and answer; buffer already accounts for \0 */
    prompt_width = (int) strlen(prompt) + 1 + buffer;
    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 - ((int) strlen(tmpstr) - 1);
        if (remaining_buf > 0) {
            height += (remaining_buf / prompt_width);
            if ((remaining_buf % prompt_width) > 0) {
                height++;
            }
        }
        free(tmpstr);
    }

    bwin = curses_create_window(prompt_width, height,
                                iflags.window_inited ? UP : 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);
        mvwaddstr(askwin, count, 0, tmpstr);
        if (count == prompt_height - 1) { /* Last line */
            if ((int) strlen(tmpstr) < maxwidth)
                waddch(askwin, ' ');
            else
                wmove(askwin, count + 1, 0);
        }
        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;
    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 (g.invent || (g.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;
    }

    /* +1: room for a trailing space where the cursor will rest */
    prompt_width = (int) strlen(askstr) + 1;

    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);
    }

    /*curses_stupid_hack = 0; */

    curs_set(1);
    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 && answer != '\0' && index(choices, answer))
            break;
    }
    curs_set(0);

    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();
    }

    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;
        nhUse(h); /* needed only to give getmaxyx three arguments */
    } 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, (int) strlen(cur_choice) + startx + 2);
        wclrtoeol(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, (int) strlen(cur_choice) + startx + 2);
            wprintw(extwin, "%s",
                    extcmdlist[ret].ef_txt + (int) strlen(cur_choice));
            curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, OFF);
        }

        curs_set(1);
        wrefresh(extwin);
        letter = getch();
        curs_set(0);
        prompt_width = (int) 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
            || (erase_char && letter == (int) (uchar) erase_char)) {
            if (prompt_width == 0) {
                ret = -1;
                break;
            } else {
                cur_choice[prompt_width - 1] = '\0';
                letter = '*';
                prompt_width--;
            }

        /* honor kill_char if it's ^U or similar, but not if it's '@' */
        } else if (kill_char && letter == (int) (uchar) kill_char
                   && (letter < ' ' || letter >= '\177')) { /*ASCII*/
            cur_choice[0] = '\0';
            letter = '*';
            prompt_width = 0;
        }

        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 ((int) strlen(extcmdlist[count].ef_txt) > 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 = cg.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->itemflags = MENU_ITEMFLAGS_NONE;
    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, unsigned itemflags)
{
    nhmenu_item *new_item, *current_items, *menu_item_ptr;
    nhmenu *current_menu = get_menu(wid);
    boolean presel = (itemflags & MENU_ITEMFLAGS_SELECTED) != 0;

    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;
    new_item->itemflags = itemflags;
    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 ((g.moves <= 1 && !g.invent) || g.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)
{
    static char next_letter;
    char ret;

    if (first) {
        next_letter = 'a';
    }

    ret = next_letter;

    if ((next_letter < 'z' && next_letter >= 'a')
        || (next_letter < 'Z' && next_letter >= 'A')) {
        next_letter++;
    } else if (next_letter == 'z') {
        next_letter = 'A';
    } else if (next_letter == 'Z') {
        next_letter = 'a'; /* wrap to beginning */
    }

    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, accel_per_page = 0;
    nhmenu_item *menu_item_ptr = menu->entries;

    if (*menu->prompt) {
        curline += curses_num_lines(menu->prompt, width) + 1;
    }

    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 {
            if (menu_item_ptr->accelerator != GOLD_SYM) {
                if (++accel_per_page > MAX_ACCEL_PER_PAGE)
                    break;
            }
            /* 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;
        }
    }
    return (menu_item_ptr != NULL) ? TRUE : FALSE;
}


/* Determine which entries go on which page, and total number of pages */

static void
menu_determine_pages(nhmenu *menu)
{
    int tmpline, num_lines, accel_per_page;
    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 */
    }
    accel_per_page = 0;

    /* Determine what entries belong on which page */
    for (menu_item_ptr = menu->entries; menu_item_ptr != NULL;
         menu_item_ptr = menu_item_ptr->next_item) {
        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 {
            if (menu_item_ptr->accelerator != GOLD_SYM)
                ++accel_per_page;
            /* 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 || accel_per_page > MAX_ACCEL_PER_PAGE) {
            ++page_num;
            accel_per_page = 0; /* reset */
            curline = tmpline;
            /* Move ptr back so entry will be reprocessed on new page */
            menu_item_ptr = menu_item_ptr->prev_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 maxwidth, maxheight, curentrywidth, lastline;
    int maxentrywidth = 0;
    int maxheaderwidth = menu->prompt ? (int) strlen(menu->prompt) : 0;
    nhmenu_item *menu_item_ptr;

    if (g.program_state.gameover) {
        /* for final inventory disclosure, use full width */
        maxwidth = term_cols - 2; /* +2: borders assumed */
    } else {
        /* this used to be 38, which is 80/2 - 2 (half a 'normal' sized
           screen minus room for a border box), but some data files
           have been manually formatted for 80 columns (usually limited
           to 78 but sometimes 79, rarely 80 itself) and using a value
           less that 40 meant that a full line would wrap twice:
           1..38, 39..76, and 77..80 */
        maxwidth = 40; /* 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 */
    for (menu_item_ptr = menu->entries; menu_item_ptr != NULL;
         menu_item_ptr = menu_item_ptr->next_item) {
        curentrywidth = (int) strlen(menu_item_ptr->str);
        if (menu_item_ptr->identifier.a_void == NULL) {
            if (curentrywidth > maxheaderwidth) {
                maxheaderwidth = curentrywidth;
            }
        } else {
            /* Add space for accelerator (selector letter) */
            curentrywidth += 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;
        }
    }

    /*
     * 3.6.3: This used to set maxwidth to maxheaderwidth when that was
     * bigger but only set it to maxentrywidth if the latter was smaller,
     * so entries wider than the default would always wrap unless at
     * least one header or separator line was long, even when there was
     * lots of space available to display them without wrapping.  The
     * reason to force narrow menus isn't known.  It may have been to
     * reduce the amount of map rewriting when menu is dismissed, but if
     * so, that was an issue due to excessive screen writing for the map
     * (output was flushed for each character) which was fixed long ago.
     */
    maxwidth = max(maxentrywidth, maxheaderwidth);
    if (maxwidth > term_cols - 2) { /* -2: space for left and right borders */
        maxwidth = term_cols - 2;
    }

    /* 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;
        }
    }

    /* avoid a tiny popup window; when it's shown over the endings of
       old messsages rather than over the map, it is fairly easy for
       the player to overlook it, particularly when walking around and
       stepping on a pile of 2 items; also, multi-page menus need enough
       room for "(Page M of N) => " even if all entries are narrower
       than that; we specify same minimum width even when single page */
    menu->width = max(maxwidth, 25);
    menu->height = max(maxheight, 5);
}


/* Displays menu selections in the given window */

static void
menu_display_page(nhmenu *menu, WINDOW * win, int page_num, char *selectors)
{
    nhmenu_item *menu_item_ptr;
    int count, curletter, entry_cols, start_col, num_lines;
    char *tmpstr;
    boolean first_accel = TRUE;
    int color = NO_COLOR, attr = A_NORMAL;
    boolean menu_color = FALSE;

    /* letters assigned to entries on current page */
    if (selectors)
        (void) memset((genericptr_t) selectors, 0, 256);

    /* 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;
            }
            /* we have a selector letter; tell caller about it */
            if (selectors)
                selectors[(unsigned) (curletter & 0xFF)]++;

            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, 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
        color = NO_COLOR;
        menu_color = iflags.use_menu_color
                     && get_menu_coloring(menu_item_ptr->str, &color, &attr);
        if (menu_color) {
            attr = curses_convert_attr(attr);
            if (color != NO_COLOR || attr != A_NORMAL)
                curses_menu_color_attr(win, color, attr, ON);
        } else {
            attr = menu_item_ptr->attr;
            if (color != NO_COLOR || attr != A_NORMAL)
                curses_toggle_color_attr(win, color, 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 (color != NO_COLOR || attr != A_NORMAL) {
            if (menu_color)
                curses_menu_color_attr(win, color, attr, OFF);
            else
                curses_toggle_color_attr(win, color, 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], selectors[256];
    nhmenu_item *menu_item_ptr = menu->entries;

    menu_display_page(menu, win, curpage, selectors);

    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:
            if (curletter <= 0 || curletter >= 256 || !selectors[curletter]) {
                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;
                }
            }
        }

        if (curletter <= 0 || curletter >= 256 || !selectors[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, selectors);
                } 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, selectors);
                }
                break;
            case KEY_END:
            case MENU_LAST_PAGE:
                if (curpage != menu->num_pages) {
                    curpage = menu->num_pages;
                    menu_display_page(menu, win, curpage, selectors);
                }
                break;
            case KEY_HOME:
            case MENU_FIRST_PAGE:
                if (curpage != 1) {
                    curpage = 1;
                    menu_display_page(menu, win, curpage, selectors);
                }
                break;
            case MENU_SEARCH:
                search_key[0] = '\0';
                curses_line_input_dialog("Search for:", search_key, BUFSZ);

                refresh();
                touchwin(win);
                wrefresh(win);

                if (!*search_key)
                    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, curpage);
                            num_selected = 1;
                            dismiss = TRUE;
                            break;
                        } else {
                            menu_select_deselect(win, menu_item_ptr,
                                                 INVERT, curpage);
                        }
                    }

                    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, selectors);
                    }

                    if (how == PICK_ONE) {
                        menu_clear_selections(menu);
                        menu_select_deselect(win, menu_item_ptr,
                                             SELECT, curpage);
                        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, curpage);
                        menu_item_ptr->count = count;
                        count = 0;
                        count_letter = '\0';
                    } else {
                        menu_select_deselect(win, menu_item_ptr,
                                             INVERT, curpage);
                    }
                }
            }
            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.
   For search operations, the toggled entry might be on a different
   page than the one currently shown. */

static void
menu_select_deselect(WINDOW *win, nhmenu_item *item,
                     menu_op operation, int current_page)
{
    int curletter = item->accelerator;
    boolean visible = (item->page_num == current_page);

    if (operation == DESELECT || (item->selected && operation == INVERT)) {
        item->selected = FALSE;
        if (visible) {
            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;
        if (visible) {
            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);
        }
    }
    if (visible)
        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, (char *) 0);
    }

    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, (char *) 0);
        }

        if (menu_item_ptr->identifier.a_void != NULL) {
            if (operation != INVERT
                || menuitem_invert_test(0, menu_item_ptr->itemflags,
                                        menu_item_ptr->selected))
                menu_select_deselect(win, menu_item_ptr, operation, current_page);
        }

        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;
}
