JAL-3949 - refactor logging from jalview.bin.Cache to jalview.bin.Console
[jalview.git] / src / jalview / gui / Desktop.java
index 1e258b8..ad36492 100644 (file)
  */
 package jalview.gui;
 
-import static jalview.util.UrlConstants.SEQUENCE_ID;
-
-import jalview.api.AlignViewportI;
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.bin.Jalview;
-import jalview.gui.ImageExporter.ImageWriterI;
-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.structure.StructureSelectionManager;
-import jalview.urls.IdOrgSettings;
-import jalview.util.BrowserLauncher;
-import jalview.util.ImageMaker.TYPE;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.util.UrlConstants;
-import jalview.util.dialogrunner.RunResponse;
-import jalview.viewmodel.AlignmentViewport;
-import jalview.ws.params.ParamManager;
-import jalview.ws.utils.UrlDownloadClient;
+import java.util.Locale;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
 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;
@@ -78,18 +50,20 @@ 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.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.Vector;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -108,7 +82,6 @@ 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;
@@ -122,11 +95,41 @@ 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.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.gui.ImageExporter.ImageWriterI;
+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.BrowserLauncher;
+import jalview.util.ChannelProperties;
+import jalview.util.ImageMaker.TYPE;
+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,9 +138,31 @@ import org.stackoverflowusers.file.WindowsShortcut;
  * @version $Revision: 1.155 $
  */
 public class Desktop extends jalview.jbgui.GDesktop
-        implements DropTargetListener, ClipboardOwner, IProgressIndicator,
-        jalview.api.StructureSelectionManagerProvider
-{
+    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("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
+    if (logo) {
+      sb.append("<br>");
+    }
+    sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
+    sb.append(uod_logo_url == null ? ""
+        : "&nbsp;<img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
+    sb.append(
+        "<br><br>For help, see the FAQ at <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list");
+    sb.append("<br><br>If  you use Jalview, please cite:"
+        + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
+        + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
+        + "<br>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;
@@ -148,8 +173,14 @@ 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 HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
+
   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
 
+  public static boolean nosplash = false;
+
   /**
    * news reader - null if it was never started.
    */
@@ -161,9 +192,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @param listener
    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
    */
-  public void addJalviewPropertyChangeListener(
-          PropertyChangeListener listener)
-  {
+  public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
     changeSupport.addJalviewPropertyChangeListener(listener);
   }
 
@@ -173,9 +202,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
    *      java.beans.PropertyChangeListener)
    */
-  public void addJalviewPropertyChangeListener(String propertyName,
-          PropertyChangeListener listener)
-  {
+  public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
     changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
   }
 
@@ -185,11 +212,8 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
    *      java.beans.PropertyChangeListener)
    */
-  public void removeJalviewPropertyChangeListener(String propertyName,
-          PropertyChangeListener listener)
-  {
-    changeSupport.removeJalviewPropertyChangeListener(propertyName,
-            listener);
+  public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+    changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
   }
 
   /** Singleton Desktop instance */
@@ -197,8 +221,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public static MyDesktopPane desktop;
 
-  public static MyDesktopPane getDesktop()
-  {
+  public static MyDesktopPane getDesktop() {
     // BH 2018 could use currentThread() here as a reference to a
     // Hashtable<Thread, MyDesktopPane> in JavaScript
     return desktop;
@@ -218,122 +241,99 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   static int fileLoadingCount = 0;
 
-  class MyDesktopManager implements DesktopManager
-  {
+  class MyDesktopManager implements DesktopManager {
 
     private DesktopManager delegate;
 
-    public MyDesktopManager(DesktopManager delegate)
-    {
+    public MyDesktopManager(DesktopManager delegate) {
       this.delegate = delegate;
     }
 
     @Override
-    public void activateFrame(JInternalFrame f)
-    {
-      try
-      {
+    public void activateFrame(JInternalFrame f) {
+      try {
         delegate.activateFrame(f);
-      } catch (NullPointerException npe)
-      {
+      } catch (NullPointerException npe) {
         Point p = getMousePosition();
         instance.showPasteMenu(p.x, p.y);
       }
     }
 
     @Override
-    public void beginDraggingFrame(JComponent f)
-    {
+    public void beginDraggingFrame(JComponent f) {
       delegate.beginDraggingFrame(f);
     }
 
     @Override
-    public void beginResizingFrame(JComponent f, int direction)
-    {
+    public void beginResizingFrame(JComponent f, int direction) {
       delegate.beginResizingFrame(f, direction);
     }
 
     @Override
-    public void closeFrame(JInternalFrame f)
-    {
+    public void closeFrame(JInternalFrame f) {
       delegate.closeFrame(f);
     }
 
     @Override
-    public void deactivateFrame(JInternalFrame f)
-    {
+    public void deactivateFrame(JInternalFrame f) {
       delegate.deactivateFrame(f);
     }
 
     @Override
-    public void deiconifyFrame(JInternalFrame f)
-    {
+    public void deiconifyFrame(JInternalFrame f) {
       delegate.deiconifyFrame(f);
     }
 
     @Override
-    public void dragFrame(JComponent f, int newX, int newY)
-    {
-      if (newY < 0)
-      {
+    public void dragFrame(JComponent f, int newX, int newY) {
+      if (newY < 0) {
         newY = 0;
       }
       delegate.dragFrame(f, newX, newY);
     }
 
     @Override
-    public void endDraggingFrame(JComponent f)
-    {
+    public void endDraggingFrame(JComponent f) {
       delegate.endDraggingFrame(f);
       desktop.repaint();
     }
 
     @Override
-    public void endResizingFrame(JComponent f)
-    {
+    public void endResizingFrame(JComponent f) {
       delegate.endResizingFrame(f);
       desktop.repaint();
     }
 
     @Override
-    public void iconifyFrame(JInternalFrame f)
-    {
+    public void iconifyFrame(JInternalFrame f) {
       delegate.iconifyFrame(f);
     }
 
     @Override
-    public void maximizeFrame(JInternalFrame f)
-    {
+    public void maximizeFrame(JInternalFrame f) {
       delegate.maximizeFrame(f);
     }
 
     @Override
-    public void minimizeFrame(JInternalFrame f)
-    {
+    public void minimizeFrame(JInternalFrame f) {
       delegate.minimizeFrame(f);
     }
 
     @Override
-    public void openFrame(JInternalFrame f)
-    {
+    public void openFrame(JInternalFrame f) {
       delegate.openFrame(f);
     }
 
     @Override
-    public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
-            int newHeight)
-    {
-      if (newY < 0)
-      {
+    public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
+      if (newY < 0) {
         newY = 0;
       }
       delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
     }
 
     @Override
-    public void setBoundsForFrame(JComponent f, int newX, int newY,
-            int newWidth, int newHeight)
-    {
+    public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
       delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
     }
 
@@ -344,87 +344,123 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * Creates a new Desktop object.
    */
-  public Desktop()
-  {
+  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;
-    if (!Jalview.isJS())
-    {
-      doVamsasClientCheck();
-    }
 
     doConfigureStructurePrefs();
-    setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
-    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-    boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
-            false);
-    boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
-            false);
+    setTitle(ChannelProperties.getProperty("app_name") + " " + 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.
+     */
+    if (Platform.isLinux()) {
+      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));
+      }
+    }
+
+    /**
+     * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
+     * macOS's application menu. APQHandlers will check to see if a handler is
+     * supported before setting it.
+     */
+    try {
+      APQHandlers.setAPQHandlers(this);
+    } catch (Exception e) {
+      System.out.println("Cannot set APQHandlers");
+      // e.printStackTrace();
+    } catch (Throwable t) {
+      jalview.bin.Console.warn("Error setting APQHandlers: " + t.toString());
+      jalview.bin.Console.trace(Cache.getStackTraceString(t));
+    }
+    setIconImages(ChannelProperties.getIconList());
+
+    addWindowListener(new WindowAdapter() {
+
+      @Override
+      public void windowClosing(WindowEvent ev) {
+        quit();
+      }
+    });
+
+    boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
+
+    boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
     desktop = new MyDesktopPane(selmemusage);
-    
-    
+
     showMemusage.setSelected(selmemusage);
     desktop.setBackground(Color.white);
+
     getContentPane().setLayout(new BorderLayout());
     // alternate config - have scrollbars - see notes in JAL-153
     // JScrollPane sp = new JScrollPane();
     // sp.getViewport().setView(desktop);
     // getContentPane().add(sp, BorderLayout.CENTER);
-    
-    // BH 2018 - just an experiment to try unclipped JInternalFrames. 
-    // Must set for all three to be active:
-       if (Jalview.isJS()) 
-       {
-         getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
-         ((JComponent) getContentPane()).putClientProperty("swingjs.overflow.hidden", "false");
-         desktop.putClientProperty("swingjs.overflow.hidden", "false");
-       }
-    
+
+    // 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);
-    
+
     // 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())));
+    desktop.setDesktopManager(new MyDesktopManager((Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
+        : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
+            : desktop.getDesktopManager())));
 
     Rectangle dims = getLastKnownDimensions("");
-    if (dims != null)
-    {
+    if (dims != null) {
       setBounds(dims);
-    }
-    else
-    {
+    } else {
       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
       int xPos = Math.max(5, (screenSize.width - 900) / 2);
       int yPos = Math.max(5, (screenSize.height - 650) / 2);
       setBounds(xPos, yPos, 900, 650);
     }
-    
-    boolean doFullLoad = /** @j2sNative ! */true;
-    
-    if (doFullLoad) {
-      
-      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"));
 
+    if (!Platform.isJS())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
+    {
+      jconsole = new Console(this, showjconsole);
+      jconsole.setHeader(Cache.getVersionDetailsForConsole());
       showConsole(showjconsole);
 
       showNews.setVisible(false);
@@ -436,64 +472,51 @@ public class Desktop extends jalview.jbgui.GDesktop
       checkURLLinks();
 
       // Spawn a thread that shows the splashscreen
+      if (!nosplash) {
+        SwingUtilities.invokeLater(new Runnable() {
+          @Override
+          public void run() {
+            new SplashScreen(true);
+          }
+        });
+      }
 
-      SwingUtilities.invokeLater(new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          new SplashScreen();
-        }
-      });
-
-      // Thread off a new instance of the file chooser - this reduces the time it
+      // Thread off a new instance of the file chooser - this reduces the time
+      // it
       // takes to open it later on.
-      new Thread(new Runnable()
-      {
+      new Thread(new Runnable() {
         @Override
-        public void run()
-        {
-          Cache.log.debug("Filechooser init thread started.");
+        public void run() {
+          jalview.bin.Console.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.");
+          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()
-              {
+      changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
 
-                @Override
-                public void propertyChange(PropertyChangeEvent evt)
-                {
-                  Cache.log.debug("Firing service changed event for "
-                          + evt.getNewValue());
-                  JalviewServicesChanged(evt);
-                }
-
-              });
-
-    } 
+        @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));
 
-    this.addWindowListener(new WindowAdapter()
-    {
+    this.addWindowListener(new WindowAdapter() {
       @Override
-      public void windowClosing(WindowEvent evt)
-      {
+      public void windowClosing(WindowEvent evt) {
         quit();
       }
     });
 
     MouseAdapter ma;
-    this.addMouseListener(ma = new MouseAdapter()
-    {
+    this.addMouseListener(ma = new MouseAdapter() {
       @Override
-      public void mousePressed(MouseEvent evt)
-      {
+      public void mousePressed(MouseEvent evt) {
         if (evt.isPopupTrigger()) // Mac
         {
           showPasteMenu(evt.getX(), evt.getY());
@@ -501,8 +524,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
 
       @Override
-      public void mouseReleased(MouseEvent evt)
-      {
+      public void mouseReleased(MouseEvent evt) {
         if (evt.isPopupTrigger()) // Windows
         {
           showPasteMenu(evt.getX(), evt.getY());
@@ -510,7 +532,6 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     });
     desktop.addMouseListener(ma);
-
   }
 
   /**
@@ -519,96 +540,71 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @return
    */
-  public boolean showExperimental()
-  {
-    String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
-            Boolean.FALSE.toString());
+  public boolean showExperimental() {
+    String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
     return Boolean.valueOf(experimental).booleanValue();
   }
 
-  public void doConfigureStructurePrefs()
-  {
+  public void doConfigureStructurePrefs() {
     // configure services
-    StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(this);
-    if (jalview.bin.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.setSecStructServices(
-              jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, true));
-    }
-    else
-    {
+    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
+    if (Cache.getDefault(Preferences.ADD_SS_ANN, 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(Cache.getDefault(Preferences.USE_RNAVIEW, false));
+    } else {
       ssm.setAddTempFacAnnot(false);
       ssm.setProcessSecondaryStructure(false);
       ssm.setSecStructServices(false);
     }
   }
 
-  public void checkForNews()
-  {
+  public void checkForNews() {
     final Desktop me = this;
     // Thread off the news reader, in case there are connection problems.
-    new Thread(new Runnable()
-    {
+    new Thread(new Runnable() {
       @Override
-      public void run()
-      {
-        Cache.log.debug("Starting news thread.");
+      public void run() {
+        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()
-      {
-        Cache.log.debug("Downloading data from identifiers.org");
-        UrlDownloadClient client = new UrlDownloadClient();
-        try
-        {
-          client.download(IdOrgSettings.getUrl(),
-                  IdOrgSettings.getDownloadLocation());
-        } catch (IOException e)
-        {
-          Cache.log.debug("Exception downloading identifiers.org data"
-                  + e.getMessage());
+  public void getIdentifiersOrgData() {
+    if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
+      // Thread off the identifiers fetcher
+      new Thread(new Runnable() {
+        @Override
+        public void run() {
+          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
-  protected void showNews_actionPerformed(ActionEvent e)
-  {
+  protected void showNews_actionPerformed(ActionEvent e) {
     showNews(showNews.isSelected());
   }
 
-  void showNews(boolean visible)
-  {
-    Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
+  void showNews(boolean visible) {
+    jalview.bin.Console.debug((visible ? "Showing" : "Hiding") + " news.");
     showNews.setSelected(visible);
-    if (visible && !jvnews.isVisible())
-    {
-      new Thread(new Runnable()
-      {
+    if (visible && !jvnews.isVisible()) {
+      new Thread(new Runnable() {
         @Override
-        public void run()
-        {
+        public void run() {
           long now = System.currentTimeMillis();
-          Desktop.instance.setProgressBar(
-                  MessageManager.getString("status.refreshing_news"), now);
+          Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
           jvnews.refreshNews();
           Desktop.instance.setProgressBar(null, now);
           jvnews.showNews();
@@ -620,54 +616,42 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * recover the last known dimensions for a jalview window
    * 
-   * @param windowName
-   *          - empty string is desktop, all other windows have unique prefix
+   * @param windowName - empty string is desktop, all other windows have unique
+   *                   prefix
    * @return null or last known dimensions scaled to current geometry (if last
    *         window geom was known)
    */
-  Rectangle getLastKnownDimensions(String windowName)
-  {
+  Rectangle getLastKnownDimensions(String windowName) {
     // 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");
-    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)
-      {
+    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 (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);
         iy = (int) (iy * sh);
         ih = (int) (ih * sh);
-        while (ix >= screenSize.width)
-        {
-          jalview.bin.Cache.log.debug(
-                  "Window geometry location recall error: shifting horizontal to within screenbounds.");
+        while (ix >= screenSize.width) {
+          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(
-                  "Window geometry location recall error: shifting vertical to within screenbounds.");
+        while (iy >= screenSize.height) {
+          jalview.bin.Console.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
           iy -= screenSize.height;
         }
-        jalview.bin.Cache.log.debug(
-                "Got last known dimensions for " + windowName + ": x:" + ix
-                        + " y:" + iy + " width:" + iw + " height:" + ih);
+        jalview.bin.Console.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
+            + " height:" + ih);
       }
       // return dimensions for new instance
       return new Rectangle(ix, iy, iw, ih);
@@ -675,56 +659,12 @@ public class Desktop extends jalview.jbgui.GDesktop
     return null;
   }
 
-  private void doVamsasClientCheck()
-  {
-    if (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)
-  {
+  void showPasteMenu(int x, int y) {
     JPopupMenu popup = new JPopupMenu();
-    JMenuItem item = new JMenuItem(
-            MessageManager.getString("label.paste_new_window"));
-    item.addActionListener(new ActionListener()
-    {
+    JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
+    item.addActionListener(new ActionListener() {
       @Override
-      public void actionPerformed(ActionEvent evt)
-      {
+      public void actionPerformed(ActionEvent evt) {
         paste();
       }
     });
@@ -733,115 +673,79 @@ public class Desktop extends jalview.jbgui.GDesktop
     popup.show(this, x, y);
   }
 
-  public void paste()
-  {
-    try
-    {
+  public void paste() {
+    try {
       Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
       Transferable contents = c.getContents(this);
 
-      if (contents != null)
-      {
-        String file = (String) contents
-                .getTransferData(DataFlavor.stringFlavor);
+      if (contents != null) {
+        String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
 
-        FileFormatI format = new IdentifyFile().identify(file,
-                DataSourceType.PASTE);
+        FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
 
         new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
 
       }
-    } catch (Exception ex)
-    {
-      System.out.println(
-              "Unable to paste alignment from system clipboard:\n" + ex);
+    } catch (Exception ex) {
+      System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
     }
   }
 
   /**
    * Adds and opens the given frame to the desktop
    * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param w
-   *          width
-   * @param h
-   *          height
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, int w, int h)
-  {
+   * @param frame Frame to show
+   * @param title Visible Title
+   * @param w     width
+   * @param h     height
+   */
+  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
     addInternalFrame(frame, title, true, w, h, true, false);
   }
 
   /**
    * Add an internal frame to the Jalview desktop
    * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param makeVisible
-   *          When true, display frame immediately, otherwise, caller must call
-   *          setVisible themselves.
-   * @param w
-   *          width
-   * @param h
-   *          height
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, boolean makeVisible,
-          int w, int h)
-  {
+   * @param frame       Frame to show
+   * @param title       Visible Title
+   * @param makeVisible When true, display frame immediately, otherwise, caller
+   *                    must call setVisible themselves.
+   * @param w           width
+   * @param h           height
+   */
+  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
+      int h) {
     addInternalFrame(frame, title, makeVisible, w, h, true, false);
   }
 
   /**
    * Add an internal frame to the Jalview desktop and make it visible
    * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param w
-   *          width
-   * @param h
-   *          height
-   * @param resizable
-   *          Allow resize
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, int w, int h,
-          boolean resizable)
-  {
+   * @param frame     Frame to show
+   * @param title     Visible Title
+   * @param w         width
+   * @param h         height
+   * @param resizable Allow resize
+   */
+  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
+      boolean resizable) {
     addInternalFrame(frame, title, true, w, h, resizable, false);
   }
 
   /**
    * Add an internal frame to the Jalview desktop
    * 
-   * @param frame
-   *          Frame to show
-   * @param title
-   *          Visible Title
-   * @param makeVisible
-   *          When true, display frame immediately, otherwise, caller must call
-   *          setVisible themselves.
-   * @param w
-   *          width
-   * @param h
-   *          height
-   * @param resizable
-   *          Allow resize
-   * @param ignoreMinSize
-   *          Do not set the default minimum size for frame
-   */
-  public static synchronized void addInternalFrame(
-          final JInternalFrame frame, String title, boolean makeVisible,
-          int w, int h, boolean resizable, boolean ignoreMinSize)
-  {
+   * @param frame         Frame to show
+   * @param title         Visible Title
+   * @param makeVisible   When true, display frame immediately, otherwise, caller
+   *                      must call setVisible themselves.
+   * @param w             width
+   * @param h             height
+   * @param resizable     Allow resize
+   * @param ignoreMinSize Do not set the default minimum size for frame
+   */
+  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
+      int h, boolean resizable, boolean ignoreMinSize) {
 
     // TODO: allow callers to determine X and Y position of frame (eg. via
     // bounds object).
@@ -849,8 +753,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // the current window title
 
     frame.setTitle(title);
-    if (frame.getWidth() < 1 || frame.getHeight() < 1)
-    {
+    if (frame.getWidth() < 1 || frame.getHeight() < 1) {
       frame.setSize(w, h);
     }
     // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
@@ -858,26 +761,21 @@ public class Desktop extends jalview.jbgui.GDesktop
     // IF JALVIEW IS RUNNING HEADLESS
     // ///////////////////////////////////////////////
     if (instance == null || (System.getProperty("java.awt.headless") != null
-            && System.getProperty("java.awt.headless").equals("true")))
-    {
+        && System.getProperty("java.awt.headless").equals("true"))) {
       return;
     }
 
     openFrameCount++;
 
-    if (!ignoreMinSize)
-    {
-      frame.setMinimumSize(
-              new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
+    if (!ignoreMinSize) {
+      frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
 
       // Set default dimension for Alignment Frame window.
       // The Alignment Frame window could be added from a number of places,
       // hence,
       // I did this here in order not to miss out on any Alignment frame.
-      if (frame instanceof AlignFrame)
-      {
-        frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
-                ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
+      if (frame instanceof AlignFrame) {
+        frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
       }
     }
 
@@ -886,30 +784,23 @@ public class Desktop extends jalview.jbgui.GDesktop
     frame.setResizable(resizable);
     frame.setMaximizable(resizable);
     frame.setIconifiable(resizable);
-    frame.setOpaque(/** @j2sNative true || */
-            false);
+    frame.setOpaque(Platform.isJS());
 
-    if (frame.getX() < 1 && frame.getY() < 1)
-    {
-      frame.setLocation(xOffset * openFrameCount,
-              yOffset * ((openFrameCount - 1) % 10) + yOffset);
+    if (frame.getX() < 1 && frame.getY() < 1) {
+      frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
     }
 
     /*
-     * 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()
-    {
+    frame.addInternalFrameListener(new InternalFrameAdapter() {
       @Override
-      public void internalFrameActivated(InternalFrameEvent evt)
-      {
+      public void internalFrameActivated(InternalFrameEvent evt) {
         JInternalFrame itf = desktop.getSelectedFrame();
-        if (itf != null)
-        {
-          if (itf instanceof AlignFrame)
-          {
+        if (itf != null) {
+          if (itf instanceof AlignFrame) {
             Jalview.setCurrentAlignFrame((AlignFrame) itf);
           }
           itf.requestFocus();
@@ -917,42 +808,34 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
 
       @Override
-      public void internalFrameClosed(InternalFrameEvent evt)
-      {
+      public void internalFrameClosed(InternalFrameEvent evt) {
         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)
-        {
+        if (openFrameCount > 0) {
           openFrameCount--;
         }
 
         /*
          * ensure no reference to alignFrame retained by menu item listener
          */
-        if (menuItem.getActionListeners().length > 0)
-        {
+        if (menuItem.getActionListeners().length > 0) {
           menuItem.removeActionListener(menuItem.getActionListeners()[0]);
         }
         windowMenu.remove(menuItem);
-      };
+      }
     });
 
-    menuItem.addActionListener(new ActionListener()
-    {
+    menuItem.addActionListener(new ActionListener() {
       @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        try
-        {
+      public void actionPerformed(ActionEvent e) {
+        try {
           frame.setSelected(true);
           frame.setIcon(false);
-        } catch (java.beans.PropertyVetoException ex)
-        {
-          // System.err.println(ex.toString());
+        } catch (java.beans.PropertyVetoException ex) {
+
         }
       }
     });
@@ -964,17 +847,14 @@ public class Desktop extends jalview.jbgui.GDesktop
     windowMenu.add(menuItem);
 
     frame.toFront();
-    try
-    {
+    try {
       frame.setSelected(true);
       frame.requestFocus();
-    } catch (java.beans.PropertyVetoException ve)
-    {
-    } 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",
-              cex);
+    } catch (java.beans.PropertyVetoException ve) {
+    } catch (java.lang.ClassCastException cex) {
+      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);
     }
   }
 
@@ -984,14 +864,11 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param frame
    */
-  private static void setKeyBindings(JInternalFrame frame)
-  {
+  private static void setKeyBindings(JInternalFrame frame) {
     @SuppressWarnings("serial")
-    final Action closeAction = new AbstractAction()
-    {
+    final Action closeAction = new AbstractAction() {
       @Override
-      public void actionPerformed(ActionEvent e)
-      {
+      public void actionPerformed(ActionEvent e) {
         frame.dispose();
       }
     };
@@ -999,13 +876,10 @@ public class Desktop extends jalview.jbgui.GDesktop
     /*
      * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
      */
-    KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            InputEvent.CTRL_DOWN_MASK);
-    KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+    KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
+    KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
 
-    InputMap inputMap = frame
-            .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+    InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
     String ctrlW = ctrlWKey.toString();
     inputMap.put(ctrlWKey, ctrlW);
     inputMap.put(cmdWKey, ctrlW);
@@ -1015,10 +889,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   @Override
-  public void lostOwnership(Clipboard clipboard, Transferable contents)
-  {
-    if (!internalCopy)
-    {
+  public void lostOwnership(Clipboard clipboard, Transferable contents) {
+    if (!internalCopy) {
       Desktop.jalviewClipboard = null;
     }
 
@@ -1026,34 +898,28 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   @Override
-  public void dragEnter(DropTargetDragEvent evt)
-  {
+  public void dragEnter(DropTargetDragEvent evt) {
   }
 
   @Override
-  public void dragExit(DropTargetEvent evt)
-  {
+  public void dragExit(DropTargetEvent evt) {
   }
 
   @Override
-  public void dragOver(DropTargetDragEvent evt)
-  {
+  public void dragOver(DropTargetDragEvent evt) {
   }
 
   @Override
-  public void dropActionChanged(DropTargetDragEvent evt)
-  {
+  public void dropActionChanged(DropTargetDragEvent evt) {
   }
 
   /**
    * DOCUMENT ME!
    * 
-   * @param evt
-   *          DOCUMENT ME!
+   * @param evt DOCUMENT ME!
    */
   @Override
-  public void drop(DropTargetDropEvent evt)
-  {
+  public void drop(DropTargetDropEvent evt) {
     boolean success = true;
     // JAL-1552 - acceptDrop required before getTransferable call for
     // Java's Transferable for native dnd
@@ -1062,44 +928,35 @@ public class Desktop extends jalview.jbgui.GDesktop
     List<Object> files = new ArrayList<>();
     List<DataSourceType> protocols = new ArrayList<>();
 
-    try
-    {
+    try {
       Desktop.transferFromDropTarget(files, protocols, evt, t);
-    } catch (Exception e)
-    {
+    } catch (Exception e) {
       e.printStackTrace();
       success = false;
     }
 
-    if (files != null)
-    {
-      try
-      {
-        for (int i = 0; i < files.size(); i++)
-        {
+    if (files != null) {
+      try {
+        for (int i = 0; i < files.size(); i++) {
           // BH 2018 File or String
           Object file = files.get(i);
           String fileName = file.toString();
-          DataSourceType protocol = (protocols == null)
-                  ? DataSourceType.FILE
-                  : protocols.get(i);
+          DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
           FileFormatI format = null;
 
-          if (fileName.endsWith(".jar"))
-          {
+          if (fileName.endsWith(".jar")) {
             format = FileFormat.Jalview;
 
-          }
-          else
-          {
+          } else {
             format = new IdentifyFile().identify(file, protocol);
           }
-
+          if (file instanceof File) {
+            Platform.cacheFileData((File) file);
+          }
           new FileLoader().LoadFile(null, file, protocol, format);
 
         }
-      } catch (Exception ex)
-      {
+      } catch (Exception ex) {
         success = false;
       }
     }
@@ -1110,26 +967,21 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * DOCUMENT ME!
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @param e DOCUMENT ME!
    */
   @Override
-  public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
-  {
+  public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
     String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
-    JalviewFileChooser chooser = JalviewFileChooser
-            .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
+    JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
+        BackupFiles.getEnabled());
 
     chooser.setFileView(new JalviewFileView());
-    chooser.setDialogTitle(
-            MessageManager.getString("label.open_local_file"));
+    chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
     chooser.setToolTipText(MessageManager.getString("action.open"));
 
-    chooser.addResponse(0, new RunResponse(JalviewFileChooser.APPROVE_OPTION)
-    {
+    chooser.setResponseHandler(0, new Runnable() {
       @Override
-      public void run()
-      {
+      public void run() {
         File selectedFile = chooser.getSelectedFile();
         Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 
@@ -1137,23 +989,18 @@ public class Desktop extends jalview.jbgui.GDesktop
 
         /*
          * 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(selectedFile,
-                    DataSourceType.FILE);
-          } catch (FileFormatException e)
-          {
+        if (FileFormats.getInstance().isIdentifiable(format)) {
+          try {
+            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);
@@ -1165,38 +1012,36 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @param viewport
    */
   @Override
-  public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
-  {
+  public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
     // This construct allows us to have a wider textfield
     // for viewing
-    JLabel label = new JLabel(
-            MessageManager.getString("label.input_file_url"));
+    JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
 
     JPanel panel = new JPanel(new GridLayout(2, 1));
     panel.add(label);
-    
+
     /*
-     * the URL to fetch is
-     * 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 = "http://www.";
-    if (Jalview.isJS())
-    {
+    String urlBase = "https://www.";
+    if (Platform.isJS()) {
       history = new JTextField(urlBase, 35);
-    }
-    else
+    } else
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
     {
       JComboBox<String> asCombo = new JComboBox<>();
       asCombo.setPreferredSize(new Dimension(400, 20));
       asCombo.setEditable(true);
       asCombo.addItem(urlBase);
       String historyItems = Cache.getProperty("RECENT_URL");
-      if (historyItems != null)
-      {
-        for (String token : historyItems.split("\\t"))
-        {
+      if (historyItems != null) {
+        for (String token : historyItems.split("\\t")) {
           asCombo.addItem(token);
         }
       }
@@ -1206,119 +1051,87 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
-    RunResponse action = new RunResponse(JvOptionPane.OK_OPTION) {
+    Runnable action = new Runnable() {
       @Override
-      public void run()
-      {
-        String url = Jalview.isJS() ? ((JTextField) history).getText()
-                : ((JComboBox<String>) history).getSelectedItem()
-                        .toString();
-
-        if (url.toLowerCase().endsWith(".jar"))
-        {
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    FileFormat.Jalview);
+      public void run() {
+        @SuppressWarnings("unchecked")
+        String url = (history instanceof JTextField ? ((JTextField) history).getText()
+            : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
+
+        if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
+          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,
-                    FileFormat.Jalview);
-          }
-        }
-        else
-        {
+        } else {
           FileFormatI format = null;
-          try
-          {
+          try {
             format = new IdentifyFile().identify(url, DataSourceType.URL);
-          } catch (FileFormatException e)
-          {
+          } catch (FileFormatException e) {
             // TODO revise error handling, distinguish between
             // URL not found and response not valid
           }
 
-          if (format == null)
-          {
+          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);
+                MessageManager.getString("label.url_not_found"), JvOptionPane.WARNING_MESSAGE);
 
             return;
           }
 
-          if (viewport != null)
-          {
-            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
-                    format);
-          }
-          else
-          {
+          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).addResponse(0, action)
-            .showInternalDialog(panel, dialogOption,
-                    JvOptionPane.YES_NO_CANCEL_OPTION,
-                    JvOptionPane.PLAIN_MESSAGE, null, options,
-                    MessageManager.getString("action.ok"));
+      }
+    };
+    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"));
   }
 
   /**
    * Opens the CutAndPaste window for the user to paste an alignment in to
    * 
-   * @param viewPanel
-   *          - if not null, the pasted alignment is added to the current
-   *          alignment; if null, to a new alignment window
+   * @param viewPanel - if not null, the pasted alignment is added to the current
+   *                  alignment; if null, to a new alignment window
    */
   @Override
-  public void inputTextboxMenuItem_actionPerformed(
-          AlignmentViewPanel viewPanel)
-  {
+  public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
     cap.setForInput(viewPanel);
-    Desktop.addInternalFrame(cap,
-            MessageManager.getString("label.cut_paste_alignmen_file"), true,
-            600, 500);
+    Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
   }
 
   /*
    * Exit the program
    */
   @Override
-  public void quit()
-  {
+  public void quit() {
     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()));
-
-    if (jconsole != null)
-    {
+    Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
+    Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
+    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
+
+    if (jconsole != null) {
       storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
       jconsole.stopConsole();
     }
-    if (jvnews != null)
-    {
+    if (jvnews != null) {
       storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
 
     }
-    if (dialogExecutor != null)
-    {
+    if (dialogExecutor != null) {
       dialogExecutor.shutdownNow();
     }
     closeAll_actionPerformed(null);
 
-    if (groovyConsole != null)
-    {
+    if (groovyConsole != null) {
       // suppress a possible repeat prompt to save script
       groovyConsole.setDirty(false);
       groovyConsole.exit();
@@ -1326,176 +1139,130 @@ public class Desktop extends jalview.jbgui.GDesktop
     System.exit(0);
   }
 
-  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
-            + " height:" + jc.height);
+  private void storeLastKnownDimensions(String string, Rectangle jc) {
+    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 + "");
   }
 
   /**
    * DOCUMENT ME!
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @param e DOCUMENT ME!
    */
   @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()
-    {
+  public void aboutMenuItem_actionPerformed(ActionEvent e) {
+    new Thread(new Runnable() {
       @Override
-      public void run()
-      {
-        new SplashScreen(true);
+      public void run() {
+        new SplashScreen(false);
       }
     }).start();
   }
 
-  public StringBuffer getAboutMessage(boolean shortv)
-  {
-    StringBuffer message = new StringBuffer();
-    message.append("<html>");
-    if (shortv)
-    {
-      message.append("<h1><strong>Version: "
-              + jalview.bin.Cache.getProperty("VERSION")
-              + "</strong></h1>");
-      message.append("<strong>Last Updated: <em>"
-              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
-              + "</em></strong>");
-
-    }
-    else
-    {
-
-      message.append("<strong>Version "
-              + jalview.bin.Cache.getProperty("VERSION")
-              + "; last updated: "
-              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
-    }
-
-    if (jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
-            .equals("Checking"))
-    {
-      message.append("<br>...Checking latest version...</br>");
-    }
-    else if (!jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
-            .equals(jalview.bin.Cache.getProperty("VERSION")))
-    {
+  /**
+   * 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() {
+    StringBuilder message = new StringBuilder(1024);
+    message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
+        .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
+        .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
+        .append(Cache.getBuildDetailsForSplash()).append("</strong>");
+
+    String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
+    if (latestVersion.equals("Checking")) {
+      // JBP removed this message for 2.11: May be reinstated in future version
+      // message.append("<br>...Checking latest version...</br>");
+    } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
       boolean red = false;
-      if (jalview.bin.Cache.getProperty("VERSION").toLowerCase()
-              .indexOf("automated build") == -1)
-      {
+      if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
         red = true;
         // Displayed when code version and jnlp version do not match and code
         // version is not a development build
         message.append("<div style=\"color: #FF0000;font-style: bold;\">");
       }
 
-      message.append("<br>!! Version "
-              + jalview.bin.Cache.getDefault("LATEST_VERSION",
-                      "..Checking..")
-              + " is available for download from "
-              + jalview.bin.Cache.getDefault("www.jalview.org",
-                      "http://www.jalview.org")
-              + " !!");
-      if (red)
-      {
+      message.append("<br>!! 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("</div>");
       }
     }
-    message.append("<br>Authors:  " + jalview.bin.Cache.getDefault(
-            "AUTHORFNAMES",
-            "The Jalview Authors (See AUTHORS file for current list)")
-            + "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
-            + "<br><br>For help, see the FAQ at <a href=\"http://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list"
-            + "<br><br>If  you use Jalview, please cite:"
-            + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
-            + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
-            + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033"
-            + "</html>");
-    return message;
+    message.append("<br>Authors:  ");
+    message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
+    message.append(CITATION);
+
+    message.append("</div>");
+
+    return message.toString();
   }
 
   /**
    * Action on requesting Help documentation
    */
   @Override
-  public void documentationMenuItem_actionPerformed()
-  {
-    try
-    {
-      if (Jalview.isJS())
-      {
-        BrowserLauncher.openURL("http://www.jalview.org/help.html");
-      }
-      else
+  public void documentationMenuItem_actionPerformed() {
+    try {
+      if (Platform.isJS()) {
+        BrowserLauncher.openURL("https://www.jalview.org/help.html");
+      } else
+      /**
+       * Java only
+       * 
+       * @j2sIgnore
+       */
       {
         Help.showHelpWindow();
       }
-    } catch (Exception ex)
-    {
+    } catch (Exception ex) {
       System.err.println("Error opening help: " + ex.getMessage());
     }
   }
 
   @Override
-  public void closeAll_actionPerformed(ActionEvent e)
-  {
+  public void closeAll_actionPerformed(ActionEvent e) {
     // TODO show a progress bar while closing?
     JInternalFrame[] frames = desktop.getAllFrames();
-    for (int i = 0; i < frames.length; i++)
-    {
-      try
-      {
+    for (int i = 0; i < frames.length; i++) {
+      try {
         frames[i].setClosed(true);
-      } catch (java.beans.PropertyVetoException ex)
-      {
+      } catch (java.beans.PropertyVetoException ex) {
       }
     }
     Jalview.setCurrentAlignFrame(null);
     System.out.println("ALL CLOSED");
-    if (v_client != null)
-    {
-      // TODO clear binding to vamsas document objects on close_all
-    }
 
     /*
      * reset state of singleton objects as appropriate (clear down session state
      * when all windows are closed)
      */
-    StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(this);
-    if (ssm != null)
-    {
+    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
+    if (ssm != null) {
       ssm.resetAll();
     }
   }
 
   @Override
-  public void raiseRelated_actionPerformed(ActionEvent e)
-  {
+  public void raiseRelated_actionPerformed(ActionEvent e) {
     reorderAssociatedWindows(false, false);
   }
 
   @Override
-  public void minimizeAssociated_actionPerformed(ActionEvent e)
-  {
+  public void minimizeAssociated_actionPerformed(ActionEvent e) {
     reorderAssociatedWindows(true, false);
   }
 
-  void closeAssociatedWindows()
-  {
+  void closeAssociatedWindows() {
     reorderAssociatedWindows(false, true);
   }
 
@@ -1506,24 +1273,21 @@ public class Desktop extends jalview.jbgui.GDesktop
    * ActionEvent)
    */
   @Override
-  protected void garbageCollect_actionPerformed(ActionEvent e)
-  {
+  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)
-  {
+  protected void showMemusage_actionPerformed(ActionEvent e) {
     desktop.showMemoryUsage(showMemusage.isSelected());
   }
 
@@ -1535,8 +1299,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * )
    */
   @Override
-  protected void showConsole_actionPerformed(ActionEvent e)
-  {
+  protected void showConsole_actionPerformed(ActionEvent e) {
     showConsole(showConsole.isSelected());
   }
 
@@ -1547,89 +1310,61 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param selected
    */
-  void showConsole(boolean selected)
-  {
+  void showConsole(boolean selected) {
     // TODO: decide if we should update properties file
     if (jconsole != null) // BH 2018
     {
       showConsole.setSelected(selected);
-      Cache.setProperty("SHOW_JAVA_CONSOLE",
-              Boolean.valueOf(selected).toString());
+      Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
       jconsole.setVisible(selected);
     }
   }
 
-  void reorderAssociatedWindows(boolean minimize, boolean close)
-  {
+  void reorderAssociatedWindows(boolean minimize, boolean close) {
     JInternalFrame[] frames = desktop.getAllFrames();
-    if (frames == null || frames.length < 1)
-    {
+    if (frames == null || frames.length < 1) {
       return;
     }
 
     AlignmentViewport source = null, target = null;
-    if (frames[0] instanceof AlignFrame)
-    {
+    if (frames[0] instanceof AlignFrame) {
       source = ((AlignFrame) frames[0]).getCurrentView();
-    }
-    else if (frames[0] instanceof TreePanel)
-    {
+    } else if (frames[0] instanceof TreePanel) {
       source = ((TreePanel) frames[0]).getViewPort();
-    }
-    else if (frames[0] instanceof PCAPanel)
-    {
+    } else if (frames[0] instanceof PCAPanel) {
       source = ((PCAPanel) frames[0]).av;
-    }
-    else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
-    {
+    } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
       source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
     }
 
-    if (source != null)
-    {
-      for (int i = 0; i < frames.length; i++)
-      {
+    if (source != null) {
+      for (int i = 0; i < frames.length; i++) {
         target = null;
-        if (frames[i] == null)
-        {
+        if (frames[i] == null) {
           continue;
         }
-        if (frames[i] instanceof AlignFrame)
-        {
+        if (frames[i] instanceof AlignFrame) {
           target = ((AlignFrame) frames[i]).getCurrentView();
-        }
-        else if (frames[i] instanceof TreePanel)
-        {
+        } else if (frames[i] instanceof TreePanel) {
           target = ((TreePanel) frames[i]).getViewPort();
-        }
-        else if (frames[i] instanceof PCAPanel)
-        {
+        } else if (frames[i] instanceof PCAPanel) {
           target = ((PCAPanel) frames[i]).av;
-        }
-        else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
-        {
+        } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
           target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
         }
 
-        if (source == target)
-        {
-          try
-          {
-            if (close)
-            {
+        if (source == target) {
+          try {
+            if (close) {
               frames[i].setClosed(true);
-            }
-            else
-            {
+            } else {
               frames[i].setIcon(minimize);
-              if (!minimize)
-              {
+              if (!minimize) {
                 frames[i].toFront();
               }
             }
 
-          } catch (java.beans.PropertyVetoException ex)
-          {
+          } catch (java.beans.PropertyVetoException ex) {
           }
         }
       }
@@ -1639,13 +1374,11 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * DOCUMENT ME!
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @param e DOCUMENT ME!
    */
   @Override
-  protected void preferences_actionPerformed(ActionEvent e)
-  {
-    new Preferences();
+  protected void preferences_actionPerformed(ActionEvent e) {
+    Preferences.openPreferences();
   }
 
   /**
@@ -1653,147 +1386,131 @@ public class Desktop extends jalview.jbgui.GDesktop
    * Jalview project file
    */
   @Override
-  public void saveState_actionPerformed(boolean asCastor)
-  {
-    JalviewFileChooser chooser = new JalviewFileChooser(
-            asCastor ? "jvp" : "jvx",
-            "Jalview Project");
+  public void saveState_actionPerformed() {
+    saveState_actionPerformed(false);
+  }
 
-    chooser.setFileView(new JalviewFileView());
-    chooser.setDialogTitle(MessageManager.getString("label.save_state"));
-    int option = chooser.showSaveDialog(this);
-    if (option == JalviewFileChooser.APPROVE_OPTION)
-    {
-      File choice = chooser.getSelectedFile();
-      setProjectFile(choice);
+  public void saveState_actionPerformed(boolean saveAs) {
+    java.io.File projectFile = getProjectFile();
+    // autoSave indicates we already have a file and don't need to ask
+    boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
 
-      new Thread(new Runnable()
-      {
+    // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
+    // saveAs="+saveAs+", Backups
+    // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
+
+    boolean approveSave = false;
+    if (!autoSave) {
+      JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
+
+      chooser.setFileView(new JalviewFileView());
+      chooser.setDialogTitle(MessageManager.getString("label.save_state"));
+
+      int value = chooser.showSaveDialog(this);
+
+      if (value == JalviewFileChooser.APPROVE_OPTION) {
+        projectFile = chooser.getSelectedFile();
+        setProjectFile(projectFile);
+        approveSave = true;
+      }
+    }
+
+    if (approveSave || autoSave) {
+      final Desktop me = this;
+      final java.io.File chosenFile = projectFile;
+      new Thread(new Runnable() {
         @Override
-        public void run()
-        {
+        public void run() {
           // TODO: refactor to Jalview desktop session controller action.
-          setProgressBar(MessageManager.formatMessage(
-                  "label.saving_jalview_project", new Object[]
-                  { choice.getName() }), choice.hashCode());
-          jalview.bin.Cache.setProperty("LAST_DIRECTORY",
-                  choice.getParent());
+          setProgressBar(
+              MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
+              chosenFile.hashCode());
+          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
-          {
-            if (asCastor)
-            {
-              new Jalview2XML().saveState(choice);
-            }
-            else
-            {
-              new jalview.project.Jalview2XML().saveState(choice);
+          try {
+            boolean doBackup = BackupFiles.getEnabled();
+            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
+
+            new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
+
+            if (doBackup) {
+              backupfiles.setWriteSuccess(true);
+              backupfiles.rollBackupsAndRenameTempFile();
             }
-          } catch (OutOfMemoryError oom)
-          {
-            new OOMWarning(
-                    "Whilst saving current state to " + choice.getName(),
-                    oom);
-          } catch (Exception ex)
-          {
-            Cache.log.error(
-                    "Problems whilst trying to save to " + choice.getName(),
-                    ex);
-            JvOptionPane.showMessageDialog(Desktop.this,
-                    MessageManager.formatMessage(
-                            "label.error_whilst_saving_current_state_to",
-                            new Object[]
-                            { choice.getName() }),
-                    MessageManager.getString("label.couldnt_save_project"),
-                    JvOptionPane.WARNING_MESSAGE);
+          } catch (OutOfMemoryError oom) {
+            new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
+          } catch (Exception ex) {
+            jalview.bin.Console.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
+            JvOptionPane.showMessageDialog(me,
+                MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
+                    new Object[] { chosenFile.getName() }),
+                MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
           }
-          setProgressBar(null, choice.hashCode());
+          setProgressBar(null, chosenFile.hashCode());
         }
       }).start();
-      }
+    }
   }
 
-  void setProjectFile(File choice)
-  {
+  @Override
+  public void saveAsState_actionPerformed(ActionEvent e) {
+    saveState_actionPerformed(true);
+  }
+
+  private void setProjectFile(File choice) {
     this.projectFile = choice;
   }
 
-  public File getProjectFile()
-  {
+  public File getProjectFile() {
     return this.projectFile;
   }
 
   /**
-   * Prompts the user to choose a file and loads in as a Jalview project file
+   * Shows a file chooser dialog and tries to read in the selected file as a
+   * Jalview project
    */
   @Override
-  public void loadState_actionPerformed(boolean asCastor)
-  {
-    // TODO: GET RID OF .JVX BEFORE RELEASE JIM!
-    final String[] suffix = asCastor ? new String[] { "jvp", "jar" }
-            : new String[]
-            { "jvx" };
-    final String[] desc = asCastor
-            ? new String[]
-            { "Jalview Project", "Jalview Project (old)" }
-            : new String[]
-            { "Jalview Project" };
-    JalviewFileChooser chooser = new JalviewFileChooser(
-            Cache.getProperty("LAST_DIRECTORY"), suffix,
-            desc,
-            "Jalview Project");
+  public void loadState_actionPerformed() {
+    final String[] suffix = new String[] { "jvp", "jar" };
+    final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
+    JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
+        "Jalview Project", true, BackupFiles.getEnabled()); // last two
+                                                            // booleans:
+                                                            // allFiles,
+    // allowBackupFiles
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
-    chooser.addResponse(0, new RunResponse(JalviewFileChooser.APPROVE_OPTION)
-    {
+    chooser.setResponseHandler(0, new Runnable() {
       @Override
-      public void run()
-      {
+      public void run() {
         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()
-        {
+        new Thread(new Runnable() {
           @Override
-          public void run()
-          {
-               try {
-              if (asCastor)
-              {
-                new Jalview2XML().loadJalviewAlign(choice);
-              }
-              else
-              {
-                new jalview.project.Jalview2XML().loadJalviewAlign(choice);
-              }
-            } 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);
+          public void run() {
+            try {
+              new Jalview2XML().loadJalviewAlign(selectedFile);
+            } catch (OutOfMemoryError oom) {
+              new OOMWarning("Whilst loading project from " + choice, oom);
+            } catch (Exception ex) {
+              jalview.bin.Console.error("Problems whilst loading project from " + choice, ex);
+              JvOptionPane.showMessageDialog(Desktop.desktop,
+                  MessageManager.formatMessage("label.error_whilst_loading_project_from", new Object[] { choice }),
+                  MessageManager.getString("label.couldnt_load_project"), JvOptionPane.WARNING_MESSAGE);
             }
           }
-        }).start();
+        }, "Project Loader").start();
       }
     });
-    
+
     chooser.showOpenDialog(this);
   }
 
   @Override
-  public void inputSequence_actionPerformed(ActionEvent e)
-  {
+  public void inputSequence_actionPerformed(ActionEvent e) {
     new SequenceFetcher(this);
   }
 
@@ -1801,21 +1518,16 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
 
-  public void startLoading(final Object fileName)
-  {
-    if (fileLoadingCount == 0)
-    {
-      fileLoadingPanels.add(addProgressPanel(MessageManager
-              .formatMessage("label.loading_file", new Object[]
-              { fileName })));
+  public void startLoading(final Object fileName) {
+    if (fileLoadingCount == 0) {
+      fileLoadingPanels
+          .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
     }
     fileLoadingCount++;
   }
 
-  private JPanel addProgressPanel(String string)
-  {
-    if (progressPanel == null)
-    {
+  private JPanel addProgressPanel(String string) {
+    if (progressPanel == null) {
       progressPanel = new JPanel(new GridLayout(1, 1));
       totalProgressCount = 0;
       instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
@@ -1828,8 +1540,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     thisprogress.add(progressBar, BorderLayout.CENTER);
     progressPanel.add(thisprogress);
-    ((GridLayout) progressPanel.getLayout()).setRows(
-            ((GridLayout) progressPanel.getLayout()).getRows() + 1);
+    ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
     ++totalProgressCount;
     instance.validate();
     return thisprogress;
@@ -1837,17 +1548,13 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   int totalProgressCount = 0;
 
-  private void removeProgressPanel(JPanel progbar)
-  {
-    if (progressPanel != null)
-    {
-      synchronized (progressPanel)
-      {
+  private void removeProgressPanel(JPanel progbar) {
+    if (progressPanel != null) {
+      synchronized (progressPanel) {
         progressPanel.remove(progbar);
         GridLayout gl = (GridLayout) progressPanel.getLayout();
         gl.setRows(gl.getRows() - 1);
-        if (--totalProgressCount < 1)
-        {
+        if (--totalProgressCount < 1) {
           this.getContentPane().remove(progressPanel);
           progressPanel = null;
         }
@@ -1856,13 +1563,10 @@ public class Desktop extends jalview.jbgui.GDesktop
     validate();
   }
 
-  public void stopLoading()
-  {
+  public void stopLoading() {
     fileLoadingCount--;
-    if (fileLoadingCount < 1)
-    {
-      while (fileLoadingPanels.size() > 0)
-      {
+    if (fileLoadingCount < 1) {
+      while (fileLoadingPanels.size() > 0) {
         removeProgressPanel(fileLoadingPanels.remove(0));
       }
       fileLoadingPanels.clear();
@@ -1871,543 +1575,181 @@ public class Desktop extends jalview.jbgui.GDesktop
     validate();
   }
 
-  public static int getViewCount(String alignmentId)
-  {
+  public static int getViewCount(String alignmentId) {
     AlignmentViewport[] aps = getViewports(alignmentId);
     return (aps == null) ? 0 : aps.length;
   }
 
   /**
    * 
-   * @param alignmentId
-   *          - if null, all sets are returned
+   * @param alignmentId - if null, all sets are returned
    * @return all AlignmentPanels concerning the alignmentId sequence set
    */
-  public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
-  {
-    if (Desktop.desktop == null)
-    {
+  public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
+    if (Desktop.desktop == null) {
       // no frames created and in headless mode
       // TODO: verify that frames are recoverable when in headless mode
       return null;
     }
     List<AlignmentPanel> aps = new ArrayList<>();
     AlignFrame[] frames = getAlignFrames();
-    if (frames == null)
-    {
+    if (frames == null) {
       return null;
     }
-    for (AlignFrame af : frames)
-    {
-      for (AlignmentPanel ap : af.alignPanels)
-      {
-        if (alignmentId == null
-                || alignmentId.equals(ap.av.getSequenceSetId()))
-        {
+    for (AlignFrame af : frames) {
+      for (AlignmentPanel ap : af.alignPanels) {
+        if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
           aps.add(ap);
         }
       }
     }
-    if (aps.size() == 0)
-    {
-      return null;
-    }
-    AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
-    return vap;
-  }
-
-  /**
-   * get all the viewports on an alignment.
-   * 
-   * @param sequenceSetId
-   *          unique alignment id (may be null - all viewports returned in that
-   *          case)
-   * @return all viewports on the alignment bound to sequenceSetId
-   */
-  public static AlignmentViewport[] getViewports(String sequenceSetId)
-  {
-    List<AlignmentViewport> viewp = new ArrayList<>();
-    if (desktop != null)
-    {
-      AlignFrame[] frames = Desktop.getAlignFrames();
-
-      for (AlignFrame afr : frames)
-      {
-        if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
-                .equals(sequenceSetId))
-        {
-          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)
-  {
-    // TODO: JAL-3048 not needed for Jalview-JS
-
-    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();
+    if (aps.size() == 0) {
+      return null;
     }
+    AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
+    return vap;
   }
 
-  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()
-          {
+  /**
+   * get all the viewports on an alignment.
+   * 
+   * @param sequenceSetId unique alignment id (may be null - all viewports
+   *                      returned in that case)
+   * @return all viewports on the alignment bound to sequenceSetId
+   */
+  public static AlignmentViewport[] getViewports(String sequenceSetId) {
+    List<AlignmentViewport> viewp = new ArrayList<>();
+    if (desktop != null) {
+      AlignFrame[] frames = Desktop.getAlignFrames();
 
-            @Override
-            public void actionPerformed(ActionEvent e)
-            {
-              if (dsktp.v_client == null)
-              {
-                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();
+      for (AlignFrame afr : frames) {
+        if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
+          if (afr.alignPanels != null) {
+            for (AlignmentPanel ap : afr.alignPanels) {
+              if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
+                viewp.add(ap.av);
               }
-            };
-          });
-          VamsasStMenu.add(sessit);
+            }
+          } else {
+            viewp.add(afr.getViewport());
+          }
         }
-        // don't show an empty menu.
-        VamsasStMenu.setVisible(sess.length > 0);
-
       }
-      else
-      {
-        jalview.bin.Cache.log.debug("No current vamsas sessions.");
-        VamsasStMenu.removeAll();
-        VamsasStMenu.setVisible(false);
+      if (viewp.size() > 0) {
+        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)
-  {
-    // TODO: JAL-3048 not needed for Jalview-JS
-
-    if (v_client != null)
-    {
-      // TODO: VAMSAS DOCUMENT EXTENSION is VDJ
-      JalviewFileChooser chooser = new JalviewFileChooser("vdj",
-              "Vamsas Document");
+  /**
+   * 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;
+    }
 
-      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)
-      {
-        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
-        {
-          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);
+      // transfer reference for existing feature settings to new alignFrame
+      if (ap == af.alignPanel) {
+        if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
+          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)
-  {
-    Cache.log.debug("Setting gui for Vamsas update "
-            + (b ? "in progress" : "finished"));
+  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 (vamUpdate != null)
-    {
-      this.removeProgressPanel(vamUpdate);
+        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)
-    {
-      vamUpdate = this.addProgressPanel(
-              MessageManager.getString("label.updating_vamsas_session"));
+
+    // refresh the feature setting UI for the source frame if it exists
+    if (source.featureSettings != null && source.featureSettings.isOpen()) {
+      source.showFeatureSettingsUI();
     }
-    vamsasStart.setVisible(!b);
-    vamsasStop.setVisible(!b);
-    vamsasSave.setVisible(!b);
+
   }
 
-  public JInternalFrame[] getAllFrames()
-  {
+  public JInternalFrame[] getAllFrames() {
     return desktop.getAllFrames();
   }
 
@@ -2417,49 +1759,37 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param url
    */
-  public void checkForQuestionnaire(String url)
-  {
+  public void checkForQuestionnaire(String url) {
     UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
     // javax.swing.SwingUtilities.invokeLater(jvq);
     new Thread(jvq).start();
   }
 
-  public void checkURLLinks()
-  {
+  public void checkURLLinks() {
     // Thread off the URL link checker
-    addDialogThread(new Runnable()
-    {
+    addDialogThread(new Runnable() {
       @Override
-      public void run()
-      {
-        if (Cache.getDefault("CHECKURLLINKS", true))
-        {
+      public void run() {
+        if (Cache.getDefault("CHECKURLLINKS", true)) {
           // check what the actual links are - if it's just the default don't
           // bother with the warning
-          List<String> links = Preferences.sequenceUrlLinks
-                  .getLinksForMenu();
+          List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
 
           // only need to check links if there is one with a
           // SEQUENCE_ID which is not the default EMBL_EBI link
           ListIterator<String> li = links.listIterator();
           boolean check = false;
           List<JLabel> urls = new ArrayList<>();
-          while (li.hasNext())
-          {
+          while (li.hasNext()) {
             String link = li.next();
-            if (link.contains(SEQUENCE_ID)
-                    && !UrlConstants.isDefaultString(link))
-            {
+            if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
               check = true;
               int barPos = link.indexOf("|");
-              String urlMsg = barPos == -1 ? link
-                      : link.substring(0, barPos) + ": "
-                              + link.substring(barPos + 1);
+              String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
               urls.add(new JLabel(urlMsg));
             }
           }
-          if (!check)
-          {
+          if (!check) {
             return;
           }
 
@@ -2468,36 +1798,27 @@ public class Desktop extends jalview.jbgui.GDesktop
           JPanel msgPanel = new JPanel();
           msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
           msgPanel.add(Box.createVerticalGlue());
-          JLabel msg = new JLabel(MessageManager
-                  .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
-          JLabel msg2 = new JLabel(MessageManager
-                  .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
+          JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
+          JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
           msgPanel.add(msg);
-          for (JLabel url : urls)
-          {
+          for (JLabel url : urls) {
             msgPanel.add(url);
           }
           msgPanel.add(msg2);
 
-          final JCheckBox jcb = new JCheckBox(
-                  MessageManager.getString("label.do_not_display_again"));
-          jcb.addActionListener(new ActionListener()
-          {
+          final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
+          jcb.addActionListener(new ActionListener() {
             @Override
-            public void actionPerformed(ActionEvent e)
-            {
+            public void actionPerformed(ActionEvent e) {
               // update Cache settings for "don't show this again"
               boolean showWarningAgain = !jcb.isSelected();
-              Cache.setProperty("CHECKURLLINKS",
-                      Boolean.valueOf(showWarningAgain).toString());
+              Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
             }
           });
           msgPanel.add(jcb);
 
           JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
-                  MessageManager
-                          .getString("label.SEQUENCE_ID_no_longer_used"),
-                  JvOptionPane.WARNING_MESSAGE);
+              MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
         }
       }
     });
@@ -2509,9 +1830,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @author AMW
    */
-  public class MyDesktopPane extends JDesktopPane
-          implements Runnable
-  {
+  public class MyDesktopPane extends JDesktopPane implements Runnable {
     private static final float ONE_MB = 1048576f;
 
     boolean showMemoryUsage = false;
@@ -2520,41 +1839,33 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     java.text.NumberFormat df;
 
-    float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
-            percentUsage;
+    float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
 
-    public MyDesktopPane(boolean showMemoryUsage)
-    {
+    public MyDesktopPane(boolean showMemoryUsage) {
       showMemoryUsage(showMemoryUsage);
     }
 
-    public void showMemoryUsage(boolean showMemory)
-    {
+    public void showMemoryUsage(boolean showMemory) {
       this.showMemoryUsage = showMemory;
-      if (showMemory)
-      {
+      if (showMemory) {
         Thread worker = new Thread(this);
         worker.start();
       }
       repaint();
     }
 
-    public boolean isShowMemoryUsage()
-    {
+    public boolean isShowMemoryUsage() {
       return showMemoryUsage;
     }
 
     @Override
-    public void run()
-    {
+    public void run() {
       df = java.text.NumberFormat.getNumberInstance();
       df.setMaximumFractionDigits(2);
       runtime = Runtime.getRuntime();
 
-      while (showMemoryUsage)
-      {
-        try
-        {
+      while (showMemoryUsage) {
+        try {
           maxMemory = runtime.maxMemory() / ONE_MB;
           allocatedMemory = runtime.totalMemory() / ONE_MB;
           freeMemory = runtime.freeMemory() / ONE_MB;
@@ -2571,32 +1882,29 @@ public class Desktop extends jalview.jbgui.GDesktop
           repaint();
           // sleep after showing usage
           Thread.sleep(3000);
-        } catch (Exception ex)
-        {
+        } catch (Exception ex) {
           ex.printStackTrace();
         }
       }
     }
 
     @Override
-    public void paintComponent(Graphics g)
-    {
-      if (showMemoryUsage && g != null && df != null)
-      {
-        if (percentUsage < 20)
-        {
+    public void paintComponent(Graphics g) {
+      if (showMemoryUsage && g != null && df != null) {
+        if (percentUsage < 20) {
           g.setColor(Color.red);
         }
         FontMetrics fm = g.getFontMetrics();
-        if (fm != null)
-        {
-          g.drawString(MessageManager.formatMessage("label.memory_stats",
-                  new Object[]
-                  { df.format(totalFreeMemory), df.format(maxMemory),
-                      df.format(percentUsage) }),
-                  10, getHeight() - fm.getHeight());
+        if (fm != null) {
+          g.drawString(
+              MessageManager.formatMessage("label.memory_stats",
+                  new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
+              10, getHeight() - fm.getHeight());
         }
       }
+
+      // output debug scale message. Important for jalview.bin.HiDPISettingTest2
+      Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
     }
   }
 
@@ -2605,46 +1913,36 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @return an array of AlignFrame, or null if none found
    */
-  public static AlignFrame[] getAlignFrames()
-  {
-    if (Jalview.isHeadlessMode())
-    {
+  public static AlignFrame[] getAlignFrames() {
+    if (Jalview.isHeadlessMode()) {
       // Desktop.desktop is null in headless mode
       return new AlignFrame[] { Jalview.currentAlignFrame };
     }
 
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
-    if (frames == null)
-    {
+    if (frames == null) {
       return null;
     }
     List<AlignFrame> avp = new ArrayList<>();
     // REVERSE ORDER
-    for (int i = frames.length - 1; i > -1; i--)
-    {
-      if (frames[i] instanceof AlignFrame)
-      {
+    for (int i = frames.length - 1; i > -1; i--) {
+      if (frames[i] instanceof AlignFrame) {
         avp.add((AlignFrame) frames[i]);
-      }
-      else if (frames[i] instanceof SplitFrame)
-      {
+      } else if (frames[i] instanceof SplitFrame) {
         /*
          * Also check for a split frame containing an AlignFrame
          */
         GSplitFrame sf = (GSplitFrame) frames[i];
-        if (sf.getTopFrame() instanceof AlignFrame)
-        {
+        if (sf.getTopFrame() instanceof AlignFrame) {
           avp.add((AlignFrame) sf.getTopFrame());
         }
-        if (sf.getBottomFrame() instanceof AlignFrame)
-        {
+        if (sf.getBottomFrame() instanceof AlignFrame) {
           avp.add((AlignFrame) sf.getBottomFrame());
         }
       }
     }
-    if (avp.size() == 0)
-    {
+    if (avp.size() == 0) {
       return null;
     }
     AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
@@ -2656,26 +1954,21 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @return
    */
-  public GStructureViewer[] getJmols()
-  {
+  public GStructureViewer[] getJmols() {
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
-    if (frames == null)
-    {
+    if (frames == null) {
       return null;
     }
     List<GStructureViewer> avp = new ArrayList<>();
     // REVERSE ORDER
-    for (int i = frames.length - 1; i > -1; i--)
-    {
-      if (frames[i] instanceof AppJmol)
-      {
+    for (int i = frames.length - 1; i > -1; i--) {
+      if (frames[i] instanceof AppJmol) {
         GStructureViewer af = (GStructureViewer) frames[i];
         avp.add(af);
       }
     }
-    if (avp.size() == 0)
-    {
+    if (avp.size() == 0) {
       return null;
     }
     GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
@@ -2686,45 +1979,37 @@ public class Desktop extends jalview.jbgui.GDesktop
    * Add Groovy Support to Jalview
    */
   @Override
-  public void groovyShell_actionPerformed()
-  {
-    try
-    {
+  public void groovyShell_actionPerformed() {
+    try {
       openGroovyConsole();
-    } catch (Exception ex)
-    {
-      jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
+    } catch (Exception ex) {
+      jalview.bin.Console.error("Groovy Shell Creation failed.", ex);
       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 
-              MessageManager.getString("label.couldnt_create_groovy_shell"),
-              MessageManager.getString("label.groovy_support_failed"),
-              JvOptionPane.ERROR_MESSAGE);
+          MessageManager.getString("label.couldnt_create_groovy_shell"),
+          MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
     }
   }
 
   /**
    * Open the Groovy console
    */
-  void openGroovyConsole()
-  {
-    if (groovyConsole == null)
-    {
+  void openGroovyConsole() {
+    if (groovyConsole == null) {
       groovyConsole = new groovy.ui.Console();
       groovyConsole.setVariable("Jalview", this);
       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()
-      {
+      window.addWindowListener(new WindowAdapter() {
         @Override
-        public void windowClosed(WindowEvent e)
-        {
+        public void windowClosed(WindowEvent e) {
           /*
            * rebind CMD-Q from Groovy Console to Jalview Quit
            */
@@ -2740,8 +2025,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);
   }
@@ -2750,17 +2035,13 @@ public class Desktop extends jalview.jbgui.GDesktop
    * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
    * when opened
    */
-  protected void addQuitHandler()
-  {
-    getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
-            .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
-                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
-                    "Quit");
-    getRootPane().getActionMap().put("Quit", new AbstractAction()
-    {
+  protected void addQuitHandler() {
+    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)
-      {
+      public void actionPerformed(ActionEvent e) {
         quit();
       }
     });
@@ -2769,22 +2050,18 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
    * 
-   * @param enabled
-   *          true if Groovy console is open
+   * @param enabled true if Groovy console is open
    */
-  public void enableExecuteGroovy(boolean enabled)
-  {
+  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();
-    if (alignFrames != null)
-    {
-      for (AlignFrame af : alignFrames)
-      {
+    if (alignFrames != null) {
+      for (AlignFrame af : alignFrames) {
         af.setGroovyEnabled(enabled);
       }
     }
@@ -2803,26 +2080,20 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
    */
   @Override
-  public void setProgressBar(String message, long id)
-  {
-    if (progressBars == null)
-    {
+  public void setProgressBar(String message, long id) {
+    if (progressBars == null) {
       progressBars = new Hashtable<>();
       progressBarHandlers = new Hashtable<>();
     }
 
-    if (progressBars.get(new Long(id)) != null)
-    {
-      JPanel panel = progressBars.remove(new Long(id));
-      if (progressBarHandlers.contains(new Long(id)))
-      {
-        progressBarHandlers.remove(new Long(id));
+    if (progressBars.get(Long.valueOf(id)) != null) {
+      JPanel panel = progressBars.remove(Long.valueOf(id));
+      if (progressBarHandlers.contains(Long.valueOf(id))) {
+        progressBarHandlers.remove(Long.valueOf(id));
       }
       removeProgressPanel(panel);
-    }
-    else
-    {
-      progressBars.put(new Long(id), addProgressPanel(message));
+    } else {
+      progressBars.put(Long.valueOf(id), addProgressPanel(message));
     }
   }
 
@@ -2833,33 +2104,22 @@ public class Desktop extends jalview.jbgui.GDesktop
    * jalview.gui.IProgressIndicatorHandler)
    */
   @Override
-  public void registerHandler(final long id,
-          final IProgressIndicatorHandler handler)
-  {
-    if (progressBarHandlers == null
-            || !progressBars.containsKey(new Long(id)))
-    {
-      throw new Error(MessageManager.getString(
-              "error.call_setprogressbar_before_registering_handler"));
-    }
-    progressBarHandlers.put(new Long(id), handler);
-    final JPanel progressPanel = progressBars.get(new Long(id));
-    if (handler.canCancel())
-    {
-      JButton cancel = new JButton(
-              MessageManager.getString("action.cancel"));
+  public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
+    if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
+      throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
+    }
+    progressBarHandlers.put(Long.valueOf(id), handler);
+    final JPanel progressPanel = progressBars.get(Long.valueOf(id));
+    if (handler.canCancel()) {
+      JButton cancel = new JButton(MessageManager.getString("action.cancel"));
       final IProgressIndicator us = this;
-      cancel.addActionListener(new ActionListener()
-      {
+      cancel.addActionListener(new ActionListener() {
 
         @Override
-        public void actionPerformed(ActionEvent e)
-        {
+        public void actionPerformed(ActionEvent e) {
           handler.cancelActivity(id);
-          us.setProgressBar(MessageManager
-                  .formatMessage("label.cancelled_params", new Object[]
-                  { ((JLabel) progressPanel.getComponent(0)).getText() }),
-                  id);
+          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
+              new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
         }
       });
       progressPanel.add(cancel, BorderLayout.EAST);
@@ -2871,10 +2131,8 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @return true if any progress bars are still active
    */
   @Override
-  public boolean operationInProgress()
-  {
-    if (progressBars != null && progressBars.size() > 0)
-    {
+  public boolean operationInProgress() {
+    if (progressBars != null && progressBars.size() > 0) {
       return true;
     }
     return false;
@@ -2887,16 +2145,11 @@ public class Desktop extends jalview.jbgui.GDesktop
    * @param viewport
    * @return alignFrame for viewport
    */
-  public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
-  {
-    if (desktop != null)
-    {
-      AlignmentPanel[] aps = getAlignmentPanels(
-              viewport.getSequenceSetId());
-      for (int panel = 0; aps != null && panel < aps.length; panel++)
-      {
-        if (aps[panel] != null && aps[panel].av == viewport)
-        {
+  public static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
+    if (desktop != null) {
+      AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
+      for (int panel = 0; aps != null && panel < aps.length; panel++) {
+        if (aps[panel] != null && aps[panel].av == viewport) {
           return aps[panel].alignFrame;
         }
       }
@@ -2904,9 +2157,9 @@ public class Desktop extends jalview.jbgui.GDesktop
     return null;
   }
 
-  public VamsasApplication getVamsasApplication()
-  {
-    return v_client;
+  public VamsasApplication getVamsasApplication() {
+    // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
+    return null;
 
   }
 
@@ -2920,8 +2173,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @return inBatchMode
    */
-  public boolean isInBatchMode()
-  {
+  public boolean isInBatchMode() {
     return inBatchMode;
   }
 
@@ -2930,26 +2182,42 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param inBatchMode
    */
-  public void setInBatchMode(boolean inBatchMode)
-  {
+  public void setInBatchMode(boolean inBatchMode) {
     this.inBatchMode = inBatchMode;
   }
 
-  public void startServiceDiscovery()
-  {
+  /**
+   * start service discovery and wait till it is done
+   */
+  public void startServiceDiscovery() {
     startServiceDiscovery(false);
   }
 
-  public void startServiceDiscovery(boolean blocking)
-  {
+  /**
+   * 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
-    if (true)
-    {
+    if (true) {
       // todo: changesupport handlers need to be transferred
-      if (discoverer == null)
-      {
+      if (discoverer == null) {
         discoverer = new jalview.ws.jws1.Discoverer();
         // register PCS handler for desktop.
         discoverer.addPropertyChangeListener(changeSupport);
@@ -2959,28 +2227,21 @@ public class Desktop extends jalview.jbgui.GDesktop
       (t0 = new Thread(discoverer)).start();
     }
 
-    if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
-    {
-      t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
-              .startDiscoverer(changeSupport);
+    if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
+      t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
     }
     Thread t3 = null;
     {
       // TODO: do rest service discovery
     }
-    if (blocking)
-    {
-      while (alive)
-      {
-        try
-        {
+    if (blocking) {
+      while (alive) {
+        try {
           Thread.sleep(15);
-        } catch (Exception e)
-        {
+        } catch (Exception e) {
         }
-        alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
-                || (t3 != null && t3.isAlive())
-                || (t0 != null && t0.isAlive());
+        alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
+            || (t0 != null && t0.isAlive());
       }
     }
   }
@@ -2990,70 +2251,50 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param evt
    */
-  protected void JalviewServicesChanged(PropertyChangeEvent evt)
-  {
-    if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
-    {
-      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
-              .getErrorMessages();
-      if (ermsg != null)
-      {
-        if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
-        {
-          if (serviceChangedDialog == null)
-          {
+  protected void JalviewServicesChanged(PropertyChangeEvent evt) {
+    if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
+      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
+      if (ermsg != null) {
+        if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
+          if (serviceChangedDialog == null) {
             // only run if we aren't already displaying one of these.
-            addDialogThread(serviceChangedDialog = new Runnable()
-            {
+            addDialogThread(serviceChangedDialog = new Runnable() {
               @Override
-              public void run()
-              {
+              public void run() {
 
                 /*
                  * 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("<html><table width=\"450\"><tr><td>" + ermsg +
+                 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
+                 * ermsg +
                  * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
                  * + " or mis-configured HTTP proxy settings.<br/>" +
-                 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
-                 * +
-                 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
-                 * ), true, true, "Web Service Configuration Problem", 450,
-                 * 400);
+                 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
+                 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
+                 * true, true, "Web Service Configuration Problem", 450, 400);
                  * 
                  * jd.waitForInput();
                  */
                 JvOptionPane.showConfirmDialog(Desktop.desktop,
-                        new JLabel("<html><table width=\"450\"><tr><td>"
-                                + ermsg + "</td></tr></table>"
-                                + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
-                                + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
-                                + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
-                                + " Tools->Preferences dialog box to change them.</p></html>"),
-                        "Web Service Configuration Problem",
-                        JvOptionPane.DEFAULT_OPTION,
-                        JvOptionPane.ERROR_MESSAGE);
+                    new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
+                        + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
+                        + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
+                        + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
+                        + " Tools->Preferences dialog box to change them.</p></html>"),
+                    "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
                 serviceChangedDialog = null;
 
               }
             });
           }
-        }
-        else
-        {
-          Cache.log.error(
-                  "Errors reported by JABA discovery service. Check web services preferences.\n"
-                          + ermsg);
+        } else {
+          jalview.bin.Console.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
         }
       }
     }
@@ -3068,8 +2309,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param url
    */
-  public static void showUrl(final String url)
-  {
+  public static void showUrl(final String url) {
     showUrl(url, Desktop.instance);
   }
 
@@ -3077,38 +2317,26 @@ public class Desktop extends jalview.jbgui.GDesktop
    * Like showUrl but allows progress handler to be specified
    * 
    * @param url
-   * @param progress
-   *          (null) or object implementing IProgressIndicator
+   * @param progress (null) or object implementing IProgressIndicator
    */
-  public static void showUrl(final String url,
-          final IProgressIndicator progress)
-  {
-    new Thread(new Runnable()
-    {
+  public static void showUrl(final String url, final IProgressIndicator progress) {
+    new Thread(new Runnable() {
       @Override
-      public void run()
-      {
-        try
-        {
-          if (progress != null)
-          {
-            progress.setProgressBar(MessageManager
-                    .formatMessage("status.opening_params", new Object[]
-                    { url }), this.hashCode());
+      public void run() {
+        try {
+          if (progress != null) {
+            progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
+                this.hashCode());
           }
           jalview.util.BrowserLauncher.openURL(url);
-        } catch (Exception ex)
-        {
+        } catch (Exception ex) {
           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                  MessageManager
-                          .getString("label.web_browser_not_found_unix"),
-                  MessageManager.getString("label.web_browser_not_found"),
-                  JvOptionPane.WARNING_MESSAGE);
+              MessageManager.getString("label.web_browser_not_found_unix"),
+              MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
 
           ex.printStackTrace();
         }
-        if (progress != null)
-        {
+        if (progress != null) {
           progress.setProgressBar(null, this.hashCode());
         }
       }
@@ -3117,10 +2345,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public static WsParamSetManager wsparamManager = null;
 
-  public static ParamManager getUserParameterStore()
-  {
-    if (wsparamManager == null)
-    {
+  public static ParamManager getUserParameterStore() {
+    if (wsparamManager == null) {
       wsparamManager = new WsParamSetManager();
     }
     return wsparamManager;
@@ -3131,28 +2357,15 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param e
    */
-  public static void hyperlinkUpdate(HyperlinkEvent e)
-  {
-    if (e.getEventType() == EventType.ACTIVATED)
-    {
+  public static void hyperlinkUpdate(HyperlinkEvent e) {
+    if (e.getEventType() == EventType.ACTIVATED) {
       String url = null;
-      try
-      {
+      try {
         url = e.getURL().toString();
         Desktop.showUrl(url);
-      } catch (Exception x)
-      {
-        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.");
-          }
+      } catch (Exception x) {
+        if (url != null) {
+          jalview.bin.Console.error("Couldn't handle string " + url + " as a URL.");
         }
         // ignore any exceptions due to dud links.
       }
@@ -3182,40 +2395,29 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param prompter
    */
-  public void addDialogThread(final Runnable prompter)
-  {
-    dialogExecutor.submit(new Runnable()
-    {
+  public void addDialogThread(final Runnable prompter) {
+    dialogExecutor.submit(new Runnable() {
       @Override
-      public void run()
-      {
-        if (dialogPause)
-        {
-          try
-          {
+      public void run() {
+        if (dialogPause) {
+          try {
             block.acquire();
-          } catch (InterruptedException x)
-          {
+          } catch (InterruptedException x) {
           }
-          ;
         }
-        if (instance == null)
-        {
+        if (instance == null) {
           return;
         }
-        try
-        {
+        try {
           SwingUtilities.invokeAndWait(prompter);
-        } catch (Exception q)
-        {
-          Cache.log.warn("Unexpected Exception in dialog thread.", q);
+        } catch (Exception q) {
+          jalview.bin.Console.warn("Unexpected Exception in dialog thread.", q);
         }
       }
     });
   }
 
-  public void startDialogQueue()
-  {
+  public void startDialogQueue() {
     // set the flag so we don't pause waiting for another permit and semaphore
     // the current task to begin
     dialogPause = false;
@@ -3232,28 +2434,22 @@ public class Desktop extends jalview.jbgui.GDesktop
    * </pre>
    */
   @Override
-  protected void snapShotWindow_actionPerformed(ActionEvent e)
-  {
+  protected void snapShotWindow_actionPerformed(ActionEvent e) {
     // currently the menu option to do this is not shown
     invalidate();
 
     int width = getWidth();
     int height = getHeight();
-    File of = new File(
-            "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
-    ImageWriterI writer = new ImageWriterI()
-    {
+    File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
+    ImageWriterI writer = new ImageWriterI() {
       @Override
-      public void exportImage(Graphics g) throws Exception
-      {
+      public void exportImage(Graphics g) throws Exception {
         paintAll(g);
-        Cache.log.info("Successfully written snapshot to file "
-                + of.getAbsolutePath());
+        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);
+    ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
     exporter.doExport(of, this, width, height, title);
   }
 
@@ -3266,31 +2462,25 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param sf
    */
-  public void explodeViews(SplitFrame sf)
-  {
+  public void explodeViews(SplitFrame sf) {
     AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
     AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
-    List<? extends AlignmentViewPanel> topPanels = oldTopFrame
-            .getAlignPanels();
-    List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
-            .getAlignPanels();
+    List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
+    List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
     int viewCount = topPanels.size();
-    if (viewCount < 2)
-    {
+    if (viewCount < 2) {
       return;
     }
 
     /*
-     * 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--)
-    {
+    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
@@ -3299,10 +2489,8 @@ public class Desktop extends jalview.jbgui.GDesktop
       AlignFrame newTopFrame = new AlignFrame(topPanel);
       newTopFrame.setSize(oldTopFrame.getSize());
       newTopFrame.setVisible(true);
-      Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
-              .getExplodedGeometry();
-      if (geometry != null)
-      {
+      Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
+      if (geometry != null) {
         newTopFrame.setSize(geometry.getSize());
       }
 
@@ -3310,27 +2498,23 @@ public class Desktop extends jalview.jbgui.GDesktop
       AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
       newBottomFrame.setSize(oldBottomFrame.getSize());
       newBottomFrame.setVisible(true);
-      geometry = ((AlignViewport) bottomPanel.getAlignViewport())
-              .getExplodedGeometry();
-      if (geometry != null)
-      {
+      geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
+      if (geometry != null) {
         newBottomFrame.setSize(geometry.getSize());
       }
 
       topPanel.av.setGatherViewsHere(false);
       bottomPanel.av.setGatherViewsHere(false);
-      JInternalFrame splitFrame = new SplitFrame(newTopFrame,
-              newBottomFrame);
-      if (geometry != null)
-      {
+      JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
+      if (geometry != null) {
         splitFrame.setLocation(geometry.getLocation());
       }
       Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
     }
 
     /*
-     * 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();
@@ -3344,8 +2528,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * @param source
    */
-  public void gatherViews(GSplitFrame source)
-  {
+  public void gatherViews(GSplitFrame source) {
     /*
      * special handling of explodedGeometry for a view within a SplitFrame: - it
      * holds the (x, y) position of the enclosing SplitFrame, and the (width,
@@ -3353,46 +2536,38 @@ public class Desktop extends jalview.jbgui.GDesktop
      */
     AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
     AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
-    myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
-            source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
-    myBottomFrame.viewport
-            .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
-                    myBottomFrame.getWidth(), myBottomFrame.getHeight()));
+    myTopFrame.viewport.setExplodedGeometry(
+        new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
+    myBottomFrame.viewport.setExplodedGeometry(
+        new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
     myTopFrame.viewport.setGatherViewsHere(true);
     myBottomFrame.viewport.setGatherViewsHere(true);
     String topViewId = myTopFrame.viewport.getSequenceSetId();
     String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
 
     JInternalFrame[] frames = desktop.getAllFrames();
-    for (JInternalFrame frame : frames)
-    {
-      if (frame instanceof SplitFrame && frame != source)
-      {
+    for (JInternalFrame frame : frames) {
+      if (frame instanceof SplitFrame && frame != source) {
         SplitFrame sf = (SplitFrame) frame;
         AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
         AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
         boolean gatherThis = false;
-        for (int a = 0; a < topFrame.alignPanels.size(); a++)
-        {
+        for (int a = 0; a < topFrame.alignPanels.size(); a++) {
           AlignmentPanel topPanel = topFrame.alignPanels.get(a);
           AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
           if (topViewId.equals(topPanel.av.getSequenceSetId())
-                  && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
-          {
+              && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
             gatherThis = true;
             topPanel.av.setGatherViewsHere(false);
             bottomPanel.av.setGatherViewsHere(false);
-            topPanel.av.setExplodedGeometry(
-                    new Rectangle(sf.getLocation(), topFrame.getSize()));
-            bottomPanel.av.setExplodedGeometry(
-                    new Rectangle(sf.getLocation(), bottomFrame.getSize()));
+            topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
+            bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
             myTopFrame.addAlignmentPanel(topPanel, false);
             myBottomFrame.addAlignmentPanel(bottomPanel, false);
           }
         }
 
-        if (gatherThis)
-        {
+        if (gatherThis) {
           topFrame.getAlignPanels().clear();
           bottomFrame.getAlignPanels().clear();
           sf.close();
@@ -3406,8 +2581,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     myTopFrame.setDisplayedView(myTopFrame.alignPanel);
   }
 
-  public static groovy.ui.Console getGroovyConsole()
-  {
+  public static groovy.ui.Console getGroovyConsole() {
     return groovyConsole;
   }
 
@@ -3416,146 +2590,88 @@ public class Desktop extends jalview.jbgui.GDesktop
    * 
    * TODO refactor to desktop utilities class
    * 
-   * @param files
-   *          - Data source strings extracted from the drop event
-   * @param protocols
-   *          - protocol for each data source extracted from the drop event
-   * @param evt
-   *          - the drop event
-   * @param t
-   *          - the payload from the drop event
+   * @param files     - Data source strings extracted from the drop event
+   * @param protocols - protocol for each data source extracted from the drop
+   *                  event
+   * @param evt       - the drop event
+   * @param t         - the payload from the drop event
    * @throws Exception
    */
-  public static void transferFromDropTarget(List<Object> files,
-          List<DataSourceType> protocols, DropTargetDropEvent evt,
-          Transferable t) throws Exception
-  {
-
-    // BH 2018 changed List<String> to List<Object> 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<File> list = (List<File>) 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
-    {
-      urlFlavour = new DataFlavor(
-              "application/x-java-url; class=java.net.URL");
-    } catch (ClassNotFoundException cfe)
-    {
-      Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
+  public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
+      Transferable t) throws Exception {
+
+    DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
+    try {
+      urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
+    } catch (ClassNotFoundException cfe) {
+      jalview.bin.Console.debug("Couldn't instantiate the URL dataflavor.", cfe);
     }
 
-    if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
-    {
+    if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
 
-      try
-      {
+      try {
         java.net.URL url = (URL) t.getTransferData(urlFlavour);
         // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
         // means url may be null.
-        if (url != null)
-        {
+        if (url != null) {
           protocols.add(DataSourceType.URL);
           files.add(url.toString());
-          Cache.log.debug("Drop handled as URL dataflavor "
-                  + files.get(files.size() - 1));
+          jalview.bin.Console.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
           return;
-        }
-        else
-        {
-          if (Platform.isAMac())
-          {
-            System.err.println(
-                    "Please ignore plist error - occurs due to problem with java 8 on OSX");
+        } else {
+          if (Platform.isAMacAndNotJS()) {
+            System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
           }
-          ;
         }
-      } catch (Throwable ex)
-      {
-        Cache.log.debug("URL drop handler failed.", ex);
+      } catch (Throwable ex) {
+        jalview.bin.Console.debug("URL drop handler failed.", ex);
       }
     }
-    if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
-    {
+    if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
       // Works on Windows and MacOSX
-      Cache.log.debug("Drop handled as javaFileListFlavor");
-      for (Object file : (List) t
-              .getTransferData(DataFlavor.javaFileListFlavor))
-      {
+      jalview.bin.Console.debug("Drop handled as javaFileListFlavor");
+      for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
         files.add(file);
         protocols.add(DataSourceType.FILE);
       }
-    }
-    else
-    {
+    } else {
       // Unix like behaviour
       boolean added = false;
       String data = null;
-      if (t.isDataFlavorSupported(uriListFlavor))
-      {
-        Cache.log.debug("Drop handled as uriListFlavor");
+      if (t.isDataFlavorSupported(uriListFlavor)) {
+        jalview.bin.Console.debug("Drop handled as uriListFlavor");
         // This is used by Unix drag system
         data = (String) t.getTransferData(uriListFlavor);
       }
-      if (data == null)
-      {
+      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");
-        if (t.isDataFlavorSupported(textDf))
-        {
+        DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
+        if (t.isDataFlavorSupported(textDf)) {
           data = (String) t.getTransferData(textDf);
         }
 
-        Cache.log.debug("Plain text drop content returned "
-                + (data == null ? "Null - failed" : data));
+        jalview.bin.Console.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
 
       }
-      if (data != null)
-      {
-        while (protocols.size() < files.size())
-        {
-          Cache.log.debug("Adding missing FILE protocol for "
-                  + files.get(protocols.size()));
+      if (data != null) {
+        while (protocols.size() < files.size()) {
+          jalview.bin.Console.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
           protocols.add(DataSourceType.FILE);
         }
-        for (java.util.StringTokenizer st = new java.util.StringTokenizer(
-                data, "\r\n"); st.hasMoreTokens();)
-        {
+        for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
           added = true;
           String s = st.nextToken();
-          if (s.startsWith("#"))
-          {
+          if (s.startsWith("#")) {
             // the line is a comment (as per the RFC 2483)
             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());
-          }
-          else
-          {
+          } else {
             // otherwise preserve old behaviour: catch all for file objects
             java.io.File file = new java.io.File(uri);
             protocols.add(DataSourceType.FILE);
@@ -3564,70 +2680,46 @@ public class Desktop extends jalview.jbgui.GDesktop
         }
       }
 
-      if (Cache.log.isDebugEnabled())
-      {
-        if (data == null || !added)
-        {
+      if (jalview.bin.Console.isDebugEnabled()) {
+        if (data == null || !added) {
 
-          if (t.getTransferDataFlavors() != null
-                  && t.getTransferDataFlavors().length > 0)
-          {
-            Cache.log.debug(
-                    "Couldn't resolve drop data. Here are the supported flavors:");
-            for (DataFlavor fl : t.getTransferDataFlavors())
-            {
-              Cache.log.debug(
-                      "Supported transfer dataflavor: " + fl.toString());
+          if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
+            jalview.bin.Console.debug("Couldn't resolve drop data. Here are the supported flavors:");
+            for (DataFlavor fl : t.getTransferDataFlavors()) {
+              jalview.bin.Console.debug("Supported transfer dataflavor: " + fl.toString());
               Object df = t.getTransferData(fl);
-              if (df != null)
-              {
-                Cache.log.debug("Retrieves: " + df);
-              }
-              else
-              {
-                Cache.log.debug("Retrieved nothing");
+              if (df != null) {
+                jalview.bin.Console.debug("Retrieves: " + df);
+              } else {
+                jalview.bin.Console.debug("Retrieved nothing");
               }
             }
-          }
-          else
-          {
-            Cache.log.debug("Couldn't resolve dataflavor for drop: "
-                    + t.toString());
+          } else {
+            jalview.bin.Console.debug("Couldn't resolve dataflavor for drop: " + t.toString());
           }
         }
       }
     }
-    if (Platform.isWindows())
-
-    {
-      Cache.log.debug("Scanning dropped content for Windows Link Files");
+    if (Platform.isWindowsAndNotJS()) {
+      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();
+      for (int f = 0; f < files.size(); f++) {
+        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
-          {
+            && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
+          try {
             Object obj = files.get(f);
-            File lf = (obj instanceof File ? (File) obj
-                    : new File((String) obj));
+            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
-                    + " to extract protocol: " + protocols.get(f));
-          } catch (Exception ex)
-          {
-            Cache.log.error(
-                    "Couldn't parse " + files.get(f) + " as a link file.",
-                    ex);
+            jalview.bin.Console.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
+          } catch (Exception ex) {
+            jalview.bin.Console.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
           }
         }
       }
@@ -3639,8 +2731,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    * depending on the state of the controlling menu item
    */
   @Override
-  protected void showExperimental_actionPerformed(boolean selected)
-  {
+  protected void showExperimental_actionPerformed(boolean selected) {
     Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
   }
 
@@ -3650,29 +2741,20 @@ public class Desktop extends jalview.jbgui.GDesktop
    * restricted to viewers of a specified class, or viewers linked to a specified
    * alignment panel.
    * 
-   * @param apanel
-   *          if not null, only return viewers linked to this panel
-   * @param structureViewerClass
-   *          if not null, only return viewers of this class
+   * @param apanel               if not null, only return viewers linked to this
+   *                             panel
+   * @param structureViewerClass if not null, only return viewers of this class
    * @return
    */
-  public List<StructureViewerBase> getStructureViewers(
-          AlignmentPanel apanel,
-          Class<? extends StructureViewerBase> structureViewerClass)
-  {
+  public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
+      Class<? extends StructureViewerBase> structureViewerClass) {
     List<StructureViewerBase> result = new ArrayList<>();
     JInternalFrame[] frames = Desktop.instance.getAllFrames();
 
-    for (JInternalFrame frame : frames)
-    {
-      if (frame instanceof StructureViewerBase)
-      {
-        if (structureViewerClass == null
-                || structureViewerClass.isInstance(frame))
-        {
-          if (apanel == null
-                  || ((StructureViewerBase) frame).isLinkedWith(apanel))
-          {
+    for (JInternalFrame frame : frames) {
+      if (frame instanceof StructureViewerBase) {
+        if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
+          if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
             result.add((StructureViewerBase) frame);
           }
         }
@@ -3680,4 +2762,30 @@ 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));
+    }
+  }
 }