From: Ben Soares Date: Mon, 24 Oct 2022 14:41:18 +0000 (+0100) Subject: JAL-1988 JAL-3772 Non-blocking modal dialogs for unsaved changes and saving files... X-Git-Tag: Release_2_11_3_0~23^2~4^2~30 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=b364e1e6d199002069dab615d1007799b5bb71e1 JAL-1988 JAL-3772 Non-blocking modal dialogs for unsaved changes and saving files. Wait timer not working. Wait dialog update/close on files saved not working. --- diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index f196687..73da48c 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -35,7 +35,7 @@ action.quit = Quit action.force_quit = Force Quit label.quit_jalview = Are you sure you want to quit Jalview? label.unsaved_changes = There are unsaved changes. -label.save_in_progress = Some files are still saving. +label.save_in_progress = Some files are still saving: label.unknown = Unknown action.wait = Wait action.cancel_quit = Cancel quit diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 5f49205..d9a64b3 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -35,7 +35,7 @@ action.quit = Salir action.force_quit = Forzar la salida label.quit_jalview = ¿Estás seguro de que quieres salir de Jalview? label.unsaved_changes = Hay cambios sin guardar. -label.save_in_progress = Algunos archivos aún se están guardando. +label.save_in_progress = Algunos archivos aún se están guardando: label.unknown = desconocido action.wait = Espere action.cancel_quit = Cancelar la salida diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 3d3e99f..f0477b9 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -281,6 +281,9 @@ public class Jalview Console.debug("Running shutdown hook"); if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT) { + // Got to here by a SIGTERM signal. + // Note we will not actually cancel the quit from here -- it's too + // late -- but we can wait for saving files. Console.debug("Checking for saving files"); QuitHandler.getQuitResponse(false); } 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 8a6907c..d8fec3a 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -59,6 +59,7 @@ import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Vector; +import java.util.concurrent.Callable; import javax.swing.ButtonGroup; import javax.swing.JCheckBoxMenuItem; @@ -1272,103 +1273,89 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } AlignExportSettingsI options = new AlignExportSettingsAdapter(false); - Runnable cancelAction = new Runnable() - { - @Override - public void run() + Callable 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) - { - Console.trace( - "ALIGNFRAME making backupfiles object for " + file); - backupfiles = new BackupFiles(file); - } - try - { - String tempFilePath = doBackup ? backupfiles.getTempFilePath() - : file; - Console.trace("ALIGNFRAME setting PrintWriter"); - PrintWriter out = new PrintWriter(new FileWriter(tempFilePath)); + String tempFilePath = doBackup ? backupfiles.getTempFilePath() + : file; + Console.trace("ALIGNFRAME setting PrintWriter"); + PrintWriter out = new PrintWriter(new FileWriter(tempFilePath)); - if (backupfiles != null) - { - Console.trace("ALIGNFRAME about to write to temp file " - + backupfiles.getTempFilePath()); - } - - out.print(output); - Console.trace("ALIGNFRAME about to close file"); - out.close(); - Console.trace("ALIGNFRAME closed file"); - AlignFrame.this.setTitle(file); - statusBar.setText(MessageManager.formatMessage( - "label.successfully_saved_to_file_in_format", - new Object[] - { fileName, format.getName() })); - lastSaveSuccessful = true; - } catch (IOException e) - { - lastSaveSuccessful = false; - Console.error( - "ALIGNFRAME Something happened writing the temp file"); - Console.error(e.getMessage()); - Console.debug(Cache.getStackTraceString(e)); - } catch (Exception ex) + if (backupfiles != null) { - lastSaveSuccessful = false; - Console.error( - "ALIGNFRAME Something unexpected happened writing the temp file"); - Console.error(ex.getMessage()); - Console.debug(Cache.getStackTraceString(ex)); + Console.trace("ALIGNFRAME about to write to temp file " + + backupfiles.getTempFilePath()); } - if (doBackup) - { - backupfiles.setWriteSuccess(lastSaveSuccessful); - Console.debug("ALIGNFRAME writing temp file was " - + (lastSaveSuccessful ? "" : "NOT ") + "successful"); - // do the backup file roll and rename the temp file to actual file - Console.trace( - "ALIGNFRAME about to rollBackupsAndRenameTempFile"); - lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile(); - Console.debug( - "ALIGNFRAME performed rollBackupsAndRenameTempFile " - + (lastSaveSuccessful ? "" : "un") - + "successfully"); - } + out.print(output); + Console.trace("ALIGNFRAME about to close file"); + out.close(); + Console.trace("ALIGNFRAME closed file"); + AlignFrame.this.setTitle(file); + statusBar.setText(MessageManager.formatMessage( + "label.successfully_saved_to_file_in_format", new Object[] + { fileName, format.getName() })); + lastSaveSuccessful = true; + } catch (IOException e) + { + lastSaveSuccessful = false; + Console.error( + "ALIGNFRAME Something happened writing the temp file"); + Console.error(e.getMessage()); + Console.debug(Cache.getStackTraceString(e)); + } catch (Exception ex) + { + lastSaveSuccessful = false; + Console.error( + "ALIGNFRAME Something unexpected happened writing the temp file"); + Console.error(ex.getMessage()); + Console.debug(Cache.getStackTraceString(ex)); + } - Console.debug("lastSaveSuccessful=" + lastSaveSuccessful); - if (lastSaveSuccessful) - { - AlignFrame.this.getViewport().setSavedUpToDate(true); - } + if (doBackup) + { + backupfiles.setWriteSuccess(lastSaveSuccessful); + Console.debug("ALIGNFRAME writing temp file was " + + (lastSaveSuccessful ? "" : "NOT ") + "successful"); + // do the backup file roll and rename the temp file to actual file + Console.trace("ALIGNFRAME about to rollBackupsAndRenameTempFile"); + lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile(); + Console.debug("ALIGNFRAME performed rollBackupsAndRenameTempFile " + + (lastSaveSuccessful ? "" : "un") + "successfully"); + } + + Console.debug("lastSaveSuccessful=" + lastSaveSuccessful); + if (lastSaveSuccessful) + { + AlignFrame.this.getViewport().setSavedUpToDate(true); } } + return null; }; /* @@ -1384,7 +1371,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - outputAction.run(); + try + { + outputAction.call(); + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } @@ -1401,34 +1395,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, FileFormatI fileFormat = FileFormats.getInstance() .forName(fileFormatName); AlignExportSettingsI options = new AlignExportSettingsAdapter(false); - Runnable outputAction = new Runnable() - { - @Override - public void run() + Callable outputAction = () -> { + // todo defer this to inside formatSequences (or later) + AlignmentExportData exportData = viewport.getAlignExportData(options); + CutAndPasteTransfer cap = new CutAndPasteTransfer(); + cap.setForInput(null); + try { - // todo defer this to inside formatSequences (or later) - AlignmentExportData exportData = viewport - .getAlignExportData(options); - CutAndPasteTransfer cap = new CutAndPasteTransfer(); - cap.setForInput(null); - try - { - FileFormatI format = fileFormat; - cap.setText(new FormatAdapter(alignPanel, options) - .formatSequences(format, exportData.getAlignment(), - exportData.getOmitHidden(), - exportData.getStartEndPostions(), - viewport.getAlignment().getHiddenColumns())); - Desktop.addInternalFrame(cap, MessageManager.formatMessage( - "label.alignment_output_command", new Object[] - { fileFormat.getName() }), 600, 500); - } catch (OutOfMemoryError oom) - { - new OOMWarning("Outputting alignment as " + fileFormat.getName(), - oom); - cap.dispose(); - } + FileFormatI format = fileFormat; + cap.setText(new FormatAdapter(alignPanel, options).formatSequences( + format, exportData.getAlignment(), + exportData.getOmitHidden(), + exportData.getStartEndPostions(), + viewport.getAlignment().getHiddenColumns())); + Desktop.addInternalFrame(cap, MessageManager.formatMessage( + "label.alignment_output_command", new Object[] + { fileFormat.getName() }), 600, 500); + } catch (OutOfMemoryError oom) + { + new OOMWarning("Outputting alignment as " + fileFormat.getName(), + oom); + cap.dispose(); } + return null; }; /* @@ -1443,7 +1432,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - outputAction.run(); + try + { + outputAction.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } @@ -1551,15 +1546,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, .getString("label.load_jalview_annotations"); chooser.setDialogTitle(tooltip); chooser.setToolTipText(tooltip); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - String choice = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", choice); - loadJalviewDataFile(chooser.getSelectedFile(), null, null, null); - } + chooser.setResponseHandler(0, () -> { + String choice = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", choice); + loadJalviewDataFile(chooser.getSelectedFile(), null, null, null); + return null; }); chooser.showOpenDialog(this); @@ -2481,36 +2472,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, return; } - Runnable okAction = new Runnable() - { - @Override - public void run() - { - SequenceI[] cut = sg.getSequences() - .toArray(new SequenceI[sg.getSize()]); + Callable okAction = () -> { + SequenceI[] cut = sg.getSequences() + .toArray(new SequenceI[sg.getSize()]); - addHistoryItem(new EditCommand( - MessageManager.getString("label.cut_sequences"), Action.CUT, - cut, sg.getStartRes(), - sg.getEndRes() - sg.getStartRes() + 1, - viewport.getAlignment())); + addHistoryItem(new EditCommand( + MessageManager.getString("label.cut_sequences"), Action.CUT, + cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1, + viewport.getAlignment())); - viewport.setSelectionGroup(null); - viewport.sendSelection(); - viewport.getAlignment().deleteGroup(sg); + viewport.setSelectionGroup(null); + viewport.sendSelection(); + viewport.getAlignment().deleteGroup(sg); - viewport.firePropertyChange("alignment", null, - viewport.getAlignment().getSequences()); - if (viewport.getAlignment().getHeight() < 1) + viewport.firePropertyChange("alignment", null, + viewport.getAlignment().getSequences()); + if (viewport.getAlignment().getHeight() < 1) + { + try + { + AlignFrame.this.setClosed(true); + } catch (Exception ex) { - try - { - AlignFrame.this.setClosed(true); - } catch (Exception ex) - { - } } } + return null; }; /* @@ -2534,7 +2520,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } else { - okAction.run(); + try + { + okAction.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } @@ -4087,36 +4079,31 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, chooser.setToolTipText( MessageManager.getString("label.load_tree_file")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() + chooser.setResponseHandler(0, () -> { + String filePath = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", filePath); + NewickFile fin = null; + try { - String filePath = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", filePath); - NewickFile fin = null; - try - { - fin = new NewickFile(new FileParse(chooser.getSelectedFile(), - DataSourceType.FILE)); - viewport.setCurrentTree(showNewickTree(fin, filePath).getTree()); - } catch (Exception ex) - { - JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(), - MessageManager - .getString("label.problem_reading_tree_file"), - JvOptionPane.WARNING_MESSAGE); - ex.printStackTrace(); - } - if (fin != null && fin.hasWarningMessage()) - { - JvOptionPane.showMessageDialog(Desktop.desktop, - fin.getWarningMessage(), - MessageManager.getString( - "label.possible_problem_with_tree_file"), - JvOptionPane.WARNING_MESSAGE); - } + fin = new NewickFile(new FileParse(chooser.getSelectedFile(), + DataSourceType.FILE)); + viewport.setCurrentTree(showNewickTree(fin, filePath).getTree()); + } catch (Exception ex) + { + JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(), + MessageManager.getString("label.problem_reading_tree_file"), + JvOptionPane.WARNING_MESSAGE); + ex.printStackTrace(); } + if (fin != null && fin.hasWarningMessage()) + { + JvOptionPane.showMessageDialog(Desktop.desktop, + fin.getWarningMessage(), + MessageManager + .getString("label.possible_problem_with_tree_file"), + JvOptionPane.WARNING_MESSAGE); + } + return null; }); chooser.showOpenDialog(this); } @@ -5888,16 +5875,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file")); chooser.setToolTipText(MessageManager.getString("label.load_vcf_file")); final AlignFrame us = this; - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - String choice = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", choice); - SequenceI[] seqs = viewport.getAlignment().getSequencesArray(); - new VCFLoader(choice).loadVCF(seqs, us); - } + chooser.setResponseHandler(0, () -> { + String choice = chooser.getSelectedFile().getPath(); + Cache.setProperty("LAST_DIRECTORY", choice); + SequenceI[] seqs = viewport.getAlignment().getSequencesArray(); + new VCFLoader(choice).loadVCF(seqs, us); + return null; }); chooser.showOpenDialog(null); diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 30ccdbe..e7c237e 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -20,6 +20,17 @@ */ package jalview.gui; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +import javax.swing.JInternalFrame; + import jalview.analysis.AlignmentUtils; import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.api.AlignViewportI; @@ -53,17 +64,6 @@ import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; import jalview.ws.params.AutoCalcSetting; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Rectangle; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - -import javax.swing.JInternalFrame; - /** * DOCUMENT ME! * @@ -748,27 +748,15 @@ public class AlignViewport extends AlignmentViewport * in reverse order) */ JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop) - .setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - addDataToAlignment(al); - } - }).setResponseHandler(1, new Runnable() - { - @Override - public void run() - { - us.openLinkedAlignmentAs(al, title, true); - } - }).setResponseHandler(2, new Runnable() - { - @Override - public void run() - { - us.openLinkedAlignmentAs(al, title, false); - } + .setResponseHandler(0, () -> { + addDataToAlignment(al); + return null; + }).setResponseHandler(1, () -> { + us.openLinkedAlignmentAs(al, title, true); + return null; + }).setResponseHandler(2, () -> { + us.openLinkedAlignmentAs(al, title, false); + return null; }); dialog.showDialog(question, MessageManager.getString("label.open_split_window"), diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java index 9976604..4702f2a 100755 --- a/src/jalview/gui/AnnotationLabels.java +++ b/src/jalview/gui/AnnotationLabels.java @@ -20,23 +20,6 @@ */ package jalview.gui; -import java.util.Locale; - -import jalview.analysis.AlignSeq; -import jalview.analysis.AlignmentUtils; -import jalview.datamodel.Alignment; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.Annotation; -import jalview.datamodel.HiddenColumns; -import jalview.datamodel.Sequence; -import jalview.datamodel.SequenceGroup; -import jalview.datamodel.SequenceI; -import jalview.io.FileFormat; -import jalview.io.FormatAdapter; -import jalview.util.Comparison; -import jalview.util.MessageManager; -import jalview.util.Platform; - import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; @@ -56,6 +39,7 @@ import java.awt.geom.AffineTransform; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; +import java.util.Locale; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenuItem; @@ -64,6 +48,21 @@ import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; +import jalview.analysis.AlignSeq; +import jalview.analysis.AlignmentUtils; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.Annotation; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.io.FileFormat; +import jalview.io.FormatAdapter; +import jalview.util.Comparison; +import jalview.util.MessageManager; +import jalview.util.Platform; + /** * The panel that holds the labels for alignment annotations, providing * tooltips, context menus, drag to reorder rows, and drag to adjust panel @@ -293,25 +292,21 @@ public class AnnotationLabels extends JPanel EditNameDialog dialog = new EditNameDialog(annotation.label, annotation.description, name, description); - dialog.showDialog(ap.alignFrame, title, new Runnable() - { - @Override - public void run() + dialog.showDialog(ap.alignFrame, title, () -> { + annotation.label = dialog.getName(); + String text = dialog.getDescription(); + if (text != null && text.length() == 0) { - annotation.label = dialog.getName(); - String text = dialog.getDescription(); - if (text != null && text.length() == 0) - { - text = null; - } - annotation.description = text; - if (addNew) - { - ap.av.getAlignment().addAnnotation(annotation); - ap.av.getAlignment().setAnnotationIndex(annotation, 0); - } - ap.refresh(true); + text = null; } + annotation.description = text; + if (addNew) + { + ap.av.getAlignment().addAnnotation(annotation); + ap.av.getAlignment().setAnnotationIndex(annotation, 0); + } + ap.refresh(true); + return null; }); } diff --git a/src/jalview/gui/Console.java b/src/jalview/gui/Console.java index 9cf2cc9..5a23048 100644 --- a/src/jalview/gui/Console.java +++ b/src/jalview/gui/Console.java @@ -114,6 +114,7 @@ public class Console extends WindowAdapter Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); frame = initFrame("Java Console", screenSize.width / 2, screenSize.height / 2, -1, -1); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); initConsole(true); } @@ -551,10 +552,12 @@ public class Console extends WindowAdapter { } } + /* if (!frame.isVisible()) { frame.dispose(); } + */ // System.exit(0); } diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 585537e..13253c3 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; @@ -454,17 +456,14 @@ public class Desktop extends jalview.jbgui.GDesktop setIconImages(ChannelProperties.getIconList()); + // override quit handling when GUI OS close [X] button pressed this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent ev) { - QResponse qresponse = desktopQuit(); - if (qresponse != QResponse.CANCEL_QUIT) - { - instance.dispose(); - } + QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag } }); @@ -1178,36 +1177,32 @@ public class Desktop extends jalview.jbgui.GDesktop MessageManager.getString("label.open_local_file")); chooser.setToolTipText(MessageManager.getString("action.open")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File selectedFile = chooser.getSelectedFile(); - Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); + Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); - FileFormatI format = chooser.getSelectedFormat(); + FileFormatI format = chooser.getSelectedFormat(); - /* - * Call IdentifyFile to verify the file contains what its extension implies. - * Skip this step for dynamically added file formats, because IdentifyFile does - * not know how to recognise them. - */ - if (FileFormats.getInstance().isIdentifiable(format)) + /* + * Call IdentifyFile to verify the file contains what its extension implies. + * Skip this step for dynamically added file formats, because IdentifyFile does + * not know how to recognise them. + */ + if (FileFormats.getInstance().isIdentifiable(format)) + { + try { - try - { - format = new IdentifyFile().identify(selectedFile, - DataSourceType.FILE); - } catch (FileFormatException e) - { - // format = null; //?? - } + format = new IdentifyFile().identify(selectedFile, + DataSourceType.FILE); + } catch (FileFormatException e) + { + // format = null; //?? } - - new FileLoader().LoadFile(viewport, selectedFile, - DataSourceType.FILE, format); } + + new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, + format); + return null; }); chooser.showOpenDialog(this); } @@ -1263,64 +1258,60 @@ public class Desktop extends jalview.jbgui.GDesktop Object[] options = new Object[] { MessageManager.getString("action.ok"), MessageManager.getString("action.cancel") }; - Runnable action = new Runnable() - { - @Override - public void run() - { - @SuppressWarnings("unchecked") - String url = (history instanceof JTextField - ? ((JTextField) history).getText() - : ((JComboBox) 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,61 +1343,70 @@ public class Desktop extends jalview.jbgui.GDesktop /* * Check with user and saving files before actually quitting */ - public QResponse desktopQuit() + public void desktopQuit() { - return desktopQuit(true); + desktopQuit(true, false); } - public QResponse desktopQuit(boolean ui) + public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag) { - QuitHandler.QResponse qresponse = QuitHandler.getQuitResponse(ui); + final Callable doDesktopQuit = () -> { + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); + Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); + storeLastKnownDimensions("", new Rectangle(getBounds().x, + getBounds().y, getWidth(), getHeight())); - if (qresponse == QResponse.CANCEL_QUIT) - { - return qresponse; - } + if (jconsole != null) + { + storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); + jconsole.stopConsole(); + } - Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); - Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); - storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, - getWidth(), getHeight())); + if (jvnews != null) + { + storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); - if (jconsole != null) - { - storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); - jconsole.stopConsole(); - } - if (jvnews != null) - { - storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); + } - } - if (dialogExecutor != null) - { - dialogExecutor.shutdownNow(); - } - closeAll_actionPerformed(null); + if (dialogExecutor != null) + { + dialogExecutor.shutdownNow(); + } - if (groovyConsole != null) - { - // suppress a possible repeat prompt to save script - groovyConsole.setDirty(false); - groovyConsole.exit(); - } + closeAll_actionPerformed(null); - if (qresponse == QResponse.FORCE_QUIT) - { - // note that shutdown hook will not be run - jalview.bin.Console.debug("Force Quit selected by user"); - Runtime.getRuntime().halt(0); - } + if (groovyConsole != null) + { + // suppress a possible repeat prompt to save script + groovyConsole.setDirty(false); + groovyConsole.exit(); + } + + if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT) + { + // note that shutdown hook will not be run + jalview.bin.Console.debug("Force Quit selected by user"); + Runtime.getRuntime().halt(0); + } + + jalview.bin.Console.debug("Quit selected by user"); + if (disposeFlag) + { + instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + // instance.dispose(); + } + jalview.bin.Console.debug("**** BEFORE quit"); + jalview.bin.Console.debug("**** QuitHandler.gotQuitResponse=" + + QuitHandler.gotQuitResponse()); + instance.quit(); + jalview.bin.Console.debug("**** AFTER quit"); - jalview.bin.Console.debug("Quit selected by user"); - quit(); + return QuitHandler.gotQuitResponse(); + }; - // unlikely to reach here! - return QResponse.QUIT; + return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit, + QuitHandler.defaultCancelQuit); } /** @@ -1855,42 +1855,37 @@ public class Desktop extends jalview.jbgui.GDesktop // allowBackupFiles chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle(MessageManager.getString("label.restore_state")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); + setProjectFile(selectedFile); + String choice = selectedFile.getAbsolutePath(); + Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); + new Thread(new Runnable() { - File selectedFile = chooser.getSelectedFile(); - setProjectFile(selectedFile); - String choice = selectedFile.getAbsolutePath(); - Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); - new Thread(new Runnable() + @Override + public void run() { - @Override - public void run() + try { - try - { - new Jalview2XML().loadJalviewAlign(selectedFile); - } catch (OutOfMemoryError oom) - { - new OOMWarning("Whilst loading project from " + choice, oom); - } catch (Exception ex) - { - jalview.bin.Console.error( - "Problems whilst loading project from " + choice, ex); - JvOptionPane.showMessageDialog(Desktop.desktop, - MessageManager.formatMessage( - "label.error_whilst_loading_project_from", - new Object[] - { choice }), - MessageManager - .getString("label.couldnt_load_project"), - JvOptionPane.WARNING_MESSAGE); - } + new Jalview2XML().loadJalviewAlign(selectedFile); + } catch (OutOfMemoryError oom) + { + new OOMWarning("Whilst loading project from " + choice, oom); + } catch (Exception ex) + { + jalview.bin.Console.error( + "Problems whilst loading project from " + choice, ex); + JvOptionPane.showMessageDialog(Desktop.desktop, + MessageManager.formatMessage( + "label.error_whilst_loading_project_from", + new Object[] + { choice }), + MessageManager.getString("label.couldnt_load_project"), + JvOptionPane.WARNING_MESSAGE); } - }, "Project Loader").start(); - } + } + }, "Project Loader").start(); + return null; }); chooser.showOpenDialog(this); @@ -2554,12 +2549,7 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void actionPerformed(ActionEvent e) { - QResponse qresponse = desktopQuit(); - if (qresponse == QResponse.CANCEL_QUIT) - { - jalview.bin.Console - .debug("Desktop: Quit action cancelled by user"); - } + desktopQuit(); } }); } 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..0d6d371 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; @@ -949,14 +947,10 @@ public class FeatureSettings extends JPanel chooser.setDialogTitle( MessageManager.getString("label.load_feature_colours")); chooser.setToolTipText(MessageManager.getString("action.load")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File file = chooser.getSelectedFile(); - load(file); - } + chooser.setResponseHandler(0, () -> { + File file = chooser.getSelectedFile(); + load(file); + return null; }); chooser.showOpenDialog(this); } 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..c5427cb 100644 --- a/src/jalview/gui/JvOptionPane.java +++ b/src/jalview/gui/JvOptionPane.java @@ -34,11 +34,13 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.Executors; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JDialog; +import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -52,13 +54,13 @@ public class JvOptionPane extends JOptionPane { private static final long serialVersionUID = -3019167117756785229L; - private static Object mockResponse = JvOptionPane.CANCEL_OPTION; + private static Object mockResponse = JOptionPane.CANCEL_OPTION; private static boolean interactiveMode = true; private Component parentComponent; - private Map callbacks = new HashMap<>(); + private Map callbacks = new HashMap<>(); /* * JalviewJS reports user choice in the dialog as the selected @@ -100,17 +102,17 @@ public class JvOptionPane extends JOptionPane } switch (optionType) { - case JvOptionPane.YES_NO_CANCEL_OPTION: + case JOptionPane.YES_NO_CANCEL_OPTION: // FeatureRenderer amendFeatures ?? TODO ?? // Chimera close // PromptUserConfig // $FALL-THROUGH$ default: - case JvOptionPane.YES_NO_OPTION: + case JOptionPane.YES_NO_OPTION: // PromptUserConfig usage stats // for now treated as "OK CANCEL" // $FALL-THROUGH$ - case JvOptionPane.OK_CANCEL_OPTION: + case JOptionPane.OK_CANCEL_OPTION: // will fall back to simple HTML return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType); @@ -198,13 +200,13 @@ public class JvOptionPane extends JOptionPane } switch (optionType) { - case JvOptionPane.YES_NO_CANCEL_OPTION: + case JOptionPane.YES_NO_CANCEL_OPTION: // ColourMenuHelper.addMenuItmers.offerRemoval TODO - case JvOptionPane.YES_NO_OPTION: + case JOptionPane.YES_NO_OPTION: // UserDefinedColoursSave -- relevant? TODO // $FALL-THROUGH$ default: - case JvOptionPane.OK_CANCEL_OPTION: + case JOptionPane.OK_CANCEL_OPTION: // EditNameDialog --- uses panel for messsage TODO @@ -233,13 +235,13 @@ public class JvOptionPane extends JOptionPane } switch (optionType) { - case JvOptionPane.YES_NO_CANCEL_OPTION: - case JvOptionPane.YES_NO_OPTION: + case JOptionPane.YES_NO_CANCEL_OPTION: + case JOptionPane.YES_NO_OPTION: // UserQuestionanaireCheck // VamsasApplication // $FALL-THROUGH$ default: - case JvOptionPane.OK_CANCEL_OPTION: + case JOptionPane.OK_CANCEL_OPTION: // will fall back to simple HTML return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType); @@ -267,11 +269,11 @@ public class JvOptionPane extends JOptionPane } switch (optionType) { - case JvOptionPane.YES_NO_CANCEL_OPTION: - case JvOptionPane.YES_NO_OPTION: + case JOptionPane.YES_NO_CANCEL_OPTION: + case JOptionPane.YES_NO_OPTION: //$FALL-THROUGH$ default: - case JvOptionPane.OK_CANCEL_OPTION: + case JOptionPane.OK_CANCEL_OPTION: // Preferences editLink/newLink return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon); @@ -708,7 +710,7 @@ public class JvOptionPane extends JOptionPane public static void resetMock() { - setMockResponse(JvOptionPane.CANCEL_OPTION); + setMockResponse(JOptionPane.CANCEL_OPTION); setInteractiveMode(true); } @@ -731,10 +733,10 @@ public class JvOptionPane extends JOptionPane { switch (messageType) { - case JvOptionPane.WARNING_MESSAGE: + case JOptionPane.WARNING_MESSAGE: prefix = "WARNING! "; break; - case JvOptionPane.ERROR_MESSAGE: + case JOptionPane.ERROR_MESSAGE: prefix = "ERROR! "; break; default: @@ -758,6 +760,11 @@ public class JvOptionPane extends JOptionPane * @param string2 * @return */ + public static JvOptionPane newOptionDialog() + { + return new JvOptionPane(null); + } + public static JvOptionPane newOptionDialog(Component parentComponent) { return new JvOptionPane(parentComponent); @@ -822,100 +829,12 @@ public class JvOptionPane extends JOptionPane * attached to the button press of the dialog. This means we can use * a non-modal JDialog for the confirmation without blocking the GUI. */ - JOptionPane joptionpane = new JOptionPane(); - // Make button options - int[] buttonActions = { JvOptionPane.YES_OPTION, - JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION }; - - // we need the strings to make the buttons with actionEventListener - if (options == null) - { - ArrayList options_default = new ArrayList<>(); - options_default - .add(UIManager.getString("OptionPane.yesButtonText")); - if (optionType == JvOptionPane.YES_NO_OPTION - || optionType == JvOptionPane.YES_NO_CANCEL_OPTION) - { - options_default - .add(UIManager.getString("OptionPane.noButtonText")); - } - if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION) - { - options_default - .add(UIManager.getString("OptionPane.cancelButtonText")); - } - options = options_default.toArray(); - } - ArrayList options_btns = new ArrayList<>(); - Object initialValue_btn = null; - if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here - { - for (int i = 0; i < options.length && i < 3; i++) - { - Object o = options[i]; - int buttonAction = buttonActions[i]; - Runnable action = callbacks.get(buttonAction); - JButton jb = new JButton(); - jb.setText((String) o); - jb.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - joptionpane.setValue(buttonAction); - if (action != null) - Executors.defaultThreadFactory().newThread(action).start(); - // joptionpane.transferFocusBackward(); - joptionpane.transferFocusBackward(); - joptionpane.setVisible(false); - // put focus and raise parent window if possible, unless cancel - // button pressed - boolean raiseParent = (parentComponent != null); - if (buttonAction == JvOptionPane.CANCEL_OPTION) - raiseParent = false; - if (optionType == JvOptionPane.YES_NO_OPTION - && buttonAction == JvOptionPane.NO_OPTION) - raiseParent = false; - if (raiseParent) - { - parentComponent.requestFocus(); - if (parentComponent instanceof JInternalFrame) - { - JInternalFrame jif = (JInternalFrame) parentComponent; - jif.show(); - jif.moveToFront(); - jif.grabFocus(); - } - else if (parentComponent instanceof Window) - { - Window w = (Window) parentComponent; - w.toFront(); - w.requestFocus(); - } - } - joptionpane.setVisible(false); - } - }); - options_btns.add(jb); - if (o.equals(initialValue)) - initialValue_btn = jb; - } - } - joptionpane.setMessage(message); - joptionpane.setMessageType(messageType); - joptionpane.setOptionType(optionType); - joptionpane.setIcon(icon); - joptionpane.setOptions( - Platform.isJS() ? options : options_btns.toArray()); - joptionpane.setInitialValue( - Platform.isJS() ? initialValue : initialValue_btn); - - JDialog dialog = joptionpane.createDialog(parentComponent, title); - dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL - : ModalityType.MODELESS); - dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + JDialog dialog = this.createDialog(parentComponent, message, title, + optionType, messageType, icon, options, initialValue, modal); + jalview.bin.Console.debug("About to setVisible(true)"); dialog.setVisible(true); + jalview.bin.Console.debug("Just setVisible(true)"); } } @@ -953,14 +872,84 @@ public class JvOptionPane extends JOptionPane } } + /* @Override public JvOptionPane setResponseHandler(Object response, Runnable action) { + callbacks.put(response, new Callable() + { + @Override + public Void call() + { + action.run(); + return null; + } + }); + return this; + } + */ + @Override + public JvOptionPane setResponseHandler(Object response, Callable action) + { callbacks.put(response, action); return this; } /** + * showDialogOnTop will create a dialog that (attempts to) come to top of OS + * desktop windows + */ + public static int showDialogOnTop(String label, String actionString, + int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) + { + // Ensure Jalview window is brought to front (primarily for Quit + // confirmation window to be visible) + + // This method of raising the Jalview window is broken in java + // jalviewDesktop.setVisible(true); + // jalviewDesktop.toFront(); + + // A better hack which works is to create a new JFrame parent with + // setAlwaysOnTop(true) + JFrame dialogParent = new JFrame(); + dialogParent.setAlwaysOnTop(true); + + int answer = JOptionPane.showConfirmDialog(dialogParent, label, + actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE); + + dialogParent.setAlwaysOnTop(false); + jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()"); + dialogParent.dispose(); + jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()"); + + return answer; + } + + public void showDialogOnTopAsync(String label, String actionString, + int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon, + Object[] options, Object initialValue, boolean modal) + { + // Ensure Jalview window is brought to front (primarily for Quit + // confirmation window to be visible) + + // This method of raising the Jalview window is broken in java + // jalviewDesktop.setVisible(true); + // jalviewDesktop.toFront(); + + // A better hack which works is to create a new JFrame parent with + // setAlwaysOnTop(true) + JFrame dialogParent = new JFrame(); + dialogParent.setAlwaysOnTop(true); + parentComponent = dialogParent; + + showDialog(label, actionString, JOPTIONPANE_OPTION, + JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal); + + dialogParent.setAlwaysOnTop(false); + dialogParent.dispose(); + } + + /** * JalviewJS signals option selection by a property change event for the * option e.g. "OK". This methods responds to that by running the response * action that corresponds to that option. @@ -993,11 +982,139 @@ public class JvOptionPane extends JOptionPane { return; } - Runnable action = callbacks.get(response); + Callable action = callbacks.get(response); if (action != null) { - action.run(); - parentComponent.requestFocus(); + try + { + action.call(); + } catch (Exception e) + { + e.printStackTrace(); + } + if (parentComponent != null) + parentComponent.requestFocus(); + } + } + + /** + * Create a non-modal confirm dialog + */ + public JDialog createDialog(Component parentComponent, String message, + String title, int optionType, int messageType, Icon icon, + Object[] options, Object initialValue, boolean modal) + { + JOptionPane joptionpane = new JOptionPane(); + // Make button options + int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, + JOptionPane.CANCEL_OPTION }; + + // we need the strings to make the buttons with actionEventListener + if (options == null) + { + ArrayList options_default = new ArrayList<>(); + options_default.add(UIManager.getString("OptionPane.yesButtonText")); + if (optionType == JOptionPane.YES_NO_OPTION + || optionType == JOptionPane.YES_NO_CANCEL_OPTION) + { + options_default.add(UIManager.getString("OptionPane.noButtonText")); + } + if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) + { + options_default + .add(UIManager.getString("OptionPane.cancelButtonText")); + } + options = options_default.toArray(); } + ArrayList options_btns = new ArrayList<>(); + Object initialValue_btn = null; + if (!Platform.isJS()) // JalviewJS already uses callback, don't need to + // add them here + { + if (((optionType == JOptionPane.YES_NO_OPTION + || optionType == JOptionPane.OK_CANCEL_OPTION) + && options.length < 2) + || (optionType == JOptionPane.YES_NO_CANCEL_OPTION + && options.length < 3)) + { + jalview.bin.Console + .debug("JvOptionPane: not enough options for dialog type"); + } + for (int i = 0; i < options.length && i < 3; i++) + { + Object o = options[i]; + int buttonAction = buttonActions[i]; + Callable action = callbacks.get(buttonAction); + JButton jb = new JButton(); + jb.setText((String) o); + jb.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + joptionpane.setValue(buttonAction); + if (action != null) + Executors.newSingleThreadExecutor().submit(action); + // joptionpane.transferFocusBackward(); + joptionpane.transferFocusBackward(); + joptionpane.setVisible(false); + // put focus and raise parent window if possible, unless cancel + // button pressed + boolean raiseParent = (parentComponent != null); + if (buttonAction == JOptionPane.CANCEL_OPTION) + raiseParent = false; + if (optionType == JOptionPane.YES_NO_OPTION + && buttonAction == JOptionPane.NO_OPTION) + raiseParent = false; + if (raiseParent) + { + parentComponent.requestFocus(); + if (parentComponent instanceof JInternalFrame) + { + JInternalFrame jif = (JInternalFrame) parentComponent; + jif.show(); + jif.moveToFront(); + jif.grabFocus(); + } + else if (parentComponent instanceof Window) + { + Window w = (Window) parentComponent; + w.toFront(); + w.requestFocus(); + } + } + joptionpane.setVisible(false); + } + }); + options_btns.add(jb); + if (o.equals(initialValue)) + initialValue_btn = jb; + } + } + joptionpane.setMessage(message); + joptionpane.setMessageType(messageType); + joptionpane.setOptionType(optionType); + joptionpane.setIcon(icon); + joptionpane + .setOptions(Platform.isJS() ? options : options_btns.toArray()); + joptionpane.setInitialValue( + Platform.isJS() ? initialValue : initialValue_btn); + + JDialog dialog = joptionpane.createDialog(parentComponent, title); + dialog.setModalityType( + modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + return dialog; + } + + /** + * Utility to programmatically click a button on a JOptionPane (as a JFrame) + * + * returns true if button was found + */ + public static boolean clickButton(JFrame frame, int buttonType) + { + + return false; } } diff --git a/src/jalview/gui/LineartOptions.java b/src/jalview/gui/LineartOptions.java index d55733c..8a530ac 100644 --- a/src/jalview/gui/LineartOptions.java +++ b/src/jalview/gui/LineartOptions.java @@ -20,12 +20,10 @@ */ package jalview.gui; -import jalview.bin.Cache; -import jalview.util.MessageManager; - import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.BorderFactory; @@ -35,6 +33,9 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; +import jalview.bin.Cache; +import jalview.util.MessageManager; + /** * A dialog where the user may choose Text or Lineart rendering, and optionally * save this as a preference ("Don't ask me again") @@ -95,7 +96,7 @@ public class LineartOptions extends JPanel * * @param action */ - public void setResponseAction(Object response, Runnable action) + public void setResponseAction(Object response, Callable action) { dialog.setResponseHandler(response, action); } diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 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/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/TextColourChooser.java b/src/jalview/gui/TextColourChooser.java index f9ff337..e72a084 100644 --- a/src/jalview/gui/TextColourChooser.java +++ b/src/jalview/gui/TextColourChooser.java @@ -27,6 +27,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; import javax.swing.BorderFactory; import javax.swing.JLabel; @@ -151,13 +152,10 @@ public class TextColourChooser MessageManager.getString("action.cancel") }; String title = MessageManager .getString("label.adjust_foreground_text_colour_threshold"); - Runnable action = new Runnable() // response for 1 = Cancel + Callable action = () -> // response for 1 = Cancel { - @Override - public void run() - { - restoreInitialSettings(); - } + restoreInitialSettings(); + return null; }; JvOptionPane.newOptionDialog(alignPanel.alignFrame) .setResponseHandler(1, action).showInternalDialog(bigpanel, diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index 1836e33..4db8bfa 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. @@ -652,45 +651,41 @@ public class UserDefinedColours extends GUserDefinedColours chooser.setDialogTitle( MessageManager.getString("label.load_colour_scheme")); chooser.setToolTipText(MessageManager.getString("action.load")); - chooser.setResponseHandler(0, new Runnable() - { - @Override - public void run() - { - File choice = chooser.getSelectedFile(); - Cache.setProperty(LAST_DIRECTORY, choice.getParent()); - - UserColourScheme ucs = ColourSchemeLoader - .loadColourScheme(choice.getAbsolutePath()); - Color[] colors = ucs.getColours(); - schemeName.setText(ucs.getSchemeName()); + chooser.setResponseHandler(0, () -> { + File choice = chooser.getSelectedFile(); + Cache.setProperty(LAST_DIRECTORY, choice.getParent()); - if (ucs.getLowerCaseColours() != null) - { - caseSensitive.setSelected(true); - lcaseColour.setEnabled(true); - resetButtonPanel(true); - for (int i = 0; i < lowerCaseButtons.size(); i++) - { - JButton button = lowerCaseButtons.get(i); - button.setBackground(ucs.getLowerCaseColours()[i]); - } - } - else - { - caseSensitive.setSelected(false); - lcaseColour.setEnabled(false); - resetButtonPanel(false); - } + UserColourScheme ucs = ColourSchemeLoader + .loadColourScheme(choice.getAbsolutePath()); + Color[] colors = ucs.getColours(); + schemeName.setText(ucs.getSchemeName()); - for (int i = 0; i < upperCaseButtons.size(); i++) + if (ucs.getLowerCaseColours() != null) + { + caseSensitive.setSelected(true); + lcaseColour.setEnabled(true); + resetButtonPanel(true); + for (int i = 0; i < lowerCaseButtons.size(); i++) { - JButton button = upperCaseButtons.get(i); - button.setBackground(colors[i]); + JButton button = lowerCaseButtons.get(i); + button.setBackground(ucs.getLowerCaseColours()[i]); } + } + else + { + caseSensitive.setSelected(false); + lcaseColour.setEnabled(false); + resetButtonPanel(false); + } - addNewColourScheme(choice.getPath()); + for (int i = 0; i < upperCaseButtons.size(); i++) + { + JButton button = upperCaseButtons.get(i); + button.setBackground(colors[i]); } + + addNewColourScheme(choice.getPath()); + return null; }); chooser.showOpenDialog(this); diff --git a/src/jalview/io/BackupFiles.java b/src/jalview/io/BackupFiles.java index 529e1b2..88524d1 100644 --- a/src/jalview/io/BackupFiles.java +++ b/src/jalview/io/BackupFiles.java @@ -32,6 +32,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import jalview.bin.Cache; import jalview.bin.Console; @@ -106,8 +111,14 @@ public class BackupFiles private static final String oldTempFileSuffix = "_oldfile_tobedeleted"; + // thread pool used for completablefutures + private static final ExecutorService executorService = Executors + .newFixedThreadPool(3); + private static List savesInProgress = new ArrayList<>(); + private CompletableFuture myFuture = null; + private boolean addSaveInProgress() { if (savesInProgress.contains(this)) @@ -116,25 +127,41 @@ public class BackupFiles } else { + this.setMyFuture(); savesInProgress.add(this); return true; } } - private boolean removeSaveInProgress() + private boolean removeSaveInProgress(boolean ret) { if (savesInProgress.contains(this)) { + this.getMyFuture().complete(ret); // remove all occurrences while (savesInProgress.remove(this)) { } return true; } - else + return false; + } + + private static CompletableFuture getNewFuture() + { + return new CompletableFuture() { - return false; - } + }; + } + + private CompletableFuture getMyFuture() + { + return this.myFuture; + } + + private void setMyFuture() + { + this.myFuture = getNewFuture(); } public static boolean hasSavesInProgress() @@ -152,6 +179,41 @@ public class BackupFiles return files; } + public static List> savesInProgressCompletableFutures() + { + List> cfs = new ArrayList<>(); + for (BackupFiles bfile : savesInProgress) + { + 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)); @@ -871,7 +933,7 @@ public class BackupFiles } // remove this file from the save in progress stack - removeSaveInProgress(); + removeSaveInProgress(rename); return rename; } 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..dba7ad1 100755 --- a/src/jalview/io/JalviewFileChooser.java +++ b/src/jalview/io/JalviewFileChooser.java @@ -21,12 +21,6 @@ ////////////////////////////////////////////////////////////////// package jalview.io; -import jalview.bin.Cache; -import jalview.gui.JvOptionPane; -import jalview.util.MessageManager; -import jalview.util.Platform; -import jalview.util.dialogrunner.DialogRunnerI; - import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; @@ -44,18 +38,26 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; +import java.util.concurrent.Callable; import javax.swing.BoxLayout; import javax.swing.DefaultListCellRenderer; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SpringLayout; import javax.swing.filechooser.FileFilter; import javax.swing.plaf.basic.BasicFileChooserUI; +import jalview.bin.Cache; +import jalview.gui.JvOptionPane; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.util.dialogrunner.DialogRunnerI; + /** * Enhanced file chooser dialog box. * @@ -70,7 +72,7 @@ public class JalviewFileChooser extends JFileChooser { private static final long serialVersionUID = 1L; - private Map callbacks = new HashMap<>(); + private Map callbacks = new HashMap<>(); File selectedFile = null; @@ -488,10 +490,13 @@ public class JalviewFileChooser extends JFileChooser if (selectedFile.exists()) { - int confirm = JvOptionPane.showConfirmDialog(this, - MessageManager.getString("label.overwrite_existing_file"), - MessageManager.getString("label.file_already_exists"), - JvOptionPane.YES_NO_OPTION); + int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true) + ? JvOptionPane.showConfirmDialog(this, + MessageManager + .getString("label.overwrite_existing_file"), + MessageManager.getString("label.file_already_exists"), + JvOptionPane.YES_NO_OPTION) + : JOptionPane.YES_OPTION; if (confirm != JvOptionPane.YES_OPTION) { @@ -596,8 +601,26 @@ public class JalviewFileChooser extends JFileChooser } + /* @Override - public DialogRunnerI setResponseHandler(Object response, Runnable action) + public JalviewFileChooser setResponseHandler(Object response, + Runnable action) + { + callbacks.put(response, new Callable() + { + @Override + public Void call() + { + action.run(); + return null; + } + }); + return this; + } + */ + + @Override + public DialogRunnerI setResponseHandler(Object response, Callable action) { callbacks.put(response, action); return this; @@ -613,10 +636,16 @@ public class JalviewFileChooser extends JFileChooser { return; } - Runnable action = callbacks.get(response); + Callable action = callbacks.get(response); if (action != null) { - action.run(); + try + { + action.call(); + } catch (Exception e) + { + e.printStackTrace(); + } } } diff --git a/src/jalview/jbgui/GDesktop.java b/src/jalview/jbgui/GDesktop.java index 7b4f477..3343c88 100755 --- a/src/jalview/jbgui/GDesktop.java +++ b/src/jalview/jbgui/GDesktop.java @@ -34,7 +34,6 @@ import jalview.api.AlignmentViewPanel; import jalview.bin.Cache; import jalview.gui.Desktop; import jalview.io.FileFormatException; -import jalview.jbgui.QuitHandler.QResponse; import jalview.util.MessageManager; import jalview.util.Platform; @@ -215,14 +214,8 @@ public class GDesktop extends JFrame @Override public void actionPerformed(ActionEvent e) { - QResponse qresponse = Desktop.instance != null - ? Desktop.instance.desktopQuit() - : QResponse.QUIT; - if (qresponse == QResponse.CANCEL_QUIT) - { - jalview.bin.Console - .debug("GDesktop: Quit action cancelled by user"); - } + if (Desktop.instance != null) + Desktop.instance.desktopQuit(); } }); aboutMenuItem.setText(MessageManager.getString("label.about")); diff --git a/src/jalview/jbgui/QuitHandler.java b/src/jalview/jbgui/QuitHandler.java index 6a95db7..6369b1a 100644 --- a/src/jalview/jbgui/QuitHandler.java +++ b/src/jalview/jbgui/QuitHandler.java @@ -1,8 +1,12 @@ package jalview.jbgui; import java.io.File; -import java.util.Date; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import javax.swing.JFrame; import javax.swing.JOptionPane; @@ -10,11 +14,13 @@ import javax.swing.JOptionPane; import com.formdev.flatlaf.extras.FlatDesktop; import jalview.api.AlignmentViewPanel; +import jalview.bin.Cache; import jalview.bin.Console; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.Desktop; +import jalview.gui.JvOptionPane; import jalview.io.BackupFiles; import jalview.project.Jalview2XML; import jalview.util.MessageManager; @@ -22,37 +28,48 @@ import jalview.util.Platform; public class QuitHandler { + private static final int INITIAL_WAIT_FOR_SAVE = 3000; + + private static final int NON_INTERACTIVE_WAIT_CYCLES = 2; + public static enum QResponse { - QUIT, CANCEL_QUIT, FORCE_QUIT + NULL, QUIT, CANCEL_QUIT, FORCE_QUIT }; - public static void setQuitHandler() + private static ExecutorService executor = Executors.newFixedThreadPool(3); + + public static QResponse setQuitHandler() { FlatDesktop.setQuitHandler(response -> { - QResponse qresponse = getQuitResponse(); - switch (qresponse) - { - case QUIT: + Callable performQuit = () -> { response.performQuit(); - break; - case CANCEL_QUIT: - response.cancelQuit(); - break; - case FORCE_QUIT: + return setResponse(QResponse.QUIT); + }; + Callable performForceQuit = () -> { response.performQuit(); - break; - default: + return setResponse(QResponse.FORCE_QUIT); + }; + Callable cancelQuit = () -> { response.cancelQuit(); - } + // reset + setResponse(QResponse.NULL); + // but return cancel + return QResponse.CANCEL_QUIT; + }; + QResponse qresponse = getQuitResponse(true, performQuit, + performForceQuit, cancelQuit); }); + + return gotQuitResponse(); } - private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT; + private static QResponse gotQuitResponse = QResponse.NULL; - private static QResponse returnResponse(QResponse qresponse) + private static QResponse setResponse(QResponse qresponse) { gotQuitResponse = qresponse; + Console.debug("##### Setting gotQuitResponse to " + qresponse); return qresponse; } @@ -61,19 +78,47 @@ public class QuitHandler return gotQuitResponse; } - public static QResponse getQuitResponse() + public static final Callable defaultCancelQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CANCELLED by user"); + // reset + setResponse(QResponse.NULL); + // and return cancel + return QResponse.CANCEL_QUIT; + }; + + public static final Callable defaultOkQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CONFIRMED by user"); + return setResponse(QResponse.QUIT); + }; + + 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); + return setResponse(QResponse.FORCE_QUIT); // this line never reached! + }; + + public static QResponse getQuitResponse(boolean ui) { - return getQuitResponse(true); + return getQuitResponse(ui, defaultOkQuit, defaultForceQuit, + defaultCancelQuit); } - public static QResponse getQuitResponse(boolean ui) + private static boolean interactive = true; + + public static QResponse getQuitResponse(boolean ui, + Callable okQuit, Callable forceQuit, + Callable cancelQuit) { - if (gotQuitResponse() != QResponse.CANCEL_QUIT) + QResponse got = gotQuitResponse(); + if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT) { - return returnResponse(getQuitResponse()); + // quit has already been selected, continue with calling quit method + Console.debug("##### getQuitResponse called. gotQuitResponse=" + got); + return got; } - boolean interactive = ui && !Platform.isHeadless(); + interactive = ui && !Platform.isHeadless(); // confirm quit if needed and wanted boolean confirmQuit = true; @@ -96,45 +141,112 @@ public class QuitHandler + "' is/defaults to " + confirmQuit + " -- " + (confirmQuit ? "" : "not ") + "confirming quit"); } + got = confirmQuit ? QResponse.NULL : QResponse.QUIT; + Console.debug("initial calculation, got=" + got); + setResponse(got); - int answer = JOptionPane.OK_OPTION; - - // if going to confirm, do it before the save in progress check to give - // the save time to finish! if (confirmQuit) { - answer = frameOnTop( + + Console.debug("********************ABOUT TO CONFIRM QUIT"); + JvOptionPane quitDialog = JvOptionPane.newOptionDialog() + .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) + .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit); + quitDialog.showDialogOnTopAsync( new StringBuilder( MessageManager.getString("label.quit_jalview")) - .append(" ") + .append("\n") .append(MessageManager .getString("label.unsaved_changes")) .toString(), MessageManager.getString("action.quit"), - JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, + new Object[] + { MessageManager.getString("action.quit"), + MessageManager.getString("action.cancel") }, + MessageManager.getString("action.quit"), true); } - if (answer == JOptionPane.CANCEL_OPTION) + got = gotQuitResponse(); + Console.debug("first user response, got=" + got); + boolean wait = false; + if (got == QResponse.CANCEL_QUIT) { - Console.debug("QuitHandler: Quit action cancelled by user"); - return returnResponse(QResponse.CANCEL_QUIT); + // reset + setResponse(QResponse.NULL); + // but return cancel + return QResponse.CANCEL_QUIT; + } + else if (got == QResponse.QUIT) + { + if (Cache.getDefault("WAIT_FOR_SAVE", true) + && BackupFiles.hasSavesInProgress()) + { + /* + Future waitGot = executor.submit(waitQuitCall); + try + { + got = waitGot.get(); + } catch (InterruptedException | ExecutionException e) + { + jalview.bin.Console.debug( + "Exception during quit handling (wait for save)", e); + } + */ + QResponse waitResponse = waitQuit(interactive, okQuit, forceQuit, + cancelQuit); + wait = waitResponse == QResponse.QUIT; + } } - // check for saves in progress - int waitForSave = 1000; // MAKE THIS BETTER - AlignFrame[] afArray = Desktop.getAlignFrames(); - if (afArray == null || afArray.length == 0) + Callable next = null; + switch (gotQuitResponse()) { - // no change + case QUIT: + Console.debug("### User selected QUIT"); + next = okQuit; + break; + case FORCE_QUIT: // not actually an option at this stage + Console.debug("### User selected FORCE QUIT"); + next = forceQuit; + break; + default: + Console.debug("### User selected CANCEL QUIT"); + next = cancelQuit; + break; } - else + try + { + got = executor.submit(next).get(); + } catch (InterruptedException | ExecutionException e) + { + jalview.bin.Console + .debug("Exception during quit handling (final choice)", e); + } + jalview.bin.Console.debug("### nextResponse=" + got.toString()); + setResponse(got); + + return gotQuitResponse(); + } + + private static QResponse waitQuit(boolean interactive, + Callable okQuit, Callable forceQuit, + Callable cancelQuit) + { + jalview.bin.Console.debug("#### waitQuit started"); + // check for saves in progress + if (!BackupFiles.hasSavesInProgress()) + return QResponse.QUIT; + + int waitTime = INITIAL_WAIT_FOR_SAVE; // start with 3 second wait + AlignFrame[] afArray = Desktop.getAlignFrames(); + if (!(afArray == null || afArray.length == 0)) { int size = 0; for (int i = 0; i < afArray.length; i++) { AlignFrame af = afArray[i]; - List avpList = (List) af - .getAlignPanels(); + List avpList = af.getAlignPanels(); for (AlignmentViewPanel avp : avpList) { AlignmentI a = avp.getAlignment(); @@ -145,101 +257,174 @@ public class QuitHandler } } } - waitForSave = size; - Console.debug("Set waitForSave to " + waitForSave); + waitTime = Math.max(waitTime, size / 2); + Console.debug("Set waitForSave to " + waitTime); } - int waitIncrement = 3000; - long startTime = new Date().getTime(); - boolean saving = BackupFiles.hasSavesInProgress(); - if (saving) + + // future that returns a Boolean when all files are saved + CompletableFuture filesAllSaved = new CompletableFuture<>(); + + // callback as each file finishes saving + for (CompletableFuture cf : BackupFiles + .savesInProgressCompletableFutures()) + { + // if this is the last one then complete filesAllSaved + cf.whenComplete((ret, e) -> { + if (!BackupFiles.hasSavesInProgress()) + { + filesAllSaved.complete(true); + } + }); + } + + final int waitTimeFinal = waitTime; + // timeout the wait -- will result in another wait button when looped + CompletableFuture waitTimeout = CompletableFuture + .supplyAsync(() -> { + executor.submit(() -> { + try + { + Thread.sleep(waitTimeFinal); + } catch (InterruptedException e) + { + // probably interrupted by all files saving + } + }); + return true; + }); + CompletableFuture waitForSave = CompletableFuture + .anyOf(waitTimeout, filesAllSaved); + Console.debug("##### WAITFORSAVE RUNNING"); + + QResponse waitResponse = QResponse.NULL; + + int iteration = 0; + boolean doIterations = true; + while (doIterations && BackupFiles.hasSavesInProgress() + && iteration++ < (interactive ? 100 : 5)) { - boolean waiting = (new Date().getTime() - startTime) < waitForSave; - while (saving && waiting) + try { - saving = !waitForSave(waitIncrement); - waiting = (new Date().getTime() - startTime) < waitForSave; + waitForSave.get(); + } catch (InterruptedException | ExecutionException e1) + { + Console.debug( + "Exception whilst waiting for files to save before quitting", + e1); } - - if (saving) // still saving after a wait + if (interactive) { - StringBuilder messageSB = new StringBuilder( - MessageManager.getString("label.save_in_progress")); - messageSB.append(":"); - boolean any = false; - for (File file : BackupFiles.savesInProgressFiles()) + Console.debug("********************About to make waitDialog"); + JvOptionPane waitDialog = JvOptionPane.newOptionDialog() + .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) + .setResponseHandler(JOptionPane.NO_OPTION, forceQuit) + .setResponseHandler(JOptionPane.CANCEL_OPTION, cancelQuit); + + // callback as each file finishes saving + for (CompletableFuture cf : BackupFiles + .savesInProgressCompletableFutures()) { - messageSB.append("\n- "); - messageSB.append(file.getName()); - any = true; + // update the list of saving files as they save too + cf.thenRun(() -> { + waitDialog.setMessage(waitingForSaveMessage()); + }); + // if this is the last one then close the dialog + cf.whenComplete((ret, e) -> { + if (!BackupFiles.hasSavesInProgress()) + { + // like a click on Wait button + Console.debug( + "***** TRYING TO MAKE THE WAIT FOR SAVE DIALOG DISAPPEAR!"); + waitDialog.setValue(JOptionPane.YES_OPTION); + } + }); } - if (!any) - { - messageSB.append("\n"); - messageSB.append(MessageManager.getString("label.unknown")); - } - int waitLonger = interactive ? JOptionPane.YES_OPTION - : JOptionPane.NO_OPTION; - while (saving && waitLonger == JOptionPane.YES_OPTION) + + waitDialog.showDialogOnTopAsync(waitingForSaveMessage(), + MessageManager.getString("action.wait"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, null, new Object[] + { MessageManager.getString("action.wait"), + MessageManager.getString("action.force_quit"), + MessageManager.getString("action.cancel_quit") }, + MessageManager.getString("action.wait"), true); + Console.debug("********************Finished waitDialog"); + + waitResponse = gotQuitResponse(); + Console.debug("####### WAITFORSAVE SET: " + waitResponse); + switch (waitResponse) { - waitLonger = waitForceQuitCancelQuitOptionDialog( - messageSB.toString(), - MessageManager.getString("action.wait")); - if (waitLonger == JOptionPane.YES_OPTION) // wait - { - // do wait stuff - saving = !waitForSave(waitIncrement); - } - else if (waitLonger == JOptionPane.NO_OPTION) // force quit - { - // do a force quit - return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this - } - else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit - { - return returnResponse(QResponse.CANCEL_QUIT); - } - else - { - Console.debug("**** Shouldn't have got here!"); - } + case QUIT: // wait -- do another iteration + break; + case FORCE_QUIT: + doIterations = false; + break; + case CANCEL_QUIT: + doIterations = false; + break; + case NULL: // already cancelled + doIterations = false; + break; + default: } - } - } + } // end if interactive - // not cancelled and not saving - return returnResponse(QResponse.QUIT); - } + } + waitResponse = gotQuitResponse(); - public static int frameOnTop(String label, String actionString, - int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) - { - return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION, - JOPTIONPANE_MESSAGETYPE); - } + Console.debug("####### WAITFORSAVE RETURNING: " + waitResponse); + return waitResponse; + }; - public static int frameOnTop(JFrame dialogParent, String label, - String actionString, int JOPTIONPANE_OPTION, - int JOPTIONPANE_MESSAGETYPE) + public static void okk() { - // ensure Jalview window is brought to front for Quit confirmation - // window to be visible - - // this method of raising the Jalview window is broken in java - // jalviewDesktop.setVisible(true); - // jalviewDesktop.toFront(); - - // a better hack which works instead - - dialogParent.setAlwaysOnTop(true); - - int answer = JOptionPane.showConfirmDialog(dialogParent, label, - actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE); - - dialogParent.setAlwaysOnTop(false); - dialogParent.dispose(); - - return answer; - } + /* + if (false) + { + if (false) + { + + waitLonger = JOptionPane.showOptionDialog(dialogParent, + waitingForSaveMessage(), + MessageManager.getString("action.wait"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, null, options, wait); + } + else + { + // non-interactive + waitLonger = iteration < NON_INTERACTIVE_WAIT_CYCLES + ? JOptionPane.YES_OPTION + : JOptionPane.NO_OPTION; + } + + if (waitLonger == JOptionPane.YES_OPTION) // "wait" + { + saving = !waitForSave(waitIncrement); + } + else if (waitLonger == JOptionPane.NO_OPTION) // "force + // quit" + { + // do a force quit + return setResponse(QResponse.FORCE_QUIT); + } + else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit + { + return setResponse(QResponse.CANCEL_QUIT); + } + else + { + // Most likely got here by user dismissing the dialog with the + // 'x' + // -- treat as a "Cancel" + return setResponse(QResponse.CANCEL_QUIT); + } + } + + // not sure how we got here, best be safe + return QResponse.CANCEL_QUIT; + */ + }; private static int waitForceQuitCancelQuitOptionDialog(Object message, String title) @@ -251,14 +436,38 @@ public class QuitHandler MessageManager.getString("action.force_quit"), MessageManager.getString("action.cancel_quit") }; + // BackupFiles.setWaitForSaveDialog(dialogParent); + int answer = JOptionPane.showOptionDialog(dialogParent, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, wait); + // BackupFiles.clearWaitForSaveDialog(); + return answer; } - private static boolean waitForSave(long t) + private static String waitingForSaveMessage() + { + StringBuilder messageSB = new StringBuilder( + MessageManager.getString("label.save_in_progress")); + boolean any = false; + for (File file : BackupFiles.savesInProgressFiles()) + { + messageSB.append("\n- "); + messageSB.append(file.getName()); + any = true; + } + if (!any) + { + messageSB.append("\n"); + messageSB.append(MessageManager.getString("label.unknown")); + } + + return messageSB.toString(); + } + + private static Boolean waitForSave(long t) { boolean ret = false; try diff --git a/src/jalview/util/dialogrunner/DialogRunnerI.java b/src/jalview/util/dialogrunner/DialogRunnerI.java index fde80f7..9be64c6 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