Merge branch 'improvement/JAL-3416_default_to_LIVE_DRAG_MODE_for_flatlaf' into merge...
authorBen Soares <b.soares@dundee.ac.uk>
Wed, 16 Nov 2022 11:52:30 +0000 (11:52 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Wed, 16 Nov 2022 11:52:30 +0000 (11:52 +0000)
44 files changed:
build.gradle
gradle.properties
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/gui/APQHandlers.java [moved from src/jalview/jbgui/APQHandlers.java with 55% similarity]
src/jalview/gui/AlignExportOptions.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/Console.java
src/jalview/gui/Desktop.java
src/jalview/gui/EditNameDialog.java
src/jalview/gui/FeatureEditor.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/ImageExporter.java
src/jalview/gui/JvOptionPane.java
src/jalview/gui/LineartOptions.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/QuitHandler.java [new file with mode: 0644]
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/BackupFiles.java
src/jalview/io/FileLoader.java
src/jalview/io/HtmlSvgOutput.java
src/jalview/io/JalviewFileChooser.java
src/jalview/jbgui/GDesktop.java
src/jalview/project/Jalview2XML.java
src/jalview/util/dialogrunner/DialogRunnerI.java
src/jalview/viewmodel/AlignmentViewport.java
test/jalview/gui/QuitHandlerTest.java [new file with mode: 0644]
test/jalview/gui/quitProps.jvprops [new file with mode: 0644]
utils/download_jdks.sh
utils/download_jres.sh
utils/install4j/auto_file_associations-i4j10.pl [new file with mode: 0755]
utils/install4j/file_associations_auto-Info_plist.xml
utils/install4j/file_associations_auto-install4j10.xml [new file with mode: 0644]
utils/install4j/file_associations_template-Info_plist.xml
utils/install4j/file_associations_template-install4j10.xml [new file with mode: 0644]
utils/install4j/install4j10_template.install4j [new file with mode: 0644]
utils/install4j/install4j9_template.install4j

index bde50b5..851c5f6 100644 (file)
@@ -47,7 +47,7 @@ plugins {
   id 'eclipse'
   id "com.diffplug.gradle.spotless" version "3.28.0"
   id 'com.github.johnrengelman.shadow' version '4.0.3'
-  id 'com.install4j.gradle' version '9.0.6'
+  id 'com.install4j.gradle' version '10.0.3'
   id 'com.dorongold.task-tree' version '2.1.0' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
   id 'com.palantir.git-version' version '0.13.0' apply false
 }
@@ -483,16 +483,10 @@ ext {
   // for install4j
   JAVA_MIN_VERSION = JAVA_VERSION
   JAVA_MAX_VERSION = JAVA_VERSION
-  def jreInstallsDir = string(jre_installs_dir)
+  jreInstallsDir = string(jre_installs_dir)
   if (jreInstallsDir.startsWith("~/")) {
     jreInstallsDir = System.getProperty("user.home") + jreInstallsDir.substring(1)
   }
-  macosJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-mac-x64/jre")
-  windowsJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-windows-x64/jre")
-  linuxJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-linux-x64/jre")
-  macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_mac_x64.tar.gz")
-  windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_windows_x64.tar.gz")
-  linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_linux_x64.tar.gz")
   install4jDir = string("${jalviewDir}/${install4j_utils_dir}")
   install4jConfFileName = string("jalview-install4j-conf.install4j")
   install4jConfFile = file("${install4jDir}/${install4jConfFileName}")
@@ -2411,12 +2405,6 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
     'JAVA_VERSION': JAVA_VERSION,
     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
     'VERSION': JALVIEW_VERSION,
-    'MACOS_JAVA_VM_DIR': macosJavaVMDir,
-    'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
-    'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
-    'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
-    'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
-    'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
     'COPYRIGHT_MESSAGE': install4j_copyright_message,
     'BUNDLE_ID': install4jBundleId,
     'INTERNAL_ID': install4jInternalId,
@@ -2446,8 +2434,29 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
     'PNG_ICON_FILE': install4jPngIconFile,
     'BACKGROUND': install4jBackground,
+  ]
 
+  def varNameMap = [
+    'mac': 'MACOS',
+    'windows': 'WINDOWS',
+    'linux': 'LINUX'
+  ]
+  
+  // these are the bundled OS/architecture VMs needed by install4j
+  def osArch = [
+    [ "mac", "x64" ],
+    [ "mac", "aarch64" ],
+    [ "windows", "x64" ],
+    [ "linux", "x64" ],
+    [ "linux", "aarch64" ]
   ]
+  osArch.forEach { os, arch ->
+    variables[ sprintf("%s_%s_JAVA_VM_DIR", varNameMap[os], arch.toUpperCase(Locale.ROOT)) ] = sprintf("%s/jre-%s-%s-%s/jre", jreInstallsDir, JAVA_INTEGER_VERSION, os, arch)
+    // N.B. For some reason install4j requires the below filename to have underscores and not hyphens
+    // otherwise running `gradle installers` generates a non-useful error:
+    // `install4j: compilation failed. Reason: java.lang.NumberFormatException: For input string: "windows"`
+    variables[ sprintf("%s_%s_JAVA_VM_TGZ", varNameMap[os], arch.toUpperCase(Locale.ROOT)) ] = sprintf("%s/tgz/jre_%s_%s_%s.tar.gz", jreInstallsDir, JAVA_INTEGER_VERSION, os, arch)
+  }
 
   //println("INSTALL4J VARIABLES:")
   //variables.each{k,v->println("${k}=${v}")}
@@ -2481,8 +2490,6 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
   inputs.dir(getdownAppBaseDir)
   inputs.file(install4jConfFile)
   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
-  inputs.dir(macosJavaVMDir)
-  inputs.dir(windowsJavaVMDir)
   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
 }
 
index acb65e9..e42f8b5 100644 (file)
@@ -129,14 +129,14 @@ flexmark_css = utils/doc/github.css
 channel_properties_dir = utils/channels
 channel_props = channel.props
 
-install4j_home_dir = ~/buildtools/install4j9
+install4j_home_dir = ~/buildtools/install4j10
 install4j_copyright_message = ...
 install4j_bundle_id = org.jalview.jalview-desktop
 install4j_utils_dir = utils/install4j
 install4j_images_dir = utils/install4j
-install4j_template = install4j9_template.install4j
+install4j_template = install4j10_template.install4j
 install4j_info_plist_file_associations = file_associations_auto-Info_plist.xml
-install4j_installer_file_associations = file_associations_auto-install4j8.xml
+install4j_installer_file_associations = file_associations_auto-install4j10.xml
 #install4j_DMG_uninstaller_app_files = uninstall_old_jalview_files.xml
 install4j_build_dir = build/install4j
 install4j_executable_name = jalviewg
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 a241abe..50c98b4 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
@@ -1383,19 +1417,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 95b9db7..0a6fabc 100644 (file)
@@ -59,6 +59,7 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JCheckBoxMenuItem;
@@ -1260,6 +1261,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
               shortName);
 
+      Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+      if (lastSaveSuccessful)
+      {
+        this.getViewport().setSavedUpToDate(true);
+      }
+
       statusBar.setText(MessageManager.formatMessage(
               "label.successfully_saved_to_file_in_format", new Object[]
               { file, format }));
@@ -1268,97 +1275,89 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable cancelAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> cancelAction = () -> {
+      lastSaveSuccessful = false;
+      return null;
+    };
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      String output = new FormatAdapter(alignPanel, options)
+              .formatSequences(format, exportData.getAlignment(),
+                      exportData.getOmitHidden(),
+                      exportData.getStartEndPostions(),
+                      viewport.getAlignment().getHiddenColumns());
+      if (output == null)
       {
         lastSaveSuccessful = false;
       }
-    };
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+      else
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        String output = new FormatAdapter(alignPanel, options)
-                .formatSequences(format, exportData.getAlignment(),
-                        exportData.getOmitHidden(),
-                        exportData.getStartEndPostions(),
-                        viewport.getAlignment().getHiddenColumns());
-        if (output == null)
+        // create backupfiles object and get new temp filename destination
+        boolean doBackup = BackupFiles.getEnabled();
+        BackupFiles backupfiles = null;
+        if (doBackup)
         {
-          lastSaveSuccessful = false;
+          Console.trace("ALIGNFRAME making backupfiles object for " + file);
+          backupfiles = new BackupFiles(file);
         }
-        else
+        try
         {
-          // create backupfiles object and get new temp filename destination
-          boolean doBackup = BackupFiles.getEnabled();
-          BackupFiles backupfiles = null;
-          if (doBackup)
+          String tempFilePath = doBackup ? backupfiles.getTempFilePath()
+                  : file;
+          Console.trace("ALIGNFRAME setting PrintWriter");
+          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
+
+          if (backupfiles != null)
           {
-            Console.trace(
-                    "ALIGNFRAME making backupfiles object for " + file);
-            backupfiles = new BackupFiles(file);
+            Console.trace("ALIGNFRAME about to write to temp file "
+                    + backupfiles.getTempFilePath());
           }
-          try
-          {
-            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
-                    : file;
-            Console.trace("ALIGNFRAME setting PrintWriter");
-            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 
-            if (backupfiles != null)
-            {
-              Console.trace("ALIGNFRAME about to write to temp file "
-                      + backupfiles.getTempFilePath());
-            }
+          out.print(output);
+          Console.trace("ALIGNFRAME about to close file");
+          out.close();
+          Console.trace("ALIGNFRAME closed file");
+          AlignFrame.this.setTitle(file);
+          statusBar.setText(MessageManager.formatMessage(
+                  "label.successfully_saved_to_file_in_format", new Object[]
+                  { fileName, format.getName() }));
+          lastSaveSuccessful = true;
+        } catch (IOException e)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something happened writing the temp file");
+          Console.error(e.getMessage());
+          Console.debug(Cache.getStackTraceString(e));
+        } catch (Exception ex)
+        {
+          lastSaveSuccessful = false;
+          Console.error(
+                  "ALIGNFRAME Something unexpected happened writing the temp file");
+          Console.error(ex.getMessage());
+          Console.debug(Cache.getStackTraceString(ex));
+        }
 
-            out.print(output);
-            Console.trace("ALIGNFRAME about to close file");
-            out.close();
-            Console.trace("ALIGNFRAME closed file");
-            AlignFrame.this.setTitle(file);
-            statusBar.setText(MessageManager.formatMessage(
-                    "label.successfully_saved_to_file_in_format",
-                    new Object[]
-                    { fileName, format.getName() }));
-            lastSaveSuccessful = true;
-          } catch (IOException e)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something happened writing the temp file");
-            Console.error(e.getMessage());
-            Console.debug(Cache.getStackTraceString(e));
-          } catch (Exception ex)
-          {
-            lastSaveSuccessful = false;
-            Console.error(
-                    "ALIGNFRAME Something unexpected happened writing the temp file");
-            Console.error(ex.getMessage());
-            Console.debug(Cache.getStackTraceString(ex));
-          }
+        if (doBackup)
+        {
+          backupfiles.setWriteSuccess(lastSaveSuccessful);
+          Console.debug("ALIGNFRAME writing temp file was "
+                  + (lastSaveSuccessful ? "" : "NOT ") + "successful");
+          // do the backup file roll and rename the temp file to actual file
+          Console.trace("ALIGNFRAME about to rollBackupsAndRenameTempFile");
+          lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
+          Console.debug("ALIGNFRAME performed rollBackupsAndRenameTempFile "
+                  + (lastSaveSuccessful ? "" : "un") + "successfully");
+        }
 
-          if (doBackup)
-          {
-            backupfiles.setWriteSuccess(lastSaveSuccessful);
-            Console.debug("ALIGNFRAME writing temp file was "
-                    + (lastSaveSuccessful ? "" : "NOT ") + "successful");
-            // do the backup file roll and rename the temp file to actual file
-            Console.trace(
-                    "ALIGNFRAME about to rollBackupsAndRenameTempFile");
-            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
-            Console.debug(
-                    "ALIGNFRAME performed rollBackupsAndRenameTempFile "
-                            + (lastSaveSuccessful ? "" : "un")
-                            + "successfully");
-          }
+        Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
+        if (lastSaveSuccessful)
+        {
+          AlignFrame.this.getViewport().setSavedUpToDate(true);
         }
       }
+      return null;
     };
 
     /*
@@ -1374,7 +1373,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1391,34 +1397,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     FileFormatI fileFormat = FileFormats.getInstance()
             .forName(fileFormatName);
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
-    Runnable outputAction = new Runnable()
-    {
-      @Override
-      public void run()
+    Callable<Void> outputAction = () -> {
+      // todo defer this to inside formatSequences (or later)
+      AlignmentExportData exportData = viewport.getAlignExportData(options);
+      CutAndPasteTransfer cap = new CutAndPasteTransfer();
+      cap.setForInput(null);
+      try
       {
-        // todo defer this to inside formatSequences (or later)
-        AlignmentExportData exportData = viewport
-                .getAlignExportData(options);
-        CutAndPasteTransfer cap = new CutAndPasteTransfer();
-        cap.setForInput(null);
-        try
-        {
-          FileFormatI format = fileFormat;
-          cap.setText(new FormatAdapter(alignPanel, options)
-                  .formatSequences(format, exportData.getAlignment(),
-                          exportData.getOmitHidden(),
-                          exportData.getStartEndPostions(),
-                          viewport.getAlignment().getHiddenColumns()));
-          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-                  "label.alignment_output_command", new Object[]
-                  { fileFormat.getName() }), 600, 500);
-        } catch (OutOfMemoryError oom)
-        {
-          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
-                  oom);
-          cap.dispose();
-        }
+        FileFormatI format = fileFormat;
+        cap.setText(new FormatAdapter(alignPanel, options).formatSequences(
+                format, exportData.getAlignment(),
+                exportData.getOmitHidden(),
+                exportData.getStartEndPostions(),
+                viewport.getAlignment().getHiddenColumns()));
+        Desktop.addInternalFrame(cap, MessageManager.formatMessage(
+                "label.alignment_output_command", new Object[]
+                { fileFormat.getName() }), 600, 500);
+      } catch (OutOfMemoryError oom)
+      {
+        new OOMWarning("Outputting alignment as " + fileFormat.getName(),
+                oom);
+        cap.dispose();
       }
+      return null;
     };
 
     /*
@@ -1433,7 +1434,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      outputAction.run();
+      try
+      {
+        outputAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -1541,15 +1548,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .getString("label.load_jalview_annotations");
     chooser.setDialogTitle(tooltip);
     chooser.setToolTipText(tooltip);
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
+      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -2471,36 +2474,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Runnable okAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        SequenceI[] cut = sg.getSequences()
-                .toArray(new SequenceI[sg.getSize()]);
+    Callable okAction = () -> {
+      SequenceI[] cut = sg.getSequences()
+              .toArray(new SequenceI[sg.getSize()]);
 
-        addHistoryItem(new EditCommand(
-                MessageManager.getString("label.cut_sequences"), Action.CUT,
-                cut, sg.getStartRes(),
-                sg.getEndRes() - sg.getStartRes() + 1,
-                viewport.getAlignment()));
+      addHistoryItem(new EditCommand(
+              MessageManager.getString("label.cut_sequences"), Action.CUT,
+              cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
+              viewport.getAlignment()));
 
-        viewport.setSelectionGroup(null);
-        viewport.sendSelection();
-        viewport.getAlignment().deleteGroup(sg);
+      viewport.setSelectionGroup(null);
+      viewport.sendSelection();
+      viewport.getAlignment().deleteGroup(sg);
 
-        viewport.firePropertyChange("alignment", null,
-                viewport.getAlignment().getSequences());
-        if (viewport.getAlignment().getHeight() < 1)
+      viewport.firePropertyChange("alignment", null,
+              viewport.getAlignment().getSequences());
+      if (viewport.getAlignment().getHeight() < 1)
+      {
+        try
+        {
+          AlignFrame.this.setClosed(true);
+        } catch (Exception ex)
         {
-          try
-          {
-            AlignFrame.this.setClosed(true);
-          } catch (Exception ex)
-          {
-          }
         }
       }
+      return null;
     };
 
     /*
@@ -2524,7 +2522,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     else
     {
-      okAction.run();
+      try
+      {
+        okAction.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
@@ -4078,36 +4082,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setToolTipText(
             MessageManager.getString("label.load_tree_file"));
 
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
+    chooser.setResponseHandler(0, () -> {
+      String filePath = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", filePath);
+      NewickFile fin = null;
+      try
       {
-        String filePath = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", filePath);
-        NewickFile fin = null;
-        try
-        {
-          fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
-                  DataSourceType.FILE));
-          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
-        } catch (Exception ex)
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
-                  MessageManager
-                          .getString("label.problem_reading_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-          ex.printStackTrace();
-        }
-        if (fin != null && fin.hasWarningMessage())
-        {
-          JvOptionPane.showMessageDialog(Desktop.desktop,
-                  fin.getWarningMessage(),
-                  MessageManager.getString(
-                          "label.possible_problem_with_tree_file"),
-                  JvOptionPane.WARNING_MESSAGE);
-        }
+        fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
+                DataSourceType.FILE));
+        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
+      } catch (Exception ex)
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
+                MessageManager.getString("label.problem_reading_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
+        ex.printStackTrace();
       }
+      if (fin != null && fin.hasWarningMessage())
+      {
+        JvOptionPane.showMessageDialog(Desktop.desktop,
+                fin.getWarningMessage(),
+                MessageManager
+                        .getString("label.possible_problem_with_tree_file"),
+                JvOptionPane.WARNING_MESSAGE);
+      }
+      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -5879,16 +5878,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
     chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
     final AlignFrame us = this;
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        String choice = chooser.getSelectedFile().getPath();
-        Cache.setProperty("LAST_DIRECTORY", choice);
-        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
-        new VCFLoader(choice).loadVCF(seqs, us);
-      }
+    chooser.setResponseHandler(0, () -> {
+      String choice = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", choice);
+      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
+      new VCFLoader(choice).loadVCF(seqs, us);
+      return null;
     });
     chooser.showOpenDialog(null);
 
index 30ccdbe..e7c237e 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.api.AlignViewportI;
@@ -53,17 +64,6 @@ import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.AutoCalcSetting;
 
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.swing.JInternalFrame;
-
 /**
  * DOCUMENT ME!
  * 
@@ -748,27 +748,15 @@ public class AlignViewport extends AlignmentViewport
      * in reverse order)
      */
     JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
-            .setResponseHandler(0, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                addDataToAlignment(al);
-              }
-            }).setResponseHandler(1, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, true);
-              }
-            }).setResponseHandler(2, new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                us.openLinkedAlignmentAs(al, title, false);
-              }
+            .setResponseHandler(0, () -> {
+              addDataToAlignment(al);
+              return null;
+            }).setResponseHandler(1, () -> {
+              us.openLinkedAlignmentAs(al, title, true);
+              return null;
+            }).setResponseHandler(2, () -> {
+              us.openLinkedAlignmentAs(al, title, false);
+              return null;
             });
     dialog.showDialog(question,
             MessageManager.getString("label.open_split_window"),
index 9976604..4702f2a 100755 (executable)
  */
 package jalview.gui;
 
-import java.util.Locale;
-
-import jalview.analysis.AlignSeq;
-import jalview.analysis.AlignmentUtils;
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.io.FileFormat;
-import jalview.io.FormatAdapter;
-import jalview.util.Comparison;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-
 import java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Dimension;
@@ -56,6 +39,7 @@ import java.awt.geom.AffineTransform;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Locale;
 
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JMenuItem;
@@ -64,6 +48,21 @@ import javax.swing.JPopupMenu;
 import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentUtils;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.io.FileFormat;
+import jalview.io.FormatAdapter;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
 /**
  * The panel that holds the labels for alignment annotations, providing
  * tooltips, context menus, drag to reorder rows, and drag to adjust panel
@@ -293,25 +292,21 @@ public class AnnotationLabels extends JPanel
     EditNameDialog dialog = new EditNameDialog(annotation.label,
             annotation.description, name, description);
 
-    dialog.showDialog(ap.alignFrame, title, new Runnable()
-    {
-      @Override
-      public void run()
+    dialog.showDialog(ap.alignFrame, title, () -> {
+      annotation.label = dialog.getName();
+      String text = dialog.getDescription();
+      if (text != null && text.length() == 0)
       {
-        annotation.label = dialog.getName();
-        String text = dialog.getDescription();
-        if (text != null && text.length() == 0)
-        {
-          text = null;
-        }
-        annotation.description = text;
-        if (addNew)
-        {
-          ap.av.getAlignment().addAnnotation(annotation);
-          ap.av.getAlignment().setAnnotationIndex(annotation, 0);
-        }
-        ap.refresh(true);
+        text = null;
       }
+      annotation.description = text;
+      if (addNew)
+      {
+        ap.av.getAlignment().addAnnotation(annotation);
+        ap.av.getAlignment().setAnnotationIndex(annotation, 0);
+      }
+      ap.refresh(true);
+      return null;
     });
   }
 
index 9cf2cc9..5a23048 100644 (file)
@@ -114,6 +114,7 @@ public class Console extends WindowAdapter
     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
     frame = initFrame("Java Console", screenSize.width / 2,
             screenSize.height / 2, -1, -1);
+    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     initConsole(true);
   }
 
@@ -551,10 +552,12 @@ public class Console extends WindowAdapter
       {
       }
     }
+    /*
     if (!frame.isVisible())
     {
       frame.dispose();
     }
+    */
     // System.exit(0);
   }
 
index 8ed4261..6a67148 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;
@@ -461,13 +465,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
       }
     });
 
@@ -581,15 +586,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()
     {
@@ -1190,36 +1186,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);
   }
@@ -1275,64 +1267,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");
@@ -1362,39 +1350,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);
   }
 
@@ -1804,7 +1832,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     saveState_actionPerformed(true);
   }
 
-  private void setProjectFile(File choice)
+  protected void setProjectFile(File choice)
   {
     this.projectFile = choice;
   }
@@ -1832,42 +1860,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);
@@ -2531,7 +2554,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        desktopQuit();
       }
     });
   }
index f7225f5..ff0fe3a 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import java.awt.FlowLayout;
 import java.awt.Font;
+import java.util.concurrent.Callable;
 
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
@@ -111,7 +112,7 @@ public class EditNameDialog
    * 
    * @param action
    */
-  public void showDialog(JComponent parent, String title, Runnable action)
+  public void showDialog(JComponent parent, String title, Callable action)
   {
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
index 844eee4..ba9da67 100644 (file)
@@ -33,6 +33,7 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
@@ -427,8 +428,9 @@ public class FeatureEditor
    */
   public void showDialog()
   {
-    Runnable okAction = forCreate ? getCreateAction() : getAmendAction();
-    Runnable cancelAction = getCancelAction();
+    Callable<Void> okAction = forCreate ? getCreateAction()
+            : getAmendAction();
+    Callable<Void> cancelAction = getCancelAction();
 
     /*
      * set dialog action handlers for OK (create/Amend) and Cancel options
@@ -474,16 +476,12 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getCancelAction()
+  protected Callable getCancelAction()
   {
-    Runnable okAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        ap.highlightSearchResults(null);
-        ap.paintAlignment(false, false);
-      }
+    Callable<Void> okAction = () -> {
+      ap.highlightSearchResults(null);
+      ap.paintAlignment(false, false);
+      return null;
     };
     return okAction;
   }
@@ -498,14 +496,14 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getCreateAction()
+  protected Callable getCreateAction()
   {
-    Runnable okAction = new Runnable()
+    Callable<Void> okAction = new Callable()
     {
       boolean useLastDefaults = features.get(0).getType() == null;
 
       @Override
-      public void run()
+      public Void call()
       {
         final String enteredType = name.getText().trim();
         final String enteredGroup = group.getText().trim();
@@ -545,6 +543,7 @@ public class FeatureEditor
 
           repaintPanel();
         }
+        return null;
       }
     };
     return okAction;
@@ -557,19 +556,15 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getDeleteAction()
+  protected Callable getDeleteAction()
   {
-    Runnable deleteAction = new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        SequenceFeature sf = features.get(featureIndex);
-        sequences.get(0).getDatasetSequence().deleteFeature(sf);
-        fr.featuresAdded();
-        ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
-        ap.paintAlignment(true, true);
-      }
+    Callable<Void> deleteAction = () -> {
+      SequenceFeature sf = features.get(featureIndex);
+      sequences.get(0).getDatasetSequence().deleteFeature(sf);
+      fr.featuresAdded();
+      ap.getSeqPanel().seqCanvas.highlightSearchResults(null);
+      ap.paintAlignment(true, true);
+      return null;
     };
     return deleteAction;
   }
@@ -660,9 +655,9 @@ public class FeatureEditor
    * 
    * @return
    */
-  protected Runnable getAmendAction()
+  protected Callable getAmendAction()
   {
-    Runnable okAction = new Runnable()
+    Callable<Void> okAction = new Callable()
     {
       boolean useLastDefaults = features.get(0).getType() == null;
 
@@ -671,7 +666,7 @@ public class FeatureEditor
       String featureGroup = group.getText();
 
       @Override
-      public void run()
+      public Void call()
       {
         final String enteredType = name.getText().trim();
         final String enteredGroup = group.getText().trim();
@@ -734,6 +729,7 @@ public class FeatureEditor
           fr.featuresAdded();
         }
         repaintPanel();
+        return null;
       }
     };
     return okAction;
index e8e6f1a..2c8f47a 100644 (file)
@@ -948,14 +948,10 @@ public class FeatureSettings extends JPanel
     chooser.setDialogTitle(
             MessageManager.getString("label.load_feature_colours"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File file = chooser.getSelectedFile();
-        load(file);
-      }
+    chooser.setResponseHandler(0, () -> {
+      File file = chooser.getSelectedFile();
+      load(file);
+      return null;
     });
     chooser.showOpenDialog(this);
   }
index ce1cb46..d849ba2 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Component;
+import java.awt.Graphics;
+import java.io.File;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.io.JalviewFileChooser;
@@ -29,11 +35,6 @@ import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
-import java.awt.Component;
-import java.awt.Graphics;
-import java.io.File;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 /**
  * A class that marshals steps in exporting a view in image graphics format
  * <ul>
@@ -155,25 +156,22 @@ public class ImageExporter
             && !Jalview.isHeadlessMode())
     {
       final File chosenFile = file;
-      Runnable okAction = new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          exportImage(chosenFile, !textSelected.get(), width, height,
-                  messageId);
-        }
+      Callable<Void> okAction = () -> {
+        exportImage(chosenFile, !textSelected.get(), width, height,
+                messageId);
+        return null;
       };
       LineartOptions epsOption = new LineartOptions(TYPE.EPS.getName(),
               textSelected);
-      epsOption.setResponseAction(1, new Runnable()
+      epsOption.setResponseAction(1, new Callable<Void>()
       {
         @Override
-        public void run()
+        public Void call()
         {
           setStatus(MessageManager.formatMessage(
                   "status.cancelled_image_export_operation",
                   imageType.getName()), messageId);
+          return null;
         }
       });
       epsOption.setResponseAction(0, okAction);
index 5d69a53..0e0b13d 100644 (file)
@@ -18,7 +18,6 @@
  * 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;
@@ -42,15 +41,18 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JDialog;
+import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 import javax.swing.event.InternalFrameEvent;
 import javax.swing.event.InternalFrameListener;
@@ -70,11 +72,11 @@ 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;
 
@@ -771,6 +773,11 @@ public class JvOptionPane extends JOptionPane
    * @param string2
    * @return
    */
+  public static JvOptionPane newOptionDialog()
+  {
+    return new JvOptionPane(null);
+  }
+
   public static JvOptionPane newOptionDialog(Component parentComponent)
   {
     return new JvOptionPane(parentComponent);
@@ -783,13 +790,22 @@ public class JvOptionPane extends JOptionPane
             initialValue, true);
   }
 
-  public void showDialog(String message, String title, int optionType,
+  public void showDialog(Object message, String title, int optionType,
           int messageType, Icon icon, Object[] options, Object initialValue,
           boolean modal)
   {
+    showDialog(message, title, optionType, messageType, icon, options,
+            initialValue, modal, null);
+  }
+
+  public void showDialog(Object message, String title, int optionType,
+          int messageType, Icon icon, Object[] options, Object initialValue,
+          boolean modal, JButton[] buttons)
+  {
     if (!isInteractiveMode())
     {
       handleResponse(getMockResponse());
+      return;
     }
     // two uses:
     //
@@ -810,13 +826,102 @@ public class JvOptionPane extends JOptionPane
 
     if (modal)
     {
+      boolean useButtons = false;
+      Object initialValueButton = null;
+      NOTNULL: if (buttons != null)
+      {
+        if (buttons.length != options.length)
+        {
+          jalview.bin.Console.error(
+                  "Supplied buttons array not the same length as supplied options array.");
+          break NOTNULL;
+        }
+        int[] buttonActions = { JOptionPane.YES_OPTION,
+            JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
+        for (int i = 0; i < options.length; i++)
+        {
+          Object o = options[i];
+          jalview.bin.Console.debug(
+                  "Setting button " + i + " to '" + o.toString() + "'");
+          JButton jb = buttons[i];
+
+          if (o.equals(initialValue))
+            initialValueButton = jb;
+
+          int buttonAction = buttonActions[i];
+          Callable<Void> action = callbacks.get(buttonAction);
+          jb.setText((String) o);
+          jb.addActionListener(new ActionListener()
+          {
+            @Override
+            public void actionPerformed(ActionEvent e)
+            {
+
+              Object obj = e.getSource();
+              if (obj == null || !(obj instanceof Component))
+              {
+                jalview.bin.Console.debug(
+                        "Could not find Component source of event object "
+                                + obj);
+                return;
+              }
+              Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
+                      JOptionPane.class, (Component) obj);
+              if (joptionpaneObject == null
+                      || !(joptionpaneObject instanceof JOptionPane))
+              {
+                jalview.bin.Console.debug(
+                        "Could not find JOptionPane ancestor of event object "
+                                + obj);
+                return;
+              }
+              JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
+              joptionpane.setValue(buttonAction);
+              if (action != null)
+                Executors.newSingleThreadExecutor().submit(action);
+              joptionpane.transferFocusBackward();
+              joptionpane.setVisible(false);
+              // put focus and raise parent window if possible, unless cancel or
+              // no button pressed
+              boolean raiseParent = (parentComponent != null);
+              if (buttonAction == JOptionPane.CANCEL_OPTION)
+                raiseParent = false;
+              if (optionType == JOptionPane.YES_NO_OPTION
+                      && buttonAction == JOptionPane.NO_OPTION)
+                raiseParent = false;
+              if (raiseParent)
+              {
+                parentComponent.requestFocus();
+                if (parentComponent instanceof JInternalFrame)
+                {
+                  JInternalFrame jif = (JInternalFrame) parentComponent;
+                  jif.show();
+                  jif.moveToFront();
+                  jif.grabFocus();
+                }
+                else if (parentComponent instanceof Window)
+                {
+                  Window w = (Window) parentComponent;
+                  w.toFront();
+                  w.requestFocus();
+                }
+              }
+              joptionpane.setVisible(false);
+            }
+          });
+
+        }
+        useButtons = true;
+      }
       // use a JOptionPane as usual
       int response = JOptionPane.showOptionDialog(parentComponent, message,
-              title, optionType, messageType, icon, options, initialValue);
+              title, optionType, messageType, icon,
+              useButtons ? buttons : options,
+              useButtons ? initialValueButton : initialValue);
 
       /*
-       * In Java, the response is returned to this thread and handled here;
-       * (for Javascript, see propertyChange)
+       * In Java, the response is returned to this thread and handled here; (for
+       * Javascript, see propertyChange)
        */
       if (!Platform.isJS())
       /**
@@ -831,9 +936,9 @@ public class JvOptionPane extends JOptionPane
     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
@@ -869,7 +974,7 @@ public class JvOptionPane extends JOptionPane
         {
           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()
@@ -879,7 +984,7 @@ public class JvOptionPane extends JOptionPane
             {
               joptionpane.setValue(buttonAction);
               if (action != null)
-                Executors.defaultThreadFactory().newThread(action).start();
+                Executors.newSingleThreadExecutor().submit(action);
               // joptionpane.transferFocusBackward();
               joptionpane.transferFocusBackward();
               joptionpane.setVisible(false);
@@ -952,6 +1057,7 @@ public class JvOptionPane extends JOptionPane
     this.setMessage(mainPanel);
 
     ourOptions = Arrays.asList(options);
+    int response;
     if (parentComponent != this)
     {
       JInternalFrame jif = this.createInternalFrame(parentComponent, title);
@@ -1024,14 +1130,105 @@ 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;
   }
 
   /**
+   * 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)
+  {
+    if (!isInteractiveMode())
+    {
+      return (int) getMockResponse();
+    }
+    // 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.setIconImages(ChannelProperties.getIconList());
+    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.setIconImages(ChannelProperties.getIconList());
+    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)
+  {
+    if (!isInteractiveMode())
+    {
+      handleResponse(getMockResponse());
+      return;
+    }
+    // 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.
@@ -1058,18 +1255,175 @@ public class JvOptionPane extends JOptionPane
   public void handleResponse(Object response)
   {
     /*
-    * this test is for NaN in Chrome
-    */
+     * this test is for NaN in Chrome
+     */
     if (response != null && !response.equals(response))
     {
       return;
     }
-    Runnable action = callbacks.get(response);
+    Callable<Void> action = callbacks.get(response);
     if (action != null)
     {
-      action.run();
-      parentComponent.requestFocus();
+      try
+      {
+        action.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
+      if (parentComponent != null)
+        parentComponent.requestFocus();
+    }
+  }
+
+  /**
+   * Create a non-modal confirm dialog
+   */
+  public JDialog createDialog(Component parentComponent, Object message,
+          String title, int optionType, int messageType, Icon icon,
+          Object[] options, Object initialValue, boolean modal)
+  {
+    return createDialog(parentComponent, message, title, optionType,
+            messageType, icon, options, initialValue, modal, null);
+  }
+
+  public JDialog createDialog(Component parentComponent, Object message,
+          String title, int optionType, int messageType, Icon icon,
+          Object[] options, Object initialValue, boolean modal,
+          JButton[] buttons)
+  {
+    if (!isInteractiveMode())
+    {
+      handleResponse(getMockResponse());
+      return null;
+    }
+    JButton[] optionsButtons = null;
+    Object initialValueButton = null;
+    JOptionPane joptionpane = new JOptionPane();
+    // Make button options
+    int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
+        JOptionPane.CANCEL_OPTION };
+
+    // we need the strings to make the buttons with actionEventListener
+    if (options == null)
+    {
+      ArrayList<String> options_default = new ArrayList<>();
+      options_default.add(UIManager.getString("OptionPane.yesButtonText"));
+      if (optionType == JOptionPane.YES_NO_OPTION
+              || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
+      {
+        options_default.add(UIManager.getString("OptionPane.noButtonText"));
+      }
+      if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
+      {
+        options_default
+                .add(UIManager.getString("OptionPane.cancelButtonText"));
+      }
+      options = options_default.toArray();
+    }
+    if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
+                          // add them here
+    {
+      if (((optionType == JOptionPane.YES_OPTION
+              || optionType == JOptionPane.NO_OPTION
+              || optionType == JOptionPane.CANCEL_OPTION
+              || optionType == JOptionPane.OK_OPTION
+              || optionType == JOptionPane.DEFAULT_OPTION)
+              && options.length < 1)
+              || ((optionType == JOptionPane.YES_NO_OPTION
+                      || optionType == JOptionPane.OK_CANCEL_OPTION)
+                      && options.length < 2)
+              || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
+                      && options.length < 3))
+      {
+        jalview.bin.Console
+                .debug("JvOptionPane: not enough options for dialog type");
+      }
+      optionsButtons = new JButton[options.length];
+      for (int i = 0; i < options.length && i < 3; i++)
+      {
+        Object o = options[i];
+        int buttonAction = buttonActions[i];
+        Callable<Void> action = callbacks.get(buttonAction);
+        JButton jb;
+        if (buttons != null && buttons.length > i && buttons[i] != null)
+        {
+          jb = buttons[i];
+        }
+        else
+        {
+          jb = new JButton();
+        }
+        jb.setText((String) o);
+        jb.addActionListener(new ActionListener()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            joptionpane.setValue(buttonAction);
+            if (action != null)
+              Executors.newSingleThreadExecutor().submit(action);
+            // joptionpane.transferFocusBackward();
+            joptionpane.transferFocusBackward();
+            joptionpane.setVisible(false);
+            // put focus and raise parent window if possible, unless cancel
+            // button pressed
+            boolean raiseParent = (parentComponent != null);
+            if (buttonAction == JOptionPane.CANCEL_OPTION)
+              raiseParent = false;
+            if (optionType == JOptionPane.YES_NO_OPTION
+                    && buttonAction == JOptionPane.NO_OPTION)
+              raiseParent = false;
+            if (raiseParent)
+            {
+              parentComponent.requestFocus();
+              if (parentComponent instanceof JInternalFrame)
+              {
+                JInternalFrame jif = (JInternalFrame) parentComponent;
+                jif.show();
+                jif.moveToFront();
+                jif.grabFocus();
+              }
+              else if (parentComponent instanceof Window)
+              {
+                Window w = (Window) parentComponent;
+                w.toFront();
+                w.requestFocus();
+              }
+            }
+            joptionpane.setVisible(false);
+          }
+        });
+        optionsButtons[i] = jb;
+        if (o.equals(initialValue))
+          initialValueButton = jb;
+      }
     }
+    joptionpane.setMessage(message);
+    joptionpane.setMessageType(messageType);
+    joptionpane.setOptionType(optionType);
+    joptionpane.setIcon(icon);
+    joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
+    joptionpane.setInitialValue(
+            Platform.isJS() ? initialValue : initialValueButton);
+
+    JDialog dialog = joptionpane.createDialog(parentComponent, title);
+    dialog.setIconImages(ChannelProperties.getIconList());
+    dialog.setModalityType(
+            modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
+    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+    return dialog;
+  }
+
+  /**
+   * Utility to programmatically click a button on a JOptionPane (as a JFrame)
+   * 
+   * returns true if button was found
+   */
+  public static boolean clickButton(JFrame frame, int buttonType)
+  {
+
+    return false;
   }
 
   /**
index d55733c..8a530ac 100644 (file)
  */
 package jalview.gui;
 
-import jalview.bin.Cache;
-import jalview.util.MessageManager;
-
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.swing.BorderFactory;
@@ -35,6 +33,9 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 
+import jalview.bin.Cache;
+import jalview.util.MessageManager;
+
 /**
  * A dialog where the user may choose Text or Lineart rendering, and optionally
  * save this as a preference ("Don't ask me again")
@@ -95,7 +96,7 @@ public class LineartOptions extends JPanel
    * 
    * @param action
    */
-  public void setResponseAction(Object response, Runnable action)
+  public void setResponseAction(Object response, Callable action)
   {
     dialog.setResponseHandler(response, action);
   }
index f4a19ff..1c03d6a 100644 (file)
@@ -1986,15 +1986,11 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             MessageManager.getString("label.group_description"));
     dialog.showDialog(ap.alignFrame,
             MessageManager.getString("label.edit_group_name_description"),
-            new Runnable()
-            {
-              @Override
-              public void run()
-              {
-                sg.setName(dialog.getName());
-                sg.setDescription(dialog.getDescription());
-                refresh();
-              }
+            () -> {
+              sg.setName(dialog.getName());
+              sg.setDescription(dialog.getDescription());
+              refresh();
+              return null;
             });
   }
 
@@ -2026,30 +2022,26 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
             sequence.getDescription(),
             MessageManager.getString("label.sequence_name"),
             MessageManager.getString("label.sequence_description"));
-    dialog.showDialog(ap.alignFrame, MessageManager.getString(
-            "label.edit_sequence_name_description"), new Runnable()
-            {
-              @Override
-              public void run()
+    dialog.showDialog(ap.alignFrame, MessageManager
+            .getString("label.edit_sequence_name_description"), () -> {
+              if (dialog.getName() != null)
               {
-                if (dialog.getName() != null)
+                if (dialog.getName().indexOf(" ") > -1)
                 {
-                  if (dialog.getName().indexOf(" ") > -1)
-                  {
-                    JvOptionPane.showMessageDialog(ap,
-                            MessageManager.getString(
-                                    "label.spaces_converted_to_underscores"),
-                            MessageManager.getString(
-                                    "label.no_spaces_allowed_sequence_name"),
-                            JvOptionPane.WARNING_MESSAGE);
-                  }
-                  sequence.setName(dialog.getName().replace(' ', '_'));
-                  ap.paintAlignment(false, false);
+                  JvOptionPane.showMessageDialog(ap,
+                          MessageManager.getString(
+                                  "label.spaces_converted_to_underscores"),
+                          MessageManager.getString(
+                                  "label.no_spaces_allowed_sequence_name"),
+                          JvOptionPane.WARNING_MESSAGE);
                 }
-                sequence.setDescription(dialog.getDescription());
-                ap.av.firePropertyChange("alignment", null,
-                        ap.av.getAlignment().getSequences());
+                sequence.setName(dialog.getName().replace(' ', '_'));
+                ap.paintAlignment(false, false);
               }
+              sequence.setDescription(dialog.getDescription());
+              ap.av.firePropertyChange("alignment", null,
+                      ap.av.getAlignment().getSequences());
+              return null;
             });
   }
 
@@ -2271,25 +2263,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
               seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1),
               null, MessageManager.getString("label.edit_sequence"), null);
       dialog.showDialog(ap.alignFrame,
-              MessageManager.getString("label.edit_sequence"),
-              new Runnable()
-              {
-                @Override
-                public void run()
-                {
-                  EditCommand editCommand = new EditCommand(
-                          MessageManager.getString("label.edit_sequences"),
-                          Action.REPLACE,
-                          dialog.getName().replace(' ',
-                                  ap.av.getGapCharacter()),
-                          sg.getSequencesAsArray(
-                                  ap.av.getHiddenRepSequences()),
-                          sg.getStartRes(), sg.getEndRes() + 1,
-                          ap.av.getAlignment());
-                  ap.alignFrame.addHistoryItem(editCommand);
-                  ap.av.firePropertyChange("alignment", null,
-                          ap.av.getAlignment().getSequences());
-                }
+              MessageManager.getString("label.edit_sequence"), () -> {
+                EditCommand editCommand = new EditCommand(
+                        MessageManager.getString("label.edit_sequences"),
+                        Action.REPLACE,
+                        dialog.getName().replace(' ',
+                                ap.av.getGapCharacter()),
+                        sg.getSequencesAsArray(
+                                ap.av.getHiddenRepSequences()),
+                        sg.getStartRes(), sg.getEndRes() + 1,
+                        ap.av.getAlignment());
+                ap.alignFrame.addHistoryItem(editCommand);
+                ap.av.firePropertyChange("alignment", null,
+                        ap.av.getAlignment().getSequences());
+                return null;
               });
     }
   }
diff --git a/src/jalview/gui/QuitHandler.java b/src/jalview/gui/QuitHandler.java
new file mode 100644 (file)
index 0000000..77eed81
--- /dev/null
@@ -0,0 +1,411 @@
+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 = 1000;
+
+  private static final int MAX_WAIT_FOR_SAVE = 20000;
+
+  private static boolean interactive = true;
+
+  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;
+
+  protected 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.CANCEL_QUIT);
+    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);
+  }
+
+  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, cancelQuit)
+              .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
+      Console.debug("Cancelling quit.  Resetting response to NULL");
+      setResponse(QResponse.NULL);
+      // but return cancel
+      return QResponse.CANCEL_QUIT;
+    }
+    else if (got == QResponse.QUIT)
+    {
+      if (Cache.getDefault("WAIT_FOR_SAVE", true)
+              && BackupFiles.hasSavesInProgress())
+      {
+        waitQuit(interactive, okQuit, forceQuit, cancelQuit);
+        QResponse waitResponse = gotQuitResponse();
+        wait = waitResponse == QResponse.QUIT;
+      }
+    }
+
+    Callable<Void> next = null;
+    switch (gotQuitResponse())
+    {
+    case QUIT:
+      next = okQuit;
+      break;
+    case FORCE_QUIT: // not actually an option at this stage
+      next = forceQuit;
+      break;
+    default:
+      next = cancelQuit;
+      break;
+    }
+    try
+    {
+      executor.submit(next).get();
+      got = gotQuitResponse();
+    } catch (InterruptedException | ExecutionException e)
+    {
+      jalview.bin.Console
+              .debug("Exception during quit handling (final choice)", e);
+    }
+    setResponse(got);
+
+    if (gotQuitResponse() == QResponse.CANCEL_QUIT)
+    {
+      // reset if cancelled
+      setResponse(QResponse.NULL);
+      return QResponse.CANCEL_QUIT;
+    }
+    return gotQuitResponse();
+  }
+
+  private static QResponse waitQuit(boolean interactive,
+          Callable<Void> okQuit, Callable<Void> forceQuit,
+          Callable<Void> cancelQuit)
+  {
+    // check for saves in progress
+    if (!BackupFiles.hasSavesInProgress())
+      return QResponse.QUIT;
+
+    int size = 0;
+    AlignFrame[] afArray = Desktop.getAlignFrames();
+    if (!(afArray == null || afArray.length == 0))
+    {
+      for (int i = 0; i < afArray.length; i++)
+      {
+        AlignFrame af = afArray[i];
+        List<? extends AlignmentViewPanel> avpList = af.getAlignPanels();
+        for (AlignmentViewPanel avp : avpList)
+        {
+          AlignmentI a = avp.getAlignment();
+          List<SequenceI> sList = a.getSequences();
+          for (SequenceI s : sList)
+          {
+            size += s.getLength();
+          }
+        }
+      }
+    }
+    int waitTime = Math.min(MAX_WAIT_FOR_SAVE,
+            Math.max(MIN_WAIT_FOR_SAVE, size / 2));
+    Console.debug("Set waitForSave to " + waitTime);
+
+    int iteration = 0;
+    boolean doIterations = true; // note iterations not used in the gui now,
+                                 // only one pass without the "Wait" button
+    while (doIterations && BackupFiles.hasSavesInProgress()
+            && iteration++ < (interactive ? 100 : 5))
+    {
+      // future that returns a Boolean when all files are saved
+      CompletableFuture<Boolean> filesAllSaved = new CompletableFuture<>();
+
+      // callback as each file finishes saving
+      for (CompletableFuture<Boolean> cf : BackupFiles
+              .savesInProgressCompletableFutures(false))
+      {
+        // if this is the last one then complete filesAllSaved
+        cf.whenComplete((ret, e) -> {
+          if (!BackupFiles.hasSavesInProgress())
+          {
+            filesAllSaved.complete(true);
+          }
+        });
+      }
+      try
+      {
+        filesAllSaved.get(waitTime, TimeUnit.MILLISECONDS);
+      } catch (InterruptedException | ExecutionException e1)
+      {
+        Console.debug(
+                "Exception whilst waiting for files to save before quit",
+                e1);
+      } catch (TimeoutException e2)
+      {
+        // this Exception to be expected
+      }
+
+      if (interactive && BackupFiles.hasSavesInProgress())
+      {
+        boolean showForceQuit = iteration > 0; // iteration > 1 to not show
+                                               // force quit the first time
+        JFrame parent = new JFrame();
+        JButton[] buttons = { new JButton(), new JButton() };
+        JvOptionPane waitDialog = JvOptionPane.newOptionDialog();
+        JTextPane messagePane = new JTextPane();
+        messagePane.setBackground(waitDialog.getBackground());
+        messagePane.setBorder(null);
+        messagePane.setText(waitingForSaveMessage());
+        // callback as each file finishes saving
+        for (CompletableFuture<Boolean> cf : BackupFiles
+                .savesInProgressCompletableFutures(false))
+        {
+          cf.whenComplete((ret, e) -> {
+            if (BackupFiles.hasSavesInProgress())
+            {
+              // update the list of saving files as they save too
+              messagePane.setText(waitingForSaveMessage());
+            }
+            else
+            {
+              if (!(QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT
+                      || QuitHandler.gotQuitResponse() == QResponse.NULL))
+              {
+                for (int i = 0; i < buttons.length; i++)
+                {
+                  Console.debug("DISABLING BUTTON " + buttons[i].getText());
+                  buttons[i].setEnabled(false);
+                  buttons[i].setVisible(false);
+                }
+                // if this is the last one then close the dialog
+                messagePane.setText(new StringBuilder()
+                        .append(MessageManager.getString("label.all_saved"))
+                        .append("\n")
+                        .append(MessageManager
+                                .getString("label.quitting_bye"))
+                        .toString());
+                messagePane.setEditable(false);
+                try
+                {
+                  Thread.sleep(1500);
+                } catch (InterruptedException e1)
+                {
+                }
+                parent.dispose();
+              }
+            }
+          });
+        }
+
+        String[] options;
+        int dialogType = -1;
+        if (showForceQuit)
+        {
+          options = new String[2];
+          options[0] = MessageManager.getString("action.force_quit");
+          options[1] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_NO_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, forceQuit)
+                  .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit);
+        }
+        else
+        {
+          options = new String[1];
+          options[0] = MessageManager.getString("action.cancel_quit");
+          dialogType = JOptionPane.YES_OPTION;
+          waitDialog.setResponseHandler(JOptionPane.YES_OPTION, cancelQuit);
+        }
+        waitDialog.showDialogOnTopAsync(parent, messagePane,
+                MessageManager.getString("label.wait_for_save"), dialogType,
+                JOptionPane.WARNING_MESSAGE, null, options,
+                MessageManager.getString("action.cancel_quit"), true,
+                buttons);
+
+        parent.dispose();
+        final QResponse thisWaitResponse = gotQuitResponse();
+        switch (thisWaitResponse)
+        {
+        case QUIT: // wait -- do another iteration
+          break;
+        case FORCE_QUIT:
+          doIterations = false;
+          break;
+        case CANCEL_QUIT:
+          doIterations = false;
+          break;
+        case NULL: // already cancelled
+          doIterations = false;
+          break;
+        default:
+        }
+      } // end if interactive
+
+    } // end while wait iteration loop
+    return gotQuitResponse();
+  };
+
+  private static String waitingForSaveMessage()
+  {
+    StringBuilder messageSB = new StringBuilder();
+
+    messageSB.append(MessageManager.getString("label.save_in_progress"));
+    List<File> files = BackupFiles.savesInProgressFiles(false);
+    boolean any = files.size() > 0;
+    if (any)
+    {
+      for (File file : files)
+      {
+        messageSB.append("\n\u2022 ").append(file.getName());
+      }
+    }
+    else
+    {
+      messageSB.append(MessageManager.getString("label.unknown"));
+    }
+    messageSB.append("\n\n")
+            .append(MessageManager.getString("label.quit_after_saving"));
+    return messageSB.toString();
+  }
+
+  public static void abortQuit()
+  {
+    setResponse(QResponse.CANCEL_QUIT);
+  }
+}
\ No newline at end of file
index 07eec2b..dbd270f 100644 (file)
@@ -30,6 +30,7 @@ import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
 import javax.swing.JCheckBox;
@@ -316,53 +317,43 @@ public class StructureChooser extends GStructureChooser
     };
 
     // fetch db refs if OK pressed
-    final Runnable discoverCanonicalDBrefs = new Runnable()
-    {
-      @Override
-      public void run()
+    final Callable discoverCanonicalDBrefs = () -> {
+      btn_queryTDB.setEnabled(false);
+      populateSeqsWithoutSourceDBRef();
+
+      final int y = seqsWithoutSourceDBRef.size();
+      if (y > 0)
       {
-        btn_queryTDB.setEnabled(false);
-        populateSeqsWithoutSourceDBRef();
+        final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
+                .toArray(new SequenceI[y]);
+        DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
+                progressBar, new DbSourceProxy[]
+                { new jalview.ws.dbsources.Uniprot() }, null, false);
+        dbRefFetcher.addListener(afterDbRefFetch);
+        // ideally this would also gracefully run with callbacks
 
-        final int y = seqsWithoutSourceDBRef.size();
-        if (y > 0)
-        {
-          final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
-                  .toArray(new SequenceI[y]);
-          DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
-                  progressBar, new DbSourceProxy[]
-                  { new jalview.ws.dbsources.Uniprot() }, null, false);
-          dbRefFetcher.addListener(afterDbRefFetch);
-          // ideally this would also gracefully run with callbacks
-
-          dbRefFetcher.fetchDBRefs(true);
-        }
-        else
-        {
-          // call finished action directly
-          afterDbRefFetch.finished();
-        }
+        dbRefFetcher.fetchDBRefs(true);
       }
-
+      else
+      {
+        // call finished action directly
+        afterDbRefFetch.finished();
+      }
+      return null;
     };
-    final Runnable revertview = new Runnable()
-    {
-      @Override
-      public void run()
+    final Callable revertview = () -> {
+      if (lastSelected != null)
       {
-        if (lastSelected != null)
-        {
-          cmb_filterOption.setSelectedItem(lastSelected);
-        }
-      };
+        cmb_filterOption.setSelectedItem(lastSelected);
+      }
+      return null;
     };
     int threshold = Cache.getDefault("UNIPROT_AUTOFETCH_THRESHOLD",
             THRESHOLD_WARN_UNIPROT_FETCH_NEEDED);
     Console.debug("Using Uniprot fetch threshold of " + threshold);
     if (ignoreGui || seqsWithoutSourceDBRef.size() < threshold)
     {
-      Executors.defaultThreadFactory().newThread(discoverCanonicalDBrefs)
-              .start();
+      Executors.newSingleThreadExecutor().submit(discoverCanonicalDBrefs);
       return;
     }
     // need cancel and no to result in the discoverPDB action - mocked is
index 6c3f6d8..1e12f7f 100644 (file)
@@ -1281,6 +1281,9 @@ public abstract class StructureViewerBase extends GStructureViewer
         if (confirm == JvOptionPane.CANCEL_OPTION
                 || confirm == JvOptionPane.CLOSED_OPTION)
         {
+          // abort possible quit handling if CANCEL chosen
+          if (confirm == JvOptionPane.CANCEL_OPTION)
+            QuitHandler.abortQuit();
           return;
         }
         forceClose = confirm == JvOptionPane.YES_OPTION;
index f9ff337..e72a084 100644 (file)
@@ -27,6 +27,7 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import javax.swing.BorderFactory;
 import javax.swing.JLabel;
@@ -151,13 +152,10 @@ public class TextColourChooser
         MessageManager.getString("action.cancel") };
     String title = MessageManager
             .getString("label.adjust_foreground_text_colour_threshold");
-    Runnable action = new Runnable() // response for 1 = Cancel
+    Callable<Void> action = () -> // response for 1 = Cancel
     {
-      @Override
-      public void run()
-      {
-        restoreInitialSettings();
-      }
+      restoreInitialSettings();
+      return null;
     };
     JvOptionPane.newOptionDialog(alignPanel.alignFrame)
             .setResponseHandler(1, action).showInternalDialog(bigpanel,
index d252910..298b8b4 100755 (executable)
@@ -652,45 +652,41 @@ public class UserDefinedColours extends GUserDefinedColours
     chooser.setDialogTitle(
             MessageManager.getString("label.load_colour_scheme"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-    chooser.setResponseHandler(0, new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        File choice = chooser.getSelectedFile();
-        Cache.setProperty(LAST_DIRECTORY, choice.getParent());
-
-        UserColourScheme ucs = ColourSchemeLoader
-                .loadColourScheme(choice.getAbsolutePath());
-        Color[] colors = ucs.getColours();
-        schemeName.setText(ucs.getSchemeName());
+    chooser.setResponseHandler(0, () -> {
+      File choice = chooser.getSelectedFile();
+      Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 
-        if (ucs.getLowerCaseColours() != null)
-        {
-          caseSensitive.setSelected(true);
-          lcaseColour.setEnabled(true);
-          resetButtonPanel(true);
-          for (int i = 0; i < lowerCaseButtons.size(); i++)
-          {
-            JButton button = lowerCaseButtons.get(i);
-            button.setBackground(ucs.getLowerCaseColours()[i]);
-          }
-        }
-        else
-        {
-          caseSensitive.setSelected(false);
-          lcaseColour.setEnabled(false);
-          resetButtonPanel(false);
-        }
+      UserColourScheme ucs = ColourSchemeLoader
+              .loadColourScheme(choice.getAbsolutePath());
+      Color[] colors = ucs.getColours();
+      schemeName.setText(ucs.getSchemeName());
 
-        for (int i = 0; i < upperCaseButtons.size(); i++)
+      if (ucs.getLowerCaseColours() != null)
+      {
+        caseSensitive.setSelected(true);
+        lcaseColour.setEnabled(true);
+        resetButtonPanel(true);
+        for (int i = 0; i < lowerCaseButtons.size(); i++)
         {
-          JButton button = upperCaseButtons.get(i);
-          button.setBackground(colors[i]);
+          JButton button = lowerCaseButtons.get(i);
+          button.setBackground(ucs.getLowerCaseColours()[i]);
         }
+      }
+      else
+      {
+        caseSensitive.setSelected(false);
+        lcaseColour.setEnabled(false);
+        resetButtonPanel(false);
+      }
 
-        addNewColourScheme(choice.getPath());
+      for (int i = 0; i < upperCaseButtons.size(); i++)
+      {
+        JButton button = upperCaseButtons.get(i);
+        button.setBackground(colors[i]);
       }
+
+      addNewColourScheme(choice.getPath());
+      return null;
     });
 
     chooser.showOpenDialog(this);
index 2039d3c..9112042 100644 (file)
@@ -29,8 +29,14 @@ import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import jalview.bin.Cache;
 import jalview.bin.Console;
@@ -105,6 +111,117 @@ public class BackupFiles
 
   private static final String oldTempFileSuffix = "_oldfile_tobedeleted";
 
+  // thread pool used for completablefutures
+  private static final ExecutorService executorService = Executors
+          .newFixedThreadPool(3);
+
+  private static List<BackupFiles> savesInProgress = new ArrayList<>();
+
+  private CompletableFuture<Boolean> myFuture = null;
+
+  private boolean addSaveInProgress()
+  {
+    if (savesInProgress.contains(this))
+    {
+      return false;
+    }
+    else
+    {
+      this.setMyFuture();
+      savesInProgress.add(this);
+      return true;
+    }
+  }
+
+  private boolean removeSaveInProgress(boolean ret)
+  {
+    if (savesInProgress.contains(this))
+    {
+      this.getMyFuture().complete(ret);
+      // remove all occurrences
+      while (savesInProgress.remove(this))
+      {
+      }
+      return true;
+    }
+    return false;
+  }
+
+  private static CompletableFuture<Boolean> getNewFuture()
+  {
+    return new CompletableFuture<Boolean>()
+    {
+    };
+  }
+
+  private CompletableFuture<Boolean> getMyFuture()
+  {
+    return this.myFuture;
+  }
+
+  private void setMyFuture()
+  {
+    this.myFuture = getNewFuture();
+  }
+
+  public static boolean hasSavesInProgress()
+  {
+    boolean has = false;
+    for (CompletableFuture cf : savesInProgressCompletableFutures(true))
+    {
+      has |= !cf.isDone();
+    }
+    return has;
+  }
+
+  public static List<File> savesInProgressFiles(boolean all)
+  {
+    List<File> files = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        files.add(bfile.getFile());
+    }
+    return files;
+  }
+
+  public static List<CompletableFuture<Boolean>> savesInProgressCompletableFutures(
+          boolean all)
+  {
+    List<CompletableFuture<Boolean>> cfs = new ArrayList<>();
+    for (BackupFiles bfile : savesInProgress)
+    {
+      if (all || !bfile.getMyFuture().isDone())
+        cfs.add(bfile.getMyFuture());
+    }
+    return cfs;
+  }
+
+  public static Future<Boolean> allSaved()
+  {
+    CompletableFuture<Boolean> f = new CompletableFuture<>();
+
+    executorService.submit(() -> {
+      for (BackupFiles buf : savesInProgress)
+      {
+        boolean allSaved = true;
+        try
+        {
+          allSaved &= buf.getMyFuture().get();
+        } catch (InterruptedException e)
+        {
+          Console.debug("InterruptedException waiting for files to save",
+                  e);
+        } catch (ExecutionException e)
+        {
+          Console.debug("ExecutionException waiting for files to save", e);
+        }
+        f.complete(allSaved);
+      }
+    });
+    return f;
+  }
+
   public BackupFiles(String filename)
   {
     this(new File(filename));
@@ -116,6 +233,10 @@ public class BackupFiles
   {
     classInit();
     this.file = file;
+
+    // add this file from the save in progress stack
+    addSaveInProgress();
+
     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
             .getSavedBackupEntry();
     this.suffix = bfpe.suffix;
@@ -819,6 +940,9 @@ public class BackupFiles
       tidyUpFiles();
     }
 
+    // remove this file from the save in progress stack
+    removeSaveInProgress(rename);
+
     return rename;
   }
 
@@ -890,6 +1014,11 @@ public class BackupFiles
     return ret;
   }
 
+  public File getFile()
+  {
+    return file;
+  }
+
   public static boolean moveFileToFile(File oldFile, File newFile)
   {
     Console.initLogger();
index 4016e71..ffeb53d 100755 (executable)
@@ -465,6 +465,7 @@ public class FileLoader implements Runnable
             {
               alignFrame.setFileName(file, format);
               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
+              alignFrame.getViewport().setSavedUpToDate(true);
             }
             if (proxyColourScheme != null)
             {
index 4b66f81..9fb3720 100644 (file)
  */
 package jalview.io;
 
-import jalview.bin.Cache;
-import jalview.gui.AlignmentPanel;
-import jalview.gui.LineartOptions;
-import jalview.gui.OOMWarning;
-import jalview.math.AlignmentDimension;
-import jalview.util.MessageManager;
-
 import java.awt.Graphics;
 import java.awt.print.PrinterException;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jfree.graphics2d.svg.SVGHints;
 
+import jalview.bin.Cache;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.LineartOptions;
+import jalview.gui.OOMWarning;
+import jalview.math.AlignmentDimension;
+import jalview.util.MessageManager;
+
 public class HtmlSvgOutput extends HTMLOutput
 {
   public HtmlSvgOutput(AlignmentPanel ap)
@@ -211,13 +212,9 @@ public class HtmlSvgOutput extends HTMLOutput
       /*
        * configure the action to run on OK in the dialog
        */
-      Runnable okAction = new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          doOutput(textOption.get());
-        }
+      Callable<Void> okAction = () -> {
+        doOutput(textOption.get());
+        return null;
       };
 
       /*
@@ -226,15 +223,11 @@ public class HtmlSvgOutput extends HTMLOutput
       if (renderStyle.equalsIgnoreCase("Prompt each time") && !isHeadless())
       {
         LineartOptions svgOption = new LineartOptions("HTML", textOption);
-        svgOption.setResponseAction(1, new Runnable()
-        {
-          @Override
-          public void run()
-          {
-            setProgressMessage(MessageManager.formatMessage(
-                    "status.cancelled_image_export_operation",
-                    getDescription()));
-          }
+        svgOption.setResponseAction(1, () -> {
+          setProgressMessage(MessageManager.formatMessage(
+                  "status.cancelled_image_export_operation",
+                  getDescription()));
+          return null;
         });
         svgOption.setResponseAction(0, okAction);
         svgOption.showDialog();
index 6dbf4f8..3b6e325 100755 (executable)
 //////////////////////////////////////////////////////////////////
 package jalview.io;
 
+import jalview.bin.Cache;
+import jalview.gui.JvOptionPane;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.util.dialogrunner.DialogRunnerI;
+
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.EventQueue;
@@ -38,6 +44,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
+import java.util.concurrent.Callable;
 
 import javax.swing.BoxLayout;
 import javax.swing.DefaultListCellRenderer;
@@ -45,6 +52,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JDialog;
 import javax.swing.JFileChooser;
 import javax.swing.JList;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SpringLayout;
@@ -72,7 +80,7 @@ public class JalviewFileChooser extends JFileChooser
 {
   private static final long serialVersionUID = 1L;
 
-  private Map<Object, Runnable> callbacks = new HashMap<>();
+  private Map<Object, Callable> callbacks = new HashMap<>();
 
   File selectedFile = null;
 
@@ -490,10 +498,13 @@ public class JalviewFileChooser extends JFileChooser
 
     if (selectedFile.exists())
     {
-      int confirm = JvOptionPane.showConfirmDialog(this,
-              MessageManager.getString("label.overwrite_existing_file"),
-              MessageManager.getString("label.file_already_exists"),
-              JvOptionPane.YES_NO_OPTION);
+      int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true)
+              ? JvOptionPane.showConfirmDialog(this,
+                      MessageManager
+                              .getString("label.overwrite_existing_file"),
+                      MessageManager.getString("label.file_already_exists"),
+                      JvOptionPane.YES_NO_OPTION)
+              : JOptionPane.YES_OPTION;
 
       if (confirm != JvOptionPane.YES_OPTION)
       {
@@ -598,8 +609,26 @@ public class JalviewFileChooser extends JFileChooser
 
   }
 
+  /*
+  @Override
+  public JalviewFileChooser setResponseHandler(Object response,
+          Runnable action)
+  {
+    callbacks.put(response, new Callable<Void>()
+    {
+      @Override
+      public Void call()
+      {
+        action.run();
+        return null;
+      }
+    });
+    return this;
+  }
+  */
+
   @Override
-  public DialogRunnerI setResponseHandler(Object response, Runnable action)
+  public DialogRunnerI setResponseHandler(Object response, Callable action)
   {
     callbacks.put(response, action);
     return this;
@@ -615,10 +644,16 @@ public class JalviewFileChooser extends JFileChooser
     {
       return;
     }
-    Runnable action = callbacks.get(response);
+    Callable action = callbacks.get(response);
     if (action != null)
     {
-      action.run();
+      try
+      {
+        action.call();
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
     }
   }
 
index ca95222..b0079b4 100755 (executable)
@@ -32,6 +32,8 @@ import javax.swing.JMenuItem;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
+import jalview.gui.APQHandlers;
+import jalview.gui.Desktop;
 import jalview.io.FileFormatException;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -140,7 +142,6 @@ public class GDesktop extends JFrame
    */
   private void jbInit() throws Exception
   {
-    boolean apqHandlersSet = false;
     /**
      * APQHandlers sets handlers for About, Preferences and Quit actions
      * peculiar to macOS's application menu. APQHandlers will check to see if a
@@ -148,7 +149,7 @@ public class GDesktop extends JFrame
      */
     try
     {
-      apqHandlersSet = APQHandlers.setAPQHandlers(this);
+      APQHandlers.setAPQHandlers((Desktop) this);
     } catch (Exception e)
     {
       System.out.println("Cannot set APQHandlers");
@@ -213,7 +214,8 @@ public class GDesktop extends JFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        quit();
+        if (Desktop.instance != null)
+          Desktop.instance.desktopQuit();
       }
     });
     aboutMenuItem.setText(MessageManager.getString("label.about"));
index d4b2c04..c8f9be6 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 = debugDelaySave;
+      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 = debugDelaySave;
+        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,44 @@ 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;
+  }
+
+  // used for debugging and tests
+  private static int debugDelaySave = 20;
+
+  public static void setDebugDelaySave(int n)
+  {
+    debugDelaySave = n;
+  }
 }
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;
+  }
 }
diff --git a/test/jalview/gui/QuitHandlerTest.java b/test/jalview/gui/QuitHandlerTest.java
new file mode 100644 (file)
index 0000000..b1dab96
--- /dev/null
@@ -0,0 +1,302 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.gui.QuitHandler.QResponse;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FileLoader;
+import jalview.project.Jalview2XML;
+
+@Test(singleThreaded = true)
+public class QuitHandlerTest
+{
+  private static String saveProjectFile = "test-output/tempSaveFile.jvp";
+
+  private static String saveFastaFile = "test-output/tempSaveFile.fa";
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    Jalview2XML.setDebugDelaySave(3);
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    Cache.loadProperties("test/jalview/gui/quitProps.jvprops");
+
+    /*
+     * set news feed last read to a future time to ensure no
+     * 'unread' news item is displayed
+     */
+    Date oneHourFromNow = new Date(
+            System.currentTimeMillis() + 3600 * 1000);
+    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
+
+    Jalview.main(
+            new String[]
+            { "-nowebservicediscovery", "-nosplash", "-nonews" });
+  }
+
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    // reset quit response
+    QuitHandler.setResponse(QResponse.NULL);
+    // reset mock response
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    // close desktop windows/frames
+    Desktop.instance.closeAll_actionPerformed(null);
+    // reset debug delay
+    Jalview2XML.setDebugDelaySave(20);
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public static void tearDownAfterClass() throws Exception
+  {
+    // reset quit response
+    QuitHandler.setResponse(QResponse.NULL);
+    // reset mock response
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    // close desktop windows/frames
+    Desktop.instance.closeAll_actionPerformed(null);
+    // reset debug delay
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    Jalview2XML.setDebugDelaySave(3);
+    // set the project file
+    Desktop.instance.setProjectFile(new File(saveProjectFile));
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public static void cleanup()
+  {
+    // delete save files
+    List<String> files = new ArrayList<>();
+    files.add(saveProjectFile);
+    files.add(saveFastaFile);
+    for (String filename : files)
+    {
+      File file = new File(filename);
+      if (file.exists())
+      {
+        file.delete();
+      }
+    }
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testInstantQuit() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+
+    long start = System.currentTimeMillis();
+
+    // if a save is attempted it will delay 3s
+    Jalview2XML.setDebugDelaySave(3);
+    Cache.setProperty("DEBUG_DELAY_SAVE", "true");
+
+    // loaded file but haven't done anything, should just quit
+    QResponse response = QuitHandler.getQuitResponse(true);
+    long end = System.currentTimeMillis();
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Assert.assertTrue(end - start < 500,
+            "Quit-with-no-save-needed took too long (" + (end - start)
+                    + "ms)");
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 10)
+  public void testWaitForSaveQuit() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+
+    long start = System.currentTimeMillis();
+
+    // start a long save (3s)
+    Jalview2XML.setDebugDelaySave(3);
+    Cache.setProperty("DEBUG_DELAY_SAVE", "true");
+    Desktop.instance.saveState_actionPerformed(false);
+
+    // give the saveState thread time to start!
+    Thread.sleep(500);
+
+    // af.saveAlignment(saveProjectFile, FileFormat.Jalview);
+    QResponse response = QuitHandler.getQuitResponse(true);
+    long end = System.currentTimeMillis();
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Assert.assertTrue(end - start > 2900,
+            "Quit-whilst-saving was too short (" + (end - start) + "ms)");
+
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
+  public void testSavedProjectChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // no hanging around needed here
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    af.saveAlignment(saveProjectFile, FileFormat.Jalview);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true);
+
+    // if not saved this would be CANCEL_QUIT
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
+  public void testSavedAlignmentChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // no hanging around needed here
+    Cache.setProperty("DEBUG_DELAY_SAVE", "false");
+    af.saveAlignment(saveFastaFile, FileFormat.Fasta);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true);
+
+    // if not saved this would be CANCEL_QUIT
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testUnsavedChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true);
+
+    Assert.assertEquals(response, QResponse.CANCEL_QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
+  public void testNoGUIUnsavedChanges() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+    AlignViewport viewport = af.getViewport();
+    // pretend something has happened
+    viewport.setSavedUpToDate(false);
+    Jalview2XML.setStateSavedUpToDate(false);
+
+    // this is only a two button dialog [Quit] [Cancel] so use NO_OPTION
+    JvOptionPane.setMockResponse(JvOptionPane.NO_OPTION);
+    /*
+    QResponse response = QuitHandler.getQuitResponse(false,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+            */
+    QResponse response = QuitHandler.getQuitResponse(false);
+
+    Assert.assertEquals(response, QResponse.QUIT);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = { "Functional" }, singleThreaded = true, priority = 11)
+  public void testForceQuit() throws Exception
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Didn't read input file " + inFile);
+
+    long start = System.currentTimeMillis();
+
+    // start a long save (10s)
+    Jalview2XML.setDebugDelaySave(10);
+    Cache.setProperty("DEBUG_DELAY_SAVE", "true");
+    Desktop.instance.saveState_actionPerformed(false);
+
+    // give the saveState thread time to start!
+    Thread.sleep(100);
+
+    // this will select "Force Quit"
+    JvOptionPane.setMockResponse(JvOptionPane.YES_OPTION);
+    QResponse response = QuitHandler.getQuitResponse(true,
+            QuitHandler.defaultOkQuit, () -> {
+              // set FORCE_QUIT without the force quit
+              jalview.bin.Console.debug(
+                      "Setting FORCE_QUIT without actually quitting");
+              QuitHandler.setResponse(QResponse.FORCE_QUIT);
+              return null;
+            }, QuitHandler.defaultCancelQuit);
+    long end = System.currentTimeMillis();
+
+    Assert.assertEquals(response, QResponse.FORCE_QUIT);
+    // if the wait (min wait is 1s) wasn't long enough...
+    Assert.assertTrue(end - start > 1000,
+            "Force-Quit-whilst-saving was too short (" + (end - start)
+                    + "ms)");
+    // if the wait was too long (probably waited for file to save)
+    Assert.assertTrue(end - start < 9090,
+            "Force-Quit-whilst-saving was too long (" + (end - start)
+                    + "ms)");
+
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+}
diff --git a/test/jalview/gui/quitProps.jvprops b/test/jalview/gui/quitProps.jvprops
new file mode 100644 (file)
index 0000000..be2b1cb
--- /dev/null
@@ -0,0 +1,7 @@
+DEBUG_DELAY_SAVE=true
+BACKUPFILES_ENABLED=true
+BACKUPFILES_FC_INCLUDE=false
+BACKUPFILES_PRESET=1
+CONFIRM_OVERWRITE_FILE=true
+SHOW_STARTUP_FILE=false
+logs.Jalview.level=DEBUG
index cf8634a..b6e8379 100755 (executable)
 ### Edited to use adoptium domain to gain access to Java 17 (LTS) versions.
 
 BASE=https://api.adoptium.net/v3/binary/latest
+ZULU_BASE=https://cdn.azul.com/zulu/bin
 RELEASE_TYPE=ga
 JVM_IMPL=hotspot
 HEAP_SIZE=normal
 VENDOR=eclipse
 IMAGE_TYPE=jdk
+TAR=tar
+ZIP=zip
+UNZIP=unzip
+
+STRIP_MAC_APP_BUNDLING=false
+# archives not needed for JDKs
+CREATE_ARCHIVES=""
+# need zip with top-level jre dir for getdown updates. need tgz without top-level jre dir for install4j bundling
 
 RM=/bin/rm
 
 # unzip-strip from https://superuser.com/questions/518347/equivalent-to-tars-strip-components-1-in-unzip
-unzip-strip() (
+unzip-strip() {
   local zip=$1
   local dest=${2:-.}
-  local temp=$(mktemp -d) && unzip -qq -d "$temp" "$zip" && mkdir -p "$dest" &&
+  local temp=$(mktemp -d) && $UNZIP -qq -d "$temp" "$zip" && mkdir -p "$dest" &&
   shopt -s dotglob && local f=("$temp"/*) &&
   if (( ${#f[@]} == 1 )) && [[ -d "${f[0]}" ]] ; then
     mv "$temp"/*/* "$dest"
   else
     mv "$temp"/* "$dest"
   fi && rmdir "$temp"/* "$temp"
-)
+}
+
+dl_zulu() {
+  local OS="$1"
+  local ARCH="$2"
+  local VERSION="$3"
+  local TARFILE="$4"
+  declare -A osmap
+  osmap[mac]=macosx
+  osmap[windows]=win
+  osmap[linux]=linux
+  ZOS="${osmap[$OS]}"
+  echo "- Looking for download from Azul"
+  LATEST_DL_URL_FILE=$(wget -q -O - "${ZULU_BASE}/" | perl -n -e 'm/<a\b[^>]*href="(([^"]*\/)?zulu[^"]*-ca-'"${IMAGE_TYPE}""${VERSION}"'\.[^"]*-'"${ZOS}"'_'"${ARCH}"'.tar.gz)"[^"]*>/ && print "$1\n";' | tail -1)
+  local URL="${ZULU_BASE}/${LATEST_DL_URL_FILE}"
+  if [ -z "${LATEST_DL_URL_FILE}" ]; then
+    echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}' found at Azul"
+    return 1
+  fi
+  echo "- Found at Azul. Downloading '${URL}'"
+  wget -q -O "${TARFILE}" "${URL}" "${TARFILE}"
+  echo RETURN=$?
+  if [ "$?" != 0 ]; then
+    echo "- Download from Azul failed"
+    return 1
+  fi
+  return 0
+}
+
+declare -A DOWNLOAD_SUMMARY
 
 for FEATURE_VERSION in 8 11 17; do
   for OS_ARCH in mac:x64 mac:aarch64 windows:x64 linux:x64 linux:arm linux:aarch64; do
@@ -42,13 +80,30 @@ for FEATURE_VERSION in 8 11 17; do
     ARCH=${OS_ARCH#*:}
     NAME="${IMAGE_TYPE}-${FEATURE_VERSION}-${OS}-${ARCH}"
     TARFILE="${NAME}.tgz"
+    DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="None"
+    STRIP_COMPONENTS=1
+    MAC_STRIP_COMPONENTS=3
     echo "* Downloading ${TARFILE}"
     URL="${BASE}/${FEATURE_VERSION}/${RELEASE_TYPE}/${OS}/${ARCH}/${IMAGE_TYPE}/${JVM_IMPL}/${HEAP_SIZE}/${VENDOR}"
     wget -q -O "${TARFILE}" "${URL}"
     if [ "$?" != 0 ]; then
-      echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}'"
+      echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}' at Adoptium"
       $RM -f "${TARFILE}"
-      continue;
+
+      # Try Azul Zulu (not an API, a bit messier, but has Java 8 JRE for mac:aarch64
+      dl_zulu "${OS}" "${ARCH}" "${FEATURE_VERSION}" "${TARFILE}"
+
+      if [ "$?" != 0 ]; then
+        DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="None"
+        continue;
+      fi
+      STRIP_COMPONENTS=2
+      MAC_STRIP_COMPONENTS=4
+      DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="Azul"
+      echo "Set ${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}=Azul"
+    else
+      DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="Adoptium"
+      echo "Set ${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}=Adoptium"
     fi
     echo "Unpacking ${TARFILE}"
     JREDIR="${NAME}/${IMAGE_TYPE}"
@@ -60,14 +115,58 @@ for FEATURE_VERSION in 8 11 17; do
       RET=$?
     else
       echo "using tar"
-      tar --strip-components=1 -C "${JREDIR}" -zxf "${TARFILE}"
-      RET=$?
+      if [ x$OS = xmac -a x$STRIP_MAC_APP_BUNDLING = xtrue ]; then
+        echo "Running $TAR --strip-components=\"${MAC_STRIP_COMPONENTS}\" -C \"${JREDIR}\" -zxf \"${TARFILE}\" \"*/Contents/Home\""
+        $TAR --strip-components="${MAC_STRIP_COMPONENTS}" -C "${JREDIR}" -zxf "${TARFILE}" "*/Contents/Home"
+        RET=$?
+      else
+        $TAR --strip-components="${STRIP_COMPONENTS}" -C "${JREDIR}" -zxf "${TARFILE}"
+        RET=$?
+      fi
     fi
     if [ "$RET" != 0 ]; then
       echo "Error unpacking ${TARFILE}"
       exit 1
     fi
     $RM "${TARFILE}"
+    if [ \! -z "$CREATE_ARCHIVES" ]; then
+      for CREATEARCHIVE in ${CREATE_ARCHIVES}; do
+        ARCHIVEDIR=$CREATEARCHIVE
+        case $CREATEARCHIVE in
+          zip)
+            EXT=${CREATEARCHIVE}
+            echo "Creating ${NAME}.${EXT} for getdown updates"
+            [ \! -d ${ARCHIVEDIR} ] && mkdir -p "${ARCHIVEDIR}"
+            ABSARCHIVEDIR="${PWD}/$ARCHIVEDIR"
+            ZIPFILE="${ABSARCHIVEDIR}/${NAME}.${CREATEARCHIVE}"
+            [ -e "${ZIPFILE}" ] && $RM "${ZIPFILE}"
+            cd ${NAME}
+            $ZIP -X -r "${ZIPFILE}" "${IMAGE_TYPE}"
+            cd -
+            ;;
+          tgz)
+            EXT=tar.gz
+            echo "Creating ${NAME}.${EXT} for install4j bundling"
+            [ \! -d ${ARCHIVEDIR} ] && mkdir -p "${ARCHIVEDIR}"
+            $TAR -C "${JREDIR}" -zcf "${ARCHIVEDIR}/${NAME}.${EXT}" .
+            # make symbolic link with _ instead of - for install4j9
+            NEWNAME=${NAME//-/_}
+            echo "Linking from ${NEWNAME}.${EXT} for install4j9"
+            [ -e "${ARCHIVEDIR}/${NEWNAME}.${EXT}" ] && $RM "${ARCHIVEDIR}/${NEWNAME}.${EXT}"
+            ln -s "${NAME}.${EXT}" "${ARCHIVEDIR}/${NEWNAME}.${EXT}"
+            ;;
+          *)
+            echo "Archiving as '${CREATEARCHIVE}' file not supported"
+            ;;
+        esac
+      done
+    fi
   done
 done
 
+echo ""
+echo "Download Summary"
+for OA in "${!DOWNLOAD_SUMMARY[@]}"; do
+  echo "$OA: ${DOWNLOAD_SUMMARY[$OA]}"
+done
+
index 26442ea..c1a5a0d 100755 (executable)
@@ -15,6 +15,7 @@
 ### Edited to use adoptium domain to gain access to Java 17 (LTS) versions.
 
 BASE=https://api.adoptium.net/v3/binary/latest
+ZULU_BASE=https://cdn.azul.com/zulu/bin
 RELEASE_TYPE=ga
 JVM_IMPL=hotspot
 HEAP_SIZE=normal
@@ -31,7 +32,7 @@ CREATE_ARCHIVES="zip tgz"
 RM=/bin/rm
 
 # unzip-strip from https://superuser.com/questions/518347/equivalent-to-tars-strip-components-1-in-unzip
-unzip-strip() (
+unzip-strip() {
   local zip=$1
   local dest=${2:-.}
   local temp=$(mktemp -d) && $UNZIP -qq -d "$temp" "$zip" && mkdir -p "$dest" &&
@@ -41,7 +42,36 @@ unzip-strip() (
   else
     mv "$temp"/* "$dest"
   fi && rmdir "$temp"/* "$temp"
-)
+}
+
+dl_zulu() {
+  local OS="$1"
+  local ARCH="$2"
+  local VERSION="$3"
+  local TARFILE="$4"
+  declare -A osmap
+  osmap[mac]=macosx
+  osmap[windows]=win
+  osmap[linux]=linux
+  ZOS="${osmap[$OS]}"
+  echo "- Looking for download from Azul"
+  LATEST_DL_URL_FILE=$(wget -q -O - "${ZULU_BASE}/" | perl -n -e 'm/<a\b[^>]*href="(([^"]*\/)?zulu[^"]*-ca-'"${IMAGE_TYPE}""${VERSION}"'\.[^"]*-'"${ZOS}"'_'"${ARCH}"'.tar.gz)"[^"]*>/ && print "$1\n";' | tail -1)
+  local URL="${ZULU_BASE}/${LATEST_DL_URL_FILE}"
+  if [ -z "${LATEST_DL_URL_FILE}" ]; then
+    echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}' found at Azul"
+    return 1
+  fi
+  echo "- Found at Azul. Downloading '${URL}'"
+  wget -q -O "${TARFILE}" "${URL}" "${TARFILE}"
+  echo RETURN=$?
+  if [ "$?" != 0 ]; then
+    echo "- Download from Azul failed"
+    return 1
+  fi
+  return 0
+}
+
+declare -A DOWNLOAD_SUMMARY
 
 for FEATURE_VERSION in 8 11 17; do
   for OS_ARCH in mac:x64 mac:aarch64 windows:x64 linux:x64 linux:arm linux:aarch64; do
@@ -49,13 +79,30 @@ for FEATURE_VERSION in 8 11 17; do
     ARCH=${OS_ARCH#*:}
     NAME="${IMAGE_TYPE}-${FEATURE_VERSION}-${OS}-${ARCH}"
     TARFILE="${NAME}.tgz"
+    DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="None"
+    STRIP_COMPONENTS=1
+    MAC_STRIP_COMPONENTS=3
     echo "* Downloading ${TARFILE}"
     URL="${BASE}/${FEATURE_VERSION}/${RELEASE_TYPE}/${OS}/${ARCH}/${IMAGE_TYPE}/${JVM_IMPL}/${HEAP_SIZE}/${VENDOR}"
     wget -q -O "${TARFILE}" "${URL}"
     if [ "$?" != 0 ]; then
-      echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}'"
+      echo "- No ${IMAGE_TYPE}-${FEATURE_VERSION} download for ${OS}-${ARCH} '${URL}' at Adoptium"
       $RM -f "${TARFILE}"
-      continue;
+
+      # Try Azul Zulu (not an API, a bit messier, but has Java 8 JRE for mac:aarch64
+      dl_zulu "${OS}" "${ARCH}" "${FEATURE_VERSION}" "${TARFILE}"
+
+      if [ "$?" != 0 ]; then
+        DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="None"
+        continue;
+      fi
+      STRIP_COMPONENTS=2
+      MAC_STRIP_COMPONENTS=4
+      DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="Azul"
+      echo "Set ${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}=Azul"
+    else
+      DOWNLOAD_SUMMARY["${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}"]="Adoptium"
+      echo "Set ${OS_ARCH}-${IMAGE_TYPE}-${FEATURE_VERSION}=Adoptium"
     fi
     echo "Unpacking ${TARFILE}"
     JREDIR="${NAME}/${IMAGE_TYPE}"
@@ -68,10 +115,11 @@ for FEATURE_VERSION in 8 11 17; do
     else
       echo "using tar"
       if [ x$OS = xmac -a x$STRIP_MAC_APP_BUNDLING = xtrue ]; then
-        $TAR --strip-components=3 -C "${JREDIR}" -zxf "${TARFILE}" "*/Contents/Home"
+        echo "Running $TAR --strip-components=\"${MAC_STRIP_COMPONENTS}\" -C \"${JREDIR}\" -zxf \"${TARFILE}\" \"*/Contents/Home\""
+        $TAR --strip-components="${MAC_STRIP_COMPONENTS}" -C "${JREDIR}" -zxf "${TARFILE}" "*/Contents/Home"
         RET=$?
       else
-        $TAR --strip-components=1 -C "${JREDIR}" -zxf "${TARFILE}"
+        $TAR --strip-components="${STRIP_COMPONENTS}" -C "${JREDIR}" -zxf "${TARFILE}"
         RET=$?
       fi
     fi
@@ -115,3 +163,9 @@ for FEATURE_VERSION in 8 11 17; do
   done
 done
 
+echo ""
+echo "Download Summary"
+for OA in "${!DOWNLOAD_SUMMARY[@]}"; do
+  echo "$OA: ${DOWNLOAD_SUMMARY[$OA]}"
+done
+
diff --git a/utils/install4j/auto_file_associations-i4j10.pl b/utils/install4j/auto_file_associations-i4j10.pl
new file mode 100755 (executable)
index 0000000..f7e17a1
--- /dev/null
@@ -0,0 +1,227 @@
+#!/usr/bin/env perl
+
+use strict;
+
+my $i4jversion = 10;
+if ($ARGV[0] eq "-v") {
+  shift @ARGV;
+  $i4jversion = shift @ARGV;
+  die("-v i4jversion must be an integer [probably 7 or 8]") unless $i4jversion =~ m/^\d+$/;
+}
+
+my $fileformats = $ARGV[0];
+$fileformats = "../../src/jalview/io/FileFormat.java" unless $fileformats;
+
+# default mimetype will be text/x-$shortname
+# TODO: find an actual extension for mat, see JAL-Xxxxx for outstanding issues too
+# TODO: look up standard mime type used for BLASTfmt matrices, etc
+my $mimetypes = {
+  rnaml => "application/rnaml+xml",
+  biojson => "application/x-jalview-biojson+json",
+  jnet => "application/x-jalview-jnet+text",
+  features => "application/x-jalview-features+text",
+  scorematrix => "application/x-jalview-scorematrix+text",
+  pdb => "chemical/x-pdb",
+  mmcif => "chemical/x-cif",
+  mmcif2 => "chemical/x-mmcif",
+  jalview => "application/x-jalview+xml+zip",
+  jvl => "application/x-jalview-jvl+text",
+  annotations => "application/x-jalview-annotations+text",
+};
+
+my @dontaddshortname = qw(features json);
+my @dontaddextension = qw(html xml json jar mfa fastq);
+my $add_associations = {
+  biojson => {shortname=>"biojson",name=>"BioJSON",extensions=>["biojson"]},
+  gff2 => {shortname=>"gff2",name=>"Generic Features Format v2",extensions=>["gff2"]},
+  gff3 => {shortname=>"gff3",name=>"Generic Features Format v3",extensions=>["gff3"]},
+  features => {shortname=>"features",name=>"Jalview Features",extensions=>["features","jvfeatures"]},
+  annotations => {shortname=>"annotations",name=>"Jalview Annotations",extensions=>["annotations","jvannotations"]},
+  mmcif => {shortname=>"mmcif",name=>"CIF",extensions=>["cif"]},
+  mmcif2 => {shortname=>"mmcif2",name=>"mmCIF",extensions=>["mcif","mmcif"]},
+  jvl => {shortname=>"jvl",name=>"Jalview Launch",extensions=>["jvl"],iconfile=>"jvl_file"},
+  jnet => {shortname=>"jnet",name=>"JnetFile",extensions=>["concise","jnet"]},
+  scorematrix => {shortname=>"scorematrix",name=>"Substitution Matrix",extensions=>["mat"]},
+};
+my $add_extensions = {
+  blc => ["blc"],
+};
+my @put_first = qw(jalview jvl);
+my @owner = @put_first;
+
+my @non_primary = qw(mmcif mmcif2 pdb);
+
+my $v = ($i4jversion >= 8)?$i4jversion:"";
+my $i4jtemplatefile = "file_associations_template-install4j${v}.xml";
+my $i4jtemplate;
+my $mactemplatefile = "file_associations_template-Info_plist.xml";
+my $mactemplate;
+
+open(MT,"<$mactemplatefile") or die("Could not open '$mactemplatefile' for reading");
+while(<MT>){
+  $mactemplate .= $_;
+}
+close(MT);
+open(IT,"<$i4jtemplatefile") or die("Could not open '$i4jtemplatefile' for reading");
+while(<IT>){
+  $i4jtemplate .= $_;
+}
+close(IT);
+my $macauto;
+my $i4jauto;
+
+my $macautofile = $mactemplatefile;
+$macautofile =~ s/template/auto$1/;
+
+my $i4jautofile = $i4jtemplatefile;
+$i4jautofile =~ s/template/auto$1/;
+
+for my $key (sort keys %$add_associations) {
+  my $a = $add_associations->{$key};
+  warn("Known file association for $a->{shortname} (".join(",",@{$a->{extensions}}).")\n");
+}
+
+open(MA,">$macautofile") or die ("Could not open '$macautofile' for writing");
+print MA "<key>CFBundleDocumentTypes</key>\n<array>\n\n";
+
+open(IA,">$i4jautofile") or die ("Could not open '$i4jautofile' for writing");
+
+open(IN, "<$fileformats") or die ("Could not open '$fileformats' for reading");
+my $id = 10000;
+my $file_associations = {};
+while(my $line = <IN>) {
+  $line =~ s/\s+/ /g;
+  $line =~ s/(^ | $)//g;
+  if ($line =~ m/^(\w+) ?\( ?"([^"]*)" ?, ?"([^"]*)" ?, ?(true|false) ?, ?(true|false) ?\)$/i) {
+    my $shortname = lc($1);
+    next if (grep($_ eq $shortname, @dontaddshortname));
+    my $name = $2;
+    my $extensions = $3;
+    $extensions =~ s/\s+//g;
+    my @possextensions = map(lc($_),split(m/,/,$extensions));
+    my @extensions;
+    my $addext = $add_extensions->{$shortname};
+    if (ref($addext) eq "ARRAY") {
+      push(@possextensions, @$addext);
+    }
+    for my $possext (@possextensions) {
+      next if grep($_ eq $possext, @extensions);
+      next if grep($_ eq $possext, @dontaddextension);
+      push(@extensions,$possext);
+    }
+    next unless scalar(@extensions);
+    $file_associations->{$shortname} = {
+      shortname => $shortname,
+      name => $name,
+      extensions => \@extensions
+    };
+    warn("Reading file association for $shortname (".join(",",@extensions).")\n");
+  }
+}
+close(IN);
+
+my %all_associations = (%$file_associations, %$add_associations);
+
+my @ordered = (@put_first, @non_primary);
+for my $key (sort keys %all_associations) {
+  next if grep($_ eq $key, @ordered);
+  push(@ordered, $key);
+}
+my $num = $#ordered + 1;
+
+warn("--\n");
+
+my $i4jcount = 0;
+for my $shortname (@ordered) {
+  my $a = $all_associations{$shortname};
+  next if (ref($a) ne "HASH");
+
+  my $name = $a->{name};
+  my $extensions = $a->{extensions};
+  my $mimetype = $mimetypes->{$shortname};
+  $mimetype = "application/x-$shortname+txt" unless $mimetype;
+
+  my $iconfile = $a->{iconfile};
+  $iconfile = "Jalview-File" unless $iconfile;
+
+  my $owner = grep($_ eq $shortname, @owner);
+  my $primary = (! grep($_ eq $shortname, @non_primary));
+  my $primarystring = $primary?"true":"false";
+  #my $role = $owner?"Owner":($primary?"Editor":"Viewer");
+  my $role = $primary?"Editor":"Viewer";
+  my $rank = $owner?"Owner":($primary?"Default":"Alternate");
+
+  my @extensions = @$extensions;
+
+  my $xname = xml_escape($name);
+  my $xmimetype = xml_escape($mimetype);
+  my $xshortname = xml_escape($shortname);
+  my $xiconfile = xml_escape($iconfile);
+  my $xrole = xml_escape($role);
+  my $xROLE = xml_escape(uc($role));
+  my $xrank = xml_escape($rank);
+  my $xprimarystring = xml_escape($primarystring);
+
+  my $macentry = $mactemplate;
+  $macentry =~ s/\$\$NAME\$\$/$xname/g;
+  $macentry =~ s/\$\$SHORTNAME\$\$/$xshortname/g;
+  $macentry =~ s/\$\$MIMETYPE\$\$/$xmimetype/g;
+  $macentry =~ s/\$\$ICONFILE\$\$/$xiconfile/g;
+  $macentry =~ s/\$\$ROLE\$\$/$xrole/g;
+  $macentry =~ s/\$\$RANK\$\$/$xrank/g;
+  $macentry =~ s/\$\$PRIMARY\$\$/$xprimarystring/g;
+  while ($macentry =~ m/\$\$([^\$]*)EXTENSIONS([^\$]*)\$\$/) {
+    my $pre = $1;
+    my $post = $2;
+    my $macextensions;
+    for my $ext (@extensions) {
+      my $xext = xml_escape($ext);
+      $macextensions .= $pre.$xext.$post;
+    }
+    $macentry =~ s/\$\$${pre}EXTENSIONS${post}\$\$/$macextensions/g;
+  }
+  print MA $macentry;
+
+  my $i4jentry = $i4jtemplate;
+  $i4jentry =~ s/\$\$NAME\$\$/$xname/g;
+  $i4jentry =~ s/\$\$SHORTNAME\$\$/$xshortname/g;
+  $i4jentry =~ s/\$\$MIMETYPE\$\$/$xmimetype/g;
+  $i4jentry =~ s/\$\$ICONFILE\$\$/$xiconfile/g;
+  $i4jentry =~ s/\$\$PRIMARY\$\$/$xprimarystring/g;
+  $i4jentry =~ s/\$\$MACASSOCIATIONROLE\$\$/$xROLE/g;
+
+  my $ext = join(",",sort(@extensions));
+  my $xdisplayext = xml_escape(join(", ", map(".$_",sort(@extensions))));
+  my $progresspercent = int(($i4jcount/$num)*100);
+  $progresspercent = 100 if $progresspercent > 100;
+  $i4jcount++;
+  my $xext = xml_escape($ext);
+  my $addunixextension = "true";
+
+  $i4jentry =~ s/\$\$ADDUNIXEXTENSION\$\$/$addunixextension/g;
+  $i4jentry =~ s/\$\$EXTENSION\$\$/$xext/g;
+  $i4jentry =~ s/\$\$DISPLAYEXTENSION\$\$/$xdisplayext/g;
+  $i4jentry =~ s/\$\$PROGRESSPERCENT\$\$/$progresspercent/g;
+  $i4jentry =~ s/\$\$ID\$\$/$id/g;
+  $id++;
+  $i4jentry =~ s/\$\$ID1\$\$/$id/g;
+  $id++;
+  $i4jentry =~ s/\$\$ID2\$\$/$id/g;
+  $id++;
+
+  print IA $i4jentry;
+
+  delete $all_associations{$shortname};
+  warn("Writing entry for $name (".join(",",@$extensions).": $mimetype)\n");
+}
+
+close(IA);
+print MA "</array>\n";
+close(MA);
+
+sub xml_escape {
+  my $x = shift;
+  # stolen from Pod::Simple::XMLOutStream in base distro
+  $x =~ s/([^-\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}a-zA-Z0-9])/'&#'.(ord($1)).';'/eg;
+  return $x;
+}  
index 0b927a8..2e23321 100644 (file)
@@ -12,6 +12,8 @@
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Owner</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview+xml+zip</string>
@@ -31,6 +33,8 @@
 <string>jvl_file.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Owner</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-jvl+text</string>
@@ -50,6 +54,8 @@
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Viewer</string>
+<key>LSHandlerRank</key>
+<string>Alternate</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>chemical/x-cif</string>
@@ -70,6 +76,8 @@
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Viewer</string>
+<key>LSHandlerRank</key>
+<string>Alternate</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>chemical/x-mmcif</string>
@@ -90,6 +98,8 @@
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Viewer</string>
+<key>LSHandlerRank</key>
+<string>Alternate</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>chemical/x-pdb</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-amsa+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-annotations+text</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-biojson+json</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-blc+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-clustal+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-embl+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-fasta+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-features+text</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-genbank+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-gff2+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-gff3+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-jnet+text</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-msf+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-pfam+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-phylip+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-pileup+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-pir+txt</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/rnaml+xml</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-jalview-scorematrix+text</string>
 <string>Jalview-File.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
+<key>LSHandlerRank</key>
+<string>Default</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>application/x-stockholm+txt</string>
diff --git a/utils/install4j/file_associations_auto-install4j10.xml b/utils/install4j/file_associations_auto-install4j10.xml
new file mode 100644 (file)
index 0000000..ee70251
--- /dev/null
@@ -0,0 +1,1125 @@
+<!-- Jalview (.jvp) BEGIN -->
+                  <action name="Jalview (.jvp) message" id="10000" customizedId="Jalview-jvp-10000-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Jalview (.jvp)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview (.jvp) progress bar 0" id="10001" customizedId="Jalview-jvp-10001-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="0" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview (.jvp) file association" id="10002" customizedId="Jalview-jvp-10002-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .jvp file association">
+                    <serializedBean>
+                      <property name="description" type="string">Jalview File</property>
+                      <property name="extension" type="string">jvp</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview+xml+zip</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Jalview Launch (.jvl) BEGIN -->
+                  <action name="Jalview Launch (.jvl) message" id="10003" customizedId="Jalview Launch-jvl-10003-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Jalview Launch (.jvl)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Launch (.jvl) progress bar 4" id="10004" customizedId="Jalview Launch-jvl-10004-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="4" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Launch (.jvl) file association" id="10005" customizedId="Jalview Launch-jvl-10005-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .jvl file association">
+                    <serializedBean>
+                      <property name="description" type="string">Jalview Launch File</property>
+                      <property name="extension" type="string">jvl</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>jvl_file.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>jvl_file.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-jvl+text</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>jvl_file.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- CIF (.cif) BEGIN -->
+                  <action name="CIF (.cif) message" id="10006" customizedId="CIF-cif-10006-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">CIF (.cif)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="CIF (.cif) progress bar 8" id="10007" customizedId="CIF-cif-10007-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="8" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="CIF (.cif) file association" id="10008" customizedId="CIF-cif-10008-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .cif file association">
+                    <serializedBean>
+                      <property name="description" type="string">CIF File</property>
+                      <property name="extension" type="string">cif</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="VIEWER" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="false" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">chemical/x-cif</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- mmCIF (.mcif, .mmcif) BEGIN -->
+                  <action name="mmCIF (.mcif, .mmcif) message" id="10009" customizedId="mmCIF-mcif,mmcif-10009-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">mmCIF (.mcif, .mmcif)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="mmCIF (.mcif, .mmcif) progress bar 12" id="10010" customizedId="mmCIF-mcif,mmcif-10010-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="12" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="mmCIF (.mcif, .mmcif) file association" id="10011" customizedId="mmCIF-mcif,mmcif-10011-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .mcif,mmcif file association">
+                    <serializedBean>
+                      <property name="description" type="string">mmCIF File</property>
+                      <property name="extension" type="string">mcif,mmcif</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="VIEWER" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="false" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">chemical/x-mmcif</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- PDB (.ent, .pdb) BEGIN -->
+                  <action name="PDB (.ent, .pdb) message" id="10012" customizedId="PDB-ent,pdb-10012-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">PDB (.ent, .pdb)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PDB (.ent, .pdb) progress bar 16" id="10013" customizedId="PDB-ent,pdb-10013-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="16" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PDB (.ent, .pdb) file association" id="10014" customizedId="PDB-ent,pdb-10014-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .ent,pdb file association">
+                    <serializedBean>
+                      <property name="description" type="string">PDB File</property>
+                      <property name="extension" type="string">ent,pdb</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="VIEWER" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="false" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">chemical/x-pdb</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- AMSA (.amsa) BEGIN -->
+                  <action name="AMSA (.amsa) message" id="10015" customizedId="AMSA-amsa-10015-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">AMSA (.amsa)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="AMSA (.amsa) progress bar 20" id="10016" customizedId="AMSA-amsa-10016-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="20" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="AMSA (.amsa) file association" id="10017" customizedId="AMSA-amsa-10017-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .amsa file association">
+                    <serializedBean>
+                      <property name="description" type="string">AMSA File</property>
+                      <property name="extension" type="string">amsa</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-amsa+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Jalview Annotations (.annotations, .jvannotations) BEGIN -->
+                  <action name="Jalview Annotations (.annotations, .jvannotations) message" id="10018" customizedId="Jalview Annotations-annotations,jvannotations-10018-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Jalview Annotations (.annotations, .jvannotations)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Annotations (.annotations, .jvannotations) progress bar 24" id="10019" customizedId="Jalview Annotations-annotations,jvannotations-10019-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="24" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Annotations (.annotations, .jvannotations) file association" id="10020" customizedId="Jalview Annotations-annotations,jvannotations-10020-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .annotations,jvannotations file association">
+                    <serializedBean>
+                      <property name="description" type="string">Jalview Annotations File</property>
+                      <property name="extension" type="string">annotations,jvannotations</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-annotations+text</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- BioJSON (.biojson) BEGIN -->
+                  <action name="BioJSON (.biojson) message" id="10021" customizedId="BioJSON-biojson-10021-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">BioJSON (.biojson)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="BioJSON (.biojson) progress bar 28" id="10022" customizedId="BioJSON-biojson-10022-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="28" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="BioJSON (.biojson) file association" id="10023" customizedId="BioJSON-biojson-10023-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .biojson file association">
+                    <serializedBean>
+                      <property name="description" type="string">BioJSON File</property>
+                      <property name="extension" type="string">biojson</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-biojson+json</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- BLC (.blc) BEGIN -->
+                  <action name="BLC (.blc) message" id="10024" customizedId="BLC-blc-10024-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">BLC (.blc)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="BLC (.blc) progress bar 32" id="10025" customizedId="BLC-blc-10025-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="32" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="BLC (.blc) file association" id="10026" customizedId="BLC-blc-10026-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .blc file association">
+                    <serializedBean>
+                      <property name="description" type="string">BLC File</property>
+                      <property name="extension" type="string">blc</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-blc+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Clustal (.aln) BEGIN -->
+                  <action name="Clustal (.aln) message" id="10027" customizedId="Clustal-aln-10027-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Clustal (.aln)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Clustal (.aln) progress bar 36" id="10028" customizedId="Clustal-aln-10028-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="36" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Clustal (.aln) file association" id="10029" customizedId="Clustal-aln-10029-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .aln file association">
+                    <serializedBean>
+                      <property name="description" type="string">Clustal File</property>
+                      <property name="extension" type="string">aln</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-clustal+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- ENA Flatfile (.txt) BEGIN -->
+                  <action name="ENA Flatfile (.txt) message" id="10030" customizedId="ENA Flatfile-txt-10030-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">ENA Flatfile (.txt)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="ENA Flatfile (.txt) progress bar 40" id="10031" customizedId="ENA Flatfile-txt-10031-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="40" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="ENA Flatfile (.txt) file association" id="10032" customizedId="ENA Flatfile-txt-10032-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .txt file association">
+                    <serializedBean>
+                      <property name="description" type="string">ENA Flatfile File</property>
+                      <property name="extension" type="string">txt</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-embl+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Fasta (.fa, .fasta) BEGIN -->
+                  <action name="Fasta (.fa, .fasta) message" id="10033" customizedId="Fasta-fa,fasta-10033-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Fasta (.fa, .fasta)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Fasta (.fa, .fasta) progress bar 44" id="10034" customizedId="Fasta-fa,fasta-10034-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="44" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Fasta (.fa, .fasta) file association" id="10035" customizedId="Fasta-fa,fasta-10035-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .fa,fasta file association">
+                    <serializedBean>
+                      <property name="description" type="string">Fasta File</property>
+                      <property name="extension" type="string">fa,fasta</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-fasta+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Jalview Features (.features, .jvfeatures) BEGIN -->
+                  <action name="Jalview Features (.features, .jvfeatures) message" id="10036" customizedId="Jalview Features-features,jvfeatures-10036-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Jalview Features (.features, .jvfeatures)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Features (.features, .jvfeatures) progress bar 48" id="10037" customizedId="Jalview Features-features,jvfeatures-10037-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="48" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Jalview Features (.features, .jvfeatures) file association" id="10038" customizedId="Jalview Features-features,jvfeatures-10038-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .features,jvfeatures file association">
+                    <serializedBean>
+                      <property name="description" type="string">Jalview Features File</property>
+                      <property name="extension" type="string">features,jvfeatures</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-features+text</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- GenBank Flatfile (.gb, .gbk) BEGIN -->
+                  <action name="GenBank Flatfile (.gb, .gbk) message" id="10039" customizedId="GenBank Flatfile-gb,gbk-10039-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">GenBank Flatfile (.gb, .gbk)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="GenBank Flatfile (.gb, .gbk) progress bar 52" id="10040" customizedId="GenBank Flatfile-gb,gbk-10040-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="52" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="GenBank Flatfile (.gb, .gbk) file association" id="10041" customizedId="GenBank Flatfile-gb,gbk-10041-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .gb,gbk file association">
+                    <serializedBean>
+                      <property name="description" type="string">GenBank Flatfile File</property>
+                      <property name="extension" type="string">gb,gbk</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-genbank+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Generic Features Format v2 (.gff2) BEGIN -->
+                  <action name="Generic Features Format v2 (.gff2) message" id="10042" customizedId="Generic Features Format v2-gff2-10042-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Generic Features Format v2 (.gff2)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Generic Features Format v2 (.gff2) progress bar 56" id="10043" customizedId="Generic Features Format v2-gff2-10043-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="56" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Generic Features Format v2 (.gff2) file association" id="10044" customizedId="Generic Features Format v2-gff2-10044-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .gff2 file association">
+                    <serializedBean>
+                      <property name="description" type="string">Generic Features Format v2 File</property>
+                      <property name="extension" type="string">gff2</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-gff2+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Generic Features Format v3 (.gff3) BEGIN -->
+                  <action name="Generic Features Format v3 (.gff3) message" id="10045" customizedId="Generic Features Format v3-gff3-10045-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Generic Features Format v3 (.gff3)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Generic Features Format v3 (.gff3) progress bar 60" id="10046" customizedId="Generic Features Format v3-gff3-10046-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="60" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Generic Features Format v3 (.gff3) file association" id="10047" customizedId="Generic Features Format v3-gff3-10047-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .gff3 file association">
+                    <serializedBean>
+                      <property name="description" type="string">Generic Features Format v3 File</property>
+                      <property name="extension" type="string">gff3</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-gff3+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- JnetFile (.concise, .jnet) BEGIN -->
+                  <action name="JnetFile (.concise, .jnet) message" id="10048" customizedId="JnetFile-concise,jnet-10048-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">JnetFile (.concise, .jnet)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="JnetFile (.concise, .jnet) progress bar 64" id="10049" customizedId="JnetFile-concise,jnet-10049-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="64" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="JnetFile (.concise, .jnet) file association" id="10050" customizedId="JnetFile-concise,jnet-10050-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .concise,jnet file association">
+                    <serializedBean>
+                      <property name="description" type="string">JnetFile File</property>
+                      <property name="extension" type="string">concise,jnet</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-jnet+text</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- MSF (.msf) BEGIN -->
+                  <action name="MSF (.msf) message" id="10051" customizedId="MSF-msf-10051-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">MSF (.msf)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="MSF (.msf) progress bar 68" id="10052" customizedId="MSF-msf-10052-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="68" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="MSF (.msf) file association" id="10053" customizedId="MSF-msf-10053-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .msf file association">
+                    <serializedBean>
+                      <property name="description" type="string">MSF File</property>
+                      <property name="extension" type="string">msf</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-msf+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- PFAM (.pfam) BEGIN -->
+                  <action name="PFAM (.pfam) message" id="10054" customizedId="PFAM-pfam-10054-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">PFAM (.pfam)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PFAM (.pfam) progress bar 72" id="10055" customizedId="PFAM-pfam-10055-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="72" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PFAM (.pfam) file association" id="10056" customizedId="PFAM-pfam-10056-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .pfam file association">
+                    <serializedBean>
+                      <property name="description" type="string">PFAM File</property>
+                      <property name="extension" type="string">pfam</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-pfam+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- PHYLIP (.phy) BEGIN -->
+                  <action name="PHYLIP (.phy) message" id="10057" customizedId="PHYLIP-phy-10057-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">PHYLIP (.phy)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PHYLIP (.phy) progress bar 76" id="10058" customizedId="PHYLIP-phy-10058-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="76" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PHYLIP (.phy) file association" id="10059" customizedId="PHYLIP-phy-10059-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .phy file association">
+                    <serializedBean>
+                      <property name="description" type="string">PHYLIP File</property>
+                      <property name="extension" type="string">phy</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-phylip+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- PileUp (.pileup) BEGIN -->
+                  <action name="PileUp (.pileup) message" id="10060" customizedId="PileUp-pileup-10060-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">PileUp (.pileup)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PileUp (.pileup) progress bar 80" id="10061" customizedId="PileUp-pileup-10061-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="80" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PileUp (.pileup) file association" id="10062" customizedId="PileUp-pileup-10062-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .pileup file association">
+                    <serializedBean>
+                      <property name="description" type="string">PileUp File</property>
+                      <property name="extension" type="string">pileup</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-pileup+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- PIR (.pir) BEGIN -->
+                  <action name="PIR (.pir) message" id="10063" customizedId="PIR-pir-10063-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">PIR (.pir)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PIR (.pir) progress bar 84" id="10064" customizedId="PIR-pir-10064-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="84" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="PIR (.pir) file association" id="10065" customizedId="PIR-pir-10065-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .pir file association">
+                    <serializedBean>
+                      <property name="description" type="string">PIR File</property>
+                      <property name="extension" type="string">pir</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-pir+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- RNAML (.rnaml) BEGIN -->
+                  <action name="RNAML (.rnaml) message" id="10066" customizedId="RNAML-rnaml-10066-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">RNAML (.rnaml)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="RNAML (.rnaml) progress bar 88" id="10067" customizedId="RNAML-rnaml-10067-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="88" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="RNAML (.rnaml) file association" id="10068" customizedId="RNAML-rnaml-10068-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .rnaml file association">
+                    <serializedBean>
+                      <property name="description" type="string">RNAML File</property>
+                      <property name="extension" type="string">rnaml</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/rnaml+xml</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Substitution Matrix (.mat) BEGIN -->
+                  <action name="Substitution Matrix (.mat) message" id="10069" customizedId="Substitution Matrix-mat-10069-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Substitution Matrix (.mat)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Substitution Matrix (.mat) progress bar 92" id="10070" customizedId="Substitution Matrix-mat-10070-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="92" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Substitution Matrix (.mat) file association" id="10071" customizedId="Substitution Matrix-mat-10071-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .mat file association">
+                    <serializedBean>
+                      <property name="description" type="string">Substitution Matrix File</property>
+                      <property name="extension" type="string">mat</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-jalview-scorematrix+text</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
+<!-- Stockholm (.stk, .sto) BEGIN -->
+                  <action name="Stockholm (.stk, .sto) message" id="10072" customizedId="Stockholm-stk,sto-10072-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">Stockholm (.stk, .sto)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Stockholm (.stk, .sto) progress bar 96" id="10073" customizedId="Stockholm-stk,sto-10073-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="96" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="Stockholm (.stk, .sto) file association" id="10074" customizedId="Stockholm-stk,sto-10074-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .stk,sto file association">
+                    <serializedBean>
+                      <property name="description" type="string">Stockholm File</property>
+                      <property name="extension" type="string">stk,sto</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="true" />
+                      <property name="unix" type="boolean" value="true" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">application/x-stockholm+txt</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
index a260658..afac20c 100644 (file)
@@ -9,6 +9,8 @@ $$</array>
 <string>$$ICONFILE$$.icns</string>
 <key>CFBundleTypeRole</key>
 <string>$$ROLE$$</string>
+<key>LSHandlerRank</key>
+<string>$$RANK$$</string>
 <key>CFBundleTypeMIMETypes</key>
 <array>
 <string>$$MIMETYPE$$</string>
diff --git a/utils/install4j/file_associations_template-install4j10.xml b/utils/install4j/file_associations_template-install4j10.xml
new file mode 100644 (file)
index 0000000..8663ed9
--- /dev/null
@@ -0,0 +1,45 @@
+<!-- $$NAME$$ ($$DISPLAYEXTENSION$$) BEGIN -->
+                  <action name="$$NAME$$ ($$DISPLAYEXTENSION$$) message" id="$$ID$$" customizedId="$$NAME$$-$$EXTENSION$$-$$ID$$-message" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="detailMessage" type="string">$$NAME$$ ($$DISPLAYEXTENSION$$)</property>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="$$NAME$$ ($$DISPLAYEXTENSION$$) progress bar $$PROGRESSPERCENT$$" id="$$ID1$$" customizedId="$$NAME$$-$$EXTENSION$$-$$ID1$$-progressbar" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="$$PROGRESSPERCENT$$" />
+                    </serializedBean>
+                  </action>
+
+                  <action name="$$NAME$$ ($$DISPLAYEXTENSION$$) file association" id="$$ID2$$" customizedId="$$NAME$$-$$EXTENSION$$-$$ID2$$-fileassociation" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make .$$EXTENSION$$ file association">
+                    <serializedBean>
+                      <property name="description" type="string">$$NAME$$ File</property>
+                      <property name="extension" type="string">$$EXTENSION$$</property>
+                      <property name="launcherId" type="string">JALVIEW</property>
+                      <!--<property name="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>$$ICONFILE$$.icns</string>
+                        </object>
+                      </property>
+                      <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="$$MACASSOCIATIONROLE$$" />-->
+                      <property name="restartFinder" type="boolean" value="true" />
+                      <property name="selected" type="boolean" value="$$PRIMARY$$" />
+                      <property name="unix" type="boolean" value="$$ADDUNIXEXTENSION$$" />
+                      <property name="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>$$ICONFILE$$.png</string>
+                        </object>
+                      </property>
+                      <property name="unixMimeType" type="string">$$MIMETYPE$$</property>
+                      <property name="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>$$ICONFILE$$.ico</string>
+                        </object>
+                      </property>
+                    </serializedBean>
+                  </action>
+<!-- END -->
+
diff --git a/utils/install4j/install4j10_template.install4j b/utils/install4j/install4j10_template.install4j
new file mode 100644 (file)
index 0000000..77cd42e
--- /dev/null
@@ -0,0 +1,1496 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<install4j version="10.0.3" transformSequenceNumber="10">
+  <directoryPresets config="bin/jalview" />
+  <application name="${compiler:JALVIEW_APPLICATION_NAME}" applicationId="${compiler:WINDOWS_APPLICATION_ID}" mediaDir="${compiler:BUILD_DIR}" lzmaCompression="true" shortName="${compiler:INTERNAL_ID}" publisher="University of Dundee" publisherWeb="https://www.jalview.org/" version="${compiler:JALVIEW_VERSION}" allPathsRelative="true" macVolumeId="5aac4968c304f65" javaMinVersion="${compiler:JAVA_MIN_VERSION}" javaMaxVersion="${compiler:JAVA_MAX_VERSION}" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
+    <searchSequence>
+      <directory location="${compiler:JRE_DIR}" />
+      <registry />
+      <envVar name="JAVA_HOME" />
+    </searchSequence>
+    <variables>
+      <variable name="JALVIEW_NAME" value="Jalview" />
+      <variable name="JALVIEW_APPLICATION_NAME" value="Jalview" />
+      <variable name="JALVIEW_DIR" value="../.." />
+      <variable name="BUILD_DIR" value="${compiler:JALVIEW_DIR}/build/install4j" />
+      <variable name="OSX_KEYSTORE" />
+      <variable name="OSX_APPLEID" />
+      <variable name="JSIGN_SH" value="echo" />
+      <variable name="JRE_DIR" value="jre" description="The folder under the app folder that the JRE will be either copied or unpacked into" />
+      <variable name="INSTALLER_TEMPLATE_VERSION" value="DEVELOPMENT_default" />
+      <variable name="JALVIEW_VERSION" value="DEVELOPMENT" />
+      <variable name="JAVA_MIN_VERSION" value="11" />
+      <variable name="JAVA_MAX_VERSION" value="11" />
+      <variable name="JAVA_VERSION" value="11" />
+      <variable name="JAVA_INTEGER_VERSION" value="11" />
+      <variable name="VERSION" value="DEVELOPMENT" />
+      <variable name="MACOS_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-mac-x64/jre" />
+      <variable name="MACOS_AARCH64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-mac-aarch64/jre" />
+      <variable name="WINDOWS_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-windows-x64/jre" />
+      <variable name="LINUX_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-linux-x64/jre" />
+      <variable name="LINUX_AARCH64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-linux-aarch64/jre" />
+      <variable name="MACOS_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_mac_x64.tar.gz" />
+      <variable name="MACOS_AARCH64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_mac_aarch64.tar.gz" />
+      <variable name="WINDOWS_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_windows_x64.tar.gz" />
+      <variable name="LINUX_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_linux_x64.tar.gz" />
+      <variable name="LINUX_AARCH64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_linux_aarch64.tar.gz" />
+      <variable name="COPYRIGHT_MESSAGE" value="..." />
+      <variable name="BUNDLE_ID" value="org.jalview.jalview-desktop" />
+      <variable name="INTERNAL_ID" value="Jalview" />
+      <variable name="WINDOWS_APPLICATION_ID" value="6595-2347-1923-0725" />
+      <variable name="MACOS_DMG_DS_STORE" value="utils/channels/default/images/jalview_default_dmg_DS_Store" />
+      <variable name="MACOS_DMG_BG_IMAGE" value="utils/channels/default/images/jalview_default_dmg_background-72dpi.png" />
+      <variable name="WRAPPER_LINK" value="jalview" />
+      <variable name="BASH_WRAPPER_SCRIPT" value="jalview.sh" />
+      <variable name="WRAPPER_SCRIPT_BIN_DIR" value="bin" />
+      <variable name="INSTALLER_NAME" value="Jalview Installer" />
+      <variable name="INSTALL4J_UTILS_DIR" value="utils/install4j" />
+      <variable name="GETDOWN_CHANNEL_DIR" value="build/website/docroot/getdown/default" />
+      <variable name="GETDOWN_FILES_DIR" value="getdown/files" />
+      <variable name="GETDOWN_RESOURCE_DIR" value="resource" />
+      <variable name="GETDOWN_DIST_DIR" value="dist" />
+      <variable name="GETDOWN_ALT_DIR" value="alt" />
+      <variable name="GETDOWN_INSTALL_DIR" value="install" />
+      <variable name="INFO_PLIST_FILE_ASSOCIATIONS_FILE" value="file_associations_auto-Info_plist.xml" />
+      <variable name="APPLICATION_CATEGORIES" value="Science;Biology;Java;" />
+      <variable name="APPLICATION_FOLDER" value="Jalview" />
+      <variable name="UNIX_APPLICATION_FOLDER" value="jalview" />
+      <variable name="EXECUTABLE_NAME" value="jalview" />
+      <variable name="EXTRA_SCHEME" value="jalviewx" />
+      <variable name="MAC_ICONS_FILE" value="utils/channels/release/images/jalview_logo.icns" />
+      <variable name="WINDOWS_ICONS_FILE" value="utils/channels/release/images/jalview_logo.ico" />
+      <variable name="PNG_ICON_FILE" value="utils/channels/release/images/jalview_logo.png" />
+      <variable name="BACKGROUND" value="utils/channels/release/images/jalview_logo_background_fade-640x480.png" />
+    </variables>
+    <codeSigning macEnabled="true" macPkcs12File="${compiler:OSX_KEYSTORE}" macNotarize="true" appleId="${compiler:OSX_APPLEID}">
+      <macAdditionalBinaries>
+        <entry>*.dylib</entry>
+        <entry>*.so</entry>
+        <entry>*.jnilib</entry>
+        <entry>unpack200</entry>
+        <entry>tnameserv</entry>
+        <entry>servertool</entry>
+        <entry>rmiregistry</entry>
+        <entry>rmid</entry>
+        <entry>policytool</entry>
+        <entry>pack200</entry>
+        <entry>orbd</entry>
+        <entry>keytool</entry>
+        <entry>jjs</entry>
+        <entry>java</entry>
+        <entry>jspawnhelper</entry>
+        <entry>libfreetype.dylib.6</entry>
+        <entry>applet</entry>
+        <entry>jaotc</entry>
+        <entry>jfr</entry>
+        <entry>jrunscript</entry>
+        <entry>libjli.dylib</entry>
+      </macAdditionalBinaries>
+    </codeSigning>
+  </application>
+  <files defaultUninstallMode="2">
+    <filesets>
+      <fileset name="Full file set" id="734" customizedId="FULL_FILE_SET" />
+      <fileset name="macOS x64 JVM" id="2801" customizedId="MACOS_X64_JVM" />
+      <fileset name="macOS aarch64 JVM" id="2803" customizedId="MACOS_AARCH64_JVM" />
+    </filesets>
+    <roots>
+      <root id="735" fileset="734" />
+      <root id="2802" fileset="2801" />
+      <root id="2804" fileset="2803" />
+    </roots>
+    <mountPoints>
+      <mountPoint id="736" root="735" />
+      <mountPoint id="2805" root="2802" />
+      <mountPoint id="2806" root="2804" />
+    </mountPoints>
+    <entries>
+      <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_CHANNEL_DIR}/${compiler:JAVA_VERSION}" uninstallMode="2" overrideOverwriteMode="true" overrideUninstallMode="true" subDirectory="files">
+        <exclude>
+          <entry location="${compiler:WRAPPER_SCRIPT_BIN_DIR}" />
+        </exclude>
+      </dirEntry>
+      <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/examples" overwriteMode="1" uninstallMode="2" overrideFileMode="true" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="examples" />
+      <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_CHANNEL_DIR}/${compiler:JAVA_VERSION}/${compiler:WRAPPER_SCRIPT_BIN_DIR}" fileMode="755" overrideFileMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:WRAPPER_SCRIPT_BIN_DIR}" overrideDirMode="true" />
+      <dirEntry mountPoint="2805" file="${compiler:MACOS_X64_JAVA_VM_DIR}" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
+      <dirEntry mountPoint="2806" file="${compiler:MACOS_AARCH64_JAVA_VM_DIR}" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
+    </entries>
+    <components>
+      <component name="jalview_getdown" id="1031">
+        <include>
+          <entry filesetId="734" />
+        </include>
+      </component>
+    </components>
+  </files>
+  <launchers>
+    <launcher name="Jalview Launcher" id="737" customizedId="JALVIEW" menuName="${compiler:JALVIEW_APPLICATION_NAME}" icnsFile="${compiler:JALVIEW_DIR}/${compiler:MAC_ICONS_FILE}" customMacBundleIdentifier="true" macBundleIdentifier="${compiler:BUNDLE_ID}" fileset="734" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:JALVIEW_APPLICATION_NAME}">
+      <executable name="${compiler:EXECUTABLE_NAME}" iconSet="true" iconFile="${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}" redirectStdout="true" executableMode="gui" changeWorkingDirectory="false" singleInstance="true" checkConsoleParameter="true">
+        <versionInfo include="true" fileDescription="${compiler:sys.fullName}" legalCopyright="${compiler:COPYRIGHT_MESSAGE}" internalName="${compiler:INTERNAL_ID}" productName="${compiler:sys.fullName}" />
+      </executable>
+      <splashScreen width="640" height="480" bitmapFile="${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" vmParameters="-Dinstaller_template_version=${compiler:INSTALLER_TEMPLATE_VERSION}" arguments="&quot;${launcher:sys.launcherDirectory}&quot; jalview">
+        <classPath>
+          <archive location="getdown-launcher.jar" />
+          <archive location="${compiler:GETDOWN_INSTALL_DIR}/getdown-launcher.jar" failOnError="false" />
+        </classPath>
+        <nativeLibraryDirectories>
+          <directory name="${compiler:JRE_DIR}/bin" />
+          <directory name="${compiler:GETDOWN_DIST_DIR}" />
+        </nativeLibraryDirectories>
+      </java>
+      <infoPlist>${compiler:file("${compiler:INFO_PLIST_FILE_ASSOCIATIONS_FILE}")}</infoPlist>
+      <iconImageFiles>
+        <file path="${compiler:JALVIEW_DIR}/${compiler:PNG_ICON_FILE}" />
+      </iconImageFiles>
+      <macStaticAssociations>
+        <urlHandler scheme="jalview" />
+        <urlHandler scheme="jalviews" />
+        <urlHandler scheme="${compiler:EXTRA_SCHEME}" />
+      </macStaticAssociations>
+    </launcher>
+  </launchers>
+  <installerGui autoUpdateDescriptorUrl="https://www.jalview.org/install4j/updates.xml">
+    <applications>
+      <application id="installer" beanClass="com.install4j.runtime.beans.applications.InstallerApplication" styleId="35" customIcnsFile="${compiler:JALVIEW_DIR}/${compiler:MAC_ICONS_FILE}" customIcoFile="${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}">
+        <serializedBean>
+          <property name="useCustomIcon" type="boolean" value="true" />
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group id="146" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <property name="backgroundColor">
+                  <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                    <object class="java.awt.Color">
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                    </object>
+                    <object class="java.awt.Color">
+                      <int>49</int>
+                      <int>52</int>
+                      <int>53</int>
+                      <int>255</int>
+                    </object>
+                  </object>
+                </property>
+                <property name="borderSides">
+                  <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                    <property name="bottom" type="boolean" value="true" />
+                  </object>
+                </property>
+                <property name="imageEdgeBackgroundColor">
+                  <object class="java.awt.Color">
+                    <int>255</int>
+                    <int>255</int>
+                    <int>255</int>
+                    <int>255</int>
+                  </object>
+                </property>
+                <property name="imageEdgeBorder" type="boolean" value="true" />
+                <property name="imageFile">
+                  <object class="com.install4j.api.beans.ExternalFile">
+                    <string>${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}</string>
+                  </object>
+                </property>
+                <property name="insets">
+                  <object class="java.awt.Insets">
+                    <int>5</int>
+                    <int>10</int>
+                    <int>10</int>
+                    <int>10</int>
+                  </object>
+                </property>
+              </serializedBean>
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+          <styleOverride name="Jalview" enabled="true">
+            <formComponent name="Watermark" id="352" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" insetTop="0" insetLeft="5" insetBottom="0" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+              <serializedBean>
+                <property name="enabledTitleText" type="boolean" value="false" />
+              </serializedBean>
+              <externalParametrizationPropertyNames>
+                <propertyName>labelText</propertyName>
+              </externalParametrizationPropertyNames>
+            </formComponent>
+          </styleOverride>
+        </styleOverrides>
+        <startup>
+          <screen id="1" beanClass="com.install4j.runtime.beans.screens.StartupScreen" rollbackBarrierExitCode="0">
+            <actions>
+              <action id="22" beanClass="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="obtainIfAdminWin" type="boolean" value="false" />
+                </serializedBean>
+              </action>
+              <action name="Set unixUserBinDir (Linux or Unix)" id="2738" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="script">
+                    <object class="com.install4j.api.beans.ScriptProperty">
+                      <property name="value" type="string">String userHome = (String)context.getVariable("sys.userHome");
+
+ArrayList&lt;String&gt; tryPaths = new ArrayList&lt;&gt; ();
+tryPaths.add(userHome + File.separator + "bin");
+tryPaths.add(userHome + File.separator + ".local" + File.separator + "bin");
+tryPaths.add(userHome + File.separator + "local" + File.separator + "bin");
+tryPaths.add(userHome + File.separator + "opt" + File.separator + "bin");
+
+if (Util.isMacOS()) { // &amp;&amp; root permission?
+    tryPaths.add(File.separator + "usr" + File.separator + "local" + File.separator + "bin"); 
+}
+
+for (int i = 0; i &lt; tryPaths.size(); i++) {
+    String tryPath = tryPaths.get(i);
+    File unixUserBinDir = new File(tryPath);
+    if (unixUserBinDir.exists()) {
+        return tryPath;
+    }
+}
+
+return null;
+</property>
+                    </object>
+                  </property>
+                  <property name="variableName" type="string">unixUserBinDir</property>
+                </serializedBean>
+                <condition>Util.isLinux() || Util.isUnixInstaller() || Util.isMacOS()</condition>
+              </action>
+              <action name="Set macWrapperLinkLocation (macOS)" id="2745" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="script">
+                    <object class="com.install4j.api.beans.ScriptProperty">
+                      <property name="value" type="string">String javaHome = System.getProperty("java.home");
+String appName = ((String)context.getCompilerVariable("JALVIEW_APPLICATION_NAME")) + ".app";
+int i = javaHome.indexOf(appName);
+String wrapperLink = null;
+if (i &gt; -1) {
+    wrapperLink = javaHome.substring(0, i) + appName + File.separator + "Contents" + File.separator + "MacOS" + File.separator + ((String)context.getCompilerVariable("WRAPPER_LINK"));
+}
+return wrapperLink;
+</property>
+                    </object>
+                  </property>
+                  <property name="variableName" type="string">macWrapperLinkLocation</property>
+                </serializedBean>
+                <condition>Util.isMacOS()</condition>
+              </action>
+            </actions>
+          </screen>
+        </startup>
+        <screens>
+          <screen id="2" beanClass="com.install4j.runtime.beans.screens.WelcomeScreen" rollbackBarrierExitCode="0">
+            <styleOverrides>
+              <styleOverride name="Customize banner image" enabled="true">
+                <group id="145" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+                  <serializedBean>
+                    <property name="backgroundColor">
+                      <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                        <object class="java.awt.Color">
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                        </object>
+                        <object class="java.awt.Color">
+                          <int>49</int>
+                          <int>52</int>
+                          <int>53</int>
+                          <int>255</int>
+                        </object>
+                      </object>
+                    </property>
+                    <property name="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <property name="bottom" type="boolean" value="true" />
+                      </object>
+                    </property>
+                    <property name="imageEdgeBackgroundColor">
+                      <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                        <object class="java.awt.Color">
+                          <int>25</int>
+                          <int>143</int>
+                          <int>220</int>
+                          <int>255</int>
+                        </object>
+                        <object class="java.awt.Color">
+                          <int>0</int>
+                          <int>74</int>
+                          <int>151</int>
+                          <int>255</int>
+                        </object>
+                      </object>
+                    </property>
+                    <property name="imageEdgeBorder" type="boolean" value="true" />
+                    <property name="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}</string>
+                      </object>
+                    </property>
+                    <property name="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </property>
+                  </serializedBean>
+                  <externalParametrizationPropertyNames>
+                    <propertyName>imageAnchor</propertyName>
+                    <propertyName>imageEdgeBackgroundColor</propertyName>
+                    <propertyName>imageFile</propertyName>
+                  </externalParametrizationPropertyNames>
+                </group>
+              </styleOverride>
+            </styleOverrides>
+            <actions>
+              <action id="7" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" multiExec="true">
+                <serializedBean>
+                  <property name="excludedVariables" type="array" elementType="string" length="1">
+                    <element index="0">sys.installationDir</element>
+                  </property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent id="3" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                <serializedBean>
+                  <property name="labelText" type="string">${form:welcomeMessage}</property>
+                </serializedBean>
+                <visibilityScript>!context.isConsole()</visibilityScript>
+              </formComponent>
+              <formComponent id="4" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                <serializedBean>
+                  <property name="consoleScript">
+                    <object class="com.install4j.api.beans.ScriptProperty">
+                      <property name="value" type="string">String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName());
+return console.askOkCancel(message, true);
+</property>
+                    </object>
+                  </property>
+                </serializedBean>
+              </formComponent>
+              <formComponent id="5" beanClass="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" useExternalParametrization="true" externalParametrizationName="Update Alert" externalParametrizationMode="include">
+                <externalParametrizationPropertyNames>
+                  <propertyName>updateCheck</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent id="6" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetTop="20">
+                <serializedBean>
+                  <property name="labelText" type="string">${i18n:ClickNext}</property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="8" beanClass="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" rollbackBarrierExitCode="0">
+            <condition>!context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+            <actions>
+              <action id="11" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" multiExec="true">
+                <serializedBean>
+                  <property name="excludedVariables" type="array" elementType="string" length="1">
+                    <element index="0">sys.installationDir</element>
+                  </property>
+                </serializedBean>
+                <condition>context.getVariable("sys.responseFile") == null</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent id="9" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="25">
+                <serializedBean>
+                  <property name="labelText" type="string">${i18n:SelectDirLabel(${compiler:sys.fullName})}</property>
+                </serializedBean>
+              </formComponent>
+              <formComponent id="10" beanClass="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent" useExternalParametrization="true" externalParametrizationName="Installation Directory Chooser" externalParametrizationMode="include">
+                <serializedBean>
+                  <property name="requestFocus" type="boolean" value="true" />
+                </serializedBean>
+                <externalParametrizationPropertyNames>
+                  <propertyName>allowSpacesOnUnix</propertyName>
+                  <propertyName>checkFreeSpace</propertyName>
+                  <propertyName>checkWritable</propertyName>
+                  <propertyName>existingDirWarning</propertyName>
+                  <propertyName>manualEntryAllowed</propertyName>
+                  <propertyName>showFreeDiskSpace</propertyName>
+                  <propertyName>showRequiredDiskSpace</propertyName>
+                  <propertyName>standardValidation</propertyName>
+                  <propertyName>suggestAppDir</propertyName>
+                  <propertyName>validateApplicationId</propertyName>
+                  <propertyName>validationScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="12" beanClass="com.install4j.runtime.beans.screens.ComponentsScreen" enabled="false" rollbackBarrierExitCode="0">
+            <formComponents>
+              <formComponent id="13" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                <serializedBean>
+                  <property name="labelText" type="string">${i18n:SelectComponentsLabel2}</property>
+                </serializedBean>
+                <visibilityScript>!context.isConsole()</visibilityScript>
+              </formComponent>
+              <formComponent id="14" beanClass="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent" useExternalParametrization="true" externalParametrizationName="Installation Components" externalParametrizationMode="include">
+                <serializedBean>
+                  <property name="fillVertical" type="boolean" value="true" />
+                </serializedBean>
+                <externalParametrizationPropertyNames>
+                  <propertyName>selectionChangedScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="1692" beanClass="com.install4j.runtime.beans.screens.FileAssociationsScreen" rollbackBarrierExitCode="0">
+            <formComponents>
+              <formComponent id="1693" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                <serializedBean>
+                  <property name="labelText" type="string">${i18n:SelectAssociationsLabel}</property>
+                </serializedBean>
+              </formComponent>
+              <formComponent id="1694" beanClass="com.install4j.runtime.beans.formcomponents.FileAssociationsComponent" useExternalParametrization="true" externalParametrizationName="File Associations" externalParametrizationMode="include">
+                <serializedBean>
+                  <property name="fillVertical" type="boolean" value="true" />
+                  <property name="selectionButtonPosition" type="enum" class="com.install4j.runtime.beans.formcomponents.VerticalDockingPosition" value="TOP" />
+                  <property name="showSelectionButtons" type="boolean" value="true" />
+                </serializedBean>
+                <externalParametrizationPropertyNames>
+                  <propertyName>showSelectionButtons</propertyName>
+                  <propertyName>selectionButtonPosition</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="15" beanClass="com.install4j.runtime.beans.screens.InstallationScreen" rollbackBarrier="true" rollbackBarrierExitCode="0">
+            <actions>
+              <action id="17" beanClass="com.install4j.runtime.beans.actions.InstallFilesAction" actionElevationType="elevated" rollbackBarrierExitCode="0" failureStrategy="quit" errorMessage="${i18n:FileCorrupted}" />
+              <action name="Create program group (RELEASE)" id="18" customizedId="PROGRAM_GROUP_RELEASE" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="allUsers" type="boolean" value="false" />
+                  <property name="categories" type="string">${compiler:APPLICATION_CATEGORIES}</property>
+                  <property name="programGroupEntryConfigs">
+                    <add>
+                      <object class="com.install4j.runtime.beans.screens.components.ProgramGroupFileConfig">
+                        <property name="name" type="string">Examples</property>
+                        <property name="target">
+                          <object class="java.io.File">
+                            <string>examples</string>
+                          </object>
+                        </property>
+                      </object>
+                    </add>
+                  </property>
+                  <property name="programGroupName" type="string">${compiler:JALVIEW_NAME}</property>
+                  <property name="uninstallerMenuName" type="string">${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</property>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action name="Create program group (NON-RELEASE)" id="2730" customizedId="PROGRAM_GROUP_NON_RELEASE" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="allUsers" type="boolean" value="false" />
+                  <property name="categories" type="string">${compiler:APPLICATION_CATEGORIES}</property>
+                  <property name="programGroupName" type="string">${compiler:JALVIEW_NAME}</property>
+                  <property name="uninstallerMenuName" type="string">${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</property>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action id="19" beanClass="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="itemName" type="string">${compiler:sys.fullName} ${compiler:sys.version}</property>
+                </serializedBean>
+              </action>
+              <action id="124" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" enabled="false" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="script">
+                    <object class="com.install4j.api.beans.ScriptProperty">
+                      <property name="value" type="string" />
+                    </object>
+                  </property>
+                  <property name="variableName" type="string" />
+                </serializedBean>
+                <condition>true</condition>
+              </action>
+              <action id="134" beanClass="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction" enabled="false" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="launcherId" type="string">121</property>
+                  <property name="vmOptions" type="array" elementType="string" length="0" />
+                </serializedBean>
+              </action>
+              <group name="File Associations" id="2251" beanClass="com.install4j.runtime.beans.groups.ActionGroup">
+                <beans>
+                  <action id="2253" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="statusMessage" type="string">Creating file associations...</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+                  <action id="2254" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="progressChangeType" type="enum" class="com.install4j.runtime.beans.actions.control.ProgressChangeType" value="SET_INDETERMINATE" />
+                    </serializedBean>
+                  </action>
+                  <group name="File Associations Replacement Group" id="2753" customizedId="EXTENSIONS_REPLACED_BY_GRADLE_PARENT_GROUP" beanClass="com.install4j.runtime.beans.groups.ActionGroup">
+                    <beans>
+                      <action name="EXTENSIONS_REPLACED_BY_GRADLE" id="1691" customizedId="EXTENSIONS" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                        <serializedBean>
+                          <property name="description" type="string">This action, identified by its name "EXTENSIONS_REPLACED_BY_GRADLE", will be replaced by gradle with the contents of file 'file_associations_auto_install4j.xml'.</property>
+                          <property name="extension" type="string">extensions_to_be_replaced_by_gradle</property>
+                          <property name="launcherId" type="string">JALVIEW</property>
+                        </serializedBean>
+                      </action>
+                    </beans>
+                  </group>
+                  <action id="2542" beanClass="com.install4j.runtime.beans.actions.control.SetMessageAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="statusMessage" type="string">Finished creating file associations</property>
+                      <property name="useDetail" type="boolean" value="true" />
+                      <property name="useStatus" type="boolean" value="true" />
+                    </serializedBean>
+                  </action>
+                  <action id="2541" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                    <serializedBean>
+                      <property name="percentValue" type="int" value="100" />
+                    </serializedBean>
+                  </action>
+                </beans>
+              </group>
+              <action id="2350" beanClass="com.install4j.runtime.beans.actions.desktop.UrlHandlerAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="launcherId" type="string">JALVIEW</property>
+                  <property name="scheme" type="string">jalview</property>
+                </serializedBean>
+              </action>
+              <action id="2450" beanClass="com.install4j.runtime.beans.actions.desktop.UrlHandlerAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="launcherId" type="string">JALVIEW</property>
+                  <property name="scheme" type="string">jalviews</property>
+                </serializedBean>
+              </action>
+              <action id="2641" beanClass="com.install4j.runtime.beans.actions.desktop.UrlHandlerAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="launcherId" type="string">JALVIEW</property>
+                  <property name="scheme" type="string">${compiler:EXTRA_SCHEME}</property>
+                </serializedBean>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent id="16" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                <serializedBean>
+                  <property name="initialStatusMessage" type="string">${i18n:WizardPreparing}</property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="20" beanClass="com.install4j.runtime.beans.screens.FinishedScreen" rollbackBarrierExitCode="0" finishScreen="true">
+            <actions>
+              <action id="2012" beanClass="com.install4j.runtime.beans.actions.desktop.CreateStartMenuEntryAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="allUsers" type="boolean" value="false" />
+                  <property name="categories" type="string">${compiler:APPLICATION_CATEGORIES}</property>
+                  <property name="entryName" type="string">${compiler:JALVIEW_APPLICATION_NAME}</property>
+                  <property name="file">
+                    <object class="java.io.File">
+                      <string>${installer:sys.contentDir}/${compiler:EXECUTABLE_NAME}</string>
+                    </object>
+                  </property>
+                  <property name="icon">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}</string>
+                    </object>
+                  </property>
+                  <property name="programGroupName" type="string">${compiler:JALVIEW_NAME}</property>
+                  <property name="unixIconFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:JALVIEW_DIR}/${compiler:PNG_ICON_FILE}</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action id="573" beanClass="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make desktop link">
+                <serializedBean>
+                  <property name="allUsers" type="boolean" value="false" />
+                  <property name="description" type="string">${compiler:JALVIEW_APPLICATION_NAME}</property>
+                  <property name="file">
+                    <object class="java.io.File">
+                      <string>${installer:sys.contentDir}/${compiler:EXECUTABLE_NAME}</string>
+                    </object>
+                  </property>
+                  <property name="name" type="string">${compiler:JALVIEW_APPLICATION_NAME}</property>
+                  <property name="unixIconFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:JALVIEW_DIR}/${compiler:PNG_ICON_FILE}</string>
+                    </object>
+                  </property>
+                  <property name="winIconFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("createDesktopLinkAction")</condition>
+              </action>
+              <action id="576" beanClass="com.install4j.runtime.beans.actions.desktop.AddToDockAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="executable">
+                    <object class="java.io.File">
+                      <string>${compiler:JALVIEW_APPLICATION_NAME}.app</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("addToDockAction")</condition>
+              </action>
+              <action name="Linux/Unix symlink" id="2733" beanClass="com.install4j.runtime.beans.actions.files.CreateSymlinkAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make symlink to wrapper script">
+                <serializedBean>
+                  <property name="file">
+                    <object class="java.io.File">
+                      <string>${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}</string>
+                    </object>
+                  </property>
+                  <property name="linkFile">
+                    <object class="java.io.File">
+                      <string>${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:WRAPPER_LINK}</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>Util.isLinux() || Util.isUnixInstaller() || Util.isMacOS()</condition>
+              </action>
+              <action name="Add Jalview bin to the user's path (Windows)" id="2740" beanClass="com.install4j.runtime.beans.actions.misc.ModifyEnvironmentVariableAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not add &quot;${installer:sys.contentDir}\${compiler:WRAPPER_SCRIPT_BIN_DIR}&quot; to the Path environment variable">
+                <serializedBean>
+                  <property name="type" type="enum" class="com.install4j.runtime.beans.actions.misc.ModifyStringType" value="APPEND" />
+                  <property name="value" type="string">${installer:sys.contentDir}\${compiler:WRAPPER_SCRIPT_BIN_DIR}</property>
+                  <property name="variableName" type="string">Path</property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("appendToPathAction")</condition>
+              </action>
+              <action name="Create Linux/Unix symbolic link to jalview.sh in user's local bin" id="2739" beanClass="com.install4j.runtime.beans.actions.files.CreateSymlinkAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make a ${compiler:WRAPPER_LINK} symbolic link in ~/${installer:unixUserBinDir}">
+                <serializedBean>
+                  <property name="file">
+                    <object class="java.io.File">
+                      <string>${installer:sys.contentDir}/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}</string>
+                    </object>
+                  </property>
+                  <property name="linkFile">
+                    <object class="java.io.File">
+                      <string>${installer:unixUserBinDir}/${compiler:WRAPPER_LINK}</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("makeSymbolicLinkAction") &amp;&amp; ( Util.isLinux() || Util.isUnixInstaller() ) &amp;&amp; ( context.getVariable("unixUserBinDir") != null )</condition>
+              </action>
+              <action name="Create macOS symbolic link to jalview in user's local bin" id="2743" beanClass="com.install4j.runtime.beans.actions.files.CreateSymlinkAction" actionElevationType="elevated" rollbackBarrierExitCode="0" errorMessage="Could not make a ${compiler:WRAPPER_LINK} symbolic link in ~/${installer:unixUserBinDir}">
+                <serializedBean>
+                  <property name="file">
+                    <object class="java.io.File">
+                      <string>${installer:macWrapperLinkLocation}</string>
+                    </object>
+                  </property>
+                  <property name="linkFile">
+                    <object class="java.io.File">
+                      <string>${installer:unixUserBinDir}/${compiler:WRAPPER_LINK}</string>
+                    </object>
+                  </property>
+                </serializedBean>
+                <condition>context.getBooleanVariable("makeSymbolicLinkAction") &amp;&amp; Util.isMacOS() &amp;&amp; ( context.getVariable("unixUserBinDir") != null ) &amp;&amp; ( context.getVariable("macWrapperLinkLocation") != null )</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent id="21" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
+                <serializedBean>
+                  <property name="labelText" type="string">${form:finishedMessage}</property>
+                </serializedBean>
+              </formComponent>
+              <formComponent name="Add a desktop link" id="574" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">${i18n:CreateDesktopIcon}</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">createDesktopLinkAction</property>
+                </serializedBean>
+                <visibilityScript>!Util.isMacOS()</visibilityScript>
+              </formComponent>
+              <formComponent name="Add an executable to the dock" id="577" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">${i18n:AddToDock}</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">addToDockAction</property>
+                </serializedBean>
+                <visibilityScript>Util.isMacOS()</visibilityScript>
+              </formComponent>
+              <formComponent name="Add jalview bin to the user's Path environment variable (Windows)" id="2734" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">Add ${compiler:JALVIEW_APPLICATION_NAME}'s bin folder to the Path environment variable</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">appendToPathAction</property>
+                </serializedBean>
+                <visibilityScript>Util.isWindows()</visibilityScript>
+              </formComponent>
+              <formComponent name="Make a symbolic link to jalview.sh bash script in the user's local bin (Linux or Unix)" id="2736" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">Make a ${compiler:WRAPPER_LINK} symbolic link in ${installer:unixUserBinDir}</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">makeSymbolicLinkAction</property>
+                </serializedBean>
+                <visibilityScript>( Util.isLinux() || Util.isUnixInstaller() || ( Util.isMacOS() &amp;&amp; ( context.getVariable("macWrapperLinkLocation") != null ) ) ) &amp;&amp; ( context.getVariable("unixUserBinDir") != null )</visibilityScript>
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+      <application id="uninstaller" beanClass="com.install4j.runtime.beans.applications.UninstallerApplication" styleId="35">
+        <serializedBean>
+          <property name="customMacosExecutableName" type="string">${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</property>
+          <property name="useCustomMacosExecutableName" type="boolean" value="true" />
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group id="147" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <property name="backgroundColor">
+                  <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                    <object class="java.awt.Color">
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                    </object>
+                    <object class="java.awt.Color">
+                      <int>49</int>
+                      <int>52</int>
+                      <int>53</int>
+                      <int>255</int>
+                    </object>
+                  </object>
+                </property>
+                <property name="borderSides">
+                  <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                    <property name="bottom" type="boolean" value="true" />
+                  </object>
+                </property>
+                <property name="imageEdgeBackgroundColor">
+                  <object class="java.awt.Color">
+                    <int>192</int>
+                    <int>192</int>
+                    <int>192</int>
+                    <int>255</int>
+                  </object>
+                </property>
+                <property name="imageEdgeBorder" type="boolean" value="true" />
+                <property name="imageFile">
+                  <object class="com.install4j.api.beans.ExternalFile">
+                    <string>${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}</string>
+                  </object>
+                </property>
+                <property name="insets">
+                  <object class="java.awt.Insets">
+                    <int>5</int>
+                    <int>10</int>
+                    <int>10</int>
+                    <int>10</int>
+                  </object>
+                </property>
+              </serializedBean>
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+        </styleOverrides>
+        <startup>
+          <screen id="23" beanClass="com.install4j.runtime.beans.screens.StartupScreen" rollbackBarrierExitCode="0">
+            <actions>
+              <action id="33" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" />
+              <action id="34" beanClass="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" actionElevationType="none" rollbackBarrierExitCode="0" />
+            </actions>
+          </screen>
+        </startup>
+        <screens>
+          <screen id="24" beanClass="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" rollbackBarrierExitCode="0">
+            <formComponents>
+              <formComponent id="25" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
+                <serializedBean>
+                  <property name="labelText" type="string">${form:welcomeMessage}</property>
+                </serializedBean>
+                <visibilityScript>!context.isConsole()</visibilityScript>
+              </formComponent>
+              <formComponent id="26" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                <serializedBean>
+                  <property name="consoleScript">
+                    <object class="com.install4j.api.beans.ScriptProperty">
+                      <property name="value" type="string">String message = context.getMessage("ConfirmUninstall", context.getApplicationName());
+return console.askYesNo(message, true);
+</property>
+                    </object>
+                  </property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="27" beanClass="com.install4j.runtime.beans.screens.UninstallationScreen" rollbackBarrierExitCode="0">
+            <actions>
+              <action id="659" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="progressChangeType" type="enum" class="com.install4j.runtime.beans.actions.control.ProgressChangeType" value="SET_INDETERMINATE" />
+                </serializedBean>
+              </action>
+              <action id="29" beanClass="com.install4j.runtime.beans.actions.UninstallFilesAction" actionElevationType="elevated" rollbackBarrierExitCode="0" />
+              <action id="660" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="false" actionElevationType="none" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="percentValue" type="int" value="95" />
+                </serializedBean>
+              </action>
+              <action id="1525" beanClass="com.install4j.runtime.beans.actions.files.DeleteFileAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="files" type="array" class="java.io.File" length="40">
+                    <element index="0">
+                      <object class="java.io.File">
+                        <string>jre</string>
+                      </object>
+                    </element>
+                    <element index="1">
+                      <object class="java.io.File">
+                        <string>jre.jar</string>
+                      </object>
+                    </element>
+                    <element index="2">
+                      <object class="java.io.File">
+                        <string>.install4j</string>
+                      </object>
+                    </element>
+                    <element index="3">
+                      <object class="java.io.File">
+                        <string>getdown-launcher.jar</string>
+                      </object>
+                    </element>
+                    <element index="4">
+                      <object class="java.io.File">
+                        <string>getdown-launcher-old.jar</string>
+                      </object>
+                    </element>
+                    <element index="5">
+                      <object class="java.io.File">
+                        <string>getdown-launcher-new.jar</string>
+                      </object>
+                    </element>
+                    <element index="6">
+                      <object class="java.io.File">
+                        <string>gettingdown.lock</string>
+                      </object>
+                    </element>
+                    <element index="7">
+                      <object class="java.io.File">
+                        <string>jre.zip</string>
+                      </object>
+                    </element>
+                    <element index="8">
+                      <object class="java.io.File">
+                        <string>digest.txt</string>
+                      </object>
+                    </element>
+                    <element index="9">
+                      <object class="java.io.File">
+                        <string>digest2.txt</string>
+                      </object>
+                    </element>
+                    <element index="10">
+                      <object class="java.io.File">
+                        <string>getdown-launcher.jarv</string>
+                      </object>
+                    </element>
+                    <element index="11">
+                      <object class="java.io.File">
+                        <string>getdown-launcher-new.jarv</string>
+                      </object>
+                    </element>
+                    <element index="12">
+                      <object class="java.io.File">
+                        <string>launcher.log</string>
+                      </object>
+                    </element>
+                    <element index="13">
+                      <object class="java.io.File">
+                        <string>proxy.txt</string>
+                      </object>
+                    </element>
+                    <element index="14">
+                      <object class="java.io.File">
+                        <string>build_properties</string>
+                      </object>
+                    </element>
+                    <element index="15">
+                      <object class="java.io.File">
+                        <string>channel_launch*.jvl</string>
+                      </object>
+                    </element>
+                    <element index="16">
+                      <object class="java.io.File">
+                        <string>jalview*.jvl</string>
+                      </object>
+                    </element>
+                    <element index="17">
+                      <object class="java.io.File">
+                        <string>*.jarv</string>
+                      </object>
+                    </element>
+                    <element index="18">
+                      <object class="java.io.File">
+                        <string>*.log</string>
+                      </object>
+                    </element>
+                    <element index="19">
+                      <object class="java.io.File">
+                        <string>*.txt</string>
+                      </object>
+                    </element>
+                    <element index="20">
+                      <object class="java.io.File">
+                        <string>*_new</string>
+                      </object>
+                    </element>
+                    <element index="21">
+                      <object class="java.io.File">
+                        <string>hs_err_*.*</string>
+                      </object>
+                    </element>
+                    <element index="22">
+                      <object class="java.io.File">
+                        <string>${compiler:GETDOWN_DIST_DIR}</string>
+                      </object>
+                    </element>
+                    <element index="23">
+                      <object class="java.io.File">
+                        <string>${compiler:GETDOWN_ALT_DIR}</string>
+                      </object>
+                    </element>
+                    <element index="24">
+                      <object class="java.io.File">
+                        <string>${compiler:GETDOWN_RESOURCE_DIR}</string>
+                      </object>
+                    </element>
+                    <element index="25">
+                      <object class="java.io.File">
+                        <string>META-INF</string>
+                      </object>
+                    </element>
+                    <element index="26">
+                      <object class="java.io.File">
+                        <string>install</string>
+                      </object>
+                    </element>
+                    <element index="27">
+                      <object class="java.io.File">
+                        <string>resource</string>
+                      </object>
+                    </element>
+                    <element index="28">
+                      <object class="java.io.File">
+                        <string>dist</string>
+                      </object>
+                    </element>
+                    <element index="29">
+                      <object class="java.io.File">
+                        <string>release</string>
+                      </object>
+                    </element>
+                    <element index="30">
+                      <object class="java.io.File">
+                        <string>alt</string>
+                      </object>
+                    </element>
+                    <element index="31">
+                      <object class="java.io.File">
+                        <string>dev</string>
+                      </object>
+                    </element>
+                    <element index="32">
+                      <object class="java.io.File">
+                        <string>build</string>
+                      </object>
+                    </element>
+                    <element index="33">
+                      <object class="java.io.File">
+                        <string>alt_*</string>
+                      </object>
+                    </element>
+                    <element index="34">
+                      <object class="java.io.File">
+                        <string>dev_*</string>
+                      </object>
+                    </element>
+                    <element index="35">
+                      <object class="java.io.File">
+                        <string>build_*</string>
+                      </object>
+                    </element>
+                    <element index="36">
+                      <object class="java.io.File">
+                        <string>${compiler:WRAPPER_SCRIPT_BIN_DIR}</string>
+                      </object>
+                    </element>
+                    <element index="37">
+                      <object class="java.io.File">
+                        <string>bin</string>
+                      </object>
+                    </element>
+                    <element index="38">
+                      <object class="java.io.File">
+                        <string>channel.props</string>
+                      </object>
+                    </element>
+                    <element index="39">
+                      <object class="java.io.File">
+                        <string>channel.propsv</string>
+                      </object>
+                    </element>
+                  </property>
+                  <property name="recursive" type="boolean" value="true" />
+                </serializedBean>
+              </action>
+              <action id="1791" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" actionElevationType="none" rollbackBarrierExitCode="0">
+                <serializedBean>
+                  <property name="percentValue" type="int" value="100" />
+                </serializedBean>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent id="28" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                <serializedBean>
+                  <property name="initialStatusMessage" type="string">${i18n:UninstallerPreparing}</property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen id="32" beanClass="com.install4j.runtime.beans.screens.UninstallFailureScreen" rollbackBarrierExitCode="0" finishScreen="true" />
+          <screen id="30" beanClass="com.install4j.runtime.beans.screens.UninstallSuccessScreen" styleId="41" rollbackBarrierExitCode="0" finishScreen="true">
+            <formComponents>
+              <formComponent id="31" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
+                <serializedBean>
+                  <property name="labelText" type="string">${form:successMessage}</property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+      <application name="MacOS Setup" id="2746" beanClass="com.install4j.runtime.beans.applications.CustomApplication" fileset="734" macEntitlementsFile="${compiler:JALVIEW_DIR}/utils/osx_signing/entitlements.txt">
+        <serializedBean>
+          <property name="executableDirectory">
+            <object class="java.io.File">
+              <string>.</string>
+            </object>
+          </property>
+          <property name="executableName" type="string">${compiler:WRAPPER_LINK}_setup</property>
+          <property name="windowTitle" type="string">${compiler:sys.fullName}</property>
+        </serializedBean>
+        <startup>
+          <screen id="2747" beanClass="com.install4j.runtime.beans.screens.StartupScreen" rollbackBarrierExitCode="0">
+            <actions>
+              <link id="2749" targetId="22" />
+              <link id="2750" targetId="2738" />
+              <link id="2751" targetId="2745" />
+            </actions>
+          </screen>
+        </startup>
+        <screens>
+          <screen id="2772" beanClass="com.install4j.runtime.beans.screens.FormScreen" rollbackBarrierExitCode="0" backButton="hidden" finishScreen="true">
+            <serializedBean>
+              <property name="title" type="string">Running ${i18n:SetupAppTitle}</property>
+            </serializedBean>
+            <actions>
+              <link id="2778" targetId="576" />
+              <link id="2779" targetId="2743" />
+            </actions>
+            <formComponents>
+              <formComponent id="2773" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
+                <serializedBean>
+                  <property name="labelText" type="string">${i18n:FinishedLabel(${compiler:JALVIEW_APPLICATION_NAME})}</property>
+                </serializedBean>
+              </formComponent>
+              <formComponent name="Add an executable to the dock" id="2775" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">${i18n:AddToDock}</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">addToDockAction</property>
+                </serializedBean>
+                <visibilityScript>Util.isMacOS()</visibilityScript>
+              </formComponent>
+              <formComponent name="Make a symbolic link to jalview.sh bash script in the user's local bin (Linux or Unix)" id="2777" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                <serializedBean>
+                  <property name="checkboxText" type="string">Make a ${compiler:WRAPPER_LINK} symbolic link in ${installer:unixUserBinDir}</property>
+                  <property name="initiallySelected" type="boolean" value="true" />
+                  <property name="variableName" type="string">makeSymbolicLinkAction</property>
+                </serializedBean>
+                <visibilityScript>( Util.isLinux() || Util.isUnixInstaller() || ( Util.isMacOS() &amp;&amp; ( context.getVariable("macWrapperLinkLocation") != null ) ) ) &amp;&amp; ( context.getVariable("unixUserBinDir") != null )</visibilityScript>
+              </formComponent>
+              <formComponent id="2780" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                <serializedBean>
+                  <property name="labelText" type="string">
+${i18n:ClickFinish}
+
+${compiler:JALVIEW_APPLICATION_NAME} will now launch.</property>
+                </serializedBean>
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+    </applications>
+    <styles defaultStyleId="35">
+      <style name="Standard" id="35" beanClass="com.install4j.runtime.beans.styles.FormStyle">
+        <formComponents>
+          <formComponent name="Header" id="36" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetTop="0" insetBottom="0">
+            <serializedBean>
+              <property name="styleId" type="string">48</property>
+            </serializedBean>
+          </formComponent>
+          <group name="Main" id="37" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+            <serializedBean>
+              <property name="imageEdgeAxisType" type="enum" class="com.install4j.runtime.beans.formcomponents.AxisType" value="HORIZONTAL" />
+              <property name="imageEdgeBackgroundColor">
+                <object class="java.awt.Color">
+                  <int>255</int>
+                  <int>255</int>
+                  <int>255</int>
+                  <int>255</int>
+                </object>
+              </property>
+              <property name="imageFile">
+                <object class="com.install4j.api.beans.ExternalFile">
+                  <string>${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}</string>
+                </object>
+              </property>
+              <property name="imageOverlap" type="boolean" value="true" />
+            </serializedBean>
+            <beans>
+              <formComponent id="38" beanClass="com.install4j.runtime.beans.styles.ContentComponent" insetTop="10" insetLeft="20" insetBottom="10" insetRight="20" />
+              <formComponent name="Watermark" id="39" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" insetTop="0" insetLeft="5" insetBottom="0" useExternalParametrization="true" externalParametrizationName="${compiler:JALVIEW_APPLICATION_NAME}" externalParametrizationMode="include">
+                <serializedBean>
+                  <property name="enabledTitleText" type="boolean" value="false" />
+                </serializedBean>
+                <externalParametrizationPropertyNames>
+                  <propertyName>labelText</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="Footer" id="40" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetTop="0" insetBottom="0">
+                <serializedBean>
+                  <property name="styleId" type="string">52</property>
+                </serializedBean>
+              </formComponent>
+            </beans>
+          </group>
+        </formComponents>
+      </style>
+      <style name="Banner" id="41" beanClass="com.install4j.runtime.beans.styles.FormStyle">
+        <formComponents>
+          <group id="42" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+            <serializedBean>
+              <property name="backgroundColor">
+                <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                  <object class="java.awt.Color">
+                    <int>255</int>
+                    <int>255</int>
+                    <int>255</int>
+                    <int>255</int>
+                  </object>
+                  <object class="java.awt.Color">
+                    <int>49</int>
+                    <int>52</int>
+                    <int>53</int>
+                    <int>255</int>
+                  </object>
+                </object>
+              </property>
+              <property name="borderSides">
+                <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                  <property name="bottom" type="boolean" value="true" />
+                </object>
+              </property>
+              <property name="imageEdgeBackgroundColor">
+                <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                  <object class="java.awt.Color">
+                    <int>25</int>
+                    <int>143</int>
+                    <int>220</int>
+                    <int>255</int>
+                  </object>
+                  <object class="java.awt.Color">
+                    <int>0</int>
+                    <int>74</int>
+                    <int>151</int>
+                    <int>255</int>
+                  </object>
+                </object>
+              </property>
+              <property name="imageEdgeBorder" type="boolean" value="true" />
+              <property name="imageFile">
+                <object class="com.install4j.api.beans.ExternalFile">
+                  <string>${compiler:sys.install4jHome}/resource/styles/wizard.png</string>
+                </object>
+              </property>
+              <property name="insets">
+                <object class="java.awt.Insets">
+                  <int>5</int>
+                  <int>10</int>
+                  <int>10</int>
+                  <int>10</int>
+                </object>
+              </property>
+            </serializedBean>
+            <beans>
+              <formComponent id="43" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" insetTop="0">
+                <serializedBean>
+                  <property name="labelFontSizePercent" type="int" value="130" />
+                  <property name="labelFontStyle" type="enum" class="com.install4j.runtime.beans.formcomponents.FontStyle" value="BOLD" />
+                  <property name="labelFontType" type="enum" class="com.install4j.runtime.beans.formcomponents.FontType" value="DERIVED" />
+                </serializedBean>
+              </formComponent>
+              <formComponent id="44" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" />
+              <formComponent id="45" beanClass="com.install4j.runtime.beans.styles.ContentComponent" insetTop="10" insetBottom="0" />
+            </beans>
+            <externalParametrizationPropertyNames>
+              <propertyName>imageAnchor</propertyName>
+              <propertyName>imageEdgeBackgroundColor</propertyName>
+              <propertyName>imageFile</propertyName>
+            </externalParametrizationPropertyNames>
+          </group>
+          <formComponent id="46" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetBottom="0">
+            <serializedBean>
+              <property name="styleId" type="string">52</property>
+            </serializedBean>
+          </formComponent>
+        </formComponents>
+      </style>
+      <group name="Style components" id="47" beanClass="com.install4j.runtime.beans.groups.StyleGroup">
+        <beans>
+          <style name="Standard header" id="48" beanClass="com.install4j.runtime.beans.styles.FormStyle">
+            <serializedBean>
+              <property name="fillVertical" type="boolean" value="false" />
+              <property name="standalone" type="boolean" value="false" />
+              <property name="verticalAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="NORTH" />
+            </serializedBean>
+            <formComponents>
+              <group id="49" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize title bar" externalParametrizationMode="include">
+                <serializedBean>
+                  <property name="backgroundColor">
+                    <object class="com.install4j.runtime.beans.LightOrDarkColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                      <object class="java.awt.Color">
+                        <int>49</int>
+                        <int>52</int>
+                        <int>53</int>
+                        <int>255</int>
+                      </object>
+                    </object>
+                  </property>
+                  <property name="borderSides">
+                    <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                      <property name="bottom" type="boolean" value="true" />
+                    </object>
+                  </property>
+                  <property name="imageAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="NORTHEAST" />
+                  <property name="imageEdgeBorderWidth" type="int" value="2" />
+                  <property name="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>icon:${installer:sys.installerApplicationMode}_header.png</string>
+                    </object>
+                  </property>
+                  <property name="imageInsets">
+                    <object class="java.awt.Insets">
+                      <int>0</int>
+                      <int>5</int>
+                      <int>1</int>
+                      <int>1</int>
+                    </object>
+                  </property>
+                  <property name="insets">
+                    <object class="java.awt.Insets">
+                      <int>0</int>
+                      <int>20</int>
+                      <int>0</int>
+                      <int>10</int>
+                    </object>
+                  </property>
+                </serializedBean>
+                <beans>
+                  <formComponent name="Title" id="50" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                    <serializedBean>
+                      <property name="labelFontStyle" type="enum" class="com.install4j.runtime.beans.formcomponents.FontStyle" value="BOLD" />
+                      <property name="labelFontType" type="enum" class="com.install4j.runtime.beans.formcomponents.FontType" value="DERIVED" />
+                    </serializedBean>
+                  </formComponent>
+                  <formComponent name="Subtitle" id="51" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" insetLeft="8">
+                    <serializedBean>
+                      <property name="titleType" type="enum" class="com.install4j.runtime.beans.styles.TitleType" value="SUB_TITLE" />
+                    </serializedBean>
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames>
+                  <propertyName>backgroundColor</propertyName>
+                  <propertyName>foregroundColor</propertyName>
+                  <propertyName>imageAnchor</propertyName>
+                  <propertyName>imageFile</propertyName>
+                  <propertyName>imageOverlap</propertyName>
+                </externalParametrizationPropertyNames>
+              </group>
+            </formComponents>
+          </style>
+          <style name="Standard footer" id="52" beanClass="com.install4j.runtime.beans.styles.FormStyle">
+            <serializedBean>
+              <property name="fillVertical" type="boolean" value="false" />
+              <property name="standalone" type="boolean" value="false" />
+              <property name="verticalAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="SOUTH" />
+            </serializedBean>
+            <formComponents>
+              <group id="53" beanClass="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup">
+                <serializedBean>
+                  <property name="alignFirstLabel" type="boolean" value="false" />
+                  <property name="insets">
+                    <object class="java.awt.Insets">
+                      <int>3</int>
+                      <int>5</int>
+                      <int>8</int>
+                      <int>5</int>
+                    </object>
+                  </property>
+                </serializedBean>
+                <beans>
+                  <formComponent id="54" beanClass="com.install4j.runtime.beans.formcomponents.SpringComponent" />
+                  <formComponent name="Back button" id="55" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                    <serializedBean>
+                      <property name="buttonText" type="string">&lt; ${i18n:ButtonBack}</property>
+                      <property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="PREVIOUS" />
+                    </serializedBean>
+                  </formComponent>
+                  <formComponent name="Next button" id="56" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                    <serializedBean>
+                      <property name="buttonText" type="string">${i18n:ButtonNext} &gt;</property>
+                      <property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="NEXT" />
+                    </serializedBean>
+                  </formComponent>
+                  <formComponent name="Cancel button" id="57" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" insetLeft="5">
+                    <serializedBean>
+                      <property name="buttonText" type="string">${i18n:ButtonCancel}</property>
+                      <property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="CANCEL" />
+                    </serializedBean>
+                  </formComponent>
+                </beans>
+              </group>
+            </formComponents>
+          </style>
+        </beans>
+      </group>
+    </styles>
+  </installerGui>
+  <mediaSets>
+    <windows name="Windows x64 EXE Installer" id="743" customizedId="WINDOWS-X64-EXE" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:APPLICATION_FOLDER}" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" customInstallBaseDir="~/AppData/Local" architecture="64">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:WINDOWS_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </windows>
+    <macosArchive name="macOS x64 Disk Image" id="878" customizedId="MACOS-X64-DMG" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-macOS-x64-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" volumeName="${compiler:INSTALLER_NAME}" launcherId="737" setupAppId="2746">
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="none" includedJre="${compiler:MACOS_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_BG_IMAGE}" />
+        <file name=".DS_Store" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_DS_STORE}" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/Jalview-File.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/Jalview-File.icns" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/jvl_file.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/jvl_file.icns" />
+        <symlink name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/MacOS/${compiler:WRAPPER_LINK}" target="../Resources/app/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}" />
+      </topLevelFiles>
+    </macosArchive>
+    <macosArchive name="macOS aarch64 Disk Image" id="2796" customizedId="MACOS-AARCH64-DMG" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-macOS-aarch64-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" volumeName="${compiler:INSTALLER_NAME}" architecture="aarch64" launcherId="737" setupAppId="2746">
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+      </exclude>
+      <jreBundle jreBundleSource="none" />
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_BG_IMAGE}" />
+        <file name=".DS_Store" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_DS_STORE}" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/Jalview-File.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/Jalview-File.icns" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/jvl_file.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/jvl_file.icns" />
+        <symlink name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/MacOS/${compiler:WRAPPER_LINK}" target="../Resources/app/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}" />
+      </topLevelFiles>
+    </macosArchive>
+    <unixInstaller name="Linux x64 Shell Installer" id="1595" customizedId="LINUX-X64-SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-linux-x64-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </unixInstaller>
+    <unixInstaller name="Linux aarch64 Shell Installer" id="2782" customizedId="LINUX-AARCH64-SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-linux-aarch64-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:LINUX_AARCH64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </unixInstaller>
+    <unixArchive name="Unix .tar.gz Archive" id="1596" customizedId="UNIX--TGZ" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </unixArchive>
+    <unixInstaller name="Unix Shell Installer" id="2639" customizedId="UNIX--SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </unixInstaller>
+  </mediaSets>
+  <buildIds>
+    <mediaSet refId="743" />
+    <mediaSet refId="878" />
+    <mediaSet refId="2796" />
+    <mediaSet refId="1595" />
+    <mediaSet refId="2782" />
+    <mediaSet refId="1596" />
+    <mediaSet refId="2639" />
+  </buildIds>
+</install4j>
index 23ff9c9..979b1a9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<install4j version="9.0.5" transformSequenceNumber="9">
+<install4j version="9.0.7" transformSequenceNumber="9">
   <directoryPresets config="bin/jalview" />
   <application name="${compiler:JALVIEW_APPLICATION_NAME}" applicationId="${compiler:WINDOWS_APPLICATION_ID}" mediaDir="${compiler:BUILD_DIR}" lzmaCompression="true" shortName="${compiler:INTERNAL_ID}" publisher="University of Dundee" publisherWeb="https://www.jalview.org/" version="${compiler:JALVIEW_VERSION}" allPathsRelative="true" macVolumeId="5aac4968c304f65" javaMinVersion="${compiler:JAVA_MIN_VERSION}" javaMaxVersion="${compiler:JAVA_MAX_VERSION}" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
     <searchSequence>
       <variable name="JAVA_VERSION" value="11" />
       <variable name="JAVA_INTEGER_VERSION" value="11" />
       <variable name="VERSION" value="DEVELOPMENT" />
-      <variable name="MACOS_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-mac-x64/jre" />
-      <variable name="WINDOWS_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-windows-x64/jre" />
-      <variable name="LINUX_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-linux-x64/jre" />
-      <variable name="MACOS_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_mac_x64.tar.gz" />
-      <variable name="WINDOWS_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_windows_x64.tar.gz" />
-      <variable name="LINUX_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_linux_x64.tar.gz" />
+      <variable name="MACOS_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-mac-x64/jre" />
+      <variable name="MACOS_AARCH64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-mac-aarch64/jre" />
+      <variable name="WINDOWS_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-windows-x64/jre" />
+      <variable name="LINUX_X64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-linux-x64/jre" />
+      <variable name="LINUX_AARCH64_JAVA_VM_DIR" value="${compiler:env.HOME}/buildtools/jre/jre-${compiler:JAVA_INTEGER_VERSION}-linux-aarch64/jre" />
+      <variable name="MACOS_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_mac_x64.tar.gz" />
+      <variable name="MACOS_AARCH64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_mac_aarch64.tar.gz" />
+      <variable name="WINDOWS_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_windows_x64.tar.gz" />
+      <variable name="LINUX_X64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_linux_x64.tar.gz" />
+      <variable name="LINUX_AARCH64_JAVA_VM_TGZ" value="${compiler:env.HOME}/buildtools/jre/tgz/jre_${compiler:JAVA_INTEGER_VERSION}_linux_aarch64.tar.gz" />
       <variable name="COPYRIGHT_MESSAGE" value="..." />
       <variable name="BUNDLE_ID" value="org.jalview.jalview-desktop" />
       <variable name="INTERNAL_ID" value="Jalview" />
       <variable name="WINDOWS_APPLICATION_ID" value="6595-2347-1923-0725" />
-      <variable name="MACOS_DMG_DS_STORE" value="jalview_dmg_DS_Store" />
-      <variable name="MACOS_DMG_BG_IMAGE" value="jalview_dmg_background-72dpi.png" />
+      <variable name="MACOS_DMG_DS_STORE" value="utils/channels/default/images/jalview_default_dmg_DS_Store" />
+      <variable name="MACOS_DMG_BG_IMAGE" value="utils/channels/default/images/jalview_default_dmg_background-72dpi.png" />
       <variable name="WRAPPER_LINK" value="jalview" />
       <variable name="BASH_WRAPPER_SCRIPT" value="jalview.sh" />
       <variable name="WRAPPER_SCRIPT_BIN_DIR" value="bin" />
   <files defaultUninstallMode="2" preserveSymlinks="false">
     <filesets>
       <fileset name="Full file set" id="734" customizedId="FULL_FILE_SET" />
-      <fileset name="Mac OS X JRE" id="880" />
-      <fileset name="Windows JRE" id="882" />
-      <fileset name="Jalview application" id="1873" />
+      <fileset name="macOS x64 JVM" id="2801" customizedId="MACOS_X64_JVM" />
+      <fileset name="macOS aarch64 JVM" id="2803" customizedId="MACOS_AARCH64_JVM" />
     </filesets>
     <roots>
       <root id="735" fileset="734" />
-      <root id="881" fileset="880" />
-      <root id="883" fileset="882" />
-      <root id="1874" fileset="1873" />
+      <root id="2802" fileset="2801" />
+      <root id="2804" fileset="2803" />
     </roots>
     <mountPoints>
-      <mountPoint id="454" />
       <mountPoint id="736" root="735" />
-      <mountPoint id="884" root="881" />
-      <mountPoint id="885" root="883" />
-      <mountPoint id="1875" root="1874" />
+      <mountPoint id="2805" root="2802" />
+      <mountPoint id="2806" root="2804" />
     </mountPoints>
     <entries>
-      <dirEntry mountPoint="454" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_FILES_DIR}/${compiler:JAVA_VERSION}" uninstallMode="2" overrideOverwriteMode="true" overrideUninstallMode="true" subDirectory="files" />
       <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_CHANNEL_DIR}/${compiler:JAVA_VERSION}" uninstallMode="2" overrideOverwriteMode="true" overrideUninstallMode="true" subDirectory="files">
         <exclude>
           <entry location="${compiler:WRAPPER_SCRIPT_BIN_DIR}" />
       </dirEntry>
       <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/examples" overwriteMode="1" uninstallMode="2" overrideFileMode="true" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="examples" />
       <dirEntry mountPoint="736" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_CHANNEL_DIR}/${compiler:JAVA_VERSION}/${compiler:WRAPPER_SCRIPT_BIN_DIR}" fileMode="755" overrideFileMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:WRAPPER_SCRIPT_BIN_DIR}" overrideDirMode="true" />
-      <dirEntry mountPoint="884" file="${compiler:MACOS_JAVA_VM_DIR}" fileMode="755" overrideFileMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
-      <dirEntry mountPoint="885" file="${compiler:WINDOWS_JAVA_VM_DIR}" fileMode="755" overrideFileMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
-      <dirEntry mountPoint="1875" file="${compiler:JALVIEW_DIR}/${compiler:GETDOWN_CHANNEL_DIR}/${compiler:JAVA_VERSION}/${compiler:GETDOWN_DIST_DIR}" overwriteMode="1" uninstallMode="2" overrideFileMode="true" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:GETDOWN_DIST_DIR}" overrideDirMode="true" />
+      <dirEntry mountPoint="2805" file="${compiler:MACOS_X64_JAVA_VM_DIR}" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
+      <dirEntry mountPoint="2806" file="${compiler:MACOS_AARCH64_JAVA_VM_DIR}" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" />
     </entries>
     <components>
       <component name="jalview_getdown" id="1031">
           <entry filesetId="734" />
         </include>
       </component>
-      <component name="macos_java_vm" id="1155">
-        <include>
-          <entry filesetId="880" />
-        </include>
-      </component>
-      <component name="windows_java_vm" id="1156">
-        <include>
-          <entry filesetId="882" />
-        </include>
-      </component>
-      <component name="getdown" id="1276">
-        <include>
-          <entry defaultFileset="true" />
-        </include>
-      </component>
-      <component name="jalview" id="1881">
-        <include>
-          <entry filesetId="1873" />
-        </include>
-      </component>
     </components>
   </files>
   <launchers>
@@ -1421,31 +1399,35 @@ ${compiler:JALVIEW_APPLICATION_NAME} will now launch.</property>
   </installerGui>
   <mediaSets>
     <windows name="Windows x64 EXE Installer" id="743" customizedId="WINDOWS-X64-EXE" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:APPLICATION_FOLDER}" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" customInstallBaseDir="~/AppData/Local">
-      <excludedComponents>
-        <component id="1155" />
-        <component id="1156" />
-        <component id="1276" />
-        <component id="1881" />
-      </excludedComponents>
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
       <exclude>
         <entry defaultFileset="true" />
-        <entry filesetId="880" />
-        <entry filesetId="882" />
-        <entry filesetId="1873" />
-        <entry filesetId="2105" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
       </exclude>
-      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:WINDOWS_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:WINDOWS_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
     </windows>
-    <macosArchive name="macOS Disk Image" id="878" customizedId="MACOS-X64-DMG" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" volumeName="${compiler:INSTALLER_NAME}" launcherId="737" executeSetupApp="true" setupAppId="2746">
-      <excludedComponents>
-        <component id="1156" />
-        <component id="1276" />
-        <component id="1881" />
-      </excludedComponents>
+    <macosArchive name="macOS x64 Disk Image" id="878" customizedId="MACOS-X64-DMG" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-macOS-x64-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" volumeName="${compiler:INSTALLER_NAME}" launcherId="737" setupAppId="2746">
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="none" includedJre="${compiler:MACOS_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_BG_IMAGE}" />
+        <file name=".DS_Store" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_DS_STORE}" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/Jalview-File.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/Jalview-File.icns" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/jvl_file.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/jvl_file.icns" />
+        <symlink name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/MacOS/${compiler:WRAPPER_LINK}" target="../Resources/app/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}" />
+      </topLevelFiles>
+    </macosArchive>
+    <macosArchive name="macOS aarch64 Disk Image" id="2796" customizedId="MACOS-AARCH64-DMG" mediaFileName="${compiler:APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-macOS-aarch64-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" volumeName="${compiler:INSTALLER_NAME}" architecture="aarch64" launcherId="737" setupAppId="2746">
       <exclude>
         <entry defaultFileset="true" />
-        <entry filesetId="882" />
-        <entry filesetId="1873" />
+        <entry filesetId="2801" />
       </exclude>
       <jreBundle jreBundleSource="none" />
       <topLevelFiles>
@@ -1457,59 +1439,57 @@ ${compiler:JALVIEW_APPLICATION_NAME} will now launch.</property>
         <symlink name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/MacOS/${compiler:WRAPPER_LINK}" target="../Resources/app/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}" />
       </topLevelFiles>
     </macosArchive>
-    <unixInstaller name="Linux x64 Shell Installer" id="1595" customizedId="LINUX-X64-SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-linux_x64-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
-      <excludedComponents>
-        <component id="1155" />
-        <component id="1156" />
-        <component id="1276" />
-        <component id="1881" />
-      </excludedComponents>
+    <unixInstaller name="Linux x64 Shell Installer" id="1595" customizedId="LINUX-X64-SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-linux-x64-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
+      <exclude>
+        <entry defaultFileset="true" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
+      </exclude>
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
+    </unixInstaller>
+    <unixInstaller name="Linux aarch64 Shell Installer" id="2782" customizedId="LINUX-AARCH64-SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-linux-aarch64-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
       <exclude>
         <entry defaultFileset="true" />
-        <entry filesetId="880" />
-        <entry filesetId="882" />
-        <entry filesetId="1873" />
-        <entry filesetId="2105" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
       </exclude>
-      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:LINUX_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <jreBundle jreBundleSource="preCreated" includedJre="${compiler:LINUX_AARCH64_JAVA_VM_TGZ}" manualJreEntry="true" />
     </unixInstaller>
     <unixArchive name="Unix .tar.gz Archive" id="1596" customizedId="UNIX--TGZ" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}">
-      <excludedComponents>
-        <component id="1155" />
-        <component id="1156" />
-        <component id="1276" />
-        <component id="1881" />
-      </excludedComponents>
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
       <exclude>
         <entry defaultFileset="true" />
-        <entry filesetId="880" />
-        <entry filesetId="882" />
-        <entry filesetId="1873" />
-        <entry filesetId="2105" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
       </exclude>
-      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
     </unixArchive>
     <unixInstaller name="Unix Shell Installer" id="2639" customizedId="UNIX--SH" mediaFileName="${compiler:UNIX_APPLICATION_FOLDER}-${compiler:JALVIEW_VERSION}-${compiler:sys.platform}-java_${compiler:JAVA_INTEGER_VERSION}" installDir="${compiler:UNIX_APPLICATION_FOLDER}" customInstallBaseDir="~/opt/">
-      <excludedComponents>
-        <component id="1155" />
-        <component id="1156" />
-        <component id="1276" />
-        <component id="1881" />
-      </excludedComponents>
+      <excludedBeans>
+        <bean refId="2746" />
+      </excludedBeans>
       <exclude>
         <entry defaultFileset="true" />
-        <entry filesetId="880" />
-        <entry filesetId="882" />
-        <entry filesetId="1873" />
-        <entry filesetId="2105" />
+        <entry filesetId="2801" />
+        <entry filesetId="2803" />
       </exclude>
-      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_JAVA_VM_TGZ}" manualJreEntry="true" />
+      <jreBundle jreBundleSource="none" includedJre="${compiler:LINUX_X64_JAVA_VM_TGZ}" manualJreEntry="true" />
     </unixInstaller>
   </mediaSets>
   <buildIds>
     <mediaSet refId="743" />
     <mediaSet refId="878" />
+    <mediaSet refId="2796" />
     <mediaSet refId="1595" />
+    <mediaSet refId="2782" />
     <mediaSet refId="1596" />
     <mediaSet refId="2639" />
   </buildIds>