// 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 #if QT_VERSION >= 0x050000 #include #endif #include "qt_post.h" #include "qt_yndlg.h" #include "qt_yndlg.moc" #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) { 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"); } } } } 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; 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 = g.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::null : qlabel, this); vb->addWidget(group); QHBoxLayout *groupbox = new QHBoxLayout(); group->setLayout(groupbox); QButtonGroup *bgroup = new QButtonGroup(group); int nchoices=ch.length(); bool 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 butheight = fontMetrics().height() * 2 + 5, butwidth = (butheight - 5) * ((is_ynq || is_lr) ? 3 : 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) { 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; } } 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; // and 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; } } 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; 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 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 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) { 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); label.setFrameStyle(QFrame::Box|QFrame::Sunken); label.setAlignment(Qt::AlignCenter); label.resize(fontMetrics().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::keyPressEvent(QKeyEvent* event) { QString text(event->text()); if (text.isEmpty() && event->modifiers()) return; if (!choices || !*choices) { if (!text.isEmpty()) { keypress = text.toUcs4()[0]; this->done(1); } } else { int where = QString::fromLatin1(choices).indexOf(text); if (where != -1 && allow_count && strchr("#0123456789", text[0].cell())) { if (text == "#") { // 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(text); le->end(false); } // (don't know whether this actually does anything useful) le->setAttribute(Qt::WA_KeyboardFocusChange, true); le->setFocus(Qt::ActiveWindowFocusReason); } else if (where != -1) { this->done(where + 1000); } else { QDialog::keyPressEvent(event); } } } void NetHackQtYnDialog::doneItem(int i) { this->done(i + 1000); } } // namespace nethack_qt_