1
0
Fork 0
mirror of https://codeberg.org/noisytoot/notnotdnethack.git synced 2024-09-19 22:14:48 +01:00
notnotdnethack/win/curses/cursdial.c
Ron Nazarov 45a8d949e2
Replace compile-time regex options with runtime options
MENU_COLOR is now non-optional (at compile-time, you can still disable
menucolors at runtime).

The POSIX regular expression is consistently used
everywhere (previously, menucolors could use either the POSIX or the
GNU interface depending on a compile-time option, defaulting to GNU,
user sounds always used GNU, and everything else used POSIX).

MENU_COLOR_REGEX, MENU_COLOR_REGEX_POSIX, and USER_SOUNDS_REGEX have
been replaced by the new menucolor_regex and usersound_regex options.
menucolor_regex defaults to TRUE to maintain backwards compatibility
with old configs, but usersound_regex defaults to FALSE for
consistency with apexception_regex and and msgtype_regex.
2024-05-10 00:37:48 +01:00

1414 lines
43 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" -*-*/
#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "cursdial.h"
#include "curswins.h"
#include "func_tab.h"
#include <ctype.h>
#include <strings.h>
/* 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 accelerator; /* Character used to select item from menu */
char group_accel; /* Group accelerator for menu item, if any */
int attr; /* Text attributes for item */
const char *str; /* Text of menu item */
boolean 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 */
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;
extern struct menucoloring *menu_colorings;
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 input[buffer];
char *tmpstr;
int prompt_width = strlen(prompt) + buffer + 1;
int prompt_height = 1;
int height = prompt_height;
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 def)
{
WINDOW *askwin = NULL;
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 (invent || (moves > 1)) {
curses_get_window_size(MAP_WIN, &map_height, &map_width);
} else {
map_height = term_rows;
map_width = term_cols;
}
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 {
linestr = curses_copy_of(askstr);
pline("%s", linestr);
free(linestr);
curs_set(1);
}
curses_stupid_hack = 0;
while (1) {
answer = getch();
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)) {
linestr = curses_copy_of(askstr);
pline("%s", linestr);
free(linestr);
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(void)
{
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;
pline("#");
}
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;
}
/* \177 is delete */
if ((letter == '\b') || (letter == KEY_BACKSPACE) || (letter == '\177')) {
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 (!extcmdlist[count].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((char *) menu_item_ptr->str);
free(menu_item_ptr);
menu_item_ptr = tmp_menu_item;
}
free((char *) menu_item_ptr->str);
free(menu_item_ptr); /* Last entry */
new_menu->entries = NULL;
}
if (new_menu->prompt != NULL) { /* Reusing existing menu */
free((char *) 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;
return;
}
new_menu = malloc(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->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;
}
}
/* Add a menu item to the given menu window */
void
curses_add_nhmenu_item(winid wid, int glyph, const anything * identifier,
char accelerator, char group_accel, int attr,
const char *str, boolean presel)
{
char *new_str;
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_str = curses_copy_of(str);
curses_rtrim((char *) new_str);
new_item = malloc(sizeof (nhmenu_item));
new_item->wid = wid;
new_item->glyph = glyph;
new_item->identifier = *identifier;
new_item->accelerator = accelerator;
new_item->group_accel = group_accel;
new_item->attr = attr;
new_item->str = new_str;
new_item->presel = presel;
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;
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;
}
}
/* 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 *current_menu = get_menu(wid);
if (current_menu == NULL) {
impossible("curses_finalize_nhmenu: attempt to finalize nonexistent menu");
return;
}
nhmenu_item *menu_item_ptr = current_menu->entries;
while (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 ** _selected)
{
nhmenu *current_menu = get_menu(wid);
nhmenu_item *menu_item_ptr;
int num_chosen, count;
WINDOW *win;
menu_item *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 *) malloc(num_chosen * sizeof (menu_item));
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)
{
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((void *)menu_item_ptr->str);
free(menu_item_ptr);
menu_item_ptr = tmp_menu_item;
}
free((void *)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 (current_menu->prev_menu != NULL) {
tmpmenu = current_menu->prev_menu;
tmpmenu->next_menu = current_menu->next_menu;
} else {
nhmenus = current_menu->next_menu; /* New head mode or NULL */
}
if (current_menu->next_menu != NULL) {
tmpmenu = current_menu->next_menu;
tmpmenu->prev_menu = current_menu->prev_menu;
}
if (current_menu->prompt) {
free((char *) current_menu->prompt);
}
free(current_menu);
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 (strlen(menu->prompt) > 0) {
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 (strlen(menu->prompt) > 0) {
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 (menu_item_ptr->glyph != NO_GLYPH
&& iflags.use_menu_glyphs)
curentrywidth += 2;
}
if (curentrywidth > maxentrywidth) {
maxentrywidth = curentrywidth;
}
menu_item_ptr = menu_item_ptr->next_item;
}
/* If the 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, footer_x;
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 (strlen(menu->prompt) > 0) {
num_lines = curses_num_lines(menu->prompt, menu->width);
for (count = 0; count < num_lines; count++) {
mvwprintw(win, count + 1, 1, "%s",
curses_break_str(menu->prompt, menu->width, count + 1));
}
}
/* 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 (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs) {
unsigned bgcolor; /*notused */
glyph_t curglyph = (glyph_t)curletter;//Note: a glyph is a long int
mapglyph(menu_item_ptr->glyph, &curglyph, &color, &bgcolor, u.ux, u.uy);
curletter = (int)curglyph;//This seems bad, but it makes explicit what this code was always doing...
nethack_char nch;
nch.ch = curletter;
nch.color = color;
nch.attr = NONE;
/* curses_write_char takes coordinates in x,y order, but mvwaddch takes them in y,x order */
curses_write_char(win, start_col, menu_item_ptr->line_num + 1, nch);
mvwaddch(win, menu_item_ptr->line_num + 1, start_col + 1, ' ');
entry_cols -= 2;
start_col += 2;
}
if (iflags.use_menu_color && (menu_color = get_menu_coloring
((char *) menu_item_ptr->str, &color,
&attr))) {
if (color != NO_COLOR) {
curses_toggle_color_attr(win, color, NONE, ON);
}
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 (strlen(menu_item_ptr->str) > 0) {
mvwprintw(win, menu_item_ptr->line_num + count + 1,
start_col, "%s", curses_break_str(menu_item_ptr->str,
entry_cols,
count + 1));
}
}
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) {
footer_x = menu->width - strlen("<- (Page X of Y) ->");
if (menu->num_pages > 9) { /* Unlikely */
footer_x -= 2;
}
mvwprintw(win, menu->height, footer_x + 3, "(Page %d of %d)",
page_num, menu->num_pages);
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);
}
if (page_num != menu->num_pages) {
curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
mvwaddstr(win, menu->height, menu->width - 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 = 1;
int num_selected = 0;
boolean dismiss = FALSE;
char search_key[BUFSZ];
nhmenu_item *menu_item_ptr = menu->entries;
menu_display_page(menu, win, 1);
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;
}
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);
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;
}
}
/* This is to get the color of a menu item if the menucolor patch is
applied */
boolean
get_menu_coloring(char *str, int *color, int *attr)
{
struct menucoloring *tmpmc;
if (iflags.use_menu_color)
for (tmpmc = menu_colorings; tmpmc; tmpmc = tmpmc->next)
if (tmpmc->is_regexp
? regexec(&tmpmc->match, str, 0, NULL, 0) == 0
: pmatch(tmpmc->pattern, str)) {
*color = tmpmc->color;
*attr = curses_convert_attr(tmpmc->attr);
return TRUE;
}
return FALSE;
}
/* Get the maximum height for a menu */
static int
menu_max_height(void)
{
return term_rows - 2;
}