From: PatR Date: Mon, 30 Nov 2020 11:18:45 +0000 (-0800) Subject: Qt extended commands X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=de8337f402ea7a1b38d885e4965b3c1b7ae88af3;p=nethack Qt extended commands 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. --- diff --git a/win/Qt/qt_menu.cpp b/win/Qt/qt_menu.cpp index 731333d99..ab8b59ea7 100644 --- a/win/Qt/qt_menu.cpp +++ b/win/Qt/qt_menu.cpp @@ -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(); diff --git a/win/Qt/qt_menu.h b/win/Qt/qt_menu.h index e69265a4f..18be9e732 100644 --- a/win/Qt/qt_menu.h +++ b/win/Qt/qt_menu.h @@ -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) { } diff --git a/win/Qt/qt_xcmd.cpp b/win/Qt/qt_xcmd.cpp index a1a343610..ce81fbb44 100644 --- a/win/Qt/qt_xcmd.cpp +++ b/win/Qt/qt_xcmd.cpp @@ -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); - QPushButton* can = 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; - QVBoxLayout* bl = new QVBoxLayout(grid); + QVBoxLayout *bl = new QVBoxLayout(grid); bl->addSpacing(fm.height()); - QGridLayout* gl = 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); } }