Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / gui / QuitHandler.java
index 77eed81..1b48c6d 100644 (file)
@@ -1,12 +1,33 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.gui;
 
 import java.io.File;
 import java.util.List;
-import java.util.concurrent.Callable;
+import java.util.Locale;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -16,6 +37,7 @@ import javax.swing.JOptionPane;
 import javax.swing.JTextPane;
 
 import com.formdev.flatlaf.extras.FlatDesktop;
+import com.formdev.flatlaf.extras.FlatDesktop.QuitResponse;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
@@ -35,36 +57,38 @@ public class QuitHandler
 
   private static boolean interactive = true;
 
+  private static QuitResponse flatlafResponse = null;
+
   public static enum QResponse
   {
     NULL, QUIT, CANCEL_QUIT, FORCE_QUIT
   };
 
+  public static enum Message
+  {
+    UNSAVED_CHANGES, UNSAVED_ALIGNMENTS
+  };
+
+  protected static Message message = Message.UNSAVED_CHANGES;
+
+  public static void setMessage(Message m)
+  {
+    message = m;
+  }
+
   private static ExecutorService executor = Executors.newFixedThreadPool(3);
 
-  public static QResponse setQuitHandler()
+  public static void setQuitHandler()
   {
     FlatDesktop.setQuitHandler(response -> {
-      Callable<Void> performQuit = () -> {
-        response.performQuit();
-        setResponse(QResponse.QUIT);
-        return null;
-      };
-      Callable<Void> performForceQuit = () -> {
-        response.performQuit();
-        setResponse(QResponse.FORCE_QUIT);
-        return null;
-      };
-      Callable<Void> cancelQuit = () -> {
-        response.cancelQuit();
-        // reset
-        setResponse(QResponse.NULL);
-        return null;
-      };
-      getQuitResponse(true, performQuit, performForceQuit, cancelQuit);
+      flatlafResponse = response;
+      Desktop.instance.desktopQuit();
     });
+  }
 
-    return gotQuitResponse();
+  public static void startForceQuit()
+  {
+    setResponse(QResponse.FORCE_QUIT);
   }
 
   private static QResponse gotQuitResponse = QResponse.NULL;
@@ -72,6 +96,11 @@ public class QuitHandler
   protected static QResponse setResponse(QResponse qresponse)
   {
     gotQuitResponse = qresponse;
+    if ((qresponse == QResponse.CANCEL_QUIT || qresponse == QResponse.NULL)
+            && flatlafResponse != null)
+    {
+      flatlafResponse.cancelQuit();
+    }
     return qresponse;
   }
 
@@ -80,25 +109,22 @@ public class QuitHandler
     return gotQuitResponse;
   }
 
-  public static final Callable<Void> defaultCancelQuit = () -> {
+  public static final Runnable defaultCancelQuit = () -> {
     Console.debug("QuitHandler: (default) Quit action CANCELLED by user");
     // reset
     setResponse(QResponse.CANCEL_QUIT);
-    return null;
   };
 
-  public static final Callable<Void> defaultOkQuit = () -> {
+  public static final Runnable defaultOkQuit = () -> {
     Console.debug("QuitHandler: (default) Quit action CONFIRMED by user");
     setResponse(QResponse.QUIT);
-    return null;
   };
 
-  public static final Callable<Void> defaultForceQuit = () -> {
+  public static final Runnable defaultForceQuit = () -> {
     Console.debug("QuitHandler: (default) Quit action FORCED by user");
     // note that shutdown hook will not be run
     Runtime.getRuntime().halt(0);
     setResponse(QResponse.FORCE_QUIT); // this line never reached!
-    return null;
   };
 
   public static QResponse getQuitResponse(boolean ui)
@@ -107,8 +133,8 @@ public class QuitHandler
             defaultCancelQuit);
   }
 
-  public static QResponse getQuitResponse(boolean ui, Callable<Void> okQuit,
-          Callable<Void> forceQuit, Callable<Void> cancelQuit)
+  public static QResponse getQuitResponse(boolean ui, Runnable okQuit,
+          Runnable forceQuit, Runnable cancelQuit)
   {
     QResponse got = gotQuitResponse();
     if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT)
@@ -145,25 +171,81 @@ public class QuitHandler
 
     if (confirmQuit)
     {
-      JvOptionPane.newOptionDialog()
+      String messageString = MessageManager
+              .getString(message == Message.UNSAVED_ALIGNMENTS
+                      ? "label.unsaved_alignments"
+                      : "label.unsaved_changes");
+      setQuitDialog(JvOptionPane.newOptionDialog()
               .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
-              .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit)
-              .showDialogOnTopAsync(
-                      new StringBuilder(MessageManager
-                              .getString("label.quit_jalview"))
-                              .append("\n")
-                              .append(MessageManager
-                                      .getString("label.unsaved_changes"))
+              .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit));
+      JvOptionPane qd = getQuitDialog();
+      qd.showDialogOnTopAsync(
+              new StringBuilder(
+                      MessageManager.getString("label.quit_jalview"))
+                              .append("\n").append(messageString)
                               .toString(),
-                      MessageManager.getString("action.quit"),
-                      JOptionPane.YES_NO_OPTION,
-                      JOptionPane.QUESTION_MESSAGE, null, new Object[]
-                      { MessageManager.getString("action.quit"),
-                          MessageManager.getString("action.cancel") },
-                      MessageManager.getString("action.quit"), true);
+              MessageManager.getString("action.quit"),
+              JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+              new Object[]
+              { MessageManager.getString("action.quit"),
+                  MessageManager.getString("action.cancel") },
+              MessageManager.getString("action.quit"), true);
     }
 
     got = gotQuitResponse();
+
+    // check for external viewer frames
+    if (got != QResponse.CANCEL_QUIT)
+    {
+      int count = Desktop.instance.structureViewersStillRunningCount();
+      if (count > 0)
+      {
+        String alwaysCloseExternalViewers = Cache
+                .getDefault("ALWAYS_CLOSE_EXTERNAL_VIEWERS", "ask");
+        String prompt = MessageManager
+                .formatMessage(count == 1 ? "label.confirm_quit_viewer"
+                        : "label.confirm_quit_viewers");
+        String title = MessageManager.getString(
+                count == 1 ? "label.close_viewer" : "label.close_viewers");
+        String cancelQuitText = MessageManager
+                .getString("action.cancel_quit");
+        String[] buttonsText = { MessageManager.getString("action.yes"),
+            MessageManager.getString("action.no"), cancelQuitText };
+
+        int confirmResponse = -1;
+        if (alwaysCloseExternalViewers == null || "ask".equals(
+                alwaysCloseExternalViewers.toLowerCase(Locale.ROOT)))
+        {
+          confirmResponse = JvOptionPane.showOptionDialog(Desktop.instance,
+                  prompt, title, JvOptionPane.YES_NO_CANCEL_OPTION,
+                  JvOptionPane.WARNING_MESSAGE, null, buttonsText,
+                  cancelQuit);
+        }
+        else
+        {
+          confirmResponse = Cache
+                  .getDefault("ALWAYS_CLOSE_EXTERNAL_VIEWERS", false)
+                          ? JvOptionPane.YES_OPTION
+                          : JvOptionPane.NO_OPTION;
+        }
+
+        if (confirmResponse == JvOptionPane.CANCEL_OPTION)
+        {
+          // Cancel Quit
+          QuitHandler.setResponse(QResponse.CANCEL_QUIT);
+        }
+        else
+        {
+          // Close viewers/Leave viewers open
+          StructureViewerBase
+                  .setQuitClose(confirmResponse == JvOptionPane.YES_OPTION);
+        }
+      }
+
+    }
+
+    got = gotQuitResponse();
+
     boolean wait = false;
     if (got == QResponse.CANCEL_QUIT)
     {
@@ -184,7 +266,7 @@ public class QuitHandler
       }
     }
 
-    Callable<Void> next = null;
+    Runnable next = null;
     switch (gotQuitResponse())
     {
     case QUIT:
@@ -201,6 +283,13 @@ public class QuitHandler
     {
       executor.submit(next).get();
       got = gotQuitResponse();
+    } catch (RejectedExecutionException e)
+    {
+      // QuitHander.abortQuit() probably called
+      // CANCEL_QUIT test will reset QuitHandler
+      Console.info("Quit aborted!");
+      got = QResponse.NULL;
+      setResponse(QResponse.NULL);
     } catch (InterruptedException | ExecutionException e)
     {
       jalview.bin.Console
@@ -208,25 +297,25 @@ public class QuitHandler
     }
     setResponse(got);
 
-    if (gotQuitResponse() == QResponse.CANCEL_QUIT)
+    if (quitCancelled())
     {
       // reset if cancelled
+      Console.debug("Quit cancelled");
       setResponse(QResponse.NULL);
       return QResponse.CANCEL_QUIT;
     }
     return gotQuitResponse();
   }
 
-  private static QResponse waitQuit(boolean interactive,
-          Callable<Void> okQuit, Callable<Void> forceQuit,
-          Callable<Void> cancelQuit)
+  private static QResponse waitQuit(boolean interactive, Runnable okQuit,
+          Runnable forceQuit, Runnable cancelQuit)
   {
     // check for saves in progress
     if (!BackupFiles.hasSavesInProgress())
       return QResponse.QUIT;
 
     int size = 0;
-    AlignFrame[] afArray = Desktop.getAlignFrames();
+    AlignFrame[] afArray = Desktop.getDesktopAlignFrames();
     if (!(afArray == null || afArray.length == 0))
     {
       for (int i = 0; i < afArray.length; i++)
@@ -305,8 +394,7 @@ public class QuitHandler
             }
             else
             {
-              if (!(QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT
-                      || QuitHandler.gotQuitResponse() == QResponse.NULL))
+              if (!(quitCancelled()))
               {
                 for (int i = 0; i < buttons.length; i++)
                 {
@@ -406,6 +494,31 @@ public class QuitHandler
 
   public static void abortQuit()
   {
-    setResponse(QResponse.CANCEL_QUIT);
+    setResponse(QResponse.NULL);
+    // executor.shutdownNow();
+  }
+
+  private static JvOptionPane quitDialog = null;
+
+  private static void setQuitDialog(JvOptionPane qd)
+  {
+    quitDialog = qd;
+  }
+
+  private static JvOptionPane getQuitDialog()
+  {
+    return quitDialog;
+  }
+
+  public static boolean quitCancelled()
+  {
+    return QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT
+            || QuitHandler.gotQuitResponse() == QResponse.NULL;
+  }
+
+  public static boolean quitting()
+  {
+    return QuitHandler.gotQuitResponse() == QResponse.QUIT
+            || QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT;
   }
-}
\ No newline at end of file
+}