X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FDesktop.java;h=35c78187a1e335d290ab422799f2babe6b008d19;hb=ba1f9fee25b87bdac0c535019252b787939a8f80;hp=195a313056796b5f48d7780215b7bae01453f1a9;hpb=5b1175ae8b46fc6c8b74cbde24e26204b6589f50;p=jalview.git diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 195a313..35c7818 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; @@ -62,6 +64,8 @@ import java.util.HashMap; 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; @@ -80,27 +84,44 @@ 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.JTextPane; 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; import javax.swing.event.InternalFrameEvent; +import javax.swing.text.JTextComponent; 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.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; +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; @@ -112,6 +133,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; @@ -120,6 +142,7 @@ import jalview.urls.IdOrgSettings; import jalview.util.BrowserLauncher; import jalview.util.ChannelProperties; import jalview.util.ImageMaker.TYPE; +import jalview.util.LaunchUtils; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.util.ShortcutKeyMaskExWrapper; @@ -137,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 @@ -149,7 +172,7 @@ public class Desktop extends jalview.jbgui.GDesktop boolean logo = (bg_logo_url != null || uod_logo_url != null); StringBuilder sb = new StringBuilder(); sb.append( - "

Development managed by The Barton Group, University of Dundee, Scotland, UK."); + "

Jalview is free software released under GPLv3.

Development is managed by The Barton Group, University of Dundee, Scotland, UK."); if (logo) { sb.append("
"); @@ -161,11 +184,11 @@ public class Desktop extends jalview.jbgui.GDesktop : " \"University"); sb.append( - "

For help, see the FAQ at www.jalview.org/faq and/or join the jalview-discuss@jalview.org mailing list"); + "

For help, see www.jalview.org/faq and join discourse.jalview.org"); sb.append("

If you use Jalview, please cite:" + "
Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)" + "
Jalview Version 2 - a multiple sequence alignment editor and analysis workbench" - + "
Bioinformatics doi: 10.1093/bioinformatics/btp033"); + + "
Bioinformatics doi: 10.1093/bioinformatics/btp033"); CITATION = sb.toString(); } @@ -181,10 +204,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; @@ -398,29 +431,41 @@ public class Desktop extends jalview.jbgui.GDesktop + Cache.getProperty("VERSION")); /** - * Set taskbar "grouped windows" name for linux desktops (works in GNOME and KDE). - * This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially documented or - * guaranteed to exist, so we access it via reflection. - * There appear to be unfathomable criteria about what this string can contain, and it if doesn't - * meet those criteria then "java" (KDE) or "jalview-bin-Jalview" (GNOME) is used. - * "Jalview", "Jalview Develop" and "Jalview Test" seem okay, but "Jalview non-release" does not. - * The reflection access may generate a warning: - * WARNING: An illegal reflective access operation has occurred - * WARNING: Illegal reflective access by jalview.gui.Desktop () to field sun.awt.X11.XToolkit.awtAppClassName - * which I don't think can be avoided. + * Set taskbar "grouped windows" name for linux desktops (works in GNOME and + * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not + * officially documented or guaranteed to exist, so we access it via + * reflection. There appear to be unfathomable criteria about what this + * string can contain, and it if doesn't meet those criteria then "java" + * (KDE) or "jalview-bin-Jalview" (GNOME) is used. "Jalview", "Jalview + * Develop" and "Jalview Test" seem okay, but "Jalview non-release" does + * not. The reflection access may generate a warning: WARNING: An illegal + * reflective access operation has occurred WARNING: Illegal reflective + * access by jalview.gui.Desktop () to field + * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided. */ if (Platform.isLinux()) { + if (LaunchUtils.getJavaVersion() >= 11) + { + /* + * 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(); Field[] declaredFields = xToolkit.getClass().getDeclaredFields(); Field awtAppClassNameField = null; - if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) + if (Arrays.stream(declaredFields) + .anyMatch(f -> f.getName().equals(awtAppClassName))) { awtAppClassNameField = xToolkit.getClass() - .getDeclaredField("awtAppClassName"); + .getDeclaredField(awtAppClassName); } String title = ChannelProperties.getProperty("app_name"); @@ -431,49 +476,58 @@ public class Desktop extends jalview.jbgui.GDesktop } else { - Cache.log.debug("XToolkit: awtAppClassName not found"); + jalview.bin.Console + .debug("XToolkit: " + awtAppClassName + " not found"); } } catch (Exception e) { - Cache.debug("Error setting awtAppClassName"); - Cache.trace(Cache.getStackTraceString(e)); + 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 (Throwable t) - { - Cache.warn("Error setting APQHandlers: " + t.toString()); - Cache.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 } }); 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); desktop.setBackground(Color.white); - this.setIconImages(ChannelProperties.getIconList()); - getContentPane().setLayout(new BorderLayout()); // alternate config - have scrollbars - see notes in JAL-153 // JScrollPane sp = new JScrollPane(); @@ -487,16 +541,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) @@ -518,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()); @@ -544,18 +598,19 @@ 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() { - Cache.log.debug("Filechooser init thread started."); - String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT"); + jalview.bin.Console.debug("Filechooser init thread started."); + String fileFormat = FileLoader.getUseDefaultFileFormat() + ? Cache.getProperty("DEFAULT_FILE_FORMAT") + : null; JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat); - Cache.log.debug("Filechooser init thread finished."); + jalview.bin.Console.debug("Filechooser init thread finished."); } }).start(); // Add the service change listener @@ -566,8 +621,9 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void propertyChange(PropertyChangeEvent evt) { - Cache.log.debug("Firing service changed event for " - + evt.getNewValue()); + jalview.bin.Console + .debug("Firing service changed event for " + + evt.getNewValue()); JalviewServicesChanged(evt); } }); @@ -575,15 +631,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() { @@ -606,6 +653,13 @@ public class Desktop extends jalview.jbgui.GDesktop } }); desktop.addMouseListener(ma); + + if (Platform.isJS()) + { + // used for jalviewjsTest + jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP"); + } + } /** @@ -632,8 +686,9 @@ public class Desktop extends jalview.jbgui.GDesktop Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true)); ssm.setProcessSecondaryStructure( Cache.getDefault(Preferences.STRUCT_FROM_PDB, true)); + // JAL-3915 - RNAView is no longer an option so this has no effect ssm.setSecStructServices( - Cache.getDefault(Preferences.USE_RNAVIEW, true)); + Cache.getDefault(Preferences.USE_RNAVIEW, false)); } else { @@ -652,35 +707,39 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void run() { - Cache.log.debug("Starting news thread."); + jalview.bin.Console.debug("Starting news thread."); jvnews = new BlogReader(me); showNews.setVisible(true); - Cache.log.debug("Completed news thread."); + jalview.bin.Console.debug("Completed news thread."); } }).start(); } public void getIdentifiersOrgData() { - // Thread off the identifiers fetcher - new Thread(new Runnable() - { - @Override - public void run() + if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) + {// Thread off the identifiers fetcher + new Thread(new Runnable() { - Cache.log.debug("Downloading data from identifiers.org"); - try - { - UrlDownloadClient.download(IdOrgSettings.getUrl(), - IdOrgSettings.getDownloadLocation()); - } catch (IOException e) + @Override + public void run() { - Cache.log.debug("Exception downloading identifiers.org data" - + e.getMessage()); + jalview.bin.Console + .debug("Downloading data from identifiers.org"); + try + { + UrlDownloadClient.download(IdOrgSettings.getUrl(), + IdOrgSettings.getDownloadLocation()); + } catch (IOException e) + { + jalview.bin.Console + .debug("Exception downloading identifiers.org data" + + e.getMessage()); + } } - } - }).start(); - + }).start(); + ; + } } @Override @@ -691,7 +750,7 @@ public class Desktop extends jalview.jbgui.GDesktop void showNews(boolean visible) { - Cache.log.debug((visible ? "Showing" : "Hiding") + " news."); + jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news."); showNews.setSelected(visible); if (visible && !jvnews.isVisible()) { @@ -745,19 +804,19 @@ 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) { - Cache.log.debug( + 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) { - Cache.log.debug( + jalview.bin.Console.debug( "Window geometry location recall error: shifting vertical to within screenbounds."); - iy -= screenSize.height; + iy = iy % screenSize.height; } - Cache.log.debug( + jalview.bin.Console.debug( "Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw + " height:" + ih); } @@ -787,26 +846,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 (contents != null) + if (Desktop.jalviewClipboard[2] != null) { - String file = (String) contents - .getTransferData(DataFlavor.stringFlavor); + HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2]; + af.viewport.setHiddenColumns(hc); + } - FileFormatI format = new IdentifyFile().identify(file, - DataSourceType.PASTE); + Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH, + AlignFrame.DEFAULT_HEIGHT); - new FileLoader().LoadFile(file, DataSourceType.PASTE, format); + } + else + { + try + { + 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); + + 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); } } @@ -947,8 +1044,8 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * add an entry for the new frame in the Window menu - * (and remove it when the frame is closed) + * add an entry for the new frame in the Window menu (and remove it when the + * frame is closed) */ final JMenuItem menuItem = new JMenuItem(title); frame.addInternalFrameListener(new InternalFrameAdapter() @@ -961,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(); } @@ -973,8 +1070,7 @@ public class Desktop extends jalview.jbgui.GDesktop PaintRefresher.RemoveComponent(frame); /* - * defensive check to prevent frames being - * added half off the window + * defensive check to prevent frames being added half off the window */ if (openFrameCount > 0) { @@ -1003,14 +1099,45 @@ public class Desktop extends jalview.jbgui.GDesktop frame.setIcon(false); } catch (java.beans.PropertyVetoException ex) { - // System.err.println(ex.toString()); + } } }); 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); @@ -1023,7 +1150,7 @@ public class Desktop extends jalview.jbgui.GDesktop { } catch (java.lang.ClassCastException cex) { - Cache.log.warn( + jalview.bin.Console.warn( "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869", cex); } @@ -1170,7 +1297,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()); @@ -1180,36 +1309,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); } @@ -1231,9 +1355,8 @@ public class Desktop extends jalview.jbgui.GDesktop panel.add(label); /* - * the URL to fetch is input in - * Java: an editable combobox with history - * JS: (pending JAL-3038) a plain text field + * the URL to fetch is input in Java: an editable combobox with history JS: + * (pending JAL-3038) a plain text field */ JComponent history; String urlBase = "https://www."; @@ -1266,62 +1389,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().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); } } }; @@ -1353,10 +1470,23 @@ 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() + { + desktopQuit(true, false); + } + + /** + * 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 + ""); @@ -1369,31 +1499,104 @@ public class Desktop extends jalview.jbgui.GDesktop storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds()); jconsole.stopConsole(); } + 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); + if (dialogExecutor != null) { dialogExecutor.shutdownNow(); } - closeAll_actionPerformed(null); if (groovyConsole != null) { // suppress a possible repeat prompt to save script groovyConsole.setDirty(false); - groovyConsole.exit(); + + // and tidy up + if (((Window) groovyConsole.getFrame()) != null + && ((Window) groovyConsole.getFrame()).isVisible()) + { + // console is visible -- FIXME JAL-4327 + groovyConsole.exit(); + } + else + { + // console is not, so just let it dispose itself when we shutdown + // we don't call groovyConsole.exit() because it calls the shutdown + // handler with invokeAndWait() causing deadlock + groovyConsole = null; + } + } + + if (terminateJvm) + { + // note that shutdown hook will not be run + jalview.bin.Console.debug("Force Quit selected by user"); + Runtime.getRuntime().halt(0); } - System.exit(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 = () -> { + + // FIRST !! check for aborted quit + if (QuitHandler.quitCancelled()) + { + 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(); + }; + + return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit, + QuitHandler.defaultCancelQuit); + } + + /** + * 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() + { + // 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) { - Cache.log.debug("Storing last known dimensions for " + string + ": x:" - + jc.x + " y:" + jc.y + " width:" + jc.width + " height:" - + jc.height); + jalview.bin.Console.debug("Storing last known dimensions for " + string + + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width + + " height:" + jc.height); Cache.setProperty(string + "SCREEN_X", jc.x + ""); Cache.setProperty(string + "SCREEN_Y", jc.y + ""); @@ -1447,7 +1650,7 @@ public class Desktop extends jalview.jbgui.GDesktop else if (!latestVersion.equals(Cache.getProperty("VERSION"))) { boolean red = false; - if (Cache.getProperty("VERSION").toLowerCase() + if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT) .indexOf("automated build") == -1) { red = true; @@ -1499,7 +1702,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()); } } @@ -1517,8 +1721,8 @@ public class Desktop extends jalview.jbgui.GDesktop { } } - Jalview.setCurrentAlignFrame(null); - System.out.println("ALL CLOSED"); + Jalview.getInstance().setCurrentAlignFrame(null); + jalview.bin.Console.info("ALL CLOSED"); /* * reset state of singleton objects as appropriate (clear down session state @@ -1532,6 +1736,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) { @@ -1559,17 +1779,16 @@ public class Desktop extends jalview.jbgui.GDesktop protected void garbageCollect_actionPerformed(ActionEvent e) { // We simply collect the garbage - Cache.log.debug("Collecting garbage..."); + jalview.bin.Console.debug("Collecting garbage..."); System.gc(); - Cache.log.debug("Finished garbage collection."); + jalview.bin.Console.debug("Finished garbage collection."); } /* * (non-Javadoc) * - * @see - * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent - * ) + * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event. + * ActionEvent ) */ @Override protected void showMemusage_actionPerformed(ActionEvent e) @@ -1715,7 +1934,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")); @@ -1774,7 +1994,7 @@ public class Desktop extends jalview.jbgui.GDesktop + chosenFile.getName(), oom); } catch (Exception ex) { - Cache.log.error("Problems whilst trying to save to " + jalview.bin.Console.error("Problems whilst trying to save to " + chosenFile.getName(), ex); JvOptionPane.showMessageDialog(me, MessageManager.formatMessage( @@ -1796,7 +2016,7 @@ public class Desktop extends jalview.jbgui.GDesktop saveState_actionPerformed(true); } - private void setProjectFile(File choice) + protected void setProjectFile(File choice) { this.projectFile = choice; } @@ -1824,42 +2044,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) - { - Cache.log.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); @@ -1966,7 +2180,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; @@ -2003,7 +2217,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) { @@ -2069,9 +2283,8 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * Restore the view's last exploded frame geometry if known. Multiple - * views from one exploded frame share and restore the same (frame) - * position and size. + * Restore the view's last exploded frame geometry if known. Multiple views from + * one exploded frame share and restore the same (frame) position and size. */ Rectangle geometry = ap.av.getExplodedGeometry(); if (geometry != null) @@ -2160,6 +2373,7 @@ public class Desktop extends jalview.jbgui.GDesktop { source.showFeatureSettingsUI(); } + } public JInternalFrame[] getAllFrames() @@ -2364,12 +2578,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(); @@ -2411,6 +2625,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 @@ -2452,7 +2685,7 @@ public class Desktop extends jalview.jbgui.GDesktop openGroovyConsole(); } catch (Exception ex) { - Cache.log.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"), @@ -2468,15 +2701,18 @@ 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(); /* * We allow only one console at a time, so that AlignFrame menu option - * 'Calculate | Run Groovy script' is unambiguous. - * Disable 'Groovy Console', and enable 'Run script', when the console is - * opened, and the reverse when it is closed + * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and + * enable 'Run script', when the console is opened, and the reverse when it is + * closed */ Window window = (Window) groovyConsole.getFrame(); window.addWindowListener(new WindowAdapter() @@ -2499,8 +2735,8 @@ public class Desktop extends jalview.jbgui.GDesktop ((Window) groovyConsole.getFrame()).setVisible(true); /* - * if we got this far, enable 'Run Groovy' in AlignFrame menus - * and disable opening a second console + * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable + * opening a second console */ enableExecuteGroovy(true); } @@ -2523,7 +2759,7 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void actionPerformed(ActionEvent e) { - quit(); + desktopQuit(); } }); } @@ -2537,12 +2773,12 @@ public class Desktop extends jalview.jbgui.GDesktop public void enableExecuteGroovy(boolean enabled) { /* - * disable opening a second Groovy console - * (or re-enable when the console is closed) + * disable opening a second Groovy console (or re-enable when the console is + * closed) */ groovyShell.setEnabled(!enabled); - AlignFrame[] alignFrames = getAlignFrames(); + AlignFrame[] alignFrames = getDesktopAlignFrames(); if (alignFrames != null) { for (AlignFrame af : alignFrames) @@ -2567,8 +2803,6 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void setProgressBar(String message, long id) { - // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK); - if (progressBars == null) { progressBars = new Hashtable<>(); @@ -2701,13 +2935,36 @@ public class Desktop extends jalview.jbgui.GDesktop this.inBatchMode = inBatchMode; } + /** + * start service discovery and wait till it is done + */ public void startServiceDiscovery() { startServiceDiscovery(false); } + /** + * start service discovery threads - blocking or non-blocking + * + * @param blocking + */ public void startServiceDiscovery(boolean blocking) { + startServiceDiscovery(blocking, false); + } + + /** + * start service discovery threads + * + * @param blocking + * - false means call returns immediately + * @param ignore_SHOW_JWS2_SERVICES_preference + * - when true JABA services are discovered regardless of user's JWS2 + * discovery preference setting + */ + public void startServiceDiscovery(boolean blocking, + boolean ignore_SHOW_JWS2_SERVICES_preference) + { boolean alive = true; Thread t0 = null, t1 = null, t2 = null; // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release @@ -2725,7 +2982,8 @@ public class Desktop extends jalview.jbgui.GDesktop (t0 = new Thread(discoverer)).start(); } - if (Cache.getDefault("SHOW_JWS2_SERVICES", true)) + if (ignore_SHOW_JWS2_SERVICES_preference + || Cache.getDefault("SHOW_JWS2_SERVICES", true)) { t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer() .startDiscoverer(changeSupport); @@ -2778,24 +3036,19 @@ public class Desktop extends jalview.jbgui.GDesktop /* * JalviewDialog jd =new JalviewDialog() { * - * @Override protected void cancelPressed() { // TODO - * Auto-generated method stub + * @Override protected void cancelPressed() { // TODO Auto-generated method stub * - * }@Override protected void okPressed() { // TODO - * Auto-generated method stub + * }@Override protected void okPressed() { // TODO Auto-generated method stub * - * }@Override protected void raiseClosed() { // TODO - * Auto-generated method stub + * }@Override protected void raiseClosed() { // TODO Auto-generated method stub * - * } }; jd.initDialogFrame(new - * JLabel("
" + ermsg + + * } }; jd.initDialogFrame(new JLabel("
" + + * ermsg + * "
It may be that you have invalid JABA URLs in your web service preferences," * + " or mis-configured HTTP proxy settings.
" + - * "Check the Connections and Web services tab of the" - * + - * " Tools->Preferences dialog box to change them.
" - * ), true, true, "Web Service Configuration Problem", 450, - * 400); + * "Check the Connections and Web services tab of the" + + * " Tools->Preferences dialog box to change them.
" ), + * true, true, "Web Service Configuration Problem", 450, 400); * * jd.waitForInput(); */ @@ -2817,7 +3070,7 @@ public class Desktop extends jalview.jbgui.GDesktop } else { - Cache.log.error( + jalview.bin.Console.error( "Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg); } @@ -2836,7 +3089,16 @@ 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."); + } + } /** @@ -2910,15 +3172,8 @@ public class Desktop extends jalview.jbgui.GDesktop { if (url != null) { - if (Cache.log != null) - { - Cache.log.error("Couldn't handle string " + url + " as a URL."); - } - else - { - System.err.println( - "Couldn't handle string " + url + " as a URL."); - } + jalview.bin.Console + .error("Couldn't handle string " + url + " as a URL."); } // ignore any exceptions due to dud links. } @@ -2929,7 +3184,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 @@ -2939,9 +3194,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 @@ -2957,12 +3212,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (dialogPause) { - try - { - block.acquire(); - } catch (InterruptedException x) - { - } + acquireDialogQueue(); } if (instance == null) { @@ -2973,18 +3223,48 @@ public class Desktop extends jalview.jbgui.GDesktop SwingUtilities.invokeAndWait(prompter); } catch (Exception q) { - Cache.log.warn("Unexpected Exception in dialog thread.", q); + jalview.bin.Console.warn("Unexpected Exception in dialog thread.", + q); } } }); } + 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; } /** @@ -3012,14 +3292,22 @@ public class Desktop extends jalview.jbgui.GDesktop public void exportImage(Graphics g) throws Exception { paintAll(g); - Cache.log.info("Successfully written snapshot to file " + jalview.bin.Console.info("Successfully written snapshot to file " + of.getAbsolutePath()); } }; 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); + } } /** @@ -3046,16 +3334,15 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * Processing in reverse order works, forwards order leaves the first panels - * not visible. I don't know why! + * Processing in reverse order works, forwards order leaves the first panels not + * visible. I don't know why! */ for (int i = viewCount - 1; i >= 0; i--) { /* - * Make new top and bottom frames. These take over the respective - * AlignmentPanel objects, including their AlignmentViewports, so the - * cdna/protein relationships between the viewports is carried over to the - * new split frames. + * Make new top and bottom frames. These take over the respective AlignmentPanel + * objects, including their AlignmentViewports, so the cdna/protein + * relationships between the viewports is carried over to the new split frames. * * explodedGeometry holds the (x, y) position of the previously exploded * SplitFrame, and the (width, height) of the AlignFrame component @@ -3094,8 +3381,8 @@ public class Desktop extends jalview.jbgui.GDesktop } /* - * Clear references to the panels (now relocated in the new SplitFrames) - * before closing the old SplitFrame. + * Clear references to the panels (now relocated in the new SplitFrames) before + * closing the old SplitFrame. */ topPanels.clear(); bottomPanels.clear(); @@ -3171,7 +3458,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; } @@ -3196,28 +3483,6 @@ public class Desktop extends jalview.jbgui.GDesktop Transferable t) throws Exception { - // BH 2018 changed List to List to allow for File from - // SwingJS - - // DataFlavor[] flavors = t.getTransferDataFlavors(); - // for (int i = 0; i < flavors.length; i++) { - // if (flavors[i].isFlavorJavaFileListType()) { - // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); - // List list = (List) t.getTransferData(flavors[i]); - // for (int j = 0; j < list.size(); j++) { - // File file = (File) list.get(j); - // byte[] data = getDroppedFileBytes(file); - // fileName.setText(file.getName() + " - " + data.length + " " + - // evt.getLocation()); - // JTextArea target = (JTextArea) ((DropTarget) - // evt.getSource()).getComponent(); - // target.setText(new String(data)); - // } - // dtde.dropComplete(true); - // return; - // } - // - DataFlavor uriListFlavor = new DataFlavor( "text/uri-list;class=java.lang.String"), urlFlavour = null; try @@ -3226,7 +3491,8 @@ public class Desktop extends jalview.jbgui.GDesktop "application/x-java-url; class=java.net.URL"); } catch (ClassNotFoundException cfe) { - Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe); + jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.", + cfe); } if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) @@ -3241,7 +3507,7 @@ public class Desktop extends jalview.jbgui.GDesktop { protocols.add(DataSourceType.URL); files.add(url.toString()); - Cache.log.debug("Drop handled as URL dataflavor " + jalview.bin.Console.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1)); return; } @@ -3249,19 +3515,19 @@ 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"); } } } catch (Throwable ex) { - Cache.log.debug("URL drop handler failed.", ex); + jalview.bin.Console.debug("URL drop handler failed.", ex); } } if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { // Works on Windows and MacOSX - Cache.log.debug("Drop handled as javaFileListFlavor"); + jalview.bin.Console.debug("Drop handled as javaFileListFlavor"); for (Object file : (List) t .getTransferData(DataFlavor.javaFileListFlavor)) { @@ -3276,14 +3542,15 @@ public class Desktop extends jalview.jbgui.GDesktop String data = null; if (t.isDataFlavorSupported(uriListFlavor)) { - Cache.log.debug("Drop handled as uriListFlavor"); + jalview.bin.Console.debug("Drop handled as uriListFlavor"); // This is used by Unix drag system data = (String) t.getTransferData(uriListFlavor); } if (data == null) { // fallback to text: workaround - on OSX where there's a JVM bug - Cache.log.debug("standard URIListFlavor failed. Trying text"); + jalview.bin.Console + .debug("standard URIListFlavor failed. Trying text"); // try text fallback DataFlavor textDf = new DataFlavor( "text/plain;class=java.lang.String"); @@ -3292,7 +3559,7 @@ public class Desktop extends jalview.jbgui.GDesktop data = (String) t.getTransferData(textDf); } - Cache.log.debug("Plain text drop content returned " + jalview.bin.Console.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data)); } @@ -3300,7 +3567,7 @@ public class Desktop extends jalview.jbgui.GDesktop { while (protocols.size() < files.size()) { - Cache.log.debug("Adding missing FILE protocol for " + jalview.bin.Console.debug("Adding missing FILE protocol for " + files.get(protocols.size())); protocols.add(DataSourceType.FILE); } @@ -3315,7 +3582,7 @@ public class Desktop extends jalview.jbgui.GDesktop continue; } java.net.URI uri = new java.net.URI(s); - if (uri.getScheme().toLowerCase().startsWith("http")) + if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) { protocols.add(DataSourceType.URL); files.add(uri.toString()); @@ -3330,7 +3597,7 @@ public class Desktop extends jalview.jbgui.GDesktop } } - if (Cache.log.isDebugEnabled()) + if (jalview.bin.Console.isDebugEnabled()) { if (data == null || !added) { @@ -3338,39 +3605,41 @@ public class Desktop extends jalview.jbgui.GDesktop if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) { - Cache.log.debug( + jalview.bin.Console.debug( "Couldn't resolve drop data. Here are the supported flavors:"); for (DataFlavor fl : t.getTransferDataFlavors()) { - Cache.log.debug( + jalview.bin.Console.debug( "Supported transfer dataflavor: " + fl.toString()); Object df = t.getTransferData(fl); if (df != null) { - Cache.log.debug("Retrieves: " + df); + jalview.bin.Console.debug("Retrieves: " + df); } else { - Cache.log.debug("Retrieved nothing"); + jalview.bin.Console.debug("Retrieved nothing"); } } } else { - Cache.log.debug("Couldn't resolve dataflavor for drop: " - + t.toString()); + jalview.bin.Console + .debug("Couldn't resolve dataflavor for drop: " + + t.toString()); } } } } if (Platform.isWindowsAndNotJS()) { - Cache.log.debug("Scanning dropped content for Windows Link Files"); + jalview.bin.Console + .debug("Scanning dropped content for Windows Link Files"); // resolve any .lnk files in the file drop for (int f = 0; f < files.size(); f++) { - String source = files.get(f).toString().toLowerCase(); + String source = files.get(f).toString().toLowerCase(Locale.ROOT); if (protocols.get(f).equals(DataSourceType.FILE) && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) @@ -3381,16 +3650,16 @@ public class Desktop extends jalview.jbgui.GDesktop File lf = (obj instanceof File ? (File) obj : new File((String) obj)); // process link file to get a URL - Cache.log.debug("Found potential link file: " + lf); + jalview.bin.Console.debug("Found potential link file: " + lf); WindowsShortcut wscfile = new WindowsShortcut(lf); String fullname = wscfile.getRealFilename(); protocols.set(f, FormatAdapter.checkProtocol(fullname)); files.set(f, fullname); - Cache.log.debug("Parsed real filename " + fullname + jalview.bin.Console.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f)); } catch (Exception ex) { - Cache.log.error( + jalview.bin.Console.error( "Couldn't parse " + files.get(f) + " as a link file.", ex); } @@ -3465,17 +3734,196 @@ public class Desktop extends jalview.jbgui.GDesktop AffineTransform t = gg.getTransform(); double scaleX = t.getScaleX(); double scaleY = t.getScaleY(); - Cache.debug(debugScaleMessage + scaleX + " (X)"); - Cache.debug(debugScaleMessage + scaleY + " (Y)"); + jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)"); + jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)"); debugScaleMessageDone = true; } else { - Cache.debug("Desktop graphics null"); + jalview.bin.Console.debug("Desktop graphics null"); } } catch (Exception e) { - Cache.debug(Cache.getStackTraceString(e)); + jalview.bin.Console.debug(Cache.getStackTraceString(e)); + } + } + + /** + * 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 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(); } }