QString question(QString::fromLatin1(question_));
QString message;
char yn_esc_map='\033';
+ int result = -1;
if (choices) {
// anything beyond <esc> is hidden>
size_t cb = choicebuf.indexOf('\033');
choicebuf = choicebuf.mid(0U, cb);
message = QString("%1 [%2] ").arg(question, choicebuf);
- if (def) message += QString("(%1) ").arg(QChar(def));
+ if (def)
+ message += QString("(%1) ").arg(QChar(def));
// escape maps to 'q' or 'n' or default, in that order
- yn_esc_map = (strchr(choices, 'q') ? 'q' :
- (strchr(choices, 'n') ? 'n' : def));
+ yn_esc_map = strchr(choices, 'q') ? 'q'
+ : strchr(choices, 'n') ? 'n'
+ : def;
} else {
message = question;
}
- if (qt_settings->ynInMessages() && WIN_MESSAGE != WIN_ERR) {
+ if (
+ /*
+ * The 'Settings' dialog doesn't present prompting-in-message-window
+ * as a candidate for customization but core supports 'popup_dialog'
+ * option so let player use that instead.
+ */
+#if 0
+ qt_settings->ynInMessages()
+#else
+ !::iflags.wc_popup_dialog
+#endif
+ && WIN_MESSAGE != WIN_ERR) {
// Similar to X11 windowport `slow' feature.
- int result = -1;
- char cbuf[40];
+ char cbuf[20];
cbuf[0] = '\0';
-#ifdef USE_POPUPS
- if (choices) {
- if (!strcmp(choices, "ynq"))
- result = QMessageBox::information (NetHackQtBind::mainWidget(),
- "NetHack", question,
- "&Yes", "&No", "&Quit", 0, 2);
- else if (!strcmp(choices, "yn"))
- result = QMessageBox::information(NetHackQtBind::mainWidget(),
- "NetHack", question,
- "&Yes", "&No", 0, 1);
- else if (!strcmp(choices, "rl"))
- result = QMessageBox::information(NetHackQtBind::mainWidget(),
- "NetHack", question,
- "&Right", "&Left", 0, 1);
-
- if (result >= 0 && result < strlen(choices)) {
- char yn_resp = choices[result];
- message += QString(" %1").arg(yn_resp);
- result = yn_resp;
- }
- }
-#endif
-
+ // add the prompt to the messsage window
NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
while (result < 0) {
}
} else {
result=ch;
- Strcpy(cbuf, (ch == ' ') ? "SPC" : visctrl(ch));
+ Strcpy(cbuf, visctrl(ch));
}
}
- // if answer was supplied via popup, it will already be appended
- // to the prompt, so included above, and cbuf[] will be empty
+ // update the prompt message line to include the response
if (cbuf[0]) {
- NetHackQtWindow *window = id_to_window[WIN_MESSAGE];
- NetHackQtMessageWindow *mesgwin
- = static_cast <NetHackQtMessageWindow *> (window);
- mesgwin->AddToStr(cbuf);
- }
+ if (!strcmp(cbuf, " "))
+ Strcpy(cbuf, "SPC");
- NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
+ NetHackQtMessageWindow *mesgwin = main->GetMessageWindow();
+ if (mesgwin)
+ mesgwin->AddToStr(cbuf);
+ }
- return result;
} else {
- NetHackQtYnDialog dialog(mainWidget(),question,choices,def);
- char ret = dialog.Exec();
- if (!(ret == '\0' || ret == '\033') && choices)
- message += QString(" %1").arg(ret);
- else if (def)
- message += QString(" %1").arg(def);
+ // use a popup dialog box
+ NetHackQtYnDialog dialog(main, question, choices, def);
+ char ret = dialog.Exec();
+ if (ret == 0) {
+ ret = '\033';
+ }
+ // discard any input that YnDialog() might have left pending
+ keybuffer.Drain();
+
+ // combine the prompt and result
+ char cbuf[40];
+ Strcpy(cbuf, (ret == '\033') ? "ESC"
+ : (ret == ' ') ? "SPC"
+ : visctrl(ret));
+ if (ret == '#' && choices && !strncmp(choices, "yn#", (size_t) 3))
+ Sprintf(eos(cbuf), " %ld", ::yn_number);
+ message += QString(" %1").arg(cbuf);
+
+ // add the prompt with appended response to the messsage window
NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
- return ret;
+ result = ret;
}
+
+ // unhighlight the prompt; does not erase the multi-line message window
+ NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
+
+ return (char) result;
}
void NetHackQtBind::qt_getlin(const char *prompt, char *line)
{
NetHackQtStringRequestor requestor(mainWidget(),prompt);
if (!requestor.Get(line)) {
- line[0]=0;
+ Strcpy(line, "\033");
+ // discard any input that Get() might have left pending
+ keybuffer.Drain();
}
+
+ // add the prompt with appended response to the messsage window
+ char buf[BUFSZ + 20], *q; /* +20: plenty of extra room for visctrl() */
+ copynchars(buf, prompt, BUFSZ - 1);
+ q = eos(buf);
+ *q++ = ' '; /* guaranteed to fit; temporary lack of terminator is ok */
+
+ if (line[0] == '\033') {
+ Strcpy(q, "ESC");
+ } else if (line[0] == ' ' && !line[1]) {
+ Strcpy(q, "SPC");
+ } else {
+ /* buf[] has more than enough room to hold one extra visctrl()
+ in case q is at the last viable slot and *p yields "M-^c" */
+ for (char *p = line; *p && q < &buf[BUFSZ - 1]; ++p, q = eos(q))
+ Strcpy(q, visctrl(*p));
+ }
+ if (q > &buf[BUFSZ - 1])
+ q = &buf[BUFSZ - 1];
+ *q = '\0';
+
+ NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, buf);
+ // unhighlight the prompt; does not erase the multi-line message window
+ NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
}
int NetHackQtBind::qt_get_ext_cmd()
window->UseRIP(how, when);
}
-char * NetHackQtBind::qt_getmsghistory(BOOLEAN_P init)
+char *NetHackQtBind::qt_getmsghistory(BOOLEAN_P init)
{
- NetHackQtMessageWindow* window = main->GetMessageWindow();
+ NetHackQtMessageWindow *window = main->GetMessageWindow();
if (window)
- return (char *)window->GetStr(init);
+ return (char *) window->GetStr((bool) init);
return NULL;
}
void NetHackQtBind::qt_putmsghistory(const char *msg, BOOLEAN_P is_restoring)
{
- NetHackQtMessageWindow* window = main->GetMessageWindow();
+ NetHackQtMessageWindow *window = main->GetMessageWindow();
if (!window)
return;
int i = 0;
const char *str;
- while ((str = window->GetStr((i == 0)))) {
+ while ((str = window->GetStr((bool) (i == 0))) != 0) {
msgs_strings->append(str);
i++;
}
#endif
} else if (msgs_saved) {
/* restore strings */
- int i;
- for (i = 0; i < msgs_strings->size(); i++) {
- window->PutStr(ATR_NONE, msgs_strings->at((i)));
+ for (int i = 0; i < msgs_strings->size(); ++i) {
+ const QString &nxtmsg = msgs_strings->at(i);
+ window->PutStr(ATR_NONE, nxtmsg);
#ifdef DUMPLOG
- dumplogmsg(msgs_strings->at(i).toLatin1().constData());
+ dumplogmsg(nxtmsg.toLatin1().constData());
#endif
}
delete msgs_strings;
WC_COLOR | WC_HILITE_PET
| WC_ASCII_MAP | WC_TILED_MAP
| WC_FONT_MAP | WC_TILE_FILE | WC_TILE_WIDTH | WC_TILE_HEIGHT
+ | WC_POPUP_DIALOG
| WC_PLAYER_SELECTION | WC_SPLASH_SCREEN,
0L,
- {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
+ {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
nethack_qt_::NetHackQtBind::qt_init_nhwindows,
nethack_qt_::NetHackQtBind::qt_player_selection,
nethack_qt_::NetHackQtBind::qt_askname,
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) :
+NetHackQtYnDialog::NetHackQtYnDialog(QWidget *parent, const QString &q,
+ const char *ch, char df) :
QDialog(parent),
question(q), choices(ch), def(df),
keypress('\033')
{
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
+ 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;
+
+ if (!strchr(choices, '9')) {
+ copynchars(altchoices, choices, BUFSZ - 1);
+ // duplicate # is intentional; explicitly separates \... and 0
+ choices = strcat(altchoices, "\033#0123456789");
+ }
+ }
+ }
}
char NetHackQtYnDialog::Exec()
if ( question[c] == '-' )
ch.append(question[c++]);
unsigned from=0;
- while ( c < question.size() && question[c] != ']' && question[c] != ' ' ) {
+ while (c < question.size()
+ && question[c] != ']' && question[c] != ' ') {
if ( question[c] == '-' ) {
- from = question[c-1].unicode();
+ from = question[c - 1].cell();
} else if ( from != 0 ) {
for (unsigned f=from+1; f<=question[c]; f++)
ch.append(QChar(f));
}
if (!ch.isNull()) {
QVBoxLayout *vb = new QVBoxLayout;
- bool bigq = qlabel.length()>40;
- if ( bigq ) {
- QLabel* q = new QLabel(qlabel,this);
+ 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);
QButtonGroup *bgroup = new QButtonGroup(group);
int nchoices=ch.length();
-
- bool allow_count=ch.contains('#');
- QString yn = "yn", ynq = "ynq";
- bool is_ynq = ch == yn || ch == ynq;
+ bool allow_count = (ch.left(3) == QString("yn#")),
+ is_ynq = (ch == QString("ynq")), // [ Yes ][ No ][Cancel]
+ is_yn = (ch == QString("yn")), // [Yes ][ No ]
+ is_lr = (ch == QString(lrq)); // [ Left ][Right ]
const int margin=8;
const int gutter=8;
const int extra=fontMetrics().height(); // Extra for group
int x=margin, y=extra+margin;
- int butsize=fontMetrics().height()*2+5;
-
- QPushButton* button;
- for (int i=0; i<nchoices && ch[i]!='\033'; i++) {
- QString button_name = QString(ch[i]);
- if (is_ynq) {
- if (button_name == ynq.mid(0, 1)) {
- button_name = "Yes";
- } else if (button_name == ynq.mid(1, 1)) {
- button_name = "No";
- } else if (button_name == ynq.mid(2, 1)) {
- button_name = "Cancel";
- }
- }
- button=new QPushButton(button_name);
- if ( !enable.isNull() ) {
- if ( !enable.contains(ch[i]) )
- button->setEnabled(false);
- }
- button->setFixedSize(butsize,butsize); // Square
- if (ch[i]==def) button->setDefault(true);
- if (i%10==9) {
- // last in row
- x=margin;
- y+=butsize+gutter;
- } else {
- x+=butsize+gutter;
- }
+ int butheight = fontMetrics().height() * 2 + 5,
+ butwidth = (butheight - 5)
+ * ((is_ynq || is_lr) ? 3 : is_yn ? 2 : 1) + 5;
+
+ QPushButton *button;
+ for (int i = 0; i < nchoices; ++i) {
+ 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_lr) {
+ switch (ch[i].cell()) {
+ case 'y':
+ button_name = "Yes";
+ break;
+ case 'n':
+ button_name = "No";
+ break;
+ case 'q':
+ // FIXME: sometimes the 'q' choice is ''cancel current
+ // action'' but other times it is actually 'quit'.
+ if (question.left(10) == QString("Dump core?"))
+ button_name = "Quit";
+ else
+ button_name = "Cancel";
+ break;
+ case 'l':
+ button_name = "Left";
+ break;
+ case 'r':
+ button_name = "Right";
+ break;
+ }
+ }
+ button=new QPushButton(button_name);
+ if (!enable.isNull()) {
+ if (!enable.contains(ch[i]))
+ button->setEnabled(false);
+ }
+ button->setFixedSize(butwidth, butheight);
+ if (ch[i] == def)
+ button->setDefault(true);
+ // '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;
+ }
groupbox->addWidget(button);
bgroup->addButton(button, i);
}
- connect(bgroup,SIGNAL(buttonClicked(int)),this,SLOT(doneItem(int)));
-
- QLabel* lb=0;
- QLineEdit* le=0;
+ connect(bgroup, SIGNAL(buttonClicked(int)), this, SLOT(doneItem(int)));
- if (allow_count) {
- QHBoxLayout *hb = new QHBoxLayout(this);
- lb=new QLabel("Count: ");
- hb->addWidget(lb);
- le=new QLineEdit();
- hb->addWidget(le);
- vb->addLayout(hb);
+ QLabel *lb = 0;
+ QLineEdit *le = 0;
+ if (allow_count) {
+ // put the Count widget in between [y] and [n][a][q]
+ lb = new QLabel("Count:");
+ groupbox->insertWidget(1, lb); // [n] button is item #1
+ le = new QLineEdit();
+ groupbox->insertWidget(2, le); // [n] became #2, Count label #1
}
+ // add an invisible right-most field to left justify the buttons
+ groupbox->addStretch(80);
setLayout(vb);
adjustSize();
show();
char choice=0;
char ch_esc=0;
- for (uint i=0; i< (uint) ch.length(); i++) {
- if (ch[i].unicode()=='q') ch_esc='q';
- else if (!ch_esc && ch[i].unicode()=='n') ch_esc='n';
- }
- exec();
- if ( result() == 0) {
- choice = ch_esc ? ch_esc : def ? def : ' ';
- } else if ( result() == 1 ) {
- choice = def ? def : ch_esc ? ch_esc : ' ';
- } else if ( result() >= 1000 ) {
- choice = ch[result() - 1000].unicode();
- }
- if (allow_count && !le->text().isEmpty()) {
- yn_number=le->text().toInt();
- choice='#';
+ 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';
}
- return choice;
+
+ //
+ // 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.
+ // FIXME: Unfortunately, it will also be selected, so typing
+ // another digit replaces it instead of being the next digit in
+ // a multiple-digit number.
+ //
+ // Theoretically typing '#' does this to, with a 0 preloaded
+ // and intentionally selected, but the KeyPress bug (below) of
+ // treating <shift> as a complete response prevents use of
+ // shift+3 from being used to generate '#'.
+ //
+ bool retry; // for digit + re-activate widget + rest of number
+ do {
+ retry = false; // might have a second pass (but not a third)
+ exec();
+ int res = result();
+ if (res == 0) {
+ choice = is_lr ? '\033' : ch_esc ? ch_esc : def ? def : ' ';
+ } else if (res == 1) {
+ choice = def ? def : ch_esc ? ch_esc : ' ';
+ } else if (res >= 1000) {
+ choice = (char) ch[res - 1000].cell();
+
+ if (allow_count && strchr("#0123456789", choice)) {
+ if (choice == '#') {
+ // 0 will be preselected; typing anything replaces it
+ le->insert(QString("0"));
+ } else {
+ le->insert(QString(choice));
+ //
+ // FIXME: despite the documentation claiming that
+ // 'false' cancels any selection, the digit always
+ // starts out selected (from running exec() again?)
+ // so typing the next digit replaces it instead of
+ // being appended to it unless the player uses
+ // right-arrow to move the cursor.
+ //
+ le->end(false);
+ }
+ // (don't know whether this actually does anything useful)
+ le->setAttribute(Qt::WA_KeyboardFocusChange, true);
+ le->setFocus(Qt::ActiveWindowFocusReason);
+ retry = true;
+ }
+ }
+ } while (retry);
+
+ // non-Null 'le' implies 'allow_count'
+ if (le && !le->text().isEmpty()) {
+ ::yn_number = le->text().toInt();
+ choice = '#';
+ }
+ keypress = choice;
+
} else {
QLabel label(qlabel,this);
QPushButton cancel("Dismiss",this);
show();
keypress = '\033';
exec();
- return keypress;
}
+ return keypress;
}
void NetHackQtYnDialog::keyPressEvent(QKeyEvent* event)
{
+ //
+ // FIXME: on OSX (possibly elsewhere), this accepts <shift>
+ // (and even <caps lock>) as the entire response before the user
+ // has a chance to type any character to be shifted.
+ //
+
// Don't want QDialog's Return/Esc behaviour
//RLC ...or do we?
QString text(event->text());