The g? structs had a mix of variables that were written to
the savefile, and those that were not.
For better clarity and to distinguish those that end up in
the savefile, relocate some g? variables that get written
directly to the savefile into different structs.
This updates EDITLEVEL, although technically it probably
didn't need to, since savefile contents are not changing.
Details:
gb.bases -> svb.bases
gb.bbubbles -> svb.bbubbles
gb.branches -> svb.branches
gc.context -> svc.context
gd.disco -> svd.disco
gd.dndest -> svd.dndest
gd.doors -> svd.doors
gd.doors_alloc -> svd.doors_alloc
gd.dungeon_topology -> svd.dungeon_topology
gd.dungeons -> svd.dungeons
ge.exclusion_zones -> sve.exclusion_zones
gh.hackpid -> svh.hackpid
gi.inv_pos -> svi.inv_pos
gk.killer -> svk.killer
gl.lastseentyp -> svl.lastseentyp
gl.level -> svl.level
gl.level_info -> svl.level_info
gm.mapseenchn -> svm.mapseenchn
gm.moves -> svm.moves
gm.mvitals -> svm.mvitals
gn.n_dgns -> svn.n_dgns
gn.n_regions -> svn.n_regions
gn.nroom -> svn.nroom
go.oracle_cnt -> svo.oracle_cnt
gp.pl_character -> svp.pl_character
gp.pl_fruit -> svp.pl_fruit
gp.plname -> svp.plname
gp.program_state -> svp.program_state
gq.quest_status -> svq.quest_status
gr.rooms -> svr.rooms
gs.sp_levchn -> svs.sp_levchn
gs.spl_book -> svs.spl_book
gt.timer_id -> svt.timer_id
gt.tune -> svt.tune
gu.updest -> svu.updest
gx.xmax -> svx.xmax
gx.xmin -> svx.xmin
gy.ymax -> svy.ymax
gy.ymin -> svy.ymin
Related note:
There are some pointer variables that are heads of chains that were not
moved from 'g?' to 'sv?', because they are not actually written to the
savefile directly, but the objects/monst/trap/lightsource/timer in the
chains they point to are. That can be changed, if desired.
Examples: gi.invent, gm.migrating_objs, gb.billobjs, gm.migrating_mons,
gf.ftrap, gl.light_base, gt.timer_base
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?")
|
|
|| (::svp.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_
|