implement "--More--" for Qt

Support MSGTYPE=stop by having qt_display_nhwindow(WIN_MESSAGE,TRUE)
issue a tty-style --More-- prompt.  For popup_dialog Off, the prompt
gets appended to the most recent message; for popup_dialog On, it is
issued via a popup and not displayed in the message window.

It accepts <space>, ^J, ^M, and ^[ (ESC) to dismiss.  There's no way
to dismiss it with the mouse (for !popup_dialog) which might need
some fix....

Several adventures along the way.  The '^C-in-parent-terminal triggers
infinite loop repeatedly complaining about "event loop already running"'
is now a one-shot complaint.  It isn't fixed but the severity of
having it happen is greatly reduced.
This commit is contained in:
PatR
2020-10-01 03:16:14 -07:00
parent 92902fd128
commit 2e90c1ebd4
7 changed files with 193 additions and 55 deletions
+2 -1
View File
@@ -1,4 +1,4 @@
NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.309 $ $NHDT-Date: 1600933440 2020/09/24 07:44:00 $ NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.315 $ $NHDT-Date: 1601547360 2020/10/01 10:16:00 $
General Fixes and Modified Features General Fixes and Modified Features
----------------------------------- -----------------------------------
@@ -424,6 +424,7 @@ Qt: enable the popup_dialog WC option (result is a bit flakey but usable)
Qt: 3.6 catchup - show unexplored locations as unexplored rather than as stone Qt: 3.6 catchup - show unexplored locations as unexplored rather than as stone
Qt: tried to honor 'showexp' but the value was unintentionally supressed by Qt: tried to honor 'showexp' but the value was unintentionally supressed by
[lack of] obsolete conditional EXP_ON_BOTL [lack of] obsolete conditional EXP_ON_BOTL
Qt: implement --More-- prompt to support MSGTYPE=stop
Qt+QSX: fix control key Qt+QSX: fix control key
Qt+OSX: rename menu entry "nethack->Preferences..." for invoking nethack's Qt+OSX: rename menu entry "nethack->Preferences..." for invoking nethack's
'O' command to "Game->Run-time options" and entry "Game->Qt settings" 'O' command to "Game->Run-time options" and entry "Game->Qt settings"
+9 -5
View File
@@ -6,11 +6,15 @@ whether to [ignore], [report], or [restart] will eventually appear
(it's slow). That should be repressed even if the report choice could (it's slow). That should be repressed even if the report choice could
be directed at nethack.org. be directed at nethack.org.
Urgent: launching Qt nethack as a synchronous subprocess (ie, no Launching Qt nethack as a synchronous subprocess (ie, no trailing '&')
trailing '&') from a Terminal window, changing focus back to that from a Terminal window, changing focus back to that terminal after
terminal after NetHack has started, and typing ^C sends the program NetHack has started, and typing ^C was sending the program into an
into an endless loop with repeated messages to stderr (the terminal) endless loop in qt_nhgetch() with repeated messages to stderr
complaining that the event loop is already running. (the terminal) complaining that the event loop is already running.
Triggered by yn_function("Really quit?") in the core. That situation
has been reduced to a single event loop complaint, do downgraded from
"Urgent", but the prompt is auto-answered with ESC instead of letting
the user respond.
On OSX, if the program is run from nethackdir/nethack rather than from On OSX, if the program is run from nethackdir/nethack rather than from
NetHack.app/Contents/MacOS/nethack (plus having NetHack.app/Contents/ NetHack.app/Contents/MacOS/nethack (plus having NetHack.app/Contents/
+125 -18
View File
@@ -313,22 +313,26 @@ winid NetHackQtBind::qt_create_nhwindow(int type)
void NetHackQtBind::qt_clear_nhwindow(winid wid) void NetHackQtBind::qt_clear_nhwindow(winid wid)
{ {
NetHackQtWindow* window=id_to_window[(int)wid]; NetHackQtWindow* window=id_to_window[(int)wid];
window->Clear(); if (window)
window->Clear();
} }
void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block) void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block)
{ {
NetHackQtWindow* window=id_to_window[(int)wid]; NetHackQtWindow* window=id_to_window[(int)wid];
window->Display(block); if (window)
window->Display(block);
} }
void NetHackQtBind::qt_destroy_nhwindow(winid wid) void NetHackQtBind::qt_destroy_nhwindow(winid wid)
{ {
NetHackQtWindow* window=id_to_window[(int)wid]; NetHackQtWindow* window=id_to_window[(int)wid];
main->RemoveWindow(window); if (window) {
if (window->Destroy()) main->RemoveWindow(window);
delete window; if (window->Destroy())
id_to_window[(int)wid] = 0; delete window;
id_to_window[(int) wid] = 0;
}
} }
void NetHackQtBind::qt_curs(winid wid, int x, int y) void NetHackQtBind::qt_curs(winid wid, int x, int y)
@@ -476,7 +480,22 @@ int NetHackQtBind::qt_nhgetch()
// Process events until a key arrives. // Process events until a key arrives.
// //
while (keybuffer.Empty()) { while (keybuffer.Empty()) {
qApp->exec(); int exc = qApp->exec();
/*
* On OSX (possibly elsewhere), this prevents an infinite
* loop repeatedly issuing the complaint:
QCoreApplication::exec: The event loop is already running
* to stderr if you syncronously start nethack from a terminal
* then switch focus back to that terminal and type ^C.
* SIGINT -> done1() -> done2() -> yn_function("Really quit?")
* in the core asks for another keystroke.
*
* However, it still issues one such complaint, and whatever
* prompt wanted a response ("Really quit?") is shown in the
* message window but is auto-answered with ESC.
*/
if (exc == -1)
keybuffer.Put('\033');
} }
// after getting a key rather than before // after getting a key rather than before
@@ -494,7 +513,10 @@ int NetHackQtBind::qt_nh_poskey(int *x, int *y, int *mod)
// Process events until a key or map-click arrives. // Process events until a key or map-click arrives.
// //
while (keybuffer.Empty() && clickbuffer.Empty()) { while (keybuffer.Empty() && clickbuffer.Empty()) {
qApp->exec(); int exc = qApp->exec();
// [see comment above in qt_nhgetch()]
if (exc == -1)
keybuffer.Put('\033');
} }
// after getting a key or click rather than before // after getting a key or click rather than before
@@ -524,6 +546,70 @@ int NetHackQtBind::qt_doprev_message()
return 0; return 0;
} }
// display "--More--" as a prompt and wait for a response from the user
//
// Used by qt_display_nhwindow(WIN_MESSAGE, TRUE) where second argument
// True requests blocking. We need it to support MSGTYPE=stop but the
// core also uses that in various other situations.
char NetHackQtBind::qt_more()
{
char ch = '\033';
// without this gameover hack, quitting via menu or window close
// button ends up provoking a complaint from qt_nhgetch() [see the
// ^C comment in that routine] when the core triggers --More-- via
// done2() -> really_done() -> display_nhwindow(WIN_MESSAGE, TRUE)
// (get rid of this if the exec() loop issue gets properly fixed)
if (::g.program_state.gameover)
return ch; // bypass --More-- and just continue with program exit
NetHackQtMessageWindow *mesgwin = main ? main->GetMessageWindow() : NULL;
// kill any typeahead; for '!popup_dialog' this forces qt_nhgetch()
keybuffer.Drain();
if (mesgwin && !::iflags.wc_popup_dialog && WIN_MESSAGE != WIN_ERR) {
mesgwin->AddToStr("--More--");
bool retry = false;
int complain = 0;
do {
ch = NetHackQtBind::qt_nhgetch();
switch (ch) {
case '\0': // hypothetical
ch = '\033';
/*FALLTHRU*/
case ' ':
case '\n':
case '\r':
case '\033':
retry = false;
break;
default:
if (++complain > 1)
NetHackQtBind::qt_nhbell();
retry = true;
break;
}
} while (retry);
// unhighlight the line with the prompt; does not erase the window
NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
} else {
// use a popup dialog box; unlike yn_function(), we don't show
// the prompt+response in the message window
NetHackQtYnDialog dialog(main, "--More--", " \033\n\r", ' ');
ch = dialog.Exec();
if (ch == '\0') {
ch = '\033';
}
// discard any input that YnDialog() might have left pending
keybuffer.Drain();
}
return ch;
}
char NetHackQtBind::qt_yn_function(const char *question_, char NetHackQtBind::qt_yn_function(const char *question_,
const char *choices, CHAR_P def) const char *choices, CHAR_P def)
{ {
@@ -533,10 +619,13 @@ char NetHackQtBind::qt_yn_function(const char *question_,
int result = -1; int result = -1;
if (choices) { if (choices) {
// anything beyond <esc> is hidden> QString choicebuf((int) strlen(choices) + 1, QChar('\0'));
QString choicebuf = choices; for (const char *p = choices; *p; ++p) {
size_t cb = choicebuf.indexOf('\033'); if (*p == '\033') // <esc> and anything beyond is hidden
choicebuf = choicebuf.mid(0U, cb); break;
choicebuf += visctrl(*p);
}
choicebuf.truncate(QBUFSZ - 1); // no effect if already shorter
message = QString("%1 [%2] ").arg(question, choicebuf); message = QString("%1 [%2] ").arg(question, choicebuf);
if (def) if (def)
message += QString("(%1) ").arg(QChar(def)); message += QString("(%1) ").arg(QChar(def));
@@ -617,14 +706,16 @@ char NetHackQtBind::qt_yn_function(const char *question_,
Sprintf(eos(cbuf), " %ld", ::yn_number); Sprintf(eos(cbuf), " %ld", ::yn_number);
message += QString(" %1").arg(cbuf); message += QString(" %1").arg(cbuf);
// add the prompt with appended response to the messsage window // add the prompt with appended response to the message window
NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message); if (WIN_MESSAGE != WIN_ERR)
NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
result = ret; result = ret;
} }
// unhighlight the prompt; does not erase the multi-line message window // unhighlight the prompt; does not erase the multi-line message window
NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE); if (WIN_MESSAGE != WIN_ERR)
NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
return (char) result; return (char) result;
} }
@@ -747,6 +838,7 @@ void NetHackQtBind::qt_putmsghistory(const char *msg, BOOLEAN_P is_restoring)
} }
} }
// event loop callback
bool NetHackQtBind::notify(QObject *receiver, QEvent *event) bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
{ {
// Ignore Alt-key navigation to menubar, it's annoying when you // Ignore Alt-key navigation to menubar, it's annoying when you
@@ -756,7 +848,9 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
return true; return true;
bool result = QApplication::notify(receiver, event); bool result = QApplication::notify(receiver, event);
if (event->type() == QEvent::KeyPress) { int evtyp = event->type();
if (evtyp == QEvent::KeyPress) {
QKeyEvent *key_event = (QKeyEvent *) event; QKeyEvent *key_event = (QKeyEvent *) event;
if (!key_event->isAccepted()) { if (!key_event->isAccepted()) {
@@ -778,10 +872,11 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
if (ch > 128) if (ch > 128)
ch = 0; ch = 0;
// on OSX, ascii control codes are not sent, force them // on OSX, ascii control codes are not sent, force them
if ((mod & Qt::ControlModifier) != 0) { if (ch == 0 && (mod & Qt::ControlModifier) != 0) {
if (ch == 0 && k >= Qt::Key_A && k <= Qt::Key_Underscore) if (k >= Qt::Key_A && k <= Qt::Key_Underscore)
ch = (QChar) (k - (Qt::Key_A - 1)); ch = (QChar) (k - (Qt::Key_A - 1));
} }
//raw_printf("notify()=%d \"%s\"", k, visctrl(ch.cell()));
// if we have a valid character, queue it up // if we have a valid character, queue it up
if (ch != 0) { if (ch != 0) {
bool alt = ((mod & Qt::AltModifier) != 0 bool alt = ((mod & Qt::AltModifier) != 0
@@ -792,6 +887,18 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
qApp->exit(); qApp->exit();
result = true; result = true;
} }
#if 0 /* this was a failed attempt to prevent qt_more() from looping
* after command+q (on OSX) is used to bring up the quit dialog;
* now qt_more() uses an early return if program_state.gameover
* is set */
} else if (evtyp == QEvent::FocusOut
|| evtyp == QEvent::ShortcutOverride
|| evtyp == QEvent::PlatformSurface) {
// leave qt_nhgetch()'s event loop if focus switches somewhere else
qApp->exit();
result = false;
#endif
} }
} }
return result; return result;
+1
View File
@@ -71,6 +71,7 @@ public:
static int qt_nh_poskey(int *x, int *y, int *mod); static int qt_nh_poskey(int *x, int *y, int *mod);
static void qt_nhbell(); static void qt_nhbell();
static int qt_doprev_message(); static int qt_doprev_message();
static char qt_more();
static char qt_yn_function(const char *question, static char qt_yn_function(const char *question,
const char *choices, CHAR_P def); const char *choices, CHAR_P def);
static void qt_getlin(const char *prompt, char *line); static void qt_getlin(const char *prompt, char *line);
+1 -1
View File
@@ -25,7 +25,7 @@ bool NetHackQtKeyBuffer::Full() const { return (in+1)%maxkey==out; }
void NetHackQtKeyBuffer::Put(int k, int a, uint kbstate) void NetHackQtKeyBuffer::Put(int k, int a, uint kbstate)
{ {
//raw_printf("k:%3d a:%3d s:0x%08x", k, a, kbstate); //raw_printf("k:%3d a:'%s' s:0x%08x", k, visctrl((char) a), kbstate);
if ( Full() ) return; // Safety if ( Full() ) return; // Safety
key[in] = k; key[in] = k;
ascii[in] = a; ascii[in] = a;
+4 -4
View File
@@ -1225,10 +1225,10 @@ void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event)
if (message) message->Scroll(0,+1); if (message) message->Scroll(0,+1);
break; break;
case Qt::Key_Space: case Qt::Key_Space:
if ( flags.rest_on_space ) { //if (flags.rest_on_space) {
event->ignore(); event->ignore(); // punt to NetHackQtBind::notify()
return; return;
} //}
case Qt::Key_Enter: case Qt::Key_Enter:
if ( map ) if ( map )
map->clickCursor(); map->clickCursor();
+51 -26
View File
@@ -75,15 +75,16 @@ void NetHackQtMessageWindow::ClearMessages()
list->clear(); list->clear();
} }
void NetHackQtMessageWindow::Display(bool block UNUSED) void NetHackQtMessageWindow::Display(bool block)
{ {
//
// FIXME: support for 'block' is necessary for MSGTYPE=stop
//
if (changed) { if (changed) {
list->repaint(); list->repaint();
changed=false; changed=false;
} }
if (block) {
// we don't care what the response is here
(void) NetHackQtBind::qt_more();
}
} }
const char * NetHackQtMessageWindow::GetStr(bool init) const char * NetHackQtMessageWindow::GetStr(bool init)
@@ -113,49 +114,73 @@ void NetHackQtMessageWindow::PutStr(int attr, const QString& text)
} else { } else {
text2 = text; text2 = text;
} }
#if 0 #if 0
QListWidgetItem *item = new QListWidgetItem(text2); if (attr != ATR_NONE) {
QListWidgetItem *item = new QListWidgetItem(text2);
QFont font = item->font(); if (attr != ATR_DIM && attr != ATR_INVERSE) {
font.setUnderline(attr == ATR_ULINE); QFont font = item->font();
font.setWeight((attr == ATR_BOLD) ? QFont::Bold : QFont::Normal); font.setUnderline(attr == ATR_ULINE);
item->setFont(font); font.setWeight((attr == ATR_BOLD) ? QFont::Bold : QFont::Normal);
item->setFont(font);
if (attr == ATR_DIM || attr == ATR_INVERSE) { // ATR_BLINK not supported
QColor fg = item->foreground().color(); } else {
QColor bg = item->background().color(); // ATR_DIM or ATR_INVERSE
if (attr == ATR_DIM) { QBrush fg = item->foreground();
fg.setAlpha(fg.alpha() / 2); QBrush bg = item->background();
new_fgbg = true; if (fg.color() == bg.color()) { // from menu coloring [AddRow()]
// default foreground and background come up the same for
// some unknown reason
//[pr: both are set to 'Qt::color1' which has same RGB
// value as 'Qt::black'; X11 on OSX behaves similarly]
if (fg.color() == Qt::color1) {
fg = Qt::black;
bg = Qt::white;
} else {
fg = (bg.color() == Qt::white) ? Qt::black : Qt::white;
}
}
if (attr == ATR_DIM) {
QColor fg_clr = fg.color();
fg_clr.setAlpha(fg_clr.alpha() / 2);
item->setFlags(Qt::NoItemFlags);
} else if (attr == ATR_INVERSE) {
QBrush swapfb;
swapfb = fg; fg = bg; bg = swapfb;
}
item->setForeground(fg);
item->setBackground(bg);
} }
if (attr == ATR_INVERSE) {
QColor swap;
swap = fg; fg = bg; bg = swap;
}
item->setForeground(fg);
item->setBackground(bg);
} }
// ATR_BLINK not supported
#endif #endif
if (list->count() >= (int) ::iflags.msg_history) if (list->count() >= (int) ::iflags.msg_history)
delete list->item(0); delete list->item(0);
list->addItem(text2); list->addItem(text2);
/* assert( list->count() > 0 ); */
// Force scrollbar to bottom // force scrollbar to bottom;
// selects most recent message, which causes it to be highlighted
list->setCurrentRow(list->count() - 1); list->setCurrentRow(list->count() - 1);
if (map) if (map)
map->putMessage(attr, text2); map->putMessage(attr, text2);
} }
// append the user's answer to a prompt message // append to the last message; usually the user's answer to a prompt
void NetHackQtMessageWindow::AddToStr(const char *answer) void NetHackQtMessageWindow::AddToStr(const char *answer)
{ {
if (list) { if (list) {
QListWidgetItem *item = list->currentItem(); QListWidgetItem *item = list->currentItem();
int ct = 0;
if (!item && (ct = list->count()) > 0) {
list->setCurrentRow(ct - 1);
item = list->currentItem();
}
if (item) if (item)
item->setText(item->text() + QString(" %1").arg(answer)); item->setText(item->text() + QString(" %1").arg(answer));
else // just in case...
NetHackQtMessageWindow::PutStr(ATR_NONE, answer);
} }
} }