# HG changeset patch
# User Daniel J Sebald <daniel.sebald@ieee.org>
# Date 1426703116 18000
# Node ID cd9fc6c503e29853b2f766c86a768c33fa8ab09b
# Parent  793230bb81d44e3d74cad8160f6ea762f03c5ffc
Add a force-quit dialog when Octave worker process is busy (bug #44485)

* main-window.cc (ForceExitDialog): Added.  Constructor inheriting QMessageBox
with an added timer.  Set application modal, add a push button "Force Exit",
connect push button slot clicked() to the dialog signal accept(), connect
timer signal timeout() to dialog slot exec(), start single shot timer.
(main_window::mainwindow): Initialize _exit_in_queue to false.
(main_window::confirm_shutdown_octave): Emit shutdown_initiated(), clear
variable _exit_in_queue.
(main_window::force_exit_app): Added.  Force interpreter thread to quit and
call exit_app(0).
(main_window::closeEvent): Only allow one "exit" issued to command queue at a
time by testing and setting _exit_in_queue.  Create a ForceExitDialog, connect
dialog signal accepted() to main window slot for_exit_app(), connect main
window signal shutdown_initiated() to dialog slot close(), set dialog to delete
upon closing.

* main-window.h: Add QTimer header file.
(main_window::shutdown_initiated): Added, new signal.
(main_window::force_exit_app): Added, new slot.
(main_window::_exit_in_queue): New variable indicating GUI requested core exit.
(ForceExitDialog:QMessageBox): Added.  New class definition for dialog box with
default 3 second delay.
(ForceExitDialog::closeEvent): Stop the timer.

* octave-qt-link.cc (quit_interpreter): Added.

* octave-qt-link.h (octave_qt_link): Add declaration for quit_interpreter().

diff --git a/libgui/src/main-window.cc b/libgui/src/main-window.cc
--- a/libgui/src/main-window.cc
+++ b/libgui/src/main-window.cc
@@ -73,6 +73,30 @@ create_default_editor (QWidget *p)
 #endif
 }
 
+ForceExitDialog::ForceExitDialog (const int msec)
+  : QMessageBox (QMessageBox::Critical, "Worker Not Responding",
+                 tr ("Octave core appears busy and not responding.\n\n"
+                     "Force exit?\n\n"
+                     "(Processing jobs will be incomplete, and history and"
+                     " editor files will not be saved.  Choose \"Cancel\""
+                     " to save files.)"),
+                 QMessageBox::Cancel, 0),
+    timer (this)
+{
+  // Create a Modal message.
+  setWindowModality (Qt::ApplicationModal);
+
+  QPushButton *pbutton = addButton (tr ("Force Exit"), QMessageBox::AcceptRole);
+
+  // For some reason accept() is not being emitted upon clicking the above
+  // button having the AcceptRole.  (Could be a Qt v4.7.4 bug.)
+  connect (pbutton, SIGNAL (clicked ()), this, SLOT (accept ()));
+
+  connect(&timer, SIGNAL(timeout()), this, SLOT(exec()));
+  timer.setSingleShot (true);
+  timer.start (msec);
+}
+
 main_window::main_window (QWidget *p)
   : QMainWindow (p),
     _workspace_model (new workspace_model ()),
@@ -95,7 +119,8 @@ main_window::main_window (QWidget *p)
     _dbg_queue (new QStringList ()),  // no debug pending
     _dbg_processing (1),
     _dbg_queue_mutex (),
-    _prevent_readline_conflicts (true)
+    _prevent_readline_conflicts (true),
+    _exit_in_queue (false)
 {
   QSettings *settings = resource_manager::get_settings ();
 
@@ -762,6 +787,16 @@ main_window::confirm_shutdown_octave (vo
 {
   bool closenow = true;
 
+  // Emit signal indicating forced exit is no longer required.  (Items
+  // attached might be forced-exit dialog, etc.)
+  emit shutdown_initiated ();
+
+  // Right now, GUI-initiated and user-initiated commands are mixed
+  // together in the same queue, so a user-initiated "exit" could clear
+  // this, but it's a rare situation and the ramification is minor, i.e.,
+  // repeated exit.
+  _exit_in_queue = false;
+
   QSettings *settings = resource_manager::get_settings ();
 
   if (settings->value ("prompt_to_exit", false).toBool ())
@@ -798,6 +833,15 @@ main_window::exit_app (int status)
 }
 
 void
+main_window::force_exit_app (void)
+{
+  // Avoid non-zero exit signal due to interpreter.
+  _octave_qt_link->quit_interpreter ();
+
+  exit_app (0);
+}
+
+void
 main_window::reset_windows (void)
 {
   QSettings *settings = resource_manager::get_default_settings ();
@@ -990,8 +1034,20 @@ void
 main_window::closeEvent (QCloseEvent *e)
 {
   e->ignore ();
-  octave_cmd_exec *cmd = new octave_cmd_exec ("exit");
-  queue_command (cmd);
+
+  if (! _exit_in_queue)
+    {
+      octave_cmd_exec *cmd = new octave_cmd_exec ("exit");
+      queue_command (cmd);
+      _exit_in_queue = true;
+    }
+
+  // Create a dialog box with timer that displays after a few seconds
+  // thereby indicating that the worker process is busy and not responding.
+  ForceExitDialog *dlg = new ForceExitDialog ();
+  connect(dlg, SIGNAL(accepted ()), this, SLOT (force_exit_app ()));
+  connect(this, SIGNAL(shutdown_initiated ()), dlg, SLOT (close ()));
+  dlg->setAttribute (Qt::WA_DeleteOnClose);
 }
 
 void
diff --git a/libgui/src/main-window.h b/libgui/src/main-window.h
--- a/libgui/src/main-window.h
+++ b/libgui/src/main-window.h
@@ -38,6 +38,7 @@ along with Octave; see the file COPYING.
 #include <QComboBox>
 #include <QSemaphore>
 #include <QPointer>
+#include <QTimer>
 
 // Editor includes
 #include "file-editor-interface.h"
@@ -104,6 +105,8 @@ signals:
   void pasteClipboard_signal (void);
   void selectAll_signal (void);
 
+  void shutdown_initiated (void);
+
 public slots:
 
   void focus_changed (QWidget *w_old, QWidget *w_new);
@@ -136,6 +139,7 @@ public slots:
   void confirm_shutdown_octave (void);
   void prepare_to_exit (void);
   void exit_app (int status);
+  void force_exit_app (void);
   void reset_windows (void);
 
   void change_directory (const QString& dir);
@@ -412,6 +416,7 @@ private:
 
   bool _prevent_readline_conflicts;
   bool _suppress_dbg_location;
+  bool _exit_in_queue;
 };
 
 class news_reader : public QObject
@@ -444,4 +449,24 @@ private:
   bool connect_to_web;
 };
 
+class ForceExitDialog : public QMessageBox
+{
+  Q_OBJECT
+
+public:
+
+  explicit ForceExitDialog (int msec = 3000);
+
+private:
+
+  void closeEvent (QCloseEvent *)
+  {
+    // The dialog doesn't actually destruct with the completion of the
+    // close event.  Destruct happens when control goes back to Qt core.
+    timer.stop ();
+  }
+
+  QTimer timer;
+};
+
 #endif // MAINWINDOW_H
diff --git a/libgui/src/octave-qt-link.cc b/libgui/src/octave-qt-link.cc
--- a/libgui/src/octave-qt-link.cc
+++ b/libgui/src/octave-qt-link.cc
@@ -66,6 +66,16 @@ octave_qt_link::execute_interpreter (voi
   emit execute_interpreter_signal ();
 }
 
+void
+octave_qt_link::quit_interpreter (void)
+{
+#if 1
+  main_thread->quit ();
+#else
+  delete main_thread;
+#endif
+}
+
 bool
 octave_qt_link::do_confirm_shutdown (void)
 {
diff --git a/libgui/src/octave-qt-link.h b/libgui/src/octave-qt-link.h
--- a/libgui/src/octave-qt-link.h
+++ b/libgui/src/octave-qt-link.h
@@ -60,6 +60,7 @@ public:
   ~octave_qt_link (void);
 
   void execute_interpreter (void);
+  void quit_interpreter (void);
 
   bool do_confirm_shutdown (void);
   bool do_exit (int status);
exporting patch:
<fdopen>
