]> granicus.if.org Git - nethack/commitdiff
implement "--More--" for Qt
authorPatR <rankin@nethack.org>
Thu, 1 Oct 2020 10:16:14 +0000 (03:16 -0700)
committerPatR <rankin@nethack.org>
Thu, 1 Oct 2020 10:16:14 +0000 (03:16 -0700)
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.

doc/fixes37.0
win/Qt/Qt-issues.txt
win/Qt/qt_bind.cpp
win/Qt/qt_bind.h
win/Qt/qt_key.cpp
win/Qt/qt_main.cpp
win/Qt/qt_msg.cpp

index 9355123bd7969fa29fa920ef4a1c85201e285ac5..41ec6dcb5f838d93b9030774f6c53ab9f6b4c323 100644 (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
 -----------------------------------
@@ -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"
index 612180399aec22b93932f4c1e9d7b3c4c5b55125..ba7ad5c9535cc8ddd04739f6d6c31ed9f712b112 100644 (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
 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/
index defc0e0ada73b37a7ce73372d4e42f7c32067f2b..821772eef7b2b0d57bbd404d481f447c28d71516 100644 (file)
@@ -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 <esc> 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') // <esc> 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;
index e020a83d489eaa781d20048697f0e78cdc6875fc..295fafc57fdb7107707622e6d5b2c9f7d120925f 100644 (file)
@@ -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);
index 81acb1786a0ffe097c8ce7327b460e02227837de..060fc1eed68816fff56c11773371b0181120c6bb 100644 (file)
@@ -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;
index e57a1a8e1784684762f122abaa7a9617a4d7e905..dffaf86140f03edc2005817558b7a104c5191e36 100644 (file)
@@ -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();
index abde5c179e35d5be39d716b3a9ba82fda9f1fc8b..bdd8f535c6d5821dcb0847c5fe24c7c6c1c7f4f5 100644 (file)
@@ -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);
     }
 }