430 lines
14 KiB
C++
430 lines
14 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_yndlg.cpp -- yes/no dialog
|
|
|
|
extern "C" {
|
|
#include "hack.h"
|
|
}
|
|
|
|
#include "qt_pre.h"
|
|
#include <QtGui/QtGui>
|
|
#if QT_VERSION >= 0x050000
|
|
#include <QtWidgets/QtWidgets>
|
|
#endif
|
|
#include "qt_post.h"
|
|
#include "qt_yndlg.h"
|
|
#include "qt_yndlg.moc"
|
|
#include "qt_key.h" // for keyValue()
|
|
#include "qt_str.h"
|
|
|
|
// temporary
|
|
extern int qt_compact_mode;
|
|
// end temporary
|
|
|
|
namespace nethack_qt_ {
|
|
|
|
static const char lrq[] = "lr\033LRq";
|
|
char altchoices[BUFSZ + 12];
|
|
|
|
// temporary
|
|
void centerOnMain(QWidget *);
|
|
// end temporary
|
|
|
|
NetHackQtYnDialog::NetHackQtYnDialog(QWidget *parent, const QString &q,
|
|
const char *ch, char df) :
|
|
QDialog(parent),
|
|
question(q), choices(ch), def(df),
|
|
keypress('\033'),
|
|
allow_count(false),
|
|
le((QLineEdit *) NULL),
|
|
y_btn((QPushButton *) NULL)
|
|
{
|
|
setWindowTitle("NetHack: Question");
|
|
|
|
// plain prompt doesn't show any room for an answer (answer won't be
|
|
// echoed but the fact that a prompt is pending and accepts typed
|
|
// input as an alternative to mouse click seems clearer when there
|
|
// is some space available to accept it)
|
|
if (!question.endsWith(" ") && !question.endsWith("_"))
|
|
question += " _"; // an underlined space would be better
|
|
|
|
if (choices) {
|
|
// special handling for wearing rings; prompt asks "right or left?"
|
|
// but side-by-side buttons look better with [left][right] instead
|
|
// (assumes that we're using left to right layout)
|
|
if (!strcmp(choices, "rl")) {
|
|
choices = lrq;
|
|
if (!def)
|
|
def = 'r';
|
|
|
|
// if count is allowed, explicitly add the digits as valid
|
|
} else if (!strncmp(choices, "yn#", (size_t) 3)) {
|
|
::yn_number = 0L;
|
|
allow_count = true;
|
|
|
|
if (!strchr(choices, '9')) {
|
|
copynchars(altchoices, choices, BUFSZ - 1);
|
|
// duplicate # is intentional; explicitly separates \... and 0
|
|
choices = strcat(altchoices, "\033#0123456789");
|
|
}
|
|
}
|
|
}
|
|
alt_answer[0] = alt_result[0] = '\0';
|
|
}
|
|
|
|
char NetHackQtYnDialog::Exec()
|
|
{
|
|
QString ch(QString::fromLatin1(choices));
|
|
// int ch_per_line=6;
|
|
QString qlabel;
|
|
QString enable;
|
|
if ( qt_compact_mode && !choices ) {
|
|
ch = "";
|
|
// expand choices from prompt
|
|
// ##### why isn't choices set properly???
|
|
int c = question.indexOf(QChar('['));
|
|
qlabel = QString(question).left(c);
|
|
if ( c >= 0 ) {
|
|
c++;
|
|
if ( question[c] == '-' )
|
|
ch.append(question[c++]);
|
|
unsigned from=0;
|
|
while (c < question.size()
|
|
&& question[c] != ']' && question[c] != ' ') {
|
|
if ( question[c] == '-' ) {
|
|
from = question[c - 1].cell();
|
|
} else if ( from != 0 ) {
|
|
for (unsigned f=from+1; QChar(f)<=question[c]; f++)
|
|
ch.append(QChar(f));
|
|
from = 0;
|
|
} else {
|
|
ch.append(question[c]);
|
|
from = 0;
|
|
}
|
|
c++;
|
|
}
|
|
if ( question[c] == ' ' ) {
|
|
while ( c < question.size() && question[c] != ']' ) {
|
|
if ( question[c] == '*' || question[c] == '?' )
|
|
ch.append(question[c]);
|
|
c++;
|
|
}
|
|
}
|
|
}
|
|
if ( question.indexOf("what direction") >= 0 ) {
|
|
// We replace this regardless, since sometimes you get choices.
|
|
const char* d = gc.Cmd.dirchars;
|
|
enable=ch;
|
|
ch="";
|
|
ch.append(d[1]);
|
|
ch.append(d[2]);
|
|
ch.append(d[3]);
|
|
ch.append(d[0]);
|
|
ch.append('.');
|
|
ch.append(d[4]);
|
|
ch.append(d[7]);
|
|
ch.append(d[6]);
|
|
ch.append(d[5]);
|
|
ch.append(d[8]);
|
|
ch.append(d[9]);
|
|
// ch_per_line = 3;
|
|
def = ' ';
|
|
} else {
|
|
// Hmm... they'll have to use a virtual keyboard
|
|
}
|
|
} else {
|
|
ch = QString::fromLatin1(choices);
|
|
qlabel = question.replace(QChar(0x200B), QString(""));
|
|
}
|
|
if (!ch.isNull()) {
|
|
QVBoxLayout *vb = new QVBoxLayout;
|
|
bool bigq = (qlabel.length() > (qt_compact_mode ? 40 : 60));
|
|
if (bigq) {
|
|
QLabel *q = new QLabel(qlabel, this);
|
|
q->setAlignment(Qt::AlignLeft);
|
|
q->setWordWrap(true);
|
|
q->setMargin(4);
|
|
vb->addWidget(q);
|
|
}
|
|
QGroupBox *group = new QGroupBox(bigq ? QString() : qlabel, this);
|
|
vb->addWidget(group);
|
|
QHBoxLayout *groupbox = new QHBoxLayout();
|
|
group->setLayout(groupbox);
|
|
QButtonGroup *bgroup = new QButtonGroup(group);
|
|
|
|
int nchoices=ch.length();
|
|
// note: is_ynaq covers nyaq too because the choices string is
|
|
// "ynaq" for both; only the default differs; likewise for nyNaq
|
|
bool is_ynaq = (ch == QString("ynaq") // [Yes ][ No ][All ][Stop]
|
|
|| ch == QString("yn#aq")
|
|
|| ch == altchoices), // alternate "yn#aq"
|
|
is_ynq = (ch == QString("ynq")), // [ Yes ][ No ][Cancel]
|
|
is_yn = (ch == QString("yn")), // [Yes ][ No ]
|
|
is_lr = (ch == QString(lrq)); // [ Left ][Right ]
|
|
|
|
#if 0
|
|
const int margin=8;
|
|
const int gutter=8;
|
|
const int extra=fontMetrics().height(); // Extra for group
|
|
int x=margin, y=extra+margin;
|
|
#endif
|
|
int butheight = fontMetrics().height() * 2 + 5,
|
|
butwidth = (butheight - 5) * ((is_ynq || is_lr) ? 3
|
|
: (is_ynaq || is_yn) ? 2 : 1) + 5;
|
|
if (butwidth == butheight) { // square, enough room for C or ^C
|
|
// some characters will be labelled by name rather than by
|
|
// keystroke so will need wider buttons
|
|
for (int i = 0; i < nchoices; ++i) {
|
|
if (ch[i] == '\033')
|
|
break; // ESC and anything after are hidden
|
|
if (ch[i] == ' ' || ch[i] == '\n' || ch[i] == '\r') {
|
|
butwidth = (butheight - 5) * 2 + 5;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QPushButton *button;
|
|
for (int i = 0; i < nchoices; ++i) {
|
|
bool making_y = false;
|
|
if (ch[i] == '\033')
|
|
break; // ESC and anything after are hidden
|
|
if (ch[i] == '#' && allow_count)
|
|
continue; // don't show a button for '#'; has Count box instead
|
|
QString button_name = QString(visctrl((char) ch[i].cell()));
|
|
if (is_yn || is_ynq || is_ynaq || is_lr) {
|
|
// FIXME: a better way to recognize which labels should
|
|
// use alternate text is needed
|
|
switch (ch[i].cell()) {
|
|
case 'y':
|
|
button_name = "Yes";
|
|
making_y = true;
|
|
break;
|
|
case 'n':
|
|
button_name = "No";
|
|
break;
|
|
case 'a':
|
|
// the display of vanquished monsters uses "ynaq" for
|
|
// convenience, where 'a' requests a sort-by menu;
|
|
// show "sort" instead of "all" and allow player to
|
|
// type either 'a' or 's' when not clicking on button
|
|
if (question.contains(QString("vanquished?")))
|
|
button_name = "Sort", AltChoice('s', 'a');
|
|
else
|
|
button_name = "All";
|
|
break;
|
|
case 'q':
|
|
// most 'q' replies are actually for "cancel" but
|
|
// for "ynaq" (where "all" is a choice) it's "stop"
|
|
// and for end of game disclosure it really is "quit"
|
|
if (question.left(10) == QString("Dump core?")
|
|
|| (::program_state.gameover
|
|
&& question.left(11) == QString("Do you want")))
|
|
button_name = "Quit";
|
|
else if (is_ynaq)
|
|
button_name = "Stop", AltChoice('s', 'q');
|
|
else
|
|
button_name = "Cancel", AltChoice('c', 'q');
|
|
break;
|
|
case 'l':
|
|
button_name = "Left";
|
|
break;
|
|
case 'r':
|
|
button_name = "Right";
|
|
break;
|
|
}
|
|
} else {
|
|
// special characters usually aren't listed among choices
|
|
// but if they are, label the buttons for them with sensible
|
|
// names; we want to avoid "^J" and "^M" for \n and \r;
|
|
// <Enter> and <Return> are equivalent to each other but
|
|
// labelling \n as newline or line-feed seems confusing;
|
|
switch (ch[i].cell()) {
|
|
case ' ':
|
|
button_name = "Spc";
|
|
break;
|
|
case '\n':
|
|
button_name = "Ent";
|
|
break;
|
|
case '\r':
|
|
button_name = "Ret";
|
|
break;
|
|
case '\033': // won't happen; ESC is hidden
|
|
button_name = "Esc";
|
|
break;
|
|
case '&':
|
|
// ampersand is used as a hidden quote char to flag
|
|
// next character as a keyboard shortcut associated
|
|
// with the current action--that's inappropriate here;
|
|
// two consecutive ampersands are needed to display
|
|
// one in a button label; first check whether caller
|
|
// has already done that, skip this one if so
|
|
if (i > 0 && ch[i - 1].cell() == '&')
|
|
continue; // next i
|
|
button_name = "&&";
|
|
break;
|
|
}
|
|
}
|
|
button=new QPushButton(button_name);
|
|
if (making_y && allow_count)
|
|
y_btn = button; // to change default in keyPressEvent()
|
|
if (!enable.isNull()) {
|
|
if (!enable.contains(ch[i]))
|
|
button->setEnabled(false);
|
|
}
|
|
button->setFixedSize(butwidth, butheight);
|
|
if (ch[i] == def)
|
|
button->setDefault(true);
|
|
#if 0
|
|
// 'x' and 'y' don't seem to actually used anywhere
|
|
// and limit of 10 buttons per row isn't enforced
|
|
if (i % 10 == 9) {
|
|
// last in row
|
|
x = margin;
|
|
y += butheight + gutter;
|
|
} else {
|
|
x += butwidth + gutter;
|
|
}
|
|
#endif
|
|
groupbox->addWidget(button);
|
|
bgroup->addButton(button, i);
|
|
}
|
|
|
|
connect(bgroup, SIGNAL(buttonClicked(int)), this, SLOT(doneItem(int)));
|
|
|
|
QLabel *lb = 0;
|
|
if (allow_count) {
|
|
// insert Count widget in front of [n], between [y] and [n][a][q]
|
|
lb = new QLabel("Count:");
|
|
groupbox->insertWidget(1, lb); // [y] button is item #0, [n] is #1
|
|
le = new QLineEdit();
|
|
groupbox->insertWidget(2, le); // [n] became #2, Count label is #1
|
|
le->setPlaceholderText(QString("#")); // grayed out
|
|
}
|
|
// add an invisible right-most field to left justify the buttons
|
|
groupbox->addStretch(80);
|
|
|
|
setLayout(vb);
|
|
adjustSize();
|
|
centerOnMain(this);
|
|
show();
|
|
char choice=0;
|
|
char ch_esc=0;
|
|
for (int i = 0; i < ch.length(); ++i) {
|
|
if (ch[i].cell() == 'q')
|
|
ch_esc = 'q';
|
|
else if (!ch_esc && ch[i].cell() == 'n')
|
|
ch_esc = 'n';
|
|
}
|
|
|
|
//
|
|
// When a count is allowed, clicking on the count widget then
|
|
// typing in digits followed by <return> is 'normal' operation.
|
|
// However, typing a digit without clicking first will set focus
|
|
// to the count widget with that typed digit preloaded.
|
|
//
|
|
exec();
|
|
int res = result();
|
|
if (res == 0) {
|
|
choice = is_lr ? '\033' : ch_esc ? ch_esc : def ? def : ' ';
|
|
} else if (res == 1) {
|
|
if (keypress)
|
|
choice = keypress;
|
|
else
|
|
choice = def ? def : ch_esc ? ch_esc : ' ';
|
|
} else if (res >= 1000) {
|
|
choice = (char) ch[res - 1000].cell();
|
|
}
|
|
|
|
// non-Null 'le' implies 'allow_count'; having a grayed-out '#'
|
|
// present in the QLineEdit widget doesn't affect its isEmpty() test
|
|
if (le && !le->text().isEmpty()) {
|
|
QString text(le->text());
|
|
if (text.at(0) == QChar('#'))
|
|
text = text.mid(1); // rest of string past [0]
|
|
::yn_number = text.toLong();
|
|
choice = '#';
|
|
}
|
|
keypress = choice;
|
|
|
|
} else {
|
|
QLabel label(qlabel,this);
|
|
QPushButton cancel("Dismiss",this);
|
|
#if __cplusplus >= 202002L
|
|
label.setFrameStyle(static_cast<int>(QFrame::Box)
|
|
| static_cast<int>(QFrame::Sunken));
|
|
#else
|
|
label.setFrameStyle(QFrame::Box|QFrame::Sunken);
|
|
#endif
|
|
label.setAlignment(Qt::AlignCenter);
|
|
label.resize(fontMetrics().QFM_WIDTH(qlabel)+60,30+fontMetrics().height());
|
|
cancel.move(width()/2-cancel.width()/2,label.geometry().bottom()+8);
|
|
connect(&cancel,SIGNAL(clicked()),this,SLOT(reject()));
|
|
centerOnMain(this);
|
|
setResult(-1);
|
|
show();
|
|
keypress = '\033';
|
|
exec();
|
|
}
|
|
return keypress;
|
|
}
|
|
|
|
void NetHackQtYnDialog::AltChoice(char ans, char res)
|
|
{
|
|
if (ans && !strchr(alt_answer, ans)) {
|
|
(void) strkitten(alt_answer, ans);
|
|
(void) strkitten(alt_result, res);
|
|
}
|
|
}
|
|
|
|
void NetHackQtYnDialog::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
keypress = keyValue(event);
|
|
if (!keypress)
|
|
return;
|
|
|
|
char *p = NULL;
|
|
if (*alt_answer && (p = strchr(alt_answer, keypress)) != 0)
|
|
keypress = alt_result[p - alt_answer];
|
|
|
|
if (!choices || !*choices || !keypress) {
|
|
this->done(1);
|
|
|
|
} else {
|
|
int where = QString::fromLatin1(choices).indexOf(QChar(keypress));
|
|
|
|
if (allow_count && strchr("#0123456789", keypress)) {
|
|
if (keypress == '#') {
|
|
// 0 will be preselected; typing anything replaces it
|
|
le->setText(QString("0"));
|
|
le->home(true);
|
|
} else {
|
|
// digit will not be preselected; typing another appends
|
|
le->setText(QChar(keypress));
|
|
le->end(false);
|
|
}
|
|
// (don't know whether this actually does anything useful)
|
|
le->setAttribute(Qt::WA_KeyboardFocusChange, true);
|
|
// this is definitely useful...
|
|
le->setFocus(Qt::ActiveWindowFocusReason);
|
|
// change default button from 'n' to 'y'
|
|
if (y_btn)
|
|
y_btn->setDefault(true);
|
|
} else if (where != -1) {
|
|
this->done(where + 1000);
|
|
|
|
} else {
|
|
QDialog::keyPressEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetHackQtYnDialog::doneItem(int i)
|
|
{
|
|
this->done(i + 1000);
|
|
}
|
|
|
|
} // namespace nethack_qt_
|