X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FDesktop.java;h=070e67db37a91302ecdaee03f2453fec94a06b6d;hb=7fa567506eea0a202c71e6939b961962f515dcec;hp=6fd70a9bbc2ac69c61497e41d1f6621f8495fa18;hpb=3bf2b5ea57b1d77d6e2e7adc11a50387e5376dc0;p=jalview.git diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 6fd70a9..070e67d 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; @@ -85,10 +88,14 @@ 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.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; @@ -96,6 +103,7 @@ import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; +import javax.swing.text.JTextComponent; import org.stackoverflowusers.file.WindowsShortcut; @@ -104,6 +112,10 @@ import jalview.api.AlignmentViewPanel; import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.bin.Jalview; +import jalview.bin.Jalview.ExitCode; +import jalview.bin.argparser.Arg; +import jalview.bin.groovy.JalviewObject; +import jalview.bin.groovy.JalviewObjectI; import jalview.datamodel.Alignment; import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; @@ -148,7 +160,7 @@ import jalview.ws.utils.UrlDownloadClient; */ public class Desktop extends jalview.jbgui.GDesktop implements DropTargetListener, ClipboardOwner, IProgressIndicator, - jalview.api.StructureSelectionManagerProvider + jalview.api.StructureSelectionManagerProvider, JalviewObjectI { private static final String CITATION; static @@ -439,7 +451,7 @@ public class Desktop extends jalview.jbgui.GDesktop * Send this message to stderr as the warning that follows (due to * reflection) also goes to stderr. */ - System.err.println( + 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"; @@ -489,7 +501,28 @@ public class Desktop extends jalview.jbgui.GDesktop boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false); - boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false); + boolean showjconsole = Cache.getArgCacheDefault(Arg.JAVACONSOLE, + "SHOW_JAVA_CONSOLE", false); + + // start dialogue queue for single dialogues + startDialogQueue(); + + if (!Platform.isJS()) + /** + * Java only + * + * @j2sIgnore + */ + { + Desktop.instance.acquireDialogQueue(); + + jconsole = new Console(this); + jconsole.setHeader(Cache.getVersionDetailsForConsole()); + showConsole(showjconsole); + + Desktop.instance.releaseDialogQueue(); + } + desktop = new MyDesktopPane(selmemusage); showMemusage.setSelected(selmemusage); @@ -543,10 +576,6 @@ public class Desktop extends jalview.jbgui.GDesktop * @j2sIgnore */ { - jconsole = new Console(this, showjconsole); - jconsole.setHeader(Cache.getVersionDetailsForConsole()); - showConsole(showjconsole); - showNews.setVisible(false); experimentalFeatures.setSelected(showExperimental()); @@ -630,6 +659,7 @@ public class Desktop extends jalview.jbgui.GDesktop // used for jalviewjsTest jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP"); } + } /** @@ -774,17 +804,17 @@ public class Desktop extends jalview.jbgui.GDesktop iw = (int) (iw * sw); iy = (int) (iy * sh); ih = (int) (ih * sh); - while (ix >= screenSize.width) + if (ix >= screenSize.width) { jalview.bin.Console.debug( "Window geometry location recall error: shifting horizontal to within screenbounds."); - ix -= screenSize.width; + ix = ix % screenSize.width; } - while (iy >= screenSize.height) + if (iy >= screenSize.height) { jalview.bin.Console.debug( "Window geometry location recall error: shifting vertical to within screenbounds."); - iy -= screenSize.height; + iy = iy % screenSize.height; } jalview.bin.Console.debug( "Got last known dimensions for " + windowName + ": x:" + ix @@ -871,7 +901,7 @@ public class Desktop extends jalview.jbgui.GDesktop } } catch (Exception ex) { - System.out.println( + jalview.bin.Console.outPrintln( "Unable to paste alignment from system clipboard:\n" + ex); } } @@ -1028,7 +1058,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (itf instanceof AlignFrame) { - Jalview.setCurrentAlignFrame((AlignFrame) itf); + Jalview.getInstance().setCurrentAlignFrame((AlignFrame) itf); } itf.requestFocus(); } @@ -1076,7 +1106,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); @@ -1416,62 +1477,73 @@ public class Desktop extends jalview.jbgui.GDesktop desktopQuit(true, false); } - 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())); + /** + * close everything, stash window geometries, and shut down all associated threads/workers + * @param dispose - sets the dispose on close flag - JVM may terminate when set + * @param terminateJvm - quit with prejudice - stops the JVM. + */ + public void quitTheDesktop(boolean dispose, boolean terminateJvm) { + 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 (jconsole != null) - { - storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); - jconsole.stopConsole(); - } + if (jconsole != null) + { + storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); + jconsole.stopConsole(); + } - if (jvnews != null) - { - storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds()); - } + 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); + // 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 (dialogExecutor != null) - { - dialogExecutor.shutdownNow(); - } + if (groovyConsole != null) + { + // suppress a possible repeat prompt to save script + groovyConsole.setDirty(false); + groovyConsole.exit(); + } - if (groovyConsole != null) - { - // suppress a possible repeat prompt to save script - groovyConsole.setDirty(false); - groovyConsole.exit(); - } + if (terminateJvm) + { + // note that shutdown hook will not be run + jalview.bin.Console.debug("Force Quit selected by user"); + Runtime.getRuntime().halt(0); + } - 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 (dispose) + { + instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + // instance.dispose(); + } + } + public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag) + { + final Runnable doDesktopQuit = () -> { - jalview.bin.Console.debug("Quit selected by user"); - if (disposeFlag) + // FIRST !! check for aborted quit + if (QuitHandler.quitCancelled()) { - instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - // instance.dispose(); + jalview.bin.Console.debug("Quit was cancelled - Desktop aborting quit"); + return; } + + // Proceed with quitting + quitTheDesktop(disposeFlag, QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT); + // and exit the JVM instance.quit(); }; @@ -1480,7 +1552,14 @@ public class Desktop extends jalview.jbgui.GDesktop } /** - * Don't call this directly, use desktopQuit() above. Exits the program. + * Exits the program and the JVM. + * + * Don't call this directly + * + * - use desktopQuit() above to tidy up first. + * + * - use closeDesktop() to shutdown Jalview without shutting down the JVM + * */ @Override public void quit() @@ -1488,7 +1567,7 @@ public class Desktop extends jalview.jbgui.GDesktop // 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.", 0); + Jalview.exit("Desktop exiting.", ExitCode.OK); } private void storeLastKnownDimensions(String string, Rectangle jc) @@ -1601,7 +1680,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()); } } @@ -1619,7 +1699,7 @@ public class Desktop extends jalview.jbgui.GDesktop { } } - Jalview.setCurrentAlignFrame(null); + Jalview.getInstance().setCurrentAlignFrame(null); jalview.bin.Console.info("ALL CLOSED"); /* @@ -1832,7 +1912,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")); @@ -2077,7 +2158,7 @@ public class Desktop extends jalview.jbgui.GDesktop return null; } List aps = new ArrayList<>(); - AlignFrame[] frames = getAlignFrames(); + AlignFrame[] frames = Desktop.getDesktopAlignFrames(); if (frames == null) { return null; @@ -2114,7 +2195,7 @@ public class Desktop extends jalview.jbgui.GDesktop List viewp = new ArrayList<>(); if (desktop != null) { - AlignFrame[] frames = Desktop.getAlignFrames(); + AlignFrame[] frames = Desktop.getDesktopAlignFrames(); for (AlignFrame afr : frames) { @@ -2475,12 +2556,12 @@ public class Desktop extends jalview.jbgui.GDesktop * * @return an array of AlignFrame, or null if none found */ - public static AlignFrame[] getAlignFrames() + @Override + public AlignFrame[] getAlignFrames() { - if (Jalview.isHeadlessMode()) + if (desktop == null) { - // Desktop.desktop is null in headless mode - return new AlignFrame[] { Jalview.currentAlignFrame }; + return null; } JInternalFrame[] frames = Desktop.desktop.getAllFrames(); @@ -2522,6 +2603,25 @@ public class Desktop extends jalview.jbgui.GDesktop } /** + * static version + */ + public static AlignFrame[] getDesktopAlignFrames() + { + if (Jalview.isHeadlessMode()) + { + // Desktop.desktop is null in headless mode + return Jalview.getInstance().getAlignFrames(); + } + + if (instance != null && desktop != null) + { + return instance.getAlignFrames(); + } + + return null; + } + + /** * Returns an array of any AppJmol frames in the Desktop (or null if none). * * @return @@ -2563,7 +2663,7 @@ public class Desktop extends jalview.jbgui.GDesktop openGroovyConsole(); } catch (Exception ex) { - jalview.bin.Console.error("Groovy Shell Creation failed.", ex); + jalview.bin.Console.error("Groovy Console creation failed.", ex); JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager.getString("label.couldnt_create_groovy_shell"), @@ -2579,8 +2679,11 @@ public class Desktop extends jalview.jbgui.GDesktop { if (groovyConsole == null) { - groovyConsole = new groovy.ui.Console(); - groovyConsole.setVariable("Jalview", this); + JalviewObjectI j = new JalviewObject(this); + groovyConsole = new groovy.console.ui.Console(); + groovyConsole.setVariable(JalviewObjectI.jalviewObjectName, j); + groovyConsole.setVariable(JalviewObjectI.currentAlFrameName, + getCurrentAlignFrame()); groovyConsole.run(); /* @@ -2653,7 +2756,7 @@ public class Desktop extends jalview.jbgui.GDesktop */ groovyShell.setEnabled(!enabled); - AlignFrame[] alignFrames = getAlignFrames(); + AlignFrame[] alignFrames = getDesktopAlignFrames(); if (alignFrames != null) { for (AlignFrame af : alignFrames) @@ -2964,7 +3067,13 @@ public class Desktop extends jalview.jbgui.GDesktop */ public static void showUrl(final String url) { - showUrl(url, Desktop.instance); + if (url!=null && !url.trim().equals("")) { + jalview.bin.Console.info("Opening URL: "+url); + showUrl(url, Desktop.instance); + } else { + jalview.bin.Console.warn("Ignoring attempt to show an empty URL."); + } + } /** @@ -3060,9 +3169,9 @@ 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; + private static groovy.console.ui.Console groovyConsole; /** * add another dialog thread to the queue @@ -3078,12 +3187,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (dialogPause) { - try - { - block.acquire(); - } catch (InterruptedException x) - { - } + acquireDialogQueue(); } if (instance == null) { @@ -3101,12 +3205,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; } /** @@ -3300,7 +3433,7 @@ public class Desktop extends jalview.jbgui.GDesktop myTopFrame.setDisplayedView(myTopFrame.alignPanel); } - public static groovy.ui.Console getGroovyConsole() + public static groovy.console.ui.Console getGroovyConsole() { return groovyConsole; } @@ -3357,7 +3490,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"); } } @@ -3591,16 +3724,180 @@ public class Desktop extends jalview.jbgui.GDesktop } /** - * closes the current instance window, disposes and forgets about it. + * closes the current instance window, but leaves the JVM running. + * Bypasses any shutdown prompts, but does not set window dispose on close in case JVM terminates. */ public static void closeDesktop() { if (Desktop.instance != null) { - Desktop.instance.closeAll_actionPerformed(null); - Desktop.instance.setVisible(false); - Desktop.instance.dispose(); - Desktop.instance = null; + Desktop us = Desktop.instance; + Desktop.instance.quitTheDesktop(false, false); + // call dispose in a separate thread - try to avoid indirect deadlocks + if (us != null) + { + 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(title, message, null, button, type, scrollable, false, + modal, -1); + } + + public void nonBlockingDialog(String title, String message, + String boxtext, String button, int type, boolean scrollable, + boolean html, boolean modal, int timeout) + { + nonBlockingDialog(32, 2, title, message, boxtext, button, type, + scrollable, html, modal, timeout); + } + + public void nonBlockingDialog(int width, int height, String title, + String message, String boxtext, String button, int type, + boolean scrollable, boolean html, boolean modal, int timeout) + { + if (type < 0) + { + type = JvOptionPane.WARNING_MESSAGE; + } + JLabel jl = new JLabel(message); + + JTextComponent jtc = null; + if (html) + { + JTextPane jtp = new JTextPane(); + jtp.setContentType("text/html"); + jtp.setEditable(false); + jtp.setAutoscrolls(true); + jtp.setText(boxtext); + + jtc = jtp; + } + else + { + JTextArea jta = new JTextArea(height, width); + // jta.setLineWrap(true); + jta.setEditable(false); + jta.setWrapStyleWord(true); + jta.setAutoscrolls(true); + jta.setText(boxtext); + + jtc = jta; + } + + JScrollPane jsp = scrollable + ? new JScrollPane(jtc, 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 + { + jtc.setAlignmentX(Component.LEFT_ALIGNMENT); + jp.add(jtc); + } + } + + jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> { + }); + jvp.setTimeout(timeout); + JButton jb = new JButton(button); + jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type, + null, new Object[] + { button }, button, modal, new JButton[] { jb }, false); + } + + @Override + public AlignFrame getCurrentAlignFrame() + { + return Jalview.getInstance().getCurrentAlignFrame(); } }