JAL-1988 JAL-3772 Quit confirmation dialog boxes with saving files check and wait
[jalview.git] / src / jalview / jbgui / QuitHandler.java
index 15cc37a..f7a30f3 100644 (file)
 package jalview.jbgui;
 
 import java.io.File;
+import java.util.Date;
 
 import javax.swing.JFrame;
 import javax.swing.JOptionPane;
 
 import com.formdev.flatlaf.extras.FlatDesktop;
 
+import jalview.bin.Console;
 import jalview.io.BackupFiles;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 public class QuitHandler
 {
+  public static enum QResponse
+  {
+    QUIT, CANCEL_QUIT, FORCE_QUIT
+  };
+
   public static void setQuitHandler()
   {
     FlatDesktop.setQuitHandler(response -> {
-      // confirm quit if needed and wanted
-      boolean confirmQuit = jalview.bin.Cache
-              .getDefault(jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
-      /* 
-         if undostack is empty
-           confirmQuit = false
-       */
-      int n = confirmQuit ? JOptionPane.CANCEL_OPTION
-              : JOptionPane.OK_OPTION;
-
-      // if going to confirm, do it before the save in progress check to give
-      // the save time to finish!
-      if (confirmQuit)
+      QResponse qresponse = getQuitResponse();
+      switch (qresponse)
       {
-        n = frameOnTop(MessageManager.getString("label.quit_jalview"),
-                MessageManager.getString("action.quit"),
-                JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
-
+      case QUIT:
+        response.performQuit();
+        break;
+      case CANCEL_QUIT:
+        response.cancelQuit();
+        break;
+      case FORCE_QUIT:
+        response.performQuit();
+        break;
+      default:
+        response.cancelQuit();
       }
+    });
+  }
+
+  private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT;
+
+  private static QResponse returnResponse(QResponse qresponse)
+  {
+    gotQuitResponse = qresponse;
+    return qresponse;
+  }
 
-      if (BackupFiles.hasSavesInProgress())
+  public static QResponse gotQuitResponse()
+  {
+    return gotQuitResponse;
+  }
+
+  public static QResponse getQuitResponse()
+  {
+    return getQuitResponse(true);
+  }
+
+  public static QResponse getQuitResponse(boolean ui)
+  {
+    if (gotQuitResponse() != QResponse.CANCEL_QUIT)
+    {
+      return returnResponse(getQuitResponse());
+    }
+
+    boolean interactive = ui && !Platform.isHeadless();
+    // confirm quit if needed and wanted
+    boolean confirmQuit = true;
+
+    if (!interactive)
+    {
+      Console.debug("Non interactive quit -- not confirming");
+      confirmQuit = false;
+    }
+    /* 
+    else if (undostack is empty) {
+      Console.debug("Nothing changed -- not confirming quit");
+      confirmQuit = false
+    }
+    */
+    else
+    {
+      confirmQuit = jalview.bin.Cache
+              .getDefault(jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
+      Console.debug("Jalview property '"
+              + jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT
+              + "' is/defaults to " + confirmQuit + " -- "
+              + (confirmQuit ? "" : "not ") + "confirming quit");
+    }
+
+    int answer = JOptionPane.OK_OPTION;
+
+    // if going to confirm, do it before the save in progress check to give
+    // the save time to finish!
+    if (confirmQuit)
+    {
+      answer = frameOnTop(MessageManager.getString("label.quit_jalview"),
+              MessageManager.getString("action.quit"),
+              JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
+    }
+
+    if (answer == JOptionPane.CANCEL_OPTION)
+    {
+      Console.debug("QuitHandler: Quit action cancelled by user");
+      return returnResponse(QResponse.CANCEL_QUIT);
+    }
+
+    // check for saves in progress
+    int waitForSave = 5000; // MAKE THIS BETTER
+    int waitIncrement = 2000;
+    long startTime = new Date().getTime();
+    boolean saving = BackupFiles.hasSavesInProgress();
+    if (saving)
+    {
+      boolean waiting = (new Date().getTime() - startTime) < waitForSave;
+      while (saving && waiting)
       {
-        // sleep 1
-        // ...
+        saving = !waitForSave(waitIncrement);
+        waiting = (new Date().getTime() - startTime) < waitForSave;
+      }
 
+      if (saving) // still saving after a wait
+      {
         StringBuilder messageSB = new StringBuilder(
                 MessageManager.getString("label.save_in_progress"));
         for (File file : BackupFiles.savesInProgressFiles())
@@ -47,26 +131,53 @@ public class QuitHandler
           messageSB.append("\n");
           messageSB.append(file.getName());
         }
-        n = frameOnTop(messageSB.toString(),
-                MessageManager.getString("action.force_quit"),
-                JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
+        int waitLonger = interactive ? JOptionPane.YES_OPTION
+                : JOptionPane.NO_OPTION;
+        while (saving && waitLonger == JOptionPane.YES_OPTION)
+        {
+          waitLonger = waitForceQuitCancelQuitOptionDialog(
+                  messageSB.toString(),
+                  MessageManager.getString("action.wait"));
+          if (waitLonger == JOptionPane.YES_OPTION) // wait
+          {
+            Console.debug("*** YES answer=" + waitLonger);
+            // do wait stuff
+            saving = !waitForSave(waitIncrement);
+          }
+          else if (waitLonger == JOptionPane.NO_OPTION) // force quit
+          {
+            Console.debug("*** NO answer=" + waitLonger);
+            // do a force quit
+            return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this
+          }
+          else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit
+          {
+            Console.debug("*** CANCEL answer=" + waitLonger);
+            return returnResponse(QResponse.CANCEL_QUIT);
+          }
+          else
+          {
+            Console.debug("**** Shouldn't have got here!");
+          }
+        }
       }
+    }
 
-      boolean canQuit = (n == JOptionPane.OK_OPTION);
-      if (canQuit)
-      {
-        response.performQuit();
-      }
-      else
-      {
-        response.cancelQuit();
-      }
-    });
+    // not cancelled and not saving
+    return returnResponse(QResponse.QUIT);
   }
 
   public static int frameOnTop(String label, String actionString,
           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
   {
+    return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION,
+            JOPTIONPANE_MESSAGETYPE);
+  }
+
+  public static int frameOnTop(JFrame dialogParent, String label,
+          String actionString, int JOPTIONPANE_OPTION,
+          int JOPTIONPANE_MESSAGETYPE)
+  {
     // ensure Jalview window is brought to front for Quit confirmation
     // window to be visible
 
@@ -76,16 +187,64 @@ public class QuitHandler
 
     // a better hack which works instead
 
-    JFrame dialogParent = new JFrame();
     dialogParent.setAlwaysOnTop(true);
 
-    int n = JOptionPane.showConfirmDialog(dialogParent, label, actionString,
-            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
+    int answer = JOptionPane.showConfirmDialog(dialogParent, label,
+            actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
 
     dialogParent.setAlwaysOnTop(false);
     dialogParent.dispose();
 
-    return n;
+    return answer;
+  }
+
+  private static int waitForceQuitCancelQuitOptionDialog(Object message,
+          String title)
+  {
+    JFrame dialogParent = new JFrame();
+    dialogParent.setAlwaysOnTop(true);
+    String wait = MessageManager.getString("action.wait");
+    Object[] options = { wait,
+        MessageManager.getString("action.force_quit"),
+        MessageManager.getString("action.cancel_quit") };
+
+    int answer = JOptionPane.showOptionDialog(dialogParent, message, title,
+            JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE,
+            null, options, wait);
+
+    return answer;
+  }
+
+  private static boolean waitForSave(long t)
+  {
+    boolean ret = false;
+    try
+    {
+      Console.debug("Wait for save to complete: " + t + "ms");
+      long c = 0;
+      int i = 100;
+      while (c < t)
+      {
+        Thread.sleep(i);
+        c += i;
+        ret = !BackupFiles.hasSavesInProgress();
+        if (ret)
+        {
+          Console.debug(
+                  "Save completed whilst waiting (" + c + "/" + t + "ms)");
+          return ret;
+        }
+        if (c % 1000 < i) // just gone over another second
+        {
+          Console.debug("...waiting (" + c + "/" + t + "ms]");
+        }
+      }
+    } catch (InterruptedException e)
+    {
+      Console.debug("Wait for save interrupted");
+    }
+    Console.debug("Save has " + (ret ? "" : "not ") + "completed");
+    return ret;
   }
 
 }