From: Ben Soares Date: Fri, 4 Nov 2022 17:58:36 +0000 (+0000) Subject: Merge branch 'merge/JAL-4054+JAL-4064' into ben-big-merge X-Git-Tag: Release_2_11_3_0~23^2~4^2~16 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=51c011768a77932d6c0aebd2d686a04fabaa2589;hp=931bd79285961f259c0c6b9bcfc4166465054423;p=jalview.git Merge branch 'merge/JAL-4054+JAL-4064' into ben-big-merge --- diff --git a/resources/images/windowIcons/alignmentIcon.png b/resources/images/windowIcons/alignmentIcon.png new file mode 100644 index 0000000..9f3d779 Binary files /dev/null and b/resources/images/windowIcons/alignmentIcon.png differ diff --git a/resources/images/windowIcons/annotationIcon.png b/resources/images/windowIcons/annotationIcon.png new file mode 100644 index 0000000..a730b7e Binary files /dev/null and b/resources/images/windowIcons/annotationIcon.png differ diff --git a/resources/images/windowIcons/featuresIcon.png b/resources/images/windowIcons/featuresIcon.png new file mode 100644 index 0000000..84da9f8 Binary files /dev/null and b/resources/images/windowIcons/featuresIcon.png differ diff --git a/resources/images/windowIcons/fetchIcon.png b/resources/images/windowIcons/fetchIcon.png new file mode 100644 index 0000000..570a929 Binary files /dev/null and b/resources/images/windowIcons/fetchIcon.png differ diff --git a/resources/images/windowIcons/overviewIcon.png b/resources/images/windowIcons/overviewIcon.png new file mode 100644 index 0000000..924ac70 Binary files /dev/null and b/resources/images/windowIcons/overviewIcon.png differ diff --git a/resources/images/windowIcons/plainIcon.png b/resources/images/windowIcons/plainIcon.png new file mode 100644 index 0000000..dbbad71 Binary files /dev/null and b/resources/images/windowIcons/plainIcon.png differ diff --git a/resources/images/windowIcons/preferencesIcon.png b/resources/images/windowIcons/preferencesIcon.png new file mode 100644 index 0000000..29ebf39 Binary files /dev/null and b/resources/images/windowIcons/preferencesIcon.png differ diff --git a/resources/images/windowIcons/splitIcon.png b/resources/images/windowIcons/splitIcon.png new file mode 100644 index 0000000..c6a0520 Binary files /dev/null and b/resources/images/windowIcons/splitIcon.png differ diff --git a/resources/images/windowIcons/structureIcon.png b/resources/images/windowIcons/structureIcon.png new file mode 100644 index 0000000..8cda58b Binary files /dev/null and b/resources/images/windowIcons/structureIcon.png differ diff --git a/resources/images/windowIcons/treeIcon.png b/resources/images/windowIcons/treeIcon.png new file mode 100644 index 0000000..02f04ed Binary files /dev/null and b/resources/images/windowIcons/treeIcon.png differ 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 1428906..b87a14d 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 @@ -1123,6 +1157,8 @@ public class Jalview UIManager.put("TabbedPane.tabWidthMode", "compact"); UIManager.put("TabbedPane.selectedBackground", Color.white); } + + Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true)); return set; } @@ -1369,19 +1405,12 @@ public class Jalview } /** - * Quit method delegates to Desktop.quit - unless running in headless mode - * when it just ends the JVM + * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit */ public void quit() { - if (desktop != null) - { - desktop.quit(); - } - else - { - System.exit(0); - } + // System.exit will run the shutdownHook first + System.exit(0); } public static AlignFrame getCurrentAlignFrame() 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 e24cbea..c11b866 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -20,8 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -59,7 +57,9 @@ import java.util.Deque; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; +import java.util.Locale; import java.util.Vector; +import java.util.concurrent.Callable; import javax.swing.ButtonGroup; import javax.swing.JCheckBoxMenuItem; @@ -350,6 +350,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, */ void init() { + setFrameIcon(WindowIcons.alignmentIcon); + // setBackground(Color.white); // BH 2019 if (!Jalview.isHeadlessMode()) @@ -1259,6 +1261,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file, shortName); + Console.debug("lastSaveSuccessful=" + lastSaveSuccessful); + if (lastSaveSuccessful) + { + this.getViewport().setSavedUpToDate(true); + } + statusBar.setText(MessageManager.formatMessage( "label.successfully_saved_to_file_in_format", new Object[] { file, format })); @@ -1267,97 +1275,89 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } AlignExportSettingsI options = new AlignExportSettingsAdapter(false); - Runnable cancelAction = new Runnable() - { - @Override - public void run() + Callable 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; }; /* @@ -1373,7 +1373,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - outputAction.run(); + try + { + outputAction.call(); + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } @@ -1390,34 +1397,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, FileFormatI fileFormat = FileFormats.getInstance() .forName(fileFormatName); AlignExportSettingsI options = new AlignExportSettingsAdapter(false); - Runnable outputAction = new Runnable() - { - @Override - public void run() + Callable outputAction = () -> { + // todo defer this to inside formatSequences (or later) + AlignmentExportData exportData = viewport.getAlignExportData(options); + CutAndPasteTransfer cap = new CutAndPasteTransfer(); + cap.setForInput(null); + try { - // todo defer this to inside formatSequences (or later) - AlignmentExportData exportData = viewport - .getAlignExportData(options); - CutAndPasteTransfer cap = new CutAndPasteTransfer(); - cap.setForInput(null); - try - { - FileFormatI format = fileFormat; - cap.setText(new FormatAdapter(alignPanel, options) - .formatSequences(format, exportData.getAlignment(), - exportData.getOmitHidden(), - exportData.getStartEndPostions(), - viewport.getAlignment().getHiddenColumns())); - Desktop.addInternalFrame(cap, MessageManager.formatMessage( - "label.alignment_output_command", new Object[] - { fileFormat.getName() }), 600, 500); - } catch (OutOfMemoryError oom) - { - new OOMWarning("Outputting alignment as " + fileFormat.getName(), - oom); - cap.dispose(); - } + FileFormatI format = fileFormat; + cap.setText(new FormatAdapter(alignPanel, options).formatSequences( + format, exportData.getAlignment(), + exportData.getOmitHidden(), + exportData.getStartEndPostions(), + viewport.getAlignment().getHiddenColumns())); + Desktop.addInternalFrame(cap, MessageManager.formatMessage( + "label.alignment_output_command", new Object[] + { fileFormat.getName() }), 600, 500); + } catch (OutOfMemoryError oom) + { + new OOMWarning("Outputting alignment as " + fileFormat.getName(), + oom); + cap.dispose(); } + return null; }; /* @@ -1432,7 +1434,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - outputAction.run(); + try + { + outputAction.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } @@ -1540,15 +1548,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, .getString("label.load_jalview_annotations"); chooser.setDialogTitle(tooltip); chooser.setToolTipText(tooltip); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - String choice = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", choice); - loadJalviewDataFile(chooser.getSelectedFile(), null, null, null); - } + chooser.setResponseHandler(0, () -> { + String choice = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", choice); + loadJalviewDataFile(chooser.getSelectedFile(), null, null, null); + return null; }); chooser.showOpenDialog(this); @@ -2470,36 +2474,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, return; } - Runnable okAction = new Runnable() - { - @Override - public void run() - { - SequenceI[] cut = sg.getSequences() - .toArray(new SequenceI[sg.getSize()]); + Callable okAction = () -> { + SequenceI[] cut = sg.getSequences() + .toArray(new SequenceI[sg.getSize()]); - addHistoryItem(new EditCommand( - MessageManager.getString("label.cut_sequences"), Action.CUT, - cut, sg.getStartRes(), - sg.getEndRes() - sg.getStartRes() + 1, - viewport.getAlignment())); + addHistoryItem(new EditCommand( + MessageManager.getString("label.cut_sequences"), Action.CUT, + cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1, + viewport.getAlignment())); - viewport.setSelectionGroup(null); - viewport.sendSelection(); - viewport.getAlignment().deleteGroup(sg); + viewport.setSelectionGroup(null); + viewport.sendSelection(); + viewport.getAlignment().deleteGroup(sg); - viewport.firePropertyChange("alignment", null, - viewport.getAlignment().getSequences()); - if (viewport.getAlignment().getHeight() < 1) + viewport.firePropertyChange("alignment", null, + viewport.getAlignment().getSequences()); + if (viewport.getAlignment().getHeight() < 1) + { + try + { + AlignFrame.this.setClosed(true); + } catch (Exception ex) { - try - { - AlignFrame.this.setClosed(true); - } catch (Exception ex) - { - } } } + return null; }; /* @@ -2523,7 +2522,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - okAction.run(); + try + { + okAction.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } @@ -3444,6 +3449,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, .formatMessage("label.overview_params", new Object[] { this.getTitle() }), true, frame.getWidth(), frame.getHeight(), true, true); + frame.setFrameIcon(WindowIcons.overviewIcon); frame.pack(); frame.setLayer(JLayeredPane.PALETTE_LAYER); frame.addInternalFrameListener( @@ -4076,36 +4082,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, chooser.setToolTipText( MessageManager.getString("label.load_tree_file")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() + chooser.setResponseHandler(0, () -> { + String filePath = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", filePath); + NewickFile fin = null; + try { - String filePath = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", filePath); - NewickFile fin = null; - try - { - fin = new NewickFile(new FileParse(chooser.getSelectedFile(), - DataSourceType.FILE)); - viewport.setCurrentTree(showNewickTree(fin, filePath).getTree()); - } catch (Exception ex) - { - JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(), - MessageManager - .getString("label.problem_reading_tree_file"), - JvOptionPane.WARNING_MESSAGE); - ex.printStackTrace(); - } - if (fin != null && fin.hasWarningMessage()) - { - JvOptionPane.showMessageDialog(Desktop.desktop, - fin.getWarningMessage(), - MessageManager.getString( - "label.possible_problem_with_tree_file"), - JvOptionPane.WARNING_MESSAGE); - } + fin = new NewickFile(new FileParse(chooser.getSelectedFile(), + DataSourceType.FILE)); + viewport.setCurrentTree(showNewickTree(fin, filePath).getTree()); + } catch (Exception ex) + { + JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(), + MessageManager.getString("label.problem_reading_tree_file"), + JvOptionPane.WARNING_MESSAGE); + ex.printStackTrace(); + } + if (fin != null && fin.hasWarningMessage()) + { + JvOptionPane.showMessageDialog(Desktop.desktop, + fin.getWarningMessage(), + MessageManager + .getString("label.possible_problem_with_tree_file"), + JvOptionPane.WARNING_MESSAGE); } + return null; }); chooser.showOpenDialog(this); } @@ -5877,16 +5878,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file")); chooser.setToolTipText(MessageManager.getString("label.load_vcf_file")); final AlignFrame us = this; - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - String choice = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", choice); - SequenceI[] seqs = viewport.getAlignment().getSequencesArray(); - new VCFLoader(choice).loadVCF(seqs, us); - } + chooser.setResponseHandler(0, () -> { + String choice = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", choice); + SequenceI[] seqs = viewport.getAlignment().getSequencesArray(); + new VCFLoader(choice).loadVCF(seqs, us); + return null; }); chooser.showOpenDialog(null); 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/AnnotationChooser.java b/src/jalview/gui/AnnotationChooser.java index 791421d..ad5e574 100644 --- a/src/jalview/gui/AnnotationChooser.java +++ b/src/jalview/gui/AnnotationChooser.java @@ -20,11 +20,6 @@ */ package jalview.gui; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.SequenceGroup; -import jalview.util.MessageManager; - import java.awt.BorderLayout; import java.awt.Checkbox; import java.awt.CheckboxGroup; @@ -45,6 +40,11 @@ import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; import javax.swing.JPanel; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceGroup; +import jalview.util.MessageManager; + /** * A panel that allows the user to select which sequence-associated annotation * rows to show or hide. @@ -602,6 +602,7 @@ public class AnnotationChooser extends JPanel private void showFrame() { frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.annotationIcon); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); Desktop.addInternalFrame(frame, diff --git a/src/jalview/gui/AnnotationColourChooser.java b/src/jalview/gui/AnnotationColourChooser.java index 4e9a26d..77dc1c5 100644 --- a/src/jalview/gui/AnnotationColourChooser.java +++ b/src/jalview/gui/AnnotationColourChooser.java @@ -20,15 +20,6 @@ */ package jalview.gui; -import jalview.bin.Cache; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.GraphLine; -import jalview.datamodel.SequenceGroup; -import jalview.gui.JalviewColourChooser.ColourChooserListener; -import jalview.schemes.AnnotationColourGradient; -import jalview.schemes.ColourSchemeI; -import jalview.util.MessageManager; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -48,6 +39,14 @@ import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; import javax.swing.JPanel; +import jalview.bin.Cache; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.GraphLine; +import jalview.datamodel.SequenceGroup; +import jalview.gui.JalviewColourChooser.ColourChooserListener; +import jalview.schemes.AnnotationColourGradient; +import jalview.schemes.ColourSchemeI; +import jalview.util.MessageManager; import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") @@ -87,6 +86,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter } } frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.annotationIcon); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); Desktop.addInternalFrame(frame, diff --git a/src/jalview/gui/AnnotationColumnChooser.java b/src/jalview/gui/AnnotationColumnChooser.java index c0d4708..401df42 100644 --- a/src/jalview/gui/AnnotationColumnChooser.java +++ b/src/jalview/gui/AnnotationColumnChooser.java @@ -21,14 +21,6 @@ package jalview.gui; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.HiddenColumns; -import jalview.io.cache.JvCacheableInputBox; -import jalview.schemes.AnnotationColourGradient; -import jalview.util.MessageManager; -import jalview.util.Platform; -import jalview.viewmodel.annotationfilter.AnnotationFilterParameter; - import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; @@ -50,6 +42,13 @@ import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.border.TitledBorder; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.HiddenColumns; +import jalview.io.cache.JvCacheableInputBox; +import jalview.schemes.AnnotationColourGradient; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.viewmodel.annotationfilter.AnnotationFilterParameter; import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") @@ -98,6 +97,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter { super(av, ap); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.annotationIcon); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); Desktop.addInternalFrame(frame, diff --git a/src/jalview/gui/AnnotationExporter.java b/src/jalview/gui/AnnotationExporter.java index 1efd100..c22761a 100644 --- a/src/jalview/gui/AnnotationExporter.java +++ b/src/jalview/gui/AnnotationExporter.java @@ -20,24 +20,13 @@ */ package jalview.gui; -import java.util.Locale; - -import jalview.api.FeatureRenderer; -import jalview.bin.Cache; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.SequenceI; -import jalview.io.AnnotationFile; -import jalview.io.FeaturesFile; -import jalview.io.JalviewFileChooser; -import jalview.io.JalviewFileView; -import jalview.util.MessageManager; - import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.FileWriter; import java.io.PrintWriter; +import java.util.Locale; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; @@ -50,6 +39,16 @@ import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.SwingConstants; +import jalview.api.FeatureRenderer; +import jalview.bin.Cache; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.SequenceI; +import jalview.io.AnnotationFile; +import jalview.io.FeaturesFile; +import jalview.io.JalviewFileChooser; +import jalview.io.JalviewFileView; +import jalview.util.MessageManager; + /** * * GUI dialog for exporting features or alignment annotations depending upon @@ -108,6 +107,7 @@ public class AnnotationExporter extends JPanel } frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.annotationIcon); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); Dimension preferredSize = frame.getPreferredSize(); 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/AppVarna.java b/src/jalview/gui/AppVarna.java index 3a64716..6fac8fc 100644 --- a/src/jalview/gui/AppVarna.java +++ b/src/jalview/gui/AppVarna.java @@ -20,23 +20,6 @@ */ package jalview.gui; -import jalview.analysis.AlignSeq; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.ColumnSelection; -import jalview.datamodel.HiddenColumns; -import jalview.datamodel.RnaViewerModel; -import jalview.datamodel.SequenceGroup; -import jalview.datamodel.SequenceI; -import jalview.ext.varna.RnaModel; -import jalview.structure.SecondaryStructureListener; -import jalview.structure.SelectionListener; -import jalview.structure.SelectionSource; -import jalview.structure.StructureSelectionManager; -import jalview.structure.VamsasSource; -import jalview.util.Comparison; -import jalview.util.MessageManager; -import jalview.util.ShiftList; - import java.awt.BorderLayout; import java.awt.Color; import java.util.Collection; @@ -60,6 +43,22 @@ import fr.orsay.lri.varna.models.FullBackup; import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation; import fr.orsay.lri.varna.models.rna.ModeleBase; import fr.orsay.lri.varna.models.rna.RNA; +import jalview.analysis.AlignSeq; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.RnaViewerModel; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.ext.varna.RnaModel; +import jalview.structure.SecondaryStructureListener; +import jalview.structure.SelectionListener; +import jalview.structure.SelectionSource; +import jalview.structure.StructureSelectionManager; +import jalview.structure.VamsasSource; +import jalview.util.Comparison; +import jalview.util.MessageManager; +import jalview.util.ShiftList; public class AppVarna extends JInternalFrame implements SelectionListener, SecondaryStructureListener, @@ -216,6 +215,7 @@ public class AppVarna extends JInternalFrame */ protected AppVarna(AlignmentPanel ap) { + this.setFrameIcon(null); this.ap = ap; this.viewId = System.currentTimeMillis() + "." + this.hashCode(); vab = new AppVarnaBinding(); diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index f7e5413..dda0a9f 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -20,15 +20,6 @@ */ package jalview.gui; -import jalview.analysis.TreeBuilder; -import jalview.analysis.scoremodels.ScoreModels; -import jalview.analysis.scoremodels.SimilarityParams; -import jalview.api.analysis.ScoreModelI; -import jalview.api.analysis.SimilarityParamsI; -import jalview.bin.Cache; -import jalview.datamodel.SequenceGroup; -import jalview.util.MessageManager; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -61,6 +52,15 @@ import javax.swing.JRadioButton; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; +import jalview.analysis.TreeBuilder; +import jalview.analysis.scoremodels.ScoreModels; +import jalview.analysis.scoremodels.SimilarityParams; +import jalview.api.analysis.ScoreModelI; +import jalview.api.analysis.SimilarityParamsI; +import jalview.bin.Cache; +import jalview.datamodel.SequenceGroup; +import jalview.util.MessageManager; + /** * A dialog where a user can choose and action Tree or PCA calculation options */ @@ -130,6 +130,7 @@ public class CalculationChooser extends JPanel { setLayout(new BorderLayout()); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.treeIcon); frame.setContentPane(this); this.setBackground(Color.white); frame.addFocusListener(new FocusListener() 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/CutAndPasteHtmlTransfer.java b/src/jalview/gui/CutAndPasteHtmlTransfer.java index 6e0032f..5c94d54 100644 --- a/src/jalview/gui/CutAndPasteHtmlTransfer.java +++ b/src/jalview/gui/CutAndPasteHtmlTransfer.java @@ -20,13 +20,6 @@ */ package jalview.gui; -import jalview.bin.Cache; -import jalview.io.JalviewFileChooser; -import jalview.io.JalviewFileView; -import jalview.jbgui.GCutAndPasteHtmlTransfer; -import jalview.util.MessageManager; -import jalview.viewmodel.AlignmentViewport; - import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; @@ -44,6 +37,13 @@ import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.event.HyperlinkListener; +import jalview.bin.Cache; +import jalview.io.JalviewFileChooser; +import jalview.io.JalviewFileView; +import jalview.jbgui.GCutAndPasteHtmlTransfer; +import jalview.util.MessageManager; +import jalview.viewmodel.AlignmentViewport; + /** * Cut'n'paste files into the desktop See JAL-1105 * @@ -58,6 +58,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer public CutAndPasteHtmlTransfer() { super(); + this.setFrameIcon(WindowIcons.logoIcon); displaySource.setSelected(false); textarea.addKeyListener(new KeyListener() { diff --git a/src/jalview/gui/CutAndPasteTransfer.java b/src/jalview/gui/CutAndPasteTransfer.java index 112d502..bc3c0d2 100644 --- a/src/jalview/gui/CutAndPasteTransfer.java +++ b/src/jalview/gui/CutAndPasteTransfer.java @@ -20,13 +20,29 @@ */ package jalview.gui; -import jalview.bin.Cache; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.api.ComplexAlignFile; import jalview.api.FeatureSettingsModelI; import jalview.api.FeaturesDisplayedI; import jalview.api.FeaturesSourceI; +import jalview.bin.Cache; import jalview.bin.Jalview; import jalview.datamodel.AlignmentI; import jalview.datamodel.HiddenColumns; @@ -45,22 +61,6 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper; import jalview.schemes.ColourSchemeI; import jalview.util.MessageManager; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; - -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.SwingUtilities; - /** * Cut'n'paste files into the desktop See JAL-1105 * @@ -78,6 +78,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer public CutAndPasteTransfer() { + this.setFrameIcon(null); SwingUtilities.invokeLater(new Runnable() { @Override diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 16603df..2605195 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; @@ -187,6 +191,14 @@ public class Desktop extends jalview.jbgui.GDesktop public static HashMap savingFiles = new HashMap(); + private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE; + + public static void setLiveDragMode(boolean b) + { + DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE + : JDesktopPane.OUTLINE_DRAG_MODE; + } + private JalviewChangeSupport changeSupport = new JalviewChangeSupport(); public static boolean nosplash = false; @@ -451,13 +463,14 @@ public class Desktop extends jalview.jbgui.GDesktop setIconImages(ChannelProperties.getIconList()); + // override quit handling when GUI OS close [X] button pressed + this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { - @Override public void windowClosing(WindowEvent ev) { - quit(); + QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag } }); @@ -482,7 +495,7 @@ public class Desktop extends jalview.jbgui.GDesktop } getContentPane().add(desktop, BorderLayout.CENTER); - desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); + desktop.setDragMode(DRAG_MODE); // This line prevents Windows Look&Feel resizing all new windows to maximum // if previous window was maximised @@ -571,15 +584,6 @@ public class Desktop extends jalview.jbgui.GDesktop this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this)); - this.addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent evt) - { - quit(); - } - }); - MouseAdapter ma; this.addMouseListener(ma = new MouseAdapter() { @@ -1180,36 +1184,32 @@ public class Desktop extends jalview.jbgui.GDesktop MessageManager.getString("label.open_local_file")); chooser.setToolTipText(MessageManager.getString("action.open")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File selectedFile = chooser.getSelectedFile(); - Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); + Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); - FileFormatI format = chooser.getSelectedFormat(); + FileFormatI format = chooser.getSelectedFormat(); - /* - * Call IdentifyFile to verify the file contains what its extension implies. - * Skip this step for dynamically added file formats, because IdentifyFile does - * not know how to recognise them. - */ - if (FileFormats.getInstance().isIdentifiable(format)) + /* + * Call IdentifyFile to verify the file contains what its extension implies. + * Skip this step for dynamically added file formats, because IdentifyFile does + * not know how to recognise them. + */ + if (FileFormats.getInstance().isIdentifiable(format)) + { + try { - try - { - format = new IdentifyFile().identify(selectedFile, - DataSourceType.FILE); - } catch (FileFormatException e) - { - // format = null; //?? - } + format = new IdentifyFile().identify(selectedFile, + DataSourceType.FILE); + } catch (FileFormatException e) + { + // format = null; //?? } - - new FileLoader().LoadFile(viewport, selectedFile, - DataSourceType.FILE, format); } + + new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, + format); + return null; }); chooser.showOpenDialog(this); } @@ -1265,64 +1265,60 @@ public class Desktop extends jalview.jbgui.GDesktop Object[] options = new Object[] { MessageManager.getString("action.ok"), MessageManager.getString("action.cancel") }; - Runnable action = new Runnable() - { - @Override - public void run() - { - @SuppressWarnings("unchecked") - String url = (history instanceof JTextField - ? ((JTextField) history).getText() - : ((JComboBox) 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"); @@ -1352,39 +1348,79 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * Exit the program + * Check with user and saving files before actually quitting */ - @Override - public void quit() + public void desktopQuit() { - Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); - Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); - storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, - getWidth(), getHeight())); + desktopQuit(true, false); + } - if (jconsole != null) - { - storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); - jconsole.stopConsole(); - } - if (jvnews != null) - { - storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); + public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag) + { + final Callable doDesktopQuit = () -> { + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); + Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); + storeLastKnownDimensions("", new Rectangle(getBounds().x, + getBounds().y, getWidth(), getHeight())); - } - if (dialogExecutor != null) - { - dialogExecutor.shutdownNow(); - } - closeAll_actionPerformed(null); + if (jconsole != null) + { + storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); + jconsole.stopConsole(); + } - if (groovyConsole != null) - { - // suppress a possible repeat prompt to save script - groovyConsole.setDirty(false); - groovyConsole.exit(); - } + if (jvnews != null) + { + storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); + + } + + if (dialogExecutor != null) + { + dialogExecutor.shutdownNow(); + } + + closeAll_actionPerformed(null); + + if (groovyConsole != null) + { + // suppress a possible repeat prompt to save script + groovyConsole.setDirty(false); + groovyConsole.exit(); + } + + if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT) + { + // note that shutdown hook will not be run + jalview.bin.Console.debug("Force Quit selected by user"); + Runtime.getRuntime().halt(0); + } + + jalview.bin.Console.debug("Quit selected by user"); + if (disposeFlag) + { + instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + // instance.dispose(); + } + instance.quit(); + + return null; // Void + }; + + return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit, + QuitHandler.defaultCancelQuit); + } + + /** + * Don't call this directly, use desktopQuit() above. Exits the program. + */ + @Override + public void quit() + { + // this will run the shutdownHook but QuitHandler.getQuitResponse() should + // not run a second time if gotQuitResponse flag has been set (i.e. user + // confirmed quit of some kind). System.exit(0); } @@ -1822,42 +1858,37 @@ public class Desktop extends jalview.jbgui.GDesktop // allowBackupFiles chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle(MessageManager.getString("label.restore_state")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); + setProjectFile(selectedFile); + String choice = selectedFile.getAbsolutePath(); + Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); + new Thread(new Runnable() { - File selectedFile = chooser.getSelectedFile(); - setProjectFile(selectedFile); - String choice = selectedFile.getAbsolutePath(); - Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); - new Thread(new Runnable() + @Override + public void run() { - @Override - public void run() + try { - try - { - new Jalview2XML().loadJalviewAlign(selectedFile); - } catch (OutOfMemoryError oom) - { - new OOMWarning("Whilst loading project from " + choice, oom); - } catch (Exception ex) - { - jalview.bin.Console.error( - "Problems whilst loading project from " + choice, ex); - JvOptionPane.showMessageDialog(Desktop.desktop, - MessageManager.formatMessage( - "label.error_whilst_loading_project_from", - new Object[] - { choice }), - MessageManager - .getString("label.couldnt_load_project"), - JvOptionPane.WARNING_MESSAGE); - } + new Jalview2XML().loadJalviewAlign(selectedFile); + } catch (OutOfMemoryError oom) + { + new OOMWarning("Whilst loading project from " + choice, oom); + } catch (Exception ex) + { + jalview.bin.Console.error( + "Problems whilst loading project from " + choice, ex); + JvOptionPane.showMessageDialog(Desktop.desktop, + MessageManager.formatMessage( + "label.error_whilst_loading_project_from", + new Object[] + { choice }), + MessageManager.getString("label.couldnt_load_project"), + JvOptionPane.WARNING_MESSAGE); } - }, "Project Loader").start(); - } + } + }, "Project Loader").start(); + return null; }); chooser.showOpenDialog(this); @@ -2521,7 +2552,7 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void actionPerformed(ActionEvent e) { - quit(); + desktopQuit(); } }); } diff --git a/src/jalview/gui/EditNameDialog.java b/src/jalview/gui/EditNameDialog.java index 52791c8..ff0fe3a 100644 --- a/src/jalview/gui/EditNameDialog.java +++ b/src/jalview/gui/EditNameDialog.java @@ -20,10 +20,9 @@ */ package jalview.gui; -import jalview.util.MessageManager; - import java.awt.FlowLayout; import java.awt.Font; +import java.util.concurrent.Callable; import javax.swing.BoxLayout; import javax.swing.JButton; @@ -32,6 +31,8 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; +import jalview.util.MessageManager; + /** * A dialog where a name and description may be edited */ @@ -111,7 +112,7 @@ public class EditNameDialog * * @param action */ - public void showDialog(JComponent parent, String title, Runnable action) + public void showDialog(JComponent parent, String title, Callable action) { Object[] options = new Object[] { MessageManager.getString("action.ok"), MessageManager.getString("action.cancel") }; 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 bb15b55..ebd4712 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -20,8 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -54,6 +52,7 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -63,7 +62,6 @@ import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JCheckBoxMenuItem; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; @@ -413,6 +411,7 @@ public class FeatureSettings extends JPanel { frame = new JInternalFrame(); frame.setContentPane(this); + frame.setFrameIcon(WindowIcons.featuresIcon); Rectangle bounds = af.getFeatureSettingsGeometry(); String title; if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels( @@ -949,14 +948,10 @@ public class FeatureSettings extends JPanel chooser.setDialogTitle( MessageManager.getString("label.load_feature_colours")); chooser.setToolTipText(MessageManager.getString("action.load")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File file = chooser.getSelectedFile(); - load(file); - } + chooser.setResponseHandler(0, () -> { + File file = chooser.getSelectedFile(); + load(file); + return null; }); chooser.showOpenDialog(this); } diff --git a/src/jalview/gui/Finder.java b/src/jalview/gui/Finder.java index 358d9a4..ee70565 100755 --- a/src/jalview/gui/Finder.java +++ b/src/jalview/gui/Finder.java @@ -20,8 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; @@ -30,6 +28,7 @@ import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -109,6 +108,7 @@ public class Finder extends GFinder focusFixed = fixedFocus; finders = new HashMap<>(); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.logoIcon); frame.setContentPane(this); frame.setLayer(JLayeredPane.PALETTE_LAYER); frame.addInternalFrameListener(new InternalFrameAdapter() diff --git a/src/jalview/gui/FontChooser.java b/src/jalview/gui/FontChooser.java index 92cc4c6..2e8e667 100755 --- a/src/jalview/gui/FontChooser.java +++ b/src/jalview/gui/FontChooser.java @@ -20,10 +20,6 @@ */ package jalview.gui; -import jalview.bin.Cache; -import jalview.jbgui.GFontChooser; -import jalview.util.MessageManager; - import java.awt.Font; import java.awt.FontMetrics; import java.awt.geom.Rectangle2D; @@ -31,6 +27,10 @@ import java.awt.geom.Rectangle2D; import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; +import jalview.bin.Cache; +import jalview.jbgui.GFontChooser; +import jalview.util.MessageManager; + /** * DOCUMENT ME! * @@ -112,6 +112,7 @@ public class FontChooser extends GFontChooser void init() { frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.logoIcon); frame.setContentPane(this); smoothFont.setSelected(ap.av.antiAlias); 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 028e50b..d805164 100644 --- a/src/jalview/gui/JvOptionPane.java +++ b/src/jalview/gui/JvOptionPane.java @@ -18,15 +18,22 @@ * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ - package jalview.gui; +import java.awt.AWTEvent; +import java.awt.ActiveEvent; import java.awt.Component; +import java.awt.Container; import java.awt.Dialog.ModalityType; +import java.awt.EventQueue; import java.awt.HeadlessException; +import java.awt.MenuComponent; +import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseMotionAdapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -34,22 +41,26 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.Executors; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JDialog; +import javax.swing.JFrame; import javax.swing.JInternalFrame; +import javax.swing.JLayeredPane; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; import jalview.util.Platform; import jalview.util.dialogrunner.DialogRunnerI; -public class JvOptionPane extends JOptionPane - implements DialogRunnerI, PropertyChangeListener -{ +public class JvOptionPane extends JOptionPane implements DialogRunnerI, PropertyChangeListener { private static final long serialVersionUID = -3019167117756785229L; private static Object mockResponse = JvOptionPane.CANCEL_OPTION; @@ -58,26 +69,21 @@ public class JvOptionPane extends JOptionPane private Component parentComponent; - private Map 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; - public JvOptionPane(final Component parent) - { + public JvOptionPane(final Component parent) { this.parentComponent = Platform.isJS() ? this : parent; } - public static int showConfirmDialog(Component parentComponent, - Object message) throws HeadlessException - { + public static int showConfirmDialog(Component parentComponent, Object message) throws HeadlessException { // only called by test - return isInteractiveMode() - ? JOptionPane.showConfirmDialog(parentComponent, message) - : (int) getMockResponse(); + return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message) : (int) getMockResponse(); } /** @@ -90,16 +96,12 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static int showConfirmDialog(Component parentComponent, - Object message, String title, int optionType) - throws HeadlessException - { - if (!isInteractiveMode()) - { + public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType) + throws HeadlessException { + if (!isInteractiveMode()) { return (int) getMockResponse(); } - switch (optionType) - { + switch (optionType) { case JvOptionPane.YES_NO_CANCEL_OPTION: // FeatureRenderer amendFeatures ?? TODO ?? // Chimera close @@ -112,8 +114,7 @@ public class JvOptionPane extends JOptionPane // $FALL-THROUGH$ case JvOptionPane.OK_CANCEL_OPTION: // will fall back to simple HTML - return JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType); + return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType); } } @@ -128,16 +129,12 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static int showConfirmDialog(Component parentComponent, - Object message, String title, int optionType, int messageType) - throws HeadlessException - { + public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType, + int messageType) throws HeadlessException { // JalviewServicesChanged // PromptUserConfig raiseDialog - return isInteractiveMode() - ? JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType, messageType) - : (int) getMockResponse(); + return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType) + : (int) getMockResponse(); } /** @@ -152,15 +149,12 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static int showConfirmDialog(Component parentComponent, - Object message, String title, int optionType, int messageType, - Icon icon) throws HeadlessException - { + public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType, + int messageType, Icon icon) throws HeadlessException { // JvOptionPaneTest only return isInteractiveMode() - ? JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType, messageType, icon) - : (int) getMockResponse(); + ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon) + : (int) getMockResponse(); } /** @@ -170,14 +164,10 @@ public class JvOptionPane extends JOptionPane * @param message * @return */ - public static int showInternalConfirmDialog(Component parentComponent, - Object message) - { + public static int showInternalConfirmDialog(Component parentComponent, Object message) { // JvOptionPaneTest only; - return isInteractiveMode() - ? JOptionPane.showInternalConfirmDialog(parentComponent, - message) - : (int) getMockResponse(); + return isInteractiveMode() ? JOptionPane.showInternalConfirmDialog(parentComponent, message) + : (int) getMockResponse(); } /** @@ -189,15 +179,11 @@ public class JvOptionPane extends JOptionPane * @param optionType * @return */ - public static int showInternalConfirmDialog(Component parentComponent, - String message, String title, int optionType) - { - if (!isInteractiveMode()) - { + public static int showInternalConfirmDialog(Component parentComponent, String message, String title, int optionType) { + if (!isInteractiveMode()) { return (int) getMockResponse(); } - switch (optionType) - { + switch (optionType) { case JvOptionPane.YES_NO_CANCEL_OPTION: // ColourMenuHelper.addMenuItmers.offerRemoval TODO case JvOptionPane.YES_NO_OPTION: @@ -210,8 +196,7 @@ public class JvOptionPane extends JOptionPane // Desktop.inputURLMenuItem // WsPreferenses - return JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType); + return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType); } } @@ -224,15 +209,12 @@ public class JvOptionPane extends JOptionPane * @param messageType * @return */ - public static int showInternalConfirmDialog(Component parentComponent, - Object message, String title, int optionType, int messageType) - { - if (!isInteractiveMode()) - { + public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType, + int messageType) { + if (!isInteractiveMode()) { return (int) getMockResponse(); } - switch (optionType) - { + switch (optionType) { case JvOptionPane.YES_NO_CANCEL_OPTION: case JvOptionPane.YES_NO_OPTION: // UserQuestionanaireCheck @@ -241,8 +223,7 @@ public class JvOptionPane extends JOptionPane default: case JvOptionPane.OK_CANCEL_OPTION: // will fall back to simple HTML - return JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType, messageType); + return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType); } } @@ -257,24 +238,19 @@ public class JvOptionPane extends JOptionPane * @param icon * @return */ - public static int showInternalConfirmDialog(Component parentComponent, - Object message, String title, int optionType, int messageType, - Icon icon) - { - if (!isInteractiveMode()) - { + public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType, + int messageType, Icon icon) { + if (!isInteractiveMode()) { return (int) getMockResponse(); } - switch (optionType) - { + switch (optionType) { case JvOptionPane.YES_NO_CANCEL_OPTION: case JvOptionPane.YES_NO_OPTION: //$FALL-THROUGH$ default: case JvOptionPane.OK_CANCEL_OPTION: // Preferences editLink/newLink - return JOptionPane.showConfirmDialog(parentComponent, message, title, - optionType, messageType, icon); + return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon); } } @@ -293,13 +269,9 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static int showOptionDialog(Component parentComponent, - String message, String title, int optionType, int messageType, - Icon icon, Object[] options, Object initialValue) - throws HeadlessException - { - if (!isInteractiveMode()) - { + public static int showOptionDialog(Component parentComponent, String message, String title, int optionType, + int messageType, Icon icon, Object[] options, Object initialValue) throws HeadlessException { + if (!isInteractiveMode()) { return (int) getMockResponse(); } // two uses: @@ -316,8 +288,8 @@ public class JvOptionPane extends JOptionPane // // 2) UserDefinedColors warning about saving over a name already defined // - return JOptionPane.showOptionDialog(parentComponent, message, title, - optionType, messageType, icon, options, initialValue); + return JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options, + initialValue); } /** @@ -326,11 +298,8 @@ public class JvOptionPane extends JOptionPane * @param message * @throws HeadlessException */ - public static void showMessageDialog(Component parentComponent, - String message) throws HeadlessException - { - if (!isInteractiveMode()) - { + public static void showMessageDialog(Component parentComponent, String message) throws HeadlessException { + if (!isInteractiveMode()) { outputMessage(message); return; } @@ -349,20 +318,16 @@ public class JvOptionPane extends JOptionPane * @param messageType * @throws HeadlessException */ - public static void showMessageDialog(Component parentComponent, - String message, String title, int messageType) - throws HeadlessException - { + public static void showMessageDialog(Component parentComponent, String message, String title, int messageType) + throws HeadlessException { // 30 implementations -- all just fine. - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { outputMessage(message); return; } - JOptionPane.showMessageDialog(parentComponent, - getPrefix(messageType) + message, title, messageType); + JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType); } /** @@ -375,35 +340,28 @@ public class JvOptionPane extends JOptionPane * @param icon * @throws HeadlessException */ - public static void showMessageDialog(Component parentComponent, - String message, String title, int messageType, Icon icon) - throws HeadlessException - { + public static void showMessageDialog(Component parentComponent, String message, String title, int messageType, + Icon icon) throws HeadlessException { // test only - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { outputMessage(message); return; } - JOptionPane.showMessageDialog(parentComponent, message, title, - messageType, icon); + JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon); } /** * was internal * */ - public static void showInternalMessageDialog(Component parentComponent, - Object message) - { + public static void showInternalMessageDialog(Component parentComponent, Object message) { // WsPreferences only - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { outputMessage(message); return; } @@ -419,20 +377,17 @@ public class JvOptionPane extends JOptionPane * @param title * @param messageType */ - public static void showInternalMessageDialog(Component parentComponent, - String message, String title, int messageType) - { + public static void showInternalMessageDialog(Component parentComponent, String message, String title, + int messageType) { // 41 references - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { outputMessage(message); return; } - JOptionPane.showMessageDialog(parentComponent, - getPrefix(messageType) + message, title, messageType); + JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType); } /** @@ -443,20 +398,17 @@ public class JvOptionPane extends JOptionPane * @param messageType * @param icon */ - public static void showInternalMessageDialog(Component parentComponent, - Object message, String title, int messageType, Icon icon) - { + public static void showInternalMessageDialog(Component parentComponent, Object message, String title, int messageType, + Icon icon) { // test only - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { outputMessage(message); return; } - JOptionPane.showMessageDialog(parentComponent, message, title, - messageType, icon); + JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon); } /** @@ -465,13 +417,10 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static String showInputDialog(Object message) - throws HeadlessException - { + public static String showInputDialog(Object message) throws HeadlessException { // test only - if (!isInteractiveMode()) - { + if (!isInteractiveMode()) { return getMockResponse().toString(); } @@ -485,11 +434,8 @@ public class JvOptionPane extends JOptionPane * @param initialSelectionValue * @return */ - public static String showInputDialog(String message, - String initialSelectionValue) - { - if (!isInteractiveMode()) - { + public static String showInputDialog(String message, String initialSelectionValue) { + if (!isInteractiveMode()) { return getMockResponse().toString(); } @@ -505,11 +451,8 @@ public class JvOptionPane extends JOptionPane * @param initialSelectionValue * @return */ - public static String showInputDialog(Object message, - Object initialSelectionValue) - { - if (!isInteractiveMode()) - { + public static String showInputDialog(Object message, Object initialSelectionValue) { + if (!isInteractiveMode()) { return getMockResponse().toString(); } @@ -526,14 +469,10 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static String showInputDialog(Component parentComponent, - String message) throws HeadlessException - { + public static String showInputDialog(Component parentComponent, String message) throws HeadlessException { // test only - return isInteractiveMode() - ? JOptionPane.showInputDialog(parentComponent, message) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message) : getMockResponse().toString(); } /** @@ -544,16 +483,12 @@ public class JvOptionPane extends JOptionPane * @param initialSelectionValue * @return */ - public static String showInputDialog(Component parentComponent, - String message, String initialSelectionValue) - { + public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue) { // AnnotationPanel - return isInteractiveMode() - ? JOptionPane.showInputDialog(parentComponent, message, - initialSelectionValue) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue) + : getMockResponse().toString(); } /** @@ -564,16 +499,12 @@ public class JvOptionPane extends JOptionPane * @param initialSelectionValue * @return */ - public static String showInputDialog(Component parentComponent, - Object message, Object initialSelectionValue) - { + public static String showInputDialog(Component parentComponent, Object message, Object initialSelectionValue) { // AnnotationPanel - return isInteractiveMode() - ? JOptionPane.showInputDialog(parentComponent, message, - initialSelectionValue) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue) + : getMockResponse().toString(); } /** @@ -585,17 +516,13 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static String showInputDialog(Component parentComponent, - String message, String title, int messageType) - throws HeadlessException - { + public static String showInputDialog(Component parentComponent, String message, String title, int messageType) + throws HeadlessException { // test only - return isInteractiveMode() - ? JOptionPane.showInputDialog(parentComponent, message, title, - messageType) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, title, messageType) + : getMockResponse().toString(); } /** @@ -611,19 +538,15 @@ public class JvOptionPane extends JOptionPane * @return * @throws HeadlessException */ - public static Object showInputDialog(Component parentComponent, - Object message, String title, int messageType, Icon icon, - Object[] selectionValues, Object initialSelectionValue) - throws HeadlessException - { + public static Object showInputDialog(Component parentComponent, Object message, String title, int messageType, + Icon icon, Object[] selectionValues, Object initialSelectionValue) throws HeadlessException { // test only return isInteractiveMode() - ? JOptionPane.showInputDialog(parentComponent, message, title, - messageType, icon, selectionValues, - initialSelectionValue) - : getMockResponse().toString(); + ? JOptionPane.showInputDialog(parentComponent, message, title, messageType, icon, selectionValues, + initialSelectionValue) + : getMockResponse().toString(); } /** @@ -633,14 +556,11 @@ public class JvOptionPane extends JOptionPane * @param message * @return */ - public static String showInternalInputDialog(Component parentComponent, - String message) - { + public static String showInternalInputDialog(Component parentComponent, String message) { // test only - return isInteractiveMode() - ? JOptionPane.showInternalInputDialog(parentComponent, message) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message) + : getMockResponse().toString(); } /** @@ -652,16 +572,14 @@ public class JvOptionPane extends JOptionPane * @param messageType * @return */ - public static String showInternalInputDialog(Component parentComponent, - String message, String title, int messageType) - { + public static String showInternalInputDialog(Component parentComponent, String message, String title, + int messageType) { // AlignFrame tabbedPane_mousePressed return isInteractiveMode() - ? JOptionPane.showInternalInputDialog(parentComponent, - getPrefix(messageType) + message, title, messageType) - : getMockResponse().toString(); + ? JOptionPane.showInternalInputDialog(parentComponent, getPrefix(messageType) + message, title, messageType) + : getMockResponse().toString(); } /** @@ -676,61 +594,47 @@ public class JvOptionPane extends JOptionPane * @param initialSelectionValue * @return */ - public static Object showInternalInputDialog(Component parentComponent, - String message, String title, int messageType, Icon icon, - Object[] selectionValues, Object initialSelectionValue) - { + public static Object showInternalInputDialog(Component parentComponent, String message, String title, int messageType, + Icon icon, Object[] selectionValues, Object initialSelectionValue) { // test only - return isInteractiveMode() - ? JOptionPane.showInternalInputDialog(parentComponent, message, - title, messageType, icon, selectionValues, - initialSelectionValue) - : getMockResponse().toString(); + return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message, title, messageType, icon, + selectionValues, initialSelectionValue) : getMockResponse().toString(); } ///////////// end of options /////////////// - private static void outputMessage(Object message) - { + private static void outputMessage(Object message) { System.out.println(">>> JOption Message : " + message.toString()); } - public static Object getMockResponse() - { + public static Object getMockResponse() { return mockResponse; } - public static void setMockResponse(Object mockOption) - { + public static void setMockResponse(Object mockOption) { JvOptionPane.mockResponse = mockOption; } - public static void resetMock() - { + public static void resetMock() { setMockResponse(JvOptionPane.CANCEL_OPTION); setInteractiveMode(true); } - public static boolean isInteractiveMode() - { + public static boolean isInteractiveMode() { return interactiveMode; } - public static void setInteractiveMode(boolean interactive) - { + public static void setInteractiveMode(boolean interactive) { JvOptionPane.interactiveMode = interactive; } - private static String getPrefix(int messageType) - { + private static String getPrefix(int messageType) { String prefix = ""; // JavaScript only - if (Platform.isJS()) - { - switch (messageType) - { + if (Platform.isJS()) { + switch (messageType) { case JvOptionPane.WARNING_MESSAGE: prefix = "WARNING! "; break; @@ -758,24 +662,27 @@ public class JvOptionPane extends JOptionPane * @param string2 * @return */ - public static JvOptionPane newOptionDialog(Component parentComponent) - { + public static JvOptionPane newOptionDialog() { + return new JvOptionPane(null); + } + + public static JvOptionPane newOptionDialog(Component parentComponent) { return new JvOptionPane(parentComponent); } - public void showDialog(String message, String title, int optionType, - int messageType, Icon icon, Object[] options, Object initialValue) - { - showDialog(message, title, optionType, messageType, icon, options, - initialValue, true); + public void showDialog(String message, String title, int optionType, int messageType, Icon icon, Object[] options, + Object initialValue) { + showDialog(message, title, optionType, messageType, icon, options, initialValue, true); } - public void showDialog(String message, String title, int optionType, - int messageType, Icon icon, Object[] options, Object initialValue, - boolean modal) - { - if (!isInteractiveMode()) - { + public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options, + Object initialValue, boolean modal) { + showDialog(message, title, optionType, messageType, icon, options, initialValue, modal, null); + } + + public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options, + Object initialValue, boolean modal, JButton[] buttons) { + if (!isInteractiveMode()) { handleResponse(getMockResponse()); } // two uses: @@ -795,15 +702,80 @@ public class JvOptionPane extends JOptionPane ourOptions = Arrays.asList(options); - if (modal) - { + if (modal) { + boolean useButtons = false; + Object initialValueButton = null; + NOTNULL: if (buttons != null) { + if (buttons.length != options.length) { + jalview.bin.Console.error("Supplied buttons array not the same length as supplied options array."); + break NOTNULL; + } + int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION }; + for (int i = 0; i < options.length; i++) { + Object o = options[i]; + jalview.bin.Console.debug("Setting button " + i + " to '" + o.toString() + "'"); + JButton jb = buttons[i]; + + if (o.equals(initialValue)) + initialValueButton = jb; + + int buttonAction = buttonActions[i]; + Callable action = callbacks.get(buttonAction); + jb.setText((String) o); + jb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + + Object obj = e.getSource(); + if (obj == null || !(obj instanceof Component)) { + jalview.bin.Console.debug("Could not find Component source of event object " + obj); + return; + } + Object joptionpaneObject = SwingUtilities.getAncestorOfClass(JOptionPane.class, (Component) obj); + if (joptionpaneObject == null || !(joptionpaneObject instanceof JOptionPane)) { + jalview.bin.Console.debug("Could not find JOptionPane ancestor of event object " + obj); + return; + } + JOptionPane joptionpane = (JOptionPane) joptionpaneObject; + joptionpane.setValue(buttonAction); + if (action != null) + Executors.newSingleThreadExecutor().submit(action); + joptionpane.transferFocusBackward(); + joptionpane.setVisible(false); + // put focus and raise parent window if possible, unless cancel or + // no button pressed + boolean raiseParent = (parentComponent != null); + if (buttonAction == JOptionPane.CANCEL_OPTION) + raiseParent = false; + if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION) + raiseParent = false; + if (raiseParent) { + parentComponent.requestFocus(); + if (parentComponent instanceof JInternalFrame) { + JInternalFrame jif = (JInternalFrame) parentComponent; + jif.show(); + jif.moveToFront(); + jif.grabFocus(); + } else if (parentComponent instanceof Window) { + Window w = (Window) parentComponent; + w.toFront(); + w.requestFocus(); + } + } + joptionpane.setVisible(false); + } + }); + + } + useButtons = true; + } // use a JOptionPane as usual - int response = JOptionPane.showOptionDialog(parentComponent, message, - title, optionType, messageType, icon, options, initialValue); + int response = JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, + useButtons ? buttons : options, useButtons ? initialValueButton : initialValue); /* - * In Java, the response is returned to this thread and handled here; - * (for Javascript, see propertyChange) + * In Java, the response is returned to this thread and handled here; (for + * Javascript, see propertyChange) */ if (!Platform.isJS()) /** @@ -814,35 +786,25 @@ public class JvOptionPane extends JOptionPane { handleResponse(response); } - } - else - { + } else { /* - * This is java similar to the swingjs handling, with the callbacks - * attached to the button press of the dialog. This means we can use - * a non-modal JDialog for the confirmation without blocking the GUI. + * This is java similar to the swingjs handling, with the callbacks attached to + * the button press of the dialog. This means we can use a non-modal JDialog for + * the confirmation without blocking the GUI. */ JOptionPane joptionpane = new JOptionPane(); // Make button options - int[] buttonActions = { JvOptionPane.YES_OPTION, - JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION }; + int[] buttonActions = { JvOptionPane.YES_OPTION, JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION }; // we need the strings to make the buttons with actionEventListener - if (options == null) - { + if (options == null) { ArrayList options_default = new ArrayList<>(); - options_default - .add(UIManager.getString("OptionPane.yesButtonText")); - if (optionType == JvOptionPane.YES_NO_OPTION - || optionType == JvOptionPane.YES_NO_CANCEL_OPTION) - { - options_default - .add(UIManager.getString("OptionPane.noButtonText")); + options_default.add(UIManager.getString("OptionPane.yesButtonText")); + if (optionType == JvOptionPane.YES_NO_OPTION || optionType == JvOptionPane.YES_NO_CANCEL_OPTION) { + options_default.add(UIManager.getString("OptionPane.noButtonText")); } - if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION) - { - options_default - .add(UIManager.getString("OptionPane.cancelButtonText")); + if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION) { + options_default.add(UIManager.getString("OptionPane.cancelButtonText")); } options = options_default.toArray(); } @@ -851,21 +813,18 @@ public class JvOptionPane extends JOptionPane Object initialValue_btn = null; if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here { - for (int i = 0; i < options.length && i < 3; i++) - { + for (int i = 0; i < options.length && i < 3; i++) { Object o = options[i]; int buttonAction = buttonActions[i]; - Runnable action = callbacks.get(buttonAction); + Callable action = callbacks.get(buttonAction); JButton jb = new JButton(); jb.setText((String) o); - jb.addActionListener(new ActionListener() - { + jb.addActionListener(new ActionListener() { @Override - public void actionPerformed(ActionEvent e) - { + public void actionPerformed(ActionEvent e) { joptionpane.setValue(buttonAction); if (action != null) - Executors.defaultThreadFactory().newThread(action).start(); + Executors.newSingleThreadExecutor().submit(action); // joptionpane.transferFocusBackward(); joptionpane.transferFocusBackward(); joptionpane.setVisible(false); @@ -874,21 +833,16 @@ public class JvOptionPane extends JOptionPane boolean raiseParent = (parentComponent != null); if (buttonAction == JvOptionPane.CANCEL_OPTION) raiseParent = false; - if (optionType == JvOptionPane.YES_NO_OPTION - && buttonAction == JvOptionPane.NO_OPTION) + if (optionType == JvOptionPane.YES_NO_OPTION && buttonAction == JvOptionPane.NO_OPTION) raiseParent = false; - if (raiseParent) - { + if (raiseParent) { parentComponent.requestFocus(); - if (parentComponent instanceof JInternalFrame) - { + if (parentComponent instanceof JInternalFrame) { JInternalFrame jif = (JInternalFrame) parentComponent; jif.show(); jif.moveToFront(); jif.grabFocus(); - } - else if (parentComponent instanceof Window) - { + } else if (parentComponent instanceof Window) { Window w = (Window) parentComponent; w.toFront(); w.requestFocus(); @@ -906,42 +860,82 @@ public class JvOptionPane extends JOptionPane joptionpane.setMessageType(messageType); joptionpane.setOptionType(optionType); joptionpane.setIcon(icon); - joptionpane.setOptions( - Platform.isJS() ? options : options_btns.toArray()); - joptionpane.setInitialValue( - Platform.isJS() ? initialValue : initialValue_btn); + joptionpane.setOptions(Platform.isJS() ? options : options_btns.toArray()); + joptionpane.setInitialValue(Platform.isJS() ? initialValue : initialValue_btn); JDialog dialog = joptionpane.createDialog(parentComponent, title); - dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL - : ModalityType.MODELESS); + dialog.setIconImage(WindowIcons.logoIcon.getImage()); + dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setVisible(true); } } - public void showInternalDialog(JPanel mainPanel, String title, - int yesNoCancelOption, int questionMessage, Icon icon, - Object[] options, String initresponse) - { - if (!isInteractiveMode()) - { + public void showInternalDialog(JPanel mainPanel, String title, int yesNoCancelOption, int questionMessage, Icon icon, + Object[] options, String initresponse) { + if (!isInteractiveMode()) { handleResponse(getMockResponse()); } + // need to set these separately so we can set the title bar icon later + this.setOptionType(yesNoCancelOption); + this.setMessageType(questionMessage); + this.setIcon(icon); + this.setInitialValue(initresponse); + this.setOptions(options); + this.setMessage(mainPanel); + ourOptions = Arrays.asList(options); int response; - if (parentComponent != this) - { - response = JOptionPane.showInternalOptionDialog(parentComponent, - mainPanel, title, yesNoCancelOption, questionMessage, icon, - options, initresponse); - } - else - { - response = JOptionPane.showOptionDialog(parentComponent, mainPanel, - title, yesNoCancelOption, questionMessage, icon, options, - initresponse); + if (parentComponent != this) { + JInternalFrame jif = this.createInternalFrame(parentComponent, title); + jif.setFrameIcon(WindowIcons.logoIcon); + jif.addInternalFrameListener(new InternalFrameListener() { + @Override + public void internalFrameActivated(InternalFrameEvent arg0) { + } + + @Override + public void internalFrameClosed(InternalFrameEvent arg0) { + JvOptionPane.this.internalDialogHandleResponse(); + } + + @Override + public void internalFrameClosing(InternalFrameEvent arg0) { + } + + @Override + public void internalFrameDeactivated(InternalFrameEvent arg0) { + } + + @Override + public void internalFrameDeiconified(InternalFrameEvent arg0) { + } + + @Override + public void internalFrameIconified(InternalFrameEvent arg0) { + } + + @Override + public void internalFrameOpened(InternalFrameEvent arg0) { + } + }); + jif.setVisible(true); + startModal(jif); + return; + } else { + JDialog dialog = this.createDialog(parentComponent, title); + dialog.setIconImage(WindowIcons.logoIcon.getImage()); + dialog.setVisible(true); // blocking + this.internalDialogHandleResponse(); + return; } + } + + private void internalDialogHandleResponse() { + String responseString = (String) this.getValue(); + int response = ourOptions.indexOf(responseString); + if (!Platform.isJS()) /** * Java only @@ -953,51 +947,294 @@ public class JvOptionPane extends JOptionPane } } + /* + * @Override public JvOptionPane setResponseHandler(Object response, Runnable + * action) { callbacks.put(response, new Callable() { + * + * @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; } /** - * JalviewJS signals option selection by a property change event for the - * option e.g. "OK". This methods responds to that by running the response - * action that corresponds to that option. + * showDialogOnTop will create a dialog that (attempts to) come to top of OS + * desktop windows + */ + public static int showDialogOnTop(String label, String actionString, int JOPTIONPANE_OPTION, + int JOPTIONPANE_MESSAGETYPE) { + // Ensure Jalview window is brought to front (primarily for Quit + // confirmation window to be visible) + + // This method of raising the Jalview window is broken in java + // jalviewDesktop.setVisible(true); + // jalviewDesktop.toFront(); + + // A better hack which works is to create a new JFrame parent with + // setAlwaysOnTop(true) + JFrame dialogParent = new JFrame(); + dialogParent.setIconImage(WindowIcons.logoIcon.getImage()); + dialogParent.setAlwaysOnTop(true); + + int answer = JOptionPane.showConfirmDialog(dialogParent, label, actionString, JOPTIONPANE_OPTION, + JOPTIONPANE_MESSAGETYPE); + + dialogParent.setAlwaysOnTop(false); + dialogParent.dispose(); + + return answer; + } + + public void showDialogOnTopAsync(String label, String actionString, int JOPTIONPANE_OPTION, + int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) { + JFrame frame = new JFrame(); + frame.setIconImage(WindowIcons.logoIcon.getImage()); + showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, + initialValue, modal); + } + + public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION, + int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) { + showDialogOnTopAsync(dialogParent, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, + initialValue, modal, null); + } + + public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION, + int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) { + // Ensure Jalview window is brought to front (primarily for Quit + // confirmation window to be visible) + + // This method of raising the Jalview window is broken in java + // jalviewDesktop.setVisible(true); + // jalviewDesktop.toFront(); + + // A better hack which works is to create a new JFrame parent with + // setAlwaysOnTop(true) + dialogParent.setAlwaysOnTop(true); + parentComponent = dialogParent; + + showDialog(label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal, + buttons); + + dialogParent.setAlwaysOnTop(false); + dialogParent.dispose(); + } + + /** + * JalviewJS signals option selection by a property change event for the option + * e.g. "OK". This methods responds to that by running the response action that + * corresponds to that option. * * @param evt */ @Override - public void propertyChange(PropertyChangeEvent evt) - { + public void propertyChange(PropertyChangeEvent evt) { Object newValue = evt.getNewValue(); int ourOption = ourOptions.indexOf(newValue); - if (ourOption >= 0) - { + if (ourOption >= 0) { handleResponse(ourOption); - } - else - { + } else { // try our luck.. handleResponse(newValue); } } @Override - public void handleResponse(Object response) - { + public void handleResponse(Object response) { /* - * this test is for NaN in Chrome - */ - if (response != null && !response.equals(response)) - { + * this test is for NaN in Chrome + */ + if (response != null && !response.equals(response)) { return; } - Runnable action = callbacks.get(response); - if (action != null) + Callable action = callbacks.get(response); + if (action != null) { + try { + action.call(); + } catch (Exception e) { + e.printStackTrace(); + } + if (parentComponent != null) + parentComponent.requestFocus(); + } + } + + /** + * Create a non-modal confirm dialog + */ + public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType, + Icon icon, Object[] options, Object initialValue, boolean modal) { + return createDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue, modal, + null); + } + + public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType, + Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) { + JButton[] optionsButtons = null; + Object initialValueButton = null; + JOptionPane joptionpane = new JOptionPane(); + // Make button options + int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION }; + + // we need the strings to make the buttons with actionEventListener + if (options == null) { + ArrayList options_default = new ArrayList<>(); + options_default.add(UIManager.getString("OptionPane.yesButtonText")); + if (optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.YES_NO_CANCEL_OPTION) { + options_default.add(UIManager.getString("OptionPane.noButtonText")); + } + if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) { + options_default.add(UIManager.getString("OptionPane.cancelButtonText")); + } + options = options_default.toArray(); + } + if (!Platform.isJS()) // JalviewJS already uses callback, don't need to + // add them here { - action.run(); - parentComponent.requestFocus(); + if (((optionType == JOptionPane.YES_OPTION || optionType == JOptionPane.NO_OPTION + || optionType == JOptionPane.CANCEL_OPTION || optionType == JOptionPane.OK_OPTION + || optionType == JOptionPane.DEFAULT_OPTION) && options.length < 1) + || ((optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.OK_CANCEL_OPTION) + && options.length < 2) + || (optionType == JOptionPane.YES_NO_CANCEL_OPTION && options.length < 3)) { + jalview.bin.Console.debug("JvOptionPane: not enough options for dialog type"); + } + optionsButtons = new JButton[options.length]; + for (int i = 0; i < options.length && i < 3; i++) { + Object o = options[i]; + int buttonAction = buttonActions[i]; + Callable action = callbacks.get(buttonAction); + JButton jb; + if (buttons != null && buttons.length > i && buttons[i] != null) { + jb = buttons[i]; + } else { + jb = new JButton(); + } + jb.setText((String) o); + jb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + joptionpane.setValue(buttonAction); + if (action != null) + Executors.newSingleThreadExecutor().submit(action); + // joptionpane.transferFocusBackward(); + joptionpane.transferFocusBackward(); + joptionpane.setVisible(false); + // put focus and raise parent window if possible, unless cancel + // button pressed + boolean raiseParent = (parentComponent != null); + if (buttonAction == JOptionPane.CANCEL_OPTION) + raiseParent = false; + if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION) + raiseParent = false; + if (raiseParent) { + parentComponent.requestFocus(); + if (parentComponent instanceof JInternalFrame) { + JInternalFrame jif = (JInternalFrame) parentComponent; + jif.show(); + jif.moveToFront(); + jif.grabFocus(); + } else if (parentComponent instanceof Window) { + Window w = (Window) parentComponent; + w.toFront(); + w.requestFocus(); + } + } + joptionpane.setVisible(false); + } + }); + optionsButtons[i] = jb; + if (o.equals(initialValue)) + initialValueButton = jb; + } + } + joptionpane.setMessage(message); + joptionpane.setMessageType(messageType); + joptionpane.setOptionType(optionType); + joptionpane.setIcon(icon); + joptionpane.setOptions(Platform.isJS() ? options : optionsButtons); + joptionpane.setInitialValue(Platform.isJS() ? initialValue : initialValueButton); + + JDialog dialog = joptionpane.createDialog(parentComponent, title); + dialog.setIconImage(WindowIcons.logoIcon.getImage()); + dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + return dialog; + } + + /** + * Utility to programmatically click a button on a JOptionPane (as a JFrame) + * + * returns true if button was found + */ + public static boolean clickButton(JFrame frame, int buttonType) { + + return false; + } + + /** + * This helper method makes the JInternalFrame wait until it is notified by an + * InternalFrameClosing event. This method also adds the given JOptionPane to + * the JInternalFrame and sizes it according to the JInternalFrame's preferred + * size. + * + * @param f The JInternalFrame to make modal. + */ + private static void startModal(JInternalFrame f) { + // We need to add an additional glasspane-like component directly + // below the frame, which intercepts all mouse events that are not + // directed at the frame itself. + JPanel modalInterceptor = new JPanel(); + modalInterceptor.setOpaque(false); + JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f); + lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue()); + modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight()); + modalInterceptor.addMouseListener(new MouseAdapter() { + }); + modalInterceptor.addMouseMotionListener(new MouseMotionAdapter() { + }); + lp.add(modalInterceptor); + f.toFront(); + + // We need to explicitly dispatch events when we are blocking the event + // dispatch thread. + EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + try { + while (!f.isClosed()) { + if (EventQueue.isDispatchThread()) { + // The getNextEventMethod() issues wait() when no + // event is available, so we don't need do explicitly wait(). + AWTEvent ev = queue.getNextEvent(); + // This mimics EventQueue.dispatchEvent(). We can't use + // EventQueue.dispatchEvent() directly, because it is + // protected, unfortunately. + if (ev instanceof ActiveEvent) + ((ActiveEvent) ev).dispatch(); + else if (ev.getSource() instanceof Component) + ((Component) ev.getSource()).dispatchEvent(ev); + else if (ev.getSource() instanceof MenuComponent) + ((MenuComponent) ev.getSource()).dispatchEvent(ev); + // Other events are ignored as per spec in + // EventQueue.dispatchEvent + } else { + // Give other threads a chance to become active. + Thread.yield(); + } + } + } catch (InterruptedException ex) { + // If we get interrupted, then leave the modal state. + } finally { + // Clean up the modal interceptor. + lp.remove(modalInterceptor); + + // Remove the internal frame from its parent, so it is no longer + // lurking around and clogging memory. + Container parent = f.getParent(); + if (parent != null) + parent.remove(f); } } } 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/PCAPanel.java b/src/jalview/gui/PCAPanel.java index e6b6b83..63ce5ad 100644 --- a/src/jalview/gui/PCAPanel.java +++ b/src/jalview/gui/PCAPanel.java @@ -20,6 +20,23 @@ */ package jalview.gui; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import javax.swing.ButtonGroup; +import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; + import jalview.analysis.scoremodels.ScoreModels; import jalview.api.AlignViewportI; import jalview.api.analysis.ScoreModelI; @@ -39,23 +56,6 @@ import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.PCAModel; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.print.PageFormat; -import java.awt.print.Printable; -import java.awt.print.PrinterException; -import java.awt.print.PrinterJob; - -import javax.swing.ButtonGroup; -import javax.swing.JMenuItem; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.event.InternalFrameAdapter; -import javax.swing.event.InternalFrameEvent; - /** * The panel holding the Principal Component Analysis 3-D visualisation */ @@ -92,6 +92,7 @@ public class PCAPanel extends GPCAPanel SimilarityParamsI params) { super(); + this.setFrameIcon(WindowIcons.treeIcon); this.av = alignPanel.av; this.ap = alignPanel; boolean nucleotide = av.getAlignment().isNucleotide(); diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 6903034..1c03d6a 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -20,8 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.ActionEvent; @@ -34,6 +32,7 @@ import java.util.Collections; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.SortedMap; @@ -1987,15 +1986,11 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener MessageManager.getString("label.group_description")); dialog.showDialog(ap.alignFrame, MessageManager.getString("label.edit_group_name_description"), - new Runnable() - { - @Override - public void run() - { - sg.setName(dialog.getName()); - sg.setDescription(dialog.getDescription()); - refresh(); - } + () -> { + sg.setName(dialog.getName()); + sg.setDescription(dialog.getDescription()); + refresh(); + return null; }); } @@ -2027,30 +2022,26 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener sequence.getDescription(), MessageManager.getString("label.sequence_name"), MessageManager.getString("label.sequence_description")); - dialog.showDialog(ap.alignFrame, MessageManager.getString( - "label.edit_sequence_name_description"), new Runnable() - { - @Override - public void run() + dialog.showDialog(ap.alignFrame, MessageManager + .getString("label.edit_sequence_name_description"), () -> { + if (dialog.getName() != null) { - if (dialog.getName() != null) + if (dialog.getName().indexOf(" ") > -1) { - if (dialog.getName().indexOf(" ") > -1) - { - JvOptionPane.showMessageDialog(ap, - MessageManager.getString( - "label.spaces_converted_to_underscores"), - MessageManager.getString( - "label.no_spaces_allowed_sequence_name"), - JvOptionPane.WARNING_MESSAGE); - } - sequence.setName(dialog.getName().replace(' ', '_')); - ap.paintAlignment(false, false); + JvOptionPane.showMessageDialog(ap, + MessageManager.getString( + "label.spaces_converted_to_underscores"), + MessageManager.getString( + "label.no_spaces_allowed_sequence_name"), + JvOptionPane.WARNING_MESSAGE); } - sequence.setDescription(dialog.getDescription()); - ap.av.firePropertyChange("alignment", null, - ap.av.getAlignment().getSequences()); + sequence.setName(dialog.getName().replace(' ', '_')); + ap.paintAlignment(false, false); } + sequence.setDescription(dialog.getDescription()); + ap.av.firePropertyChange("alignment", null, + ap.av.getAlignment().getSequences()); + return null; }); } @@ -2272,25 +2263,20 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1), null, MessageManager.getString("label.edit_sequence"), null); dialog.showDialog(ap.alignFrame, - MessageManager.getString("label.edit_sequence"), - new Runnable() - { - @Override - public void run() - { - EditCommand editCommand = new EditCommand( - MessageManager.getString("label.edit_sequences"), - Action.REPLACE, - dialog.getName().replace(' ', - ap.av.getGapCharacter()), - sg.getSequencesAsArray( - ap.av.getHiddenRepSequences()), - sg.getStartRes(), sg.getEndRes() + 1, - ap.av.getAlignment()); - ap.alignFrame.addHistoryItem(editCommand); - ap.av.firePropertyChange("alignment", null, - ap.av.getAlignment().getSequences()); - } + MessageManager.getString("label.edit_sequence"), () -> { + EditCommand editCommand = new EditCommand( + MessageManager.getString("label.edit_sequences"), + Action.REPLACE, + dialog.getName().replace(' ', + ap.av.getGapCharacter()), + sg.getSequencesAsArray( + ap.av.getHiddenRepSequences()), + sg.getStartRes(), sg.getEndRes() + 1, + ap.av.getAlignment()); + ap.alignFrame.addHistoryItem(editCommand); + ap.av.firePropertyChange("alignment", null, + ap.av.getAlignment().getSequences()); + return null; }); } } diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index 06d3a60..987051f 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -249,6 +249,7 @@ public class Preferences extends GPreferences { super(); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.preferencesIcon); frame.setContentPane(this); if (!Platform.isJS()) /** diff --git a/src/jalview/gui/QuitHandler.java b/src/jalview/gui/QuitHandler.java new file mode 100644 index 0000000..e90a2d5 --- /dev/null +++ b/src/jalview/gui/QuitHandler.java @@ -0,0 +1,410 @@ +package jalview.gui; + +import java.io.File; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JTextPane; + +import com.formdev.flatlaf.extras.FlatDesktop; + +import jalview.api.AlignmentViewPanel; +import jalview.bin.Cache; +import jalview.bin.Console; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; +import jalview.io.BackupFiles; +import jalview.project.Jalview2XML; +import jalview.util.MessageManager; +import jalview.util.Platform; + +public class QuitHandler +{ + private static final int MIN_WAIT_FOR_SAVE = 3000; + + private static final int MAX_WAIT_FOR_SAVE = 20000; + + public static enum QResponse + { + NULL, QUIT, CANCEL_QUIT, FORCE_QUIT + }; + + private static ExecutorService executor = Executors.newFixedThreadPool(3); + + public static QResponse setQuitHandler() + { + FlatDesktop.setQuitHandler(response -> { + Callable 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; + + private 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.NULL); + 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); + } + + private static boolean interactive = true; + + 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, defaultCancelQuit) + .showDialogOnTopAsync( + new StringBuilder(MessageManager + .getString("label.quit_jalview")) + .append("\n") + .append(MessageManager.getString( + "label.unsaved_changes")) + .toString(), + MessageManager.getString("action.quit"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, new Object[] + { MessageManager.getString("action.quit"), + MessageManager.getString("action.cancel") }, + MessageManager.getString("action.quit"), true); + } + + got = gotQuitResponse(); + boolean wait = false; + if (got == QResponse.CANCEL_QUIT) + { + // reset + setResponse(QResponse.NULL); + // but return cancel + return QResponse.CANCEL_QUIT; + } + else if (got == QResponse.QUIT) + { + if (Cache.getDefault("WAIT_FOR_SAVE", true) + && BackupFiles.hasSavesInProgress()) + { + waitQuit(interactive, okQuit, forceQuit, cancelQuit); + QResponse waitResponse = gotQuitResponse(); + wait = waitResponse == QResponse.QUIT; + } + } + + Callable 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/RedundancyPanel.java b/src/jalview/gui/RedundancyPanel.java index 6ed3248..7a50758 100755 --- a/src/jalview/gui/RedundancyPanel.java +++ b/src/jalview/gui/RedundancyPanel.java @@ -20,15 +20,6 @@ */ package jalview.gui; -import jalview.analysis.AlignSeq; -import jalview.commands.CommandI; -import jalview.commands.EditCommand; -import jalview.commands.EditCommand.Action; -import jalview.datamodel.SequenceGroup; -import jalview.datamodel.SequenceI; -import jalview.jbgui.GSliderPanel; -import jalview.util.MessageManager; - import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; @@ -42,6 +33,15 @@ import javax.swing.event.ChangeListener; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; +import jalview.analysis.AlignSeq; +import jalview.commands.CommandI; +import jalview.commands.EditCommand; +import jalview.commands.EditCommand.Action; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.jbgui.GSliderPanel; +import jalview.util.MessageManager; + /** * DOCUMENT ME! * @@ -100,6 +100,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable worker.start(); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.logoIcon); frame.setContentPane(this); Desktop.addInternalFrame(frame, MessageManager diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index e596fbf..3b93059 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -186,6 +186,7 @@ public class SequenceFetcher extends JPanel implements Runnable frame = new JInternalFrame(); frame.setContentPane(this); + frame.setFrameIcon(WindowIcons.fetchIcon); Desktop.addInternalFrame(frame, getFrameTitle(), true, 400, Platform.isAMacAndNotJS() ? 240 : 180); } diff --git a/src/jalview/gui/SliderPanel.java b/src/jalview/gui/SliderPanel.java index 5e1357a..6665588 100755 --- a/src/jalview/gui/SliderPanel.java +++ b/src/jalview/gui/SliderPanel.java @@ -156,6 +156,7 @@ public class SliderPanel extends GSliderPanel { sliderPanel = new SliderPanel(ap, rs.getConservationInc(), true, rs); conservationSlider = new JInternalFrame(); + conservationSlider.setFrameIcon(WindowIcons.logoIcon); conservationSlider.setContentPane(sliderPanel); conservationSlider.setLayer(JLayeredPane.PALETTE_LAYER); } @@ -269,6 +270,7 @@ public class SliderPanel extends GSliderPanel { sliderPanel = new SliderPanel(ap, threshold, false, rs); PIDSlider = new JInternalFrame(); + PIDSlider.setFrameIcon(WindowIcons.logoIcon); PIDSlider.setContentPane(sliderPanel); PIDSlider.setLayer(JLayeredPane.PALETTE_LAYER); } diff --git a/src/jalview/gui/SplitFrame.java b/src/jalview/gui/SplitFrame.java index 6ebedb7..7398a3f 100644 --- a/src/jalview/gui/SplitFrame.java +++ b/src/jalview/gui/SplitFrame.java @@ -105,6 +105,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI */ protected void init() { + setFrameIcon(WindowIcons.splitIcon); getTopFrame().setSplitFrame(this); getBottomFrame().setSplitFrame(this); getTopFrame().setVisible(true); 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 ec5579c..82b6759 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -135,6 +135,7 @@ public abstract class StructureViewerBase extends GStructureViewer public StructureViewerBase() { super(); + setFrameIcon(WindowIcons.structureIcon); } /** @@ -1280,6 +1281,9 @@ public abstract class StructureViewerBase extends GStructureViewer if (confirm == JvOptionPane.CANCEL_OPTION || confirm == JvOptionPane.CLOSED_OPTION) { + // abort possible quit handling if CANCEL chosen + if (confirm == JvOptionPane.CANCEL_OPTION) + QuitHandler.abortQuit(); return; } forceClose = confirm == JvOptionPane.YES_OPTION; 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/TreePanel.java b/src/jalview/gui/TreePanel.java index d735402..2d446d9 100755 --- a/src/jalview/gui/TreePanel.java +++ b/src/jalview/gui/TreePanel.java @@ -20,8 +20,26 @@ */ package jalview.gui; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import javax.swing.ButtonGroup; +import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; + +import org.jibble.epsgraphics.EpsGraphics2D; + import jalview.analysis.AlignmentSorter; import jalview.analysis.AverageDistanceTree; import jalview.analysis.NJTree; @@ -53,25 +71,6 @@ import jalview.util.ImageMaker.TYPE; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.File; -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.ButtonGroup; -import javax.swing.JMenuItem; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.event.InternalFrameAdapter; -import javax.swing.event.InternalFrameEvent; - -import org.jibble.epsgraphics.EpsGraphics2D; - /** * DOCUMENT ME! * @@ -106,6 +105,7 @@ public class TreePanel extends GTreePanel SimilarityParamsI options) { super(); + this.setFrameIcon(WindowIcons.treeIcon); this.similarityParams = options; initTreePanel(ap, type, modelName, null, null); diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index 1836e33..4979746 100755 --- a/src/jalview/gui/UserDefinedColours.java +++ b/src/jalview/gui/UserDefinedColours.java @@ -20,25 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - -import jalview.bin.Cache; -import jalview.io.JalviewFileChooser; -import jalview.io.JalviewFileView; -import jalview.jbgui.GUserDefinedColours; -import jalview.schemes.ColourSchemeI; -import jalview.schemes.ColourSchemeLoader; -import jalview.schemes.ColourSchemes; -import jalview.schemes.ResidueProperties; -import jalview.schemes.UserColourScheme; -import jalview.util.ColorUtils; -import jalview.util.Format; -import jalview.util.MessageManager; -import jalview.util.Platform; -import jalview.xml.binding.jalview.JalviewUserColours; -import jalview.xml.binding.jalview.JalviewUserColours.Colour; -import jalview.xml.binding.jalview.ObjectFactory; - import java.awt.Color; import java.awt.Font; import java.awt.Insets; @@ -50,6 +31,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.swing.JButton; import javax.swing.JInternalFrame; @@ -58,6 +40,23 @@ import javax.swing.event.ChangeListener; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; +import jalview.bin.Cache; +import jalview.io.JalviewFileChooser; +import jalview.io.JalviewFileView; +import jalview.jbgui.GUserDefinedColours; +import jalview.schemes.ColourSchemeI; +import jalview.schemes.ColourSchemeLoader; +import jalview.schemes.ColourSchemes; +import jalview.schemes.ResidueProperties; +import jalview.schemes.UserColourScheme; +import jalview.util.ColorUtils; +import jalview.util.Format; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.xml.binding.jalview.JalviewUserColours; +import jalview.xml.binding.jalview.JalviewUserColours.Colour; +import jalview.xml.binding.jalview.ObjectFactory; + /** * This panel allows the user to assign colours to Amino Acid residue codes, and * save the colour scheme. @@ -149,6 +148,7 @@ public class UserDefinedColours extends GUserDefinedColours { colorChooser.getSelectionModel().addChangeListener(this); frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.logoIcon); frame.setContentPane(this); Desktop.addInternalFrame(frame, MessageManager.getString("label.user_defined_colours"), @@ -652,45 +652,41 @@ public class UserDefinedColours extends GUserDefinedColours chooser.setDialogTitle( MessageManager.getString("label.load_colour_scheme")); chooser.setToolTipText(MessageManager.getString("action.load")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File choice = chooser.getSelectedFile(); - Cache.setProperty(LAST_DIRECTORY, choice.getParent()); - - UserColourScheme ucs = ColourSchemeLoader - .loadColourScheme(choice.getAbsolutePath()); - Color[] colors = ucs.getColours(); - schemeName.setText(ucs.getSchemeName()); + chooser.setResponseHandler(0, () -> { + File choice = chooser.getSelectedFile(); + Cache.setProperty(LAST_DIRECTORY, choice.getParent()); - if (ucs.getLowerCaseColours() != null) - { - caseSensitive.setSelected(true); - lcaseColour.setEnabled(true); - resetButtonPanel(true); - for (int i = 0; i < lowerCaseButtons.size(); i++) - { - JButton button = lowerCaseButtons.get(i); - button.setBackground(ucs.getLowerCaseColours()[i]); - } - } - else - { - caseSensitive.setSelected(false); - lcaseColour.setEnabled(false); - resetButtonPanel(false); - } + UserColourScheme ucs = ColourSchemeLoader + .loadColourScheme(choice.getAbsolutePath()); + Color[] colors = ucs.getColours(); + schemeName.setText(ucs.getSchemeName()); - for (int i = 0; i < upperCaseButtons.size(); i++) + if (ucs.getLowerCaseColours() != null) + { + caseSensitive.setSelected(true); + lcaseColour.setEnabled(true); + resetButtonPanel(true); + for (int i = 0; i < lowerCaseButtons.size(); i++) { - JButton button = upperCaseButtons.get(i); - button.setBackground(colors[i]); + JButton button = lowerCaseButtons.get(i); + button.setBackground(ucs.getLowerCaseColours()[i]); } + } + else + { + caseSensitive.setSelected(false); + lcaseColour.setEnabled(false); + resetButtonPanel(false); + } - addNewColourScheme(choice.getPath()); + for (int i = 0; i < upperCaseButtons.size(); i++) + { + JButton button = upperCaseButtons.get(i); + button.setBackground(colors[i]); } + + addNewColourScheme(choice.getPath()); + return null; }); chooser.showOpenDialog(this); diff --git a/src/jalview/gui/WebserviceInfo.java b/src/jalview/gui/WebserviceInfo.java index ee1b473..edcab0c 100644 --- a/src/jalview/gui/WebserviceInfo.java +++ b/src/jalview/gui/WebserviceInfo.java @@ -20,8 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -33,6 +31,7 @@ import java.awt.MediaTracker; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; +import java.util.Locale; import java.util.Vector; import javax.swing.JComponent; @@ -324,6 +323,7 @@ public class WebserviceInfo extends GWebserviceInfo boolean makeVisible) { frame = new JInternalFrame(); + frame.setFrameIcon(WindowIcons.logoIcon); frame.setContentPane(this); Desktop.addInternalFrame(frame, title, makeVisible, width, height); frame.setClosable(false); diff --git a/src/jalview/gui/WindowIcons.java b/src/jalview/gui/WindowIcons.java new file mode 100644 index 0000000..b735667 --- /dev/null +++ b/src/jalview/gui/WindowIcons.java @@ -0,0 +1,51 @@ +package jalview.gui; + +import javax.swing.ImageIcon; + +import jalview.util.ChannelProperties; + +public class WindowIcons +{ + public static final ImageIcon logoIcon = new ImageIcon( + ChannelProperties.getImage("logo.16")); + + protected static final ImageIcon plainIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/plainIcon.png")); + + protected static final ImageIcon alignmentIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/alignmentIcon.png")); + + protected static final ImageIcon featuresIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/featuresIcon.png")); + + protected static final ImageIcon fetchIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/fetchIcon.png")); + + protected static final ImageIcon annotationIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/annotationIcon.png")); + + protected static final ImageIcon overviewIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/overviewIcon.png")); + + protected static final ImageIcon treeIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/treeIcon.png")); + + protected static final ImageIcon structureIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/structureIcon.png")); + + protected static final ImageIcon splitIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/splitIcon.png")); + + protected static final ImageIcon preferencesIcon = new ImageIcon( + WindowIcons.class + .getResource("/images/windowIcons/preferencesIcon.png")); +} 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 a9101a1..077bffb 100755 --- a/src/jalview/io/JalviewFileChooser.java +++ b/src/jalview/io/JalviewFileChooser.java @@ -44,18 +44,28 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; +import java.util.concurrent.Callable; import javax.swing.BoxLayout; import javax.swing.DefaultListCellRenderer; import javax.swing.JCheckBox; +import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SpringLayout; import javax.swing.filechooser.FileFilter; import javax.swing.plaf.basic.BasicFileChooserUI; +import jalview.bin.Cache; +import jalview.gui.JvOptionPane; +import jalview.gui.WindowIcons; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.util.dialogrunner.DialogRunnerI; + /** * Enhanced file chooser dialog box. * @@ -70,7 +80,7 @@ public class JalviewFileChooser extends JFileChooser { private static final long serialVersionUID = 1L; - private Map callbacks = new HashMap<>(); + private Map callbacks = new HashMap<>(); File selectedFile = null; @@ -488,10 +498,13 @@ public class JalviewFileChooser extends JFileChooser if (selectedFile.exists()) { - int confirm = JvOptionPane.showConfirmDialog(this, - MessageManager.getString("label.overwrite_existing_file"), - MessageManager.getString("label.file_already_exists"), - JvOptionPane.YES_NO_OPTION); + int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true) + ? JvOptionPane.showConfirmDialog(this, + MessageManager + .getString("label.overwrite_existing_file"), + MessageManager.getString("label.file_already_exists"), + JvOptionPane.YES_NO_OPTION) + : JOptionPane.YES_OPTION; if (confirm != JvOptionPane.YES_OPTION) { @@ -596,8 +609,26 @@ public class JalviewFileChooser extends JFileChooser } + /* @Override - public DialogRunnerI setResponseHandler(Object response, Runnable action) + public JalviewFileChooser setResponseHandler(Object response, + Runnable action) + { + callbacks.put(response, new Callable() + { + @Override + public Void call() + { + action.run(); + return null; + } + }); + return this; + } + */ + + @Override + public DialogRunnerI setResponseHandler(Object response, Callable action) { callbacks.put(response, action); return this; @@ -613,10 +644,16 @@ public class JalviewFileChooser extends JFileChooser { return; } - Runnable action = callbacks.get(response); + Callable action = callbacks.get(response); if (action != null) { - action.run(); + try + { + action.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } @@ -641,4 +678,13 @@ public class JalviewFileChooser extends JFileChooser break; } } + + @Override + protected JDialog createDialog(Component parent) throws HeadlessException + { + JDialog dialog = super.createDialog(parent); + dialog.setIconImage(WindowIcons.logoIcon.getImage()); + return dialog; + } + } 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..9cec063 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 = 20; + int i = 0; + while (i < n) + { + Console.debug("***** debugging save sleep " + i + "/" + n); + try + { + Thread.sleep(1000); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + i++; + } + } + if (frames == null) { return; @@ -763,6 +789,25 @@ public class Jalview2XML FileOutputStream fos = new FileOutputStream( doBackup ? backupfiles.getTempFilePath() : jarFile); + if (Cache.getDefault("DEBUG_DELAY_SAVE", false)) + { + int n = 20; + int i = 0; + while (i < n) + { + Console.debug("***** debugging save sleep " + i + "/" + n); + try + { + Thread.sleep(1000); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + i++; + } + } + JarOutputStream jout = new JarOutputStream(fos); List frames = new ArrayList<>(); @@ -6537,4 +6582,36 @@ public class Jalview2XML return colour; } + + public static void setStateSavedUpToDate(boolean s) + { + Console.debug("Setting overall stateSavedUpToDate to " + s); + stateSavedUpToDate = s; + } + + public static boolean stateSavedUpToDate() + { + Console.debug("Returning overall stateSavedUpToDate value: " + + stateSavedUpToDate); + return stateSavedUpToDate; + } + + public static boolean allSavedUpToDate() + { + if (stateSavedUpToDate()) // nothing happened since last project save + return true; + + AlignFrame[] frames = Desktop.getAlignFrames(); + if (frames != null) + { + for (int i = 0; i < frames.length; i++) + { + if (frames[i] == null) + continue; + if (!frames[i].getViewport().savedUpToDate()) + return false; // at least one alignment is not individually saved + } + } + return true; + } } 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; + } }