]> granicus.if.org Git - nethack/commitdiff
\#perminv, 2 of 2: implementation
authorPatR <rankin@nethack.org>
Sun, 14 Mar 2021 02:18:53 +0000 (18:18 -0800)
committerPatR <rankin@nethack.org>
Sun, 14 Mar 2021 02:18:53 +0000 (18:18 -0800)
Add new '|' command, aka #perminv, which allows the player to
send menu scrolling keystrokes to the persistent inventory window.

Implemented for X11, where its usefulness is limited, and for
curses, where it is more needed and also more fully functional.
The interface can either prompt for one keystroke, act upon it,
and return to normal play, or it can loop for multiple keystrokes
until player types <return> or <escape>.  X11 does the former if
the 'slow' application resource is False so that prompting uses
popups, and the latter when 'slow' is True where prompting is in
a fixed spot and doesn't end up causing the persistent inventory
window to be stacked behind the map window.  curses always does
the loop-until-done approach.  It also accepts up and down arrow
keys to scroll one line at a time.

Also adds two new menu scrolling commands, menu_shift_right (key
'}' by default) and menu_shift_left ('{') if wincap2 flags contain
WC2_MENU_SHIFT.  Shifting allows different substrings of too-long
lines to be seen.

For X11, neither works because their handling requires a horizontal
scrollbar and for some reason that escapes me our menus don't have
one of those.  If they did, shifts could work for all menus but a
shifted window would hide the selection letters.  So shifting would
be most usefully done as:  pan right, read more of any long lines,
immediately pan back to the left.

For curses, they only apply to the persistent inventory window.
Shift right redraws it with class headers and inventory letters
shown normally but the item descriptions omit their leftmost
portion, showing more text towards the end.  Shift left reverses
that and does nothing if the beginning is already in view.  Forward
and backward scrolling while shifted leave the shift in place.

21 files changed:
dat/hh
dat/opthelp
doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes37.0
doc/window.doc
include/extern.h
include/optlist.h
include/winX.h
include/wincurs.h
include/winprocs.h
include/wintype.h
src/cmd.c
src/invent.c
src/options.c
win/X11/X11-issues.txt
win/X11/winX.c
win/X11/winmenu.c
win/curses/cursinvt.c
win/curses/cursinvt.h
win/curses/cursmain.c

diff --git a/dat/hh b/dat/hh
index 75f87b33b7300bdfe518385f4650ec031e175faf..17d68bf96077849840ad0d1e2d2f37c296a23946 100644 (file)
--- 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)
index 5b031eb03189bbc6ea4d7cdb7cc957167e8efa94..c13294d3ca3108346e393adf6055339248e2b663 100644 (file)
@@ -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)
index d271c8fe0ede62edd41e8689e3c4d0feb5d39b40..7bafb7c0c179898952cf4e80f99908905d9ec018 100644 (file)
@@ -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.
index 41bd4229dbaddad7c3bf89fd68983ff17e407f23..e2ed921ae88679f891b3cdb716444ab647c6ac92 100644 (file)
@@ -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).
index 2c1e523b17b45113fac796c008f5f44e5413b36a..aa1e7ba68af69e2603963129586ab5cb9500bff4 100644 (file)
@@ -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
index 1c26bf9523a639d250dbed86a2569a7589d33408..bac8008b5ba152cc54c053407f49de650dc25695 100644 (file)
@@ -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
+                     <return> or <esc> 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 |
index 90ff05575d041662d3ac29f9e714f198a1abb5aa..a13c05a63bb19b2af51ceb8dcdb3c2ba075a28ce 100644 (file)
@@ -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 *);
index a1b363330c7917bcf976bf036e44ca70c5049e1d..2dc3cc9408f331aaf88ff3ed819dd6ad8277058a 100644 (file)
@@ -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,
index 4fc4279bdc4424ff0ef44fd7f987b65aca3fb329..06a6409e9d81a6c802085a99bcb9e81c718dfd11 100644 (file)
@@ -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 *);
 
index 252af8e6e86291206c10f22b76ca5fab2f2d390d..f9b70ca066554291ae32fa69422a7537c7403882 100644 (file)
@@ -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 */
 
index 581a5dbc645cddd301255820eb5a6c389140ba6f..6b7df8aa80f56f6ce668ec2067aa3b3aa4f5ed69 100644 (file)
@@ -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
index 47b0b39bf96d5f4888ef42f3e618ce3b1b541efc..48c974e29d2e43f7bde772bdd2f35495ab4ce8f4 100644 (file)
@@ -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         '@'
index 09156831dc9d460e94e04eab10930e24b9950663..6277dec280263b523f8512c9e259b5d7a643e59b 100644 (file)
--- 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",
index bd36622e3b362ceb4e57d7a16765c3308441f251..f0b0fd06e5f59ccb605af3d59629a381c6565c21 100644 (file)
@@ -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 <return> or <esc> 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 <interface>" 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)
index 2dc6a2acf727e363c8f35d823e008408a577eb7e..4865ed6754b5262c39fac109a750ab025b8c646b 100644 (file)
@@ -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 */
index 1e4f84d89eba6a9a446c4f97ab1cf61e661db5b1..838ae667d66b5cf3feb1ece53dce9ecdec103b47 100644 (file)
@@ -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.
+
index c9bf7d2b2f861a6201bf489063df03030c2c4c09..97bbab271f9cba4af0b34de092c776f5fa233bb3 100644 (file)
@@ -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
index 4eb127310ef1f4f1293516a0ebfd3f391814aee8..fe52a52e203d7b63c9aac49aec7b1a0a02919d41 100644 (file)
@@ -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 <escape>, leaving menu positioned as-is
+         * and returning to play; <delete> or <backspace> will be treated
+         * like <return> and <space>, 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') /* <delete> or <backspace> */
+            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)
 {
index 05f9bc4b7a0699320602db628c09898999d054a4..bee3793942997e3b2140dabff9dc309a1088b535 100644 (file)
@@ -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);
+    /* clear anything displayed from previous update */
+    werase(win);
 
-    /* Figure out drawing area */
-    if (border) {
-        x++;
-        y++;
-    }
+    if (!arg) {
 
-    /* Clear the window as it is at the moment. */
-    werase(win);
+        if (pi.array) /* previous data is obsolete */
+            curs_purge_perminv_data(FALSE);
 
-    display_inventory(NULL, 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);
 
-    if (border)
+    } 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);
+    uheight    = (unsigned) height;
+    uwidth     = (unsigned) width;
+    uhalfwidth = uwidth / 2;
+
+    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 <escape>, leave window with scrolling as-is */
+        res = -1;
+        break;
+    case '\n':
+    case '\r':
+    case '\b':
+    case '\177':
+        /* for <return>, or <space> 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;
+    }
+    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 */
+
     /*
-     * 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 (!strncmpi(str, "a ", 2))
+     *     skip = 2;
+     * else if (!strncmpi(str, "an ", 3))
+     *     skip = 3;
+     * else if (!strncmpi(str, "the ", 4))
+     *     skip = 4;
      */
-    if (y - border >= height) /* 'height' is already -2 for Top+Btm borders */
-        return;
-    available_width = width; /* 'width' also already -2 for Lft+Rgt borders */
+    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;
 
-    wmove(win, y, x);
+    len = strlen(str);
     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 "<class> of"
-         * from "<prefix><class> of <item><suffix> 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;
+        /* +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 "<class> of" from "<prefix><class> of <item><suffix>"
+             * 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);
     }
-    /* 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 (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);
     }
-    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;
 }
index fc367ab2bb2d063a1a38834bedefc8c8b6252a75..569b1ccb6be148abfe1a69c7e4d73258f51dcd13 100644 (file)
@@ -9,6 +9,6 @@
 
 /* Global declarations */
 
-void curses_update_inv(void);
+void curses_update_inv(int);
 
 #endif /* CURSINVT_H */
index 79104a80012d4b99dc8adc5c79fba39798f5a777..835762546f2139f5b388a44a4722c1b6678a1d7f 100644 (file)
@@ -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);
+    }
 }
 
 /*