diff --git a/dat/hh b/dat/hh index 75f87b33b..17d68bf96 100644 --- a/dat/hh +++ b/dat/hh @@ -32,6 +32,7 @@ S save save the game (to be continued later) and exit O options set options / what-is tell what a map symbol represents \ known display list of what's been discovered +| perminv interact with persistent inventory window instead of hero+map v version display version number V history display game history ^A again redo the previous command (^A denotes the keystroke CTRL-A) diff --git a/dat/opthelp b/dat/opthelp index 5b031eb03..c13294d3c 100644 --- a/dat/opthelp +++ b/dat/opthelp @@ -290,6 +290,8 @@ menu_* specify single character accelerators for menu commands. menu_last_page jump to the last page in a menu [|](tcwxq) menu_next_page advance to the next menu page [>](tcwxq) menu_previous_page back up to the previous menu page [<](tcwxq) + menu_shift_left pan view to left (perm_invent only) [{](cx) + menu_shift_right pan view to right (perm_invent only) [}](cx) menu_select_all select all items in a menu [.](tcwxq) menu_select_page select all items on this menu page [,](tcwq) menu_deselect_all deselect all items in a menu [-](tcwxq) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index d271c8fe0..7bafb7c0c 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -35,7 +35,7 @@ .ds vr "NetHack 3.7 .ds f0 "\*(vr .ds f1 -.ds f2 "February 11, 2021 +.ds f2 "March 12, 2021 . .\" A note on some special characters: .\" \(lq = left double quote @@ -1068,6 +1068,20 @@ May be preceded by \(oq\f(CRm\fP\(cq to select preferred display order. Show discovered types for one class of objects. .lp "" May be preceded by \(oq\f(CRm\fP\(cq to select preferred display order. +.lp | +If persistent inventory display is supported and enabled (with the +.op perm_invent +option), interact with it instead of with the map. +.lp "" +Allows scrolling with the +menu_first_page, menu_previous_page, +menu_next_page, and menu_last_page +keys (\(oq\f(CR\(ha\fP\(cq, \(oq\f(CR<\fP\(cq, +\(oq\f(CR>\fP\(cq, \(oq\f(CR|\fP\(cq by default). +Some interfaces also support menu_shift_left and menu_shift_right +keys (\(oq\f(CR{\fP\(cq and \(oq\f(CR}\fP\(cq by default). +Use the \fIReturn\fP (aka \fIEnter\fP) or \fIEscape\fP key to +resume play. .lp ! Escape to a shell. See \(lq#shell\(rq below for more details. @@ -3828,6 +3842,20 @@ Default \(oq.\(cq. Menu character accelerator to select all items on this page of a menu. Implemented by the Amiga, Gem and tty ports. Default \(oq,\(cq. +.lp menu_shift_left +Menu character accelerator to scroll a menu\(emone which has been +scrolled right\(emback to the left. +Implemented by curses for +.op perm_invent +only and by X11. +Default \(oq{\(cq. +.lp menu_shift_right +Menu character accelerator to scroll a menu which has text beyond the +right edge to the right. +Implemented by curses for +.op perm_invent +only and by X11. +Default \(oq}\(cq. ." .lp menu_tab_sep ." Format menu entries using TAB to separate columns (default off). ." Only applicable to some menus, and only useful to some interfaces. diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 41bd4229d..e2ed921ae 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -45,7 +45,7 @@ %.au \author{Original version - Eric S. Raymond\\ (Edited and expanded for 3.7 by Mike Stephenson and others)} -\date{February 11, 2021} +\date{March 12, 2021} \maketitle @@ -1174,6 +1174,23 @@ Show discovered types for one class of objects. \\ .lp "" May be preceded by `{\tt m}' to select preferred display order. + +%.lp +\item[\tb{|}] +If persistent inventory display is supported and enabled (with the +{\it perm_invent\/} +option), interact with it instead of with the map. +\\ +%.lp "" +Allows scrolling with the +menu\verb+_+first\verb+_+page, menu\verb+_+previous\verb+_+page, +menu\verb+_+next\verb+_+page, and menu\verb+_+last\verb+_+page +keys (`{\tt \^{}}', `{\tt <}', `{\tt >}', `{\tt |}' by default). +Some interfaces also support menu_shift_left and menu_shift_right +keys (`{\tt \verb+{+}' and `{\tt \verb+}+}' by default). +Use the {\it Return\/} (aka {\it Enter\/}) or {\it Escape\/} key to +resume play. + %.lp \item[\tb{!}] Escape to a shell. @@ -4137,6 +4154,21 @@ Default `.'. Menu character accelerator to select all items on this page of a menu. Implemented by the Amiga, Gem and tty ports. Default `,'. + +%.lp +\item[\ib{menu\verb+_+shift\verb+_+left}] +Menu character accelerator to scroll a menu---one which has been +scrolled right---back to the left. +Implemented by curses for {\it perm\verb+_+invent\/} only and by X11. +Default `{\tt \verb+{+}'. + +%.lp +\item[\ib{menu\verb+_+shift\verb+_+right}] +Menu character accelerator to scroll a menu which has text beyond the +right edge to the right. +Implemented by curses for {\it perm\verb+_+invent\/} only and by X11. +Default `{\tt \verb+}+}'. +Default \(oq}\(cq. % %.lp % \item[\ib{menu\verb+_+tab\verb+_+sep}] % Format menu entries using TAB to separate columns (default off). diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 2c1e523b1..aa1e7ba68 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -823,6 +823,11 @@ show bones levels information in enlightenment at end of game or in explore and wizmode for #wizintrinsic, use any counts entered during menu selection give feedback when boolean options are toggled interactively ('O' command) +'|' command (#perminv) for interacting with persistent inventory display + (curses and X11 only) +menu_shift_left, menu_shift_right menu command keys; default '{' and '}' + (curses for perm_invent only; implemented for X11 too but menus for + it lack horizontal scroll bars so the shifts don't work there) Platform- and/or Interface-Specific New Features diff --git a/doc/window.doc b/doc/window.doc index 1c26bf952..bac8008b5 100644 --- a/doc/window.doc +++ b/doc/window.doc @@ -259,11 +259,16 @@ display_file(str, boolean complain) -- Display the file named str. Complain about missing files iff complain is TRUE. update_inventory(arg) - -- Indicate to the window port that the inventory has been - changed. - -- Merely calls display_inventory() for window-ports that - leave the window up, otherwise empty. - -- 'arg' is not used yet + -- For an argument of 0: + -- Indicate to the window port that the inventory has + been changed. + -- Merely calls display_inventory() for window-ports + that leave the window up, otherwise empty. + -- or for a non-zero argument: + -- Prompts the user for a menu scrolling action and + executes that. + -- May repeat until user finishes (typically by using + or but interface may use other means). doprev_message() -- Display previous messages. Used by the ^P command. -- On the tty-port this scrolls WIN_MESSAGE back one line. @@ -810,6 +815,8 @@ to support: | guicolor | WC2_GUICOLOR | wc2_guicolor |boolean | | hilite_status | WC2_HILITE_STATUS | wc2_hilite_status |strings | | hitpointbar | WC2_HITPOINTBAR | wc2_hitpointbar |boolean | + | menu_shift_left | WC2_MENU_SHIFT | n/a |char | + | menu_shift_right | WC2_MENU_SHIFT | n/a |char | | petattr | WC2_PETATTR | wc2_petattr |int | | selectsaved | WC2_SELECTSAVED | wc2_selectsaved |boolean | | softkeyboard | WC2_SOFTKEYBOARD | wc2_softkeyboard |boolean | diff --git a/include/extern.h b/include/extern.h index 90ff05575..a13c05a63 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1019,6 +1019,7 @@ extern int count_unidentified(struct obj *); extern void identify_pack(int, boolean); extern void learn_unseen_invent(void); extern void update_inventory(void); +extern int doperminv(void); extern void prinv(const char *, struct obj *, long); extern char *xprname(struct obj *, const char *, char, boolean, long, long); extern int ddoinv(void); @@ -1800,6 +1801,7 @@ extern void oc_to_str(char *, char *); extern void add_menu_cmd_alias(char, char); extern char get_menu_cmd_key(char); extern char map_menu_cmd(char); +extern char *collect_menu_keys(char *, unsigned, boolean); extern void show_menu_controls(winid, boolean); extern void assign_warnings(uchar *); extern char *nh_getenv(const char *); diff --git a/include/optlist.h b/include/optlist.h index a1b363330..2dc3cc940 100644 --- a/include/optlist.h +++ b/include/optlist.h @@ -274,7 +274,7 @@ opt_##a, NHOPTC(menu_last_page, 4, opt_in, set_in_config, No, Yes, No, No, NoAlias, "jump to the last page in a menu") NHOPTC(menu_next_page, 4, opt_in, set_in_config, No, Yes, No, No, NoAlias, - "goto the next menu page") + "go to the next menu page") NHOPTB(menu_objsyms, 0, opt_in, set_in_game, Off, Yes, No, No, NoAlias, &iflags.menu_head_objsym) #ifdef TTY_GRAPHICS @@ -285,13 +285,17 @@ opt_##a, (boolean *) 0) #endif NHOPTC(menu_previous_page, 4, opt_in, set_in_config, No, Yes, No, No, - NoAlias, "goto the previous menu page") + NoAlias, "go to the previous menu page") NHOPTC(menu_search, 4, opt_in, set_in_config, No, Yes, No, No, NoAlias, "search for a menu item") NHOPTC(menu_select_all, 4, opt_in, set_in_config, No, Yes, No, No, NoAlias, "select all items in a menu") NHOPTC(menu_select_page, 4, opt_in, set_in_config, No, Yes, No, No, NoAlias, "select all items on this page of a menu") + NHOPTC(menu_shift_left, 4, opt_in, set_in_config, No, Yes, No, No, + NoAlias, "pan current menu page left") + NHOPTC(menu_shift_right, 4, opt_in, set_in_config, No, Yes, No, No, + NoAlias, "pan current menu page right") NHOPTB(menu_tab_sep, 0, opt_in, set_wizonly, Off, Yes, No, No, NoAlias, &iflags.menu_tab_sep) NHOPTB(menucolors, 0, opt_in, set_in_game, Off, Yes, Yes, No, NoAlias, diff --git a/include/winX.h b/include/winX.h index 4fc4279bd..06a6409e9 100644 --- a/include/winX.h +++ b/include/winX.h @@ -363,6 +363,7 @@ extern int x_event(int); extern void menu_delete(Widget, XEvent *, String *, Cardinal *); extern void menu_key(Widget, XEvent *, String *, Cardinal *); extern void x11_no_perminv(struct xwindow *); +extern void x11_scroll_perminv(int); extern void create_menu_window(struct xwindow *); extern void destroy_menu_window(struct xwindow *); diff --git a/include/wincurs.h b/include/wincurs.h index 252af8e6e..f9b70ca06 100644 --- a/include/wincurs.h +++ b/include/wincurs.h @@ -195,8 +195,9 @@ extern void curses_status_update(int, genericptr_t, int, int, int, /* cursinvt.c */ -extern void curses_update_inv(void); -extern void curses_add_inv(int, char, attr_t, const char *); +extern void curs_purge_perminv_data(boolean); +extern void curs_update_invt(int); +extern void curs_add_invt(int, char, attr_t, const char *); /* cursinit.c */ diff --git a/include/winprocs.h b/include/winprocs.h index 581a5dbc6..6b7df8aa8 100644 --- a/include/winprocs.h +++ b/include/winprocs.h @@ -231,7 +231,8 @@ extern * via non-display attribute flag */ #define WC2_SUPPRESS_HIST 0x8000L /* 16 putstr(WIN_MESSAGE) supports history * suppression via non-disp attr */ - /* 16 free bits */ +#define WC2_MENU_SHIFT 0x010000L /* 17 horizontal menu scrolling */ + /* 15 free bits */ #define ALIGN_LEFT 1 #define ALIGN_RIGHT 2 diff --git a/include/wintype.h b/include/wintype.h index 47b0b39bf..48c974e29 100644 --- a/include/wintype.h +++ b/include/wintype.h @@ -110,12 +110,15 @@ typedef struct mi { /* invalid winid */ #define WIN_ERR ((winid) -1) -/* menu window keyboard commands (may be mapped) */ +/* menu window keyboard commands (may be mapped); menu_shift_right and + menu_shift_left are for interacting with persistent inventory window */ /* clang-format off */ #define MENU_FIRST_PAGE '^' #define MENU_LAST_PAGE '|' #define MENU_NEXT_PAGE '>' #define MENU_PREVIOUS_PAGE '<' +#define MENU_SHIFT_RIGHT '}' +#define MENU_SHIFT_LEFT '{' #define MENU_SELECT_ALL '.' #define MENU_UNSELECT_ALL '-' #define MENU_INVERT_ALL '@' diff --git a/src/cmd.c b/src/cmd.c index 09156831d..6277dec28 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1885,6 +1885,8 @@ struct ext_func_tab extcmdlist[] = { wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL }, { 'p', "pay", "pay your shopping bill", dopay, 0, NULL }, + { '|', "perminv", "scroll persistent inventory display", + doperminv, IFBURIED | GENERALCMD, NULL }, { ',', "pickup", "pick up things at the current location", dopickup, 0, NULL }, { '\0', "polyself", "polymorph self", diff --git a/src/invent.c b/src/invent.c index bd36622e3..f0b0fd06e 100644 --- a/src/invent.c +++ b/src/invent.c @@ -2291,6 +2291,55 @@ update_inventory(void) (*windowprocs.win_update_inventory)(0); } +/* '|' command - call interface's persistent inventory manipulation routine */ +int +doperminv(void) +{ + /* + * If persistent inventory window is enabled, interact with it. + * + * Depending on interface, might accept and execute one scrolling + * request (MENU_{FIRST,NEXT,PREVIOUS,LAST}_PAGE) then return, + * or might stay and handle multiple requests until user finishes + * (typically by typing or but that's up to interface). + */ + + if (iflags.debug_fuzzer) + return 0; +#if 0 + /* [currently this would redraw the persistent inventory window + whether that's needed or not, so also reset any previous + scrolling; we don't want that if the interface only accepts + one scroll command at a time] */ + update_inventory(); /* make sure that it's up to date */ +#endif + + if ((windowprocs.wincap & WC_PERM_INVENT) == 0) { + /* [TODO? perhaps omit "by " if all the window ports + compiled into this binary lack support for perm_invent...] */ + pline("Persistent inventory display is not supported by '%s'.", + windowprocs.name); + + } else if (!iflags.perm_invent) { + pline( + "Persistent inventory ('perm_invent' option) is not presently enabled."); + + } else if (!g.invent) { + /* [should this be left for the interface to decide?] */ + pline("Persistent inventory display is empty."); + + } else { + /* note: we used to request a scrolling key here and pass that to + (*win_update_inventory)(key), but that limited the functionality + and also cluttered message history with prompt and response so + just send non-zero and have the interface be responsible for it */ + (*windowprocs.win_update_inventory)(1); + + } /* iflags.perm_invent */ + + return 0; +} + /* should of course only be called for things in invent */ static char obj_to_let(struct obj *obj) diff --git a/src/options.c b/src/options.c index 2dc6a2acf..4865ed675 100644 --- a/src/options.c +++ b/src/options.c @@ -224,6 +224,10 @@ static const menu_cmd_t default_menu_cmd_info[] = { "Unselect all items on current page" }, { "menu_search", MENU_SEARCH, "Search and invert matching items" }, + { "menu_shift_right", MENU_SHIFT_RIGHT, + "Pan current page to right (perm_invent only)" }, + { "menu_shift_left", MENU_SHIFT_LEFT, + "Pan current page to left (perm_invent only)" }, { (char *) 0, '\0', (char *) 0 } }; @@ -1562,6 +1566,20 @@ optfn_menu_select_page(int optidx, int req, boolean negated, return shared_menu_optfn(optidx, req, negated, opts, op); } +static int +optfn_menu_shift_left(int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} + +static int +optfn_menu_shift_right(int optidx, int req, boolean negated, + char *opts, char *op) +{ + return shared_menu_optfn(optidx, req, negated, opts, op); +} + /* end of shared key assignments for menu commands */ static int @@ -6814,6 +6832,46 @@ map_menu_cmd(char ch) return ch; } +/* get keystrokes that are used for menu scrolling operations which apply; + printable: for use in a prompt, non-printable: for yn_function() choices */ +char * +collect_menu_keys( + char *outbuf, /* at least big enough for 6 "M-^X" sequences +'\0'*/ + unsigned scrollmask, /* 1: backwards, "^<"; 2: forwards, ">|"; + * 4: left, "{"; 8: right, "}"; */ + boolean printable) /* False: output is string of raw characters, + * True: output is a string of visctrl() sequences; + * matters iff user has mapped any menu scrolling + * commands to control or meta characters */ +{ + struct menuscrollinfo { + char cmdkey; + uchar maskindx; + }; + static const struct menuscrollinfo scroll_keys[] = { + { MENU_FIRST_PAGE, 1 }, + { MENU_PREVIOUS_PAGE, 1 }, + { MENU_NEXT_PAGE, 2 }, + { MENU_LAST_PAGE, 2 }, + { MENU_SHIFT_LEFT, 4 }, + { MENU_SHIFT_RIGHT, 8 }, + }; + int i; + + outbuf[0] = '\0'; + for (i = 0; i < SIZE(scroll_keys); ++i) { + if (scrollmask & scroll_keys[i].maskindx) { + char c = get_menu_cmd_key(scroll_keys[i].cmdkey); + + if (printable) + Strcat(outbuf, visctrl(c)); + else + (void) strkitten(outbuf, c); + } + } + return outbuf; +} + /* Returns the fid of the fruit type; if that type already exists, it * returns the fid of that one; if it does not exist, it adds a new fruit * type to the chain and returns the new one. @@ -7381,6 +7439,7 @@ show_menu_controls(winid win, boolean dolist) char buf[BUFSZ]; const char *fmt, *arg; const struct xtra_cntrls *xcp; + boolean has_menu_shift = wc2_supported("menu_shift"); /* * Relies on spaces to line things up in columns, so must be rendered @@ -7390,11 +7449,16 @@ show_menu_controls(winid win, boolean dolist) putstr(win, 0, "Menu control keys:"); if (dolist) { /* key bindings help: '?i' */ int i; + char ch; fmt = "%-7s %s"; for (i = 0; default_menu_cmd_info[i].desc; i++) { + ch = default_menu_cmd_info[i].cmd; + if ((ch == MENU_SHIFT_RIGHT + || ch == MENU_SHIFT_LEFT) && !has_menu_shift) + continue; Sprintf(buf, fmt, - visctrl(get_menu_cmd_key(default_menu_cmd_info[i].cmd)), + visctrl(get_menu_cmd_key(ch)), default_menu_cmd_info[i].desc); putstr(win, 0, buf); } @@ -7436,6 +7500,16 @@ show_menu_controls(winid win, boolean dolist) visctrl(get_menu_cmd_key(MENU_LAST_PAGE)), "Last page"); putstr(win, 0, buf); + if (has_menu_shift) { + Sprintf(buf, mc_fmt, "Pan view", + visctrl(get_menu_cmd_key(MENU_SHIFT_RIGHT)), + "Right (perm_invent only)"); + putstr(win, 0, buf); + Sprintf(buf, mc_fmt, "", + visctrl(get_menu_cmd_key(MENU_SHIFT_LEFT)), + "Left"); + putstr(win, 0, buf); + } putstr(win, 0, ""); Sprintf(buf, mc_fmt, "Search", visctrl(get_menu_cmd_key(MENU_SEARCH)), @@ -8090,6 +8164,7 @@ static struct wc_Opt wc2_options[] = { { "guicolor", WC2_GUICOLOR }, { "hilite_status", WC2_HILITE_STATUS }, { "hitpointbar", WC2_HITPOINTBAR }, + { "menu_shift", WC2_MENU_SHIFT }, { "petattr", WC2_PETATTR }, { "softkeyboard", WC2_SOFTKEYBOARD }, /* name shown in 'O' menu is different */ diff --git a/win/X11/X11-issues.txt b/win/X11/X11-issues.txt index 1e4f84d89..838ae667d 100644 --- a/win/X11/X11-issues.txt +++ b/win/X11/X11-issues.txt @@ -5,7 +5,8 @@ back to the main window. More focus: although the menu window gets focus, the menu inside that window does not accept focus, so scrolling persistent inventory via -keyboard won't work. +keyboard won't work. [Does not affect the new '|' command which takes +input via yn_function() rather than directly from the perm_invent menu.] When persistent inventory window is displayed, an update that ought to make it grow won't do so even if there is room on the screen for that. @@ -31,3 +32,8 @@ approximately half the width of the vertical scrollbar. It wasn't inserted space in between two sides of map; it was undrawn map. If it had been drawn, the map would have been normal. +Menus which have entries that are too wide to display have their wide +lines truncated rather than adding a horizontal scrollbar. Resizing +larger doesn't recover truncated text and resizing smaller truncates +even more. + diff --git a/win/X11/winX.c b/win/X11/winX.c index c9bf7d2b2..97bbab271 100644 --- a/win/X11/winX.c +++ b/win/X11/winX.c @@ -105,7 +105,7 @@ struct window_procs X11_procs = { #ifdef STATUS_HILITES | WC2_RESET_STATUS | WC2_HILITE_STATUS #endif - | 0L ), + | WC2_MENU_SHIFT ), {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */ X11_init_nhwindows, X11_player_selection, X11_askname, X11_get_nh_event, X11_exit_nhwindows, @@ -1241,7 +1241,7 @@ X11_destroy_nhwindow(winid window) /* display persistent inventory in its own window */ void -X11_update_inventory(int arg UNUSED) +X11_update_inventory(int arg) { struct xwindow *wp = 0; @@ -1252,7 +1252,11 @@ X11_update_inventory(int arg UNUSED) /* skip any calls to update_inventory() before in_moveloop starts */ if (g.program_state.in_moveloop || g.program_state.gameover) { updated_inventory = 1; /* hack to avoid mapping&raising window */ - (void) display_inventory((char *) 0, FALSE); + if (!arg) { + (void) display_inventory((char *) 0, FALSE); + } else { + x11_scroll_perminv(arg); + } updated_inventory = 0; } } else if ((wp = &window_list[WIN_INVEN]) != 0 diff --git a/win/X11/winmenu.c b/win/X11/winmenu.c index 4eb127310..fe52a52e2 100644 --- a/win/X11/winmenu.c +++ b/win/X11/winmenu.c @@ -52,6 +52,7 @@ static void select_match(struct xwindow *, char *); static void invert_all(struct xwindow *); static void invert_match(struct xwindow *, char *); static void menu_popdown(struct xwindow *); +static unsigned menu_scrollmask(struct xwindow *); static Widget menu_create_buttons(struct xwindow *, Widget, Widget); static void menu_create_entries(struct xwindow *, struct menu *); static void destroy_menu_entry_widgets(struct xwindow *); @@ -210,6 +211,8 @@ invert_line(struct xwindow *wp, x11_menu_item *curr, int which, long how_many) } } +static XEvent fake_perminv_event; + /* * Called when we get a key press event on a menu window. */ @@ -220,9 +223,11 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) struct menu_info_t *menu_info; x11_menu_item *curr; struct xwindow *wp; + Widget hbar, vbar; char ch; int count; - boolean selected_something; + boolean selected_something, + perminv_scrolling = (event == &fake_perminv_event); nhUse(params); nhUse(num_params); @@ -230,7 +235,10 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) wp = find_widget(w); menu_info = wp->menu_information; - ch = key_event_to_char((XKeyEvent *) event); + if (!perminv_scrolling) + ch = key_event_to_char((XKeyEvent *) event); + else + ch = (char) fake_perminv_event.type; if (ch == '\0') { /* don't accept nul char/modifier event */ /* don't beep */ @@ -238,7 +246,7 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) } /* don't exclude PICK_NONE menus; doing so disables scrolling via keys */ - if (menu_info->is_active) { /* waiting for input */ + if (menu_info->is_active || perminv_scrolling) { /* handle the input */ /* first check for an explicit selector match, so that it won't be overridden if it happens to duplicate a mapped menu command (':' to look inside a container vs ':' to select via search string) */ @@ -290,7 +298,6 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) X11_nhbell(); return; } else if (ch == MENU_FIRST_PAGE || ch == MENU_LAST_PAGE) { - Widget hbar, vbar; float top = (ch == MENU_FIRST_PAGE) ? 0.0 : 1.0; find_scrollbars(wp->w, wp->popup, &hbar, &vbar); @@ -298,8 +305,6 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) XtCallCallbacks(vbar, XtNjumpProc, &top); return; } else if (ch == MENU_NEXT_PAGE || ch == MENU_PREVIOUS_PAGE) { - Widget hbar, vbar; - find_scrollbars(wp->w, wp->popup, &hbar, &vbar); if (vbar) { float shown, top; @@ -312,6 +317,20 @@ menu_key(Widget w, XEvent *event, String *params, Cardinal *num_params) XtCallCallbacks(vbar, XtNjumpProc, &top); } return; + } else if (ch == MENU_SHIFT_RIGHT || ch == MENU_SHIFT_LEFT) { + find_scrollbars(wp->w, wp->popup, &hbar, &vbar); + if (hbar) { + float shown, halfshown, left; + Arg arg[2]; + + XtSetArg(arg[0], nhStr(XtNshown), &shown); + XtSetArg(arg[1], nhStr(XtNtopOfThumb), &left); + XtGetValues(hbar, arg, TWO); + halfshown = shown * 0.5; + left += ((ch == MENU_NEXT_PAGE) ? halfshown : -halfshown); + XtCallCallbacks(hbar, XtNjumpProc, &left); + } + return; } else if (index(menu_info->curr_menu.gacc, ch)) { group_accel: /* matched a group accelerator */ @@ -573,6 +592,39 @@ menu_popdown(struct xwindow *wp) wp->menu_information->is_up = FALSE; /* menu is down */ } +/* construct a bit mask specifying which scrolling operations are allowed */ +static unsigned +menu_scrollmask(struct xwindow *wp) +{ + float shown, top; + Arg args[2]; + Widget hbar = (Widget) 0, vbar = (Widget) 0; + unsigned scrlmask = 0U; + + /* set up args once, then use twice (provided that both scrollbars are + present); 'top' is left for horizontal scrollbar */ + (void) memset(args, 0, sizeof args); + XtSetArg(args[0], nhStr(XtNshown), &shown); + XtSetArg(args[1], nhStr(XtNtopOfThumb), &top); + + find_scrollbars(wp->w, wp->popup, &hbar, &vbar); + if (vbar) { + XtGetValues(vbar, args, TWO); + if (top > 0.0) + scrlmask |= 1U; /* not at top; can scroll up */ + if (top + shown < 1.0) + scrlmask |= 2U; /* more beyond bottom; can scroll down */ + } + if (hbar) { + XtGetValues(hbar, args, TWO); + if (top > 0.0) + scrlmask |= 4U; /* not at left edge; can scroll to left */ + if (top + shown < 1.0) + scrlmask |= 8U; /* more beyond right side; can scroll to right */ + } + return scrlmask; +} + /* Global functions ======================================================= */ /* called by X11_update_inventory() if persistent inventory is currently @@ -586,6 +638,91 @@ x11_no_perminv(struct xwindow *wp) } } +/* called by X11_update_inventory() if user has executed #perminv command */ +void +x11_scroll_perminv(int arg UNUSED) /* arg is always 1 */ +{ + static const char extrakeys[] = "\033 \n\r\003\177\b"; + char ch, menukeys[QBUFSZ]; + boolean save_is_active; + unsigned scrlmask; + Cardinal no_args = 0; + struct xwindow *wp = &window_list[WIN_INVEN]; + + /* caller has ensured that perm_invent is enabled, but the window + might not be displayed; if that's the case, display it */ + if (wp->type == NHW_MENU && !wp->menu_information->is_up) + X11_update_inventory(0); + /* if it's still not displayed for some reason, bail out now */ + if (wp->type != NHW_MENU || !wp->menu_information->is_up) { + X11_nhbell(); + return; + } + + do { + scrlmask = menu_scrollmask(wp); + (void) collect_menu_keys(menukeys, scrlmask, FALSE); + /* + * Add quitchars plus a few others to the player's scrolling keys. + * We accept some extra characters that menus usually ignore: + * ^C will be treated like , leaving menu positioned as-is + * and returning to play; or will be treated + * like and , reseting the menu to its top and + * returning to play; other charcters will either be rejected by + * yn_function or stay here for scrolling. + */ + Strcat(menukeys, extrakeys); + /* append any scrolling keys excluded by scrlmask, after the \033 + added by extrakeys; they'll be acceptable but not shown */ + (void) collect_menu_keys(eos(menukeys), ~scrlmask, FALSE); + + /* normally the perm_invent menu is not flagged 'is_active' because + it doesn't accept input, so menu_popdown() doesn't set the flag + for the event loop to exit; force 'is_active' while this prompt + is in progress so that it won't be left pending if player closes + the menu via mouse */ + save_is_active = wp->menu_information->is_active; + wp->menu_information->is_active = TRUE; + ch = X11_yn_function_core("Inventory scroll:", menukeys, + 0, YN_NO_LOGMESG); + if (wp->menu_information->is_up) + wp->menu_information->is_active = save_is_active; + else + ch = 0; + + if (ch == C('c')) /* ^C */ + ch = '\033'; + else if (ch == '\177' || ch == '\b') /* or */ + ch = '\n'; + + if (ch && ch != '\033') { + /* in case persistent inventory window is covered, force it + to be on top; does not grab pointer or keyboard focus */ + XMapRaised(XtDisplay(wp->popup), XtWindow(wp->popup)); + + /* the fake event never goes onto X's event queue; it is only + examined by menu_key(), so we shortcut the messy details in + favor of easy to handle union type code; might conceivably + confuse a sophisticated debugger so we should possibly redo + this to set it up properly: event->keyevent->keycode */ + fake_perminv_event.type = !index(quitchars, ch) ? ch + : MENU_FIRST_PAGE; + menu_key(wp->w, &fake_perminv_event, (String *) 0, &no_args); + fake_perminv_event.type = 0; + } + + /* if yn_function() is using a popup (the 'slow=False' setting + in NetHack.ad) for its prompt+response and there is any + overlap between the persistent inventory and main windows, + perm_invent would be pushed behind the map every iteration of + this loop, so handle only one character at a time for !slow */ + if (!appResources.slow) + break; + } while (ch && !index(quitchars, ch)); + + return; +} + void X11_start_menu(winid window, unsigned long mbehavior UNUSED) { diff --git a/win/curses/cursinvt.c b/win/curses/cursinvt.c index 05f9bc4b7..bee379394 100644 --- a/win/curses/cursinvt.c +++ b/win/curses/cursinvt.c @@ -8,15 +8,62 @@ #include "wincurs.h" #include "cursinvt.h" -/* Permanent inventory for curses interface */ +static void curs_invt_updated(WINDOW *); +static unsigned pi_article_skip(const char *); +static int curs_scroll_invt(WINDOW *); +static void curs_show_invt(WINDOW *); + +/* + * Persistent inventory (perm_invent) for curses interface. + * It resembles a menu but does not function like one. + */ + +/* pseudo menu line data */ +struct pi_line { + char *invtxt; /* class header or inventory item without letter prefix */ + attr_t c_attr; /* attribute for class headers */ + char letter; /* inventory letter; accelerator if this was really a menu; + * used to distinguish item lines from header lines and for + * display (no selection possible) */ +}; +static struct pi_line zero_pi_line; + +/* full perm_invent data; added to array[] one line at a time */ +struct pi_data { + struct pi_line *array; /* one element for each line of perminv */ + unsigned allocsize, inuseindx; /* num elements allocated and populated */ + unsigned rowoffset, coloffset; /* for displaying a subset due to space */ + unsigned widest; /* longest array[].invtxt */ +}; +#define PERMINV_CHUNK 20 /* number of elements to grow array[] when needed */ + +/* current persistent inventory */ +static struct pi_data pi = { (struct pi_line *) 0, 0, 0, 0, 0, 0 }; + +/* discard saved persistent inventory data */ +void +curs_purge_perminv_data(boolean everything) +{ + if (pi.array) { + unsigned idx; + + for (idx = 0; idx < pi.inuseindx; ++idx) + free(pi.array[idx].invtxt), pi.array[idx].invtxt = 0; + + if (everything) + free(pi.array), pi.array = 0, pi.allocsize = 0; + } + + pi.inuseindx = 0; + pi.rowoffset = pi.coloffset = 0; + pi.widest = 0; +} /* Runs when the game indicates that the inventory has been updated */ void -curses_update_inv(void) +curs_update_invt(int arg) { WINDOW *win = curses_get_nhwin(INV_WIN); - boolean border; - int x = 0, y = 0; /* Check if the inventory window is enabled in first place */ if (!win) { @@ -34,88 +81,344 @@ curses_update_inv(void) return; } - border = curses_window_has_border(INV_WIN); - - /* Figure out drawing area */ - if (border) { - x++; - y++; - } - - /* Clear the window as it is at the moment. */ + /* clear anything displayed from previous update */ werase(win); - display_inventory(NULL, FALSE); + if (!arg) { - if (border) + if (pi.array) /* previous data is obsolete */ + curs_purge_perminv_data(FALSE); + + /* ask core to display full inventory in a PICK_NONE menu; + instead of setting up an ordinary menu, it will indirectly + call curs_add_invt() for each line (including class headers) */ + display_inventory(NULL, FALSE); + curs_invt_updated(win); + + } else { /* 'arg' is non-zero but otherwise unused */ + int scrollingdone; + + /* previous data is still valid; let player interactively scroll it */ + do { + scrollingdone = curs_scroll_invt(win); + curs_invt_updated(win); + } while (!scrollingdone); + + } + return; +} + +/* persistent inventory has been updated or scrolled/panned; re-display it */ +static void +curs_invt_updated(WINDOW *win) +{ + /* display collected inventory data, probably clipped */ + curs_show_invt(win); + + if (curses_window_has_border(INV_WIN)) box(win, 0, 0); wnoutrefresh(win); } -/* Adds an inventory item. 'y' is 1 rather than 0 for the first item. */ -void -curses_add_inv(int y, char accelerator, attr_t attr, const char *str) +/* scroll persistent inventory window forwards or backwards or side-to-side */ +static int +curs_scroll_invt(WINDOW *win UNUSED) { - WINDOW *win = curses_get_nhwin(INV_WIN); - int color = NO_COLOR; - int x = 0, width, height, available_width, stroffset = 0, - border = curses_window_has_border(INV_WIN) ? 1 : 0; - - /* Figure out where to draw the line */ - x += border; /* x starts at 0 and is incremented for border */ - y -= 1 - border; /* y starts at 1 and is decremented for non-border */ + char menukeys[QBUFSZ], qbuf[QBUFSZ]; + unsigned uheight, uwidth, uhalfwidth, scrlmask; + int ch, menucmd, height, width; + int res = 0; curses_get_window_size(INV_WIN, &height, &width); - /* - * TODO: - * Implement a way to switch focus from map to inventory so that - * the latter can be scrolled. Must not require use of a mouse. - * - * Also, when entries are omitted due to lack of space, mark the - * last line to indicate "there's more that you can't see" (like - * horizontal status window does for excess status conditions). - * Normal menu does this via 'page M of N'. - */ - if (y - border >= height) /* 'height' is already -2 for Top+Btm borders */ - return; - available_width = width; /* 'width' also already -2 for Lft+Rgt borders */ + uheight = (unsigned) height; + uwidth = (unsigned) width; + uhalfwidth = uwidth / 2; - wmove(win, y, x); - if (accelerator) { - /* despite being shown as a menu, nothing is selectable from the - persistent inventory window so avoid the usual highlighting of - inventory letters */ - wprintw(win, "%c) ", accelerator); - available_width -= 3; /* letter+parenthesis+space */ - /* - * Narrow the entries to fit more of the interesting text. Do so - * unconditionally rather than trying to figure whether it's needed. - * When 'sortpack' is enabled we could also strip out " of" - * from " of but if that's to be done, - * the core ought to do it. - * - * 'stroffset': defer skipping the article prefix until after menu - * color pattern matching has taken place so that the persistent - * inventory window always gets same coloring as regular inventory. - */ - if (!strncmpi(str, "a ", 2)) - stroffset = 2; - else if (!strncmpi(str, "an ", 3)) - stroffset = 3; - else if (!strncmpi(str, "the ", 4)) - stroffset = 4; + menukeys[0] = '\0'; + scrlmask = 0U; + if (pi.rowoffset > 0) + scrlmask |= 1U; /* include scroll backwards: ^ and < */ + if (pi.rowoffset + uheight <= pi.inuseindx) + scrlmask |= 2U; /* include scroll forwards: > and | */ + if (pi.coloffset > 0) + scrlmask |= 4U; /* include scroll left: { */ + if (pi.widest > pi.coloffset + uwidth) + scrlmask |= 8U; /* include scroll right: } */ + (void) collect_menu_keys(menukeys, scrlmask, TRUE); + + Snprintf(qbuf, sizeof qbuf, "Inventory scroll: [%s%s%s] ", + menukeys, *menukeys ? " " : "", "Ret Esc"); + + curses_count_window(qbuf); + ch = getch(); + curses_count_window((char *) 0); + curses_clear_unhighlight_message_window(); + + menucmd = (ch <= 0 || ch >= 255) ? ch : (int) (uchar) map_menu_cmd(ch); + switch (menucmd) { + case KEY_ESC: + case C('c'): /* ^C */ + /* for , leave window with scrolling as-is */ + res = -1; + break; + case '\n': + case '\r': + case '\b': + case '\177': + /* for , or when already on last page, + restore window to unscrolled */ + pi.rowoffset = pi.coloffset = 0; + res = 1; + break; + case ' ': + if (pi.rowoffset + uheight <= pi.inuseindx) { + pi.rowoffset = pi.coloffset = 0; + res = 1; + break; + } + /*FALLTHRU*/ + case KEY_RIGHT: + case KEY_NPAGE: + case MENU_NEXT_PAGE: + if (pi.inuseindx <= uheight) + pi.rowoffset = 0; + else if (pi.rowoffset + 2 * uheight <= pi.inuseindx) + pi.rowoffset += uheight; + else + pi.rowoffset = pi.inuseindx - (uheight - 1); + break; + case KEY_LEFT: + case KEY_PPAGE: + case MENU_PREVIOUS_PAGE: + if (pi.rowoffset >= uheight) + pi.rowoffset -= uheight; + else + pi.rowoffset = 0; + break; + + case KEY_END: + case MENU_LAST_PAGE: + if (pi.inuseindx > uheight) + pi.rowoffset = pi.inuseindx - (uheight - 1); + else + pi.rowoffset = 0; + break; + case KEY_HOME: + case MENU_FIRST_PAGE: + pi.rowoffset = 0; + break; + + case KEY_DOWN: + if (pi.rowoffset + uheight <= pi.inuseindx) + pi.rowoffset += 1; + break; + case KEY_UP: + if (pi.rowoffset > 0) + pi.rowoffset -= 1; + else + pi.rowoffset = 0; + break; + + case MENU_SHIFT_RIGHT: + if (pi.widest <= uwidth) { + pi.coloffset = 0; + } else { + pi.coloffset += uhalfwidth; + if (pi.coloffset + uwidth > pi.widest) + pi.coloffset = pi.widest - uwidth; + } + break; + case MENU_SHIFT_LEFT: + if (pi.coloffset >= uhalfwidth) + pi.coloffset -= uhalfwidth; + else + pi.coloffset = 0; + break; + +#if 0 + case MENU_SEARCH: + break; +#endif + case '\0': + default: + curses_nhbell(); + break; } - /* only perform menu coloring on item entries, not subtitles */ - if (accelerator && iflags.use_menu_color) { - attr = 0; - get_menu_coloring(str, &color, (int *) &attr); - attr = curses_convert_attr(attr); - } - if (color == NO_COLOR) - color = NONE; - curses_menu_color_attr(win, color, attr, ON); - wprintw(win, "%.*s", available_width, str + stroffset); - curses_menu_color_attr(win, color, attr, OFF); - wclrtoeol(win); + return res; +} + +/* check 'str' for an article prefix and return length of that */ +static unsigned +pi_article_skip(const char *str) +{ + unsigned skip = 0; /* number of chars to skip when displaying str */ + + /* + * if (!strncmpi(str, "a ", 2)) + * skip = 2; + * else if (!strncmpi(str, "an ", 3)) + * skip = 3; + * else if (!strncmpi(str, "the ", 4)) + * skip = 4; + */ + if (str[0] == 'a') { + if (str[1] == ' ') + skip = 2; + else if (str[1] == 'n' && str[2] == ' ') + skip = 3; + } else if (str[0] == 't') { + if (str[1] == 'h' && str[2] == 'e' && str[3] == ' ') + skip = 4; + } + + return skip; +} + +/* store an inventory item or class header but don't display anything yet */ +void +curs_add_invt( + int linenum, /* line index; 1..n rather than 0..n-1 */ + char accelerator, /* selector letter for items, 0 for class headers */ + attr_t attr, /* curses attribute for headers, 0 for items */ + const char *str) /* formatted inventory item, without invlet prefix, + * or class header text */ +{ + unsigned idx, len; + struct pi_line newelement, *aptr = pi.array; + + if ((unsigned) linenum > pi.allocsize) { + pi.allocsize += PERMINV_CHUNK; + pi.array = (struct pi_line *) alloc(pi.allocsize * sizeof *aptr); + for (idx = 0; idx < pi.allocsize; ++idx) + pi.array[idx] = (idx < pi.inuseindx) ? aptr[idx] : zero_pi_line; + aptr = pi.array; + } + + newelement.invtxt = dupstr(str); + newelement.c_attr = attr ? A_NORMAL : NONE; /* override menu_headings */ + newelement.letter = accelerator; + aptr[pi.inuseindx++] = newelement; + + len = strlen(str); + if (accelerator) { + /* +3: ")c " inventory letter will be inserted before invtxt; + invtxt's "a "/"an "/"the " prefix, if any, will be skipped */ + len += 3; + if (len > pi.widest) + len -= pi_article_skip(str); + } + if (len > pi.widest) + pi.widest = len; +} + +/* display the inventory menu-like data collected in pi.array[] */ +static void +curs_show_invt(WINDOW *win) +{ + const char *str; + char accelerator, tmpbuf[BUFSZ]; + int attr, color; + unsigned lineno, stroffset, widest, left_col, right_col, + first_shown = 0, last_shown = 0, item_count = 0; + int x, y, width, height, available_width, + border = curses_window_has_border(INV_WIN) ? 1 : 0; + + x = border; /* same for every line; 1 if border, 0 otherwise */ + + curses_get_window_size(INV_WIN, &height, &width); + widest = pi.widest; + left_col = pi.coloffset + 1; + right_col = left_col + (unsigned) width - 1; + + for (lineno = 0; lineno < pi.rowoffset; ++lineno) + if (pi.array[lineno].letter) + ++item_count; + + for (lineno = pi.rowoffset; lineno < pi.inuseindx; ++lineno) { + str = pi.array[lineno].invtxt; + accelerator = pi.array[lineno].letter; + attr = pi.array[lineno].c_attr; + color = NO_COLOR; + + if (accelerator) + ++item_count; + + /* Figure out where to draw the line */ + y = (int) (lineno - pi.rowoffset) + border; + if (y - border >= height) { /* height already -2 for Top+Btm border */ + /* 'y' has grown too big; there are too many lines to fit */ + continue; /* skip, but still loop to update 'item_count' */ + } + available_width = width; /* width is already -2 for Lft+Rgt borders */ + + wmove(win, y, x); + + stroffset = 0; + if (accelerator) { /* inventory item line */ + if (!first_shown) + first_shown = item_count; + last_shown = item_count; + /* despite being shown as a menu, nothing is selectable from the + persistent inventory window so avoid the usual highlighting + of inventory letters */ + wprintw(win, "%c) ", accelerator); + available_width -= 3; /* letter+parenthesis+space */ + /* + * Narrow the entries to fit more of the interesting text, + * but defer the removal until after menu colors matching. + * Do so unconditionally rather than trying to figure whether + * it's needed. When 'sortpack' is enabled we could also strip + * out " of" from " of " + * but if that's to be done, the core ought to do it. + */ + stroffset = pi_article_skip(str); /* to skip "a "/"an "/"the " */ + /* if/when scrolled right, invtxt for item lines gets shifted */ + stroffset += pi.coloffset; + + /* only perform menu coloring on item entries, not subtitles */ + if (iflags.use_menu_color) { + attr = 0; + get_menu_coloring(str, &color, (int *) &attr); + attr = curses_convert_attr(attr); + } + } + + if (stroffset < strlen(str)) { + if (color == NO_COLOR) + color = NONE; + curses_menu_color_attr(win, color, attr, ON); + wprintw(win, "%.*s", available_width, str + stroffset); + curses_menu_color_attr(win, color, attr, OFF); + } + + wclrtoeol(win); + } /* lineno loop */ + + if (pi.inuseindx > (unsigned) height) { + /* some lines aren't shown; overwrite rightmost portion of + last line with something like "[1-24 of 30>"; right justified + so that the line might still show something useful; could be on + line of its own, in which case we needed to erase that first */ + y = height - (1 - border); + if ((unsigned) y == pi.inuseindx - pi.rowoffset) { + wmove(win, y, x); + wclrtoeol(win); + } + Sprintf(tmpbuf, "%c%u-%u of %u%c", + (first_shown > 1) ? '<' : '[', + first_shown, last_shown, item_count, + (last_shown < item_count) ? '>' : ']'); + mvwaddstr(win, y, x + (width - (int) strlen(tmpbuf)), tmpbuf); + } + if (widest > (unsigned) width) { + /* some columns aren't shown; overwrite rightmost portion of + first line with something like "[1-25 of 40}" */ + Sprintf(tmpbuf, "%c%u-%u of %u%c", + (left_col > 1) ? '{' : '[', + left_col, right_col, widest, + (right_col < widest) ? '}' : ']'); + mvwaddstr(win, border, x + (width - (int) strlen(tmpbuf)), tmpbuf); + } + return; } diff --git a/win/curses/cursinvt.h b/win/curses/cursinvt.h index fc367ab2b..569b1ccb6 100644 --- a/win/curses/cursinvt.h +++ b/win/curses/cursinvt.h @@ -9,6 +9,6 @@ /* Global declarations */ -void curses_update_inv(void); +void curses_update_inv(int); #endif /* CURSINVT_H */ diff --git a/win/curses/cursmain.c b/win/curses/cursmain.c index 79104a800..835762546 100644 --- a/win/curses/cursmain.c +++ b/win/curses/cursmain.c @@ -48,7 +48,7 @@ struct window_procs curses_procs = { #endif | WC2_FLUSH_STATUS | WC2_TERM_SIZE | WC2_STATUSLINES | WC2_WINDOWBORDERS | WC2_PETATTR | WC2_GUICOLOR - | WC2_SUPPRESS_HIST), + | WC2_SUPPRESS_HIST | WC2_MENU_SHIFT), {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */ curses_init_nhwindows, curses_player_selection, @@ -436,6 +436,7 @@ curses_destroy_nhwindow(winid wid) curses_status_finish(); /* discard cached status data */ break; case INV_WIN: + curs_purge_perminv_data(TRUE); iflags.perm_invent = 0; /* avoid unexpected update_inventory() */ break; case MAP_WIN: @@ -568,7 +569,7 @@ curses_add_menu(winid wid, const glyph_info *glyphinfo, /* persistent inventory window; nothing is selectable; omit glyphinfo because perm_invent is to the side of the map so usually cramped for space */ - curses_add_inv(inv_update, accelerator, curses_attr, str); + curs_add_invt(inv_update, accelerator, curses_attr, str); inv_update++; return; } @@ -631,24 +632,34 @@ curses_select_menu(winid wid, int how, MENU_ITEM_P ** selected) } void -curses_update_inventory(int arg UNUSED) +curses_update_inventory(int arg) { /* Don't do anything if perm_invent is off unless it was on and player just changed the option. */ if (!iflags.perm_invent) { if (curses_get_nhwin(INV_WIN)) { curs_reset_windows(TRUE, FALSE); + curs_purge_perminv_data(FALSE); } return; } - /* Update inventory sidebar. NetHack uses normal menu functions - when drawing the inventory, and we don't want to change the - underlying code. So instead, track if an inventory update is - being performed with a static variable. */ - inv_update = 1; - curses_update_inv(); - inv_update = 0; + /* skip inventory updating during character initialization */ + if (!g.program_state.in_moveloop && !g.program_state.gameover) + return; + + if (!arg) { + /* Update inventory sidebar. NetHack uses normal menu functions + when gathering the inventory, and we don't want to change the + underlying code. So instead, track if an inventory update is + being performed with a static variable. */ + inv_update = 1; + curs_update_invt(0); + inv_update = 0; + } else { + /* perform scrolling operations on persistent inventory window */ + curs_update_invt(arg); + } } /*