From: PatR Date: Thu, 1 Oct 2020 10:16:14 +0000 (-0700) Subject: implement "--More--" for Qt X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2e90c1ebd44c0b64adbf24ec107acccd61cb6918;p=nethack 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 , ^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. --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 9355123bd..41ec6dcb5 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 ----------------------------------- @@ -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: tried to honor 'showexp' but the value was unintentionally supressed by [lack of] obsolete conditional EXP_ON_BOTL +Qt: implement --More-- prompt to support MSGTYPE=stop Qt+QSX: fix control key Qt+OSX: rename menu entry "nethack->Preferences..." for invoking nethack's 'O' command to "Game->Run-time options" and entry "Game->Qt settings" diff --git a/win/Qt/Qt-issues.txt b/win/Qt/Qt-issues.txt index 612180399..ba7ad5c95 100644 --- a/win/Qt/Qt-issues.txt +++ b/win/Qt/Qt-issues.txt @@ -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 be directed at nethack.org. -Urgent: launching Qt nethack as a synchronous subprocess (ie, no -trailing '&') from a Terminal window, changing focus back to that -terminal after NetHack has started, and typing ^C sends the program -into an endless loop with repeated messages to stderr (the terminal) -complaining that the event loop is already running. +Launching Qt nethack as a synchronous subprocess (ie, no trailing '&') +from a Terminal window, changing focus back to that terminal after +NetHack has started, and typing ^C was sending the program into an +endless loop in qt_nhgetch() with repeated messages to stderr +(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 NetHack.app/Contents/MacOS/nethack (plus having NetHack.app/Contents/ diff --git a/win/Qt/qt_bind.cpp b/win/Qt/qt_bind.cpp index defc0e0ad..821772eef 100644 --- a/win/Qt/qt_bind.cpp +++ b/win/Qt/qt_bind.cpp @@ -313,22 +313,26 @@ winid NetHackQtBind::qt_create_nhwindow(int type) void NetHackQtBind::qt_clear_nhwindow(winid wid) { NetHackQtWindow* window=id_to_window[(int)wid]; - window->Clear(); + if (window) + window->Clear(); } void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block) { NetHackQtWindow* window=id_to_window[(int)wid]; - window->Display(block); + if (window) + window->Display(block); } void NetHackQtBind::qt_destroy_nhwindow(winid wid) { NetHackQtWindow* window=id_to_window[(int)wid]; - main->RemoveWindow(window); - if (window->Destroy()) - delete window; - id_to_window[(int)wid] = 0; + if (window) { + main->RemoveWindow(window); + if (window->Destroy()) + delete window; + id_to_window[(int) wid] = 0; + } } void NetHackQtBind::qt_curs(winid wid, int x, int y) @@ -476,7 +480,22 @@ int NetHackQtBind::qt_nhgetch() // Process events until a key arrives. // 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 @@ -494,7 +513,10 @@ int NetHackQtBind::qt_nh_poskey(int *x, int *y, int *mod) // Process events until a key or map-click arrives. // 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 @@ -524,6 +546,70 @@ int NetHackQtBind::qt_doprev_message() 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_, const char *choices, CHAR_P def) { @@ -533,10 +619,13 @@ char NetHackQtBind::qt_yn_function(const char *question_, int result = -1; if (choices) { - // anything beyond is hidden> - QString choicebuf = choices; - size_t cb = choicebuf.indexOf('\033'); - choicebuf = choicebuf.mid(0U, cb); + QString choicebuf((int) strlen(choices) + 1, QChar('\0')); + for (const char *p = choices; *p; ++p) { + if (*p == '\033') // and anything beyond is hidden + break; + choicebuf += visctrl(*p); + } + choicebuf.truncate(QBUFSZ - 1); // no effect if already shorter message = QString("%1 [%2] ").arg(question, choicebuf); if (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); message += QString(" %1").arg(cbuf); - // add the prompt with appended response to the messsage window - NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message); + // add the prompt with appended response to the message window + if (WIN_MESSAGE != WIN_ERR) + NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message); result = ret; } // 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; } @@ -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) { // Ignore Alt-key navigation to menubar, it's annoying when you @@ -756,7 +848,9 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event) return true; bool result = QApplication::notify(receiver, event); - if (event->type() == QEvent::KeyPress) { + int evtyp = event->type(); + + if (evtyp == QEvent::KeyPress) { QKeyEvent *key_event = (QKeyEvent *) event; if (!key_event->isAccepted()) { @@ -778,10 +872,11 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event) if (ch > 128) ch = 0; // on OSX, ascii control codes are not sent, force them - if ((mod & Qt::ControlModifier) != 0) { - if (ch == 0 && k >= Qt::Key_A && k <= Qt::Key_Underscore) + if (ch == 0 && (mod & Qt::ControlModifier) != 0) { + if (k >= Qt::Key_A && k <= Qt::Key_Underscore) 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 (ch != 0) { bool alt = ((mod & Qt::AltModifier) != 0 @@ -792,6 +887,18 @@ bool NetHackQtBind::notify(QObject *receiver, QEvent *event) qApp->exit(); 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; diff --git a/win/Qt/qt_bind.h b/win/Qt/qt_bind.h index e020a83d4..295fafc57 100644 --- a/win/Qt/qt_bind.h +++ b/win/Qt/qt_bind.h @@ -71,6 +71,7 @@ public: static int qt_nh_poskey(int *x, int *y, int *mod); static void qt_nhbell(); static int qt_doprev_message(); + static char qt_more(); static char qt_yn_function(const char *question, const char *choices, CHAR_P def); static void qt_getlin(const char *prompt, char *line); diff --git a/win/Qt/qt_key.cpp b/win/Qt/qt_key.cpp index 81acb1786..060fc1eed 100644 --- a/win/Qt/qt_key.cpp +++ b/win/Qt/qt_key.cpp @@ -25,7 +25,7 @@ bool NetHackQtKeyBuffer::Full() const { return (in+1)%maxkey==out; } 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 key[in] = k; ascii[in] = a; diff --git a/win/Qt/qt_main.cpp b/win/Qt/qt_main.cpp index e57a1a8e1..dffaf8614 100644 --- a/win/Qt/qt_main.cpp +++ b/win/Qt/qt_main.cpp @@ -1225,10 +1225,10 @@ void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event) if (message) message->Scroll(0,+1); break; case Qt::Key_Space: - if ( flags.rest_on_space ) { - event->ignore(); - return; - } + //if (flags.rest_on_space) { + event->ignore(); // punt to NetHackQtBind::notify() + return; + //} case Qt::Key_Enter: if ( map ) map->clickCursor(); diff --git a/win/Qt/qt_msg.cpp b/win/Qt/qt_msg.cpp index abde5c179..bdd8f535c 100644 --- a/win/Qt/qt_msg.cpp +++ b/win/Qt/qt_msg.cpp @@ -75,15 +75,16 @@ void NetHackQtMessageWindow::ClearMessages() list->clear(); } -void NetHackQtMessageWindow::Display(bool block UNUSED) +void NetHackQtMessageWindow::Display(bool block) { - // - // FIXME: support for 'block' is necessary for MSGTYPE=stop - // if (changed) { list->repaint(); changed=false; } + if (block) { + // we don't care what the response is here + (void) NetHackQtBind::qt_more(); + } } const char * NetHackQtMessageWindow::GetStr(bool init) @@ -113,49 +114,73 @@ void NetHackQtMessageWindow::PutStr(int attr, const QString& text) } else { text2 = text; } + #if 0 - QListWidgetItem *item = new QListWidgetItem(text2); - - QFont font = item->font(); - font.setUnderline(attr == ATR_ULINE); - font.setWeight((attr == ATR_BOLD) ? QFont::Bold : QFont::Normal); - item->setFont(font); - - if (attr == ATR_DIM || attr == ATR_INVERSE) { - QColor fg = item->foreground().color(); - QColor bg = item->background().color(); - if (attr == ATR_DIM) { - fg.setAlpha(fg.alpha() / 2); - new_fgbg = true; - } - if (attr == ATR_INVERSE) { - QColor swap; - swap = fg; fg = bg; bg = swap; + if (attr != ATR_NONE) { + QListWidgetItem *item = new QListWidgetItem(text2); + if (attr != ATR_DIM && attr != ATR_INVERSE) { + QFont font = item->font(); + font.setUnderline(attr == ATR_ULINE); + font.setWeight((attr == ATR_BOLD) ? QFont::Bold : QFont::Normal); + item->setFont(font); + // ATR_BLINK not supported + } else { + // ATR_DIM or ATR_INVERSE + QBrush fg = item->foreground(); + QBrush bg = item->background(); + 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); } - item->setForeground(fg); - item->setBackground(bg); } - // ATR_BLINK not supported #endif if (list->count() >= (int) ::iflags.msg_history) delete list->item(0); 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); if (map) 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) { if (list) { QListWidgetItem *item = list->currentItem(); + int ct = 0; + if (!item && (ct = list->count()) > 0) { + list->setCurrentRow(ct - 1); + item = list->currentItem(); + } if (item) item->setText(item->text() + QString(" %1").arg(answer)); + else // just in case... + NetHackQtMessageWindow::PutStr(ATR_NONE, answer); } }