]> granicus.if.org Git - nethack/commitdiff
Qt extended commands
authorPatR <rankin@nethack.org>
Mon, 30 Nov 2020 11:18:45 +0000 (03:18 -0800)
committerPatR <rankin@nethack.org>
Mon, 30 Nov 2020 11:18:45 +0000 (03:18 -0800)
When responding to '#', the Qt interface puts up a grid of buttons
labelled with the names of commands.  Then if the user types
instead of clicking on a button, buttons which can no longer match
are removed rather than grayed out.  The remaining ones keep their
same relative positions.  Once whole rows or whole columns were
gone, it looked awful.  With rows gone, the size of the grid
shrank but the popup stayed the same size, so the one-line prompt
area expanded to fill up the vacated vertical space.  That caused
the prompt and partial response to move as they stayed centered in
their growing area.  With columns gone, the width of the buttons
in remaining columns expanded and they spread out to take up
vacated horizontal space.  Once the candidate commands were all
in one column, the buttons spanned the width of the grid.  (That's
mostly my fault due to changing the grid from being row-oriented
[a b c]
[d e  ]
to column oriented
[a d]
[b e]
[c  ]
which resulted in columns going away a lot faster and possibly down
to one when the old layout always had at least two.  But old layout
could drop to one row; the current layout always has at least two.)

Also, accept ^[ as ESC.  Typing ESC when partial input is present
kills that input but keeps prompting.  Typing ESC when no input
is present (none entered yet or a second of two consecutive ESCs)
cancels the operation.

Allow ^U to kill partial input.  If used when no input is present,
nothing happens, similar to backspace.  Unlike tty and curses, it's
hardcoded here.  That shouldn't be a problem because ESC can be
used as a substitute if ^U isn't what the player normally uses.

win/Qt/qt_menu.cpp
win/Qt/qt_menu.h
win/Qt/qt_xcmd.cpp

index 731333d99c62b1259e08ca60d973246c7a41b81a..ab8b59ea75a3feaeb816de17267ef0305045d8d1 100644 (file)
@@ -55,7 +55,7 @@ namespace nethack_qt_ {
 void centerOnMain( QWidget* w );
 // end temporary
 
-static uchar keyValue(QKeyEvent *key_event)
+uchar keyValue(QKeyEvent *key_event)
 {
     // key_event manipulation derived from NetHackQtBind::notify()
     const int k = key_event->key();
index e69265a4f42bfe4c32e93343cd260be0b16d4946..18be9e7321d3999b6216e25a8fb364c694770494 100644 (file)
@@ -15,6 +15,8 @@
 
 namespace nethack_qt_ {
 
+extern uchar keyValue(QKeyEvent *key_event); // also used in qt_xcmd.cpp
+
 class NetHackQtTextListBox : public QListWidget {
 public:
     NetHackQtTextListBox(QWidget* parent = NULL) : QListWidget(parent) { }
index a1a34361004fa30274f7a8bd027512b07f661592..ce81fbb447e0ff07fb6865f50f783234d2b73871 100644 (file)
@@ -3,6 +3,15 @@
 // NetHack may be freely redistributed.  See license for details.
 
 // qt_xcmd.cpp -- extended command widget
+//
+// TODO:
+//  Add button that toggles the grid of command names from column-oriented
+//    to row-oriented and vice versa.
+//  Add another button to filter out commands that can be invoked by a
+//    'normal' keystroke (not Meta) with current key bindings.
+//  If not those, move the [cancel] button from being absurdly wide at the
+//    top of the popup to being ordinary width, right justified on same
+//    line as the prompt where user's typed characters are shown.
 
 extern "C" {
 #include "hack.h"
@@ -23,6 +32,8 @@ extern "C" {
 
 namespace nethack_qt_ {
 
+extern uchar keyValue(QKeyEvent *key_event); // from qt_menu.cpp
+
 // temporary
 void centerOnMain(QWidget *);
 // end temporary
@@ -31,7 +42,8 @@ static inline bool
 interesting_command(unsigned indx)
 {
     return (!(extcmdlist[indx].flags & CMD_NOT_AVAILABLE)
-            /* 'wizard' is #undef'd above [why?] so rely on its internals */
+            /* 'wizard' is #undef'd above because Qt uses that token
+               so rely on its internals */
             && (flags.debug || !(extcmdlist[indx].flags & WIZMODECMD)));
 }
 
@@ -40,7 +52,7 @@ NetHackQtExtCmdRequestor::NetHackQtExtCmdRequestor(QWidget *parent) :
 {
     QVBoxLayout *l = new QVBoxLayout(this);
 
-    QPushButtoncan = new QPushButton("Cancel", this);
+    QPushButton *can = new QPushButton("Cancel", this);
     can->setDefault(true);
     can->setMinimumSize(can->sizeHint());
     l->addWidget(can);
@@ -66,7 +78,7 @@ NetHackQtExtCmdRequestor::NetHackQtExtCmdRequestor(QWidget *parent) :
        when resulting 'nrows' is too big, if GroupBox supports that);
        it used to be hardcoded 4 but after every command became accessible
        as an extended command, that resulted in so many rows that some of
-       the buttoms were chopped off at the bottom of the grid */
+       the buttons were chopped off at the bottom of the grid */
     unsigned ncols = !flags.debug ? 6 : 8,
              nrows = (ncmds + ncols - 1) / ncols;
     /*
@@ -84,21 +96,36 @@ NetHackQtExtCmdRequestor::NetHackQtExtCmdRequestor(QWidget *parent) :
      */
     bool by_column = true;
 
-    QVBoxLayoutbl = new QVBoxLayout(grid);
+    QVBoxLayout *bl = new QVBoxLayout(grid);
     bl->addSpacing(fm.height());
-    QGridLayoutgl = new QGridLayout();
+    QGridLayout *gl = new QGridLayout();
     bl->addLayout(gl);
     for (i = j = 0; extcmdlist[i].ef_txt; ++i) {
         if (interesting_command(i)) {
             QPushButton *pb = new QPushButton(extcmdlist[i].ef_txt, grid);
             pb->setMinimumSize(butw, pb->sizeHint().height());
+            // force the button to have fixed width or it can move around a
+            // pixel or two (tiny but visibly noticeable) when enableButtons()
+            // hides whole columns [see stretch comment below]
+            pb->setMaximumSize(pb->minimumSize());
             group->addButton(pb, i + 1);
-            if (by_column)
-                /* 0..R-1 down first column, R..2*R-1 down second column,...*/
-                gl->addWidget(pb, j % nrows, j / nrows);
-            else
-                /* 0..C-1 across first row, C..2*C-1 across second row, ... */
-                gl->addWidget(pb, j / ncols, j % ncols);
+            /*
+             * by_column:
+             *  0..R-1 down first column, R..2*R-1 down second column, ...
+             * otherwise:
+             *  0..C-1 across first row, C..2*C-1 across second row, ...
+             */
+            int row = by_column ? j % nrows : j / ncols;
+            int col = by_column ? j / nrows : j % ncols;
+            gl->addWidget(pb, row, col);
+            // these stretch settings prevent the grid from becoming very
+            // ugly when enableButtons() disables whole rows and/or columns
+            // as typed characters reduce the pool of possible matches
+            if (row == 0)
+                gl->setColumnStretch(col, 1);
+            if (col == 0)
+                gl->setRowStretch(row, 1);
+
             buttons.append(pb);
             ++j;
         }
@@ -116,23 +143,35 @@ void NetHackQtExtCmdRequestor::cancel()
     reject();
 }
 
+#define Ctrl(c) (0x1f & (c)) /* ASCII */
+// Note: we don't necessarily have access to a terminal to query
+// it for user's preferred kill character, so use hardcoded ^U.
+#define KILL_CHAR Ctrl('u')
+
 void NetHackQtExtCmdRequestor::keyPressEvent(QKeyEvent *event)
 {
-    QString text = event->text();
-    if (text == "\r" || text == "\n" || text == " " || text == "\033")
-    {
-       reject();
-    }
-    else if (text == "\b" || text == "\177")
-    {
-       QString promptstr = prompt->text();
+    QString promptstr = prompt->text();
+    uchar uc = keyValue(event);
+    if (!uc) {
+        // shift or control or meta, another character should be coming
+        QWidget::keyPressEvent(event);
+    } else if (uc == '\033' || uc == KILL_CHAR) {
+        // Escape when some response is already present kills that text
+        // but keeps prompting; escape when response is empty cancels.
+        // Kill gets rid of current text, if any, and always re-prompts.
+        if (uc == '\033' && promptstr == "#")
+            reject(); // cancel() if ESC used when string is empty
+        prompt->setText("#");
+        enableButtons();
+    } else if (uc == '\b' || uc == '\177') {
        if (promptstr != "#")
-           prompt->setText(promptstr.left(promptstr.size()-1));
+           prompt->setText(promptstr.left(promptstr.size() - 1));
         enableButtons();
-    }
-    else
-    {
-       QString promptstr = prompt->text() + text;
+  /*} else if (uc == '\r' || uc == '\n'; || uc  == ' ') {*/
+    } else if (uc < ' ' || uc > std::max('z', 'Z')) {
+       reject(); // done()
+    } else {
+       promptstr += QChar(uc); // event()->text()
        QString typedstr = promptstr.mid(1); // skip the '#'
        unsigned matches = 0;
        unsigned match = 0;
@@ -171,19 +210,20 @@ int NetHackQtExtCmdRequestor::get()
     return result()-1;
 }
 
-/*
- * FIXME:
- *  This looks terrible.  [Possibly a difference between initial
- *  implementation using Qt2 and the current Qt version?]
- */
 // Enable only buttons that match the current prompt string
 void NetHackQtExtCmdRequestor::enableButtons()
 {
     QString typedstr = prompt->text().mid(1); // skip the '#'
     std::size_t len = typedstr.size();
 
+    // This used to look really bad when whole rows became empty:  the
+    // grid shrank and the one line prompt area expanded to fill the
+    // vacated vertical space.  Hiding whole columns looked bad too,
+    // remaining buttons were widened to take the space.  Now the grid is
+    // forced to have fixed layout (via stretch settings in constructor).
     for (auto b = buttons.begin(); b != buttons.end(); ++b) {
-        (*b)->setVisible((*b)->text().left(len) == typedstr);
+        bool showit = ((*b)->text().left(len) == typedstr);
+        (*b)->setVisible(showit);
     }
 }