Files
nethack/win/Qt/qt_yndlg.cpp
nhmall 02a48aa8cf split g into multiple structures
The consolidation of global variables from scattered source
files into decl.c and declared in decl.h was begun in 3.7.0.
Their placement in common files was done for centralized
initialization and potential re-initialization during a
"play again" scenario.

It wasn't really necessary for all of them to be housed in a
single huge structure to meet the "play again" requirement,
and the single huge structure has been a little unwieldy when
it comes to maintenance.

Following this commit, instead of one single extremely large structure
named 'g' to house all of the relocated global variables, they
are distributed into several ga through gz.

To make things easy for the developer, each variable is placed
into the struct corresponding to the starting letter of the variable.
That way, no lookup is required in order to know which struct houses
a particular variable, it is a simple match to the starting letter
for all the centralized global variables.

A global variable named 'amulets', would be found in ga.
    ga.amulets
     ^ ^
A global varable named 'move', would be found in gm.
    gm.moves
     ^ ^
A global variable named 'val_for_n_or_more' would be found in gv.
    gv.val_for_n_or_more
     ^ ^
A global variable named 'youmonst' would be found in gy.
    gy.youmonst
     ^ ^
2022-11-29 21:53:21 -05:00

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 alterate 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?")
|| (::gp.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_