JAL-1988 JAL-3772 Non-blocking modal dialogs for unsaved changes and saving files...
authorBen Soares <b.soares@dundee.ac.uk>
Mon, 24 Oct 2022 14:41:18 +0000 (15:41 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Mon, 24 Oct 2022 14:41:18 +0000 (15:41 +0100)
25 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/Jalview.java
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/StructureChooser.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/BackupFiles.java
src/jalview/io/HtmlSvgOutput.java
src/jalview/io/JalviewFileChooser.java
src/jalview/jbgui/GDesktop.java
src/jalview/jbgui/QuitHandler.java
src/jalview/util/dialogrunner/DialogRunnerI.java

index f196687..73da48c 100644 (file)
@@ -35,7 +35,7 @@ action.quit = Quit
 action.force_quit = Force Quit
 label.quit_jalview = Are you sure you want to quit Jalview?
 label.unsaved_changes = There are unsaved changes.
-label.save_in_progress = Some files are still saving.
+label.save_in_progress = Some files are still saving:
 label.unknown = Unknown
 action.wait = Wait
 action.cancel_quit = Cancel quit
index 5f49205..d9a64b3 100644 (file)
@@ -35,7 +35,7 @@ action.quit = Salir
 action.force_quit = Forzar la salida
 label.quit_jalview = ¿Estás seguro de que quieres salir de Jalview?
 label.unsaved_changes = Hay cambios sin guardar.
-label.save_in_progress = Algunos archivos aún se están guardando.
+label.save_in_progress = Algunos archivos aún se están guardando:
 label.unknown = desconocido
 action.wait = Espere
 action.cancel_quit = Cancelar la salida
index 3d3e99f..f0477b9 100755 (executable)
@@ -281,6 +281,9 @@ public class Jalview
           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);
           }
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 8a6907c..d8fec3a 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;
@@ -1272,103 +1273,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)
-          {
-            Console.trace(
-                    "ALIGNFRAME making backupfiles object for " + file);
-            backupfiles = new BackupFiles(file);
-          }
-          try
-          {
-            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
-                    : file;
-            Console.trace("ALIGNFRAME setting PrintWriter");
-            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
+          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)
+          if (backupfiles != null)
           {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something unexpected happened writing the temp file");
-            Console.error(ex.getMessage());
-            Console.debug(Cache.getStackTraceString(ex));
+            Console.trace("ALIGNFRAME about to write to temp file "
+                    + backupfiles.getTempFilePath());
           }
 
-          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");
-          }
+          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));
+        }
 
-          Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
-          if (lastSaveSuccessful)
-          {
-            AlignFrame.this.getViewport().setSavedUpToDate(true);
-          }
+        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;
     };
 
     /*
@@ -1384,7 +1371,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1401,34 +1395,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;
     };
 
     /*
@@ -1443,7 +1432,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1551,15 +1546,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);
@@ -2481,36 +2472,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;
     };
 
     /*
@@ -2534,7 +2520,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      okAction.run();
+      try
+      {
+        okAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -4087,36 +4079,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);
   }
@@ -5888,16 +5875,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 585537e..13253c3 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;
@@ -454,17 +456,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)
       {
-        QResponse qresponse = desktopQuit();
-        if (qresponse != QResponse.CANCEL_QUIT)
-        {
-          instance.dispose();
-        }
+        QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
       }
     });
 
@@ -1178,36 +1177,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);
   }
@@ -1263,64 +1258,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");
@@ -1352,61 +1343,70 @@ public class Desktop extends jalview.jbgui.GDesktop
   /*
    * Check with user and saving files before actually quitting
    */
-  public QResponse desktopQuit()
+  public void desktopQuit()
   {
-    return desktopQuit(true);
+    desktopQuit(true, false);
   }
 
-  public QResponse desktopQuit(boolean ui)
+  public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
   {
-    QuitHandler.QResponse qresponse = QuitHandler.getQuitResponse(ui);
+    final Callable<QuitHandler.QResponse> 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 (qresponse == QResponse.CANCEL_QUIT)
-    {
-      return qresponse;
-    }
+      if (jconsole != null)
+      {
+        storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
+        jconsole.stopConsole();
+      }
 
-    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 (jvnews != null)
+      {
+        storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
 
-    if (jconsole != null)
-    {
-      storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
-      jconsole.stopConsole();
-    }
-    if (jvnews != null)
-    {
-      storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
+      }
 
-    }
-    if (dialogExecutor != null)
-    {
-      dialogExecutor.shutdownNow();
-    }
-    closeAll_actionPerformed(null);
+      if (dialogExecutor != null)
+      {
+        dialogExecutor.shutdownNow();
+      }
 
-    if (groovyConsole != null)
-    {
-      // suppress a possible repeat prompt to save script
-      groovyConsole.setDirty(false);
-      groovyConsole.exit();
-    }
+      closeAll_actionPerformed(null);
 
-    if (qresponse == QResponse.FORCE_QUIT)
-    {
-      // note that shutdown hook will not be run
-      jalview.bin.Console.debug("Force Quit selected by user");
-      Runtime.getRuntime().halt(0);
-    }
+      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();
+      }
+      jalview.bin.Console.debug("**** BEFORE quit");
+      jalview.bin.Console.debug("**** QuitHandler.gotQuitResponse="
+              + QuitHandler.gotQuitResponse());
+      instance.quit();
+      jalview.bin.Console.debug("**** AFTER quit");
 
-    jalview.bin.Console.debug("Quit selected by user");
-    quit();
+      return QuitHandler.gotQuitResponse();
+    };
 
-    // unlikely to reach here!
-    return QResponse.QUIT;
+    return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
+            QuitHandler.defaultCancelQuit);
   }
 
   /**
@@ -1855,42 +1855,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);
@@ -2554,12 +2549,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        QResponse qresponse = desktopQuit();
-        if (qresponse == QResponse.CANCEL_QUIT)
-        {
-          jalview.bin.Console
-                  .debug("Desktop: Quit action cancelled by user");
-        }
+        desktopQuit();
       }
     });
   }
index 52791c8..ff0fe3a 100644 (file)
  */
 package jalview.gui;
 
-import jalview.util.MessageManager;
-
 import java.awt.FlowLayout;
 import java.awt.Font;
+import java.util.concurrent.Callable;
 
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
@@ -32,6 +31,8 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 
+import jalview.util.MessageManager;
+
 /**
  * A dialog where a name and description may be edited
  */
@@ -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 bb15b55..0d6d371 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import java.util.Locale;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
@@ -54,6 +52,7 @@ import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -63,7 +62,6 @@ import javax.swing.BorderFactory;
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JLayeredPane;
@@ -949,14 +947,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 028e50b..c5427cb 100644 (file)
@@ -34,11 +34,13 @@ 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.JOptionPane;
 import javax.swing.JPanel;
@@ -52,13 +54,13 @@ public class JvOptionPane extends JOptionPane
 {
   private static final long serialVersionUID = -3019167117756785229L;
 
-  private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
+  private static Object mockResponse = JOptionPane.CANCEL_OPTION;
 
   private static boolean interactiveMode = true;
 
   private Component parentComponent;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable> callbacks = new HashMap<>();
 
   /*
    * JalviewJS reports user choice in the dialog as the selected
@@ -100,17 +102,17 @@ public class JvOptionPane extends JOptionPane
     }
     switch (optionType)
     {
-    case JvOptionPane.YES_NO_CANCEL_OPTION:
+    case JOptionPane.YES_NO_CANCEL_OPTION:
       // FeatureRenderer amendFeatures ?? TODO ??
       // Chimera close
       // PromptUserConfig
       // $FALL-THROUGH$
     default:
-    case JvOptionPane.YES_NO_OPTION:
+    case JOptionPane.YES_NO_OPTION:
       // PromptUserConfig usage stats
       // for now treated as "OK CANCEL"
       // $FALL-THROUGH$
-    case JvOptionPane.OK_CANCEL_OPTION:
+    case JOptionPane.OK_CANCEL_OPTION:
       // will fall back to simple HTML
       return JOptionPane.showConfirmDialog(parentComponent, message, title,
               optionType);
@@ -198,13 +200,13 @@ public class JvOptionPane extends JOptionPane
     }
     switch (optionType)
     {
-    case JvOptionPane.YES_NO_CANCEL_OPTION:
+    case JOptionPane.YES_NO_CANCEL_OPTION:
       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
-    case JvOptionPane.YES_NO_OPTION:
+    case JOptionPane.YES_NO_OPTION:
       // UserDefinedColoursSave -- relevant? TODO
       // $FALL-THROUGH$
     default:
-    case JvOptionPane.OK_CANCEL_OPTION:
+    case JOptionPane.OK_CANCEL_OPTION:
 
       // EditNameDialog --- uses panel for messsage TODO
 
@@ -233,13 +235,13 @@ public class JvOptionPane extends JOptionPane
     }
     switch (optionType)
     {
-    case JvOptionPane.YES_NO_CANCEL_OPTION:
-    case JvOptionPane.YES_NO_OPTION:
+    case JOptionPane.YES_NO_CANCEL_OPTION:
+    case JOptionPane.YES_NO_OPTION:
       // UserQuestionanaireCheck
       // VamsasApplication
       // $FALL-THROUGH$
     default:
-    case JvOptionPane.OK_CANCEL_OPTION:
+    case JOptionPane.OK_CANCEL_OPTION:
       // will fall back to simple HTML
       return JOptionPane.showConfirmDialog(parentComponent, message, title,
               optionType, messageType);
@@ -267,11 +269,11 @@ public class JvOptionPane extends JOptionPane
     }
     switch (optionType)
     {
-    case JvOptionPane.YES_NO_CANCEL_OPTION:
-    case JvOptionPane.YES_NO_OPTION:
+    case JOptionPane.YES_NO_CANCEL_OPTION:
+    case JOptionPane.YES_NO_OPTION:
       //$FALL-THROUGH$
     default:
-    case JvOptionPane.OK_CANCEL_OPTION:
+    case JOptionPane.OK_CANCEL_OPTION:
       // Preferences editLink/newLink
       return JOptionPane.showConfirmDialog(parentComponent, message, title,
               optionType, messageType, icon);
@@ -708,7 +710,7 @@ public class JvOptionPane extends JOptionPane
 
   public static void resetMock()
   {
-    setMockResponse(JvOptionPane.CANCEL_OPTION);
+    setMockResponse(JOptionPane.CANCEL_OPTION);
     setInteractiveMode(true);
   }
 
@@ -731,10 +733,10 @@ public class JvOptionPane extends JOptionPane
     {
       switch (messageType)
       {
-      case JvOptionPane.WARNING_MESSAGE:
+      case JOptionPane.WARNING_MESSAGE:
         prefix = "WARNING! ";
         break;
-      case JvOptionPane.ERROR_MESSAGE:
+      case JOptionPane.ERROR_MESSAGE:
         prefix = "ERROR! ";
         break;
       default:
@@ -758,6 +760,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);
@@ -822,100 +829,12 @@ public class JvOptionPane extends JOptionPane
        * attached to the button press of the dialog.  This means we can use
        * a non-modal JDialog for the confirmation without blocking the GUI.
        */
-      JOptionPane joptionpane = new JOptionPane();
-      // Make button options
-      int[] buttonActions = { JvOptionPane.YES_OPTION,
-          JvOptionPane.NO_OPTION, JvOptionPane.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 == JvOptionPane.YES_NO_OPTION
-                || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
-        {
-          options_default
-                  .add(UIManager.getString("OptionPane.noButtonText"));
-        }
-        if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
-        {
-          options_default
-                  .add(UIManager.getString("OptionPane.cancelButtonText"));
-        }
-        options = options_default.toArray();
-      }
 
-      ArrayList<JButton> options_btns = new ArrayList<>();
-      Object initialValue_btn = null;
-      if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here
-      {
-        for (int i = 0; i < options.length && i < 3; i++)
-        {
-          Object o = options[i];
-          int buttonAction = buttonActions[i];
-          Runnable action = callbacks.get(buttonAction);
-          JButton jb = new JButton();
-          jb.setText((String) o);
-          jb.addActionListener(new ActionListener()
-          {
-            @Override
-            public void actionPerformed(ActionEvent e)
-            {
-              joptionpane.setValue(buttonAction);
-              if (action != null)
-                Executors.defaultThreadFactory().newThread(action).start();
-              // 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 == JvOptionPane.CANCEL_OPTION)
-                raiseParent = false;
-              if (optionType == JvOptionPane.YES_NO_OPTION
-                      && buttonAction == JvOptionPane.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);
-            }
-          });
-          options_btns.add(jb);
-          if (o.equals(initialValue))
-            initialValue_btn = jb;
-        }
-      }
-      joptionpane.setMessage(message);
-      joptionpane.setMessageType(messageType);
-      joptionpane.setOptionType(optionType);
-      joptionpane.setIcon(icon);
-      joptionpane.setOptions(
-              Platform.isJS() ? options : options_btns.toArray());
-      joptionpane.setInitialValue(
-              Platform.isJS() ? initialValue : initialValue_btn);
-
-      JDialog dialog = joptionpane.createDialog(parentComponent, title);
-      dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
-              : ModalityType.MODELESS);
-      dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+      JDialog dialog = this.createDialog(parentComponent, message, title,
+              optionType, messageType, icon, options, initialValue, modal);
+      jalview.bin.Console.debug("About to setVisible(true)");
       dialog.setVisible(true);
+      jalview.bin.Console.debug("Just setVisible(true)");
     }
   }
 
@@ -953,14 +872,84 @@ 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 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);
+    jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()");
+    dialogParent.dispose();
+    jalview.bin.Console.debug("*********** BEFORE 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)
+  {
+    // 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);
+    parentComponent = dialogParent;
+
+    showDialog(label, actionString, JOPTIONPANE_OPTION,
+            JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal);
+
+    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.
@@ -993,11 +982,139 @@ 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, String message,
+          String title, int optionType, int messageType, Icon icon,
+          Object[] options, Object initialValue, boolean modal)
+  {
+    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();
     }
+    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 (((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");
+      }
+      for (int i = 0; i < options.length && i < 3; i++)
+      {
+        Object o = options[i];
+        int buttonAction = buttonActions[i];
+        Callable action = callbacks.get(buttonAction);
+        JButton 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);
+          }
+        });
+        options_btns.add(jb);
+        if (o.equals(initialValue))
+          initialValue_btn = jb;
+      }
+    }
+    joptionpane.setMessage(message);
+    joptionpane.setMessageType(messageType);
+    joptionpane.setOptionType(optionType);
+    joptionpane.setIcon(icon);
+    joptionpane
+            .setOptions(Platform.isJS() ? options : options_btns.toArray());
+    joptionpane.setInitialValue(
+            Platform.isJS() ? initialValue : initialValue_btn);
+
+    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;
   }
 }
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 6903034..1c03d6a 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import java.util.Locale;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
@@ -34,6 +32,7 @@ import java.util.Collections;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.SortedMap;
@@ -1987,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;
             });
   }
 
@@ -2027,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;
             });
   }
 
@@ -2272,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;
               });
     }
   }
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 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 1836e33..4db8bfa 100755 (executable)
  */
 package jalview.gui;
 
-import java.util.Locale;
-
-import jalview.bin.Cache;
-import jalview.io.JalviewFileChooser;
-import jalview.io.JalviewFileView;
-import jalview.jbgui.GUserDefinedColours;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ColourSchemeLoader;
-import jalview.schemes.ColourSchemes;
-import jalview.schemes.ResidueProperties;
-import jalview.schemes.UserColourScheme;
-import jalview.util.ColorUtils;
-import jalview.util.Format;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.xml.binding.jalview.JalviewUserColours;
-import jalview.xml.binding.jalview.JalviewUserColours.Colour;
-import jalview.xml.binding.jalview.ObjectFactory;
-
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Insets;
@@ -50,6 +31,7 @@ import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 import javax.swing.JButton;
 import javax.swing.JInternalFrame;
@@ -58,6 +40,23 @@ import javax.swing.event.ChangeListener;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.Marshaller;
 
+import jalview.bin.Cache;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.jbgui.GUserDefinedColours;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
+import jalview.schemes.ColourSchemes;
+import jalview.schemes.ResidueProperties;
+import jalview.schemes.UserColourScheme;
+import jalview.util.ColorUtils;
+import jalview.util.Format;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.xml.binding.jalview.JalviewUserColours;
+import jalview.xml.binding.jalview.JalviewUserColours.Colour;
+import jalview.xml.binding.jalview.ObjectFactory;
+
 /**
  * This panel allows the user to assign colours to Amino Acid residue codes, and
  * save the colour scheme.
@@ -652,45 +651,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 529e1b2..88524d1 100644 (file)
@@ -32,6 +32,11 @@ 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;
@@ -106,8 +111,14 @@ 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))
@@ -116,25 +127,41 @@ public class BackupFiles
     }
     else
     {
+      this.setMyFuture();
       savesInProgress.add(this);
       return true;
     }
   }
 
-  private boolean removeSaveInProgress()
+  private boolean removeSaveInProgress(boolean ret)
   {
     if (savesInProgress.contains(this))
     {
+      this.getMyFuture().complete(ret);
       // remove all occurrences
       while (savesInProgress.remove(this))
       {
       }
       return true;
     }
-    else
+    return false;
+  }
+
+  private static CompletableFuture<Boolean> getNewFuture()
+  {
+    return new CompletableFuture<Boolean>()
     {
-      return false;
-    }
+    };
+  }
+
+  private CompletableFuture<Boolean> getMyFuture()
+  {
+    return this.myFuture;
+  }
+
+  private void setMyFuture()
+  {
+    this.myFuture = getNewFuture();
   }
 
   public static boolean hasSavesInProgress()
@@ -152,6 +179,41 @@ public class BackupFiles
     return files;
   }
 
+  public static List<CompletableFuture<Boolean>> savesInProgressCompletableFutures()
+  {
+    List<CompletableFuture<Boolean>> cfs = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      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));
@@ -871,7 +933,7 @@ public class BackupFiles
     }
 
     // remove this file from the save in progress stack
-    removeSaveInProgress();
+    removeSaveInProgress(rename);
 
     return rename;
   }
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 a9101a1..dba7ad1 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;
@@ -44,18 +38,26 @@ 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;
 import javax.swing.JCheckBox;
 import javax.swing.JFileChooser;
 import javax.swing.JList;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SpringLayout;
 import javax.swing.filechooser.FileFilter;
 import javax.swing.plaf.basic.BasicFileChooserUI;
 
+import jalview.bin.Cache;
+import jalview.gui.JvOptionPane;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.dialogrunner.DialogRunnerI;
+
 /**
  * Enhanced file chooser dialog box.
  *
@@ -70,7 +72,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;
 
@@ -488,10 +490,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)
       {
@@ -596,8 +601,26 @@ public class JalviewFileChooser extends JFileChooser
 
   }
 
+  /*
   @Override
-  public DialogRunnerI setResponseHandler(Object response, Runnable action)
+  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, Callable action)
   {
     callbacks.put(response, action);
     return this;
@@ -613,10 +636,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 7b4f477..3343c88 100755 (executable)
@@ -34,7 +34,6 @@ import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.gui.Desktop;
 import jalview.io.FileFormatException;
-import jalview.jbgui.QuitHandler.QResponse;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
@@ -215,14 +214,8 @@ public class GDesktop extends JFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        QResponse qresponse = Desktop.instance != null
-                ? Desktop.instance.desktopQuit()
-                : QResponse.QUIT;
-        if (qresponse == QResponse.CANCEL_QUIT)
-        {
-          jalview.bin.Console
-                  .debug("GDesktop: Quit action cancelled by user");
-        }
+        if (Desktop.instance != null)
+          Desktop.instance.desktopQuit();
       }
     });
     aboutMenuItem.setText(MessageManager.getString("label.about"));
index 6a95db7..6369b1a 100644 (file)
@@ -1,8 +1,12 @@
 package jalview.jbgui;
 
 import java.io.File;
-import java.util.Date;
 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 javax.swing.JFrame;
 import javax.swing.JOptionPane;
@@ -10,11 +14,13 @@ import javax.swing.JOptionPane;
 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.gui.AlignFrame;
 import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
 import jalview.io.BackupFiles;
 import jalview.project.Jalview2XML;
 import jalview.util.MessageManager;
@@ -22,37 +28,48 @@ import jalview.util.Platform;
 
 public class QuitHandler
 {
+  private static final int INITIAL_WAIT_FOR_SAVE = 3000;
+
+  private static final int NON_INTERACTIVE_WAIT_CYCLES = 2;
+
   public static enum QResponse
   {
-    QUIT, CANCEL_QUIT, FORCE_QUIT
+    NULL, QUIT, CANCEL_QUIT, FORCE_QUIT
   };
 
-  public static void setQuitHandler()
+  private static ExecutorService executor = Executors.newFixedThreadPool(3);
+
+  public static QResponse setQuitHandler()
   {
     FlatDesktop.setQuitHandler(response -> {
-      QResponse qresponse = getQuitResponse();
-      switch (qresponse)
-      {
-      case QUIT:
+      Callable<QResponse> performQuit = () -> {
         response.performQuit();
-        break;
-      case CANCEL_QUIT:
-        response.cancelQuit();
-        break;
-      case FORCE_QUIT:
+        return setResponse(QResponse.QUIT);
+      };
+      Callable<QResponse> performForceQuit = () -> {
         response.performQuit();
-        break;
-      default:
+        return setResponse(QResponse.FORCE_QUIT);
+      };
+      Callable<QResponse> cancelQuit = () -> {
         response.cancelQuit();
-      }
+        // reset
+        setResponse(QResponse.NULL);
+        // but return cancel
+        return QResponse.CANCEL_QUIT;
+      };
+      QResponse qresponse = getQuitResponse(true, performQuit,
+              performForceQuit, cancelQuit);
     });
+
+    return gotQuitResponse();
   }
 
-  private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT;
+  private static QResponse gotQuitResponse = QResponse.NULL;
 
-  private static QResponse returnResponse(QResponse qresponse)
+  private static QResponse setResponse(QResponse qresponse)
   {
     gotQuitResponse = qresponse;
+    Console.debug("##### Setting gotQuitResponse to " + qresponse);
     return qresponse;
   }
 
@@ -61,19 +78,47 @@ public class QuitHandler
     return gotQuitResponse;
   }
 
-  public static QResponse getQuitResponse()
+  public static final Callable<QResponse> defaultCancelQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CANCELLED by user");
+    // reset
+    setResponse(QResponse.NULL);
+    // and return cancel
+    return QResponse.CANCEL_QUIT;
+  };
+
+  public static final Callable<QResponse> defaultOkQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CONFIRMED by user");
+    return setResponse(QResponse.QUIT);
+  };
+
+  public static final Callable<QResponse> defaultForceQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action FORCED by user");
+    // note that shutdown hook will not be run
+    Runtime.getRuntime().halt(0);
+    return setResponse(QResponse.FORCE_QUIT); // this line never reached!
+  };
+
+  public static QResponse getQuitResponse(boolean ui)
   {
-    return getQuitResponse(true);
+    return getQuitResponse(ui, defaultOkQuit, defaultForceQuit,
+            defaultCancelQuit);
   }
 
-  public static QResponse getQuitResponse(boolean ui)
+  private static boolean interactive = true;
+
+  public static QResponse getQuitResponse(boolean ui,
+          Callable<QResponse> okQuit, Callable<QResponse> forceQuit,
+          Callable<QResponse> cancelQuit)
   {
-    if (gotQuitResponse() != QResponse.CANCEL_QUIT)
+    QResponse got = gotQuitResponse();
+    if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT)
     {
-      return returnResponse(getQuitResponse());
+      // quit has already been selected, continue with calling quit method
+      Console.debug("##### getQuitResponse called. gotQuitResponse=" + got);
+      return got;
     }
 
-    boolean interactive = ui && !Platform.isHeadless();
+    interactive = ui && !Platform.isHeadless();
     // confirm quit if needed and wanted
     boolean confirmQuit = true;
 
@@ -96,45 +141,112 @@ public class QuitHandler
               + "' is/defaults to " + confirmQuit + " -- "
               + (confirmQuit ? "" : "not ") + "confirming quit");
     }
+    got = confirmQuit ? QResponse.NULL : QResponse.QUIT;
+    Console.debug("initial calculation, got=" + got);
+    setResponse(got);
 
-    int answer = JOptionPane.OK_OPTION;
-
-    // if going to confirm, do it before the save in progress check to give
-    // the save time to finish!
     if (confirmQuit)
     {
-      answer = frameOnTop(
+
+      Console.debug("********************ABOUT TO CONFIRM QUIT");
+      JvOptionPane quitDialog = JvOptionPane.newOptionDialog()
+              .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
+              .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit);
+      quitDialog.showDialogOnTopAsync(
               new StringBuilder(
                       MessageManager.getString("label.quit_jalview"))
-                              .append(" ")
+                              .append("\n")
                               .append(MessageManager
                                       .getString("label.unsaved_changes"))
                               .toString(),
               MessageManager.getString("action.quit"),
-              JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
+              JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+              new Object[]
+              { MessageManager.getString("action.quit"),
+                  MessageManager.getString("action.cancel") },
+              MessageManager.getString("action.quit"), true);
     }
 
-    if (answer == JOptionPane.CANCEL_OPTION)
+    got = gotQuitResponse();
+    Console.debug("first user response, got=" + got);
+    boolean wait = false;
+    if (got == QResponse.CANCEL_QUIT)
     {
-      Console.debug("QuitHandler: Quit action cancelled by user");
-      return returnResponse(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())
+      {
+        /*
+        Future<QResponse> waitGot = executor.submit(waitQuitCall);
+        try
+        {
+          got = waitGot.get();
+        } catch (InterruptedException | ExecutionException e)
+        {
+          jalview.bin.Console.debug(
+                  "Exception during quit handling (wait for save)", e);
+        }
+        */
+        QResponse waitResponse = waitQuit(interactive, okQuit, forceQuit,
+                cancelQuit);
+        wait = waitResponse == QResponse.QUIT;
+      }
     }
 
-    // check for saves in progress
-    int waitForSave = 1000; // MAKE THIS BETTER
-    AlignFrame[] afArray = Desktop.getAlignFrames();
-    if (afArray == null || afArray.length == 0)
+    Callable<QResponse> next = null;
+    switch (gotQuitResponse())
     {
-      // no change
+    case QUIT:
+      Console.debug("### User selected QUIT");
+      next = okQuit;
+      break;
+    case FORCE_QUIT: // not actually an option at this stage
+      Console.debug("### User selected FORCE QUIT");
+      next = forceQuit;
+      break;
+    default:
+      Console.debug("### User selected CANCEL QUIT");
+      next = cancelQuit;
+      break;
     }
-    else
+    try
+    {
+      got = executor.submit(next).get();
+    } catch (InterruptedException | ExecutionException e)
+    {
+      jalview.bin.Console
+              .debug("Exception during quit handling (final choice)", e);
+    }
+    jalview.bin.Console.debug("### nextResponse=" + got.toString());
+    setResponse(got);
+
+    return gotQuitResponse();
+  }
+
+  private static QResponse waitQuit(boolean interactive,
+          Callable<QResponse> okQuit, Callable<QResponse> forceQuit,
+          Callable<QResponse> cancelQuit)
+  {
+    jalview.bin.Console.debug("#### waitQuit started");
+    // check for saves in progress
+    if (!BackupFiles.hasSavesInProgress())
+      return QResponse.QUIT;
+
+    int waitTime = INITIAL_WAIT_FOR_SAVE; // start with 3 second wait
+    AlignFrame[] afArray = Desktop.getAlignFrames();
+    if (!(afArray == null || afArray.length == 0))
     {
       int size = 0;
       for (int i = 0; i < afArray.length; i++)
       {
         AlignFrame af = afArray[i];
-        List<AlignmentViewPanel> avpList = (List<AlignmentViewPanel>) af
-                .getAlignPanels();
+        List<? extends AlignmentViewPanel> avpList = af.getAlignPanels();
         for (AlignmentViewPanel avp : avpList)
         {
           AlignmentI a = avp.getAlignment();
@@ -145,101 +257,174 @@ public class QuitHandler
           }
         }
       }
-      waitForSave = size;
-      Console.debug("Set waitForSave to " + waitForSave);
+      waitTime = Math.max(waitTime, size / 2);
+      Console.debug("Set waitForSave to " + waitTime);
     }
-    int waitIncrement = 3000;
-    long startTime = new Date().getTime();
-    boolean saving = BackupFiles.hasSavesInProgress();
-    if (saving)
+
+    // 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())
+    {
+      // if this is the last one then complete filesAllSaved
+      cf.whenComplete((ret, e) -> {
+        if (!BackupFiles.hasSavesInProgress())
+        {
+          filesAllSaved.complete(true);
+        }
+      });
+    }
+
+    final int waitTimeFinal = waitTime;
+    // timeout the wait -- will result in another wait button when looped
+    CompletableFuture<Boolean> waitTimeout = CompletableFuture
+            .supplyAsync(() -> {
+              executor.submit(() -> {
+                try
+                {
+                  Thread.sleep(waitTimeFinal);
+                } catch (InterruptedException e)
+                {
+                  // probably interrupted by all files saving
+                }
+              });
+              return true;
+            });
+    CompletableFuture<Object> waitForSave = CompletableFuture
+            .anyOf(waitTimeout, filesAllSaved);
+    Console.debug("##### WAITFORSAVE RUNNING");
+
+    QResponse waitResponse = QResponse.NULL;
+
+    int iteration = 0;
+    boolean doIterations = true;
+    while (doIterations && BackupFiles.hasSavesInProgress()
+            && iteration++ < (interactive ? 100 : 5))
     {
-      boolean waiting = (new Date().getTime() - startTime) < waitForSave;
-      while (saving && waiting)
+      try
       {
-        saving = !waitForSave(waitIncrement);
-        waiting = (new Date().getTime() - startTime) < waitForSave;
+        waitForSave.get();
+      } catch (InterruptedException | ExecutionException e1)
+      {
+        Console.debug(
+                "Exception whilst waiting for files to save before quitting",
+                e1);
       }
-
-      if (saving) // still saving after a wait
+      if (interactive)
       {
-        StringBuilder messageSB = new StringBuilder(
-                MessageManager.getString("label.save_in_progress"));
-        messageSB.append(":");
-        boolean any = false;
-        for (File file : BackupFiles.savesInProgressFiles())
+        Console.debug("********************About to make waitDialog");
+        JvOptionPane waitDialog = JvOptionPane.newOptionDialog()
+                .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
+                .setResponseHandler(JOptionPane.NO_OPTION, forceQuit)
+                .setResponseHandler(JOptionPane.CANCEL_OPTION, cancelQuit);
+
+        // callback as each file finishes saving
+        for (CompletableFuture<Boolean> cf : BackupFiles
+                .savesInProgressCompletableFutures())
         {
-          messageSB.append("\n- ");
-          messageSB.append(file.getName());
-          any = true;
+          // update the list of saving files as they save too
+          cf.thenRun(() -> {
+            waitDialog.setMessage(waitingForSaveMessage());
+          });
+          // if this is the last one then close the dialog
+          cf.whenComplete((ret, e) -> {
+            if (!BackupFiles.hasSavesInProgress())
+            {
+              // like a click on Wait button
+              Console.debug(
+                      "***** TRYING TO MAKE THE WAIT FOR SAVE DIALOG DISAPPEAR!");
+              waitDialog.setValue(JOptionPane.YES_OPTION);
+            }
+          });
         }
-        if (!any)
-        {
-          messageSB.append("\n");
-          messageSB.append(MessageManager.getString("label.unknown"));
-        }
-        int waitLonger = interactive ? JOptionPane.YES_OPTION
-                : JOptionPane.NO_OPTION;
-        while (saving && waitLonger == JOptionPane.YES_OPTION)
+
+        waitDialog.showDialogOnTopAsync(waitingForSaveMessage(),
+                MessageManager.getString("action.wait"),
+                JOptionPane.YES_NO_CANCEL_OPTION,
+                JOptionPane.WARNING_MESSAGE, null, new Object[]
+                { MessageManager.getString("action.wait"),
+                    MessageManager.getString("action.force_quit"),
+                    MessageManager.getString("action.cancel_quit") },
+                MessageManager.getString("action.wait"), true);
+        Console.debug("********************Finished waitDialog");
+
+        waitResponse = gotQuitResponse();
+        Console.debug("####### WAITFORSAVE SET: " + waitResponse);
+        switch (waitResponse)
         {
-          waitLonger = waitForceQuitCancelQuitOptionDialog(
-                  messageSB.toString(),
-                  MessageManager.getString("action.wait"));
-          if (waitLonger == JOptionPane.YES_OPTION) // wait
-          {
-            // do wait stuff
-            saving = !waitForSave(waitIncrement);
-          }
-          else if (waitLonger == JOptionPane.NO_OPTION) // force quit
-          {
-            // do a force quit
-            return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this
-          }
-          else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit
-          {
-            return returnResponse(QResponse.CANCEL_QUIT);
-          }
-          else
-          {
-            Console.debug("**** Shouldn't have got here!");
-          }
+        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
 
-    // not cancelled and not saving
-    return returnResponse(QResponse.QUIT);
-  }
+    }
+    waitResponse = gotQuitResponse();
 
-  public static int frameOnTop(String label, String actionString,
-          int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
-  {
-    return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION,
-            JOPTIONPANE_MESSAGETYPE);
-  }
+    Console.debug("####### WAITFORSAVE RETURNING: " + waitResponse);
+    return waitResponse;
+  };
 
-  public static int frameOnTop(JFrame dialogParent, String label,
-          String actionString, int JOPTIONPANE_OPTION,
-          int JOPTIONPANE_MESSAGETYPE)
+  public static void okk()
   {
-    // 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
-
-    dialogParent.setAlwaysOnTop(true);
-
-    int answer = JOptionPane.showConfirmDialog(dialogParent, label,
-            actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
-
-    dialogParent.setAlwaysOnTop(false);
-    dialogParent.dispose();
-
-    return answer;
-  }
+    /*
+    if (false)
+    {
+      if (false)
+      {
+    
+        waitLonger = JOptionPane.showOptionDialog(dialogParent,
+                waitingForSaveMessage(),
+                MessageManager.getString("action.wait"),
+                JOptionPane.YES_NO_CANCEL_OPTION,
+                JOptionPane.WARNING_MESSAGE, null, options, wait);
+      }
+      else
+      {
+        // non-interactive
+        waitLonger = iteration < NON_INTERACTIVE_WAIT_CYCLES
+                ? JOptionPane.YES_OPTION
+                : JOptionPane.NO_OPTION;
+      }
+    
+      if (waitLonger == JOptionPane.YES_OPTION) // "wait"
+      {
+        saving = !waitForSave(waitIncrement);
+      }
+      else if (waitLonger == JOptionPane.NO_OPTION) // "force
+      // quit"
+      {
+        // do a force quit
+        return setResponse(QResponse.FORCE_QUIT);
+      }
+      else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit
+      {
+        return setResponse(QResponse.CANCEL_QUIT);
+      }
+      else
+      {
+        // Most likely got here by user dismissing the dialog with the
+        // 'x'
+        // -- treat as a "Cancel"
+        return setResponse(QResponse.CANCEL_QUIT);
+      }
+    }
+    
+    // not sure how we got here, best be safe
+    return QResponse.CANCEL_QUIT;
+    */
+  };
 
   private static int waitForceQuitCancelQuitOptionDialog(Object message,
           String title)
@@ -251,14 +436,38 @@ public class QuitHandler
         MessageManager.getString("action.force_quit"),
         MessageManager.getString("action.cancel_quit") };
 
+    // BackupFiles.setWaitForSaveDialog(dialogParent);
+
     int answer = JOptionPane.showOptionDialog(dialogParent, message, title,
             JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE,
             null, options, wait);
 
+    // BackupFiles.clearWaitForSaveDialog();
+
     return answer;
   }
 
-  private static boolean waitForSave(long t)
+  private static String waitingForSaveMessage()
+  {
+    StringBuilder messageSB = new StringBuilder(
+            MessageManager.getString("label.save_in_progress"));
+    boolean any = false;
+    for (File file : BackupFiles.savesInProgressFiles())
+    {
+      messageSB.append("\n- ");
+      messageSB.append(file.getName());
+      any = true;
+    }
+    if (!any)
+    {
+      messageSB.append("\n");
+      messageSB.append(MessageManager.getString("label.unknown"));
+    }
+
+    return messageSB.toString();
+  }
+
+  private static Boolean waitForSave(long t)
   {
     boolean ret = false;
     try
index fde80f7..9be64c6 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 action);
+
+  // DialogRunnerI setResponseHandler(Object response, Runnable action);
 
   /**
    * Runs the registered handler (if any) for the given response. The default