# HG changeset patch
# User Daniel J Sebald <daniel.sebald@ieee.org>
# Date 1436388260 18000
# Node ID 25fc73b8f70b174c8100556d4d09610f6ebeae6c
# Parent  6c178e2b4496882f53b478eef070f7c83dfc5d50
Add a GUI force-quit dialog when Octave worker thread is busy (bug #44485)

* file-editor-interface.h (file_editor_interface:check_closing_in_progress):
New virtual base function.
(file_editor_interface:check_closing): Change function to private.

* file-editor-tab.cc (file_editor_tab::_cancelled): Variable removed.
(file_editor_tab::closeEvent): Remove reference to _cancelled.
(file_editor_tab::check_modified_file): Removed.
(file_editor_tab::show_dialog): Return answer if modal dialog.
(file_editor_tab::check_file_modified): Remove reference to _cancelled and get
dialog answer directly if contents modified.  Emit signal save_continue() if
contents not modified.
(file_editor_tab::handle_file_modified_answer): If answer "save", after the
save emit save_continue() signal.  If answer "discard", emit save_continue().
If answer "cancel", emit save_cancel().
(file_editor_tab::file_name_query): Always return name, even if empty.

* file-editor-tab.h (file_editor_tab::reset_cancel): Removed.
(file_editor_tab::was_cancelled): Removed.
(file_editor_tab::check_modified_file): Removed slot.
(file_editor_tab::add_filename_to_list): Change QWidget pointer argument to
derived class file_editor_tab pointer.
(file_editor_tab::save_continue): Added, new signal.
(file_editor_tab::save_cancel): Added, new signal.
(file_editor_tab::check_file_modified): Change private function to a private
slot function.
(file_editor_tab::show_dialog): Change return type from void to int.
(file_editor_tab::_cancelled): Removed variable.

* file-editor.cc (file_editor::check_closing): Change return type from bool
to void.  Use number of receivers of fetab_check_modified_file() signal to
determine if check-closing is already in progress.  Create linked-list type of
connection of signal slots for all editor tabs that returns to save_completed()
when done.  Remove call to reset_cancel() and was_cancelled() check.  Move the
closing of all tabs to a new slot close_all_tabs().
(file_editor::close_all_tabs): Loop through all tabs, delete and removeTab().
(file_editor::save_completed): Disconnect fetab_check_modified_file() signal.
Construct file name list and save to program settings again.  Call
close_all_tabs() and emit save_finished(true).
(file_editor::save_cancelled): Disconnect fetab_check_modified_file() signal
and emit save_finished(false).
(file_editor::find_tab_widget): Interchange first/second role of iterator p.
(file_editor::handle_add_filename_to_list): Change QWidget pointer argument to
derived class file_editor_tab pointer.  Make ID the unique independent variable
of the map table and fileName the dependent variable.
(file_editor::add_file_editor_tab): Change QWidget pointer argument to derived
class file_editor_tab pointer.  Remove connect from fetab_check_modified_file()
to check_modified_file().

* file-editor.h (file_editor::editor_tab_map_iterator): Change QWidget pointer
to file_editor_tab pointer and swap roles of the map arguments.
(file_editor::editor_tab_map_const_iterator): Ditto.
(file_editor::check_closing_in_progress): Added.  Use number of receivers of
fetab_check_modified_file() signal to determine if check-closing is already
in progress.
(file_editor::save_finished): Added, new signal.
(file_editor::check_closing): Change return type from bool to void.
(file_editor::close_all_tabs): Added, new slot.
(file_editor::save_completed): Added, new slot.
(file_editor::save_cancelled): Added, new slot.
(file_editor::handle_add_filename_to_list): Change QWidget pointer argument to
file_editor_tab pointer.
(file_editor::editor_tab_map): Change QWidget pointer argument to derived class
file_editor_tab pointer.  Make ID the unique independent variable of the map
table and fileName the dependent variable.

* 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): Clear variable _exit_in_queue.  If
not _closing in addition to _start_gui, emit shutdown_initiated(), connect
slot answer_shutdown_octave() to save_finished() signal and emit
confirm_save_close().  Call answer_shutdown_octave() directly in case no
save/confirmation is needed.
(main_window::answer_shutdown_octave): Set _closing to true if input argument
is true.  The rest is hunk of code from confirm_shutdown_octave() that grabs
the mutex, sets return variable and awakes worker thread.
(main_window::force_confirm_save_tabs): Connect signal save_finished() to slot
force_exit_app() and emit confirm_save_close().
(main_window::force_exit_app): Added.  If closenow, set _closing to true,
issue an interrupt of interpreter, place "exit" in queue, create timer and
connect it to terminate_and_exit() slot, start five second single shot timer.
(main_window::terminate_and_exit): Added slot.  Terminate the interpreter.
Exit application with value 0.
(main_window::closeEvent): Test whether closing process is already in progress.
Only allow one "exit" issued to command queue at a time by testing and setting
_exit_in_queue.  If there isn't a ForceExitDialog already present, create one
and connect dialog signal accepted() to main window force_confirm_save_tabs()
slot, connect main window signal shutdown_initiated() to dialog slot close(),
set dialog to delete upon closing.
(main_window::construct): Connect confirm_save_close() signal to
editor_window::check_closing() slot.

* main-window.h: Add QTimer header file.
(main_window::shutdown_initiated): Added, new signal.
(main_window::confirm_save_close): Added, new signal.
(main_window::answer_shutdown_octave): Added, new slot.
(main_window::force_confirm_save_tabs): Added, new slot.
(main_window::force_exit_app): Added, new slot.
(main_window::terminate_and_exit): 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
QTimer timer having default 2 second delay.
(ForceExitDialog::closeEvent): Stop timer.

* octave-qt-link.cc: (octave_qt_link::terminate_interpreter): Added.  Terminate
main_thread and wait for terminate to complete.

* octave-qt-link.h: (octave_qt_link::terminate_interpreter): Added.

diff --git a/libgui/src/m-editor/file-editor-interface.h b/libgui/src/m-editor/file-editor-interface.h
--- a/libgui/src/m-editor/file-editor-interface.h
+++ b/libgui/src/m-editor/file-editor-interface.h
@@ -60,7 +60,7 @@ public:
 
   virtual void handle_edit_file_request (const QString& file) = 0;
 
-  virtual bool check_closing (void) = 0;
+  virtual bool check_closing_in_progress (void) = 0;
 
   virtual void empty_script (bool, bool) = 0;
 
@@ -75,6 +75,7 @@ public slots:
                                   bool debug_pointer = false,
                                   bool breakpoint_marker = false,
                                   bool insert = true) = 0;
+  virtual void check_closing (void) = 0;
 //signals:
 
 //protected:
diff --git a/libgui/src/m-editor/file-editor-tab.cc b/libgui/src/m-editor/file-editor-tab.cc
--- a/libgui/src/m-editor/file-editor-tab.cc
+++ b/libgui/src/m-editor/file-editor-tab.cc
@@ -63,8 +63,6 @@ along with Octave; see the file COPYING.
 #include "defaults.h"
 #include <oct-map.h>
 
-bool file_editor_tab::_cancelled = false;
-
 // Make parent null for the file editor tab so that warning
 // WindowModal messages don't affect grandparents.
 file_editor_tab::file_editor_tab (const QString& directory_arg)
@@ -186,9 +184,6 @@ file_editor_tab::~file_editor_tab (void)
 void
 file_editor_tab::closeEvent (QCloseEvent *e)
 {
-  _cancelled = false;  // prevent unwanted interaction of previous
-                       // exits of octave which were canceled by the user
-
   if (check_file_modified () == QMessageBox::Cancel)
     { // ignore close event if file is not saved and user cancels
       // closing this window
@@ -648,16 +643,6 @@ file_editor_tab::context_edit (const QWi
 }
 
 void
-file_editor_tab::check_modified_file (void)
-{
-  if (_cancelled)
-    return;
-
-  if (check_file_modified () == QMessageBox::Cancel)
-    _cancelled = true;
-}
-
-void
 file_editor_tab::save_file (const QWidget *ID)
 {
   if (ID != this)
@@ -1238,23 +1223,28 @@ file_editor_tab::handle_copy_available (
 }
 
 // show_dialog: shows a modal or non modal dialog depending on input arg
-void
+int
 file_editor_tab::show_dialog (QDialog *dlg, bool modal)
 {
+  int ans = -1;
+
   dlg->setAttribute (Qt::WA_DeleteOnClose);
   if (modal)
-    dlg->exec ();
+    ans = dlg->exec ();
   else
     {
       dlg->setWindowModality (Qt::NonModal);
       dlg->show ();
     }
+
+  return ans;
 }
 
 int
 file_editor_tab::check_file_modified ()
 {
   int decision = QMessageBox::Yes;
+
   if (_edit_area->isModified ())
     {
       // File is modified but not saved, ask user what to do.  The file
@@ -1290,17 +1280,12 @@ file_editor_tab::check_file_modified ()
       connect (msgBox, SIGNAL (finished (int)),
                this, SLOT (handle_file_modified_answer (int)));
 
-      show_dialog (msgBox, true);
-
-      if (_cancelled)
-        return QMessageBox::Cancel;
-      else
-        return decision;
+      decision = show_dialog (msgBox, false);
     }
   else
     {
-      // Nothing was modified.  Leave tab present in case user
-      // decides to cancel some point further along.
+      // Nothing was modified.  Possibly other files to check.
+      emit save_continue ();
     }
 
   return decision;
@@ -1313,16 +1298,18 @@ file_editor_tab::handle_file_modified_an
     {
       // Save file, but do not remove from editor.
       save_file (_file_name, false);
+      emit save_continue ();
     }
   else if (decision == QMessageBox::Discard)
     {
       // User doesn't want to save, leave tab and remove subsequently.
+      emit save_continue ();
     }
   else
     {
       // User canceled, allow editing again.
       _edit_area->setReadOnly (false);
-      _cancelled = true;
+      emit save_cancel ();
     }
 }
 
@@ -1965,9 +1952,7 @@ file_editor_tab::file_name_query (const 
   if (ID != this && ID != 0)
     return;
 
-  // Unnamed files shouldn't be transmitted.
-  if (!_file_name.isEmpty ())
-    emit add_filename_to_list (_file_name, this);
+  emit add_filename_to_list (_file_name, this);
 }
 
 void
diff --git a/libgui/src/m-editor/file-editor-tab.h b/libgui/src/m-editor/file-editor-tab.h
--- a/libgui/src/m-editor/file-editor-tab.h
+++ b/libgui/src/m-editor/file-editor-tab.h
@@ -54,9 +54,6 @@ public:
   // Will initiate close if associated with the identifier tag.
   bool conditional_close (void);
 
-  static void reset_cancel (void) {_cancelled = false;}
-  static bool was_cancelled (void) {return _cancelled;}
-
 public slots:
 
   void update_window_title (bool modified);
@@ -77,7 +74,6 @@ public slots:
   void set_current_directory (const QString& dir);
   void context_help (const QWidget *ID, bool);
   void context_edit (const QWidget *ID);
-  void check_modified_file (void);
   void save_file (const QWidget *ID);
   void save_file (const QWidget *ID, const QString& fileName,
                   bool remove_on_success);
@@ -132,13 +128,15 @@ signals:
   void file_name_changed (const QString& fileName, const QString& toolTip);
   void editor_state_changed (bool copy_available, bool is_octave_file);
   void tab_remove_request ();
-  void add_filename_to_list (const QString&, QWidget *);
+  void add_filename_to_list (const QString&, file_editor_tab *);
   void mru_add_file (const QString& file_name);
   void editor_check_conflict_save (const QString& saveFileName,
                                    bool remove_on_success);
   void run_file_signal (const QFileInfo& info);
   void set_global_edit_shortcuts_signal (bool);
   void request_open_file (const QString&);
+  void save_continue ();
+  void save_cancel ();
 
 protected:
 
@@ -174,6 +172,8 @@ private slots:
 
   void handle_cursor_moved (int line, int col);
 
+  int check_file_modified ();
+
 private:
 
   enum editor_markers
@@ -202,8 +202,7 @@ private:
   void request_add_breakpoint (int line);
   void request_remove_breakpoint (int line);
 
-  void show_dialog (QDialog *dlg, bool modal);
-  int check_file_modified ();
+  int show_dialog (QDialog *dlg, bool modal);
   void do_comment_selected_text (bool comment);
   QString comment_string (const QString&);
   void do_indent_selected_text (bool indent);
@@ -245,8 +244,6 @@ private:
 
   QsciAPIs *_lexer_apis;
   QString _prep_apis_file;
-
-  static bool _cancelled;
 };
 
 #endif
diff --git a/libgui/src/m-editor/file-editor.cc b/libgui/src/m-editor/file-editor.cc
--- a/libgui/src/m-editor/file-editor.cc
+++ b/libgui/src/m-editor/file-editor.cc
@@ -67,44 +67,103 @@ file_editor::~file_editor (void)
     delete _mru_file_menu;
 }
 
-bool
+void
 file_editor::check_closing (void)
 {
-  // Save open files for restoring in next session; this only is possible
+  // Check if another save/confirm is already in progress.
+  if (receivers (SIGNAL (fetab_check_modified_file (void))))
+    return;
+
+  // Save open files for restoring in next session
   QSettings *settings = resource_manager::get_settings ();
 
   // Have all file editor tabs signal what their file names are.
   editor_tab_map.clear ();
   emit fetab_file_name_query (0);
 
-  // save file names (even if last session will not be restored next time)
+  // Save file names (even if last session will not be restored next time)
   QStringList fetFileNames;
-  for (editor_tab_map_const_iterator p = editor_tab_map.begin ();
+  // And create linked-list of signals/slots
+  connect (this, SIGNAL (fetab_check_modified_file (void)),
+           this, SLOT (save_completed (void)));
+  for (editor_tab_map_const_iterator p = editor_tab_map.begin (), p_minus_1;
        p != editor_tab_map.end (); p++)
     {
-      QString file_name = p->first;
+      QString file_name = p->second;
+      // Do not append unnamed files
       if (!file_name.isEmpty ())
-        fetFileNames.append (p->first);  // do not append unnamed files
+        fetFileNames.append (file_name);
+      // Place a signal/slot link in the list of files.
+      if (p == editor_tab_map.begin ())
+        {
+          disconnect (this, SIGNAL (fetab_check_modified_file (void)), 0, 0);
+          connect (this, SIGNAL (fetab_check_modified_file (void)),
+                   p->first, SLOT (check_file_modified (void)));
+        }
+      else
+        {
+          disconnect (p_minus_1->first, SIGNAL (save_continue (void)), 0, 0);
+          connect (p_minus_1->first, SIGNAL (save_continue (void)),
+                   p->first, SLOT (check_file_modified (void)));
+        }
+      disconnect (p->first, SIGNAL (save_continue (void)), 0, 0);
+      connect (p->first, SIGNAL (save_continue (void)),
+               this, SLOT (save_completed (void)));
+      disconnect (p->first, SIGNAL (save_cancel (void)), 0, 0);
+      connect (p->first, SIGNAL (save_cancel (void)),
+               this, SLOT (save_cancelled (void)));
+
+      p_minus_1 = p;
     }
 
   settings->setValue ("editor/savedSessionTabs", fetFileNames);
   settings->sync ();
 
-  // Save all tabs with confirmation.
-  file_editor_tab::reset_cancel ();
+  // Start chain of saving tabs with confirmation.
   emit fetab_check_modified_file ();
+}
 
-  // Close all tabs if there was no cancellation.
-  if (file_editor_tab::was_cancelled ())
-    return false;
-
+void
+file_editor::close_all_tabs (void)
+{
   for (int i = 0; i < _tab_widget->count (); i++)
     {
       delete _tab_widget->widget (i);
       _tab_widget->removeTab (i);
     }
+}
 
-  return true;
+void
+file_editor::save_completed (void)
+{
+  disconnect (this, SIGNAL (fetab_check_modified_file (void)), 0, 0);
+
+  // The user may have created new files by saving unnamed files.
+  // Save open files for restoring in next session.
+  editor_tab_map.clear ();
+  emit fetab_file_name_query (0);
+  QStringList fetFileNames;
+  for (editor_tab_map_const_iterator p = editor_tab_map.begin ();
+       p != editor_tab_map.end (); p++)
+    {
+      QString file_name = p->second;
+      // Do not append unnamed files
+      if (!file_name.isEmpty ())
+        fetFileNames.append (file_name);
+    }
+  QSettings *settings = resource_manager::get_settings ();
+  settings->setValue ("editor/savedSessionTabs", fetFileNames);
+  settings->sync ();
+
+  close_all_tabs ();
+  emit save_finished (true);
+}
+
+void
+file_editor::save_cancelled (void)
+{
+  disconnect (this, SIGNAL (fetab_check_modified_file (void)), 0, 0);
+  emit save_finished (false);
 }
 
 void
@@ -252,11 +311,11 @@ file_editor::find_tab_widget (const QStr
   for (editor_tab_map_const_iterator p = editor_tab_map.begin ();
        p != editor_tab_map.end (); p++)
     {
-      QString tab_file = p->first;
+      QString tab_file = p->second;
 
       if (same_file (file.toStdString (), tab_file.toStdString ()))
         {
-          retval = p->second;
+          retval = p->first;
           break;
         }
     }
@@ -1015,11 +1074,10 @@ file_editor::handle_tab_remove_request (
 }
 
 void
-file_editor::handle_add_filename_to_list (const QString& fileName, QWidget *ID)
+file_editor::handle_add_filename_to_list (const QString& fileName, file_editor_tab *ID)
 {
   // Should we allow multiple tabs for a single file?
-
-  editor_tab_map[fileName] = ID;
+  editor_tab_map[ID] = fileName;
 }
 
 // context menu of edit area
@@ -1623,8 +1681,8 @@ file_editor::add_file_editor_tab (file_e
   connect (f, SIGNAL (tab_remove_request ()),
            this, SLOT (handle_tab_remove_request ()));
 
-  connect (f, SIGNAL (add_filename_to_list (const QString&, QWidget*)),
-           this, SLOT (handle_add_filename_to_list (const QString&, QWidget*)));
+  connect (f, SIGNAL (add_filename_to_list (const QString&, file_editor_tab*)),
+           this, SLOT (handle_add_filename_to_list (const QString&, file_editor_tab*)));
 
   connect (f, SIGNAL (editor_check_conflict_save (const QString&, bool)),
            this, SLOT (check_conflict_save (const QString&, bool)));
@@ -1653,10 +1711,8 @@ file_editor::add_file_editor_tab (file_e
 
   connect (this, SIGNAL (fetab_save_file (const QWidget*, const QString&,
                                           bool)),
-           f, SLOT (save_file (const QWidget*, const QString&, bool)));
-
-  connect (this, SIGNAL (fetab_check_modified_file (void)),
-           f, SLOT (check_modified_file (void)));
+           f, SLOT (save_file (const QWidget*, const QString&,
+                               bool)));
 
   // Signals from the file_editor trivial operations
   connect (this, SIGNAL (fetab_set_directory (const QString&)),
diff --git a/libgui/src/m-editor/file-editor.h b/libgui/src/m-editor/file-editor.h
--- a/libgui/src/m-editor/file-editor.h
+++ b/libgui/src/m-editor/file-editor.h
@@ -45,8 +45,8 @@ class file_editor : public file_editor_i
 
 public:
 
-  typedef std::map<QString, QWidget *>::iterator editor_tab_map_iterator;
-  typedef std::map<QString, QWidget *>::const_iterator editor_tab_map_const_iterator;
+  typedef std::map<file_editor_tab *, QString>::iterator editor_tab_map_iterator;
+  typedef std::map<file_editor_tab *, QString>::const_iterator editor_tab_map_const_iterator;
 
   file_editor (QWidget *p);
   ~file_editor (void);
@@ -64,6 +64,8 @@ public:
   void check_actions (void);
   void empty_script (bool startup, bool visible);
 
+  bool check_closing_in_progress (void) { return receivers (SIGNAL (fetab_check_modified_file (void))) != 0; }
+
 signals:
 
   void fetab_settings_changed (const QSettings *settings);
@@ -116,11 +118,16 @@ signals:
   void execute_command_in_terminal_signal (const QString&);
   void file_loaded_signal ();
 
+  void save_finished (bool closenow);
+
 public slots:
 
   void focus (void);
   void enable_menu_shortcuts (bool);
-  bool check_closing (void);
+  void check_closing (void);
+  void close_all_tabs (void);
+  void save_completed (void);
+  void save_cancelled (void);
 
   void request_new_file (const QString& commands);
   void request_new_script (const QString& commands);
@@ -187,7 +194,7 @@ public slots:
                                  const QString& toolTip);
   void handle_tab_close_request (int index);
   void handle_tab_remove_request (void);
-  void handle_add_filename_to_list (const QString& fileName, QWidget *ID);
+  void handle_add_filename_to_list (const QString& fileName, file_editor_tab *ID);
   void active_tab_changed (int index);
   void handle_editor_state_changed (bool enableCopy, bool is_octave_file);
   void handle_mru_add_file (const QString& file_name);
@@ -261,7 +268,7 @@ private:
 
   QMenu* m_add_menu (QMenuBar *p, QString text);
 
-  std::map<QString, QWidget *> editor_tab_map;
+  std::map<file_editor_tab *, QString> editor_tab_map;
   QHash<QMenu*, QStringList> _hash_menu_text;
 
   QString ced;
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,31 @@ 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.)  Make a
+  // connection to accept() instead.
+  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, bool start_gui)
   : QMainWindow (p),
     _workspace_model (start_gui ? new workspace_model () : 0),
@@ -97,7 +122,8 @@ main_window::main_window (QWidget *p, bo
     _dbg_queue_mutex (),
     _prevent_readline_conflicts (true),
     _suppress_dbg_location (true),
-    _start_gui (start_gui)
+    _start_gui (start_gui),
+    _exit_in_queue (false)
 {
   QSettings *settings = resource_manager::get_settings ();
 
@@ -793,8 +819,18 @@ main_window::confirm_shutdown_octave (vo
 {
   bool closenow = true;
 
-  if (_start_gui)
+  // 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;
+
+  if (_start_gui && ! _closing)
     {
+      // Emit signal indicating forced exit is no longer required.  (Items
+      // attached might be forced-exit dialog, etc.)
+      emit shutdown_initiated ();
+
       QSettings *settings = resource_manager::get_settings ();
 
       if (settings->value ("prompt_to_exit", false).toBool ())
@@ -809,10 +845,30 @@ main_window::confirm_shutdown_octave (vo
 
 #ifdef HAVE_QSCINTILLA
       if (closenow)
-        closenow = editor_window->check_closing ();
+        {
+          // Assume responsibility for answers to the confirm-save if
+          // it is already in process due to user forcing exit.
+          disconnect (editor_window, SIGNAL (save_finished (bool)), 0, 0);
+          connect (editor_window, SIGNAL (save_finished (bool)),
+                   this, SLOT (answer_shutdown_octave (bool)));
+          emit confirm_save_close ();
+          return;
+        }
 #endif
     }
 
+  answer_shutdown_octave (closenow);
+}
+
+void
+main_window::answer_shutdown_octave (bool closenow)
+{
+  // If confirmed, do not allow shutdown sequence again.  Doing
+  // so would mean something is wrong with the core's exit
+  // method and could lead to indefinite loop.
+  if (closenow)
+    _closing = true;
+
   // Wait for link thread to go to sleep state.
   _octave_qt_link->mutex.lock ();
 
@@ -822,7 +878,6 @@ main_window::confirm_shutdown_octave (vo
 
   // Awake the worker thread so that it continues shutting down (or not).
   _octave_qt_link->waitcondition.wakeAll ();
-
 }
 
 void
@@ -844,6 +899,53 @@ main_window::exit_app (int status)
 }
 
 void
+main_window::force_confirm_save_tabs (void)
+{
+  // This connection may be replaced by non-forced exit slot
+  // in the process of confirming file save.
+  connect (editor_window, SIGNAL (save_finished (bool)),
+           this, SLOT (force_exit_app (bool)));
+  emit confirm_save_close();
+}
+
+void
+main_window::force_exit_app (bool closenow)
+{
+  if (! closenow)
+    return;
+
+  // If confirmed, do not allow shutdown sequence again.  Doing
+  // so would mean something is wrong with the core's exit
+  // method and could lead to indefinite loop.
+  _closing = true;
+
+  // First interrupt will stop a process, so attempt exit.
+  _octave_qt_link->terminal_interrupt ();
+  octave_cmd_exec *cmd = new octave_cmd_exec ("exit");
+  queue_command (cmd);
+  _exit_in_queue = true;
+
+  // Give the core/worker two seconds to wrap up.
+  QTimer *timer = new QTimer ();
+  connect (timer, SIGNAL (timeout ()), this, SLOT (terminate_and_exit ()));
+  timer->setSingleShot (true);
+  timer->start (5000);
+}
+
+void
+main_window::terminate_and_exit (void)
+{
+  // Having reached this point, the core is really hung.  Terminating
+  // is suggested only as last resort.  (See Qt discussion boards on
+  // proper termination.)
+  _octave_qt_link->terminate_interpreter ();
+
+  // Could make non-zero return, but on some systems that causes dialog
+  // messages, which is to be avoided because the user intended to quit.
+  exit_app (0);
+}
+
+void
 main_window::reset_windows (void)
 {
   QSettings *settings = resource_manager::get_default_settings ();
@@ -1036,8 +1138,27 @@ void
 main_window::closeEvent (QCloseEvent *e)
 {
   e->ignore ();
-  octave_cmd_exec *cmd = new octave_cmd_exec ("exit");
-  queue_command (cmd);
+
+  // Ensure shutdown confirmation is not already in progress.
+  if (! editor_window->check_closing_in_progress ())
+    {
+      if (! _exit_in_queue)
+        {
+          octave_cmd_exec *cmd = new octave_cmd_exec ("exit");
+          queue_command (cmd);
+          _exit_in_queue = true;
+        }
+
+      if (! receivers (SIGNAL (shutdown_initiated ())))
+        {
+          // 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_confirm_save_tabs ()));
+          connect (this, SIGNAL (shutdown_initiated ()), dlg, SLOT (close ()));
+          dlg->setAttribute (Qt::WA_DeleteOnClose);
+        }
+    }
 }
 
 void
@@ -1447,6 +1568,10 @@ main_window::construct (void)
                SLOT (handle_update_breakpoint_marker_request (bool,
                                                               const QString&,
                                                               int)));
+      connect (this,
+               SIGNAL (confirm_save_close (void)),
+               editor_window,
+               SLOT (check_closing (void)));
 #endif
 
       octave_link::post_event (this, &main_window::resize_command_window_callback);
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"
@@ -107,6 +108,9 @@ signals:
 
   void add_actions_signal (QList <QAction *> action_list);
 
+  void shutdown_initiated (void);
+  void confirm_save_close (void);
+
 public slots:
 
   void focus_changed (QWidget *w_old, QWidget *w_new);
@@ -140,8 +144,12 @@ public slots:
   void show_about_octave (void);
   void notice_settings (const QSettings *settings);
   void confirm_shutdown_octave (void);
+  void answer_shutdown_octave (bool close_now);
   void prepare_to_exit (void);
   void exit_app (int status);
+  void force_confirm_save_tabs (void);
+  void force_exit_app (bool close_now);
+  void terminate_and_exit (void);
   void reset_windows (void);
 
   void change_directory (const QString& dir);
@@ -419,6 +427,7 @@ private:
   bool _prevent_readline_conflicts;
   bool _suppress_dbg_location;
   bool _start_gui;
+  bool _exit_in_queue;
 };
 
 class news_reader : public QObject
@@ -451,4 +460,24 @@ private:
   bool connect_to_web;
 };
 
+class ForceExitDialog : public QMessageBox
+{
+  Q_OBJECT
+
+public:
+
+  explicit ForceExitDialog (int msec = 2000);
+
+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
@@ -69,6 +69,13 @@ octave_qt_link::execute_interpreter (voi
   emit execute_interpreter_signal ();
 }
 
+void
+octave_qt_link::terminate_interpreter (void)
+{
+  main_thread->terminate ();
+  main_thread->wait ();
+}
+
 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 terminate_interpreter (void);
 
   bool do_confirm_shutdown (void);
   bool do_exit (int status);
exporting patch:
<fdopen>
