From: Ben Soares Date: Wed, 16 Nov 2022 11:52:30 +0000 (+0000) Subject: Merge branch 'improvement/JAL-3416_default_to_LIVE_DRAG_MODE_for_flatlaf' into merge... X-Git-Tag: Release_2_11_3_0~23^2~4^2~9 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=3b0aa1a072e9c18ce9b7040204584ce810803bb6;hp=7f0c5c453a345a700b9e19caebaf7a3b55783759;p=jalview.git Merge branch 'improvement/JAL-3416_default_to_LIVE_DRAG_MODE_for_flatlaf' into merge/JAL-1988_JAL-3772+JAL-3416+JAL-4054+JAL-4064 --- diff --git a/build.gradle b/build.gradle index bde50b5..851c5f6 100644 --- a/build.gradle +++ b/build.gradle @@ -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}") } diff --git a/gradle.properties b/gradle.properties index acb65e9..e42f8b5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 3843ddb..a406f33 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -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... diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index d0bfd65..b50226a 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -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 diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 370a243..bb70c40 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -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 diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index a241abe..50c98b4 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -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() diff --git a/src/jalview/jbgui/APQHandlers.java b/src/jalview/gui/APQHandlers.java similarity index 55% rename from src/jalview/jbgui/APQHandlers.java rename to src/jalview/gui/APQHandlers.java index 1a7e971..00ec217 100644 --- a/src/jalview/jbgui/APQHandlers.java +++ b/src/jalview/gui/APQHandlers.java @@ -18,15 +18,11 @@ * along with Jalview. If not, see . * 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. diff --git a/src/jalview/gui/AlignExportOptions.java b/src/jalview/gui/AlignExportOptions.java index 08ff021..a23cbfa 100644 --- a/src/jalview/gui/AlignExportOptions.java +++ b/src/jalview/gui/AlignExportOptions.java @@ -20,21 +20,22 @@ */ 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); } diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 95b9db7..0a6fabc 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -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 cancelAction = () -> { + lastSaveSuccessful = false; + return null; + }; + Callable 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 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); diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 30ccdbe..e7c237e 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -20,6 +20,17 @@ */ 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"), diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java index 9976604..4702f2a 100755 --- a/src/jalview/gui/AnnotationLabels.java +++ b/src/jalview/gui/AnnotationLabels.java @@ -20,23 +20,6 @@ */ 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; }); } diff --git a/src/jalview/gui/Console.java b/src/jalview/gui/Console.java index 9cf2cc9..5a23048 100644 --- a/src/jalview/gui/Console.java +++ b/src/jalview/gui/Console.java @@ -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); } diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 8ed4261..6a67148 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -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) history).getEditor().getItem() - .toString().trim()); + Callable action = () -> { + @SuppressWarnings("unchecked") + String url = (history instanceof JTextField + ? ((JTextField) history).getText() + : ((JComboBox) 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 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(); } }); } diff --git a/src/jalview/gui/EditNameDialog.java b/src/jalview/gui/EditNameDialog.java index f7225f5..ff0fe3a 100644 --- a/src/jalview/gui/EditNameDialog.java +++ b/src/jalview/gui/EditNameDialog.java @@ -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") }; diff --git a/src/jalview/gui/FeatureEditor.java b/src/jalview/gui/FeatureEditor.java index 844eee4..ba9da67 100644 --- a/src/jalview/gui/FeatureEditor.java +++ b/src/jalview/gui/FeatureEditor.java @@ -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 okAction = forCreate ? getCreateAction() + : getAmendAction(); + Callable 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 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 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 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 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; diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index e8e6f1a..2c8f47a 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -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); } diff --git a/src/jalview/gui/ImageExporter.java b/src/jalview/gui/ImageExporter.java index ce1cb46..d849ba2 100644 --- a/src/jalview/gui/ImageExporter.java +++ b/src/jalview/gui/ImageExporter.java @@ -20,6 +20,12 @@ */ 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 *
    @@ -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 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() { @Override - public void run() + public Void call() { setStatus(MessageManager.formatMessage( "status.cancelled_image_export_operation", imageType.getName()), messageId); + return null; } }); epsOption.setResponseAction(0, okAction); diff --git a/src/jalview/gui/JvOptionPane.java b/src/jalview/gui/JvOptionPane.java index 5d69a53..0e0b13d 100644 --- a/src/jalview/gui/JvOptionPane.java +++ b/src/jalview/gui/JvOptionPane.java @@ -18,7 +18,6 @@ * along with Jalview. If not, see . * 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 callbacks = new HashMap<>(); + private Map> 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 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 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 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() { + * + * @Override public Void call() { action.run(); return null; } }); return this; + * } + */ @Override - public JvOptionPane setResponseHandler(Object response, Runnable action) + public JvOptionPane setResponseHandler(Object response, + Callable action) { callbacks.put(response, action); return this; } /** + * showDialogOnTop will create a dialog that (attempts to) come to top of OS + * desktop windows + */ + public static int showDialogOnTop(String label, String actionString, + int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) + { + 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 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 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 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; } /** diff --git a/src/jalview/gui/LineartOptions.java b/src/jalview/gui/LineartOptions.java index d55733c..8a530ac 100644 --- a/src/jalview/gui/LineartOptions.java +++ b/src/jalview/gui/LineartOptions.java @@ -20,12 +20,10 @@ */ 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); } diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index f4a19ff..1c03d6a 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -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 index 0000000..77eed81 --- /dev/null +++ b/src/jalview/gui/QuitHandler.java @@ -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 performQuit = () -> { + response.performQuit(); + setResponse(QResponse.QUIT); + return null; + }; + Callable performForceQuit = () -> { + response.performQuit(); + setResponse(QResponse.FORCE_QUIT); + return null; + }; + Callable 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 defaultCancelQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CANCELLED by user"); + // reset + setResponse(QResponse.CANCEL_QUIT); + return null; + }; + + public static final Callable defaultOkQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CONFIRMED by user"); + setResponse(QResponse.QUIT); + return null; + }; + + public static final Callable 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 okQuit, + Callable forceQuit, Callable 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 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 okQuit, Callable forceQuit, + Callable 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 avpList = af.getAlignPanels(); + for (AlignmentViewPanel avp : avpList) + { + AlignmentI a = avp.getAlignment(); + List 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 filesAllSaved = new CompletableFuture<>(); + + // callback as each file finishes saving + for (CompletableFuture 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 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 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 diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 07eec2b..dbd270f 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -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 diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 6c3f6d8..1e12f7f 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -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; diff --git a/src/jalview/gui/TextColourChooser.java b/src/jalview/gui/TextColourChooser.java index f9ff337..e72a084 100644 --- a/src/jalview/gui/TextColourChooser.java +++ b/src/jalview/gui/TextColourChooser.java @@ -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 action = () -> // response for 1 = Cancel { - @Override - public void run() - { - restoreInitialSettings(); - } + restoreInitialSettings(); + return null; }; JvOptionPane.newOptionDialog(alignPanel.alignFrame) .setResponseHandler(1, action).showInternalDialog(bigpanel, diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index d252910..298b8b4 100755 --- a/src/jalview/gui/UserDefinedColours.java +++ b/src/jalview/gui/UserDefinedColours.java @@ -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); diff --git a/src/jalview/io/BackupFiles.java b/src/jalview/io/BackupFiles.java index 2039d3c..9112042 100644 --- a/src/jalview/io/BackupFiles.java +++ b/src/jalview/io/BackupFiles.java @@ -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 savesInProgress = new ArrayList<>(); + + private CompletableFuture 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 getNewFuture() + { + return new CompletableFuture() + { + }; + } + + private CompletableFuture 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 savesInProgressFiles(boolean all) + { + List files = new ArrayList<>(); + for (BackupFiles bfile : savesInProgress) + { + if (all || !bfile.getMyFuture().isDone()) + files.add(bfile.getFile()); + } + return files; + } + + public static List> savesInProgressCompletableFutures( + boolean all) + { + List> cfs = new ArrayList<>(); + for (BackupFiles bfile : savesInProgress) + { + if (all || !bfile.getMyFuture().isDone()) + cfs.add(bfile.getMyFuture()); + } + return cfs; + } + + public static Future allSaved() + { + CompletableFuture 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(); diff --git a/src/jalview/io/FileLoader.java b/src/jalview/io/FileLoader.java index 4016e71..ffeb53d 100755 --- a/src/jalview/io/FileLoader.java +++ b/src/jalview/io/FileLoader.java @@ -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) { diff --git a/src/jalview/io/HtmlSvgOutput.java b/src/jalview/io/HtmlSvgOutput.java index 4b66f81..9fb3720 100644 --- a/src/jalview/io/HtmlSvgOutput.java +++ b/src/jalview/io/HtmlSvgOutput.java @@ -20,23 +20,24 @@ */ 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 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(); diff --git a/src/jalview/io/JalviewFileChooser.java b/src/jalview/io/JalviewFileChooser.java index 6dbf4f8..3b6e325 100755 --- a/src/jalview/io/JalviewFileChooser.java +++ b/src/jalview/io/JalviewFileChooser.java @@ -21,6 +21,12 @@ ////////////////////////////////////////////////////////////////// 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 callbacks = new HashMap<>(); + private Map 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() + { + @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(); + } } } diff --git a/src/jalview/jbgui/GDesktop.java b/src/jalview/jbgui/GDesktop.java index ca95222..b0079b4 100755 --- a/src/jalview/jbgui/GDesktop.java +++ b/src/jalview/jbgui/GDesktop.java @@ -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")); diff --git a/src/jalview/project/Jalview2XML.java b/src/jalview/project/Jalview2XML.java index d4b2c04..c8f9be6 100644 --- a/src/jalview/project/Jalview2XML.java +++ b/src/jalview/project/Jalview2XML.java @@ -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 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; + } } diff --git a/src/jalview/util/dialogrunner/DialogRunnerI.java b/src/jalview/util/dialogrunner/DialogRunnerI.java index fde80f7..1fc41e7 100644 --- a/src/jalview/util/dialogrunner/DialogRunnerI.java +++ b/src/jalview/util/dialogrunner/DialogRunnerI.java @@ -20,6 +20,8 @@ */ package jalview.util.dialogrunner; +import java.util.concurrent.Callable; + /** * An interface for blocking dialog response handling. This is motivated by * JalviewJS - when running as Javascript, there is only a single thread, and @@ -41,7 +43,9 @@ public interface DialogRunnerI * @param action * @return */ - DialogRunnerI setResponseHandler(Object response, Runnable action); + DialogRunnerI setResponseHandler(Object response, Callable action); + + // DialogRunnerI setResponseHandler(Object response, Runnable action); /** * Runs the registered handler (if any) for the given response. The default diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 08af2ec..5a4ceb9 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -20,6 +20,18 @@ */ 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 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 index 0000000..b1dab96 --- /dev/null +++ b/test/jalview/gui/QuitHandlerTest.java @@ -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 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 index 0000000..be2b1cb --- /dev/null +++ b/test/jalview/gui/quitProps.jvprops @@ -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 diff --git a/utils/download_jdks.sh b/utils/download_jdks.sh index cf8634a..b6e8379 100755 --- a/utils/download_jdks.sh +++ b/utils/download_jdks.sh @@ -15,26 +15,64 @@ ### 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/]*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 + diff --git a/utils/download_jres.sh b/utils/download_jres.sh index 26442ea..c1a5a0d 100755 --- a/utils/download_jres.sh +++ b/utils/download_jres.sh @@ -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/]*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 index 0000000..f7e17a1 --- /dev/null +++ b/utils/install4j/auto_file_associations-i4j10.pl @@ -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(){ + $mactemplate .= $_; +} +close(MT); +open(IT,"<$i4jtemplatefile") or die("Could not open '$i4jtemplatefile' for reading"); +while(){ + $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 "CFBundleDocumentTypes\n\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 = ) { + $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 "\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; +} diff --git a/utils/install4j/file_associations_auto-Info_plist.xml b/utils/install4j/file_associations_auto-Info_plist.xml index 0b927a8..2e23321 100644 --- a/utils/install4j/file_associations_auto-Info_plist.xml +++ b/utils/install4j/file_associations_auto-Info_plist.xml @@ -12,6 +12,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Owner CFBundleTypeMIMETypes application/x-jalview+xml+zip @@ -31,6 +33,8 @@ jvl_file.icns CFBundleTypeRole Editor +LSHandlerRank +Owner CFBundleTypeMIMETypes application/x-jalview-jvl+text @@ -50,6 +54,8 @@ Jalview-File.icns CFBundleTypeRole Viewer +LSHandlerRank +Alternate CFBundleTypeMIMETypes chemical/x-cif @@ -70,6 +76,8 @@ Jalview-File.icns CFBundleTypeRole Viewer +LSHandlerRank +Alternate CFBundleTypeMIMETypes chemical/x-mmcif @@ -90,6 +98,8 @@ Jalview-File.icns CFBundleTypeRole Viewer +LSHandlerRank +Alternate CFBundleTypeMIMETypes chemical/x-pdb @@ -109,6 +119,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-amsa+txt @@ -129,6 +141,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-jalview-annotations+text @@ -148,6 +162,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-jalview-biojson+json @@ -167,6 +183,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-blc+txt @@ -186,6 +204,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-clustal+txt @@ -205,6 +225,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-embl+txt @@ -225,6 +247,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-fasta+txt @@ -245,6 +269,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-jalview-features+text @@ -265,6 +291,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-genbank+txt @@ -284,6 +312,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-gff2+txt @@ -303,6 +333,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-gff3+txt @@ -323,6 +355,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-jalview-jnet+text @@ -342,6 +376,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-msf+txt @@ -361,6 +397,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-pfam+txt @@ -380,6 +418,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-phylip+txt @@ -399,6 +439,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-pileup+txt @@ -418,6 +460,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-pir+txt @@ -437,6 +481,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/rnaml+xml @@ -456,6 +502,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-jalview-scorematrix+text @@ -476,6 +524,8 @@ Jalview-File.icns CFBundleTypeRole Editor +LSHandlerRank +Default CFBundleTypeMIMETypes application/x-stockholm+txt diff --git a/utils/install4j/file_associations_auto-install4j10.xml b/utils/install4j/file_associations_auto-install4j10.xml new file mode 100644 index 0000000..ee70251 --- /dev/null +++ b/utils/install4j/file_associations_auto-install4j10.xml @@ -0,0 +1,1125 @@ + + + + Jalview (.jvp) + Creating file associations... + + + + + + + + + + + + + + Jalview File + jvp + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview+xml+zip + + + Jalview-File.ico + + + + + + + + + + Jalview Launch (.jvl) + Creating file associations... + + + + + + + + + + + + + + Jalview Launch File + jvl + JALVIEW + + + + + + + jvl_file.png + + + application/x-jalview-jvl+text + + + jvl_file.ico + + + + + + + + + + CIF (.cif) + Creating file associations... + + + + + + + + + + + + + + CIF File + cif + JALVIEW + + + + + + + Jalview-File.png + + + chemical/x-cif + + + Jalview-File.ico + + + + + + + + + + mmCIF (.mcif, .mmcif) + Creating file associations... + + + + + + + + + + + + + + mmCIF File + mcif,mmcif + JALVIEW + + + + + + + Jalview-File.png + + + chemical/x-mmcif + + + Jalview-File.ico + + + + + + + + + + PDB (.ent, .pdb) + Creating file associations... + + + + + + + + + + + + + + PDB File + ent,pdb + JALVIEW + + + + + + + Jalview-File.png + + + chemical/x-pdb + + + Jalview-File.ico + + + + + + + + + + AMSA (.amsa) + Creating file associations... + + + + + + + + + + + + + + AMSA File + amsa + JALVIEW + + + + + + + Jalview-File.png + + + application/x-amsa+txt + + + Jalview-File.ico + + + + + + + + + + Jalview Annotations (.annotations, .jvannotations) + Creating file associations... + + + + + + + + + + + + + + Jalview Annotations File + annotations,jvannotations + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview-annotations+text + + + Jalview-File.ico + + + + + + + + + + BioJSON (.biojson) + Creating file associations... + + + + + + + + + + + + + + BioJSON File + biojson + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview-biojson+json + + + Jalview-File.ico + + + + + + + + + + BLC (.blc) + Creating file associations... + + + + + + + + + + + + + + BLC File + blc + JALVIEW + + + + + + + Jalview-File.png + + + application/x-blc+txt + + + Jalview-File.ico + + + + + + + + + + Clustal (.aln) + Creating file associations... + + + + + + + + + + + + + + Clustal File + aln + JALVIEW + + + + + + + Jalview-File.png + + + application/x-clustal+txt + + + Jalview-File.ico + + + + + + + + + + ENA Flatfile (.txt) + Creating file associations... + + + + + + + + + + + + + + ENA Flatfile File + txt + JALVIEW + + + + + + + Jalview-File.png + + + application/x-embl+txt + + + Jalview-File.ico + + + + + + + + + + Fasta (.fa, .fasta) + Creating file associations... + + + + + + + + + + + + + + Fasta File + fa,fasta + JALVIEW + + + + + + + Jalview-File.png + + + application/x-fasta+txt + + + Jalview-File.ico + + + + + + + + + + Jalview Features (.features, .jvfeatures) + Creating file associations... + + + + + + + + + + + + + + Jalview Features File + features,jvfeatures + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview-features+text + + + Jalview-File.ico + + + + + + + + + + GenBank Flatfile (.gb, .gbk) + Creating file associations... + + + + + + + + + + + + + + GenBank Flatfile File + gb,gbk + JALVIEW + + + + + + + Jalview-File.png + + + application/x-genbank+txt + + + Jalview-File.ico + + + + + + + + + + Generic Features Format v2 (.gff2) + Creating file associations... + + + + + + + + + + + + + + Generic Features Format v2 File + gff2 + JALVIEW + + + + + + + Jalview-File.png + + + application/x-gff2+txt + + + Jalview-File.ico + + + + + + + + + + Generic Features Format v3 (.gff3) + Creating file associations... + + + + + + + + + + + + + + Generic Features Format v3 File + gff3 + JALVIEW + + + + + + + Jalview-File.png + + + application/x-gff3+txt + + + Jalview-File.ico + + + + + + + + + + JnetFile (.concise, .jnet) + Creating file associations... + + + + + + + + + + + + + + JnetFile File + concise,jnet + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview-jnet+text + + + Jalview-File.ico + + + + + + + + + + MSF (.msf) + Creating file associations... + + + + + + + + + + + + + + MSF File + msf + JALVIEW + + + + + + + Jalview-File.png + + + application/x-msf+txt + + + Jalview-File.ico + + + + + + + + + + PFAM (.pfam) + Creating file associations... + + + + + + + + + + + + + + PFAM File + pfam + JALVIEW + + + + + + + Jalview-File.png + + + application/x-pfam+txt + + + Jalview-File.ico + + + + + + + + + + PHYLIP (.phy) + Creating file associations... + + + + + + + + + + + + + + PHYLIP File + phy + JALVIEW + + + + + + + Jalview-File.png + + + application/x-phylip+txt + + + Jalview-File.ico + + + + + + + + + + PileUp (.pileup) + Creating file associations... + + + + + + + + + + + + + + PileUp File + pileup + JALVIEW + + + + + + + Jalview-File.png + + + application/x-pileup+txt + + + Jalview-File.ico + + + + + + + + + + PIR (.pir) + Creating file associations... + + + + + + + + + + + + + + PIR File + pir + JALVIEW + + + + + + + Jalview-File.png + + + application/x-pir+txt + + + Jalview-File.ico + + + + + + + + + + RNAML (.rnaml) + Creating file associations... + + + + + + + + + + + + + + RNAML File + rnaml + JALVIEW + + + + + + + Jalview-File.png + + + application/rnaml+xml + + + Jalview-File.ico + + + + + + + + + + Substitution Matrix (.mat) + Creating file associations... + + + + + + + + + + + + + + Substitution Matrix File + mat + JALVIEW + + + + + + + Jalview-File.png + + + application/x-jalview-scorematrix+text + + + Jalview-File.ico + + + + + + + + + + Stockholm (.stk, .sto) + Creating file associations... + + + + + + + + + + + + + + Stockholm File + stk,sto + JALVIEW + + + + + + + Jalview-File.png + + + application/x-stockholm+txt + + + Jalview-File.ico + + + + + + diff --git a/utils/install4j/file_associations_template-Info_plist.xml b/utils/install4j/file_associations_template-Info_plist.xml index a260658..afac20c 100644 --- a/utils/install4j/file_associations_template-Info_plist.xml +++ b/utils/install4j/file_associations_template-Info_plist.xml @@ -9,6 +9,8 @@ $$ $$ICONFILE$$.icns CFBundleTypeRole $$ROLE$$ +LSHandlerRank +$$RANK$$ CFBundleTypeMIMETypes $$MIMETYPE$$ diff --git a/utils/install4j/file_associations_template-install4j10.xml b/utils/install4j/file_associations_template-install4j10.xml new file mode 100644 index 0000000..8663ed9 --- /dev/null +++ b/utils/install4j/file_associations_template-install4j10.xml @@ -0,0 +1,45 @@ + + + + $$NAME$$ ($$DISPLAYEXTENSION$$) + Creating file associations... + + + + + + + + + + + + + + $$NAME$$ File + $$EXTENSION$$ + JALVIEW + + + + + + + $$ICONFILE$$.png + + + $$MIMETYPE$$ + + + $$ICONFILE$$.ico + + + + + + diff --git a/utils/install4j/install4j10_template.install4j b/utils/install4j/install4j10_template.install4j new file mode 100644 index 0000000..77cd42e --- /dev/null +++ b/utils/install4j/install4j10_template.install4j @@ -0,0 +1,1496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *.dylib + *.so + *.jnilib + unpack200 + tnameserv + servertool + rmiregistry + rmid + policytool + pack200 + orbd + keytool + jjs + java + jspawnhelper + libfreetype.dylib.6 + applet + jaotc + jfr + jrunscript + libjli.dylib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${compiler:file("${compiler:INFO_PLIST_FILE_ASSOCIATIONS_FILE}")} + + + + + + + + + + + + + + + + + + + + + + + + 255 + 255 + 255 + 255 + + + 49 + 52 + 53 + 255 + + + + + + + + + + + 255 + 255 + 255 + 255 + + + + + + ${compiler:JALVIEW_DIR}/${compiler:BACKGROUND} + + + + + 5 + 10 + 10 + 10 + + + + + imageAnchor + imageEdgeBackgroundColor + imageFile + + + + + + + + + + labelText + + + + + + + + + + + + + + + + + String userHome = (String)context.getVariable("sys.userHome"); + +ArrayList<String> tryPaths = new ArrayList<> (); +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()) { // && root permission? + tryPaths.add(File.separator + "usr" + File.separator + "local" + File.separator + "bin"); +} + +for (int i = 0; i < tryPaths.size(); i++) { + String tryPath = tryPaths.get(i); + File unixUserBinDir = new File(tryPath); + if (unixUserBinDir.exists()) { + return tryPath; + } +} + +return null; + + + + unixUserBinDir + + Util.isLinux() || Util.isUnixInstaller() || Util.isMacOS() + + + + + + 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 > -1) { + wrapperLink = javaHome.substring(0, i) + appName + File.separator + "Contents" + File.separator + "MacOS" + File.separator + ((String)context.getCompilerVariable("WRAPPER_LINK")); +} +return wrapperLink; + + + + macWrapperLinkLocation + + Util.isMacOS() + + + + + + + + + + + + + + 255 + 255 + 255 + 255 + + + 49 + 52 + 53 + 255 + + + + + + + + + + + + 25 + 143 + 220 + 255 + + + 0 + 74 + 151 + 255 + + + + + + + ${compiler:JALVIEW_DIR}/${compiler:BACKGROUND} + + + + + 5 + 10 + 10 + 10 + + + + + imageAnchor + imageEdgeBackgroundColor + imageFile + + + + + + + + + sys.installationDir + + + context.getBooleanVariable("sys.confirmedUpdateInstallation") + + + + + + ${form:welcomeMessage} + + !context.isConsole() + + + + + + String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName()); +return console.askOkCancel(message, true); + + + + + + + + updateCheck + + + + + ${i18n:ClickNext} + + + + + + !context.getBooleanVariable("sys.confirmedUpdateInstallation") + + + + + sys.installationDir + + + context.getVariable("sys.responseFile") == null + + + + + + ${i18n:SelectDirLabel(${compiler:sys.fullName})} + + + + + + + + allowSpacesOnUnix + checkFreeSpace + checkWritable + existingDirWarning + manualEntryAllowed + showFreeDiskSpace + showRequiredDiskSpace + standardValidation + suggestAppDir + validateApplicationId + validationScript + + + + + + + + + ${i18n:SelectComponentsLabel2} + + !context.isConsole() + + + + + + + selectionChangedScript + + + + + + + + + ${i18n:SelectAssociationsLabel} + + + + + + + + + + showSelectionButtons + selectionButtonPosition + + + + + + + + + + + ${compiler:APPLICATION_CATEGORIES} + + + + Examples + + + examples + + + + + + ${compiler:JALVIEW_NAME} + ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} + + !context.getBooleanVariable("sys.programGroupDisabled") + + + + + ${compiler:APPLICATION_CATEGORIES} + ${compiler:JALVIEW_NAME} + ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} + + !context.getBooleanVariable("sys.programGroupDisabled") + + + + ${compiler:sys.fullName} ${compiler:sys.version} + + + + + + + + + + + + true + + + + 121 + + + + + + + + Creating file associations... + + + + + + + + + + + + + + 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'. + extensions_to_be_replaced_by_gradle + JALVIEW + + + + + + + Finished creating file associations + + + + + + + + + + + + + + JALVIEW + jalview + + + + + JALVIEW + jalviews + + + + + JALVIEW + ${compiler:EXTRA_SCHEME} + + + + + + + ${i18n:WizardPreparing} + + + + + + + + + + ${compiler:APPLICATION_CATEGORIES} + ${compiler:JALVIEW_APPLICATION_NAME} + + + ${installer:sys.contentDir}/${compiler:EXECUTABLE_NAME} + + + + + ${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE} + + + ${compiler:JALVIEW_NAME} + + + ${compiler:JALVIEW_DIR}/${compiler:PNG_ICON_FILE} + + + + !context.getBooleanVariable("sys.programGroupDisabled") + + + + + ${compiler:JALVIEW_APPLICATION_NAME} + + + ${installer:sys.contentDir}/${compiler:EXECUTABLE_NAME} + + + ${compiler:JALVIEW_APPLICATION_NAME} + + + ${compiler:JALVIEW_DIR}/${compiler:PNG_ICON_FILE} + + + + + ${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE} + + + + context.getBooleanVariable("createDesktopLinkAction") + + + + + + ${compiler:JALVIEW_APPLICATION_NAME}.app + + + + context.getBooleanVariable("addToDockAction") + + + + + + ${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT} + + + + + ${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:WRAPPER_LINK} + + + + Util.isLinux() || Util.isUnixInstaller() || Util.isMacOS() + + + + + ${installer:sys.contentDir}\${compiler:WRAPPER_SCRIPT_BIN_DIR} + Path + + context.getBooleanVariable("appendToPathAction") + + + + + + ${installer:sys.contentDir}/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT} + + + + + ${installer:unixUserBinDir}/${compiler:WRAPPER_LINK} + + + + context.getBooleanVariable("makeSymbolicLinkAction") && ( Util.isLinux() || Util.isUnixInstaller() ) && ( context.getVariable("unixUserBinDir") != null ) + + + + + + ${installer:macWrapperLinkLocation} + + + + + ${installer:unixUserBinDir}/${compiler:WRAPPER_LINK} + + + + context.getBooleanVariable("makeSymbolicLinkAction") && Util.isMacOS() && ( context.getVariable("unixUserBinDir") != null ) && ( context.getVariable("macWrapperLinkLocation") != null ) + + + + + + ${form:finishedMessage} + + + + + ${i18n:CreateDesktopIcon} + + createDesktopLinkAction + + !Util.isMacOS() + + + + ${i18n:AddToDock} + + addToDockAction + + Util.isMacOS() + + + + Add ${compiler:JALVIEW_APPLICATION_NAME}'s bin folder to the Path environment variable + + appendToPathAction + + Util.isWindows() + + + + Make a ${compiler:WRAPPER_LINK} symbolic link in ${installer:unixUserBinDir} + + makeSymbolicLinkAction + + ( Util.isLinux() || Util.isUnixInstaller() || ( Util.isMacOS() && ( context.getVariable("macWrapperLinkLocation") != null ) ) ) && ( context.getVariable("unixUserBinDir") != null ) + + + + + + + + ${i18n:UninstallerMenuEntry(${compiler:sys.fullName})} + + + + + + + + + + 255 + 255 + 255 + 255 + + + 49 + 52 + 53 + 255 + + + + + + + + + + + 192 + 192 + 192 + 255 + + + + + + ${compiler:JALVIEW_DIR}/${compiler:BACKGROUND} + + + + + 5 + 10 + 10 + 10 + + + + + imageAnchor + imageEdgeBackgroundColor + imageFile + + + + + + + + + + + + + + + + + + ${form:welcomeMessage} + + !context.isConsole() + + + + + + String message = context.getMessage("ConfirmUninstall", context.getApplicationName()); +return console.askYesNo(message, true); + + + + + + + + + + + + + + + + + + + + + + + + + + jre + + + + + jre.jar + + + + + .install4j + + + + + getdown-launcher.jar + + + + + getdown-launcher-old.jar + + + + + getdown-launcher-new.jar + + + + + gettingdown.lock + + + + + jre.zip + + + + + digest.txt + + + + + digest2.txt + + + + + getdown-launcher.jarv + + + + + getdown-launcher-new.jarv + + + + + launcher.log + + + + + proxy.txt + + + + + build_properties + + + + + channel_launch*.jvl + + + + + jalview*.jvl + + + + + *.jarv + + + + + *.log + + + + + *.txt + + + + + *_new + + + + + hs_err_*.* + + + + + ${compiler:GETDOWN_DIST_DIR} + + + + + ${compiler:GETDOWN_ALT_DIR} + + + + + ${compiler:GETDOWN_RESOURCE_DIR} + + + + + META-INF + + + + + install + + + + + resource + + + + + dist + + + + + release + + + + + alt + + + + + dev + + + + + build + + + + + alt_* + + + + + dev_* + + + + + build_* + + + + + ${compiler:WRAPPER_SCRIPT_BIN_DIR} + + + + + bin + + + + + channel.props + + + + + channel.propsv + + + + + + + + + + + + + + + + ${i18n:UninstallerPreparing} + + + + + + + + + + ${form:successMessage} + + + + + + + + + + + . + + + ${compiler:WRAPPER_LINK}_setup + ${compiler:sys.fullName} + + + + + + + + + + + + + + Running ${i18n:SetupAppTitle} + + + + + + + + + ${i18n:FinishedLabel(${compiler:JALVIEW_APPLICATION_NAME})} + + + + + ${i18n:AddToDock} + + addToDockAction + + Util.isMacOS() + + + + Make a ${compiler:WRAPPER_LINK} symbolic link in ${installer:unixUserBinDir} + + makeSymbolicLinkAction + + ( Util.isLinux() || Util.isUnixInstaller() || ( Util.isMacOS() && ( context.getVariable("macWrapperLinkLocation") != null ) ) ) && ( context.getVariable("unixUserBinDir") != null ) + + + + +${i18n:ClickFinish} + +${compiler:JALVIEW_APPLICATION_NAME} will now launch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utils/install4j/install4j9_template.install4j b/utils/install4j/install4j9_template.install4j index 23ff9c9..979b1a9 100644 --- a/utils/install4j/install4j9_template.install4j +++ b/utils/install4j/install4j9_template.install4j @@ -1,5 +1,5 @@ - + @@ -23,18 +23,22 @@ - - - - - - + + + + + + + + + + - - + + @@ -86,25 +90,20 @@ - - - + + - - - + + - - - - + + - @@ -112,9 +111,8 @@ - - - + + @@ -122,26 +120,6 @@ - - - - - - - - - - - - - - - - - - - - @@ -1421,31 +1399,35 @@ ${compiler:JALVIEW_APPLICATION_NAME} will now launch. - - - - - - + + + - - - - + + - + - - - - - - + + + + + + + + + + + + + + + + - - + @@ -1457,59 +1439,57 @@ ${compiler:JALVIEW_APPLICATION_NAME} will now launch. - - - - - - - + + + + + + + + + + + + + + + - - - - + + - + - - - - - - + + + - - - - + + - + - - - - - - + + + - - - - + + - + + +