X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FDesktop.java;h=ee24c250a238c7b097c3a818d521b937b4cdd860;hb=fd86db8856109e69c21a5f4102bc8dd94664ae1f;hp=98584bce54a1023daee5a57256acd7340a31c57a;hpb=1f6504bd0b7187c99cd4a2551c15e854d14583b5;p=jalview.git diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 98584bc..ee24c25 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -20,39 +20,13 @@ */ package jalview.gui; -import jalview.api.AlignViewportI; -import jalview.api.AlignmentViewPanel; -import jalview.bin.Cache; -import jalview.bin.Jalview; -import jalview.io.BackupFiles; -import jalview.io.DataSourceType; -import jalview.io.FileFormat; -import jalview.io.FileFormatException; -import jalview.io.FileFormatI; -import jalview.io.FileFormats; -import jalview.io.FileLoader; -import jalview.io.FormatAdapter; -import jalview.io.IdentifyFile; -import jalview.io.JalviewFileChooser; -import jalview.io.JalviewFileView; -import jalview.jbgui.GSplitFrame; -import jalview.jbgui.GStructureViewer; -import jalview.project.Jalview2XML; -import jalview.structure.StructureSelectionManager; -import jalview.urls.IdOrgSettings; -import jalview.util.ImageMaker; -import jalview.util.MessageManager; -import jalview.util.Platform; -import jalview.util.UrlConstants; -import jalview.viewmodel.AlignmentViewport; -import jalview.ws.params.ParamManager; -import jalview.ws.utils.UrlDownloadClient; - import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Point; import java.awt.Rectangle; @@ -75,20 +49,23 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.awt.geom.AffineTransform; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.BufferedInputStream; +import java.beans.PropertyVetoException; import java.io.File; -import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.ListIterator; -import java.util.StringTokenizer; +import java.util.Locale; +import java.util.Map; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -107,23 +84,68 @@ 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; import javax.swing.event.InternalFrameEvent; -import javax.swing.event.MenuEvent; -import javax.swing.event.MenuListener; 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; +import jalview.io.FileFormatException; +import jalview.io.FileFormatI; +import jalview.io.FileFormats; +import jalview.io.FileLoader; +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; +import jalview.structure.StructureSelectionManager; +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; +import jalview.util.UrlConstants; +import jalview.viewmodel.AlignmentViewport; +import jalview.ws.params.ParamManager; +import jalview.ws.utils.UrlDownloadClient; + /** * Jalview Desktop * @@ -135,6 +157,38 @@ public class Desktop extends jalview.jbgui.GDesktop implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider { + private static final String CITATION; + static + { + URL bg_logo_url = ChannelProperties.getImageURL( + "bg_logo." + String.valueOf(SplashScreen.logoSize)); + URL uod_logo_url = ChannelProperties.getImageURL( + "uod_banner." + String.valueOf(SplashScreen.logoSize)); + boolean logo = (bg_logo_url != null || uod_logo_url != null); + StringBuilder sb = new StringBuilder(); + sb.append( + "

Jalview is free software released under GPLv3.

Development is managed by The Barton Group, University of Dundee, Scotland, UK."); + if (logo) + { + sb.append("
"); + } + sb.append(bg_logo_url == null ? "" + : "\"Barton"); + sb.append(uod_logo_url == null ? "" + : " \"University"); + sb.append( + "

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"); + CITATION = sb.toString(); + } + + private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)"; + private static int DEFAULT_MIN_WIDTH = 300; private static int DEFAULT_MIN_HEIGHT = 250; @@ -145,12 +199,24 @@ 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(); - 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; + /** * news reader - null if it was never started. */ @@ -198,6 +264,13 @@ public class Desktop extends jalview.jbgui.GDesktop public static MyDesktopPane desktop; + public static MyDesktopPane getDesktop() + { + // BH 2018 could use currentThread() here as a reference to a + // Hashtable in JavaScript + return desktop; + } + static int openFrameCount = 0; static final int xOffset = 30; @@ -340,57 +413,92 @@ public class Desktop extends jalview.jbgui.GDesktop */ public Desktop() { + super(); /** * A note to implementors. It is ESSENTIAL that any activities that might * block are spawned off as threads rather than waited for during this * constructor. */ instance = this; - doVamsasClientCheck(); doConfigureStructurePrefs(); - setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION")); - /* - if (!Platform.isAMac()) - { - // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - } - else - { - this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - */ + setTitle(ChannelProperties.getProperty("app_name") + " " + + Cache.getProperty("VERSION")); - try - { - APQHandlers.setAPQHandlers(this); - } catch (Exception e) - { - System.out.println("Cannot set APQHandlers"); - // e.printStackTrace(); - } catch (Throwable t) + /** + * 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()) { - System.out.println("Cannot set APQHandlers"); - // t.printStackTrace(); + 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))) + { + awtAppClassNameField = xToolkit.getClass() + .getDeclaredField(awtAppClassName); + } + + String title = ChannelProperties.getProperty("app_name"); + if (awtAppClassNameField != null) + { + awtAppClassNameField.setAccessible(true); + awtAppClassNameField.set(xToolkit, title); + } + else + { + jalview.bin.Console + .debug("XToolkit: " + awtAppClassName + " not found"); + } + } catch (Exception e) + { + jalview.bin.Console.debug("Error setting " + awtAppClassName); + jalview.bin.Console.trace(Cache.getStackTraceString(e)); + } } + 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 = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE", - false); + boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false); - boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE", - false); + boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false); desktop = new MyDesktopPane(selmemusage); + showMemusage.setSelected(selmemusage); desktop.setBackground(Color.white); @@ -399,17 +507,28 @@ public class Desktop extends jalview.jbgui.GDesktop // JScrollPane sp = new JScrollPane(); // sp.getViewport().setView(desktop); // getContentPane().add(sp, BorderLayout.CENTER); + + // BH 2018 - just an experiment to try unclipped JInternalFrames. + if (Platform.isJS()) + { + getRootPane().putClientProperty("swingjs.overflow.hidden", "false"); + } + 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.isWindows() ? new DefaultDesktopManager() - : Platform.isAMac() - ? 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) @@ -419,50 +538,79 @@ public class Desktop extends jalview.jbgui.GDesktop else { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds((screenSize.width - 900) / 2, (screenSize.height - 650) / 2, - 900, 650); - } - jconsole = new Console(this, showjconsole); - // add essential build information - jconsole.setHeader( - "Jalview Version: " + jalview.bin.Cache.getProperty("VERSION") - + "\n" + "Jalview Installation: " - + jalview.bin.Cache.getDefault("INSTALLATION", - "unknown") - + "\n" + "Build Date: " - + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") - + "\n" + "Java version: " - + System.getProperty("java.version") + "\n" - + System.getProperty("os.arch") + " " - + System.getProperty("os.name") + " " - + System.getProperty("os.version") - + (jalview.bin.Cache.getProperty("VERSION").equals("DEVELOPMENT") - ? "\nJava path:" - + System.getProperty( - "java.home") - + File.separator + "bin" - + File.separator + "java" - : "") - ); - - showConsole(showjconsole); - - showNews.setVisible(false); - - experimentalFeatures.setSelected(showExperimental()); - - getIdentifiersOrgData(); - - checkURLLinks(); - - this.addWindowListener(new WindowAdapter() + int xPos = Math.max(5, (screenSize.width - 900) / 2); + int yPos = Math.max(5, (screenSize.height - 650) / 2); + setBounds(xPos, yPos, 900, 650); + } + + // start dialogue queue for single dialogues + startDialogQueue(); + + if (!Platform.isJS()) + /** + * Java only + * + * @j2sIgnore + */ { - @Override - public void windowClosing(WindowEvent evt) + jconsole = new Console(this, showjconsole); + jconsole.setHeader(Cache.getVersionDetailsForConsole()); + showConsole(showjconsole); + + showNews.setVisible(false); + + experimentalFeatures.setSelected(showExperimental()); + + getIdentifiersOrgData(); + + checkURLLinks(); + + // Spawn a thread that shows the splashscreen + if (!nosplash) { - quit(); + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + new SplashScreen(true); + } + }); } - }); + + // Thread off a new instance of the file chooser - this reduces the time + // 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 = FileLoader.getUseDefaultFileFormat() + ? Cache.getProperty("DEFAULT_FILE_FORMAT") + : null; + JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), + fileFormat); + jalview.bin.Console.debug("Filechooser init thread finished."); + } + }).start(); + // Add the service change listener + changeSupport.addJalviewPropertyChangeListener("services", + new PropertyChangeListener() + { + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + jalview.bin.Console + .debug("Firing service changed event for " + + evt.getNewValue()); + JalviewServicesChanged(evt); + } + }); + } + + this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this)); MouseAdapter ma; this.addMouseListener(ma = new MouseAdapter() @@ -487,45 +635,12 @@ public class Desktop extends jalview.jbgui.GDesktop }); desktop.addMouseListener(ma); - this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this)); - // Spawn a thread that shows the splashscreen - SwingUtilities.invokeLater(new Runnable() - { - @Override - public void run() - { - new SplashScreen(); - } - }); - - // Thread off a new instance of the file chooser - this reduces the time it - // takes to open it later on. - new Thread(new Runnable() + if (Platform.isJS()) { - @Override - public void run() - { - Cache.log.debug("Filechooser init thread started."); - String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT"); - JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), - fileFormat); - Cache.log.debug("Filechooser init thread finished."); - } - }).start(); - // Add the service change listener - changeSupport.addJalviewPropertyChangeListener("services", - new PropertyChangeListener() - { - - @Override - public void propertyChange(PropertyChangeEvent evt) - { - Cache.log.debug("Firing service changed event for " - + evt.getNewValue()); - JalviewServicesChanged(evt); - } + // used for jalviewjsTest + jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP"); + } - }); } /** @@ -546,14 +661,15 @@ public class Desktop extends jalview.jbgui.GDesktop // configure services StructureSelectionManager ssm = StructureSelectionManager .getStructureSelectionManager(this); - if (jalview.bin.Cache.getDefault(Preferences.ADD_SS_ANN, true)) + if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) { - ssm.setAddTempFacAnnot(jalview.bin.Cache - .getDefault(Preferences.ADD_TEMPFACT_ANN, true)); - ssm.setProcessSecondaryStructure(jalview.bin.Cache - .getDefault(Preferences.STRUCT_FROM_PDB, true)); + ssm.setAddTempFacAnnot( + 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( - jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, true)); + Cache.getDefault(Preferences.USE_RNAVIEW, false)); } else { @@ -572,37 +688,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"); - UrlDownloadClient client = new UrlDownloadClient(); - try - { - client.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 @@ -613,26 +731,23 @@ public class Desktop extends jalview.jbgui.GDesktop void showNews(boolean visible) { + jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news."); + showNews.setSelected(visible); + if (visible && !jvnews.isVisible()) { - Cache.log.debug((visible ? "Showing" : "Hiding") + " news."); - showNews.setSelected(visible); - if (visible && !jvnews.isVisible()) + new Thread(new Runnable() { - new Thread(new Runnable() + @Override + public void run() { - @Override - public void run() - { - long now = System.currentTimeMillis(); - Desktop.instance.setProgressBar( - MessageManager.getString("status.refreshing_news"), - now); - jvnews.refreshNews(); - Desktop.instance.setProgressBar(null, now); - jvnews.showNews(); - } - }).start(); - } + long now = System.currentTimeMillis(); + Desktop.instance.setProgressBar( + MessageManager.getString("status.refreshing_news"), now); + jvnews.refreshNews(); + Desktop.instance.setProgressBar(null, now); + jvnews.showNews(); + } + }).start(); } } @@ -648,25 +763,23 @@ public class Desktop extends jalview.jbgui.GDesktop { // TODO: lock aspect ratio for scaling desktop Bug #0058199 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - String x = jalview.bin.Cache.getProperty(windowName + "SCREEN_X"); - String y = jalview.bin.Cache.getProperty(windowName + "SCREEN_Y"); - String width = jalview.bin.Cache - .getProperty(windowName + "SCREEN_WIDTH"); - String height = jalview.bin.Cache - .getProperty(windowName + "SCREEN_HEIGHT"); + String x = Cache.getProperty(windowName + "SCREEN_X"); + String y = Cache.getProperty(windowName + "SCREEN_Y"); + String width = Cache.getProperty(windowName + "SCREEN_WIDTH"); + String height = Cache.getProperty(windowName + "SCREEN_HEIGHT"); if ((x != null) && (y != null) && (width != null) && (height != null)) { int ix = Integer.parseInt(x), iy = Integer.parseInt(y), iw = Integer.parseInt(width), ih = Integer.parseInt(height); - if (jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) + if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null) { // attempt #1 - try to cope with change in screen geometry - this // version doesn't preserve original jv aspect ratio. // take ratio of current screen size vs original screen size. - double sw = ((1f * screenSize.width) / (1f * Integer.parseInt( - jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH")))); - double sh = ((1f * screenSize.height) / (1f * Integer.parseInt( - jalview.bin.Cache.getProperty("SCREENGEOMETRY_HEIGHT")))); + double sw = ((1f * screenSize.width) / (1f * Integer + .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH")))); + double sh = ((1f * screenSize.height) / (1f * Integer + .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT")))); // rescale the bounds depending upon the current screen geometry. ix = (int) (ix * sw); iw = (int) (iw * sw); @@ -674,17 +787,17 @@ public class Desktop extends jalview.jbgui.GDesktop ih = (int) (ih * sh); while (ix >= screenSize.width) { - jalview.bin.Cache.log.debug( + jalview.bin.Console.debug( "Window geometry location recall error: shifting horizontal to within screenbounds."); ix -= screenSize.width; } while (iy >= screenSize.height) { - jalview.bin.Cache.log.debug( + jalview.bin.Console.debug( "Window geometry location recall error: shifting vertical to within screenbounds."); iy -= screenSize.height; } - jalview.bin.Cache.log.debug( + jalview.bin.Console.debug( "Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw + " height:" + ih); } @@ -694,46 +807,6 @@ public class Desktop extends jalview.jbgui.GDesktop return null; } - private void doVamsasClientCheck() - { - if (jalview.bin.Cache.vamsasJarsPresent()) - { - setupVamsasDisconnectedGui(); - VamsasMenu.setVisible(true); - final Desktop us = this; - VamsasMenu.addMenuListener(new MenuListener() - { - // this listener remembers when the menu was first selected, and - // doesn't rebuild the session list until it has been cleared and - // reselected again. - boolean refresh = true; - - @Override - public void menuCanceled(MenuEvent e) - { - refresh = true; - } - - @Override - public void menuDeselected(MenuEvent e) - { - refresh = true; - } - - @Override - public void menuSelected(MenuEvent e) - { - if (refresh) - { - us.buildVamsasStMenu(); - refresh = false; - } - } - }); - vamsasStart.setVisible(true); - } - } - void showPasteMenu(int x, int y) { JPopupMenu popup = new JPopupMenu(); @@ -754,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); + } + + Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH, + AlignFrame.DEFAULT_HEIGHT); - if (contents != null) + } + else + { + try { - String file = (String) contents - .getTransferData(DataFlavor.stringFlavor); + Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable contents = c.getContents(this); - FileFormatI format = new IdentifyFile().identify(file, - DataSourceType.PASTE); + 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); + 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); } } @@ -905,7 +1016,7 @@ public class Desktop extends jalview.jbgui.GDesktop frame.setResizable(resizable); frame.setMaximizable(resizable); frame.setIconifiable(resizable); - frame.setOpaque(false); + frame.setOpaque(Platform.isJS()); if (frame.getX() < 1 && frame.getY() < 1) { @@ -914,8 +1025,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() @@ -940,8 +1051,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) { @@ -956,7 +1066,7 @@ public class Desktop extends jalview.jbgui.GDesktop menuItem.removeActionListener(menuItem.getActionListeners()[0]); } windowMenu.remove(menuItem); - }; + } }); menuItem.addActionListener(new ActionListener() @@ -977,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); @@ -990,8 +1131,8 @@ public class Desktop extends jalview.jbgui.GDesktop { } catch (java.lang.ClassCastException cex) { - Cache.log.warn( - "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869", + 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); } } @@ -1020,7 +1161,7 @@ public class Desktop extends jalview.jbgui.GDesktop KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK); KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, - jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()); + ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()); InputMap inputMap = frame .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); @@ -1077,7 +1218,7 @@ public class Desktop extends jalview.jbgui.GDesktop // Java's Transferable for native dnd evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Transferable t = evt.getTransferable(); - List files = new ArrayList<>(); + List files = new ArrayList<>(); List protocols = new ArrayList<>(); try @@ -1095,13 +1236,15 @@ public class Desktop extends jalview.jbgui.GDesktop { for (int i = 0; i < files.size(); i++) { - String file = files.get(i).toString(); + // BH 2018 File or String + Object file = files.get(i); + String fileName = file.toString(); DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i); FileFormatI format = null; - if (file.endsWith(".jar")) + if (fileName.endsWith(".jar")) { format = FileFormat.Jalview; @@ -1110,8 +1253,11 @@ public class Desktop extends jalview.jbgui.GDesktop { format = new IdentifyFile().identify(file, protocol); } - - new FileLoader().LoadFile(file, protocol, format); + if (file instanceof File) + { + Platform.cacheFileData((File) file); + } + new FileLoader().LoadFile(null, file, protocol, format); } } catch (Exception ex) @@ -1132,58 +1278,51 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) { - String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT"); - JalviewFileChooser chooser = JalviewFileChooser - .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, true); + String fileFormat = FileLoader.getUseDefaultFileFormat() + ? Cache.getProperty("DEFAULT_FILE_FORMAT") + : null; + JalviewFileChooser chooser = JalviewFileChooser.forRead( + Cache.getProperty("LAST_DIRECTORY"), fileFormat, + BackupFiles.getEnabled()); chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle( MessageManager.getString("label.open_local_file")); chooser.setToolTipText(MessageManager.getString("action.open")); - int value = chooser.showOpenDialog(this); - - if (value == JalviewFileChooser.APPROVE_OPTION) - { - String choice = chooser.getSelectedFile().getPath(); - Cache.setProperty("LAST_DIRECTORY", - chooser.getSelectedFile().getParent()); + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); + Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); 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. + * Skip this step for dynamically added file formats, because IdentifyFile does + * not know how to recognise them. */ if (FileFormats.getInstance().isIdentifiable(format)) { try { - format = new IdentifyFile().identify(choice, DataSourceType.FILE); + format = new IdentifyFile().identify(selectedFile, + DataSourceType.FILE); } catch (FileFormatException e) { // format = null; //?? } } - if (viewport != null) - { - new FileLoader().LoadFile(viewport, choice, DataSourceType.FILE, - format); - } - else - { - new FileLoader().LoadFile(choice, DataSourceType.FILE, format); - } - } + new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, + format); + }); + chooser.showOpenDialog(this); } /** - * DOCUMENT ME! + * Shows a dialog for input of a URL at which to retrieve alignment data * - * @param e - * DOCUMENT ME! + * @param viewport */ @Override public void inputURLMenuItem_actionPerformed(AlignViewport viewport) @@ -1192,87 +1331,105 @@ public class Desktop extends jalview.jbgui.GDesktop // for viewing JLabel label = new JLabel( MessageManager.getString("label.input_file_url")); - final JComboBox history = new JComboBox(); JPanel panel = new JPanel(new GridLayout(2, 1)); panel.add(label); - panel.add(history); - history.setPreferredSize(new Dimension(400, 20)); - history.setEditable(true); - history.addItem("http://www."); - - String historyItems = jalview.bin.Cache.getProperty("RECENT_URL"); - - StringTokenizer st; - - if (historyItems != null) - { - st = new StringTokenizer(historyItems, "\t"); - - while (st.hasMoreTokens()) - { - history.addItem(st.nextElement()); - } - } - - int reply = JvOptionPane.showInternalConfirmDialog(desktop, panel, - MessageManager.getString("label.input_alignment_from_url"), - JvOptionPane.OK_CANCEL_OPTION); - - if (reply != JvOptionPane.OK_OPTION) - { - return; - } - String url = history.getSelectedItem().toString(); - - if (url.toLowerCase().endsWith(".jar")) + /* + * 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."; + if (Platform.isJS()) { - if (viewport != null) - { - new FileLoader().LoadFile(viewport, url, DataSourceType.URL, - FileFormat.Jalview); - } - else - { - new FileLoader().LoadFile(url, DataSourceType.URL, - FileFormat.Jalview); - } + history = new JTextField(urlBase, 35); } else + /** + * Java only + * + * @j2sIgnore + */ { - FileFormatI format = null; - try + JComboBox asCombo = new JComboBox<>(); + asCombo.setPreferredSize(new Dimension(400, 20)); + asCombo.setEditable(true); + asCombo.addItem(urlBase); + String historyItems = Cache.getProperty("RECENT_URL"); + if (historyItems != null) { - format = new IdentifyFile().identify(url, DataSourceType.URL); - } catch (FileFormatException e) - { - // TODO revise error handling, distinguish between - // URL not found and response not valid + for (String token : historyItems.split("\\t")) + { + asCombo.addItem(token); + } } + history = asCombo; + } + panel.add(history); - if (format == null) - { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.formatMessage("label.couldnt_locate", - new Object[] - { url }), - MessageManager.getString("label.url_not_found"), - JvOptionPane.WARNING_MESSAGE); + Object[] options = new Object[] { MessageManager.getString("action.ok"), + MessageManager.getString("action.cancel") }; + Runnable action = () -> { + @SuppressWarnings("unchecked") + String url = (history instanceof JTextField + ? ((JTextField) history).getText() + : ((JComboBox) history).getEditor().getItem() + .toString().trim()); - return; - } - - if (viewport != null) + if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) { - new FileLoader().LoadFile(viewport, url, DataSourceType.URL, - format); + if (viewport != null) + { + new FileLoader().LoadFile(viewport, url, DataSourceType.URL, + FileFormat.Jalview); + } + else + { + new FileLoader().LoadFile(url, DataSourceType.URL, + FileFormat.Jalview); + } } else { - new FileLoader().LoadFile(url, DataSourceType.URL, format); + 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); + return; + } + + if (viewport != null) + { + new FileLoader().LoadFile(viewport, url, DataSourceType.URL, + format); + } + else + { + new FileLoader().LoadFile(url, DataSourceType.URL, format); + } } - } + }; + String dialogOption = MessageManager + .getString("label.input_alignment_from_url"); + JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action) + .showInternalDialog(panel, dialogOption, + JvOptionPane.YES_NO_CANCEL_OPTION, + JvOptionPane.PLAIN_MESSAGE, null, options, + MessageManager.getString("action.ok")); } /** @@ -1294,56 +1451,98 @@ 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() { - //System.out.println("********** Desktop.quit()"); - //System.out.println(savingFiles.toString()); - Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - jalview.bin.Cache.setProperty("SCREENGEOMETRY_WIDTH", - screen.width + ""); - jalview.bin.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) { - jalview.bin.Cache.log.debug("Storing last known dimensions for " - + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width + jalview.bin.Console.debug("Storing last known dimensions for " + string + + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width + " height:" + jc.height); - jalview.bin.Cache.setProperty(string + "SCREEN_X", jc.x + ""); - jalview.bin.Cache.setProperty(string + "SCREEN_Y", jc.y + ""); - jalview.bin.Cache.setProperty(string + "SCREEN_WIDTH", jc.width + ""); - jalview.bin.Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + ""); + Cache.setProperty(string + "SCREEN_X", jc.x + ""); + Cache.setProperty(string + "SCREEN_Y", jc.y + ""); + Cache.setProperty(string + "SCREEN_WIDTH", jc.width + ""); + Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + ""); } /** @@ -1355,53 +1554,44 @@ public class Desktop extends jalview.jbgui.GDesktop @Override public void aboutMenuItem_actionPerformed(ActionEvent e) { - // StringBuffer message = getAboutMessage(false); - // JvOptionPane.showInternalMessageDialog(Desktop.desktop, - // - // message.toString(), "About Jalview", JvOptionPane.INFORMATION_MESSAGE); new Thread(new Runnable() { @Override public void run() { - new SplashScreen(true); + new SplashScreen(false); } }).start(); } - public StringBuffer getAboutMessage(boolean shortv) + /** + * Returns the html text for the About screen, including any available version + * number, build details, author details and citation reference, but without + * the enclosing {@code html} tags + * + * @return + */ + public String getAboutMessage() { - StringBuffer message = new StringBuffer(); - message.append(""); - if (shortv) - { - message.append("

Version: " - + jalview.bin.Cache.getProperty("VERSION") - + "

"); - message.append("Last Updated: " - + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") - + ""); + StringBuilder message = new StringBuilder(1024); + message.append("
") + .append("

Version: ") + .append(Cache.getProperty("VERSION")).append("

") + .append("Built: ") + .append(Cache.getDefault("BUILD_DATE", "unknown")) + .append(" from ").append(Cache.getBuildDetailsForSplash()) + .append(""); - } - else + String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking"); + if (latestVersion.equals("Checking")) { - - message.append("Version " - + jalview.bin.Cache.getProperty("VERSION") - + "; last updated: " - + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")); + // JBP removed this message for 2.11: May be reinstated in future version + // message.append("
...Checking latest version...
"); } - - if (jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking") - .equals("Checking")) - { - message.append("
...Checking latest version...
"); - } - else if (!jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking") - .equals(jalview.bin.Cache.getProperty("VERSION"))) + else if (!latestVersion.equals(Cache.getProperty("VERSION"))) { boolean red = false; - if (jalview.bin.Cache.getProperty("VERSION").toLowerCase() + if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT) .indexOf("automated build") == -1) { red = true; @@ -1410,45 +1600,51 @@ public class Desktop extends jalview.jbgui.GDesktop message.append("
"); } - message.append("
!! Version " - + jalview.bin.Cache.getDefault("LATEST_VERSION", - "..Checking..") - + " is available for download from " - + jalview.bin.Cache.getDefault("www.jalview.org", - "http://www.jalview.org") - + " !!"); + message.append("
!! Version ") + .append(Cache.getDefault("LATEST_VERSION", "..Checking..")) + .append(" is available for download from ") + .append(Cache.getDefault("www.jalview.org", + "https://www.jalview.org")) + .append(" !!"); if (red) { message.append("
"); } } - message.append("
Authors: " + jalview.bin.Cache.getDefault( - "AUTHORFNAMES", - "The Jalview Authors (See AUTHORS file for current list)") - + "

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

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

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" - + ""); - return message; + message.append("
Authors: "); + message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS)); + message.append(CITATION); + + message.append("
"); + + return message.toString(); } /** - * DOCUMENT ME! - * - * @param e - * DOCUMENT ME! + * Action on requesting Help documentation */ @Override - public void documentationMenuItem_actionPerformed(ActionEvent e) + public void documentationMenuItem_actionPerformed() { try { - Help.showHelpWindow(); + if (Platform.isJS()) + { + BrowserLauncher.openURL("https://www.jalview.org/help.html"); + } + else + /** + * Java only + * + * @j2sIgnore + */ + { + Help.showHelpWindow(); + } } catch (Exception ex) { + jalview.bin.Console + .errPrintln("Error opening help: " + ex.getMessage()); } } @@ -1467,11 +1663,7 @@ public class Desktop extends jalview.jbgui.GDesktop } } Jalview.setCurrentAlignFrame(null); - System.out.println("ALL CLOSED"); - if (v_client != null) - { - // TODO clear binding to vamsas document objects on close_all - } + jalview.bin.Console.info("ALL CLOSED"); /* * reset state of singleton objects as appropriate (clear down session state @@ -1485,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) { @@ -1512,17 +1720,16 @@ public class Desktop extends jalview.jbgui.GDesktop protected void garbageCollect_actionPerformed(ActionEvent e) { // We simply collect the garbage - jalview.bin.Cache.log.debug("Collecting garbage..."); + jalview.bin.Console.debug("Collecting garbage..."); System.gc(); - jalview.bin.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) @@ -1552,11 +1759,14 @@ public class Desktop extends jalview.jbgui.GDesktop */ void showConsole(boolean selected) { - showConsole.setSelected(selected); // TODO: decide if we should update properties file - Cache.setProperty("SHOW_JAVA_CONSOLE", - Boolean.valueOf(selected).toString()); - jconsole.setVisible(selected); + if (jconsole != null) // BH 2018 + { + showConsole.setSelected(selected); + Cache.setProperty("SHOW_JAVA_CONSOLE", + Boolean.valueOf(selected).toString()); + jconsole.setVisible(selected); + } } void reorderAssociatedWindows(boolean minimize, boolean close) @@ -1645,12 +1855,12 @@ public class Desktop extends jalview.jbgui.GDesktop @Override protected void preferences_actionPerformed(ActionEvent e) { - new Preferences(); + Preferences.openPreferences(); } /** - * Shows a file chooser dialog and writes out the current session as a Jalview - * project file + * Prompts the user to choose a file and then saves the Jalview state as a + * Jalview project file */ @Override public void saveState_actionPerformed() @@ -1665,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")); @@ -1701,25 +1912,30 @@ public class Desktop extends jalview.jbgui.GDesktop setProgressBar(MessageManager.formatMessage( "label.saving_jalview_project", new Object[] { chosenFile.getName() }), chosenFile.hashCode()); - jalview.bin.Cache.setProperty("LAST_DIRECTORY", - chosenFile.getParent()); + Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent()); // TODO catch and handle errors for savestate // TODO prevent user from messing with the Desktop whilst we're saving try { - BackupFiles backupfiles = new BackupFiles(chosenFile); + boolean doBackup = BackupFiles.getEnabled(); + BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) + : null; - new Jalview2XML().saveState(backupfiles.getTempFile()); + new Jalview2XML().saveState( + doBackup ? backupfiles.getTempFile() : chosenFile); - backupfiles.setWriteSuccess(true); - backupfiles.rollBackupsAndRenameTempFile(); + if (doBackup) + { + backupfiles.setWriteSuccess(true); + backupfiles.rollBackupsAndRenameTempFile(); + } } catch (OutOfMemoryError oom) { new OOMWarning("Whilst saving current state to " + 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( @@ -1741,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; } @@ -1763,36 +1979,31 @@ public class Desktop extends jalview.jbgui.GDesktop "Jalview Project (old)" }; JalviewFileChooser chooser = new JalviewFileChooser( Cache.getProperty("LAST_DIRECTORY"), suffix, desc, - "Jalview Project", true, true); // last two booleans: allFiles, - // allowBackupFiles + "Jalview Project", true, BackupFiles.getEnabled()); // last two + // booleans: + // allFiles, + // allowBackupFiles chooser.setFileView(new JalviewFileView()); chooser.setDialogTitle(MessageManager.getString("label.restore_state")); - - int value = chooser.showOpenDialog(this); - - if (value == JalviewFileChooser.APPROVE_OPTION) - { - final File selectedFile = chooser.getSelectedFile(); + chooser.setResponseHandler(0, () -> { + File selectedFile = chooser.getSelectedFile(); setProjectFile(selectedFile); - final String choice = selectedFile.getAbsolutePath(); + String choice = selectedFile.getAbsolutePath(); Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); new Thread(new Runnable() { @Override public void run() { - setProgressBar(MessageManager.formatMessage( - "label.loading_jalview_project", new Object[] - { choice }), choice.hashCode()); try { - new Jalview2XML().loadJalviewAlign(choice); + new Jalview2XML().loadJalviewAlign(selectedFile); } catch (OutOfMemoryError oom) { new OOMWarning("Whilst loading project from " + choice, oom); } catch (Exception ex) { - Cache.log.error( + jalview.bin.Console.error( "Problems whilst loading project from " + choice, ex); JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager.formatMessage( @@ -1802,10 +2013,11 @@ public class Desktop extends jalview.jbgui.GDesktop MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE); } - setProgressBar(null, choice.hashCode()); } - }).start(); - } + }, "Project Loader").start(); + }); + + chooser.showOpenDialog(this); } @Override @@ -1818,7 +2030,7 @@ public class Desktop extends jalview.jbgui.GDesktop ArrayList fileLoadingPanels = new ArrayList<>(); - public void startLoading(final String fileName) + public void startLoading(final Object fileName) { if (fileLoadingCount == 0) { @@ -1955,468 +2167,154 @@ public class Desktop extends jalview.jbgui.GDesktop { if (afr.alignPanels != null) { - for (AlignmentPanel ap : afr.alignPanels) - { - if (sequenceSetId == null - || sequenceSetId.equals(ap.av.getSequenceSetId())) - { - viewp.add(ap.av); - } - } - } - else - { - viewp.add(afr.getViewport()); - } - } - } - if (viewp.size() > 0) - { - return viewp.toArray(new AlignmentViewport[viewp.size()]); - } - } - return null; - } - - /** - * Explode the views in the given frame into separate AlignFrame - * - * @param af - */ - public static void explodeViews(AlignFrame af) - { - int size = af.alignPanels.size(); - if (size < 2) - { - return; - } - - for (int i = 0; i < size; i++) - { - AlignmentPanel ap = af.alignPanels.get(i); - AlignFrame newaf = new AlignFrame(ap); - - /* - * 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) - { - newaf.setBounds(geometry); - } - - ap.av.setGatherViewsHere(false); - - addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, - AlignFrame.DEFAULT_HEIGHT); - } - - af.alignPanels.clear(); - af.closeMenuItem_actionPerformed(true); - - } - - /** - * Gather expanded views (separate AlignFrame's) with the same sequence set - * identifier back in to this frame as additional views, and close the - * expanded views. Note the expanded frames may themselves have multiple - * views. We take the lot. - * - * @param source - */ - public void gatherViews(AlignFrame source) - { - source.viewport.setGatherViewsHere(true); - source.viewport.setExplodedGeometry(source.getBounds()); - JInternalFrame[] frames = desktop.getAllFrames(); - String viewId = source.viewport.getSequenceSetId(); - - for (int t = 0; t < frames.length; t++) - { - if (frames[t] instanceof AlignFrame && frames[t] != source) - { - AlignFrame af = (AlignFrame) frames[t]; - boolean gatherThis = false; - for (int a = 0; a < af.alignPanels.size(); a++) - { - AlignmentPanel ap = af.alignPanels.get(a); - if (viewId.equals(ap.av.getSequenceSetId())) - { - gatherThis = true; - ap.av.setGatherViewsHere(false); - ap.av.setExplodedGeometry(af.getBounds()); - source.addAlignmentPanel(ap, false); - } - } - - if (gatherThis) - { - af.alignPanels.clear(); - af.closeMenuItem_actionPerformed(true); - } - } - } - - } - - jalview.gui.VamsasApplication v_client = null; - - @Override - public void vamsasImport_actionPerformed(ActionEvent e) - { - if (v_client == null) - { - // Load and try to start a session. - JalviewFileChooser chooser = new JalviewFileChooser( - jalview.bin.Cache.getProperty("LAST_DIRECTORY")); - - chooser.setFileView(new JalviewFileView()); - chooser.setDialogTitle( - MessageManager.getString("label.open_saved_vamsas_session")); - chooser.setToolTipText(MessageManager.getString( - "label.select_vamsas_session_opened_as_new_vamsas_session")); - - int value = chooser.showOpenDialog(this); - - if (value == JalviewFileChooser.APPROVE_OPTION) - { - String fle = chooser.getSelectedFile().toString(); - if (!vamsasImport(chooser.getSelectedFile())) - { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.formatMessage( - "label.couldnt_import_as_vamsas_session", - new Object[] - { fle }), - MessageManager - .getString("label.vamsas_document_import_failed"), - JvOptionPane.ERROR_MESSAGE); - } - } - } - else - { - jalview.bin.Cache.log.error( - "Implementation error - load session from a running session is not supported."); - } - } - - /** - * import file into a new vamsas session (uses jalview.gui.VamsasApplication) - * - * @param file - * @return true if import was a success and a session was started. - */ - public boolean vamsasImport(URL url) - { - // TODO: create progress bar - if (v_client != null) - { - - jalview.bin.Cache.log.error( - "Implementation error - load session from a running session is not supported."); - return false; - } - - try - { - // copy the URL content to a temporary local file - // TODO: be a bit cleverer here with nio (?!) - File file = File.createTempFile("vdocfromurl", ".vdj"); - FileOutputStream fos = new FileOutputStream(file); - BufferedInputStream bis = new BufferedInputStream(url.openStream()); - byte[] buffer = new byte[2048]; - int ln; - while ((ln = bis.read(buffer)) > -1) - { - fos.write(buffer, 0, ln); - } - bis.close(); - fos.close(); - v_client = new jalview.gui.VamsasApplication(this, file, - url.toExternalForm()); - } catch (Exception ex) - { - jalview.bin.Cache.log.error( - "Failed to create new vamsas session from contents of URL " - + url, - ex); - return false; - } - setupVamsasConnectedGui(); - v_client.initial_update(); // TODO: thread ? - return v_client.inSession(); - } - - /** - * import file into a new vamsas session (uses jalview.gui.VamsasApplication) - * - * @param file - * @return true if import was a success and a session was started. - */ - public boolean vamsasImport(File file) - { - if (v_client != null) - { - - jalview.bin.Cache.log.error( - "Implementation error - load session from a running session is not supported."); - return false; - } - - setProgressBar(MessageManager.formatMessage( - "status.importing_vamsas_session_from", new Object[] - { file.getName() }), file.hashCode()); - try - { - v_client = new jalview.gui.VamsasApplication(this, file, null); - } catch (Exception ex) - { - setProgressBar(MessageManager.formatMessage( - "status.importing_vamsas_session_from", new Object[] - { file.getName() }), file.hashCode()); - jalview.bin.Cache.log.error( - "New vamsas session from existing session file failed:", ex); - return false; - } - setupVamsasConnectedGui(); - v_client.initial_update(); // TODO: thread ? - setProgressBar(MessageManager.formatMessage( - "status.importing_vamsas_session_from", new Object[] - { file.getName() }), file.hashCode()); - return v_client.inSession(); - } - - public boolean joinVamsasSession(String mysesid) - { - if (v_client != null) - { - throw new Error(MessageManager - .getString("error.try_join_vamsas_session_another")); - } - if (mysesid == null) - { - throw new Error( - MessageManager.getString("error.invalid_vamsas_session_id")); - } - v_client = new VamsasApplication(this, mysesid); - setupVamsasConnectedGui(); - v_client.initial_update(); - return (v_client.inSession()); - } - - @Override - public void vamsasStart_actionPerformed(ActionEvent e) - { - if (v_client == null) - { - // Start a session. - // we just start a default session for moment. - /* - * JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache. - * getProperty("LAST_DIRECTORY")); - * - * chooser.setFileView(new JalviewFileView()); - * chooser.setDialogTitle("Load Vamsas file"); - * chooser.setToolTipText("Import"); - * - * int value = chooser.showOpenDialog(this); - * - * if (value == JalviewFileChooser.APPROVE_OPTION) { v_client = new - * jalview.gui.VamsasApplication(this, chooser.getSelectedFile()); - */ - v_client = new VamsasApplication(this); - setupVamsasConnectedGui(); - v_client.initial_update(); // TODO: thread ? - } - else - { - // store current data in session. - v_client.push_update(); // TODO: thread - } - } - - protected void setupVamsasConnectedGui() - { - vamsasStart.setText(MessageManager.getString("label.session_update")); - vamsasSave.setVisible(true); - vamsasStop.setVisible(true); - vamsasImport.setVisible(false); // Document import to existing session is - // not possible for vamsas-client-1.0. - } - - protected void setupVamsasDisconnectedGui() - { - vamsasSave.setVisible(false); - vamsasStop.setVisible(false); - vamsasImport.setVisible(true); - vamsasStart - .setText(MessageManager.getString("label.new_vamsas_session")); - } - - @Override - public void vamsasStop_actionPerformed(ActionEvent e) - { - if (v_client != null) - { - v_client.end_session(); - v_client = null; - setupVamsasDisconnectedGui(); - } - } - - protected void buildVamsasStMenu() - { - if (v_client == null) - { - String[] sess = null; - try - { - sess = VamsasApplication.getSessionList(); - } catch (Exception e) - { - jalview.bin.Cache.log.warn("Problem getting current sessions list.", - e); - sess = null; - } - if (sess != null) - { - jalview.bin.Cache.log.debug( - "Got current sessions list: " + sess.length + " entries."); - VamsasStMenu.removeAll(); - for (int i = 0; i < sess.length; i++) - { - JMenuItem sessit = new JMenuItem(); - sessit.setText(sess[i]); - sessit.setToolTipText(MessageManager - .formatMessage("label.connect_to_session", new Object[] - { sess[i] })); - final Desktop dsktp = this; - final String mysesid = sess[i]; - sessit.addActionListener(new ActionListener() - { - - @Override - public void actionPerformed(ActionEvent e) + for (AlignmentPanel ap : afr.alignPanels) { - if (dsktp.v_client == null) + if (sequenceSetId == null + || sequenceSetId.equals(ap.av.getSequenceSetId())) { - Thread rthr = new Thread(new Runnable() - { - - @Override - public void run() - { - dsktp.v_client = new VamsasApplication(dsktp, mysesid); - dsktp.setupVamsasConnectedGui(); - dsktp.v_client.initial_update(); - } - - }); - rthr.start(); + viewp.add(ap.av); } - }; - }); - VamsasStMenu.add(sessit); + } + } + else + { + viewp.add(afr.getViewport()); + } } - // don't show an empty menu. - VamsasStMenu.setVisible(sess.length > 0); - } - else + if (viewp.size() > 0) { - jalview.bin.Cache.log.debug("No current vamsas sessions."); - VamsasStMenu.removeAll(); - VamsasStMenu.setVisible(false); + return viewp.toArray(new AlignmentViewport[viewp.size()]); } } - else - { - // Not interested in the content. Just hide ourselves. - VamsasStMenu.setVisible(false); - } + return null; } - @Override - public void vamsasSave_actionPerformed(ActionEvent e) + /** + * Explode the views in the given frame into separate AlignFrame + * + * @param af + */ + public static void explodeViews(AlignFrame af) { - if (v_client != null) + int size = af.alignPanels.size(); + if (size < 2) { - // TODO: VAMSAS DOCUMENT EXTENSION is VDJ - JalviewFileChooser chooser = new JalviewFileChooser("vdj", - "Vamsas Document"); + return; + } - chooser.setFileView(new JalviewFileView()); - chooser.setDialogTitle(MessageManager - .getString("label.save_vamsas_document_archive")); + // FIXME: ideally should use UI interface API + FeatureSettings viewFeatureSettings = (af.featureSettings != null + && af.featureSettings.isOpen()) ? af.featureSettings : null; + Rectangle fsBounds = af.getFeatureSettingsGeometry(); + for (int i = 0; i < size; i++) + { + AlignmentPanel ap = af.alignPanels.get(i); - int value = chooser.showSaveDialog(this); + AlignFrame newaf = new AlignFrame(ap); - if (value == JalviewFileChooser.APPROVE_OPTION) + // transfer reference for existing feature settings to new alignFrame + if (ap == af.alignPanel) { - java.io.File choice = chooser.getSelectedFile(); - JPanel progpanel = addProgressPanel(MessageManager - .formatMessage("label.saving_vamsas_doc", new Object[] - { choice.getName() })); - Cache.setProperty("LAST_DIRECTORY", choice.getParent()); - String warnmsg = null; - String warnttl = null; - try + if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) { - v_client.vclient.storeDocument(choice); - } catch (Error ex) - { - warnttl = "Serious Problem saving Vamsas Document"; - warnmsg = ex.toString(); - jalview.bin.Cache.log - .error("Error Whilst saving document to " + choice, ex); + newaf.featureSettings = viewFeatureSettings; + } + newaf.setFeatureSettingsGeometry(fsBounds); + } - } catch (Exception ex) - { - warnttl = "Problem saving Vamsas Document."; - warnmsg = ex.toString(); - jalview.bin.Cache.log.warn( - "Exception Whilst saving document to " + choice, ex); + /* + * 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) + { + newaf.setBounds(geometry); + } - } - removeProgressPanel(progpanel); - if (warnmsg != null) - { - JvOptionPane.showInternalMessageDialog(Desktop.desktop, + ap.av.setGatherViewsHere(false); - warnmsg, warnttl, JvOptionPane.ERROR_MESSAGE); - } + addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, + AlignFrame.DEFAULT_HEIGHT); + // and materialise a new feature settings dialog instance for the new + // alignframe + // (closes the old as if 'OK' was pressed) + if (ap == af.alignPanel && newaf.featureSettings != null + && newaf.featureSettings.isOpen() + && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) + { + newaf.showFeatureSettingsUI(); } } - } - JPanel vamUpdate = null; + af.featureSettings = null; + af.alignPanels.clear(); + af.closeMenuItem_actionPerformed(true); + + } /** - * hide vamsas user gui bits when a vamsas document event is being handled. + * Gather expanded views (separate AlignFrame's) with the same sequence set + * identifier back in to this frame as additional views, and close the + * expanded views. Note the expanded frames may themselves have multiple + * views. We take the lot. * - * @param b - * true to hide gui, false to reveal gui + * @param source */ - public void setVamsasUpdate(boolean b) + public void gatherViews(AlignFrame source) { - Cache.log.debug("Setting gui for Vamsas update " - + (b ? "in progress" : "finished")); - - if (vamUpdate != null) + source.viewport.setGatherViewsHere(true); + source.viewport.setExplodedGeometry(source.getBounds()); + JInternalFrame[] frames = desktop.getAllFrames(); + String viewId = source.viewport.getSequenceSetId(); + for (int t = 0; t < frames.length; t++) { - this.removeProgressPanel(vamUpdate); + if (frames[t] instanceof AlignFrame && frames[t] != source) + { + AlignFrame af = (AlignFrame) frames[t]; + boolean gatherThis = false; + for (int a = 0; a < af.alignPanels.size(); a++) + { + AlignmentPanel ap = af.alignPanels.get(a); + if (viewId.equals(ap.av.getSequenceSetId())) + { + gatherThis = true; + ap.av.setGatherViewsHere(false); + ap.av.setExplodedGeometry(af.getBounds()); + source.addAlignmentPanel(ap, false); + } + } + + if (gatherThis) + { + if (af.featureSettings != null && af.featureSettings.isOpen()) + { + if (source.featureSettings == null) + { + // preserve the feature settings geometry for this frame + source.featureSettings = af.featureSettings; + source.setFeatureSettingsGeometry( + af.getFeatureSettingsGeometry()); + } + else + { + // close it and forget + af.featureSettings.close(); + } + } + af.alignPanels.clear(); + af.closeMenuItem_actionPerformed(true); + } + } } - if (b) + + // refresh the feature setting UI for the source frame if it exists + if (source.featureSettings != null && source.featureSettings.isOpen()) { - vamUpdate = this.addProgressPanel( - MessageManager.getString("label.updating_vamsas_session")); + source.showFeatureSettingsUI(); } - vamsasStart.setVisible(!b); - vamsasStop.setVisible(!b); - vamsasSave.setVisible(!b); + } public JInternalFrame[] getAllFrames() @@ -2525,7 +2423,6 @@ public class Desktop extends jalview.jbgui.GDesktop */ public class MyDesktopPane extends JDesktopPane implements Runnable { - private static final float ONE_MB = 1048576f; boolean showMemoryUsage = false; @@ -2611,6 +2508,9 @@ public class Desktop extends jalview.jbgui.GDesktop 10, getHeight() - fm.getHeight()); } } + + // output debug scale message. Important for jalview.bin.HiDPISettingTest2 + Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics()); } } @@ -2707,7 +2607,7 @@ public class Desktop extends jalview.jbgui.GDesktop openGroovyConsole(); } catch (Exception ex) { - jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex); + jalview.bin.Console.error("Groovy Shell Creation failed.", ex); JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager.getString("label.couldnt_create_groovy_shell"), @@ -2729,9 +2629,9 @@ public class Desktop extends jalview.jbgui.GDesktop /* * 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() @@ -2754,8 +2654,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); } @@ -2766,16 +2666,19 @@ public class Desktop extends jalview.jbgui.GDesktop */ protected void addQuitHandler() { - getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) - .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, - jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()), + getRootPane() + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( + KeyStroke + .getKeyStroke(KeyEvent.VK_Q, + jalview.util.ShortcutKeyMaskExWrapper + .getMenuShortcutKeyMaskEx()), "Quit"); getRootPane().getActionMap().put("Quit", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { - quit(); + desktopQuit(); } }); } @@ -2789,8 +2692,8 @@ 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); @@ -2921,7 +2824,8 @@ public class Desktop extends jalview.jbgui.GDesktop public VamsasApplication getVamsasApplication() { - return v_client; + // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS + return null; } @@ -2950,13 +2854,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 @@ -2974,7 +2901,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); @@ -3027,24 +2955,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(); */ @@ -3066,7 +2989,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); } @@ -3159,15 +3082,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. } @@ -3178,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 @@ -3188,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; @@ -3206,13 +3122,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (dialogPause) { - try - { - block.acquire(); - } catch (InterruptedException x) - { - } - ; + acquireDialogQueue(); } if (instance == null) { @@ -3223,42 +3133,91 @@ 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; } + /** + * Outputs an image of the desktop to file in EPS format, after prompting the + * user for choice of Text or Lineart character rendering (unless a preference + * has been set). The file name is generated as + * + *
+   * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
+   * 
+ */ @Override protected void snapShotWindow_actionPerformed(ActionEvent e) { + // currently the menu option to do this is not shown invalidate(); - File of; - ImageMaker im = new jalview.util.ImageMaker( - this, ImageMaker.TYPE.EPS, "View of Desktop", getWidth(), - getHeight(), of = new File("Jalview_snapshot" - + System.currentTimeMillis() + ".eps"), - "View of desktop", null, 0, false); + + int width = getWidth(); + int height = getHeight(); + File of = new File( + "Jalview_snapshot_" + System.currentTimeMillis() + ".eps"); + ImageWriterI writer = new ImageWriterI() + { + @Override + public void exportImage(Graphics g) throws Exception + { + paintAll(g); + 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); try { - paintAll(im.getGraphics()); - im.writeImage(); - } catch (Exception q) + exporter.doExport(of, this, width, height, title); + } catch (ImageOutputException ioex) { - Cache.log.error("Couldn't write snapshot to " + of.getAbsolutePath(), - q); - return; + jalview.bin.Console.error( + "Unexpected error whilst writing Jalview desktop snapshot as EPS", + ioex); } - Cache.log.info("Successfully written snapshot to file " - + of.getAbsolutePath()); } /** @@ -3285,16 +3244,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 @@ -3333,8 +3291,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(); @@ -3430,7 +3388,7 @@ public class Desktop extends jalview.jbgui.GDesktop * - the payload from the drop event * @throws Exception */ - public static void transferFromDropTarget(List files, + public static void transferFromDropTarget(List files, List protocols, DropTargetDropEvent evt, Transferable t) throws Exception { @@ -3443,7 +3401,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)) @@ -3458,32 +3417,31 @@ 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; } else { - if (Platform.isAMac()) + 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)) { - files.add(((File) file).toString()); + files.add(file); protocols.add(DataSourceType.FILE); } } @@ -3494,14 +3452,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"); @@ -3510,7 +3469,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)); } @@ -3518,7 +3477,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); } @@ -3533,7 +3492,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()); @@ -3548,7 +3507,7 @@ public class Desktop extends jalview.jbgui.GDesktop } } - if (Cache.log.isDebugEnabled()) + if (jalview.bin.Console.isDebugEnabled()) { if (data == null || !added) { @@ -3556,58 +3515,61 @@ 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.isWindows()) - + 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).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"))) { try { - File lf = new File(files.get(f)); + Object obj = files.get(f); + 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); } @@ -3662,4 +3624,182 @@ public class Desktop extends jalview.jbgui.GDesktop } return result; } + + public static final String debugScaleMessage = "Desktop graphics transform scale="; + + private static boolean debugScaleMessageDone = false; + + public static void debugScaleMessage(Graphics g) + { + if (debugScaleMessageDone) + { + return; + } + // output used by tests to check HiDPI scaling settings in action + try + { + Graphics2D gg = (Graphics2D) g; + if (gg != null) + { + AffineTransform t = gg.getTransform(); + double scaleX = t.getScaleX(); + double scaleY = t.getScaleY(); + jalview.bin.Console.debug(debugScaleMessage + scaleX + " (X)"); + jalview.bin.Console.debug(debugScaleMessage + scaleY + " (Y)"); + debugScaleMessageDone = true; + } + else + { + jalview.bin.Console.debug("Desktop graphics null"); + } + } catch (Exception e) + { + 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); + } + }