Merge branch 'merge/JAL-4054+JAL-4064' into ben-big-merge
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:58:36 +0000 (17:58 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:58:36 +0000 (17:58 +0000)
60 files changed:
resources/images/windowIcons/alignmentIcon.png [new file with mode: 0644]
resources/images/windowIcons/annotationIcon.png [new file with mode: 0644]
resources/images/windowIcons/featuresIcon.png [new file with mode: 0644]
resources/images/windowIcons/fetchIcon.png [new file with mode: 0644]
resources/images/windowIcons/overviewIcon.png [new file with mode: 0644]
resources/images/windowIcons/plainIcon.png [new file with mode: 0644]
resources/images/windowIcons/preferencesIcon.png [new file with mode: 0644]
resources/images/windowIcons/splitIcon.png [new file with mode: 0644]
resources/images/windowIcons/structureIcon.png [new file with mode: 0644]
resources/images/windowIcons/treeIcon.png [new file with mode: 0644]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/gui/APQHandlers.java [moved from src/jalview/jbgui/APQHandlers.java with 55% similarity]
src/jalview/gui/AlignExportOptions.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationChooser.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AppVarna.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/Console.java
src/jalview/gui/CutAndPasteHtmlTransfer.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/EditNameDialog.java
src/jalview/gui/FeatureEditor.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/FontChooser.java
src/jalview/gui/ImageExporter.java
src/jalview/gui/JvOptionPane.java
src/jalview/gui/LineartOptions.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/QuitHandler.java [new file with mode: 0644]
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/SliderPanel.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/TreePanel.java
src/jalview/gui/UserDefinedColours.java
src/jalview/gui/WebserviceInfo.java
src/jalview/gui/WindowIcons.java [new file with mode: 0644]
src/jalview/io/BackupFiles.java
src/jalview/io/FileLoader.java
src/jalview/io/HtmlSvgOutput.java
src/jalview/io/JalviewFileChooser.java
src/jalview/jbgui/GDesktop.java
src/jalview/project/Jalview2XML.java
src/jalview/util/dialogrunner/DialogRunnerI.java
src/jalview/viewmodel/AlignmentViewport.java

diff --git a/resources/images/windowIcons/alignmentIcon.png b/resources/images/windowIcons/alignmentIcon.png
new file mode 100644 (file)
index 0000000..9f3d779
Binary files /dev/null and b/resources/images/windowIcons/alignmentIcon.png differ
diff --git a/resources/images/windowIcons/annotationIcon.png b/resources/images/windowIcons/annotationIcon.png
new file mode 100644 (file)
index 0000000..a730b7e
Binary files /dev/null and b/resources/images/windowIcons/annotationIcon.png differ
diff --git a/resources/images/windowIcons/featuresIcon.png b/resources/images/windowIcons/featuresIcon.png
new file mode 100644 (file)
index 0000000..84da9f8
Binary files /dev/null and b/resources/images/windowIcons/featuresIcon.png differ
diff --git a/resources/images/windowIcons/fetchIcon.png b/resources/images/windowIcons/fetchIcon.png
new file mode 100644 (file)
index 0000000..570a929
Binary files /dev/null and b/resources/images/windowIcons/fetchIcon.png differ
diff --git a/resources/images/windowIcons/overviewIcon.png b/resources/images/windowIcons/overviewIcon.png
new file mode 100644 (file)
index 0000000..924ac70
Binary files /dev/null and b/resources/images/windowIcons/overviewIcon.png differ
diff --git a/resources/images/windowIcons/plainIcon.png b/resources/images/windowIcons/plainIcon.png
new file mode 100644 (file)
index 0000000..dbbad71
Binary files /dev/null and b/resources/images/windowIcons/plainIcon.png differ
diff --git a/resources/images/windowIcons/preferencesIcon.png b/resources/images/windowIcons/preferencesIcon.png
new file mode 100644 (file)
index 0000000..29ebf39
Binary files /dev/null and b/resources/images/windowIcons/preferencesIcon.png differ
diff --git a/resources/images/windowIcons/splitIcon.png b/resources/images/windowIcons/splitIcon.png
new file mode 100644 (file)
index 0000000..c6a0520
Binary files /dev/null and b/resources/images/windowIcons/splitIcon.png differ
diff --git a/resources/images/windowIcons/structureIcon.png b/resources/images/windowIcons/structureIcon.png
new file mode 100644 (file)
index 0000000..8cda58b
Binary files /dev/null and b/resources/images/windowIcons/structureIcon.png differ
diff --git a/resources/images/windowIcons/treeIcon.png b/resources/images/windowIcons/treeIcon.png
new file mode 100644 (file)
index 0000000..02f04ed
Binary files /dev/null and b/resources/images/windowIcons/treeIcon.png differ
index 3843ddb..a406f33 100644 (file)
@@ -32,7 +32,17 @@ action.load_project = Load Project
 action.save_project = Save Project
 action.save_project_as = Save Project as...
 action.quit = Quit
-label.quit_jalview = Quit Jalview?
+action.force_quit = Force quit
+label.quit_jalview = Are you sure you want to quit Jalview?
+label.wait_for_save = Wait for save
+label.unsaved_changes = There are unsaved changes.
+label.save_in_progress = Some files are still saving:
+label.unknown = Unknown
+label.quit_after_saving = Jalview will quit after saving.
+label.all_saved = All files saved.
+label.quitting_bye = Quitting, bye!
+action.wait = Wait
+action.cancel_quit = Cancel quit
 action.expand_views = Expand Views
 action.gather_views = Gather Views
 action.page_setup = Page Setup...
index d0bfd65..b50226a 100644 (file)
@@ -32,7 +32,17 @@ action.load_project = Cargar proyecto
 action.save_project = Guardar proyecto
 action.save_project_as = Guardar proyecto como...
 action.quit = Salir
-label.quit_jalview = Salir de Jalview?
+action.force_quit = Forzar la salida
+label.quit_jalview = ¿Estás seguro de que quieres salir de Jalview?
+label.wait_for_save = Esperar a guardar
+label.unsaved_changes = Hay cambios sin guardar.
+label.save_in_progress = Algunos archivos aún se están guardando:
+label.unknown = desconocido
+label.quit_after_saving = Jalview se cerrará después de guardar.
+label.all_saved = Todos los archivos guardados.
+label.quitting_bye = Saliendo ¡chao!
+action.wait = Espere
+action.cancel_quit = Cancelar la salida
 action.expand_views = Expandir vistas
 action.gather_views = Capturar vistas
 action.page_setup = Configuración de la página
index 370a243..bb70c40 100755 (executable)
@@ -1190,6 +1190,7 @@ public class Cache
     sb.append("Java version: ");
     sb.append(System.getProperty("java.version"));
     sb.append("\n");
+    sb.append("Java platform: ");
     sb.append(System.getProperty("os.arch"));
     sb.append(" ");
     sb.append(System.getProperty("os.name"));
@@ -1210,17 +1211,19 @@ public class Cache
     sb.append(" (");
     sb.append(lafClass);
     sb.append(")\n");
+    appendIfNotNull(sb, "Channel: ",
+            ChannelProperties.getProperty("channel"), "\n", null);
     if (Console.isDebugEnabled()
             || !"release".equals(ChannelProperties.getProperty("channel")))
     {
-      appendIfNotNull(sb, "Channel: ",
-              ChannelProperties.getProperty("channel"), "\n", null);
       appendIfNotNull(sb, "Getdown appdir: ",
               System.getProperty("getdowninstanceappdir"), "\n", null);
       appendIfNotNull(sb, "Getdown appbase: ",
               System.getProperty("getdowninstanceappbase"), "\n", null);
       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
               "\n", "unknown");
+      appendIfNotNull(sb, "Preferences file: ", propertiesFile, "\n",
+              "unknown");
     }
     return sb.toString();
   }
@@ -1401,10 +1404,11 @@ public class Cache
                 if (customProxySet &&
                 // we have a username but no password for the scheme being
                 // requested
-                (protocol.equalsIgnoreCase("http")
-                        && (httpUser != null && httpUser.length() > 0
-                                && (httpPassword == null
-                                        || httpPassword.length == 0)))
+                        (protocol.equalsIgnoreCase("http")
+                                && (httpUser != null
+                                        && httpUser.length() > 0
+                                        && (httpPassword == null
+                                                || httpPassword.length == 0)))
                         || (protocol.equalsIgnoreCase("https")
                                 && (httpsUser != null
                                         && httpsUser.length() > 0
index 1428906..b87a14d 100755 (executable)
@@ -61,6 +61,8 @@ import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.PromptUserConfig;
+import jalview.gui.QuitHandler;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
@@ -271,6 +273,28 @@ public class Jalview
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
+
+      Runtime.getRuntime().addShutdownHook(new Thread()
+      {
+        public void run()
+        {
+          Console.debug("Running shutdown hook");
+          if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
+          {
+            // Got to here by a SIGTERM signal.
+            // Note we will not actually cancel the quit from here -- it's too
+            // late -- but we can wait for saving files.
+            Console.debug("Checking for saving files");
+            QuitHandler.getQuitResponse(false);
+          }
+          else
+          {
+            Console.debug("Nothing more to do");
+          }
+          Console.debug("Exiting, bye!");
+          // shutdownHook cannot be cancelled, JVM will now halt
+        }
+      });
     }
 
     System.out
@@ -279,6 +303,7 @@ public class Jalview
     System.out.println(System.getProperty("os.arch") + " "
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
+
     String val = System.getProperty("sys.install4jVersion");
     if (val != null)
     {
@@ -299,10 +324,12 @@ public class Jalview
     Cache.loadBuildProperties(true);
 
     ArgsParser aparser = new ArgsParser(args);
+
     boolean headless = false;
 
     String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this before
+    Cache.loadProperties(usrPropsFile); // must do this
+                                        // before
     if (usrPropsFile != null)
     {
       System.out.println(
@@ -380,7 +407,9 @@ public class Jalview
     try
     {
       Console.initLogger();
-    } catch (NoClassDefFoundError error)
+    } catch (
+
+    NoClassDefFoundError error)
     {
       error.printStackTrace();
       System.out.println("\nEssential logging libraries not found."
@@ -555,8 +584,11 @@ public class Jalview
     }
 
     String file = null, data = null;
+
     FileFormatI format = null;
+
     DataSourceType protocol = null;
+
     FileLoader fileLoader = new FileLoader(!headless);
 
     String groovyscript = null; // script to execute after all loading is
@@ -570,6 +602,7 @@ public class Jalview
       System.out.println("No files to open!");
       System.exit(1);
     }
+
     long progress = -1;
     // Finally, deal with the remaining input data.
     if (file != null)
@@ -829,6 +862,7 @@ public class Jalview
         }
       }
     }
+
     AlignFrame startUpAlframe = null;
     // We'll only open the default file if the desktop is visible.
     // And the user
@@ -1123,6 +1157,8 @@ public class Jalview
       UIManager.put("TabbedPane.tabWidthMode", "compact");
       UIManager.put("TabbedPane.selectedBackground", Color.white);
     }
+
+    Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
     return set;
   }
 
@@ -1369,19 +1405,12 @@ public class Jalview
   }
 
   /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
+   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
    */
   public void quit()
   {
-    if (desktop != null)
-    {
-      desktop.quit();
-    }
-    else
-    {
-      System.exit(0);
-    }
+    // System.exit will run the shutdownHook first
+    System.exit(0);
   }
 
   public static AlignFrame getCurrentAlignFrame()
similarity index 55%
rename from src/jalview/jbgui/APQHandlers.java
rename to src/jalview/gui/APQHandlers.java
index 1a7e971..00ec217 100644 (file)
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
-package jalview.jbgui;
-
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
+package jalview.gui;
 
 import com.formdev.flatlaf.extras.FlatDesktop;
 import com.formdev.flatlaf.extras.FlatDesktop.Action;
 
-import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 public class APQHandlers
@@ -37,7 +33,7 @@ public class APQHandlers
 
   public static boolean setQuit = false;
 
-  public static boolean setAPQHandlers(GDesktop desktop)
+  public static boolean setAPQHandlers(Desktop desktop)
   {
     if (Platform.isJS())
     {
@@ -59,47 +55,7 @@ public class APQHandlers
     }
     if (FlatDesktop.isSupported(Action.APP_QUIT_HANDLER))
     {
-      FlatDesktop.setQuitHandler(response -> {
-        boolean confirmQuit = jalview.bin.Cache.getDefault(
-                jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
-        boolean canQuit = !confirmQuit;
-        int n;
-        if (confirmQuit)
-        {
-          // ensure Jalview window is brought to front for Quit confirmation
-          // window to be visible
-
-          // this method of raising the Jalview window is broken in java
-          // jalviewDesktop.setVisible(true);
-          // jalviewDesktop.toFront();
-
-          // a better hack which works instead
-          JFrame dialogParent = new JFrame();
-          dialogParent.setAlwaysOnTop(true);
-
-          n = JOptionPane.showConfirmDialog(dialogParent,
-                  MessageManager.getString("label.quit_jalview"),
-                  MessageManager.getString("action.quit"),
-                  JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
-                  null);
-
-          dialogParent.setAlwaysOnTop(false);
-          dialogParent.dispose();
-        }
-        else
-        {
-          n = JOptionPane.OK_OPTION;
-        }
-        canQuit = (n == JOptionPane.OK_OPTION);
-        if (canQuit)
-        {
-          response.performQuit();
-        }
-        else
-        {
-          response.cancelQuit();
-        }
-      });
+      QuitHandler.setQuitHandler();
       setQuit = true;
     }
     // if we got to here, no exceptions occurred when we set the handlers.
index 08ff021..a23cbfa 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignExportSettingsI;
-import jalview.api.AlignViewportI;
-import jalview.io.FileFormatI;
-import jalview.util.MessageManager;
-
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.util.concurrent.Callable;
 
 import javax.swing.JCheckBox;
 import javax.swing.JPanel;
 
+import jalview.api.AlignExportSettingsI;
+import jalview.api.AlignViewportI;
+import jalview.io.FileFormatI;
+import jalview.util.MessageManager;
+
 /**
  * A dialog that allows the user to specify whether to include hidden columns or
  * sequences in an alignment export, and possibly features, annotations and
@@ -119,7 +120,7 @@ public class AlignExportOptions extends JPanel
    * 
    * @param action
    */
-  public void setResponseAction(Object response, Runnable action)
+  public void setResponseAction(Object response, Callable action)
   {
     dialog.setResponseHandler(response, action);
   }
index e24cbea..c11b866 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;
@@ -59,7 +57,9 @@ import java.util.Deque;
 import java.util.Enumeration;
 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;
@@ -350,6 +350,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    */
   void init()
   {
+    setFrameIcon(WindowIcons.alignmentIcon);
+
     // setBackground(Color.white); // BH 2019
 
     if (!Jalview.isHeadlessMode())
@@ -1259,6 +1261,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
               shortName);
 
+      Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+      if (lastSaveSuccessful)
+      {
+        this.getViewport().setSavedUpToDate(true);
+      }
+
       statusBar.setText(MessageManager.formatMessage(
               "label.successfully_saved_to_file_in_format", new Object[]
               { file, format }));
@@ -1267,97 +1275,89 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable cancelAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> cancelAction = () -> {
+      lastSaveSuccessful = false;
+      return null;
+    };
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      String output = new FormatAdapter(alignPanel, options)
+              .formatSequences(format, exportData.getAlignment(),
+                      exportData.getOmitHidden(),
+                      exportData.getStartEndPostions(),
+                      viewport.getAlignment().getHiddenColumns());
+      if (output == null)
       {
         lastSaveSuccessful = false;
       }
-    };
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+      else
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        String output = new FormatAdapter(alignPanel, options)
-                .formatSequences(format, exportData.getAlignment(),
-                        exportData.getOmitHidden(),
-                        exportData.getStartEndPostions(),
-                        viewport.getAlignment().getHiddenColumns());
-        if (output == null)
+        // create backupfiles object and get new temp filename destination
+        boolean doBackup = BackupFiles.getEnabled();
+        BackupFiles backupfiles = null;
+        if (doBackup)
         {
-          lastSaveSuccessful = false;
+          Console.trace("ALIGNFRAME making backupfiles object for " + file);
+          backupfiles = new BackupFiles(file);
         }
-        else
+        try
         {
-          // create backupfiles object and get new temp filename destination
-          boolean doBackup = BackupFiles.getEnabled();
-          BackupFiles backupfiles = null;
-          if (doBackup)
+          String tempFilePath = doBackup ? backupfiles.getTempFilePath()
+                  : file;
+          Console.trace("ALIGNFRAME setting PrintWriter");
+          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
+
+          if (backupfiles != null)
           {
-            Console.trace(
-                    "ALIGNFRAME making backupfiles object for " + file);
-            backupfiles = new BackupFiles(file);
+            Console.trace("ALIGNFRAME about to write to temp file "
+                    + backupfiles.getTempFilePath());
           }
-          try
-          {
-            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
-                    : file;
-            Console.trace("ALIGNFRAME setting PrintWriter");
-            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 
-            if (backupfiles != null)
-            {
-              Console.trace("ALIGNFRAME about to write to temp file "
-                      + backupfiles.getTempFilePath());
-            }
+          out.print(output);
+          Console.trace("ALIGNFRAME about to close file");
+          out.close();
+          Console.trace("ALIGNFRAME closed file");
+          AlignFrame.this.setTitle(file);
+          statusBar.setText(MessageManager.formatMessage(
+                  "label.successfully_saved_to_file_in_format", new Object[]
+                  { fileName, format.getName() }));
+          lastSaveSuccessful = true;
+        } catch (IOException e)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something happened writing the temp file");
+          Console.error(e.getMessage());
+          Console.debug(Cache.getStackTraceString(e));
+        } catch (Exception ex)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something unexpected happened writing the temp file");
+          Console.error(ex.getMessage());
+          Console.debug(Cache.getStackTraceString(ex));
+        }
 
-            out.print(output);
-            Console.trace("ALIGNFRAME about to close file");
-            out.close();
-            Console.trace("ALIGNFRAME closed file");
-            AlignFrame.this.setTitle(file);
-            statusBar.setText(MessageManager.formatMessage(
-                    "label.successfully_saved_to_file_in_format",
-                    new Object[]
-                    { fileName, format.getName() }));
-            lastSaveSuccessful = true;
-          } catch (IOException e)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something happened writing the temp file");
-            Console.error(e.getMessage());
-            Console.debug(Cache.getStackTraceString(e));
-          } catch (Exception ex)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something unexpected happened writing the temp file");
-            Console.error(ex.getMessage());
-            Console.debug(Cache.getStackTraceString(ex));
-          }
+        if (doBackup)
+        {
+          backupfiles.setWriteSuccess(lastSaveSuccessful);
+          Console.debug("ALIGNFRAME writing temp file was "
+                  + (lastSaveSuccessful ? "" : "NOT ") + "successful");
+          // do the backup file roll and rename the temp file to actual file
+          Console.trace("ALIGNFRAME about to rollBackupsAndRenameTempFile");
+          lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
+          Console.debug("ALIGNFRAME performed rollBackupsAndRenameTempFile "
+                  + (lastSaveSuccessful ? "" : "un") + "successfully");
+        }
 
-          if (doBackup)
-          {
-            backupfiles.setWriteSuccess(lastSaveSuccessful);
-            Console.debug("ALIGNFRAME writing temp file was "
-                    + (lastSaveSuccessful ? "" : "NOT ") + "successful");
-            // do the backup file roll and rename the temp file to actual file
-            Console.trace(
-                    "ALIGNFRAME about to rollBackupsAndRenameTempFile");
-            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
-            Console.debug(
-                    "ALIGNFRAME performed rollBackupsAndRenameTempFile "
-                            + (lastSaveSuccessful ? "" : "un")
-                            + "successfully");
-          }
+        Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+        if (lastSaveSuccessful)
+        {
+          AlignFrame.this.getViewport().setSavedUpToDate(true);
         }
       }
+      return null;
     };
 
     /*
@@ -1373,7 +1373,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1390,34 +1397,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     FileFormatI fileFormat = FileFormats.getInstance()
             .forName(fileFormatName);
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      CutAndPasteTransfer cap = new CutAndPasteTransfer();
+      cap.setForInput(null);
+      try
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        CutAndPasteTransfer cap = new CutAndPasteTransfer();
-        cap.setForInput(null);
-        try
-        {
-          FileFormatI format = fileFormat;
-          cap.setText(new FormatAdapter(alignPanel, options)
-                  .formatSequences(format, exportData.getAlignment(),
-                          exportData.getOmitHidden(),
-                          exportData.getStartEndPostions(),
-                          viewport.getAlignment().getHiddenColumns()));
-          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-                  "label.alignment_output_command", new Object[]
-                  { fileFormat.getName() }), 600, 500);
-        } catch (OutOfMemoryError oom)
-        {
-          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
-                  oom);
-          cap.dispose();
-        }
+        FileFormatI format = fileFormat;
+        cap.setText(new FormatAdapter(alignPanel, options).formatSequences(
+                format, exportData.getAlignment(),
+                exportData.getOmitHidden(),
+                exportData.getStartEndPostions(),
+                viewport.getAlignment().getHiddenColumns()));
+        Desktop.addInternalFrame(cap, MessageManager.formatMessage(
+                "label.alignment_output_command", new Object[]
+                { fileFormat.getName() }), 600, 500);
+      } catch (OutOfMemoryError oom)
+      {
+        new OOMWarning("Outputting alignment as " + fileFormat.getName(),
+                oom);
+        cap.dispose();
       }
+      return null;
     };
 
     /*
@@ -1432,7 +1434,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1540,15 +1548,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .getString("label.load_jalview_annotations");
     chooser.setDialogTitle(tooltip);
     chooser.setToolTipText(tooltip);
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
+      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -2470,36 +2474,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Runnable okAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        SequenceI[] cut = sg.getSequences()
-                .toArray(new SequenceI[sg.getSize()]);
+    Callable okAction = () -> {
+      SequenceI[] cut = sg.getSequences()
+              .toArray(new SequenceI[sg.getSize()]);
 
-        addHistoryItem(new EditCommand(
-                MessageManager.getString("label.cut_sequences"), Action.CUT,
-                cut, sg.getStartRes(),
-                sg.getEndRes() - sg.getStartRes() + 1,
-                viewport.getAlignment()));
+      addHistoryItem(new EditCommand(
+              MessageManager.getString("label.cut_sequences"), Action.CUT,
+              cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
+              viewport.getAlignment()));
 
-        viewport.setSelectionGroup(null);
-        viewport.sendSelection();
-        viewport.getAlignment().deleteGroup(sg);
+      viewport.setSelectionGroup(null);
+      viewport.sendSelection();
+      viewport.getAlignment().deleteGroup(sg);
 
-        viewport.firePropertyChange("alignment", null,
-                viewport.getAlignment().getSequences());
-        if (viewport.getAlignment().getHeight() < 1)
+      viewport.firePropertyChange("alignment", null,
+              viewport.getAlignment().getSequences());
+      if (viewport.getAlignment().getHeight() < 1)
+      {
+        try
+        {
+          AlignFrame.this.setClosed(true);
+        } catch (Exception ex)
         {
-          try
-          {
-            AlignFrame.this.setClosed(true);
-          } catch (Exception ex)
-          {
-          }
         }
       }
+      return null;
     };
 
     /*
@@ -2523,7 +2522,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      okAction.run();
+      try
+      {
+        okAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -3444,6 +3449,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .formatMessage("label.overview_params", new Object[]
             { this.getTitle() }), true, frame.getWidth(), frame.getHeight(),
             true, true);
+    frame.setFrameIcon(WindowIcons.overviewIcon);
     frame.pack();
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     frame.addInternalFrameListener(
@@ -4076,36 +4082,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setToolTipText(
             MessageManager.getString("label.load_tree_file"));
 
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
+    chooser.setResponseHandler(0, () -> {
+      String filePath = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", filePath);
+      NewickFile fin = null;
+      try
       {
-        String filePath = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", filePath);
-        NewickFile fin = null;
-        try
-        {
-          fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
-                  DataSourceType.FILE));
-          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
-        } catch (Exception ex)
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
-                  MessageManager
-                          .getString("label.problem_reading_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-          ex.printStackTrace();
-        }
-        if (fin != null && fin.hasWarningMessage())
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop,
-                  fin.getWarningMessage(),
-                  MessageManager.getString(
-                          "label.possible_problem_with_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-        }
+        fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
+                DataSourceType.FILE));
+        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
+      } catch (Exception ex)
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
+                MessageManager.getString("label.problem_reading_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
+        ex.printStackTrace();
+      }
+      if (fin != null && fin.hasWarningMessage())
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop,
+                fin.getWarningMessage(),
+                MessageManager
+                        .getString("label.possible_problem_with_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
       }
+      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -5877,16 +5878,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
     final AlignFrame us = this;
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
-        new VCFLoader(choice).loadVCF(seqs, us);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
+      new VCFLoader(choice).loadVCF(seqs, us);
+      return null;
     });
     chooser.showOpenDialog(null);
 
index 30ccdbe..e7c237e 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.api.AlignViewportI;
@@ -53,17 +64,6 @@ import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.AutoCalcSetting;
 
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.swing.JInternalFrame;
-
 /**
  * DOCUMENT ME!
  * 
@@ -748,27 +748,15 @@ public class AlignViewport extends AlignmentViewport
      * in reverse order)
      */
     JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
-            .setResponseHandler(0, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                addDataToAlignment(al);
-              }
-            }).setResponseHandler(1, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, true);
-              }
-            }).setResponseHandler(2, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, false);
-              }
+            .setResponseHandler(0, () -> {
+              addDataToAlignment(al);
+              return null;
+            }).setResponseHandler(1, () -> {
+              us.openLinkedAlignmentAs(al, title, true);
+              return null;
+            }).setResponseHandler(2, () -> {
+              us.openLinkedAlignmentAs(al, title, false);
+              return null;
             });
     dialog.showDialog(question,
             MessageManager.getString("label.open_split_window"),
index 791421d..ad5e574 100644 (file)
  */
 package jalview.gui;
 
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceGroup;
-import jalview.util.MessageManager;
-
 import java.awt.BorderLayout;
 import java.awt.Checkbox;
 import java.awt.CheckboxGroup;
@@ -45,6 +40,11 @@ import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JPanel;
 
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceGroup;
+import jalview.util.MessageManager;
+
 /**
  * A panel that allows the user to select which sequence-associated annotation
  * rows to show or hide.
@@ -602,6 +602,7 @@ public class AnnotationChooser extends JPanel
   private void showFrame()
   {
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.annotationIcon);
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     Desktop.addInternalFrame(frame,
index 4e9a26d..77dc1c5 100644 (file)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.GraphLine;
-import jalview.datamodel.SequenceGroup;
-import jalview.gui.JalviewColourChooser.ColourChooserListener;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.schemes.ColourSchemeI;
-import jalview.util.MessageManager;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
@@ -48,6 +39,14 @@ import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JPanel;
 
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.SequenceGroup;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.schemes.ColourSchemeI;
+import jalview.util.MessageManager;
 import net.miginfocom.swing.MigLayout;
 
 @SuppressWarnings("serial")
@@ -87,6 +86,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       }
     }
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.annotationIcon);
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     Desktop.addInternalFrame(frame,
index c0d4708..401df42 100644 (file)
 
 package jalview.gui;
 
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.HiddenColumns;
-import jalview.io.cache.JvCacheableInputBox;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
-
 import java.awt.BorderLayout;
 import java.awt.CardLayout;
 import java.awt.Color;
@@ -50,6 +42,13 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.border.TitledBorder;
 
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.HiddenColumns;
+import jalview.io.cache.JvCacheableInputBox;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
 import net.miginfocom.swing.MigLayout;
 
 @SuppressWarnings("serial")
@@ -98,6 +97,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
   {
     super(av, ap);
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.annotationIcon);
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     Desktop.addInternalFrame(frame,
index 1efd100..c22761a 100644 (file)
  */
 package jalview.gui;
 
-import java.util.Locale;
-
-import jalview.api.FeatureRenderer;
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.SequenceI;
-import jalview.io.AnnotationFile;
-import jalview.io.FeaturesFile;
-import jalview.io.JalviewFileChooser;
-import jalview.io.JalviewFileView;
-import jalview.util.MessageManager;
-
 import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.FileWriter;
 import java.io.PrintWriter;
+import java.util.Locale;
 
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
@@ -50,6 +39,16 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.SwingConstants;
 
+import jalview.api.FeatureRenderer;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.SequenceI;
+import jalview.io.AnnotationFile;
+import jalview.io.FeaturesFile;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.util.MessageManager;
+
 /**
  * 
  * GUI dialog for exporting features or alignment annotations depending upon
@@ -108,6 +107,7 @@ public class AnnotationExporter extends JPanel
     }
 
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.annotationIcon);
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     Dimension preferredSize = frame.getPreferredSize();
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 3a64716..6fac8fc 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.AlignSeq;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.RnaViewerModel;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.ext.varna.RnaModel;
-import jalview.structure.SecondaryStructureListener;
-import jalview.structure.SelectionListener;
-import jalview.structure.SelectionSource;
-import jalview.structure.StructureSelectionManager;
-import jalview.structure.VamsasSource;
-import jalview.util.Comparison;
-import jalview.util.MessageManager;
-import jalview.util.ShiftList;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.util.Collection;
@@ -60,6 +43,22 @@ import fr.orsay.lri.varna.models.FullBackup;
 import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
 import fr.orsay.lri.varna.models.rna.ModeleBase;
 import fr.orsay.lri.varna.models.rna.RNA;
+import jalview.analysis.AlignSeq;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.RnaViewerModel;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.ext.varna.RnaModel;
+import jalview.structure.SecondaryStructureListener;
+import jalview.structure.SelectionListener;
+import jalview.structure.SelectionSource;
+import jalview.structure.StructureSelectionManager;
+import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.util.ShiftList;
 
 public class AppVarna extends JInternalFrame
         implements SelectionListener, SecondaryStructureListener,
@@ -216,6 +215,7 @@ public class AppVarna extends JInternalFrame
    */
   protected AppVarna(AlignmentPanel ap)
   {
+    this.setFrameIcon(null);
     this.ap = ap;
     this.viewId = System.currentTimeMillis() + "." + this.hashCode();
     vab = new AppVarnaBinding();
index f7e5413..dda0a9f 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.TreeBuilder;
-import jalview.analysis.scoremodels.ScoreModels;
-import jalview.analysis.scoremodels.SimilarityParams;
-import jalview.api.analysis.ScoreModelI;
-import jalview.api.analysis.SimilarityParamsI;
-import jalview.bin.Cache;
-import jalview.datamodel.SequenceGroup;
-import jalview.util.MessageManager;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
@@ -61,6 +52,15 @@ import javax.swing.JRadioButton;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.bin.Cache;
+import jalview.datamodel.SequenceGroup;
+import jalview.util.MessageManager;
+
 /**
  * A dialog where a user can choose and action Tree or PCA calculation options
  */
@@ -130,6 +130,7 @@ public class CalculationChooser extends JPanel
   {
     setLayout(new BorderLayout());
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.treeIcon);
     frame.setContentPane(this);
     this.setBackground(Color.white);
     frame.addFocusListener(new FocusListener()
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 6e0032f..5c94d54 100644 (file)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
-import jalview.io.JalviewFileChooser;
-import jalview.io.JalviewFileView;
-import jalview.jbgui.GCutAndPasteHtmlTransfer;
-import jalview.util.MessageManager;
-import jalview.viewmodel.AlignmentViewport;
-
 import java.awt.Toolkit;
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.StringSelection;
@@ -44,6 +37,13 @@ import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkEvent.EventType;
 import javax.swing.event.HyperlinkListener;
 
+import jalview.bin.Cache;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.jbgui.GCutAndPasteHtmlTransfer;
+import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
+
 /**
  * Cut'n'paste files into the desktop See JAL-1105
  * 
@@ -58,6 +58,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
   public CutAndPasteHtmlTransfer()
   {
     super();
+    this.setFrameIcon(WindowIcons.logoIcon);
     displaySource.setSelected(false);
     textarea.addKeyListener(new KeyListener()
     {
index 112d502..bc3c0d2 100644 (file)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.ComplexAlignFile;
 import jalview.api.FeatureSettingsModelI;
 import jalview.api.FeaturesDisplayedI;
 import jalview.api.FeaturesSourceI;
+import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
@@ -45,22 +61,6 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.schemes.ColourSchemeI;
 import jalview.util.MessageManager;
 
-import java.awt.Toolkit;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.StringSelection;
-import java.awt.datatransfer.Transferable;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseEvent;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingUtilities;
-
 /**
  * Cut'n'paste files into the desktop See JAL-1105
  * 
@@ -78,6 +78,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
 
   public CutAndPasteTransfer()
   {
+    this.setFrameIcon(null);
     SwingUtilities.invokeLater(new Runnable()
     {
       @Override
index 16603df..2605195 100644 (file)
@@ -64,6 +64,7 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
@@ -81,6 +82,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
+import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
@@ -90,6 +92,7 @@ import javax.swing.JProgressBar;
 import javax.swing.JTextField;
 import javax.swing.KeyStroke;
 import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkEvent.EventType;
 import javax.swing.event.InternalFrameAdapter;
@@ -102,6 +105,7 @@ import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.BackupFiles;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
@@ -187,6 +191,14 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
 
+  private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
+
+  public static void setLiveDragMode(boolean b)
+  {
+    DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
+            : JDesktopPane.OUTLINE_DRAG_MODE;
+  }
+
   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
 
   public static boolean nosplash = false;
@@ -451,13 +463,14 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     setIconImages(ChannelProperties.getIconList());
 
+    // override quit handling when GUI OS close [X] button pressed
+    this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
     addWindowListener(new WindowAdapter()
     {
-
       @Override
       public void windowClosing(WindowEvent ev)
       {
-        quit();
+        QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
       }
     });
 
@@ -482,7 +495,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
 
     getContentPane().add(desktop, BorderLayout.CENTER);
-    desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+    desktop.setDragMode(DRAG_MODE);
 
     // This line prevents Windows Look&Feel resizing all new windows to maximum
     // if previous window was maximised
@@ -571,15 +584,6 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
 
-    this.addWindowListener(new WindowAdapter()
-    {
-      @Override
-      public void windowClosing(WindowEvent evt)
-      {
-        quit();
-      }
-    });
-
     MouseAdapter ma;
     this.addMouseListener(ma = new MouseAdapter()
     {
@@ -1180,36 +1184,32 @@ public class Desktop extends jalview.jbgui.GDesktop
             MessageManager.getString("label.open_local_file"));
     chooser.setToolTipText(MessageManager.getString("action.open"));
 
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File selectedFile = chooser.getSelectedFile();
-        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
+    chooser.setResponseHandler(0, () -> {
+      File selectedFile = chooser.getSelectedFile();
+      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 
-        FileFormatI format = chooser.getSelectedFormat();
+      FileFormatI format = chooser.getSelectedFormat();
 
-        /*
-         * Call IdentifyFile to verify the file contains what its extension implies.
-         * Skip this step for dynamically added file formats, because IdentifyFile does
-         * not know how to recognise them.
-         */
-        if (FileFormats.getInstance().isIdentifiable(format))
+      /*
+       * Call IdentifyFile to verify the file contains what its extension implies.
+       * Skip this step for dynamically added file formats, because IdentifyFile does
+       * not know how to recognise them.
+       */
+      if (FileFormats.getInstance().isIdentifiable(format))
+      {
+        try
         {
-          try
-          {
-            format = new IdentifyFile().identify(selectedFile,
-                    DataSourceType.FILE);
-          } catch (FileFormatException e)
-          {
-            // format = null; //??
-          }
+          format = new IdentifyFile().identify(selectedFile,
+                  DataSourceType.FILE);
+        } catch (FileFormatException e)
+        {
+          // format = null; //??
         }
-
-        new FileLoader().LoadFile(viewport, selectedFile,
-                DataSourceType.FILE, format);
       }
+
+      new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
+              format);
+      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -1265,64 +1265,60 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
-    Runnable action = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        @SuppressWarnings("unchecked")
-        String url = (history instanceof JTextField
-                ? ((JTextField) history).getText()
-                : ((JComboBox<String>) history).getEditor().getItem()
-                        .toString().trim());
+    Callable<Void> action = () -> {
+      @SuppressWarnings("unchecked")
+      String url = (history instanceof JTextField
+              ? ((JTextField) history).getText()
+              : ((JComboBox<String>) history).getEditor().getItem()
+                      .toString().trim());
 
-        if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
+      if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
+      {
+        if (viewport != null)
         {
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    FileFormat.Jalview);
-          }
-          else
-          {
-            new FileLoader().LoadFile(url, DataSourceType.URL,
-                    FileFormat.Jalview);
-          }
+          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                  FileFormat.Jalview);
         }
         else
         {
-          FileFormatI format = null;
-          try
-          {
-            format = new IdentifyFile().identify(url, DataSourceType.URL);
-          } catch (FileFormatException e)
-          {
-            // TODO revise error handling, distinguish between
-            // URL not found and response not valid
-          }
+          new FileLoader().LoadFile(url, DataSourceType.URL,
+                  FileFormat.Jalview);
+        }
+      }
+      else
+      {
+        FileFormatI format = null;
+        try
+        {
+          format = new IdentifyFile().identify(url, DataSourceType.URL);
+        } catch (FileFormatException e)
+        {
+          // TODO revise error handling, distinguish between
+          // URL not found and response not valid
+        }
 
-          if (format == null)
-          {
-            String msg = MessageManager
-                    .formatMessage("label.couldnt_locate", url);
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
-                    MessageManager.getString("label.url_not_found"),
-                    JvOptionPane.WARNING_MESSAGE);
+        if (format == null)
+        {
+          String msg = MessageManager.formatMessage("label.couldnt_locate",
+                  url);
+          JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
+                  MessageManager.getString("label.url_not_found"),
+                  JvOptionPane.WARNING_MESSAGE);
 
-            return;
-          }
+          return null; // Void
+        }
 
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    format);
-          }
-          else
-          {
-            new FileLoader().LoadFile(url, DataSourceType.URL, format);
-          }
+        if (viewport != null)
+        {
+          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+                  format);
+        }
+        else
+        {
+          new FileLoader().LoadFile(url, DataSourceType.URL, format);
         }
       }
+      return null; // Void
     };
     String dialogOption = MessageManager
             .getString("label.input_alignment_from_url");
@@ -1352,39 +1348,79 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /*
-   * Exit the program
+   * Check with user and saving files before actually quitting
    */
-  @Override
-  public void quit()
+  public void desktopQuit()
   {
-    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
-    Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
-    Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
-    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
-            getWidth(), getHeight()));
+    desktopQuit(true, false);
+  }
 
-    if (jconsole != null)
-    {
-      storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
-      jconsole.stopConsole();
-    }
-    if (jvnews != null)
-    {
-      storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
+  public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
+  {
+    final Callable<Void> doDesktopQuit = () -> {
+      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+      Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
+      Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
+      storeLastKnownDimensions("", new Rectangle(getBounds().x,
+              getBounds().y, getWidth(), getHeight()));
 
-    }
-    if (dialogExecutor != null)
-    {
-      dialogExecutor.shutdownNow();
-    }
-    closeAll_actionPerformed(null);
+      if (jconsole != null)
+      {
+        storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
+        jconsole.stopConsole();
+      }
 
-    if (groovyConsole != null)
-    {
-      // suppress a possible repeat prompt to save script
-      groovyConsole.setDirty(false);
-      groovyConsole.exit();
-    }
+      if (jvnews != null)
+      {
+        storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
+
+      }
+
+      if (dialogExecutor != null)
+      {
+        dialogExecutor.shutdownNow();
+      }
+
+      closeAll_actionPerformed(null);
+
+      if (groovyConsole != null)
+      {
+        // suppress a possible repeat prompt to save script
+        groovyConsole.setDirty(false);
+        groovyConsole.exit();
+      }
+
+      if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
+      {
+        // note that shutdown hook will not be run
+        jalview.bin.Console.debug("Force Quit selected by user");
+        Runtime.getRuntime().halt(0);
+      }
+
+      jalview.bin.Console.debug("Quit selected by user");
+      if (disposeFlag)
+      {
+        instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        // instance.dispose();
+      }
+      instance.quit();
+
+      return null; // Void
+    };
+
+    return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
+            QuitHandler.defaultCancelQuit);
+  }
+
+  /**
+   * Don't call this directly, use desktopQuit() above. Exits the program.
+   */
+  @Override
+  public void quit()
+  {
+    // this will run the shutdownHook but QuitHandler.getQuitResponse() should
+    // not run a second time if gotQuitResponse flag has been set (i.e. user
+    // confirmed quit of some kind).
     System.exit(0);
   }
 
@@ -1822,42 +1858,37 @@ public class Desktop extends jalview.jbgui.GDesktop
     // allowBackupFiles
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
+    chooser.setResponseHandler(0, () -> {
+      File selectedFile = chooser.getSelectedFile();
+      setProjectFile(selectedFile);
+      String choice = selectedFile.getAbsolutePath();
+      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
+      new Thread(new Runnable()
       {
-        File selectedFile = chooser.getSelectedFile();
-        setProjectFile(selectedFile);
-        String choice = selectedFile.getAbsolutePath();
-        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
-        new Thread(new Runnable()
+        @Override
+        public void run()
         {
-          @Override
-          public void run()
+          try
           {
-            try
-            {
-              new Jalview2XML().loadJalviewAlign(selectedFile);
-            } catch (OutOfMemoryError oom)
-            {
-              new OOMWarning("Whilst loading project from " + choice, oom);
-            } catch (Exception ex)
-            {
-              jalview.bin.Console.error(
-                      "Problems whilst loading project from " + choice, ex);
-              JvOptionPane.showMessageDialog(Desktop.desktop,
-                      MessageManager.formatMessage(
-                              "label.error_whilst_loading_project_from",
-                              new Object[]
-                              { choice }),
-                      MessageManager
-                              .getString("label.couldnt_load_project"),
-                      JvOptionPane.WARNING_MESSAGE);
-            }
+            new Jalview2XML().loadJalviewAlign(selectedFile);
+          } catch (OutOfMemoryError oom)
+          {
+            new OOMWarning("Whilst loading project from " + choice, oom);
+          } catch (Exception ex)
+          {
+            jalview.bin.Console.error(
+                    "Problems whilst loading project from " + choice, ex);
+            JvOptionPane.showMessageDialog(Desktop.desktop,
+                    MessageManager.formatMessage(
+                            "label.error_whilst_loading_project_from",
+                            new Object[]
+                            { choice }),
+                    MessageManager.getString("label.couldnt_load_project"),
+                    JvOptionPane.WARNING_MESSAGE);
           }
-        }, "Project Loader").start();
-      }
+        }
+      }, "Project Loader").start();
+      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -2521,7 +2552,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        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..ebd4712 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;
@@ -413,6 +411,7 @@ public class FeatureSettings extends JPanel
     {
       frame = new JInternalFrame();
       frame.setContentPane(this);
+      frame.setFrameIcon(WindowIcons.featuresIcon);
       Rectangle bounds = af.getFeatureSettingsGeometry();
       String title;
       if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
@@ -949,14 +948,10 @@ public class FeatureSettings extends JPanel
     chooser.setDialogTitle(
             MessageManager.getString("label.load_feature_colours"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File file = chooser.getSelectedFile();
-        load(file);
-      }
+    chooser.setResponseHandler(0, () -> {
+      File file = chooser.getSelectedFile();
+      load(file);
+      return null;
     });
     chooser.showOpenDialog(this);
   }
index 358d9a4..ee70565 100755 (executable)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import java.util.Locale;
-
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.FocusAdapter;
@@ -30,6 +28,7 @@ import java.awt.event.KeyEvent;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -109,6 +108,7 @@ public class Finder extends GFinder
     focusFixed = fixedFocus;
     finders = new HashMap<>();
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.logoIcon);
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     frame.addInternalFrameListener(new InternalFrameAdapter()
index 92cc4c6..2e8e667 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
-import jalview.jbgui.GFontChooser;
-import jalview.util.MessageManager;
-
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.geom.Rectangle2D;
@@ -31,6 +27,10 @@ import java.awt.geom.Rectangle2D;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 
+import jalview.bin.Cache;
+import jalview.jbgui.GFontChooser;
+import jalview.util.MessageManager;
+
 /**
  * DOCUMENT ME!
  * 
@@ -112,6 +112,7 @@ public class FontChooser extends GFontChooser
   void init()
   {
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.logoIcon);
     frame.setContentPane(this);
 
     smoothFont.setSelected(ap.av.antiAlias);
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..d805164 100644 (file)
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
-
 package jalview.gui;
 
+import java.awt.AWTEvent;
+import java.awt.ActiveEvent;
 import java.awt.Component;
+import java.awt.Container;
 import java.awt.Dialog.ModalityType;
+import java.awt.EventQueue;
 import java.awt.HeadlessException;
+import java.awt.MenuComponent;
+import java.awt.Toolkit;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseMotionAdapter;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
@@ -34,22 +41,26 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JDialog;
+import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
+import javax.swing.event.InternalFrameEvent;
+import javax.swing.event.InternalFrameListener;
 
 import jalview.util.Platform;
 import jalview.util.dialogrunner.DialogRunnerI;
 
-public class JvOptionPane extends JOptionPane
-        implements DialogRunnerI, PropertyChangeListener
-{
+public class JvOptionPane extends JOptionPane implements DialogRunnerI, PropertyChangeListener {
   private static final long serialVersionUID = -3019167117756785229L;
 
   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
@@ -58,26 +69,21 @@ public class JvOptionPane extends JOptionPane
 
   private Component parentComponent;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable<Void>> callbacks = new HashMap<>();
 
   /*
-   * JalviewJS reports user choice in the dialog as the selected
-   * option (text); this list allows conversion to index (int)
+   * JalviewJS reports user choice in the dialog as the selected option (text);
+   * this list allows conversion to index (int)
    */
   List<Object> ourOptions;
 
-  public JvOptionPane(final Component parent)
-  {
+  public JvOptionPane(final Component parent) {
     this.parentComponent = Platform.isJS() ? this : parent;
   }
 
-  public static int showConfirmDialog(Component parentComponent,
-          Object message) throws HeadlessException
-  {
+  public static int showConfirmDialog(Component parentComponent, Object message) throws HeadlessException {
     // only called by test
-    return isInteractiveMode()
-            ? JOptionPane.showConfirmDialog(parentComponent, message)
-            : (int) getMockResponse();
+    return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message) : (int) getMockResponse();
   }
 
   /**
@@ -90,16 +96,12 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static int showConfirmDialog(Component parentComponent,
-          Object message, String title, int optionType)
-          throws HeadlessException
-  {
-    if (!isInteractiveMode())
-    {
+  public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType)
+      throws HeadlessException {
+    if (!isInteractiveMode()) {
       return (int) getMockResponse();
     }
-    switch (optionType)
-    {
+    switch (optionType) {
     case JvOptionPane.YES_NO_CANCEL_OPTION:
       // FeatureRenderer amendFeatures ?? TODO ??
       // Chimera close
@@ -112,8 +114,7 @@ public class JvOptionPane extends JOptionPane
       // $FALL-THROUGH$
     case JvOptionPane.OK_CANCEL_OPTION:
       // will fall back to simple HTML
-      return JOptionPane.showConfirmDialog(parentComponent, message, title,
-              optionType);
+      return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType);
     }
   }
 
@@ -128,16 +129,12 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static int showConfirmDialog(Component parentComponent,
-          Object message, String title, int optionType, int messageType)
-          throws HeadlessException
-  {
+  public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType,
+      int messageType) throws HeadlessException {
     // JalviewServicesChanged
     // PromptUserConfig raiseDialog
-    return isInteractiveMode()
-            ? JOptionPane.showConfirmDialog(parentComponent, message, title,
-                    optionType, messageType)
-            : (int) getMockResponse();
+    return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType)
+        : (int) getMockResponse();
   }
 
   /**
@@ -152,15 +149,12 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static int showConfirmDialog(Component parentComponent,
-          Object message, String title, int optionType, int messageType,
-          Icon icon) throws HeadlessException
-  {
+  public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType,
+      int messageType, Icon icon) throws HeadlessException {
     // JvOptionPaneTest only
     return isInteractiveMode()
-            ? JOptionPane.showConfirmDialog(parentComponent, message, title,
-                    optionType, messageType, icon)
-            : (int) getMockResponse();
+        ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon)
+        : (int) getMockResponse();
   }
 
   /**
@@ -170,14 +164,10 @@ public class JvOptionPane extends JOptionPane
    * @param message
    * @return
    */
-  public static int showInternalConfirmDialog(Component parentComponent,
-          Object message)
-  {
+  public static int showInternalConfirmDialog(Component parentComponent, Object message) {
     // JvOptionPaneTest only;
-    return isInteractiveMode()
-            ? JOptionPane.showInternalConfirmDialog(parentComponent,
-                    message)
-            : (int) getMockResponse();
+    return isInteractiveMode() ? JOptionPane.showInternalConfirmDialog(parentComponent, message)
+        : (int) getMockResponse();
   }
 
   /**
@@ -189,15 +179,11 @@ public class JvOptionPane extends JOptionPane
    * @param optionType
    * @return
    */
-  public static int showInternalConfirmDialog(Component parentComponent,
-          String message, String title, int optionType)
-  {
-    if (!isInteractiveMode())
-    {
+  public static int showInternalConfirmDialog(Component parentComponent, String message, String title, int optionType) {
+    if (!isInteractiveMode()) {
       return (int) getMockResponse();
     }
-    switch (optionType)
-    {
+    switch (optionType) {
     case JvOptionPane.YES_NO_CANCEL_OPTION:
       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
     case JvOptionPane.YES_NO_OPTION:
@@ -210,8 +196,7 @@ public class JvOptionPane extends JOptionPane
 
       // Desktop.inputURLMenuItem
       // WsPreferenses
-      return JOptionPane.showConfirmDialog(parentComponent, message, title,
-              optionType);
+      return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType);
     }
   }
 
@@ -224,15 +209,12 @@ public class JvOptionPane extends JOptionPane
    * @param messageType
    * @return
    */
-  public static int showInternalConfirmDialog(Component parentComponent,
-          Object message, String title, int optionType, int messageType)
-  {
-    if (!isInteractiveMode())
-    {
+  public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType,
+      int messageType) {
+    if (!isInteractiveMode()) {
       return (int) getMockResponse();
     }
-    switch (optionType)
-    {
+    switch (optionType) {
     case JvOptionPane.YES_NO_CANCEL_OPTION:
     case JvOptionPane.YES_NO_OPTION:
       // UserQuestionanaireCheck
@@ -241,8 +223,7 @@ public class JvOptionPane extends JOptionPane
     default:
     case JvOptionPane.OK_CANCEL_OPTION:
       // will fall back to simple HTML
-      return JOptionPane.showConfirmDialog(parentComponent, message, title,
-              optionType, messageType);
+      return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType);
     }
   }
 
@@ -257,24 +238,19 @@ public class JvOptionPane extends JOptionPane
    * @param icon
    * @return
    */
-  public static int showInternalConfirmDialog(Component parentComponent,
-          Object message, String title, int optionType, int messageType,
-          Icon icon)
-  {
-    if (!isInteractiveMode())
-    {
+  public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType,
+      int messageType, Icon icon) {
+    if (!isInteractiveMode()) {
       return (int) getMockResponse();
     }
-    switch (optionType)
-    {
+    switch (optionType) {
     case JvOptionPane.YES_NO_CANCEL_OPTION:
     case JvOptionPane.YES_NO_OPTION:
       //$FALL-THROUGH$
     default:
     case JvOptionPane.OK_CANCEL_OPTION:
       // Preferences editLink/newLink
-      return JOptionPane.showConfirmDialog(parentComponent, message, title,
-              optionType, messageType, icon);
+      return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon);
     }
 
   }
@@ -293,13 +269,9 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static int showOptionDialog(Component parentComponent,
-          String message, String title, int optionType, int messageType,
-          Icon icon, Object[] options, Object initialValue)
-          throws HeadlessException
-  {
-    if (!isInteractiveMode())
-    {
+  public static int showOptionDialog(Component parentComponent, String message, String title, int optionType,
+      int messageType, Icon icon, Object[] options, Object initialValue) throws HeadlessException {
+    if (!isInteractiveMode()) {
       return (int) getMockResponse();
     }
     // two uses:
@@ -316,8 +288,8 @@ public class JvOptionPane extends JOptionPane
     //
     // 2) UserDefinedColors warning about saving over a name already defined
     //
-    return JOptionPane.showOptionDialog(parentComponent, message, title,
-            optionType, messageType, icon, options, initialValue);
+    return JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options,
+        initialValue);
   }
 
   /**
@@ -326,11 +298,8 @@ public class JvOptionPane extends JOptionPane
    * @param message
    * @throws HeadlessException
    */
-  public static void showMessageDialog(Component parentComponent,
-          String message) throws HeadlessException
-  {
-    if (!isInteractiveMode())
-    {
+  public static void showMessageDialog(Component parentComponent, String message) throws HeadlessException {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
@@ -349,20 +318,16 @@ public class JvOptionPane extends JOptionPane
    * @param messageType
    * @throws HeadlessException
    */
-  public static void showMessageDialog(Component parentComponent,
-          String message, String title, int messageType)
-          throws HeadlessException
-  {
+  public static void showMessageDialog(Component parentComponent, String message, String title, int messageType)
+      throws HeadlessException {
     // 30 implementations -- all just fine.
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
 
-    JOptionPane.showMessageDialog(parentComponent,
-            getPrefix(messageType) + message, title, messageType);
+    JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType);
   }
 
   /**
@@ -375,35 +340,28 @@ public class JvOptionPane extends JOptionPane
    * @param icon
    * @throws HeadlessException
    */
-  public static void showMessageDialog(Component parentComponent,
-          String message, String title, int messageType, Icon icon)
-          throws HeadlessException
-  {
+  public static void showMessageDialog(Component parentComponent, String message, String title, int messageType,
+      Icon icon) throws HeadlessException {
 
     // test only
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
 
-    JOptionPane.showMessageDialog(parentComponent, message, title,
-            messageType, icon);
+    JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon);
   }
 
   /**
    * was internal
    * 
    */
-  public static void showInternalMessageDialog(Component parentComponent,
-          Object message)
-  {
+  public static void showInternalMessageDialog(Component parentComponent, Object message) {
 
     // WsPreferences only
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
@@ -419,20 +377,17 @@ public class JvOptionPane extends JOptionPane
    * @param title
    * @param messageType
    */
-  public static void showInternalMessageDialog(Component parentComponent,
-          String message, String title, int messageType)
-  {
+  public static void showInternalMessageDialog(Component parentComponent, String message, String title,
+      int messageType) {
 
     // 41 references
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
 
-    JOptionPane.showMessageDialog(parentComponent,
-            getPrefix(messageType) + message, title, messageType);
+    JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType);
   }
 
   /**
@@ -443,20 +398,17 @@ public class JvOptionPane extends JOptionPane
    * @param messageType
    * @param icon
    */
-  public static void showInternalMessageDialog(Component parentComponent,
-          Object message, String title, int messageType, Icon icon)
-  {
+  public static void showInternalMessageDialog(Component parentComponent, Object message, String title, int messageType,
+      Icon icon) {
 
     // test only
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       outputMessage(message);
       return;
     }
 
-    JOptionPane.showMessageDialog(parentComponent, message, title,
-            messageType, icon);
+    JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon);
   }
 
   /**
@@ -465,13 +417,10 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static String showInputDialog(Object message)
-          throws HeadlessException
-  {
+  public static String showInputDialog(Object message) throws HeadlessException {
     // test only
 
-    if (!isInteractiveMode())
-    {
+    if (!isInteractiveMode()) {
       return getMockResponse().toString();
     }
 
@@ -485,11 +434,8 @@ public class JvOptionPane extends JOptionPane
    * @param initialSelectionValue
    * @return
    */
-  public static String showInputDialog(String message,
-          String initialSelectionValue)
-  {
-    if (!isInteractiveMode())
-    {
+  public static String showInputDialog(String message, String initialSelectionValue) {
+    if (!isInteractiveMode()) {
       return getMockResponse().toString();
     }
 
@@ -505,11 +451,8 @@ public class JvOptionPane extends JOptionPane
    * @param initialSelectionValue
    * @return
    */
-  public static String showInputDialog(Object message,
-          Object initialSelectionValue)
-  {
-    if (!isInteractiveMode())
-    {
+  public static String showInputDialog(Object message, Object initialSelectionValue) {
+    if (!isInteractiveMode()) {
       return getMockResponse().toString();
     }
 
@@ -526,14 +469,10 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static String showInputDialog(Component parentComponent,
-          String message) throws HeadlessException
-  {
+  public static String showInputDialog(Component parentComponent, String message) throws HeadlessException {
     // test only
 
-    return isInteractiveMode()
-            ? JOptionPane.showInputDialog(parentComponent, message)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message) : getMockResponse().toString();
   }
 
   /**
@@ -544,16 +483,12 @@ public class JvOptionPane extends JOptionPane
    * @param initialSelectionValue
    * @return
    */
-  public static String showInputDialog(Component parentComponent,
-          String message, String initialSelectionValue)
-  {
+  public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue) {
 
     // AnnotationPanel
 
-    return isInteractiveMode()
-            ? JOptionPane.showInputDialog(parentComponent, message,
-                    initialSelectionValue)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue)
+        : getMockResponse().toString();
   }
 
   /**
@@ -564,16 +499,12 @@ public class JvOptionPane extends JOptionPane
    * @param initialSelectionValue
    * @return
    */
-  public static String showInputDialog(Component parentComponent,
-          Object message, Object initialSelectionValue)
-  {
+  public static String showInputDialog(Component parentComponent, Object message, Object initialSelectionValue) {
 
     // AnnotationPanel
 
-    return isInteractiveMode()
-            ? JOptionPane.showInputDialog(parentComponent, message,
-                    initialSelectionValue)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue)
+        : getMockResponse().toString();
   }
 
   /**
@@ -585,17 +516,13 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static String showInputDialog(Component parentComponent,
-          String message, String title, int messageType)
-          throws HeadlessException
-  {
+  public static String showInputDialog(Component parentComponent, String message, String title, int messageType)
+      throws HeadlessException {
 
     // test only
 
-    return isInteractiveMode()
-            ? JOptionPane.showInputDialog(parentComponent, message, title,
-                    messageType)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, title, messageType)
+        : getMockResponse().toString();
   }
 
   /**
@@ -611,19 +538,15 @@ public class JvOptionPane extends JOptionPane
    * @return
    * @throws HeadlessException
    */
-  public static Object showInputDialog(Component parentComponent,
-          Object message, String title, int messageType, Icon icon,
-          Object[] selectionValues, Object initialSelectionValue)
-          throws HeadlessException
-  {
+  public static Object showInputDialog(Component parentComponent, Object message, String title, int messageType,
+      Icon icon, Object[] selectionValues, Object initialSelectionValue) throws HeadlessException {
 
     // test only
 
     return isInteractiveMode()
-            ? JOptionPane.showInputDialog(parentComponent, message, title,
-                    messageType, icon, selectionValues,
-                    initialSelectionValue)
-            : getMockResponse().toString();
+        ? JOptionPane.showInputDialog(parentComponent, message, title, messageType, icon, selectionValues,
+            initialSelectionValue)
+        : getMockResponse().toString();
   }
 
   /**
@@ -633,14 +556,11 @@ public class JvOptionPane extends JOptionPane
    * @param message
    * @return
    */
-  public static String showInternalInputDialog(Component parentComponent,
-          String message)
-  {
+  public static String showInternalInputDialog(Component parentComponent, String message) {
     // test only
 
-    return isInteractiveMode()
-            ? JOptionPane.showInternalInputDialog(parentComponent, message)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message)
+        : getMockResponse().toString();
   }
 
   /**
@@ -652,16 +572,14 @@ public class JvOptionPane extends JOptionPane
    * @param messageType
    * @return
    */
-  public static String showInternalInputDialog(Component parentComponent,
-          String message, String title, int messageType)
-  {
+  public static String showInternalInputDialog(Component parentComponent, String message, String title,
+      int messageType) {
 
     // AlignFrame tabbedPane_mousePressed
 
     return isInteractiveMode()
-            ? JOptionPane.showInternalInputDialog(parentComponent,
-                    getPrefix(messageType) + message, title, messageType)
-            : getMockResponse().toString();
+        ? JOptionPane.showInternalInputDialog(parentComponent, getPrefix(messageType) + message, title, messageType)
+        : getMockResponse().toString();
   }
 
   /**
@@ -676,61 +594,47 @@ public class JvOptionPane extends JOptionPane
    * @param initialSelectionValue
    * @return
    */
-  public static Object showInternalInputDialog(Component parentComponent,
-          String message, String title, int messageType, Icon icon,
-          Object[] selectionValues, Object initialSelectionValue)
-  {
+  public static Object showInternalInputDialog(Component parentComponent, String message, String title, int messageType,
+      Icon icon, Object[] selectionValues, Object initialSelectionValue) {
     // test only
 
-    return isInteractiveMode()
-            ? JOptionPane.showInternalInputDialog(parentComponent, message,
-                    title, messageType, icon, selectionValues,
-                    initialSelectionValue)
-            : getMockResponse().toString();
+    return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message, title, messageType, icon,
+        selectionValues, initialSelectionValue) : getMockResponse().toString();
   }
 
   ///////////// end of options ///////////////
 
-  private static void outputMessage(Object message)
-  {
+  private static void outputMessage(Object message) {
     System.out.println(">>> JOption Message : " + message.toString());
   }
 
-  public static Object getMockResponse()
-  {
+  public static Object getMockResponse() {
     return mockResponse;
   }
 
-  public static void setMockResponse(Object mockOption)
-  {
+  public static void setMockResponse(Object mockOption) {
     JvOptionPane.mockResponse = mockOption;
   }
 
-  public static void resetMock()
-  {
+  public static void resetMock() {
     setMockResponse(JvOptionPane.CANCEL_OPTION);
     setInteractiveMode(true);
   }
 
-  public static boolean isInteractiveMode()
-  {
+  public static boolean isInteractiveMode() {
     return interactiveMode;
   }
 
-  public static void setInteractiveMode(boolean interactive)
-  {
+  public static void setInteractiveMode(boolean interactive) {
     JvOptionPane.interactiveMode = interactive;
   }
 
-  private static String getPrefix(int messageType)
-  {
+  private static String getPrefix(int messageType) {
     String prefix = "";
 
     // JavaScript only
-    if (Platform.isJS())
-    {
-      switch (messageType)
-      {
+    if (Platform.isJS()) {
+      switch (messageType) {
       case JvOptionPane.WARNING_MESSAGE:
         prefix = "WARNING! ";
         break;
@@ -758,24 +662,27 @@ public class JvOptionPane extends JOptionPane
    * @param string2
    * @return
    */
-  public static JvOptionPane newOptionDialog(Component parentComponent)
-  {
+  public static JvOptionPane newOptionDialog() {
+    return new JvOptionPane(null);
+  }
+
+  public static JvOptionPane newOptionDialog(Component parentComponent) {
     return new JvOptionPane(parentComponent);
   }
 
-  public void showDialog(String message, String title, int optionType,
-          int messageType, Icon icon, Object[] options, Object initialValue)
-  {
-    showDialog(message, title, optionType, messageType, icon, options,
-            initialValue, true);
+  public void showDialog(String message, String title, int optionType, int messageType, Icon icon, Object[] options,
+      Object initialValue) {
+    showDialog(message, title, optionType, messageType, icon, options, initialValue, true);
   }
 
-  public void showDialog(String message, String title, int optionType,
-          int messageType, Icon icon, Object[] options, Object initialValue,
-          boolean modal)
-  {
-    if (!isInteractiveMode())
-    {
+  public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options,
+      Object initialValue, boolean modal) {
+    showDialog(message, title, optionType, messageType, icon, options, initialValue, modal, null);
+  }
+
+  public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options,
+      Object initialValue, boolean modal, JButton[] buttons) {
+    if (!isInteractiveMode()) {
       handleResponse(getMockResponse());
     }
     // two uses:
@@ -795,15 +702,80 @@ public class JvOptionPane extends JOptionPane
 
     ourOptions = Arrays.asList(options);
 
-    if (modal)
-    {
+    if (modal) {
+      boolean useButtons = false;
+      Object initialValueButton = null;
+      NOTNULL: if (buttons != null) {
+        if (buttons.length != options.length) {
+          jalview.bin.Console.error("Supplied buttons array not the same length as supplied options array.");
+          break NOTNULL;
+        }
+        int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
+        for (int i = 0; i < options.length; i++) {
+          Object o = options[i];
+          jalview.bin.Console.debug("Setting button " + i + " to '" + o.toString() + "'");
+          JButton jb = buttons[i];
+
+          if (o.equals(initialValue))
+            initialValueButton = jb;
+
+          int buttonAction = buttonActions[i];
+          Callable<Void> action = callbacks.get(buttonAction);
+          jb.setText((String) o);
+          jb.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+
+              Object obj = e.getSource();
+              if (obj == null || !(obj instanceof Component)) {
+                jalview.bin.Console.debug("Could not find Component source of event object " + obj);
+                return;
+              }
+              Object joptionpaneObject = SwingUtilities.getAncestorOfClass(JOptionPane.class, (Component) obj);
+              if (joptionpaneObject == null || !(joptionpaneObject instanceof JOptionPane)) {
+                jalview.bin.Console.debug("Could not find JOptionPane ancestor of event object " + obj);
+                return;
+              }
+              JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
+              joptionpane.setValue(buttonAction);
+              if (action != null)
+                Executors.newSingleThreadExecutor().submit(action);
+              joptionpane.transferFocusBackward();
+              joptionpane.setVisible(false);
+              // put focus and raise parent window if possible, unless cancel or
+              // no button pressed
+              boolean raiseParent = (parentComponent != null);
+              if (buttonAction == JOptionPane.CANCEL_OPTION)
+                raiseParent = false;
+              if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION)
+                raiseParent = false;
+              if (raiseParent) {
+                parentComponent.requestFocus();
+                if (parentComponent instanceof JInternalFrame) {
+                  JInternalFrame jif = (JInternalFrame) parentComponent;
+                  jif.show();
+                  jif.moveToFront();
+                  jif.grabFocus();
+                } else if (parentComponent instanceof Window) {
+                  Window w = (Window) parentComponent;
+                  w.toFront();
+                  w.requestFocus();
+                }
+              }
+              joptionpane.setVisible(false);
+            }
+          });
+
+        }
+        useButtons = true;
+      }
       // use a JOptionPane as usual
-      int response = JOptionPane.showOptionDialog(parentComponent, message,
-              title, optionType, messageType, icon, options, initialValue);
+      int response = JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon,
+          useButtons ? buttons : options, useButtons ? initialValueButton : initialValue);
 
       /*
-       * In Java, the response is returned to this thread and handled here;
-       * (for Javascript, see propertyChange)
+       * In Java, the response is returned to this thread and handled here; (for
+       * Javascript, see propertyChange)
        */
       if (!Platform.isJS())
       /**
@@ -814,35 +786,25 @@ public class JvOptionPane extends JOptionPane
       {
         handleResponse(response);
       }
-    }
-    else
-    {
+    } else {
       /*
-       * This is java similar to the swingjs handling, with the callbacks
-       * attached to the button press of the dialog.  This means we can use
-       * a non-modal JDialog for the confirmation without blocking the GUI.
+       * This is java similar to the swingjs handling, with the callbacks 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 };
+      int[] buttonActions = { JvOptionPane.YES_OPTION, JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
 
       // we need the strings to make the buttons with actionEventListener
-      if (options == null)
-      {
+      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"));
+        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"));
+        if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION) {
+          options_default.add(UIManager.getString("OptionPane.cancelButtonText"));
         }
         options = options_default.toArray();
       }
@@ -851,21 +813,18 @@ public class JvOptionPane extends JOptionPane
       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++)
-        {
+        for (int i = 0; i < options.length && i < 3; i++) {
           Object o = options[i];
           int buttonAction = buttonActions[i];
-          Runnable action = callbacks.get(buttonAction);
+          Callable<Void> action = callbacks.get(buttonAction);
           JButton jb = new JButton();
           jb.setText((String) o);
-          jb.addActionListener(new ActionListener()
-          {
+          jb.addActionListener(new ActionListener() {
             @Override
-            public void actionPerformed(ActionEvent e)
-            {
+            public void actionPerformed(ActionEvent e) {
               joptionpane.setValue(buttonAction);
               if (action != null)
-                Executors.defaultThreadFactory().newThread(action).start();
+                Executors.newSingleThreadExecutor().submit(action);
               // joptionpane.transferFocusBackward();
               joptionpane.transferFocusBackward();
               joptionpane.setVisible(false);
@@ -874,21 +833,16 @@ public class JvOptionPane extends JOptionPane
               boolean raiseParent = (parentComponent != null);
               if (buttonAction == JvOptionPane.CANCEL_OPTION)
                 raiseParent = false;
-              if (optionType == JvOptionPane.YES_NO_OPTION
-                      && buttonAction == JvOptionPane.NO_OPTION)
+              if (optionType == JvOptionPane.YES_NO_OPTION && buttonAction == JvOptionPane.NO_OPTION)
                 raiseParent = false;
-              if (raiseParent)
-              {
+              if (raiseParent) {
                 parentComponent.requestFocus();
-                if (parentComponent instanceof JInternalFrame)
-                {
+                if (parentComponent instanceof JInternalFrame) {
                   JInternalFrame jif = (JInternalFrame) parentComponent;
                   jif.show();
                   jif.moveToFront();
                   jif.grabFocus();
-                }
-                else if (parentComponent instanceof Window)
-                {
+                } else if (parentComponent instanceof Window) {
                   Window w = (Window) parentComponent;
                   w.toFront();
                   w.requestFocus();
@@ -906,42 +860,82 @@ public class JvOptionPane extends JOptionPane
       joptionpane.setMessageType(messageType);
       joptionpane.setOptionType(optionType);
       joptionpane.setIcon(icon);
-      joptionpane.setOptions(
-              Platform.isJS() ? options : options_btns.toArray());
-      joptionpane.setInitialValue(
-              Platform.isJS() ? initialValue : initialValue_btn);
+      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.setIconImage(WindowIcons.logoIcon.getImage());
+      dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
       dialog.setVisible(true);
     }
   }
 
-  public void showInternalDialog(JPanel mainPanel, String title,
-          int yesNoCancelOption, int questionMessage, Icon icon,
-          Object[] options, String initresponse)
-  {
-    if (!isInteractiveMode())
-    {
+  public void showInternalDialog(JPanel mainPanel, String title, int yesNoCancelOption, int questionMessage, Icon icon,
+      Object[] options, String initresponse) {
+    if (!isInteractiveMode()) {
       handleResponse(getMockResponse());
     }
 
+    // need to set these separately so we can set the title bar icon later
+    this.setOptionType(yesNoCancelOption);
+    this.setMessageType(questionMessage);
+    this.setIcon(icon);
+    this.setInitialValue(initresponse);
+    this.setOptions(options);
+    this.setMessage(mainPanel);
+
     ourOptions = Arrays.asList(options);
     int response;
-    if (parentComponent != this)
-    {
-      response = JOptionPane.showInternalOptionDialog(parentComponent,
-              mainPanel, title, yesNoCancelOption, questionMessage, icon,
-              options, initresponse);
-    }
-    else
-    {
-      response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
-              title, yesNoCancelOption, questionMessage, icon, options,
-              initresponse);
+    if (parentComponent != this) {
+      JInternalFrame jif = this.createInternalFrame(parentComponent, title);
+      jif.setFrameIcon(WindowIcons.logoIcon);
+      jif.addInternalFrameListener(new InternalFrameListener() {
+        @Override
+        public void internalFrameActivated(InternalFrameEvent arg0) {
+        }
+
+        @Override
+        public void internalFrameClosed(InternalFrameEvent arg0) {
+          JvOptionPane.this.internalDialogHandleResponse();
+        }
+
+        @Override
+        public void internalFrameClosing(InternalFrameEvent arg0) {
+        }
+
+        @Override
+        public void internalFrameDeactivated(InternalFrameEvent arg0) {
+        }
+
+        @Override
+        public void internalFrameDeiconified(InternalFrameEvent arg0) {
+        }
+
+        @Override
+        public void internalFrameIconified(InternalFrameEvent arg0) {
+        }
+
+        @Override
+        public void internalFrameOpened(InternalFrameEvent arg0) {
+        }
+      });
+      jif.setVisible(true);
+      startModal(jif);
+      return;
+    } else {
+      JDialog dialog = this.createDialog(parentComponent, title);
+      dialog.setIconImage(WindowIcons.logoIcon.getImage());
+      dialog.setVisible(true); // blocking
+      this.internalDialogHandleResponse();
+      return;
     }
+  }
+
+  private void internalDialogHandleResponse() {
+    String responseString = (String) this.getValue();
+    int response = ourOptions.indexOf(responseString);
+
     if (!Platform.isJS())
     /**
      * Java only
@@ -953,51 +947,294 @@ 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, Runnable action)
-  {
+  public JvOptionPane setResponseHandler(Object response, Callable<Void> action) {
     callbacks.put(response, action);
     return this;
   }
 
   /**
-   * 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.
+   * 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.setIconImage(WindowIcons.logoIcon.getImage());
+    dialogParent.setAlwaysOnTop(true);
+
+    int answer = JOptionPane.showConfirmDialog(dialogParent, label, actionString, JOPTIONPANE_OPTION,
+        JOPTIONPANE_MESSAGETYPE);
+
+    dialogParent.setAlwaysOnTop(false);
+    dialogParent.dispose();
+
+    return answer;
+  }
+
+  public void showDialogOnTopAsync(String label, String actionString, int JOPTIONPANE_OPTION,
+      int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) {
+    JFrame frame = new JFrame();
+    frame.setIconImage(WindowIcons.logoIcon.getImage());
+    showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
+        initialValue, modal);
+  }
+
+  public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION,
+      int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) {
+    showDialogOnTopAsync(dialogParent, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
+        initialValue, modal, null);
+  }
+
+  public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION,
+      int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) {
+    // Ensure Jalview window is brought to front (primarily for Quit
+    // confirmation window to be visible)
+
+    // This method of raising the Jalview window is broken in java
+    // jalviewDesktop.setVisible(true);
+    // jalviewDesktop.toFront();
+
+    // A better hack which works is to create a new JFrame parent with
+    // setAlwaysOnTop(true)
+    dialogParent.setAlwaysOnTop(true);
+    parentComponent = dialogParent;
+
+    showDialog(label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
+        buttons);
+
+    dialogParent.setAlwaysOnTop(false);
+    dialogParent.dispose();
+  }
+
+  /**
+   * JalviewJS signals option selection by a property change event for the option
+   * e.g. "OK". This methods responds to that by running the response action that
+   * corresponds to that option.
    * 
    * @param evt
    */
   @Override
-  public void propertyChange(PropertyChangeEvent evt)
-  {
+  public void propertyChange(PropertyChangeEvent evt) {
     Object newValue = evt.getNewValue();
     int ourOption = ourOptions.indexOf(newValue);
-    if (ourOption >= 0)
-    {
+    if (ourOption >= 0) {
       handleResponse(ourOption);
-    }
-    else
-    {
+    } else {
       // try our luck..
       handleResponse(newValue);
     }
   }
 
   @Override
-  public void handleResponse(Object response)
-  {
+  public void handleResponse(Object response) {
     /*
-    * this test is for NaN in Chrome
-    */
-    if (response != null && !response.equals(response))
-    {
+     * this test is for NaN in Chrome
+     */
+    if (response != null && !response.equals(response)) {
       return;
     }
-    Runnable action = callbacks.get(response);
-    if (action != null)
+    Callable<Void> action = callbacks.get(response);
+    if (action != null) {
+      try {
+        action.call();
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+      if (parentComponent != null)
+        parentComponent.requestFocus();
+    }
+  }
+
+  /**
+   * Create a non-modal confirm dialog
+   */
+  public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType,
+      Icon icon, Object[] options, Object initialValue, boolean modal) {
+    return createDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue, modal,
+        null);
+  }
+
+  public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType,
+      Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) {
+    JButton[] optionsButtons = null;
+    Object initialValueButton = null;
+    JOptionPane joptionpane = new JOptionPane();
+    // Make button options
+    int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
+
+    // we need the strings to make the buttons with actionEventListener
+    if (options == null) {
+      ArrayList<String> options_default = new ArrayList<>();
+      options_default.add(UIManager.getString("OptionPane.yesButtonText"));
+      if (optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
+        options_default.add(UIManager.getString("OptionPane.noButtonText"));
+      }
+      if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
+        options_default.add(UIManager.getString("OptionPane.cancelButtonText"));
+      }
+      options = options_default.toArray();
+    }
+    if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
+                          // add them here
     {
-      action.run();
-      parentComponent.requestFocus();
+      if (((optionType == JOptionPane.YES_OPTION || optionType == JOptionPane.NO_OPTION
+          || optionType == JOptionPane.CANCEL_OPTION || optionType == JOptionPane.OK_OPTION
+          || optionType == JOptionPane.DEFAULT_OPTION) && options.length < 1)
+          || ((optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.OK_CANCEL_OPTION)
+              && options.length < 2)
+          || (optionType == JOptionPane.YES_NO_CANCEL_OPTION && options.length < 3)) {
+        jalview.bin.Console.debug("JvOptionPane: not enough options for dialog type");
+      }
+      optionsButtons = new JButton[options.length];
+      for (int i = 0; i < options.length && i < 3; i++) {
+        Object o = options[i];
+        int buttonAction = buttonActions[i];
+        Callable<Void> action = callbacks.get(buttonAction);
+        JButton jb;
+        if (buttons != null && buttons.length > i && buttons[i] != null) {
+          jb = buttons[i];
+        } else {
+          jb = new JButton();
+        }
+        jb.setText((String) o);
+        jb.addActionListener(new ActionListener() {
+          @Override
+          public void actionPerformed(ActionEvent e) {
+            joptionpane.setValue(buttonAction);
+            if (action != null)
+              Executors.newSingleThreadExecutor().submit(action);
+            // joptionpane.transferFocusBackward();
+            joptionpane.transferFocusBackward();
+            joptionpane.setVisible(false);
+            // put focus and raise parent window if possible, unless cancel
+            // button pressed
+            boolean raiseParent = (parentComponent != null);
+            if (buttonAction == JOptionPane.CANCEL_OPTION)
+              raiseParent = false;
+            if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION)
+              raiseParent = false;
+            if (raiseParent) {
+              parentComponent.requestFocus();
+              if (parentComponent instanceof JInternalFrame) {
+                JInternalFrame jif = (JInternalFrame) parentComponent;
+                jif.show();
+                jif.moveToFront();
+                jif.grabFocus();
+              } else if (parentComponent instanceof Window) {
+                Window w = (Window) parentComponent;
+                w.toFront();
+                w.requestFocus();
+              }
+            }
+            joptionpane.setVisible(false);
+          }
+        });
+        optionsButtons[i] = jb;
+        if (o.equals(initialValue))
+          initialValueButton = jb;
+      }
+    }
+    joptionpane.setMessage(message);
+    joptionpane.setMessageType(messageType);
+    joptionpane.setOptionType(optionType);
+    joptionpane.setIcon(icon);
+    joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
+    joptionpane.setInitialValue(Platform.isJS() ? initialValue : initialValueButton);
+
+    JDialog dialog = joptionpane.createDialog(parentComponent, title);
+    dialog.setIconImage(WindowIcons.logoIcon.getImage());
+    dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
+    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+    return dialog;
+  }
+
+  /**
+   * Utility to programmatically click a button on a JOptionPane (as a JFrame)
+   * 
+   * returns true if button was found
+   */
+  public static boolean clickButton(JFrame frame, int buttonType) {
+
+    return false;
+  }
+
+  /**
+   * This helper method makes the JInternalFrame wait until it is notified by an
+   * InternalFrameClosing event. This method also adds the given JOptionPane to
+   * the JInternalFrame and sizes it according to the JInternalFrame's preferred
+   * size.
+   *
+   * @param f The JInternalFrame to make modal.
+   */
+  private static void startModal(JInternalFrame f) {
+    // We need to add an additional glasspane-like component directly
+    // below the frame, which intercepts all mouse events that are not
+    // directed at the frame itself.
+    JPanel modalInterceptor = new JPanel();
+    modalInterceptor.setOpaque(false);
+    JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
+    lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
+    modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
+    modalInterceptor.addMouseListener(new MouseAdapter() {
+    });
+    modalInterceptor.addMouseMotionListener(new MouseMotionAdapter() {
+    });
+    lp.add(modalInterceptor);
+    f.toFront();
+
+    // We need to explicitly dispatch events when we are blocking the event
+    // dispatch thread.
+    EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
+    try {
+      while (!f.isClosed()) {
+        if (EventQueue.isDispatchThread()) {
+          // The getNextEventMethod() issues wait() when no
+          // event is available, so we don't need do explicitly wait().
+          AWTEvent ev = queue.getNextEvent();
+          // This mimics EventQueue.dispatchEvent(). We can't use
+          // EventQueue.dispatchEvent() directly, because it is
+          // protected, unfortunately.
+          if (ev instanceof ActiveEvent)
+            ((ActiveEvent) ev).dispatch();
+          else if (ev.getSource() instanceof Component)
+            ((Component) ev.getSource()).dispatchEvent(ev);
+          else if (ev.getSource() instanceof MenuComponent)
+            ((MenuComponent) ev.getSource()).dispatchEvent(ev);
+          // Other events are ignored as per spec in
+          // EventQueue.dispatchEvent
+        } else {
+          // Give other threads a chance to become active.
+          Thread.yield();
+        }
+      }
+    } catch (InterruptedException ex) {
+      // If we get interrupted, then leave the modal state.
+    } finally {
+      // Clean up the modal interceptor.
+      lp.remove(modalInterceptor);
+
+      // Remove the internal frame from its parent, so it is no longer
+      // lurking around and clogging memory.
+      Container parent = f.getParent();
+      if (parent != null)
+        parent.remove(f);
     }
   }
 }
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 e6b6b83..63ce5ad 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
 import jalview.analysis.scoremodels.ScoreModels;
 import jalview.api.AlignViewportI;
 import jalview.api.analysis.ScoreModelI;
@@ -39,23 +56,6 @@ import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.PCAModel;
 
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.print.PageFormat;
-import java.awt.print.Printable;
-import java.awt.print.PrinterException;
-import java.awt.print.PrinterJob;
-
-import javax.swing.ButtonGroup;
-import javax.swing.JMenuItem;
-import javax.swing.JRadioButtonMenuItem;
-import javax.swing.event.InternalFrameAdapter;
-import javax.swing.event.InternalFrameEvent;
-
 /**
  * The panel holding the Principal Component Analysis 3-D visualisation
  */
@@ -92,6 +92,7 @@ public class PCAPanel extends GPCAPanel
           SimilarityParamsI params)
   {
     super();
+    this.setFrameIcon(WindowIcons.treeIcon);
     this.av = alignPanel.av;
     this.ap = alignPanel;
     boolean nucleotide = av.getAlignment().isNucleotide();
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 06d3a60..987051f 100755 (executable)
@@ -249,6 +249,7 @@ public class Preferences extends GPreferences
   {
     super();
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.preferencesIcon);
     frame.setContentPane(this);
     if (!Platform.isJS())
     /**
diff --git a/src/jalview/gui/QuitHandler.java b/src/jalview/gui/QuitHandler.java
new file mode 100644 (file)
index 0000000..e90a2d5
--- /dev/null
@@ -0,0 +1,410 @@
+package jalview.gui;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JTextPane;
+
+import com.formdev.flatlaf.extras.FlatDesktop;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.BackupFiles;
+import jalview.project.Jalview2XML;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+public class QuitHandler
+{
+  private static final int MIN_WAIT_FOR_SAVE = 3000;
+
+  private static final int MAX_WAIT_FOR_SAVE = 20000;
+
+  public static enum QResponse
+  {
+    NULL, QUIT, CANCEL_QUIT, FORCE_QUIT
+  };
+
+  private static ExecutorService executor = Executors.newFixedThreadPool(3);
+
+  public static QResponse setQuitHandler()
+  {
+    FlatDesktop.setQuitHandler(response -> {
+      Callable<Void> performQuit = () -> {
+        response.performQuit();
+        setResponse(QResponse.QUIT);
+        return null;
+      };
+      Callable<Void> performForceQuit = () -> {
+        response.performQuit();
+        setResponse(QResponse.FORCE_QUIT);
+        return null;
+      };
+      Callable<Void> cancelQuit = () -> {
+        response.cancelQuit();
+        // reset
+        setResponse(QResponse.NULL);
+        return null;
+      };
+      getQuitResponse(true, performQuit, performForceQuit, cancelQuit);
+    });
+
+    return gotQuitResponse();
+  }
+
+  private static QResponse gotQuitResponse = QResponse.NULL;
+
+  private static QResponse setResponse(QResponse qresponse)
+  {
+    gotQuitResponse = qresponse;
+    return qresponse;
+  }
+
+  public static QResponse gotQuitResponse()
+  {
+    return gotQuitResponse;
+  }
+
+  public static final Callable<Void> defaultCancelQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CANCELLED by user");
+    // reset
+    setResponse(QResponse.NULL);
+    return null;
+  };
+
+  public static final Callable<Void> defaultOkQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action CONFIRMED by user");
+    setResponse(QResponse.QUIT);
+    return null;
+  };
+
+  public static final Callable<Void> defaultForceQuit = () -> {
+    Console.debug("QuitHandler: (default) Quit action FORCED by user");
+    // note that shutdown hook will not be run
+    Runtime.getRuntime().halt(0);
+    setResponse(QResponse.FORCE_QUIT); // this line never reached!
+    return null;
+  };
+
+  public static QResponse getQuitResponse(boolean ui)
+  {
+    return getQuitResponse(ui, defaultOkQuit, defaultForceQuit,
+            defaultCancelQuit);
+  }
+
+  private static boolean interactive = true;
+
+  public static QResponse getQuitResponse(boolean ui, Callable<Void> okQuit,
+          Callable<Void> forceQuit, Callable<Void> cancelQuit)
+  {
+    QResponse got = gotQuitResponse();
+    if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT)
+    {
+      // quit has already been selected, continue with calling quit method
+      return got;
+    }
+
+    interactive = ui && !Platform.isHeadless();
+    // confirm quit if needed and wanted
+    boolean confirmQuit = true;
+
+    if (!interactive)
+    {
+      Console.debug("Non interactive quit -- not confirming");
+      confirmQuit = false;
+    }
+    else if (Jalview2XML.allSavedUpToDate())
+    {
+      Console.debug("Nothing changed -- not confirming quit");
+      confirmQuit = false;
+    }
+    else
+    {
+      confirmQuit = jalview.bin.Cache
+              .getDefault(jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
+      Console.debug("Jalview property '"
+              + jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT
+              + "' is/defaults to " + confirmQuit + " -- "
+              + (confirmQuit ? "" : "not ") + "confirming quit");
+    }
+    got = confirmQuit ? QResponse.NULL : QResponse.QUIT;
+    setResponse(got);
+
+    if (confirmQuit)
+    {
+      JvOptionPane.newOptionDialog()
+              .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit)
+              .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit)
+              .showDialogOnTopAsync(
+                      new StringBuilder(MessageManager
+                              .getString("label.quit_jalview"))
+                                      .append("\n")
+                                      .append(MessageManager.getString(
+                                              "label.unsaved_changes"))
+                                      .toString(),
+                      MessageManager.getString("action.quit"),
+                      JOptionPane.YES_NO_OPTION,
+                      JOptionPane.QUESTION_MESSAGE, null, new Object[]
+                      { MessageManager.getString("action.quit"),
+                          MessageManager.getString("action.cancel") },
+                      MessageManager.getString("action.quit"), true);
+    }
+
+    got = gotQuitResponse();
+    boolean wait = false;
+    if (got == QResponse.CANCEL_QUIT)
+    {
+      // reset
+      setResponse(QResponse.NULL);
+      // but return cancel
+      return QResponse.CANCEL_QUIT;
+    }
+    else if (got == QResponse.QUIT)
+    {
+      if (Cache.getDefault("WAIT_FOR_SAVE", true)
+              && BackupFiles.hasSavesInProgress())
+      {
+        waitQuit(interactive, okQuit, forceQuit, cancelQuit);
+        QResponse waitResponse = gotQuitResponse();
+        wait = waitResponse == QResponse.QUIT;
+      }
+    }
+
+    Callable<Void> next = null;
+    switch (gotQuitResponse())
+    {
+    case QUIT:
+      next = okQuit;
+      break;
+    case FORCE_QUIT: // not actually an option at this stage
+      next = forceQuit;
+      break;
+    default:
+      next = cancelQuit;
+      break;
+    }
+    try
+    {
+      executor.submit(next).get();
+      got = gotQuitResponse();
+    } catch (InterruptedException | ExecutionException e)
+    {
+      jalview.bin.Console
+              .debug("Exception during quit handling (final choice)", e);
+    }
+    setResponse(got);
+
+    if (gotQuitResponse() == QResponse.CANCEL_QUIT)
+    {
+      // reset if cancelled
+      setResponse(QResponse.NULL);
+      return QResponse.CANCEL_QUIT;
+    }
+    return gotQuitResponse();
+  }
+
+  private static QResponse waitQuit(boolean interactive,
+          Callable<Void> okQuit, Callable<Void> forceQuit,
+          Callable<Void> cancelQuit)
+  {
+    // check for saves in progress
+    if (!BackupFiles.hasSavesInProgress())
+      return QResponse.QUIT;
+
+    int size = 0;
+    AlignFrame[] afArray = Desktop.getAlignFrames();
+    if (!(afArray == null || afArray.length == 0))
+    {
+      for (int i = 0; i < afArray.length; i++)
+      {
+        AlignFrame af = afArray[i];
+        List<? extends AlignmentViewPanel> avpList = af.getAlignPanels();
+        for (AlignmentViewPanel avp : avpList)
+        {
+          AlignmentI a = avp.getAlignment();
+          List<SequenceI> sList = a.getSequences();
+          for (SequenceI s : sList)
+          {
+            size += s.getLength();
+          }
+        }
+      }
+    }
+    int waitTime = Math.min(MAX_WAIT_FOR_SAVE,
+            Math.max(MIN_WAIT_FOR_SAVE, size / 2));
+    Console.debug("Set waitForSave to " + waitTime);
+
+    int iteration = 0;
+    boolean doIterations = true; // note iterations not used in the gui now,
+                                 // only one pass without the "Wait" button
+    while (doIterations && BackupFiles.hasSavesInProgress()
+            && iteration++ < (interactive ? 100 : 5))
+    {
+      // future that returns a Boolean when all files are saved
+      CompletableFuture<Boolean> filesAllSaved = new CompletableFuture<>();
+
+      // callback as each file finishes saving
+      for (CompletableFuture<Boolean> cf : BackupFiles
+              .savesInProgressCompletableFutures(false))
+      {
+        // if this is the last one then complete filesAllSaved
+        cf.whenComplete((ret, e) -> {
+          if (!BackupFiles.hasSavesInProgress())
+          {
+            filesAllSaved.complete(true);
+          }
+        });
+      }
+      try
+      {
+        filesAllSaved.get(waitTime, TimeUnit.MILLISECONDS);
+      } catch (InterruptedException | ExecutionException e1)
+      {
+        Console.debug(
+                "Exception whilst waiting for files to save before quit",
+                e1);
+      } catch (TimeoutException e2)
+      {
+        // this Exception to be expected
+      }
+
+      if (interactive && BackupFiles.hasSavesInProgress())
+      {
+        boolean showForceQuit = iteration > 0; // iteration > 1 to not show
+                                               // force quit the first time
+        JFrame parent = new JFrame();
+        JButton[] buttons = { new JButton(), new JButton() };
+        JvOptionPane waitDialog = JvOptionPane.newOptionDialog();
+        JTextPane messagePane = new JTextPane();
+        messagePane.setBackground(waitDialog.getBackground());
+        messagePane.setBorder(null);
+        messagePane.setText(waitingForSaveMessage());
+        // callback as each file finishes saving
+        for (CompletableFuture<Boolean> cf : BackupFiles
+                .savesInProgressCompletableFutures(false))
+        {
+          cf.whenComplete((ret, e) -> {
+            if (BackupFiles.hasSavesInProgress())
+            {
+              // update the list of saving files as they save too
+              messagePane.setText(waitingForSaveMessage());
+            }
+            else
+            {
+              if (!(QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT
+                      || QuitHandler.gotQuitResponse() == QResponse.NULL))
+              {
+                for (int i = 0; i < buttons.length; i++)
+                {
+                  Console.debug("DISABLING BUTTON " + buttons[i].getText());
+                  buttons[i].setEnabled(false);
+                  buttons[i].setVisible(false);
+                }
+                // if this is the last one then close the dialog
+                messagePane.setText(new StringBuilder()
+                        .append(MessageManager.getString("label.all_saved"))
+                        .append("\n")
+                        .append(MessageManager
+                                .getString("label.quitting_bye"))
+                        .toString());
+                messagePane.setEditable(false);
+                try
+                {
+                  Thread.sleep(1500);
+                } catch (InterruptedException e1)
+                {
+                }
+                parent.dispose();
+              }
+            }
+          });
+        }
+
+        String[] options;
+        int dialogType = -1;
+        if (showForceQuit)
+        {
+          options = new String[2];
+          options[0] = MessageManager.getString("action.force_quit");
+          options[1] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_NO_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, forceQuit)
+                  .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit);
+        }
+        else
+        {
+          options = new String[1];
+          options[0] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, cancelQuit);
+        }
+        waitDialog.showDialogOnTopAsync(parent, messagePane,
+                MessageManager.getString("label.wait_for_save"), dialogType,
+                JOptionPane.WARNING_MESSAGE, null, options,
+                MessageManager.getString("action.cancel_quit"), true,
+                buttons);
+
+        parent.dispose();
+        final QResponse thisWaitResponse = gotQuitResponse();
+        switch (thisWaitResponse)
+        {
+        case QUIT: // wait -- do another iteration
+          break;
+        case FORCE_QUIT:
+          doIterations = false;
+          break;
+        case CANCEL_QUIT:
+          doIterations = false;
+          break;
+        case NULL: // already cancelled
+          doIterations = false;
+          break;
+        default:
+        }
+      } // end if interactive
+
+    } // end while wait iteration loop
+    return gotQuitResponse();
+  };
+
+  private static String waitingForSaveMessage()
+  {
+    StringBuilder messageSB = new StringBuilder();
+
+    messageSB.append(MessageManager.getString("label.save_in_progress"));
+    List<File> files = BackupFiles.savesInProgressFiles(false);
+    boolean any = files.size() > 0;
+    if (any)
+    {
+      for (File file : files)
+      {
+        messageSB.append("\n\u2022 ").append(file.getName());
+      }
+    }
+    else
+    {
+      messageSB.append(MessageManager.getString("label.unknown"));
+    }
+    messageSB.append("\n\n")
+            .append(MessageManager.getString("label.quit_after_saving"));
+    return messageSB.toString();
+  }
+
+  public static void abortQuit()
+  {
+    setResponse(QResponse.CANCEL_QUIT);
+  }
+}
\ No newline at end of file
index 6ed3248..7a50758 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.analysis.AlignSeq;
-import jalview.commands.CommandI;
-import jalview.commands.EditCommand;
-import jalview.commands.EditCommand.Action;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.jbgui.GSliderPanel;
-import jalview.util.MessageManager;
-
 import java.awt.event.ActionEvent;
 import java.util.ArrayList;
 import java.util.List;
@@ -42,6 +33,15 @@ import javax.swing.event.ChangeListener;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
+import jalview.analysis.AlignSeq;
+import jalview.commands.CommandI;
+import jalview.commands.EditCommand;
+import jalview.commands.EditCommand.Action;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.jbgui.GSliderPanel;
+import jalview.util.MessageManager;
+
 /**
  * DOCUMENT ME!
  * 
@@ -100,6 +100,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     worker.start();
 
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.logoIcon);
     frame.setContentPane(this);
     Desktop.addInternalFrame(frame,
             MessageManager
index e596fbf..3b93059 100755 (executable)
@@ -186,6 +186,7 @@ public class SequenceFetcher extends JPanel implements Runnable
 
     frame = new JInternalFrame();
     frame.setContentPane(this);
+    frame.setFrameIcon(WindowIcons.fetchIcon);
     Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
             Platform.isAMacAndNotJS() ? 240 : 180);
   }
index 5e1357a..6665588 100755 (executable)
@@ -156,6 +156,7 @@ public class SliderPanel extends GSliderPanel
     {
       sliderPanel = new SliderPanel(ap, rs.getConservationInc(), true, rs);
       conservationSlider = new JInternalFrame();
+      conservationSlider.setFrameIcon(WindowIcons.logoIcon);
       conservationSlider.setContentPane(sliderPanel);
       conservationSlider.setLayer(JLayeredPane.PALETTE_LAYER);
     }
@@ -269,6 +270,7 @@ public class SliderPanel extends GSliderPanel
     {
       sliderPanel = new SliderPanel(ap, threshold, false, rs);
       PIDSlider = new JInternalFrame();
+      PIDSlider.setFrameIcon(WindowIcons.logoIcon);
       PIDSlider.setContentPane(sliderPanel);
       PIDSlider.setLayer(JLayeredPane.PALETTE_LAYER);
     }
index 6ebedb7..7398a3f 100644 (file)
@@ -105,6 +105,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
    */
   protected void init()
   {
+    setFrameIcon(WindowIcons.splitIcon);
     getTopFrame().setSplitFrame(this);
     getBottomFrame().setSplitFrame(this);
     getTopFrame().setVisible(true);
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 ec5579c..82b6759 100644 (file)
@@ -135,6 +135,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   public StructureViewerBase()
   {
     super();
+    setFrameIcon(WindowIcons.structureIcon);
   }
 
   /**
@@ -1280,6 +1281,9 @@ public abstract class StructureViewerBase extends GStructureViewer
         if (confirm == JvOptionPane.CANCEL_OPTION
                 || confirm == JvOptionPane.CLOSED_OPTION)
         {
+          // abort possible quit handling if CANCEL chosen
+          if (confirm == JvOptionPane.CANCEL_OPTION)
+            QuitHandler.abortQuit();
           return;
         }
         forceClose = confirm == JvOptionPane.YES_OPTION;
index f9ff337..e72a084 100644 (file)
@@ -27,6 +27,7 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import javax.swing.BorderFactory;
 import javax.swing.JLabel;
@@ -151,13 +152,10 @@ public class TextColourChooser
         MessageManager.getString("action.cancel") };
     String title = MessageManager
             .getString("label.adjust_foreground_text_colour_threshold");
-    Runnable action = new Runnable() // response for 1 = Cancel
+    Callable<Void> action = () -> // response for 1 = Cancel
     {
-      @Override
-      public void run()
-      {
-        restoreInitialSettings();
-      }
+      restoreInitialSettings();
+      return null;
     };
     JvOptionPane.newOptionDialog(alignPanel.alignFrame)
             .setResponseHandler(1, action).showInternalDialog(bigpanel,
index d735402..2d446d9 100755 (executable)
  */
 package jalview.gui;
 
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
+import javax.swing.ButtonGroup;
+import javax.swing.JMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+import org.jibble.epsgraphics.EpsGraphics2D;
+
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AverageDistanceTree;
 import jalview.analysis.NJTree;
@@ -53,25 +71,6 @@ import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 
-import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.ButtonGroup;
-import javax.swing.JMenuItem;
-import javax.swing.JRadioButtonMenuItem;
-import javax.swing.event.InternalFrameAdapter;
-import javax.swing.event.InternalFrameEvent;
-
-import org.jibble.epsgraphics.EpsGraphics2D;
-
 /**
  * DOCUMENT ME!
  * 
@@ -106,6 +105,7 @@ public class TreePanel extends GTreePanel
           SimilarityParamsI options)
   {
     super();
+    this.setFrameIcon(WindowIcons.treeIcon);
     this.similarityParams = options;
     initTreePanel(ap, type, modelName, null, null);
 
index 1836e33..4979746 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.
@@ -149,6 +148,7 @@ public class UserDefinedColours extends GUserDefinedColours
   {
     colorChooser.getSelectionModel().addChangeListener(this);
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.logoIcon);
     frame.setContentPane(this);
     Desktop.addInternalFrame(frame,
             MessageManager.getString("label.user_defined_colours"),
@@ -652,45 +652,41 @@ public class UserDefinedColours extends GUserDefinedColours
     chooser.setDialogTitle(
             MessageManager.getString("label.load_colour_scheme"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File choice = chooser.getSelectedFile();
-        Cache.setProperty(LAST_DIRECTORY, choice.getParent());
-
-        UserColourScheme ucs = ColourSchemeLoader
-                .loadColourScheme(choice.getAbsolutePath());
-        Color[] colors = ucs.getColours();
-        schemeName.setText(ucs.getSchemeName());
+    chooser.setResponseHandler(0, () -> {
+      File choice = chooser.getSelectedFile();
+      Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 
-        if (ucs.getLowerCaseColours() != null)
-        {
-          caseSensitive.setSelected(true);
-          lcaseColour.setEnabled(true);
-          resetButtonPanel(true);
-          for (int i = 0; i < lowerCaseButtons.size(); i++)
-          {
-            JButton button = lowerCaseButtons.get(i);
-            button.setBackground(ucs.getLowerCaseColours()[i]);
-          }
-        }
-        else
-        {
-          caseSensitive.setSelected(false);
-          lcaseColour.setEnabled(false);
-          resetButtonPanel(false);
-        }
+      UserColourScheme ucs = ColourSchemeLoader
+              .loadColourScheme(choice.getAbsolutePath());
+      Color[] colors = ucs.getColours();
+      schemeName.setText(ucs.getSchemeName());
 
-        for (int i = 0; i < upperCaseButtons.size(); i++)
+      if (ucs.getLowerCaseColours() != null)
+      {
+        caseSensitive.setSelected(true);
+        lcaseColour.setEnabled(true);
+        resetButtonPanel(true);
+        for (int i = 0; i < lowerCaseButtons.size(); i++)
         {
-          JButton button = upperCaseButtons.get(i);
-          button.setBackground(colors[i]);
+          JButton button = lowerCaseButtons.get(i);
+          button.setBackground(ucs.getLowerCaseColours()[i]);
         }
+      }
+      else
+      {
+        caseSensitive.setSelected(false);
+        lcaseColour.setEnabled(false);
+        resetButtonPanel(false);
+      }
 
-        addNewColourScheme(choice.getPath());
+      for (int i = 0; i < upperCaseButtons.size(); i++)
+      {
+        JButton button = upperCaseButtons.get(i);
+        button.setBackground(colors[i]);
       }
+
+      addNewColourScheme(choice.getPath());
+      return null;
     });
 
     chooser.showOpenDialog(this);
index ee1b473..edcab0c 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import java.util.Locale;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
@@ -33,6 +31,7 @@ import java.awt.MediaTracker;
 import java.awt.RenderingHints;
 import java.awt.event.ActionEvent;
 import java.awt.image.BufferedImage;
+import java.util.Locale;
 import java.util.Vector;
 
 import javax.swing.JComponent;
@@ -324,6 +323,7 @@ public class WebserviceInfo extends GWebserviceInfo
           boolean makeVisible)
   {
     frame = new JInternalFrame();
+    frame.setFrameIcon(WindowIcons.logoIcon);
     frame.setContentPane(this);
     Desktop.addInternalFrame(frame, title, makeVisible, width, height);
     frame.setClosable(false);
diff --git a/src/jalview/gui/WindowIcons.java b/src/jalview/gui/WindowIcons.java
new file mode 100644 (file)
index 0000000..b735667
--- /dev/null
@@ -0,0 +1,51 @@
+package jalview.gui;
+
+import javax.swing.ImageIcon;
+
+import jalview.util.ChannelProperties;
+
+public class WindowIcons
+{
+  public static final ImageIcon logoIcon = new ImageIcon(
+          ChannelProperties.getImage("logo.16"));
+
+  protected static final ImageIcon plainIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/plainIcon.png"));
+
+  protected static final ImageIcon alignmentIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/alignmentIcon.png"));
+
+  protected static final ImageIcon featuresIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/featuresIcon.png"));
+
+  protected static final ImageIcon fetchIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/fetchIcon.png"));
+
+  protected static final ImageIcon annotationIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/annotationIcon.png"));
+
+  protected static final ImageIcon overviewIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/overviewIcon.png"));
+
+  protected static final ImageIcon treeIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/treeIcon.png"));
+
+  protected static final ImageIcon structureIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/structureIcon.png"));
+
+  protected static final ImageIcon splitIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/splitIcon.png"));
+
+  protected static final ImageIcon preferencesIcon = new ImageIcon(
+          WindowIcons.class
+                  .getResource("/images/windowIcons/preferencesIcon.png"));
+}
index 2039d3c..9112042 100644 (file)
@@ -29,8 +29,14 @@ import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import jalview.bin.Cache;
 import jalview.bin.Console;
@@ -105,6 +111,117 @@ public class BackupFiles
 
   private static final String oldTempFileSuffix = "_oldfile_tobedeleted";
 
+  // thread pool used for completablefutures
+  private static final ExecutorService executorService = Executors
+          .newFixedThreadPool(3);
+
+  private static List<BackupFiles> savesInProgress = new ArrayList<>();
+
+  private CompletableFuture<Boolean> myFuture = null;
+
+  private boolean addSaveInProgress()
+  {
+    if (savesInProgress.contains(this))
+    {
+      return false;
+    }
+    else
+    {
+      this.setMyFuture();
+      savesInProgress.add(this);
+      return true;
+    }
+  }
+
+  private boolean removeSaveInProgress(boolean ret)
+  {
+    if (savesInProgress.contains(this))
+    {
+      this.getMyFuture().complete(ret);
+      // remove all occurrences
+      while (savesInProgress.remove(this))
+      {
+      }
+      return true;
+    }
+    return false;
+  }
+
+  private static CompletableFuture<Boolean> getNewFuture()
+  {
+    return new CompletableFuture<Boolean>()
+    {
+    };
+  }
+
+  private CompletableFuture<Boolean> getMyFuture()
+  {
+    return this.myFuture;
+  }
+
+  private void setMyFuture()
+  {
+    this.myFuture = getNewFuture();
+  }
+
+  public static boolean hasSavesInProgress()
+  {
+    boolean has = false;
+    for (CompletableFuture cf : savesInProgressCompletableFutures(true))
+    {
+      has |= !cf.isDone();
+    }
+    return has;
+  }
+
+  public static List<File> savesInProgressFiles(boolean all)
+  {
+    List<File> files = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        files.add(bfile.getFile());
+    }
+    return files;
+  }
+
+  public static List<CompletableFuture<Boolean>> savesInProgressCompletableFutures(
+          boolean all)
+  {
+    List<CompletableFuture<Boolean>> cfs = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        cfs.add(bfile.getMyFuture());
+    }
+    return cfs;
+  }
+
+  public static Future<Boolean> allSaved()
+  {
+    CompletableFuture<Boolean> f = new CompletableFuture<>();
+
+    executorService.submit(() -> {
+      for (BackupFiles buf : savesInProgress)
+      {
+        boolean allSaved = true;
+        try
+        {
+          allSaved &= buf.getMyFuture().get();
+        } catch (InterruptedException e)
+        {
+          Console.debug("InterruptedException waiting for files to save",
+                  e);
+        } catch (ExecutionException e)
+        {
+          Console.debug("ExecutionException waiting for files to save", e);
+        }
+        f.complete(allSaved);
+      }
+    });
+    return f;
+  }
+
   public BackupFiles(String filename)
   {
     this(new File(filename));
@@ -116,6 +233,10 @@ public class BackupFiles
   {
     classInit();
     this.file = file;
+
+    // add this file from the save in progress stack
+    addSaveInProgress();
+
     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
             .getSavedBackupEntry();
     this.suffix = bfpe.suffix;
@@ -819,6 +940,9 @@ public class BackupFiles
       tidyUpFiles();
     }
 
+    // remove this file from the save in progress stack
+    removeSaveInProgress(rename);
+
     return rename;
   }
 
@@ -890,6 +1014,11 @@ public class BackupFiles
     return ret;
   }
 
+  public File getFile()
+  {
+    return file;
+  }
+
   public static boolean moveFileToFile(File oldFile, File newFile)
   {
     Console.initLogger();
index 4016e71..ffeb53d 100755 (executable)
@@ -465,6 +465,7 @@ public class FileLoader implements Runnable
             {
               alignFrame.setFileName(file, format);
               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
+              alignFrame.getViewport().setSavedUpToDate(true);
             }
             if (proxyColourScheme != null)
             {
index 4b66f81..9fb3720 100644 (file)
  */
 package jalview.io;
 
-import jalview.bin.Cache;
-import jalview.gui.AlignmentPanel;
-import jalview.gui.LineartOptions;
-import jalview.gui.OOMWarning;
-import jalview.math.AlignmentDimension;
-import jalview.util.MessageManager;
-
 import java.awt.Graphics;
 import java.awt.print.PrinterException;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jfree.graphics2d.svg.SVGHints;
 
+import jalview.bin.Cache;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.LineartOptions;
+import jalview.gui.OOMWarning;
+import jalview.math.AlignmentDimension;
+import jalview.util.MessageManager;
+
 public class HtmlSvgOutput extends HTMLOutput
 {
   public HtmlSvgOutput(AlignmentPanel ap)
@@ -211,13 +212,9 @@ public class HtmlSvgOutput extends HTMLOutput
       /*
        * configure the action to run on OK in the dialog
        */
-      Runnable okAction = new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          doOutput(textOption.get());
-        }
+      Callable<Void> okAction = () -> {
+        doOutput(textOption.get());
+        return null;
       };
 
       /*
@@ -226,15 +223,11 @@ public class HtmlSvgOutput extends HTMLOutput
       if (renderStyle.equalsIgnoreCase("Prompt each time") && !isHeadless())
       {
         LineartOptions svgOption = new LineartOptions("HTML", textOption);
-        svgOption.setResponseAction(1, new Runnable()
-        {
-          @Override
-          public void run()
-          {
-            setProgressMessage(MessageManager.formatMessage(
-                    "status.cancelled_image_export_operation",
-                    getDescription()));
-          }
+        svgOption.setResponseAction(1, () -> {
+          setProgressMessage(MessageManager.formatMessage(
+                  "status.cancelled_image_export_operation",
+                  getDescription()));
+          return null;
         });
         svgOption.setResponseAction(0, okAction);
         svgOption.showDialog();
index a9101a1..077bffb 100755 (executable)
@@ -44,18 +44,28 @@ 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.JDialog;
 import javax.swing.JFileChooser;
 import javax.swing.JList;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SpringLayout;
 import javax.swing.filechooser.FileFilter;
 import javax.swing.plaf.basic.BasicFileChooserUI;
 
+import jalview.bin.Cache;
+import jalview.gui.JvOptionPane;
+import jalview.gui.WindowIcons;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.dialogrunner.DialogRunnerI;
+
 /**
  * Enhanced file chooser dialog box.
  *
@@ -70,7 +80,7 @@ public class JalviewFileChooser extends JFileChooser
 {
   private static final long serialVersionUID = 1L;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable> callbacks = new HashMap<>();
 
   File selectedFile = null;
 
@@ -488,10 +498,13 @@ public class JalviewFileChooser extends JFileChooser
 
     if (selectedFile.exists())
     {
-      int confirm = JvOptionPane.showConfirmDialog(this,
-              MessageManager.getString("label.overwrite_existing_file"),
-              MessageManager.getString("label.file_already_exists"),
-              JvOptionPane.YES_NO_OPTION);
+      int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true)
+              ? JvOptionPane.showConfirmDialog(this,
+                      MessageManager
+                              .getString("label.overwrite_existing_file"),
+                      MessageManager.getString("label.file_already_exists"),
+                      JvOptionPane.YES_NO_OPTION)
+              : JOptionPane.YES_OPTION;
 
       if (confirm != JvOptionPane.YES_OPTION)
       {
@@ -596,8 +609,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 +644,16 @@ public class JalviewFileChooser extends JFileChooser
     {
       return;
     }
-    Runnable action = callbacks.get(response);
+    Callable action = callbacks.get(response);
     if (action != null)
     {
-      action.run();
+      try
+      {
+        action.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -641,4 +678,13 @@ public class JalviewFileChooser extends JFileChooser
       break;
     }
   }
+
+  @Override
+  protected JDialog createDialog(Component parent) throws HeadlessException
+  {
+    JDialog dialog = super.createDialog(parent);
+    dialog.setIconImage(WindowIcons.logoIcon.getImage());
+    return dialog;
+  }
+
 }
index ca95222..b0079b4 100755 (executable)
@@ -32,6 +32,8 @@ import javax.swing.JMenuItem;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
+import jalview.gui.APQHandlers;
+import jalview.gui.Desktop;
 import jalview.io.FileFormatException;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -140,7 +142,6 @@ public class GDesktop extends JFrame
    */
   private void jbInit() throws Exception
   {
-    boolean apqHandlersSet = false;
     /**
      * APQHandlers sets handlers for About, Preferences and Quit actions
      * peculiar to macOS's application menu. APQHandlers will check to see if a
@@ -148,7 +149,7 @@ public class GDesktop extends JFrame
      */
     try
     {
-      apqHandlersSet = APQHandlers.setAPQHandlers(this);
+      APQHandlers.setAPQHandlers((Desktop) this);
     } catch (Exception e)
     {
       System.out.println("Cannot set APQHandlers");
@@ -213,7 +214,8 @@ public class GDesktop extends JFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        if (Desktop.instance != null)
+          Desktop.instance.desktopQuit();
       }
     });
     aboutMenuItem.setText(MessageManager.getString("label.about"));
index d4b2c04..9cec063 100644 (file)
@@ -229,6 +229,11 @@ public class Jalview2XML
   private static final String UTF_8 = "UTF-8";
 
   /**
+   * used in decision if quit confirmation should be issued
+   */
+  private static boolean stateSavedUpToDate = false;
+
+  /**
    * prefix for recovering datasets for alignments with multiple views where
    * non-existent dataset IDs were written for some views
    */
@@ -616,6 +621,27 @@ public class Jalview2XML
   {
     AlignFrame[] frames = Desktop.getAlignFrames();
 
+    setStateSavedUpToDate(true);
+
+    if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+    {
+      int n = 20;
+      int i = 0;
+      while (i < n)
+      {
+        Console.debug("***** debugging save sleep " + i + "/" + n);
+        try
+        {
+          Thread.sleep(1000);
+        } catch (InterruptedException e)
+        {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+        }
+        i++;
+      }
+    }
+
     if (frames == null)
     {
       return;
@@ -763,6 +789,25 @@ public class Jalview2XML
       FileOutputStream fos = new FileOutputStream(
               doBackup ? backupfiles.getTempFilePath() : jarFile);
 
+      if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+      {
+        int n = 20;
+        int i = 0;
+        while (i < n)
+        {
+          Console.debug("***** debugging save sleep " + i + "/" + n);
+          try
+          {
+            Thread.sleep(1000);
+          } catch (InterruptedException e)
+          {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          }
+          i++;
+        }
+      }
+
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
 
@@ -6537,4 +6582,36 @@ public class Jalview2XML
 
     return colour;
   }
+
+  public static void setStateSavedUpToDate(boolean s)
+  {
+    Console.debug("Setting overall stateSavedUpToDate to " + s);
+    stateSavedUpToDate = s;
+  }
+
+  public static boolean stateSavedUpToDate()
+  {
+    Console.debug("Returning overall stateSavedUpToDate value: "
+            + stateSavedUpToDate);
+    return stateSavedUpToDate;
+  }
+
+  public static boolean allSavedUpToDate()
+  {
+    if (stateSavedUpToDate()) // nothing happened since last project save
+      return true;
+
+    AlignFrame[] frames = Desktop.getAlignFrames();
+    if (frames != null)
+    {
+      for (int i = 0; i < frames.length; i++)
+      {
+        if (frames[i] == null)
+          continue;
+        if (!frames[i].getViewport().savedUpToDate())
+          return false; // at least one alignment is not individually saved
+      }
+    }
+    return true;
+  }
 }
index fde80f7..1fc41e7 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.util.dialogrunner;
 
+import java.util.concurrent.Callable;
+
 /**
  * An interface for blocking dialog response handling. This is motivated by
  * JalviewJS - when running as Javascript, there is only a single thread, and
@@ -41,7 +43,9 @@ public interface DialogRunnerI
    * @param action
    * @return
    */
-  DialogRunnerI setResponseHandler(Object response, Runnable action);
+  DialogRunnerI setResponseHandler(Object response, Callable<Void> action);
+
+  // DialogRunnerI setResponseHandler(Object response, Runnable action);
 
   /**
    * Runs the registered handler (if any) for the given response. The default
index 08af2ec..5a4ceb9 100644 (file)
  */
 package jalview.viewmodel;
 
+import java.awt.Color;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
@@ -29,6 +41,7 @@ import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeaturesDisplayedI;
 import jalview.api.ViewStyleI;
+import jalview.bin.Console;
 import jalview.commands.CommandI;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
@@ -45,6 +58,7 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.project.Jalview2XML;
 import jalview.renderer.ResidueShader;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
@@ -61,18 +75,6 @@ import jalview.workers.ComplementConsensusThread;
 import jalview.workers.ConsensusThread;
 import jalview.workers.StrucConsensusThread;
 
-import java.awt.Color;
-import java.beans.PropertyChangeSupport;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * base class holding visualization and analysis attributes and common logic for
  * an active alignment view displayed in the GUI
@@ -100,6 +102,11 @@ public abstract class AlignmentViewport
   protected Deque<CommandI> redoList = new ArrayDeque<>();
 
   /**
+   * used to determine if quit should be confirmed
+   */
+  private boolean savedUpToDate = false;
+
+  /**
    * alignment displayed in the viewport. Please use get/setter
    */
   protected AlignmentI alignment;
@@ -2614,6 +2621,8 @@ public abstract class AlignmentViewport
     {
       this.historyList.push(command);
       broadcastCommand(command, false);
+      setSavedUpToDate(false);
+      Jalview2XML.setStateSavedUpToDate(false);
     }
   }
 
@@ -3096,4 +3105,18 @@ public abstract class AlignmentViewport
     return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
             false));
   }
+
+  public void setSavedUpToDate(boolean s)
+  {
+    Console.debug(
+            "Setting " + this.getViewId() + " setSavedUpToDate to " + s);
+    savedUpToDate = s;
+  }
+
+  public boolean savedUpToDate()
+  {
+    Console.debug("Returning " + this.getViewId() + " savedUpToDate value: "
+            + savedUpToDate);
+    return savedUpToDate;
+  }
 }