X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FDesktop.java;h=ee24c250a238c7b097c3a818d521b937b4cdd860;hb=fd86db8856109e69c21a5f4102bc8dd94664ae1f;hp=ccfb1d276b3893d4b3b292cc4a840da6d3efd00c;hpb=4ac785e695238e94653d77e57dbcad5fb1bf89be;p=jalview.git diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index ccfb1d2..ee24c25 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -22,6 +22,7 @@ package jalview.gui; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; @@ -51,6 +52,7 @@ import java.awt.event.WindowEvent; import java.awt.geom.AffineTransform; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -63,6 +65,7 @@ import java.util.Hashtable; import java.util.List; import java.util.ListIterator; import java.util.Locale; +import java.util.Map; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -81,15 +84,20 @@ 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; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.event.InternalFrameAdapter; @@ -99,9 +107,16 @@ import org.stackoverflowusers.file.WindowsShortcut; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; +import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.bin.Jalview; +import jalview.bin.Jalview.ExitCode; +import jalview.datamodel.Alignment; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; import jalview.gui.ImageExporter.ImageWriterI; +import jalview.gui.QuitHandler.QResponse; import jalview.io.BackupFiles; import jalview.io.DataSourceType; import jalview.io.FileFormat; @@ -113,6 +128,7 @@ import jalview.io.FormatAdapter; import jalview.io.IdentifyFile; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; +import jalview.io.exceptions.ImageOutputException; import jalview.jbgui.GSplitFrame; import jalview.jbgui.GStructureViewer; import jalview.project.Jalview2XML; @@ -183,10 +199,20 @@ public class Desktop extends jalview.jbgui.GDesktop private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES"; - protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT"; + public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT"; public static HashMap savingFiles = new HashMap(); + private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE; + + public static void setLiveDragMode(boolean b) + { + DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE + : JDesktopPane.OUTLINE_DRAG_MODE; + if (desktop != null) + desktop.setDragMode(DRAG_MODE); + } + private JalviewChangeSupport changeSupport = new JalviewChangeSupport(); public static boolean nosplash = false; @@ -416,9 +442,14 @@ public class Desktop extends jalview.jbgui.GDesktop { if (LaunchUtils.getJavaVersion() >= 11) { - jalview.bin.Console.info( + /* + * Send this message to stderr as the warning that follows (due to + * reflection) also goes to stderr. + */ + jalview.bin.Console.errPrintln( "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that."); } + final String awtAppClassName = "awtAppClassName"; try { Toolkit xToolkit = Toolkit.getDefaultToolkit(); @@ -426,10 +457,10 @@ public class Desktop extends jalview.jbgui.GDesktop Field awtAppClassNameField = null; if (Arrays.stream(declaredFields) - .anyMatch(f -> f.getName().equals("awtAppClassName"))) + .anyMatch(f -> f.getName().equals(awtAppClassName))) { awtAppClassNameField = xToolkit.getClass() - .getDeclaredField("awtAppClassName"); + .getDeclaredField(awtAppClassName); } String title = ChannelProperties.getProperty("app_name"); @@ -440,42 +471,26 @@ public class Desktop extends jalview.jbgui.GDesktop } else { - jalview.bin.Console.debug("XToolkit: awtAppClassName not found"); + jalview.bin.Console + .debug("XToolkit: " + awtAppClassName + " not found"); } } catch (Exception e) { - jalview.bin.Console.debug("Error setting awtAppClassName"); + jalview.bin.Console.debug("Error setting " + awtAppClassName); jalview.bin.Console.trace(Cache.getStackTraceString(e)); } } - /** - * APQHandlers sets handlers for About, Preferences and Quit actions - * peculiar to macOS's application menu. APQHandlers will check to see if a - * handler is supported before setting it. - */ - try - { - APQHandlers.setAPQHandlers(this); - } catch (Exception e) - { - System.out.println("Cannot set APQHandlers"); - // e.printStackTrace(); - } catch (Throwable t) - { - jalview.bin.Console - .warn("Error setting APQHandlers: " + t.toString()); - jalview.bin.Console.trace(Cache.getStackTraceString(t)); - } setIconImages(ChannelProperties.getIconList()); + // override quit handling when GUI OS close [X] button pressed + this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { - @Override public void windowClosing(WindowEvent ev) { - quit(); + QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag } }); @@ -500,16 +515,20 @@ public class Desktop extends jalview.jbgui.GDesktop } getContentPane().add(desktop, BorderLayout.CENTER); - desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); + desktop.setDragMode(DRAG_MODE); // This line prevents Windows Look&Feel resizing all new windows to maximum // if previous window was maximised desktop.setDesktopManager(new MyDesktopManager( - (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager() - : Platform.isAMacAndNotJS() - ? new AquaInternalFrameManager( - desktop.getDesktopManager()) - : desktop.getDesktopManager()))); + Platform.isJS() ? desktop.getDesktopManager() + : new DefaultDesktopManager())); + /* + (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager() + : Platform.isAMacAndNotJS() + ? new AquaInternalFrameManager( + desktop.getDesktopManager()) + : desktop.getDesktopManager()))); + */ Rectangle dims = getLastKnownDimensions(""); if (dims != null) @@ -524,6 +543,9 @@ public class Desktop extends jalview.jbgui.GDesktop setBounds(xPos, yPos, 900, 650); } + // start dialogue queue for single dialogues + startDialogQueue(); + if (!Platform.isJS()) /** * Java only @@ -557,15 +579,16 @@ public class Desktop extends jalview.jbgui.GDesktop } // Thread off a new instance of the file chooser - this reduces the time - // it - // takes to open it later on. + // it takes to open it later on. new Thread(new Runnable() { @Override public void run() { jalview.bin.Console.debug("Filechooser init thread started."); - String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT"); + String fileFormat = FileLoader.getUseDefaultFileFormat() + ? Cache.getProperty("DEFAULT_FILE_FORMAT") + : null; JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat); jalview.bin.Console.debug("Filechooser init thread finished."); @@ -589,15 +612,6 @@ public class Desktop extends jalview.jbgui.GDesktop this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this)); - this.addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent evt) - { - quit(); - } - }); - MouseAdapter ma; this.addMouseListener(ma = new MouseAdapter() { @@ -620,6 +634,13 @@ public class Desktop extends jalview.jbgui.GDesktop } }); desktop.addMouseListener(ma); + + if (Platform.isJS()) + { + // used for jalviewjsTest + jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP"); + } + } /** @@ -806,26 +827,64 @@ public class Desktop extends jalview.jbgui.GDesktop public void paste() { - try + // quick patch for JAL-4150 - needs some more work and test coverage + // TODO - unify below and AlignFrame.paste() + // TODO - write tests and fix AlignFrame.paste() which doesn't track if + // clipboard has come from a different alignment window than the one where + // paste has been called! JAL-4151 + + if (Desktop.jalviewClipboard != null) { - Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); - Transferable contents = c.getContents(this); + // The clipboard was filled from within Jalview, we must use the + // sequences + // And dataset from the copied alignment + SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0]; + // be doubly sure that we create *new* sequence objects. + SequenceI[] sequences = new SequenceI[newseq.length]; + for (int i = 0; i < newseq.length; i++) + { + sequences[i] = new Sequence(newseq[i]); + } + Alignment alignment = new Alignment(sequences); + // dataset is inherited + alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]); + AlignFrame af = new AlignFrame(alignment, AlignFrame.DEFAULT_WIDTH, + AlignFrame.DEFAULT_HEIGHT); + String newtitle = new String("Copied sequences"); + + if (Desktop.jalviewClipboard[2] != null) + { + HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2]; + af.viewport.setHiddenColumns(hc); + } - if (contents != null) + Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH, + AlignFrame.DEFAULT_HEIGHT); + + } + else + { + try { - String file = (String) contents - .getTransferData(DataFlavor.stringFlavor); + Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable contents = c.getContents(this); + + if (contents != null) + { + String file = (String) contents + .getTransferData(DataFlavor.stringFlavor); - FileFormatI format = new IdentifyFile().identify(file, - DataSourceType.PASTE); + FileFormatI format = new IdentifyFile().identify(file, + DataSourceType.PASTE); - new FileLoader().LoadFile(file, DataSourceType.PASTE, format); + new FileLoader().LoadFile(file, DataSourceType.PASTE, format); + } + } catch (Exception ex) + { + jalview.bin.Console.outPrintln( + "Unable to paste alignment from system clipboard:\n" + ex); } - } catch (Exception ex) - { - System.out.println( - "Unable to paste alignment from system clipboard:\n" + ex); } } @@ -1028,7 +1087,38 @@ public class Desktop extends jalview.jbgui.GDesktop setKeyBindings(frame); - desktop.add(frame); + // Since the latest FlatLaf patch, we occasionally have problems showing + // structureViewer frames... + int tries = 3; + boolean shown = false; + Exception last = null; + do + { + try + { + desktop.add(frame); + shown = true; + } catch (IllegalArgumentException iaex) + { + last = iaex; + tries--; + jalview.bin.Console.info("Squashed IllegalArgument Exception (" + + tries + " left) for " + frame.getTitle(), iaex); + try + { + Thread.sleep(5); + } catch (InterruptedException iex) + { + } + ; + } + } while (!shown && tries > 0); + if (!shown) + { + jalview.bin.Console.error( + "Serious Problem whilst showing window " + frame.getTitle(), + last); + } windowMenu.add(menuItem); @@ -1188,7 +1278,9 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) { - String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT"); + String fileFormat = FileLoader.getUseDefaultFileFormat() + ? Cache.getProperty("DEFAULT_FILE_FORMAT") + : null; JalviewFileChooser chooser = JalviewFileChooser.forRead( Cache.getProperty("LAST_DIRECTORY"), fileFormat, BackupFiles.getEnabled()); @@ -1198,36 +1290,31 @@ 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); }); chooser.showOpenDialog(this); } @@ -1283,62 +1370,56 @@ 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()); + Runnable 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 - } - - 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); + 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 + } - return; - } + 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; + } - 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); } } }; @@ -1370,40 +1451,86 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * Exit the program + * Check with user and saving files before actually quitting */ - @Override - public void quit() + public void desktopQuit() { - Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); - Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); - storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, - getWidth(), getHeight())); + desktopQuit(true, false); + } - if (jconsole != null) - { - storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); - jconsole.stopConsole(); - } - if (jvnews != null) - { - storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); + public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag) + { + final Runnable doDesktopQuit = () -> { + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + ""); + Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + ""); + storeLastKnownDimensions("", new Rectangle(getBounds().x, + getBounds().y, getWidth(), getHeight())); - } - if (dialogExecutor != null) - { - dialogExecutor.shutdownNow(); - } - closeAll_actionPerformed(null); + if (jconsole != null) + { + storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); + jconsole.stopConsole(); + } - if (groovyConsole != null) - { - // suppress a possible repeat prompt to save script - groovyConsole.setDirty(false); - groovyConsole.exit(); - } - System.exit(0); + if (jvnews != null) + { + storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); + } + + // Frames should all close automatically. Keeping external + // viewers open should already be decided by user. + closeAll_actionPerformed(null); + + // check for aborted quit + if (QuitHandler.quitCancelled()) + { + jalview.bin.Console.debug("Desktop aborting quit"); + return; + } + + if (dialogExecutor != null) + { + dialogExecutor.shutdownNow(); + } + + if (groovyConsole != null) + { + // suppress a possible repeat prompt to save script + groovyConsole.setDirty(false); + groovyConsole.exit(); + } + + if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT) + { + // note that shutdown hook will not be run + jalview.bin.Console.debug("Force Quit selected by user"); + Runtime.getRuntime().halt(0); + } + + jalview.bin.Console.debug("Quit selected by user"); + if (disposeFlag) + { + instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + // instance.dispose(); + } + instance.quit(); + }; + + return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit, + QuitHandler.defaultCancelQuit); + } + + /** + * Don't call this directly, use desktopQuit() above. Exits the program. + */ + @Override + public void quit() + { + // this will run the shutdownHook but QuitHandler.getQuitResponse() should + // not run a second time if gotQuitResponse flag has been set (i.e. user + // confirmed quit of some kind). + Jalview.exit("Desktop exiting.", ExitCode.OK); } private void storeLastKnownDimensions(String string, Rectangle jc) @@ -1516,7 +1643,8 @@ public class Desktop extends jalview.jbgui.GDesktop } } catch (Exception ex) { - System.err.println("Error opening help: " + ex.getMessage()); + jalview.bin.Console + .errPrintln("Error opening help: " + ex.getMessage()); } } @@ -1535,7 +1663,7 @@ public class Desktop extends jalview.jbgui.GDesktop } } Jalview.setCurrentAlignFrame(null); - System.out.println("ALL CLOSED"); + jalview.bin.Console.info("ALL CLOSED"); /* * reset state of singleton objects as appropriate (clear down session state @@ -1549,6 +1677,22 @@ public class Desktop extends jalview.jbgui.GDesktop } } + public int structureViewersStillRunningCount() + { + int count = 0; + JInternalFrame[] frames = desktop.getAllFrames(); + for (int i = 0; i < frames.length; i++) + { + if (frames[i] != null + && frames[i] instanceof JalviewStructureDisplayI) + { + if (((JalviewStructureDisplayI) frames[i]).stillRunning()) + count++; + } + } + return count; + } + @Override public void raiseRelated_actionPerformed(ActionEvent e) { @@ -1731,7 +1875,8 @@ public class Desktop extends jalview.jbgui.GDesktop boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled(); - // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"', + // jalview.bin.Console.outPrintln("autoSave="+autoSave+", + // projectFile='"+projectFile+"', // saveAs="+saveAs+", Backups // "+(BackupFiles.getEnabled()?"enabled":"disabled")); @@ -1812,7 +1957,7 @@ public class Desktop extends jalview.jbgui.GDesktop saveState_actionPerformed(true); } - private void setProjectFile(File choice) + protected void setProjectFile(File choice) { this.projectFile = choice; } @@ -1840,42 +1985,36 @@ 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(); }); chooser.showOpenDialog(this); @@ -2539,7 +2678,7 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void actionPerformed(ActionEvent e) { - quit(); + desktopQuit(); } }); } @@ -2955,7 +3094,7 @@ public class Desktop extends jalview.jbgui.GDesktop /** * single thread that handles display of dialogs to user. */ - ExecutorService dialogExecutor = Executors.newSingleThreadExecutor(); + ExecutorService dialogExecutor = Executors.newFixedThreadPool(3); /** * flag indicating if dialogExecutor should try to acquire a permit @@ -2965,7 +3104,7 @@ public class Desktop extends jalview.jbgui.GDesktop /** * pause the queue */ - private java.util.concurrent.Semaphore block = new Semaphore(0); + private Semaphore block = new Semaphore(0); private static groovy.ui.Console groovyConsole; @@ -2983,12 +3122,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (dialogPause) { - try - { - block.acquire(); - } catch (InterruptedException x) - { - } + acquireDialogQueue(); } if (instance == null) { @@ -3006,12 +3140,41 @@ public class Desktop extends jalview.jbgui.GDesktop }); } + private boolean dialogQueueStarted = false; + public void startDialogQueue() { + if (dialogQueueStarted) + { + return; + } // set the flag so we don't pause waiting for another permit and semaphore // the current task to begin - dialogPause = false; + releaseDialogQueue(); + dialogQueueStarted = true; + } + + public void acquireDialogQueue() + { + try + { + block.acquire(); + dialogPause = true; + } catch (InterruptedException e) + { + jalview.bin.Console.debug("Interruption when acquiring DialogueQueue", + e); + } + } + + public void releaseDialogQueue() + { + if (!dialogPause) + { + return; + } block.release(); + dialogPause = false; } /** @@ -3046,7 +3209,15 @@ public class Desktop extends jalview.jbgui.GDesktop String title = "View of desktop"; ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title); - exporter.doExport(of, this, width, height, title); + try + { + exporter.doExport(of, this, width, height, title); + } catch (ImageOutputException ioex) + { + jalview.bin.Console.error( + "Unexpected error whilst writing Jalview desktop snapshot as EPS", + ioex); + } } /** @@ -3254,7 +3425,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (Platform.isAMacAndNotJS()) { - System.err.println( + jalview.bin.Console.errPrintln( "Please ignore plist error - occurs due to problem with java 8 on OSX"); } } @@ -3486,4 +3657,149 @@ public class Desktop extends jalview.jbgui.GDesktop jalview.bin.Console.debug(Cache.getStackTraceString(e)); } } + + /** + * closes the current instance window, disposes and forgets about it. + */ + public static void closeDesktop() + { + if (Desktop.instance != null) + { + Desktop.instance.closeAll_actionPerformed(null); + Desktop.instance.setVisible(false); + Desktop us = Desktop.instance; + Desktop.instance = null; + // call dispose in a separate thread - try to avoid indirect deadlocks + new Thread(new Runnable() + { + @Override + public void run() + { + ExecutorService dex = us.dialogExecutor; + if (dex != null) + { + dex.shutdownNow(); + us.dialogExecutor = null; + us.block.drainPermits(); + } + us.dispose(); + } + }).start(); + } + } + + /** + * checks if any progress bars are being displayed in any of the windows + * managed by the desktop + * + * @return + */ + public boolean operationsAreInProgress() + { + JInternalFrame[] frames = getAllFrames(); + for (JInternalFrame frame : frames) + { + if (frame instanceof IProgressIndicator) + { + if (((IProgressIndicator) frame).operationInProgress()) + { + return true; + } + } + } + return operationInProgress(); + } + + /** + * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames. + * The way the modal JInternalFrame is made means it cannot be a child of an + * AlignFrame, so closing the AlignFrame might leave the modal open :( + */ + private static Map alignFrameModalMap = new HashMap<>(); + + protected static void addModal(AlignFrame af, JInternalFrame jif) + { + alignFrameModalMap.put(af, jif); + } + + protected static void closeModal(AlignFrame af) + { + if (!alignFrameModalMap.containsKey(af)) + { + return; + } + JInternalFrame jif = alignFrameModalMap.get(af); + if (jif != null) + { + try + { + jif.setClosed(true); + } catch (PropertyVetoException e) + { + e.printStackTrace(); + } + } + alignFrameModalMap.remove(af); + } + + public void nonBlockingDialog(String title, String message, String button, + int type, boolean scrollable, boolean modal) + { + nonBlockingDialog(32, 2, title, message, null, button, type, scrollable, + modal); + } + + public void nonBlockingDialog(int width, int height, String title, + String message, String boxtext, String button, int type, + boolean scrollable, boolean modal) + { + if (type < 0) + { + type = JvOptionPane.WARNING_MESSAGE; + } + JLabel jl = new JLabel(message); + + JTextArea jta = new JTextArea(height, width); + // jta.setLineWrap(true); + jta.setEditable(false); + jta.setWrapStyleWord(true); + jta.setAutoscrolls(true); + jta.setText(boxtext); + + JScrollPane jsp = scrollable + ? new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED) + : null; + + JvOptionPane jvp = JvOptionPane.newOptionDialog(this); + + JPanel jp = new JPanel(); + jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS)); + + if (message != null) + { + jl.setAlignmentX(Component.LEFT_ALIGNMENT); + jp.add(jl); + } + if (boxtext != null) + { + if (scrollable) + { + jsp.setAlignmentX(Component.LEFT_ALIGNMENT); + jp.add(jsp); + } + else + { + jta.setAlignmentX(Component.LEFT_ALIGNMENT); + jp.add(jta); + } + } + + jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> { + }); + jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type, + null, new Object[] + { button }, button, modal, null, false); + } + }