JAL-1998 JAL-3772 JAL-3416 Merge conflict resolution of JalviewFileChooser and JvOpti...
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:53:36 +0000 (17:53 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:53:36 +0000 (17:53 +0000)
31 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/gui/APQHandlers.java [moved from src/jalview/jbgui/APQHandlers.java with 55% similarity]
src/jalview/gui/AlignExportOptions.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/Console.java
src/jalview/gui/Desktop.java
src/jalview/gui/EditNameDialog.java
src/jalview/gui/FeatureEditor.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/ImageExporter.java
src/jalview/gui/JvOptionPane.java
src/jalview/gui/LineartOptions.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/QuitHandler.java [new file with mode: 0644]
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/BackupFiles.java
src/jalview/io/FileLoader.java
src/jalview/io/HtmlSvgOutput.java
src/jalview/io/JalviewFileChooser.java
src/jalview/jbgui/GDesktop.java
src/jalview/project/Jalview2XML.java
src/jalview/util/dialogrunner/DialogRunnerI.java
src/jalview/viewmodel/AlignmentViewport.java

index 3843ddb..a406f33 100644 (file)
@@ -32,7 +32,17 @@ action.load_project = Load Project
 action.save_project = Save Project
 action.save_project_as = Save Project as...
 action.quit = Quit
-label.quit_jalview = Quit Jalview?
+action.force_quit = Force quit
+label.quit_jalview = Are you sure you want to quit Jalview?
+label.wait_for_save = Wait for save
+label.unsaved_changes = There are unsaved changes.
+label.save_in_progress = Some files are still saving:
+label.unknown = Unknown
+label.quit_after_saving = Jalview will quit after saving.
+label.all_saved = All files saved.
+label.quitting_bye = Quitting, bye!
+action.wait = Wait
+action.cancel_quit = Cancel quit
 action.expand_views = Expand Views
 action.gather_views = Gather Views
 action.page_setup = Page Setup...
index d0bfd65..b50226a 100644 (file)
@@ -32,7 +32,17 @@ action.load_project = Cargar proyecto
 action.save_project = Guardar proyecto
 action.save_project_as = Guardar proyecto como...
 action.quit = Salir
-label.quit_jalview = Salir de Jalview?
+action.force_quit = Forzar la salida
+label.quit_jalview = ¿Estás seguro de que quieres salir de Jalview?
+label.wait_for_save = Esperar a guardar
+label.unsaved_changes = Hay cambios sin guardar.
+label.save_in_progress = Algunos archivos aún se están guardando:
+label.unknown = desconocido
+label.quit_after_saving = Jalview se cerrará después de guardar.
+label.all_saved = Todos los archivos guardados.
+label.quitting_bye = Saliendo ¡chao!
+action.wait = Espere
+action.cancel_quit = Cancelar la salida
 action.expand_views = Expandir vistas
 action.gather_views = Capturar vistas
 action.page_setup = Configuración de la página
index 370a243..bb70c40 100755 (executable)
@@ -1190,6 +1190,7 @@ public class Cache
     sb.append("Java version: ");
     sb.append(System.getProperty("java.version"));
     sb.append("\n");
+    sb.append("Java platform: ");
     sb.append(System.getProperty("os.arch"));
     sb.append(" ");
     sb.append(System.getProperty("os.name"));
@@ -1210,17 +1211,19 @@ public class Cache
     sb.append(" (");
     sb.append(lafClass);
     sb.append(")\n");
+    appendIfNotNull(sb, "Channel: ",
+            ChannelProperties.getProperty("channel"), "\n", null);
     if (Console.isDebugEnabled()
             || !"release".equals(ChannelProperties.getProperty("channel")))
     {
-      appendIfNotNull(sb, "Channel: ",
-              ChannelProperties.getProperty("channel"), "\n", null);
       appendIfNotNull(sb, "Getdown appdir: ",
               System.getProperty("getdowninstanceappdir"), "\n", null);
       appendIfNotNull(sb, "Getdown appbase: ",
               System.getProperty("getdowninstanceappbase"), "\n", null);
       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
               "\n", "unknown");
+      appendIfNotNull(sb, "Preferences file: ", propertiesFile, "\n",
+              "unknown");
     }
     return sb.toString();
   }
@@ -1401,10 +1404,11 @@ public class Cache
                 if (customProxySet &&
                 // we have a username but no password for the scheme being
                 // requested
-                (protocol.equalsIgnoreCase("http")
-                        && (httpUser != null && httpUser.length() > 0
-                                && (httpPassword == null
-                                        || httpPassword.length == 0)))
+                        (protocol.equalsIgnoreCase("http")
+                                && (httpUser != null
+                                        && httpUser.length() > 0
+                                        && (httpPassword == null
+                                                || httpPassword.length == 0)))
                         || (protocol.equalsIgnoreCase("https")
                                 && (httpsUser != null
                                         && httpsUser.length() > 0
index 9f37540..b87a14d 100755 (executable)
@@ -61,6 +61,8 @@ import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.PromptUserConfig;
+import jalview.gui.QuitHandler;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
@@ -271,6 +273,28 @@ public class Jalview
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
+
+      Runtime.getRuntime().addShutdownHook(new Thread()
+      {
+        public void run()
+        {
+          Console.debug("Running shutdown hook");
+          if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
+          {
+            // Got to here by a SIGTERM signal.
+            // Note we will not actually cancel the quit from here -- it's too
+            // late -- but we can wait for saving files.
+            Console.debug("Checking for saving files");
+            QuitHandler.getQuitResponse(false);
+          }
+          else
+          {
+            Console.debug("Nothing more to do");
+          }
+          Console.debug("Exiting, bye!");
+          // shutdownHook cannot be cancelled, JVM will now halt
+        }
+      });
     }
 
     System.out
@@ -279,6 +303,7 @@ public class Jalview
     System.out.println(System.getProperty("os.arch") + " "
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
+
     String val = System.getProperty("sys.install4jVersion");
     if (val != null)
     {
@@ -299,10 +324,12 @@ public class Jalview
     Cache.loadBuildProperties(true);
 
     ArgsParser aparser = new ArgsParser(args);
+
     boolean headless = false;
 
     String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this before
+    Cache.loadProperties(usrPropsFile); // must do this
+                                        // before
     if (usrPropsFile != null)
     {
       System.out.println(
@@ -380,7 +407,9 @@ public class Jalview
     try
     {
       Console.initLogger();
-    } catch (NoClassDefFoundError error)
+    } catch (
+
+    NoClassDefFoundError error)
     {
       error.printStackTrace();
       System.out.println("\nEssential logging libraries not found."
@@ -555,8 +584,11 @@ public class Jalview
     }
 
     String file = null, data = null;
+
     FileFormatI format = null;
+
     DataSourceType protocol = null;
+
     FileLoader fileLoader = new FileLoader(!headless);
 
     String groovyscript = null; // script to execute after all loading is
@@ -570,6 +602,7 @@ public class Jalview
       System.out.println("No files to open!");
       System.exit(1);
     }
+
     long progress = -1;
     // Finally, deal with the remaining input data.
     if (file != null)
@@ -829,6 +862,7 @@ public class Jalview
         }
       }
     }
+
     AlignFrame startUpAlframe = null;
     // We'll only open the default file if the desktop is visible.
     // And the user
@@ -1371,19 +1405,12 @@ public class Jalview
   }
 
   /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
+   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
    */
   public void quit()
   {
-    if (desktop != null)
-    {
-      desktop.quit();
-    }
-    else
-    {
-      System.exit(0);
-    }
+    // System.exit will run the shutdownHook first
+    System.exit(0);
   }
 
   public static AlignFrame getCurrentAlignFrame()
similarity index 55%
rename from src/jalview/jbgui/APQHandlers.java
rename to src/jalview/gui/APQHandlers.java
index 1a7e971..00ec217 100644 (file)
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
-package jalview.jbgui;
-
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
+package jalview.gui;
 
 import com.formdev.flatlaf.extras.FlatDesktop;
 import com.formdev.flatlaf.extras.FlatDesktop.Action;
 
-import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 public class APQHandlers
@@ -37,7 +33,7 @@ public class APQHandlers
 
   public static boolean setQuit = false;
 
-  public static boolean setAPQHandlers(GDesktop desktop)
+  public static boolean setAPQHandlers(Desktop desktop)
   {
     if (Platform.isJS())
     {
@@ -59,47 +55,7 @@ public class APQHandlers
     }
     if (FlatDesktop.isSupported(Action.APP_QUIT_HANDLER))
     {
-      FlatDesktop.setQuitHandler(response -> {
-        boolean confirmQuit = jalview.bin.Cache.getDefault(
-                jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
-        boolean canQuit = !confirmQuit;
-        int n;
-        if (confirmQuit)
-        {
-          // ensure Jalview window is brought to front for Quit confirmation
-          // window to be visible
-
-          // this method of raising the Jalview window is broken in java
-          // jalviewDesktop.setVisible(true);
-          // jalviewDesktop.toFront();
-
-          // a better hack which works instead
-          JFrame dialogParent = new JFrame();
-          dialogParent.setAlwaysOnTop(true);
-
-          n = JOptionPane.showConfirmDialog(dialogParent,
-                  MessageManager.getString("label.quit_jalview"),
-                  MessageManager.getString("action.quit"),
-                  JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
-                  null);
-
-          dialogParent.setAlwaysOnTop(false);
-          dialogParent.dispose();
-        }
-        else
-        {
-          n = JOptionPane.OK_OPTION;
-        }
-        canQuit = (n == JOptionPane.OK_OPTION);
-        if (canQuit)
-        {
-          response.performQuit();
-        }
-        else
-        {
-          response.cancelQuit();
-        }
-      });
+      QuitHandler.setQuitHandler();
       setQuit = true;
     }
     // if we got to here, no exceptions occurred when we set the handlers.
index 08ff021..a23cbfa 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignExportSettingsI;
-import jalview.api.AlignViewportI;
-import jalview.io.FileFormatI;
-import jalview.util.MessageManager;
-
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.util.concurrent.Callable;
 
 import javax.swing.JCheckBox;
 import javax.swing.JPanel;
 
+import jalview.api.AlignExportSettingsI;
+import jalview.api.AlignViewportI;
+import jalview.io.FileFormatI;
+import jalview.util.MessageManager;
+
 /**
  * A dialog that allows the user to specify whether to include hidden columns or
  * sequences in an alignment export, and possibly features, annotations and
@@ -119,7 +120,7 @@ public class AlignExportOptions extends JPanel
    * 
    * @param action
    */
-  public void setResponseAction(Object response, Runnable action)
+  public void setResponseAction(Object response, Callable action)
   {
     dialog.setResponseHandler(response, action);
   }
index abc7437..c11b866 100644 (file)
@@ -59,6 +59,7 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JCheckBoxMenuItem;
@@ -1260,6 +1261,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
               shortName);
 
+      Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+      if (lastSaveSuccessful)
+      {
+        this.getViewport().setSavedUpToDate(true);
+      }
+
       statusBar.setText(MessageManager.formatMessage(
               "label.successfully_saved_to_file_in_format", new Object[]
               { file, format }));
@@ -1268,97 +1275,89 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable cancelAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> cancelAction = () -> {
+      lastSaveSuccessful = false;
+      return null;
+    };
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      String output = new FormatAdapter(alignPanel, options)
+              .formatSequences(format, exportData.getAlignment(),
+                      exportData.getOmitHidden(),
+                      exportData.getStartEndPostions(),
+                      viewport.getAlignment().getHiddenColumns());
+      if (output == null)
       {
         lastSaveSuccessful = false;
       }
-    };
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+      else
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        String output = new FormatAdapter(alignPanel, options)
-                .formatSequences(format, exportData.getAlignment(),
-                        exportData.getOmitHidden(),
-                        exportData.getStartEndPostions(),
-                        viewport.getAlignment().getHiddenColumns());
-        if (output == null)
+        // create backupfiles object and get new temp filename destination
+        boolean doBackup = BackupFiles.getEnabled();
+        BackupFiles backupfiles = null;
+        if (doBackup)
         {
-          lastSaveSuccessful = false;
+          Console.trace("ALIGNFRAME making backupfiles object for " + file);
+          backupfiles = new BackupFiles(file);
         }
-        else
+        try
         {
-          // create backupfiles object and get new temp filename destination
-          boolean doBackup = BackupFiles.getEnabled();
-          BackupFiles backupfiles = null;
-          if (doBackup)
+          String tempFilePath = doBackup ? backupfiles.getTempFilePath()
+                  : file;
+          Console.trace("ALIGNFRAME setting PrintWriter");
+          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
+
+          if (backupfiles != null)
           {
-            Console.trace(
-                    "ALIGNFRAME making backupfiles object for " + file);
-            backupfiles = new BackupFiles(file);
+            Console.trace("ALIGNFRAME about to write to temp file "
+                    + backupfiles.getTempFilePath());
           }
-          try
-          {
-            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
-                    : file;
-            Console.trace("ALIGNFRAME setting PrintWriter");
-            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 
-            if (backupfiles != null)
-            {
-              Console.trace("ALIGNFRAME about to write to temp file "
-                      + backupfiles.getTempFilePath());
-            }
+          out.print(output);
+          Console.trace("ALIGNFRAME about to close file");
+          out.close();
+          Console.trace("ALIGNFRAME closed file");
+          AlignFrame.this.setTitle(file);
+          statusBar.setText(MessageManager.formatMessage(
+                  "label.successfully_saved_to_file_in_format", new Object[]
+                  { fileName, format.getName() }));
+          lastSaveSuccessful = true;
+        } catch (IOException e)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something happened writing the temp file");
+          Console.error(e.getMessage());
+          Console.debug(Cache.getStackTraceString(e));
+        } catch (Exception ex)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something unexpected happened writing the temp file");
+          Console.error(ex.getMessage());
+          Console.debug(Cache.getStackTraceString(ex));
+        }
 
-            out.print(output);
-            Console.trace("ALIGNFRAME about to close file");
-            out.close();
-            Console.trace("ALIGNFRAME closed file");
-            AlignFrame.this.setTitle(file);
-            statusBar.setText(MessageManager.formatMessage(
-                    "label.successfully_saved_to_file_in_format",
-                    new Object[]
-                    { fileName, format.getName() }));
-            lastSaveSuccessful = true;
-          } catch (IOException e)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something happened writing the temp file");
-            Console.error(e.getMessage());
-            Console.debug(Cache.getStackTraceString(e));
-          } catch (Exception ex)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something unexpected happened writing the temp file");
-            Console.error(ex.getMessage());
-            Console.debug(Cache.getStackTraceString(ex));
-          }
+        if (doBackup)
+        {
+          backupfiles.setWriteSuccess(lastSaveSuccessful);
+          Console.debug("ALIGNFRAME writing temp file was "
+                  + (lastSaveSuccessful ? "" : "NOT ") + "successful");
+          // do the backup file roll and rename the temp file to actual file
+          Console.trace("ALIGNFRAME about to rollBackupsAndRenameTempFile");
+          lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
+          Console.debug("ALIGNFRAME performed rollBackupsAndRenameTempFile "
+                  + (lastSaveSuccessful ? "" : "un") + "successfully");
+        }
 
-          if (doBackup)
-          {
-            backupfiles.setWriteSuccess(lastSaveSuccessful);
-            Console.debug("ALIGNFRAME writing temp file was "
-                    + (lastSaveSuccessful ? "" : "NOT ") + "successful");
-            // do the backup file roll and rename the temp file to actual file
-            Console.trace(
-                    "ALIGNFRAME about to rollBackupsAndRenameTempFile");
-            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
-            Console.debug(
-                    "ALIGNFRAME performed rollBackupsAndRenameTempFile "
-                            + (lastSaveSuccessful ? "" : "un")
-                            + "successfully");
-          }
+        Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+        if (lastSaveSuccessful)
+        {
+          AlignFrame.this.getViewport().setSavedUpToDate(true);
         }
       }
+      return null;
     };
 
     /*
@@ -1374,7 +1373,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1391,34 +1397,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     FileFormatI fileFormat = FileFormats.getInstance()
             .forName(fileFormatName);
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      CutAndPasteTransfer cap = new CutAndPasteTransfer();
+      cap.setForInput(null);
+      try
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        CutAndPasteTransfer cap = new CutAndPasteTransfer();
-        cap.setForInput(null);
-        try
-        {
-          FileFormatI format = fileFormat;
-          cap.setText(new FormatAdapter(alignPanel, options)
-                  .formatSequences(format, exportData.getAlignment(),
-                          exportData.getOmitHidden(),
-                          exportData.getStartEndPostions(),
-                          viewport.getAlignment().getHiddenColumns()));
-          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-                  "label.alignment_output_command", new Object[]
-                  { fileFormat.getName() }), 600, 500);
-        } catch (OutOfMemoryError oom)
-        {
-          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
-                  oom);
-          cap.dispose();
-        }
+        FileFormatI format = fileFormat;
+        cap.setText(new FormatAdapter(alignPanel, options).formatSequences(
+                format, exportData.getAlignment(),
+                exportData.getOmitHidden(),
+                exportData.getStartEndPostions(),
+                viewport.getAlignment().getHiddenColumns()));
+        Desktop.addInternalFrame(cap, MessageManager.formatMessage(
+                "label.alignment_output_command", new Object[]
+                { fileFormat.getName() }), 600, 500);
+      } catch (OutOfMemoryError oom)
+      {
+        new OOMWarning("Outputting alignment as " + fileFormat.getName(),
+                oom);
+        cap.dispose();
       }
+      return null;
     };
 
     /*
@@ -1433,7 +1434,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1541,15 +1548,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .getString("label.load_jalview_annotations");
     chooser.setDialogTitle(tooltip);
     chooser.setToolTipText(tooltip);
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
+      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -2471,36 +2474,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Runnable okAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        SequenceI[] cut = sg.getSequences()
-                .toArray(new SequenceI[sg.getSize()]);
+    Callable okAction = () -> {
+      SequenceI[] cut = sg.getSequences()
+              .toArray(new SequenceI[sg.getSize()]);
 
-        addHistoryItem(new EditCommand(
-                MessageManager.getString("label.cut_sequences"), Action.CUT,
-                cut, sg.getStartRes(),
-                sg.getEndRes() - sg.getStartRes() + 1,
-                viewport.getAlignment()));
+      addHistoryItem(new EditCommand(
+              MessageManager.getString("label.cut_sequences"), Action.CUT,
+              cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
+              viewport.getAlignment()));
 
-        viewport.setSelectionGroup(null);
-        viewport.sendSelection();
-        viewport.getAlignment().deleteGroup(sg);
+      viewport.setSelectionGroup(null);
+      viewport.sendSelection();
+      viewport.getAlignment().deleteGroup(sg);
 
-        viewport.firePropertyChange("alignment", null,
-                viewport.getAlignment().getSequences());
-        if (viewport.getAlignment().getHeight() < 1)
+      viewport.firePropertyChange("alignment", null,
+              viewport.getAlignment().getSequences());
+      if (viewport.getAlignment().getHeight() < 1)
+      {
+        try
+        {
+          AlignFrame.this.setClosed(true);
+        } catch (Exception ex)
         {
-          try
-          {
-            AlignFrame.this.setClosed(true);
-          } catch (Exception ex)
-          {
-          }
         }
       }
+      return null;
     };
 
     /*
@@ -2524,7 +2522,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      okAction.run();
+      try
+      {
+        okAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -4078,36 +4082,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setToolTipText(
             MessageManager.getString("label.load_tree_file"));
 
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
+    chooser.setResponseHandler(0, () -> {
+      String filePath = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", filePath);
+      NewickFile fin = null;
+      try
       {
-        String filePath = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", filePath);
-        NewickFile fin = null;
-        try
-        {
-          fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
-                  DataSourceType.FILE));
-          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
-        } catch (Exception ex)
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
-                  MessageManager
-                          .getString("label.problem_reading_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-          ex.printStackTrace();
-        }
-        if (fin != null && fin.hasWarningMessage())
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop,
-                  fin.getWarningMessage(),
-                  MessageManager.getString(
-                          "label.possible_problem_with_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-        }
+        fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
+                DataSourceType.FILE));
+        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
+      } catch (Exception ex)
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
+                MessageManager.getString("label.problem_reading_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
+        ex.printStackTrace();
       }
+      if (fin != null && fin.hasWarningMessage())
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop,
+                fin.getWarningMessage(),
+                MessageManager
+                        .getString("label.possible_problem_with_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
+      }
+      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -5879,16 +5878,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
     final AlignFrame us = this;
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
-        new VCFLoader(choice).loadVCF(seqs, us);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
+      new VCFLoader(choice).loadVCF(seqs, us);
+      return null;
     });
     chooser.showOpenDialog(null);
 
index 30ccdbe..e7c237e 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.api.AlignViewportI;
@@ -53,17 +64,6 @@ import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.AutoCalcSetting;
 
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.swing.JInternalFrame;
-
 /**
  * DOCUMENT ME!
  * 
@@ -748,27 +748,15 @@ public class AlignViewport extends AlignmentViewport
      * in reverse order)
      */
     JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
-            .setResponseHandler(0, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                addDataToAlignment(al);
-              }
-            }).setResponseHandler(1, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, true);
-              }
-            }).setResponseHandler(2, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, false);
-              }
+            .setResponseHandler(0, () -> {
+              addDataToAlignment(al);
+              return null;
+            }).setResponseHandler(1, () -> {
+              us.openLinkedAlignmentAs(al, title, true);
+              return null;
+            }).setResponseHandler(2, () -> {
+              us.openLinkedAlignmentAs(al, title, false);
+              return null;
             });
     dialog.showDialog(question,
             MessageManager.getString("label.open_split_window"),
index 9976604..4702f2a 100755 (executable)
  */
 package jalview.gui;
 
-import java.util.Locale;
-
-import jalview.analysis.AlignSeq;
-import jalview.analysis.AlignmentUtils;
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.io.FileFormat;
-import jalview.io.FormatAdapter;
-import jalview.util.Comparison;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-
 import java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Dimension;
@@ -56,6 +39,7 @@ import java.awt.geom.AffineTransform;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Locale;
 
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JMenuItem;
@@ -64,6 +48,21 @@ import javax.swing.JPopupMenu;
 import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentUtils;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.io.FileFormat;
+import jalview.io.FormatAdapter;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
 /**
  * The panel that holds the labels for alignment annotations, providing
  * tooltips, context menus, drag to reorder rows, and drag to adjust panel
@@ -293,25 +292,21 @@ public class AnnotationLabels extends JPanel
     EditNameDialog dialog = new EditNameDialog(annotation.label,
             annotation.description, name, description);
 
-    dialog.showDialog(ap.alignFrame, title, new Runnable()
-    {
-      @Override
-      public void run()
+    dialog.showDialog(ap.alignFrame, title, () -> {
+      annotation.label = dialog.getName();
+      String text = dialog.getDescription();
+      if (text != null && text.length() == 0)
       {
-        annotation.label = dialog.getName();
-        String text = dialog.getDescription();
-        if (text != null && text.length() == 0)
-        {
-          text = null;
-        }
-        annotation.description = text;
-        if (addNew)
-        {
-          ap.av.getAlignment().addAnnotation(annotation);
-          ap.av.getAlignment().setAnnotationIndex(annotation, 0);
-        }
-        ap.refresh(true);
+        text = null;
       }
+      annotation.description = text;
+      if (addNew)
+      {
+        ap.av.getAlignment().addAnnotation(annotation);
+        ap.av.getAlignment().setAnnotationIndex(annotation, 0);
+      }
+      ap.refresh(true);
+      return null;
     });
   }
 
index 9cf2cc9..5a23048 100644 (file)
@@ -114,6 +114,7 @@ public class Console extends WindowAdapter
     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
     frame = initFrame("Java Console", screenSize.width / 2,
             screenSize.height / 2, -1, -1);
+    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     initConsole(true);
   }
 
@@ -551,10 +552,12 @@ public class Console extends WindowAdapter
       {
       }
     }
+    /*
     if (!frame.isVisible())
     {
       frame.dispose();
     }
+    */
     // System.exit(0);
   }
 
index 20532be..2605195 100644 (file)
@@ -64,6 +64,7 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
@@ -81,6 +82,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
+import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
@@ -90,6 +92,7 @@ import javax.swing.JProgressBar;
 import javax.swing.JTextField;
 import javax.swing.KeyStroke;
 import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkEvent.EventType;
 import javax.swing.event.InternalFrameAdapter;
@@ -102,6 +105,7 @@ import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.BackupFiles;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
@@ -459,13 +463,14 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     setIconImages(ChannelProperties.getIconList());
 
+    // override quit handling when GUI OS close [X] button pressed
+    this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
     addWindowListener(new WindowAdapter()
     {
-
       @Override
       public void windowClosing(WindowEvent ev)
       {
-        quit();
+        QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
       }
     });
 
@@ -579,15 +584,6 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
 
-    this.addWindowListener(new WindowAdapter()
-    {
-      @Override
-      public void windowClosing(WindowEvent evt)
-      {
-        quit();
-      }
-    });
-
     MouseAdapter ma;
     this.addMouseListener(ma = new MouseAdapter()
     {
@@ -1188,36 +1184,32 @@ public class Desktop extends jalview.jbgui.GDesktop
             MessageManager.getString("label.open_local_file"));
     chooser.setToolTipText(MessageManager.getString("action.open"));
 
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File selectedFile = chooser.getSelectedFile();
-        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
+    chooser.setResponseHandler(0, () -> {
+      File selectedFile = chooser.getSelectedFile();
+      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 
-        FileFormatI format = chooser.getSelectedFormat();
+      FileFormatI format = chooser.getSelectedFormat();
 
-        /*
-         * Call IdentifyFile to verify the file contains what its extension implies.
-         * Skip this step for dynamically added file formats, because IdentifyFile does
-         * not know how to recognise them.
-         */
-        if (FileFormats.getInstance().isIdentifiable(format))
+      /*
+       * Call IdentifyFile to verify the file contains what its extension implies.
+       * Skip this step for dynamically added file formats, because IdentifyFile does
+       * not know how to recognise them.
+       */
+      if (FileFormats.getInstance().isIdentifiable(format))
+      {
+        try
         {
-          try
-          {
-            format = new IdentifyFile().identify(selectedFile,
-                    DataSourceType.FILE);
-          } catch (FileFormatException e)
-          {
-            // format = null; //??
-          }
+          format = new IdentifyFile().identify(selectedFile,
+                  DataSourceType.FILE);
+        } catch (FileFormatException e)
+        {
+          // format = null; //??
         }
-
-        new FileLoader().LoadFile(viewport, selectedFile,
-                DataSourceType.FILE, format);
       }
+
+      new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
+              format);
+      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -1273,64 +1265,60 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
-    Runnable action = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        @SuppressWarnings("unchecked")
-        String url = (history instanceof JTextField
-                ? ((JTextField) history).getText()
-                : ((JComboBox<String>) history).getEditor().getItem()
-                        .toString().trim());
+    Callable<Void> action = () -> {
+      @SuppressWarnings("unchecked")
+      String url = (history instanceof JTextField
+              ? ((JTextField) history).getText()
+              : ((JComboBox<String>) history).getEditor().getItem()
+                      .toString().trim());
 
-        if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
+      if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
+      {
+        if (viewport != null)
         {
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    FileFormat.Jalview);
-          }
-          else
-          {
-            new FileLoader().LoadFile(url, DataSourceType.URL,
-                    FileFormat.Jalview);
-          }
+          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                  FileFormat.Jalview);
         }
         else
         {
-          FileFormatI format = null;
-          try
-          {
-            format = new IdentifyFile().identify(url, DataSourceType.URL);
-          } catch (FileFormatException e)
-          {
-            // TODO revise error handling, distinguish between
-            // URL not found and response not valid
-          }
+          new FileLoader().LoadFile(url, DataSourceType.URL,
+                  FileFormat.Jalview);
+        }
+      }
+      else
+      {
+        FileFormatI format = null;
+        try
+        {
+          format = new IdentifyFile().identify(url, DataSourceType.URL);
+        } catch (FileFormatException e)
+        {
+          // TODO revise error handling, distinguish between
+          // URL not found and response not valid
+        }
 
-          if (format == null)
-          {
-            String msg = MessageManager
-                    .formatMessage("label.couldnt_locate", url);
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
-                    MessageManager.getString("label.url_not_found"),
-                    JvOptionPane.WARNING_MESSAGE);
+        if (format == null)
+        {
+          String msg = MessageManager.formatMessage("label.couldnt_locate",
+                  url);
+          JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
+                  MessageManager.getString("label.url_not_found"),
+                  JvOptionPane.WARNING_MESSAGE);
 
-            return;
-          }
+          return null; // Void
+        }
 
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    format);
-          }
-          else
-          {
-            new FileLoader().LoadFile(url, DataSourceType.URL, format);
-          }
+        if (viewport != null)
+        {
+          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                  format);
+        }
+        else
+        {
+          new FileLoader().LoadFile(url, DataSourceType.URL, format);
         }
       }
+      return null; // Void
     };
     String dialogOption = MessageManager
             .getString("label.input_alignment_from_url");
@@ -1360,39 +1348,79 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /*
-   * Exit the program
+   * Check with user and saving files before actually quitting
    */
-  @Override
-  public void quit()
+  public void desktopQuit()
   {
-    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
-    Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
-    Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
-    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
-            getWidth(), getHeight()));
+    desktopQuit(true, false);
+  }
 
-    if (jconsole != null)
-    {
-      storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
-      jconsole.stopConsole();
-    }
-    if (jvnews != null)
-    {
-      storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
+  public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
+  {
+    final Callable<Void> doDesktopQuit = () -> {
+      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+      Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
+      Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
+      storeLastKnownDimensions("", new Rectangle(getBounds().x,
+              getBounds().y, getWidth(), getHeight()));
 
-    }
-    if (dialogExecutor != null)
-    {
-      dialogExecutor.shutdownNow();
-    }
-    closeAll_actionPerformed(null);
+      if (jconsole != null)
+      {
+        storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
+        jconsole.stopConsole();
+      }
 
-    if (groovyConsole != null)
-    {
-      // suppress a possible repeat prompt to save script
-      groovyConsole.setDirty(false);
-      groovyConsole.exit();
-    }
+      if (jvnews != null)
+      {
+        storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
+
+      }
+
+      if (dialogExecutor != null)
+      {
+        dialogExecutor.shutdownNow();
+      }
+
+      closeAll_actionPerformed(null);
+
+      if (groovyConsole != null)
+      {
+        // suppress a possible repeat prompt to save script
+        groovyConsole.setDirty(false);
+        groovyConsole.exit();
+      }
+
+      if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
+      {
+        // note that shutdown hook will not be run
+        jalview.bin.Console.debug("Force Quit selected by user");
+        Runtime.getRuntime().halt(0);
+      }
+
+      jalview.bin.Console.debug("Quit selected by user");
+      if (disposeFlag)
+      {
+        instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        // instance.dispose();
+      }
+      instance.quit();
+
+      return null; // Void
+    };
+
+    return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
+            QuitHandler.defaultCancelQuit);
+  }
+
+  /**
+   * Don't call this directly, use desktopQuit() above. Exits the program.
+   */
+  @Override
+  public void quit()
+  {
+    // this will run the shutdownHook but QuitHandler.getQuitResponse() should
+    // not run a second time if gotQuitResponse flag has been set (i.e. user
+    // confirmed quit of some kind).
     System.exit(0);
   }
 
@@ -1830,42 +1858,37 @@ public class Desktop extends jalview.jbgui.GDesktop
     // allowBackupFiles
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
+    chooser.setResponseHandler(0, () -> {
+      File selectedFile = chooser.getSelectedFile();
+      setProjectFile(selectedFile);
+      String choice = selectedFile.getAbsolutePath();
+      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
+      new Thread(new Runnable()
       {
-        File selectedFile = chooser.getSelectedFile();
-        setProjectFile(selectedFile);
-        String choice = selectedFile.getAbsolutePath();
-        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
-        new Thread(new Runnable()
+        @Override
+        public void run()
         {
-          @Override
-          public void run()
+          try
           {
-            try
-            {
-              new Jalview2XML().loadJalviewAlign(selectedFile);
-            } catch (OutOfMemoryError oom)
-            {
-              new OOMWarning("Whilst loading project from " + choice, oom);
-            } catch (Exception ex)
-            {
-              jalview.bin.Console.error(
-                      "Problems whilst loading project from " + choice, ex);
-              JvOptionPane.showMessageDialog(Desktop.desktop,
-                      MessageManager.formatMessage(
-                              "label.error_whilst_loading_project_from",
-                              new Object[]
-                              { choice }),
-                      MessageManager
-                              .getString("label.couldnt_load_project"),
-                      JvOptionPane.WARNING_MESSAGE);
-            }
+            new Jalview2XML().loadJalviewAlign(selectedFile);
+          } catch (OutOfMemoryError oom)
+          {
+            new OOMWarning("Whilst loading project from " + choice, oom);
+          } catch (Exception ex)
+          {
+            jalview.bin.Console.error(
+                    "Problems whilst loading project from " + choice, ex);
+            JvOptionPane.showMessageDialog(Desktop.desktop,
+                    MessageManager.formatMessage(
+                            "label.error_whilst_loading_project_from",
+                            new Object[]
+                            { choice }),
+                    MessageManager.getString("label.couldnt_load_project"),
+                    JvOptionPane.WARNING_MESSAGE);
           }
-        }, "Project Loader").start();
-      }
+        }
+      }, "Project Loader").start();
+      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -2529,7 +2552,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        desktopQuit();
       }
     });
   }
index f7225f5..ff0fe3a 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import java.awt.FlowLayout;
 import java.awt.Font;
+import java.util.concurrent.Callable;
 
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
@@ -111,7 +112,7 @@ public class EditNameDialog
    * 
    * @param action
    */
-  public void showDialog(JComponent parent, String title, Runnable action)
+  public void showDialog(JComponent parent, String title, Callable action)
   {
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
index 844eee4..ba9da67 100644 (file)
@@ -33,6 +33,7 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
@@ -427,8 +428,9 @@ public class FeatureEditor
    */
   public void showDialog()
   {
-    Runnable okAction = forCreate ? getCreateAction() : getAmendAction();
-    Runnable cancelAction = getCancelAction();
+    Callable<Void> okAction = forCreate ? getCreateAction()
+            : getAmendAction();
+    Callable<Void> cancelAction = getCancelAction();
 
     /*
      * set dialog action handlers for OK (create/Amend) and Cancel options
@@ -474,16 +476,12 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getCancelAction()
+  protected Callable getCancelAction()
   {
-    Runnable okAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        ap.highlightSearchResults(null);
-        ap.paintAlignment(false, false);
-      }
+    Callable<Void> okAction = () -> {
+      ap.highlightSearchResults(null);
+      ap.paintAlignment(false, false);
+      return null;
     };
     return okAction;
   }
@@ -498,14 +496,14 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getCreateAction()
+  protected Callable getCreateAction()
   {
-    Runnable okAction = new Runnable()
+    Callable<Void> okAction = new Callable()
     {
       boolean useLastDefaults = features.get(0).getType() == null;
 
       @Override
-      public void run()
+      public Void call()
       {
         final String enteredType = name.getText().trim();
         final String enteredGroup = group.getText().trim();
@@ -545,6 +543,7 @@ public class FeatureEditor
 
           repaintPanel();
         }
+        return null;
       }
     };
     return okAction;
@@ -557,19 +556,15 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getDeleteAction()
+  protected Callable getDeleteAction()
   {
-    Runnable deleteAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        SequenceFeature sf = features.get(featureIndex);
-        sequences.get(0).getDatasetSequence().deleteFeature(sf);
-        fr.featuresAdded();
-        ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
-        ap.paintAlignment(true, true);
-      }
+    Callable<Void> deleteAction = () -> {
+      SequenceFeature sf = features.get(featureIndex);
+      sequences.get(0).getDatasetSequence().deleteFeature(sf);
+      fr.featuresAdded();
+      ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
+      ap.paintAlignment(true, true);
+      return null;
     };
     return deleteAction;
   }
@@ -660,9 +655,9 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getAmendAction()
+  protected Callable getAmendAction()
   {
-    Runnable okAction = new Runnable()
+    Callable<Void> okAction = new Callable()
     {
       boolean useLastDefaults = features.get(0).getType() == null;
 
@@ -671,7 +666,7 @@ public class FeatureEditor
       String featureGroup = group.getText();
 
       @Override
-      public void run()
+      public Void call()
       {
         final String enteredType = name.getText().trim();
         final String enteredGroup = group.getText().trim();
@@ -734,6 +729,7 @@ public class FeatureEditor
           fr.featuresAdded();
         }
         repaintPanel();
+        return null;
       }
     };
     return okAction;
index 922cc45..ebd4712 100644 (file)
@@ -948,14 +948,10 @@ public class FeatureSettings extends JPanel
     chooser.setDialogTitle(
             MessageManager.getString("label.load_feature_colours"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File file = chooser.getSelectedFile();
-        load(file);
-      }
+    chooser.setResponseHandler(0, () -> {
+      File file = chooser.getSelectedFile();
+      load(file);
+      return null;
     });
     chooser.showOpenDialog(this);
   }
index ce1cb46..d849ba2 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Component;
+import java.awt.Graphics;
+import java.io.File;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.io.JalviewFileChooser;
@@ -29,11 +35,6 @@ import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
-import java.awt.Component;
-import java.awt.Graphics;
-import java.io.File;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 /**
  * A class that marshals steps in exporting a view in image graphics format
  * <ul>
@@ -155,25 +156,22 @@ public class ImageExporter
             && !Jalview.isHeadlessMode())
     {
       final File chosenFile = file;
-      Runnable okAction = new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          exportImage(chosenFile, !textSelected.get(), width, height,
-                  messageId);
-        }
+      Callable<Void> okAction = () -> {
+        exportImage(chosenFile, !textSelected.get(), width, height,
+                messageId);
+        return null;
       };
       LineartOptions epsOption = new LineartOptions(TYPE.EPS.getName(),
               textSelected);
-      epsOption.setResponseAction(1, new Runnable()
+      epsOption.setResponseAction(1, new Callable<Void>()
       {
         @Override
-        public void run()
+        public Void call()
         {
           setStatus(MessageManager.formatMessage(
                   "status.cancelled_image_export_operation",
                   imageType.getName()), messageId);
+          return null;
         }
       });
       epsOption.setResponseAction(0, okAction);
index 0b684d0..752f25c 100644 (file)
@@ -42,15 +42,18 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JDialog;
+import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 import javax.swing.event.InternalFrameEvent;
 import javax.swing.event.InternalFrameListener;
@@ -69,7 +72,7 @@ public class JvOptionPane extends JOptionPane
 
   private Component parentComponent;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable<Void>> callbacks = new HashMap<>();
 
   /*
    * JalviewJS reports user choice in the dialog as the selected
@@ -80,7 +83,6 @@ public class JvOptionPane extends JOptionPane
   public JvOptionPane(final Component parent)
   {
     this.parentComponent = Platform.isJS() ? this : parent;
-    this.setIcon(WindowIcons.logoIcon);
   }
 
   public static int showConfirmDialog(Component parentComponent,
@@ -770,6 +772,11 @@ public class JvOptionPane extends JOptionPane
    * @param string2
    * @return
    */
+  public static JvOptionPane newOptionDialog()
+  {
+    return new JvOptionPane(null);
+  }
+
   public static JvOptionPane newOptionDialog(Component parentComponent)
   {
     return new JvOptionPane(parentComponent);
@@ -782,10 +789,18 @@ public class JvOptionPane extends JOptionPane
             initialValue, true);
   }
 
-  public void showDialog(String message, String title, int optionType,
+  public void showDialog(Object message, String title, int optionType,
           int messageType, Icon icon, Object[] options, Object initialValue,
           boolean modal)
   {
+    showDialog(message, title, optionType, messageType, icon, options,
+            initialValue, modal, null);
+  }
+
+  public void showDialog(Object message, String title, int optionType,
+          int messageType, Icon icon, Object[] options, Object initialValue,
+          boolean modal, JButton[] buttons)
+  {
     if (!isInteractiveMode())
     {
       handleResponse(getMockResponse());
@@ -809,9 +824,98 @@ public class JvOptionPane extends JOptionPane
 
     if (modal)
     {
+      boolean useButtons = false;
+      Object initialValueButton = null;
+      NOTNULL: if (buttons != null)
+      {
+        if (buttons.length != options.length)
+        {
+          jalview.bin.Console.error(
+                  "Supplied buttons array not the same length as supplied options array.");
+          break NOTNULL;
+        }
+        int[] buttonActions = { JOptionPane.YES_OPTION,
+            JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
+        for (int i = 0; i < options.length; i++)
+        {
+          Object o = options[i];
+          jalview.bin.Console.debug(
+                  "Setting button " + i + " to '" + o.toString() + "'");
+          JButton jb = buttons[i];
+
+          if (o.equals(initialValue))
+            initialValueButton = jb;
+
+          int buttonAction = buttonActions[i];
+          Callable<Void> action = callbacks.get(buttonAction);
+          jb.setText((String) o);
+          jb.addActionListener(new ActionListener()
+          {
+            @Override
+            public void actionPerformed(ActionEvent e)
+            {
+
+              Object obj = e.getSource();
+              if (obj == null || !(obj instanceof Component))
+              {
+                jalview.bin.Console.debug(
+                        "Could not find Component source of event object "
+                                + obj);
+                return;
+              }
+              Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
+                      JOptionPane.class, (Component) obj);
+              if (joptionpaneObject == null
+                      || !(joptionpaneObject instanceof JOptionPane))
+              {
+                jalview.bin.Console.debug(
+                        "Could not find JOptionPane ancestor of event object "
+                                + obj);
+                return;
+              }
+              JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
+              joptionpane.setValue(buttonAction);
+              if (action != null)
+                Executors.newSingleThreadExecutor().submit(action);
+              joptionpane.transferFocusBackward();
+              joptionpane.setVisible(false);
+              // put focus and raise parent window if possible, unless cancel or
+              // no button pressed
+              boolean raiseParent = (parentComponent != null);
+              if (buttonAction == JOptionPane.CANCEL_OPTION)
+                raiseParent = false;
+              if (optionType == JOptionPane.YES_NO_OPTION
+                      && buttonAction == JOptionPane.NO_OPTION)
+                raiseParent = false;
+              if (raiseParent)
+              {
+                parentComponent.requestFocus();
+                if (parentComponent instanceof JInternalFrame)
+                {
+                  JInternalFrame jif = (JInternalFrame) parentComponent;
+                  jif.show();
+                  jif.moveToFront();
+                  jif.grabFocus();
+                }
+                else if (parentComponent instanceof Window)
+                {
+                  Window w = (Window) parentComponent;
+                  w.toFront();
+                  w.requestFocus();
+                }
+              }
+              joptionpane.setVisible(false);
+            }
+          });
+
+        }
+        useButtons = true;
+      }
       // use a JOptionPane as usual
       int response = JOptionPane.showOptionDialog(parentComponent, message,
-              title, optionType, messageType, icon, options, initialValue);
+              title, optionType, messageType, icon,
+              useButtons ? buttons : options,
+              useButtons ? initialValueButton : initialValue);
 
       /*
        * In Java, the response is returned to this thread and handled here;
@@ -861,8 +965,7 @@ public class JvOptionPane extends JOptionPane
 
       ArrayList<JButton> options_btns = new ArrayList<>();
       Object initialValue_btn = null;
-      if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
-                            // add them here
+      if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here
       {
         for (int i = 0; i < options.length && i < 3; i++)
         {
@@ -951,6 +1054,7 @@ public class JvOptionPane extends JOptionPane
     this.setMessage(mainPanel);
 
     ourOptions = Arrays.asList(options);
+    int response;
     if (parentComponent != this)
     {
       JInternalFrame jif = this.createInternalFrame(parentComponent, title);
@@ -1023,14 +1127,103 @@ public class JvOptionPane extends JOptionPane
     }
   }
 
+  /*
   @Override
   public JvOptionPane setResponseHandler(Object response, Runnable action)
   {
+    callbacks.put(response, new Callable<Void>()
+    {
+      @Override
+      public Void call()
+      {
+        action.run();
+        return null;
+      }
+    });
+    return this;
+  }
+  */
+  @Override
+  public JvOptionPane setResponseHandler(Object response,
+          Callable<Void> action)
+  {
     callbacks.put(response, action);
     return this;
   }
 
   /**
+   * showDialogOnTop will create a dialog that (attempts to) come to top of OS
+   * desktop windows
+   */
+  public static int showDialogOnTop(String label, String actionString,
+          int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
+  {
+    // Ensure Jalview window is brought to front (primarily for Quit
+    // confirmation window to be visible)
+
+    // This method of raising the Jalview window is broken in java
+    // jalviewDesktop.setVisible(true);
+    // jalviewDesktop.toFront();
+
+    // A better hack which works is to create a new JFrame parent with
+    // setAlwaysOnTop(true)
+    JFrame dialogParent = new JFrame();
+    dialogParent.setAlwaysOnTop(true);
+
+    int answer = JOptionPane.showConfirmDialog(dialogParent, label,
+            actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
+
+    dialogParent.setAlwaysOnTop(false);
+    dialogParent.dispose();
+
+    return answer;
+  }
+
+  public void showDialogOnTopAsync(String label, String actionString,
+          int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
+          Object[] options, Object initialValue, boolean modal)
+  {
+    showDialogOnTopAsync(new JFrame(), label, actionString,
+            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
+            initialValue, modal);
+  }
+
+  public void showDialogOnTopAsync(JFrame dialogParent, Object label,
+          String actionString, int JOPTIONPANE_OPTION,
+          int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
+          Object initialValue, boolean modal)
+  {
+    showDialogOnTopAsync(dialogParent, label, actionString,
+            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
+            initialValue, modal, null);
+  }
+
+  public void showDialogOnTopAsync(JFrame dialogParent, Object label,
+          String actionString, int JOPTIONPANE_OPTION,
+          int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
+          Object initialValue, boolean modal, JButton[] buttons)
+  {
+    // Ensure Jalview window is brought to front (primarily for Quit
+    // confirmation window to be visible)
+
+    // This method of raising the Jalview window is broken in java
+    // jalviewDesktop.setVisible(true);
+    // jalviewDesktop.toFront();
+
+    // A better hack which works is to create a new JFrame parent with
+    // setAlwaysOnTop(true)
+    dialogParent.setAlwaysOnTop(true);
+    parentComponent = dialogParent;
+
+    showDialog(label, actionString, JOPTIONPANE_OPTION,
+            JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
+            buttons);
+
+    dialogParent.setAlwaysOnTop(false);
+    dialogParent.dispose();
+  }
+
+  /**
    * JalviewJS signals option selection by a property change event for the
    * option e.g. "OK". This methods responds to that by running the response
    * action that corresponds to that option.
@@ -1063,15 +1256,166 @@ public class JvOptionPane extends JOptionPane
     {
       return;
     }
-    Runnable action = callbacks.get(response);
+    Callable<Void> action = callbacks.get(response);
     if (action != null)
     {
-      action.run();
-      parentComponent.requestFocus();
+      try
+      {
+        action.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
+      if (parentComponent != null)
+        parentComponent.requestFocus();
     }
   }
 
   /**
+   * Create a non-modal confirm dialog
+   */
+  public JDialog createDialog(Component parentComponent, Object message,
+          String title, int optionType, int messageType, Icon icon,
+          Object[] options, Object initialValue, boolean modal)
+  {
+    return createDialog(parentComponent, message, title, optionType,
+            messageType, icon, options, initialValue, modal, null);
+  }
+
+  public JDialog createDialog(Component parentComponent, Object message,
+          String title, int optionType, int messageType, Icon icon,
+          Object[] options, Object initialValue, boolean modal,
+          JButton[] buttons)
+  {
+    JButton[] optionsButtons = null;
+    Object initialValueButton = null;
+    JOptionPane joptionpane = new JOptionPane();
+    // Make button options
+    int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
+        JOptionPane.CANCEL_OPTION };
+
+    // we need the strings to make the buttons with actionEventListener
+    if (options == null)
+    {
+      ArrayList<String> options_default = new ArrayList<>();
+      options_default.add(UIManager.getString("OptionPane.yesButtonText"));
+      if (optionType == JOptionPane.YES_NO_OPTION
+              || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
+      {
+        options_default.add(UIManager.getString("OptionPane.noButtonText"));
+      }
+      if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
+      {
+        options_default
+                .add(UIManager.getString("OptionPane.cancelButtonText"));
+      }
+      options = options_default.toArray();
+    }
+    if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
+                          // add them here
+    {
+      if (((optionType == JOptionPane.YES_OPTION
+              || optionType == JOptionPane.NO_OPTION
+              || optionType == JOptionPane.CANCEL_OPTION
+              || optionType == JOptionPane.OK_OPTION
+              || optionType == JOptionPane.DEFAULT_OPTION)
+              && options.length < 1)
+              || ((optionType == JOptionPane.YES_NO_OPTION
+                      || optionType == JOptionPane.OK_CANCEL_OPTION)
+                      && options.length < 2)
+              || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
+                      && options.length < 3))
+      {
+        jalview.bin.Console
+                .debug("JvOptionPane: not enough options for dialog type");
+      }
+      optionsButtons = new JButton[options.length];
+      for (int i = 0; i < options.length && i < 3; i++)
+      {
+        Object o = options[i];
+        int buttonAction = buttonActions[i];
+        Callable<Void> action = callbacks.get(buttonAction);
+        JButton jb;
+        if (buttons != null && buttons.length > i && buttons[i] != null)
+        {
+          jb = buttons[i];
+        }
+        else
+        {
+          jb = new JButton();
+        }
+        jb.setText((String) o);
+        jb.addActionListener(new ActionListener()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            joptionpane.setValue(buttonAction);
+            if (action != null)
+              Executors.newSingleThreadExecutor().submit(action);
+            // joptionpane.transferFocusBackward();
+            joptionpane.transferFocusBackward();
+            joptionpane.setVisible(false);
+            // put focus and raise parent window if possible, unless cancel
+            // button pressed
+            boolean raiseParent = (parentComponent != null);
+            if (buttonAction == JOptionPane.CANCEL_OPTION)
+              raiseParent = false;
+            if (optionType == JOptionPane.YES_NO_OPTION
+                    && buttonAction == JOptionPane.NO_OPTION)
+              raiseParent = false;
+            if (raiseParent)
+            {
+              parentComponent.requestFocus();
+              if (parentComponent instanceof JInternalFrame)
+              {
+                JInternalFrame jif = (JInternalFrame) parentComponent;
+                jif.show();
+                jif.moveToFront();
+                jif.grabFocus();
+              }
+              else if (parentComponent instanceof Window)
+              {
+                Window w = (Window) parentComponent;
+                w.toFront();
+                w.requestFocus();
+              }
+            }
+            joptionpane.setVisible(false);
+          }
+        });
+        optionsButtons[i] = jb;
+        if (o.equals(initialValue))
+          initialValueButton = jb;
+      }
+    }
+    joptionpane.setMessage(message);
+    joptionpane.setMessageType(messageType);
+    joptionpane.setOptionType(optionType);
+    joptionpane.setIcon(icon);
+    joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
+    joptionpane.setInitialValue(
+            Platform.isJS() ? initialValue : initialValueButton);
+
+    JDialog dialog = joptionpane.createDialog(parentComponent, title);
+    dialog.setModalityType(
+            modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
+    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+    return dialog;
+  }
+
+  /**
+   * Utility to programmatically click a button on a JOptionPane (as a JFrame)
+   * 
+   * returns true if button was found
+   */
+  public static boolean clickButton(JFrame frame, int buttonType)
+  {
+
+    return false;
+  }
+
+  /**
    * This helper method makes the JInternalFrame wait until it is notified by an
    * InternalFrameClosing event. This method also adds the given JOptionPane to
    * the JInternalFrame and sizes it according to the JInternalFrame's preferred
index d55733c..8a530ac 100644 (file)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
-import jalview.util.MessageManager;
-
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.swing.BorderFactory;
@@ -35,6 +33,9 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 
+import jalview.bin.Cache;
+import jalview.util.MessageManager;
+
 /**
  * A dialog where the user may choose Text or Lineart rendering, and optionally
  * save this as a preference ("Don't ask me again")
@@ -95,7 +96,7 @@ public class LineartOptions extends JPanel
    * 
    * @param action
    */
-  public void setResponseAction(Object response, Runnable action)
+  public void setResponseAction(Object response, Callable action)
   {
     dialog.setResponseHandler(response, action);
   }
index f4a19ff..1c03d6a 100644 (file)
@@ -1986,15 +1986,11 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             MessageManager.getString("label.group_description"));
     dialog.showDialog(ap.alignFrame,
             MessageManager.getString("label.edit_group_name_description"),
-            new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                sg.setName(dialog.getName());
-                sg.setDescription(dialog.getDescription());
-                refresh();
-              }
+            () -> {
+              sg.setName(dialog.getName());
+              sg.setDescription(dialog.getDescription());
+              refresh();
+              return null;
             });
   }
 
@@ -2026,30 +2022,26 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             sequence.getDescription(),
             MessageManager.getString("label.sequence_name"),
             MessageManager.getString("label.sequence_description"));
-    dialog.showDialog(ap.alignFrame, MessageManager.getString(
-            "label.edit_sequence_name_description"), new Runnable()
-            {
-              @Override
-              public void run()
+    dialog.showDialog(ap.alignFrame, MessageManager
+            .getString("label.edit_sequence_name_description"), () -> {
+              if (dialog.getName() != null)
               {
-                if (dialog.getName() != null)
+                if (dialog.getName().indexOf(" ") > -1)
                 {
-                  if (dialog.getName().indexOf(" ") > -1)
-                  {
-                    JvOptionPane.showMessageDialog(ap,
-                            MessageManager.getString(
-                                    "label.spaces_converted_to_underscores"),
-                            MessageManager.getString(
-                                    "label.no_spaces_allowed_sequence_name"),
-                            JvOptionPane.WARNING_MESSAGE);
-                  }
-                  sequence.setName(dialog.getName().replace(' ', '_'));
-                  ap.paintAlignment(false, false);
+                  JvOptionPane.showMessageDialog(ap,
+                          MessageManager.getString(
+                                  "label.spaces_converted_to_underscores"),
+                          MessageManager.getString(
+                                  "label.no_spaces_allowed_sequence_name"),
+                          JvOptionPane.WARNING_MESSAGE);
                 }
-                sequence.setDescription(dialog.getDescription());
-                ap.av.firePropertyChange("alignment", null,
-                        ap.av.getAlignment().getSequences());
+                sequence.setName(dialog.getName().replace(' ', '_'));
+                ap.paintAlignment(false, false);
               }
+              sequence.setDescription(dialog.getDescription());
+              ap.av.firePropertyChange("alignment", null,
+                      ap.av.getAlignment().getSequences());
+              return null;
             });
   }
 
@@ -2271,25 +2263,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
               seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1),
               null, MessageManager.getString("label.edit_sequence"), null);
       dialog.showDialog(ap.alignFrame,
-              MessageManager.getString("label.edit_sequence"),
-              new Runnable()
-              {
-                @Override
-                public void run()
-                {
-                  EditCommand editCommand = new EditCommand(
-                          MessageManager.getString("label.edit_sequences"),
-                          Action.REPLACE,
-                          dialog.getName().replace(' ',
-                                  ap.av.getGapCharacter()),
-                          sg.getSequencesAsArray(
-                                  ap.av.getHiddenRepSequences()),
-                          sg.getStartRes(), sg.getEndRes() + 1,
-                          ap.av.getAlignment());
-                  ap.alignFrame.addHistoryItem(editCommand);
-                  ap.av.firePropertyChange("alignment", null,
-                          ap.av.getAlignment().getSequences());
-                }
+              MessageManager.getString("label.edit_sequence"), () -> {
+                EditCommand editCommand = new EditCommand(
+                        MessageManager.getString("label.edit_sequences"),
+                        Action.REPLACE,
+                        dialog.getName().replace(' ',
+                                ap.av.getGapCharacter()),
+                        sg.getSequencesAsArray(
+                                ap.av.getHiddenRepSequences()),
+                        sg.getStartRes(), sg.getEndRes() + 1,
+                        ap.av.getAlignment());
+                ap.alignFrame.addHistoryItem(editCommand);
+                ap.av.firePropertyChange("alignment", null,
+                        ap.av.getAlignment().getSequences());
+                return null;
               });
     }
   }
diff --git a/src/jalview/gui/QuitHandler.java b/src/jalview/gui/QuitHandler.java
new file mode 100644 (file)
index 0000000..e90a2d5
--- /dev/null
@@ -0,0 +1,410 @@
+package jalview.gui;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JTextPane;
+
+import com.formdev.flatlaf.extras.FlatDesktop;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.BackupFiles;
+import jalview.project.Jalview2XML;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+public class QuitHandler
+{
+  private static final int MIN_WAIT_FOR_SAVE = 3000;
+
+  private static final int MAX_WAIT_FOR_SAVE = 20000;
+
+  public static enum QResponse
+  {
+    NULL, QUIT, CANCEL_QUIT, FORCE_QUIT
+  };
+
+  private static ExecutorService executor = Executors.newFixedThreadPool(3);
+
+  public static QResponse 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);
+    });
+
+    return gotQuitResponse();
+  }
+
+  private static QResponse gotQuitResponse = QResponse.NULL;
+
+  private static QResponse setResponse(QResponse qresponse)
+  {
+    gotQuitResponse = qresponse;
+    return qresponse;
+  }
+
+  public static QResponse gotQuitResponse()
+  {
+    return gotQuitResponse;
+  }
+
+  public static final Callable<Void> defaultCancelQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CANCELLED by user");
+    // reset
+    setResponse(QResponse.NULL);
+    return null;
+  };
+
+  public static final Callable<Void> defaultOkQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CONFIRMED by user");
+    setResponse(QResponse.QUIT);
+    return null;
+  };
+
+  public static final Callable<Void> 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)
+  {
+    return getQuitResponse(ui, defaultOkQuit, defaultForceQuit,
+            defaultCancelQuit);
+  }
+
+  private static boolean interactive = true;
+
+  public static QResponse getQuitResponse(boolean ui, Callable<Void> okQuit,
+          Callable<Void> forceQuit, Callable<Void> cancelQuit)
+  {
+    QResponse got = gotQuitResponse();
+    if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT)
+    {
+      // quit has already been selected, continue with calling quit method
+      return got;
+    }
+
+    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 (Jalview2XML.allSavedUpToDate())
+    {
+      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");
+    }
+    got = confirmQuit ? QResponse.NULL : QResponse.QUIT;
+    setResponse(got);
+
+    if (confirmQuit)
+    {
+      JvOptionPane.newOptionDialog()
+              .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
+              .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit)
+              .showDialogOnTopAsync(
+                      new StringBuilder(MessageManager
+                              .getString("label.quit_jalview"))
+                                      .append("\n")
+                                      .append(MessageManager.getString(
+                                              "label.unsaved_changes"))
+                                      .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);
+    }
+
+    got = gotQuitResponse();
+    boolean wait = false;
+    if (got == QResponse.CANCEL_QUIT)
+    {
+      // reset
+      setResponse(QResponse.NULL);
+      // but return cancel
+      return QResponse.CANCEL_QUIT;
+    }
+    else if (got == QResponse.QUIT)
+    {
+      if (Cache.getDefault("WAIT_FOR_SAVE", true)
+              && BackupFiles.hasSavesInProgress())
+      {
+        waitQuit(interactive, okQuit, forceQuit, cancelQuit);
+        QResponse waitResponse = gotQuitResponse();
+        wait = waitResponse == QResponse.QUIT;
+      }
+    }
+
+    Callable<Void> next = null;
+    switch (gotQuitResponse())
+    {
+    case QUIT:
+      next = okQuit;
+      break;
+    case FORCE_QUIT: // not actually an option at this stage
+      next = forceQuit;
+      break;
+    default:
+      next = cancelQuit;
+      break;
+    }
+    try
+    {
+      executor.submit(next).get();
+      got = gotQuitResponse();
+    } catch (InterruptedException | ExecutionException e)
+    {
+      jalview.bin.Console
+              .debug("Exception during quit handling (final choice)", e);
+    }
+    setResponse(got);
+
+    if (gotQuitResponse() == QResponse.CANCEL_QUIT)
+    {
+      // reset if 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)
+  {
+    // check for saves in progress
+    if (!BackupFiles.hasSavesInProgress())
+      return QResponse.QUIT;
+
+    int size = 0;
+    AlignFrame[] afArray = Desktop.getAlignFrames();
+    if (!(afArray == null || afArray.length == 0))
+    {
+      for (int i = 0; i < afArray.length; i++)
+      {
+        AlignFrame af = afArray[i];
+        List<? extends AlignmentViewPanel> avpList = af.getAlignPanels();
+        for (AlignmentViewPanel avp : avpList)
+        {
+          AlignmentI a = avp.getAlignment();
+          List<SequenceI> sList = a.getSequences();
+          for (SequenceI s : sList)
+          {
+            size += s.getLength();
+          }
+        }
+      }
+    }
+    int waitTime = Math.min(MAX_WAIT_FOR_SAVE,
+            Math.max(MIN_WAIT_FOR_SAVE, size / 2));
+    Console.debug("Set waitForSave to " + waitTime);
+
+    int iteration = 0;
+    boolean doIterations = true; // note iterations not used in the gui now,
+                                 // only one pass without the "Wait" button
+    while (doIterations && BackupFiles.hasSavesInProgress()
+            && iteration++ < (interactive ? 100 : 5))
+    {
+      // future that returns a Boolean when all files are saved
+      CompletableFuture<Boolean> filesAllSaved = new CompletableFuture<>();
+
+      // callback as each file finishes saving
+      for (CompletableFuture<Boolean> cf : BackupFiles
+              .savesInProgressCompletableFutures(false))
+      {
+        // if this is the last one then complete filesAllSaved
+        cf.whenComplete((ret, e) -> {
+          if (!BackupFiles.hasSavesInProgress())
+          {
+            filesAllSaved.complete(true);
+          }
+        });
+      }
+      try
+      {
+        filesAllSaved.get(waitTime, TimeUnit.MILLISECONDS);
+      } catch (InterruptedException | ExecutionException e1)
+      {
+        Console.debug(
+                "Exception whilst waiting for files to save before quit",
+                e1);
+      } catch (TimeoutException e2)
+      {
+        // this Exception to be expected
+      }
+
+      if (interactive && BackupFiles.hasSavesInProgress())
+      {
+        boolean showForceQuit = iteration > 0; // iteration > 1 to not show
+                                               // force quit the first time
+        JFrame parent = new JFrame();
+        JButton[] buttons = { new JButton(), new JButton() };
+        JvOptionPane waitDialog = JvOptionPane.newOptionDialog();
+        JTextPane messagePane = new JTextPane();
+        messagePane.setBackground(waitDialog.getBackground());
+        messagePane.setBorder(null);
+        messagePane.setText(waitingForSaveMessage());
+        // callback as each file finishes saving
+        for (CompletableFuture<Boolean> cf : BackupFiles
+                .savesInProgressCompletableFutures(false))
+        {
+          cf.whenComplete((ret, e) -> {
+            if (BackupFiles.hasSavesInProgress())
+            {
+              // update the list of saving files as they save too
+              messagePane.setText(waitingForSaveMessage());
+            }
+            else
+            {
+              if (!(QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT
+                      || QuitHandler.gotQuitResponse() == QResponse.NULL))
+              {
+                for (int i = 0; i < buttons.length; i++)
+                {
+                  Console.debug("DISABLING BUTTON " + buttons[i].getText());
+                  buttons[i].setEnabled(false);
+                  buttons[i].setVisible(false);
+                }
+                // if this is the last one then close the dialog
+                messagePane.setText(new StringBuilder()
+                        .append(MessageManager.getString("label.all_saved"))
+                        .append("\n")
+                        .append(MessageManager
+                                .getString("label.quitting_bye"))
+                        .toString());
+                messagePane.setEditable(false);
+                try
+                {
+                  Thread.sleep(1500);
+                } catch (InterruptedException e1)
+                {
+                }
+                parent.dispose();
+              }
+            }
+          });
+        }
+
+        String[] options;
+        int dialogType = -1;
+        if (showForceQuit)
+        {
+          options = new String[2];
+          options[0] = MessageManager.getString("action.force_quit");
+          options[1] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_NO_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, forceQuit)
+                  .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit);
+        }
+        else
+        {
+          options = new String[1];
+          options[0] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, cancelQuit);
+        }
+        waitDialog.showDialogOnTopAsync(parent, messagePane,
+                MessageManager.getString("label.wait_for_save"), dialogType,
+                JOptionPane.WARNING_MESSAGE, null, options,
+                MessageManager.getString("action.cancel_quit"), true,
+                buttons);
+
+        parent.dispose();
+        final QResponse thisWaitResponse = gotQuitResponse();
+        switch (thisWaitResponse)
+        {
+        case QUIT: // wait -- do another iteration
+          break;
+        case FORCE_QUIT:
+          doIterations = false;
+          break;
+        case CANCEL_QUIT:
+          doIterations = false;
+          break;
+        case NULL: // already cancelled
+          doIterations = false;
+          break;
+        default:
+        }
+      } // end if interactive
+
+    } // end while wait iteration loop
+    return gotQuitResponse();
+  };
+
+  private static String waitingForSaveMessage()
+  {
+    StringBuilder messageSB = new StringBuilder();
+
+    messageSB.append(MessageManager.getString("label.save_in_progress"));
+    List<File> files = BackupFiles.savesInProgressFiles(false);
+    boolean any = files.size() > 0;
+    if (any)
+    {
+      for (File file : files)
+      {
+        messageSB.append("\n\u2022 ").append(file.getName());
+      }
+    }
+    else
+    {
+      messageSB.append(MessageManager.getString("label.unknown"));
+    }
+    messageSB.append("\n\n")
+            .append(MessageManager.getString("label.quit_after_saving"));
+    return messageSB.toString();
+  }
+
+  public static void abortQuit()
+  {
+    setResponse(QResponse.CANCEL_QUIT);
+  }
+}
\ No newline at end of file
index 07eec2b..dbd270f 100644 (file)
@@ -30,6 +30,7 @@ import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
 import javax.swing.JCheckBox;
@@ -316,53 +317,43 @@ public class StructureChooser extends GStructureChooser
     };
 
     // fetch db refs if OK pressed
-    final Runnable discoverCanonicalDBrefs = new Runnable()
-    {
-      @Override
-      public void run()
+    final Callable discoverCanonicalDBrefs = () -> {
+      btn_queryTDB.setEnabled(false);
+      populateSeqsWithoutSourceDBRef();
+
+      final int y = seqsWithoutSourceDBRef.size();
+      if (y > 0)
       {
-        btn_queryTDB.setEnabled(false);
-        populateSeqsWithoutSourceDBRef();
+        final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
+                .toArray(new SequenceI[y]);
+        DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
+                progressBar, new DbSourceProxy[]
+                { new jalview.ws.dbsources.Uniprot() }, null, false);
+        dbRefFetcher.addListener(afterDbRefFetch);
+        // ideally this would also gracefully run with callbacks
 
-        final int y = seqsWithoutSourceDBRef.size();
-        if (y > 0)
-        {
-          final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
-                  .toArray(new SequenceI[y]);
-          DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
-                  progressBar, new DbSourceProxy[]
-                  { new jalview.ws.dbsources.Uniprot() }, null, false);
-          dbRefFetcher.addListener(afterDbRefFetch);
-          // ideally this would also gracefully run with callbacks
-
-          dbRefFetcher.fetchDBRefs(true);
-        }
-        else
-        {
-          // call finished action directly
-          afterDbRefFetch.finished();
-        }
+        dbRefFetcher.fetchDBRefs(true);
       }
-
+      else
+      {
+        // call finished action directly
+        afterDbRefFetch.finished();
+      }
+      return null;
     };
-    final Runnable revertview = new Runnable()
-    {
-      @Override
-      public void run()
+    final Callable revertview = () -> {
+      if (lastSelected != null)
       {
-        if (lastSelected != null)
-        {
-          cmb_filterOption.setSelectedItem(lastSelected);
-        }
-      };
+        cmb_filterOption.setSelectedItem(lastSelected);
+      }
+      return null;
     };
     int threshold = Cache.getDefault("UNIPROT_AUTOFETCH_THRESHOLD",
             THRESHOLD_WARN_UNIPROT_FETCH_NEEDED);
     Console.debug("Using Uniprot fetch threshold of " + threshold);
     if (ignoreGui || seqsWithoutSourceDBRef.size() < threshold)
     {
-      Executors.defaultThreadFactory().newThread(discoverCanonicalDBrefs)
-              .start();
+      Executors.newSingleThreadExecutor().submit(discoverCanonicalDBrefs);
       return;
     }
     // need cancel and no to result in the discoverPDB action - mocked is
index f4970ae..82b6759 100644 (file)
@@ -1281,6 +1281,9 @@ public abstract class StructureViewerBase extends GStructureViewer
         if (confirm == JvOptionPane.CANCEL_OPTION
                 || confirm == JvOptionPane.CLOSED_OPTION)
         {
+          // abort possible quit handling if CANCEL chosen
+          if (confirm == JvOptionPane.CANCEL_OPTION)
+            QuitHandler.abortQuit();
           return;
         }
         forceClose = confirm == JvOptionPane.YES_OPTION;
index f9ff337..e72a084 100644 (file)
@@ -27,6 +27,7 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import javax.swing.BorderFactory;
 import javax.swing.JLabel;
@@ -151,13 +152,10 @@ public class TextColourChooser
         MessageManager.getString("action.cancel") };
     String title = MessageManager
             .getString("label.adjust_foreground_text_colour_threshold");
-    Runnable action = new Runnable() // response for 1 = Cancel
+    Callable<Void> action = () -> // response for 1 = Cancel
     {
-      @Override
-      public void run()
-      {
-        restoreInitialSettings();
-      }
+      restoreInitialSettings();
+      return null;
     };
     JvOptionPane.newOptionDialog(alignPanel.alignFrame)
             .setResponseHandler(1, action).showInternalDialog(bigpanel,
index 1a7ae4a..4979746 100755 (executable)
@@ -652,45 +652,41 @@ public class UserDefinedColours extends GUserDefinedColours
     chooser.setDialogTitle(
             MessageManager.getString("label.load_colour_scheme"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File choice = chooser.getSelectedFile();
-        Cache.setProperty(LAST_DIRECTORY, choice.getParent());
-
-        UserColourScheme ucs = ColourSchemeLoader
-                .loadColourScheme(choice.getAbsolutePath());
-        Color[] colors = ucs.getColours();
-        schemeName.setText(ucs.getSchemeName());
+    chooser.setResponseHandler(0, () -> {
+      File choice = chooser.getSelectedFile();
+      Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 
-        if (ucs.getLowerCaseColours() != null)
-        {
-          caseSensitive.setSelected(true);
-          lcaseColour.setEnabled(true);
-          resetButtonPanel(true);
-          for (int i = 0; i < lowerCaseButtons.size(); i++)
-          {
-            JButton button = lowerCaseButtons.get(i);
-            button.setBackground(ucs.getLowerCaseColours()[i]);
-          }
-        }
-        else
-        {
-          caseSensitive.setSelected(false);
-          lcaseColour.setEnabled(false);
-          resetButtonPanel(false);
-        }
+      UserColourScheme ucs = ColourSchemeLoader
+              .loadColourScheme(choice.getAbsolutePath());
+      Color[] colors = ucs.getColours();
+      schemeName.setText(ucs.getSchemeName());
 
-        for (int i = 0; i < upperCaseButtons.size(); i++)
+      if (ucs.getLowerCaseColours() != null)
+      {
+        caseSensitive.setSelected(true);
+        lcaseColour.setEnabled(true);
+        resetButtonPanel(true);
+        for (int i = 0; i < lowerCaseButtons.size(); i++)
         {
-          JButton button = upperCaseButtons.get(i);
-          button.setBackground(colors[i]);
+          JButton button = lowerCaseButtons.get(i);
+          button.setBackground(ucs.getLowerCaseColours()[i]);
         }
+      }
+      else
+      {
+        caseSensitive.setSelected(false);
+        lcaseColour.setEnabled(false);
+        resetButtonPanel(false);
+      }
 
-        addNewColourScheme(choice.getPath());
+      for (int i = 0; i < upperCaseButtons.size(); i++)
+      {
+        JButton button = upperCaseButtons.get(i);
+        button.setBackground(colors[i]);
       }
+
+      addNewColourScheme(choice.getPath());
+      return null;
     });
 
     chooser.showOpenDialog(this);
index 2039d3c..9112042 100644 (file)
@@ -29,8 +29,14 @@ import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import jalview.bin.Cache;
 import jalview.bin.Console;
@@ -105,6 +111,117 @@ public class BackupFiles
 
   private static final String oldTempFileSuffix = "_oldfile_tobedeleted";
 
+  // thread pool used for completablefutures
+  private static final ExecutorService executorService = Executors
+          .newFixedThreadPool(3);
+
+  private static List<BackupFiles> savesInProgress = new ArrayList<>();
+
+  private CompletableFuture<Boolean> myFuture = null;
+
+  private boolean addSaveInProgress()
+  {
+    if (savesInProgress.contains(this))
+    {
+      return false;
+    }
+    else
+    {
+      this.setMyFuture();
+      savesInProgress.add(this);
+      return true;
+    }
+  }
+
+  private boolean removeSaveInProgress(boolean ret)
+  {
+    if (savesInProgress.contains(this))
+    {
+      this.getMyFuture().complete(ret);
+      // remove all occurrences
+      while (savesInProgress.remove(this))
+      {
+      }
+      return true;
+    }
+    return false;
+  }
+
+  private static CompletableFuture<Boolean> getNewFuture()
+  {
+    return new CompletableFuture<Boolean>()
+    {
+    };
+  }
+
+  private CompletableFuture<Boolean> getMyFuture()
+  {
+    return this.myFuture;
+  }
+
+  private void setMyFuture()
+  {
+    this.myFuture = getNewFuture();
+  }
+
+  public static boolean hasSavesInProgress()
+  {
+    boolean has = false;
+    for (CompletableFuture cf : savesInProgressCompletableFutures(true))
+    {
+      has |= !cf.isDone();
+    }
+    return has;
+  }
+
+  public static List<File> savesInProgressFiles(boolean all)
+  {
+    List<File> files = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        files.add(bfile.getFile());
+    }
+    return files;
+  }
+
+  public static List<CompletableFuture<Boolean>> savesInProgressCompletableFutures(
+          boolean all)
+  {
+    List<CompletableFuture<Boolean>> cfs = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        cfs.add(bfile.getMyFuture());
+    }
+    return cfs;
+  }
+
+  public static Future<Boolean> allSaved()
+  {
+    CompletableFuture<Boolean> f = new CompletableFuture<>();
+
+    executorService.submit(() -> {
+      for (BackupFiles buf : savesInProgress)
+      {
+        boolean allSaved = true;
+        try
+        {
+          allSaved &= buf.getMyFuture().get();
+        } catch (InterruptedException e)
+        {
+          Console.debug("InterruptedException waiting for files to save",
+                  e);
+        } catch (ExecutionException e)
+        {
+          Console.debug("ExecutionException waiting for files to save", e);
+        }
+        f.complete(allSaved);
+      }
+    });
+    return f;
+  }
+
   public BackupFiles(String filename)
   {
     this(new File(filename));
@@ -116,6 +233,10 @@ public class BackupFiles
   {
     classInit();
     this.file = file;
+
+    // add this file from the save in progress stack
+    addSaveInProgress();
+
     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
             .getSavedBackupEntry();
     this.suffix = bfpe.suffix;
@@ -819,6 +940,9 @@ public class BackupFiles
       tidyUpFiles();
     }
 
+    // remove this file from the save in progress stack
+    removeSaveInProgress(rename);
+
     return rename;
   }
 
@@ -890,6 +1014,11 @@ public class BackupFiles
     return ret;
   }
 
+  public File getFile()
+  {
+    return file;
+  }
+
   public static boolean moveFileToFile(File oldFile, File newFile)
   {
     Console.initLogger();
index 4016e71..ffeb53d 100755 (executable)
@@ -465,6 +465,7 @@ public class FileLoader implements Runnable
             {
               alignFrame.setFileName(file, format);
               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
+              alignFrame.getViewport().setSavedUpToDate(true);
             }
             if (proxyColourScheme != null)
             {
index 4b66f81..9fb3720 100644 (file)
  */
 package jalview.io;
 
-import jalview.bin.Cache;
-import jalview.gui.AlignmentPanel;
-import jalview.gui.LineartOptions;
-import jalview.gui.OOMWarning;
-import jalview.math.AlignmentDimension;
-import jalview.util.MessageManager;
-
 import java.awt.Graphics;
 import java.awt.print.PrinterException;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jfree.graphics2d.svg.SVGHints;
 
+import jalview.bin.Cache;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.LineartOptions;
+import jalview.gui.OOMWarning;
+import jalview.math.AlignmentDimension;
+import jalview.util.MessageManager;
+
 public class HtmlSvgOutput extends HTMLOutput
 {
   public HtmlSvgOutput(AlignmentPanel ap)
@@ -211,13 +212,9 @@ public class HtmlSvgOutput extends HTMLOutput
       /*
        * configure the action to run on OK in the dialog
        */
-      Runnable okAction = new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          doOutput(textOption.get());
-        }
+      Callable<Void> okAction = () -> {
+        doOutput(textOption.get());
+        return null;
       };
 
       /*
@@ -226,15 +223,11 @@ public class HtmlSvgOutput extends HTMLOutput
       if (renderStyle.equalsIgnoreCase("Prompt each time") && !isHeadless())
       {
         LineartOptions svgOption = new LineartOptions("HTML", textOption);
-        svgOption.setResponseAction(1, new Runnable()
-        {
-          @Override
-          public void run()
-          {
-            setProgressMessage(MessageManager.formatMessage(
-                    "status.cancelled_image_export_operation",
-                    getDescription()));
-          }
+        svgOption.setResponseAction(1, () -> {
+          setProgressMessage(MessageManager.formatMessage(
+                  "status.cancelled_image_export_operation",
+                  getDescription()));
+          return null;
         });
         svgOption.setResponseAction(0, okAction);
         svgOption.showDialog();
index 8cd5406..077bffb 100755 (executable)
 //////////////////////////////////////////////////////////////////
 package jalview.io;
 
+import jalview.bin.Cache;
+import jalview.gui.JvOptionPane;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.dialogrunner.DialogRunnerI;
+
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.EventQueue;
@@ -38,6 +44,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 
 import javax.swing.BoxLayout;
 import javax.swing.DefaultListCellRenderer;
@@ -45,6 +52,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JDialog;
 import javax.swing.JFileChooser;
 import javax.swing.JList;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SpringLayout;
@@ -72,7 +80,7 @@ public class JalviewFileChooser extends JFileChooser
 {
   private static final long serialVersionUID = 1L;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable> callbacks = new HashMap<>();
 
   File selectedFile = null;
 
@@ -490,10 +498,13 @@ public class JalviewFileChooser extends JFileChooser
 
     if (selectedFile.exists())
     {
-      int confirm = JvOptionPane.showConfirmDialog(this,
-              MessageManager.getString("label.overwrite_existing_file"),
-              MessageManager.getString("label.file_already_exists"),
-              JvOptionPane.YES_NO_OPTION);
+      int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true)
+              ? JvOptionPane.showConfirmDialog(this,
+                      MessageManager
+                              .getString("label.overwrite_existing_file"),
+                      MessageManager.getString("label.file_already_exists"),
+                      JvOptionPane.YES_NO_OPTION)
+              : JOptionPane.YES_OPTION;
 
       if (confirm != JvOptionPane.YES_OPTION)
       {
@@ -598,8 +609,26 @@ public class JalviewFileChooser extends JFileChooser
 
   }
 
+  /*
+  @Override
+  public JalviewFileChooser setResponseHandler(Object response,
+          Runnable action)
+  {
+    callbacks.put(response, new Callable<Void>()
+    {
+      @Override
+      public Void call()
+      {
+        action.run();
+        return null;
+      }
+    });
+    return this;
+  }
+  */
+
   @Override
-  public DialogRunnerI setResponseHandler(Object response, Runnable action)
+  public DialogRunnerI setResponseHandler(Object response, Callable action)
   {
     callbacks.put(response, action);
     return this;
@@ -615,10 +644,16 @@ public class JalviewFileChooser extends JFileChooser
     {
       return;
     }
-    Runnable action = callbacks.get(response);
+    Callable action = callbacks.get(response);
     if (action != null)
     {
-      action.run();
+      try
+      {
+        action.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
index ca95222..b0079b4 100755 (executable)
@@ -32,6 +32,8 @@ import javax.swing.JMenuItem;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
+import jalview.gui.APQHandlers;
+import jalview.gui.Desktop;
 import jalview.io.FileFormatException;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -140,7 +142,6 @@ public class GDesktop extends JFrame
    */
   private void jbInit() throws Exception
   {
-    boolean apqHandlersSet = false;
     /**
      * APQHandlers sets handlers for About, Preferences and Quit actions
      * peculiar to macOS's application menu. APQHandlers will check to see if a
@@ -148,7 +149,7 @@ public class GDesktop extends JFrame
      */
     try
     {
-      apqHandlersSet = APQHandlers.setAPQHandlers(this);
+      APQHandlers.setAPQHandlers((Desktop) this);
     } catch (Exception e)
     {
       System.out.println("Cannot set APQHandlers");
@@ -213,7 +214,8 @@ public class GDesktop extends JFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        if (Desktop.instance != null)
+          Desktop.instance.desktopQuit();
       }
     });
     aboutMenuItem.setText(MessageManager.getString("label.about"));
index d4b2c04..9cec063 100644 (file)
@@ -229,6 +229,11 @@ public class Jalview2XML
   private static final String UTF_8 = "UTF-8";
 
   /**
+   * used in decision if quit confirmation should be issued
+   */
+  private static boolean stateSavedUpToDate = false;
+
+  /**
    * prefix for recovering datasets for alignments with multiple views where
    * non-existent dataset IDs were written for some views
    */
@@ -616,6 +621,27 @@ public class Jalview2XML
   {
     AlignFrame[] frames = Desktop.getAlignFrames();
 
+    setStateSavedUpToDate(true);
+
+    if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+    {
+      int n = 20;
+      int i = 0;
+      while (i < n)
+      {
+        Console.debug("***** debugging save sleep " + i + "/" + n);
+        try
+        {
+          Thread.sleep(1000);
+        } catch (InterruptedException e)
+        {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+        }
+        i++;
+      }
+    }
+
     if (frames == null)
     {
       return;
@@ -763,6 +789,25 @@ public class Jalview2XML
       FileOutputStream fos = new FileOutputStream(
               doBackup ? backupfiles.getTempFilePath() : jarFile);
 
+      if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+      {
+        int n = 20;
+        int i = 0;
+        while (i < n)
+        {
+          Console.debug("***** debugging save sleep " + i + "/" + n);
+          try
+          {
+            Thread.sleep(1000);
+          } catch (InterruptedException e)
+          {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          }
+          i++;
+        }
+      }
+
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
 
@@ -6537,4 +6582,36 @@ public class Jalview2XML
 
     return colour;
   }
+
+  public static void setStateSavedUpToDate(boolean s)
+  {
+    Console.debug("Setting overall stateSavedUpToDate to " + s);
+    stateSavedUpToDate = s;
+  }
+
+  public static boolean stateSavedUpToDate()
+  {
+    Console.debug("Returning overall stateSavedUpToDate value: "
+            + stateSavedUpToDate);
+    return stateSavedUpToDate;
+  }
+
+  public static boolean allSavedUpToDate()
+  {
+    if (stateSavedUpToDate()) // nothing happened since last project save
+      return true;
+
+    AlignFrame[] frames = Desktop.getAlignFrames();
+    if (frames != null)
+    {
+      for (int i = 0; i < frames.length; i++)
+      {
+        if (frames[i] == null)
+          continue;
+        if (!frames[i].getViewport().savedUpToDate())
+          return false; // at least one alignment is not individually saved
+      }
+    }
+    return true;
+  }
 }
index fde80f7..1fc41e7 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.util.dialogrunner;
 
+import java.util.concurrent.Callable;
+
 /**
  * An interface for blocking dialog response handling. This is motivated by
  * JalviewJS - when running as Javascript, there is only a single thread, and
@@ -41,7 +43,9 @@ public interface DialogRunnerI
    * @param action
    * @return
    */
-  DialogRunnerI setResponseHandler(Object response, Runnable action);
+  DialogRunnerI setResponseHandler(Object response, Callable<Void> action);
+
+  // DialogRunnerI setResponseHandler(Object response, Runnable action);
 
   /**
    * Runs the registered handler (if any) for the given response. The default
index 08af2ec..5a4ceb9 100644 (file)
  */
 package jalview.viewmodel;
 
+import java.awt.Color;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
@@ -29,6 +41,7 @@ import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeaturesDisplayedI;
 import jalview.api.ViewStyleI;
+import jalview.bin.Console;
 import jalview.commands.CommandI;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
@@ -45,6 +58,7 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.project.Jalview2XML;
 import jalview.renderer.ResidueShader;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
@@ -61,18 +75,6 @@ import jalview.workers.ComplementConsensusThread;
 import jalview.workers.ConsensusThread;
 import jalview.workers.StrucConsensusThread;
 
-import java.awt.Color;
-import java.beans.PropertyChangeSupport;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * base class holding visualization and analysis attributes and common logic for
  * an active alignment view displayed in the GUI
@@ -100,6 +102,11 @@ public abstract class AlignmentViewport
   protected Deque<CommandI> redoList = new ArrayDeque<>();
 
   /**
+   * used to determine if quit should be confirmed
+   */
+  private boolean savedUpToDate = false;
+
+  /**
    * alignment displayed in the viewport. Please use get/setter
    */
   protected AlignmentI alignment;
@@ -2614,6 +2621,8 @@ public abstract class AlignmentViewport
     {
       this.historyList.push(command);
       broadcastCommand(command, false);
+      setSavedUpToDate(false);
+      Jalview2XML.setStateSavedUpToDate(false);
     }
   }
 
@@ -3096,4 +3105,18 @@ public abstract class AlignmentViewport
     return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
             false));
   }
+
+  public void setSavedUpToDate(boolean s)
+  {
+    Console.debug(
+            "Setting " + this.getViewId() + " setSavedUpToDate to " + s);
+    savedUpToDate = s;
+  }
+
+  public boolean savedUpToDate()
+  {
+    Console.debug("Returning " + this.getViewId() + " savedUpToDate value: "
+            + savedUpToDate);
+    return savedUpToDate;
+  }
 }