Files
nethack/win/Qt/qt_xcmd.cpp
PatR 3e7283e8fd Qt fix for typing "#version"
The #version command is a leading substring of the #versionshort
command and for Qt, it couldn't be executed by typing, only via
mouse click or one of the Qt-specific menus.  #version<return>
or #version<space> now works for that.

The #versionshort command ought to be renamed to something else.
2020-12-02 16:11:53 -08:00

245 lines
8.1 KiB
C++

// Copyright (c) Warwick Allison, 1999.
// Qt4 conversion copyright (c) Ray Chason, 2012-2014.
// 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"
#include "func_tab.h"
}
#include "qt_pre.h"
#include <QtGui/QtGui>
#if QT_VERSION >= 0x050000
#include <QtWidgets/QtWidgets>
#endif
#include "qt_post.h"
#include "qt_xcmd.h"
#include "qt_xcmd.moc"
#include "qt_bind.h"
#include "qt_set.h"
#include "qt_str.h"
namespace nethack_qt_ {
extern uchar keyValue(QKeyEvent *key_event); // from qt_menu.cpp
// temporary
void centerOnMain(QWidget *);
// end temporary
static inline bool
interesting_command(unsigned indx)
{
return (!(extcmdlist[indx].flags & CMD_NOT_AVAILABLE)
/* 'wizard' is #undef'd above because Qt uses that token
so rely on its internals */
&& (flags.debug || !(extcmdlist[indx].flags & WIZMODECMD)));
}
NetHackQtExtCmdRequestor::NetHackQtExtCmdRequestor(QWidget *parent) :
QDialog(parent)
{
QVBoxLayout *l = new QVBoxLayout(this);
QPushButton *can = new QPushButton("Cancel", this);
can->setDefault(true);
can->setMinimumSize(can->sizeHint());
l->addWidget(can);
prompt = new QLabel("#", this);
l->addWidget(prompt);
QButtonGroup *group=new QButtonGroup(this);
QGroupBox *grid=new QGroupBox("Extended commands",this);
l->addWidget(grid);
unsigned i, j, ncmds = 0;
int butw = 50;
QFontMetrics fm = fontMetrics();
for (i = 0; extcmdlist[i].ef_txt; ++i) {
if (interesting_command(i)) {
++ncmds;
butw = std::max(butw, 30 + fm.width(extcmdlist[i].ef_txt));
}
}
/* 'ncols' should be calculated to fit (or enable a vertical scrollbar
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 buttons were chopped off at the bottom of the grid */
unsigned ncols = !flags.debug ? 6 : 8,
nrows = (ncmds + ncols - 1) / ncols;
/*
* Choose grid layout. This ought to selected via a button that can
* be used to toggle the setting back and forth.
*
* by row vs by column
* a b a e
* c d b f
* e f c g
* g d
*
* Prior to 3.7, it was always by-row, but by-column is more natural
* for an alphabetized list.
*/
bool by_column = true;
QVBoxLayout *bl = new QVBoxLayout(grid);
bl->addSpacing(fm.height());
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);
/*
* 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;
}
}
group->addButton(can, 0);
connect(group,SIGNAL(buttonPressed(int)),this,SLOT(done(int)));
bl->activate();
l->activate();
resize(1,1);
}
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 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));
enableButtons();
} else if ((uc < ' ' && !(uc == '\n' || uc == '\r'))
|| uc > std::max('z', 'Z')) {
reject(); // done()
} else {
// <return> is necessary if one command is a leading substring
// of another and superfluous otherwise
boolean checkexact = (uc == '\n' || uc == '\r' || uc == ' ');
if (!checkexact)
promptstr += QChar(uc); // event()->text()
QString typedstr = promptstr.mid(1); // skip the '#'
unsigned matches = 0;
unsigned matchindx = 0;
for (unsigned i=0; extcmdlist[i].ef_txt; i++) {
if (!interesting_command(i))
continue;
QString cmdtxt = QString(extcmdlist[i].ef_txt);
if (cmdtxt.startsWith(typedstr)) {
if (checkexact) {
if (cmdtxt == typedstr) {
matchindx = i;
matches = 1;
break;
}
} else {
if (++matches >= 2)
break;
matchindx = i;
}
}
}
if (matches == 1)
done(matchindx + 1);
else if (checkexact)
reject();
else if (matches >= 2)
prompt->setText(promptstr);
enableButtons();
}
}
int NetHackQtExtCmdRequestor::get()
{
const int none = -10;
resize(1,1); // pack
centerOnMain(this);
// Add any keys presently buffered to the prompt
setResult(none);
while (NetHackQtBind::qt_kbhit() && result() == none) {
int ch = NetHackQtBind::qt_nhgetch();
QKeyEvent event(QEvent::KeyPress, 0, Qt::NoModifier, QChar(ch));
keyPressEvent(&event);
}
if (result() == none)
exec();
return result()-1;
}
// 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) {
bool showit = ((*b)->text().left(len) == typedstr);
(*b)->setVisible(showit);
}
}
} // namespace nethack_qt_