merge from develop: Most of the changes
authorsoares <bsoares@dundee.ac.uk>
Tue, 7 Apr 2020 16:55:50 +0000 (17:55 +0100)
committersoares <bsoares@dundee.ac.uk>
Tue, 7 Apr 2020 16:55:50 +0000 (17:55 +0100)
1  2 
src/jalview/gui/Desktop.java

   */
  package jalview.gui;
  
 -import jalview.api.AlignViewportI;
 -import jalview.api.AlignmentViewPanel;
 -import jalview.bin.Cache;
 -import jalview.bin.Jalview;
 -import jalview.io.BackupFiles;
 -import jalview.io.DataSourceType;
 -import jalview.io.FileFormat;
 -import jalview.io.FileFormatException;
 -import jalview.io.FileFormatI;
 -import jalview.io.FileFormats;
 -import jalview.io.FileLoader;
 -import jalview.io.FormatAdapter;
 -import jalview.io.IdentifyFile;
 -import jalview.io.JalviewFileChooser;
 -import jalview.io.JalviewFileView;
 -import jalview.jbgui.GSplitFrame;
 -import jalview.jbgui.GStructureViewer;
 -import jalview.project.Jalview2XML;
 -import jalview.structure.StructureSelectionManager;
 -import jalview.urls.IdOrgSettings;
 -import jalview.util.ImageMaker;
 -import jalview.util.MessageManager;
 -import jalview.util.Platform;
 -import jalview.util.UrlConstants;
 -import jalview.viewmodel.AlignmentViewport;
 -import jalview.ws.params.ParamManager;
 -import jalview.ws.utils.UrlDownloadClient;
 -
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
@@@ -58,6 -86,7 +58,6 @@@ 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;
@@@ -82,7 -111,6 +82,7 @@@ import javax.swing.JMenuItem
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
  import javax.swing.JProgressBar;
 +import javax.swing.JTextField;
  import javax.swing.KeyStroke;
  import javax.swing.SwingUtilities;
  import javax.swing.event.HyperlinkEvent;
@@@ -92,37 -120,6 +92,37 @@@ import javax.swing.event.InternalFrameE
  
  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.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
   * 
@@@ -134,15 -131,6 +134,15 @@@ public class Desktop extends jalview.jb
          implements DropTargetListener, ClipboardOwner, IProgressIndicator,
          jalview.api.StructureSelectionManagerProvider
  {
 +  private static final String CITATION = "<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";
 +
 +  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;
  
    protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
  
-   public static HashMap<String, FileWriter> savingFiles = new HashMap<>();
+   public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
  
    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
  
  
    public static MyDesktopPane desktop;
  
 +  public static MyDesktopPane getDesktop()
 +  {
 +    // BH 2018 could use currentThread() here as a reference to a
 +    // Hashtable<Thread, MyDesktopPane> in JavaScript
 +    return desktop;
 +  }
 +
    static int openFrameCount = 0;
  
    static final int xOffset = 30;
     */
    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
      instance = this;
  
      doConfigureStructurePrefs();
 -    setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
 +    setTitle("Jalview " + Cache.getProperty("VERSION"));
      /*
      if (!Platform.isAMac())
      {
      try
      {
        APQHandlers.setAPQHandlers(this);
 -    } catch (Exception e)
 -    {
 -      System.out.println("Cannot set APQHandlers");
 -      // e.printStackTrace();
      } catch (Throwable t)
      {
 -      System.out.println("Cannot set APQHandlers");
 +      System.out.println("Error setting APQHandlers: " + t.toString());
        // t.printStackTrace();
      }
  
 -
      addWindowListener(new WindowAdapter()
      {
  
        }
      });
  
 -    boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
 +    boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE",
              false);
  
 -    boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
 +    boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE",
              false);
      desktop = new MyDesktopPane(selmemusage);
 +
      showMemusage.setSelected(selmemusage);
      desktop.setBackground(Color.white);
  
      // JScrollPane sp = new JScrollPane();
      // sp.getViewport().setView(desktop);
      // getContentPane().add(sp, BorderLayout.CENTER);
 +
 +    // BH 2018 - just an experiment to try unclipped JInternalFrames.
 +    if (Platform.isJS())
 +    {
 +      getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
 +    }
 +
      getContentPane().add(desktop, BorderLayout.CENTER);
      desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
  
      // 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()
 +            (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
 +                    : Platform.isAMacAndNotJS()
                              ? new AquaInternalFrameManager(
                                      desktop.getDesktopManager())
                              : desktop.getDesktopManager())));
      else
      {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 -      setBounds((screenSize.width - 900) / 2, (screenSize.height - 650) / 2,
 -              900, 650);
 +      int xPos = Math.max(5, (screenSize.width - 900) / 2);
 +      int yPos = Math.max(5, (screenSize.height - 650) / 2);
 +      setBounds(xPos, yPos, 900, 650);
      }
 -    jconsole = new Console(this, showjconsole);
 -    // add essential build information
 -    jconsole.setHeader(jalview.bin.Cache.getVersionDetailsForConsole());
  
 -    showConsole(showjconsole);
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      jconsole = new Console(this, showjconsole);
 +      jconsole.setHeader(Cache.getVersionDetailsForConsole());
 +      showConsole(showjconsole);
 +
 +      showNews.setVisible(false);
  
 -    showNews.setVisible(false);
 +      experimentalFeatures.setSelected(showExperimental());
  
 -    experimentalFeatures.setSelected(showExperimental());
 +      getIdentifiersOrgData();
  
 -    getIdentifiersOrgData();
 +      checkURLLinks();
  
 -    checkURLLinks();
 +      // Spawn a thread that shows the splashscreen
 +
 +      SwingUtilities.invokeLater(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          new SplashScreen(true);
 +        }
 +      });
 +
 +      // Thread off a new instance of the file chooser - this reduces the time
 +      // it
 +      // takes to open it later on.
 +      new Thread(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          Cache.log.debug("Filechooser init thread started.");
 +          String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 +          JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
 +                  fileFormat);
 +          Cache.log.debug("Filechooser init thread finished.");
 +        }
 +      }).start();
 +      // Add the service change listener
 +      changeSupport.addJalviewPropertyChangeListener("services",
 +              new PropertyChangeListener()
 +              {
 +
 +                @Override
 +                public void propertyChange(PropertyChangeEvent evt)
 +                {
 +                  Cache.log.debug("Firing service changed event for "
 +                          + evt.getNewValue());
 +                  JalviewServicesChanged(evt);
 +                }
 +              });
 +    }
 +
 +    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
  
      this.addWindowListener(new WindowAdapter()
      {
      });
      desktop.addMouseListener(ma);
  
 -    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
 -    // Spawn a thread that shows the splashscreen
 -    SwingUtilities.invokeLater(new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        new SplashScreen();
 -      }
 -    });
 -
 -    // Thread off a new instance of the file chooser - this reduces the time it
 -    // takes to open it later on.
 -    new Thread(new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        Cache.log.debug("Filechooser init thread started.");
 -        String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 -        JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
 -                fileFormat);
 -        Cache.log.debug("Filechooser init thread finished.");
 -      }
 -    }).start();
 -    // Add the service change listener
 -    changeSupport.addJalviewPropertyChangeListener("services",
 -            new PropertyChangeListener()
 -            {
 -
 -              @Override
 -              public void propertyChange(PropertyChangeEvent evt)
 -              {
 -                Cache.log.debug("Firing service changed event for "
 -                        + evt.getNewValue());
 -                JalviewServicesChanged(evt);
 -              }
 -
 -            });
    }
  
    /**
      // configure services
      StructureSelectionManager ssm = StructureSelectionManager
              .getStructureSelectionManager(this);
 -    if (jalview.bin.Cache.getDefault(Preferences.ADD_SS_ANN, true))
 +    if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
      {
 -      ssm.setAddTempFacAnnot(jalview.bin.Cache
 +      ssm.setAddTempFacAnnot(Cache
                .getDefault(Preferences.ADD_TEMPFACT_ANN, true));
 -      ssm.setProcessSecondaryStructure(jalview.bin.Cache
 +      ssm.setProcessSecondaryStructure(Cache
                .getDefault(Preferences.STRUCT_FROM_PDB, true));
        ssm.setSecStructServices(
 -              jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, true));
 +              Cache.getDefault(Preferences.USE_RNAVIEW, true));
      }
      else
      {
        public void run()
        {
          Cache.log.debug("Starting news thread.");
 -
          jvnews = new BlogReader(me);
          showNews.setVisible(true);
          Cache.log.debug("Completed news thread.");
        public void run()
        {
          Cache.log.debug("Downloading data from identifiers.org");
 -        UrlDownloadClient client = new UrlDownloadClient();
          try
          {
 -          client.download(IdOrgSettings.getUrl(),
 +          UrlDownloadClient.download(IdOrgSettings.getUrl(),
                    IdOrgSettings.getDownloadLocation());
          } catch (IOException e)
          {
          }
        }
      }).start();
 -    ;
 +    
    }
  
    @Override
  
    void showNews(boolean visible)
    {
 +    Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
 +    showNews.setSelected(visible);
 +    if (visible && !jvnews.isVisible())
      {
 -      Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
 -      showNews.setSelected(visible);
 -      if (visible && !jvnews.isVisible())
 +      new Thread(new Runnable()
        {
 -        new Thread(new Runnable()
 +        @Override
 +        public void run()
          {
 -          @Override
 -          public void run()
 -          {
 -            long now = System.currentTimeMillis();
 -            Desktop.instance.setProgressBar(
 -                    MessageManager.getString("status.refreshing_news"),
 -                    now);
 -            jvnews.refreshNews();
 -            Desktop.instance.setProgressBar(null, now);
 -            jvnews.showNews();
 -          }
 -        }).start();
 -      }
 +          long now = System.currentTimeMillis();
 +          Desktop.instance.setProgressBar(
 +                  MessageManager.getString("status.refreshing_news"), now);
 +          jvnews.refreshNews();
 +          Desktop.instance.setProgressBar(null, now);
 +          jvnews.showNews();
 +        }
 +      }).start();
      }
    }
  
    {
      // 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
 +    String x = Cache.getProperty(windowName + "SCREEN_X");
 +    String y = Cache.getProperty(windowName + "SCREEN_Y");
 +    String width = Cache
              .getProperty(windowName + "SCREEN_WIDTH");
 -    String height = jalview.bin.Cache
 +    String height = Cache
              .getProperty(windowName + "SCREEN_HEIGHT");
      if ((x != null) && (y != null) && (width != null) && (height != null))
      {
        int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
                iw = Integer.parseInt(width), ih = Integer.parseInt(height);
 -      if (jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
 +      if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
        {
          // attempt #1 - try to cope with change in screen geometry - this
          // version doesn't preserve original jv aspect ratio.
          // take ratio of current screen size vs original screen size.
          double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(
 -                jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
 +                Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
          double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(
 -                jalview.bin.Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
 +                Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
          // rescale the bounds depending upon the current screen geometry.
          ix = (int) (ix * sw);
          iw = (int) (iw * sw);
          ih = (int) (ih * sh);
          while (ix >= screenSize.width)
          {
 -          jalview.bin.Cache.log.debug(
 +          Cache.log.debug(
                    "Window geometry location recall error: shifting horizontal to within screenbounds.");
            ix -= screenSize.width;
          }
          while (iy >= screenSize.height)
          {
 -          jalview.bin.Cache.log.debug(
 +          Cache.log.debug(
                    "Window geometry location recall error: shifting vertical to within screenbounds.");
            iy -= screenSize.height;
          }
 -        jalview.bin.Cache.log.debug(
 +        Cache.log.debug(
                  "Got last known dimensions for " + windowName + ": x:" + ix
                          + " y:" + iy + " width:" + iw + " height:" + ih);
        }
      frame.setResizable(resizable);
      frame.setMaximizable(resizable);
      frame.setIconifiable(resizable);
 -    frame.setOpaque(false);
 +    frame.setOpaque(Platform.isJS());
  
      if (frame.getX() < 1 && frame.getY() < 1)
      {
            menuItem.removeActionListener(menuItem.getActionListeners()[0]);
          }
          windowMenu.remove(menuItem);
 -      };
 +      }
      });
  
      menuItem.addActionListener(new ActionListener()
            frame.setIcon(false);
          } catch (java.beans.PropertyVetoException ex)
          {
 -
 +          // System.err.println(ex.toString());
          }
        }
      });
    }
  
    /**
 -   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
 -   * the window
 +   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
 +   * window
     * 
     * @param frame
     */
      KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
              InputEvent.CTRL_DOWN_MASK);
      KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
 -            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
 +            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
  
      InputMap inputMap = frame
              .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
 -    List<String> files = new ArrayList<>();
 +    List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
        {
          for (int i = 0; i < files.size(); i++)
          {
 -          String file = files.get(i).toString();
 +          // BH 2018 File or String
 +          Object file = files.get(i);
 +          String fileName = file.toString();
            DataSourceType protocol = (protocols == null)
                    ? DataSourceType.FILE
                    : protocols.get(i);
            FileFormatI format = null;
  
 -          if (file.endsWith(".jar"))
 +          if (fileName.endsWith(".jar"))
            {
              format = FileFormat.Jalview;
  
            {
              format = new IdentifyFile().identify(file, protocol);
            }
 -
 -          new FileLoader().LoadFile(file, protocol, format);
 +          if (file instanceof File)
 +          {
 +            Platform.cacheFileData((File) file);
 +          }
 +          new FileLoader().LoadFile(null, file, protocol, format);
  
          }
        } catch (Exception ex)
              MessageManager.getString("label.open_local_file"));
      chooser.setToolTipText(MessageManager.getString("action.open"));
  
 -    int value = chooser.showOpenDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      String choice = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY",
 -              chooser.getSelectedFile().getParent());
 +      @Override
 +      public void run()
 +      {
 +        File selectedFile = chooser.getSelectedFile();
 +        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
  
 -      FileFormatI format = chooser.getSelectedFormat();
 +        FileFormatI format = chooser.getSelectedFormat();
  
 -      /*
 -       * Call IdentifyFile to verify the file contains what its extension implies.
 -       * Skip this step for dynamically added file formats, because
 -       * IdentifyFile does not know how to recognise them.
 -       */
 -      if (FileFormats.getInstance().isIdentifiable(format))
 -      {
 -        try
 -        {
 -          format = new IdentifyFile().identify(choice, DataSourceType.FILE);
 -        } catch (FileFormatException e)
 +        /*
 +         * Call IdentifyFile to verify the file contains what its extension implies.
 +         * Skip this step for dynamically added file formats, because
 +         * IdentifyFile does not know how to recognise them.
 +         */
 +        if (FileFormats.getInstance().isIdentifiable(format))
          {
 -          // format = null; //??
 +          try
 +          {
 +            format = new IdentifyFile().identify(selectedFile,
 +                    DataSourceType.FILE);
 +          } catch (FileFormatException e)
 +          {
 +            // format = null; //??
 +          }
          }
 -      }
  
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, choice, DataSourceType.FILE,
 -                format);
 +        new FileLoader().LoadFile(viewport, selectedFile,
 +                DataSourceType.FILE, format);
        }
 -      else
 -      {
 -        new FileLoader().LoadFile(choice, DataSourceType.FILE, format);
 -      }
 -    }
 +    });
 +    chooser.showOpenDialog(this);
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Shows a dialog for input of a URL at which to retrieve alignment data
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param viewport
     */
    @Override
    public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
      // for viewing
      JLabel label = new JLabel(
              MessageManager.getString("label.input_file_url"));
  
      JPanel panel = new JPanel(new GridLayout(2, 1));
      panel.add(label);
 -    panel.add(history);
 -    history.setPreferredSize(new Dimension(400, 20));
 -    history.setEditable(true);
 -    history.addItem("http://www.");
 -
 -    String historyItems = jalview.bin.Cache.getProperty("RECENT_URL");
  
 -    StringTokenizer st;
 -
 -    if (historyItems != null)
 -    {
 -      st = new StringTokenizer(historyItems, "\t");
 -
 -      while (st.hasMoreTokens())
 -      {
 -        history.addItem(st.nextElement());
 -      }
 -    }
 -
 -    int reply = JvOptionPane.showInternalConfirmDialog(desktop, panel,
 -            MessageManager.getString("label.input_alignment_from_url"),
 -            JvOptionPane.OK_CANCEL_OPTION);
 -
 -    if (reply != JvOptionPane.OK_OPTION)
 +    /*
 +     * the URL to fetch is
 +     * Java: an editable combobox with history
 +     * JS: (pending JAL-3038) a plain text field
 +     */
 +    JComponent history;
 +    String urlBase = "http://www.";
 +    if (Platform.isJS())
      {
 -      return;
 +      history = new JTextField(urlBase, 35);
      }
 -
 -    String url = history.getSelectedItem().toString();
 -
 -    if (url.toLowerCase().endsWith(".jar"))
 +    else
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
      {
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                FileFormat.Jalview);
 -      }
 -      else
 +      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)
        {
 -        new FileLoader().LoadFile(url, DataSourceType.URL,
 -                FileFormat.Jalview);
 +        for (String token : historyItems.split("\\t"))
 +        {
 +          asCombo.addItem(token);
 +        }
        }
 +      history = asCombo;
      }
 -    else
 +    panel.add(history);
 +
 +    Object[] options = new Object[] { MessageManager.getString("action.ok"),
 +        MessageManager.getString("action.cancel") };
 +    Runnable action = new Runnable()
      {
 -      FileFormatI format = null;
 -      try
 -      {
 -        format = new IdentifyFile().identify(url, DataSourceType.URL);
 -      } catch (FileFormatException e)
 +      @Override
 +      public void run()
        {
 -        // TODO revise error handling, distinguish between
 -        // URL not found and response not valid
 -      }
 +        @SuppressWarnings("unchecked")
 +        String url = (history instanceof JTextField
 +                ? ((JTextField) history).getText()
 +                : ((JComboBox<String>) history).getSelectedItem()
 +                        .toString());
  
 -      if (format == null)
 -      {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -                MessageManager.formatMessage("label.couldnt_locate",
 -                        new Object[]
 -                        { url }),
 -                MessageManager.getString("label.url_not_found"),
 -                JvOptionPane.WARNING_MESSAGE);
 +        if (url.toLowerCase().endsWith(".jar"))
 +        {
 +          if (viewport != null)
 +          {
 +            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                    FileFormat.Jalview);
 +          }
 +          else
 +          {
 +            new FileLoader().LoadFile(url, DataSourceType.URL,
 +                    FileFormat.Jalview);
 +          }
 +        }
 +        else
 +        {
 +          FileFormatI format = null;
 +          try
 +          {
 +            format = new IdentifyFile().identify(url, DataSourceType.URL);
 +          } catch (FileFormatException e)
 +          {
 +            // TODO revise error handling, distinguish between
 +            // URL not found and response not valid
 +          }
  
 -        return;
 -      }
 +          if (format == null)
 +          {
 +            String msg = MessageManager
 +                    .formatMessage("label.couldnt_locate", url);
 +            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
 +                    MessageManager.getString("label.url_not_found"),
 +                    JvOptionPane.WARNING_MESSAGE);
  
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                format);
 -      }
 -      else
 -      {
 -        new FileLoader().LoadFile(url, DataSourceType.URL, format);
 +            return;
 +          }
 +
 +          if (viewport != null)
 +          {
 +            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                    format);
 +          }
 +          else
 +          {
 +            new FileLoader().LoadFile(url, DataSourceType.URL, format);
 +          }
 +        }
        }
 -    }
 +    };
 +    String dialogOption = MessageManager
 +            .getString("label.input_alignment_from_url");
 +    JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
 +            .showInternalDialog(panel, dialogOption,
 +                    JvOptionPane.YES_NO_CANCEL_OPTION,
 +                    JvOptionPane.PLAIN_MESSAGE, null, options,
 +                    MessageManager.getString("action.ok"));
    }
  
    /**
    public void quit()
    {
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
 -    jalview.bin.Cache.setProperty("SCREENGEOMETRY_WIDTH",
 +    Cache.setProperty("SCREENGEOMETRY_WIDTH",
              screen.width + "");
 -    jalview.bin.Cache.setProperty("SCREENGEOMETRY_HEIGHT",
 +    Cache.setProperty("SCREENGEOMETRY_HEIGHT",
              screen.height + "");
      storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
              getWidth(), getHeight()));
  
    private void storeLastKnownDimensions(String string, Rectangle jc)
    {
 -    jalview.bin.Cache.log.debug("Storing last known dimensions for "
 +    Cache.log.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 + "");
    }
  
    /**
    @Override
    public void aboutMenuItem_actionPerformed(ActionEvent e)
    {
 -    // StringBuffer message = getAboutMessage(false);
 -    // JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -    //
 -    // message.toString(), "About Jalview", JvOptionPane.INFORMATION_MESSAGE);
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
 -        new SplashScreen(true);
 +        new SplashScreen(false);
        }
      }).start();
    }
  
 -  public StringBuffer getAboutMessage(boolean shortv)
 +  /**
 +   * Returns the html text for the About screen, including any available version
 +   * number, build details, author details and citation reference, but without
 +   * the enclosing {@code html} tags
 +   * 
 +   * @return
 +   */
 +  public String getAboutMessage()
    {
 -    StringBuffer message = new StringBuffer();
 -    message.append("<html>");
 -    if (shortv)
 -    {
 -      message.append("<h1><strong>Version: "
 -              + jalview.bin.Cache.getProperty("VERSION")
 -              + "</strong></h1>");
 -      message.append("<strong>Built: <em>"
 -              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
 -              + "</em> from " + jalview.bin.Cache.getBuildDetailsForSplash()
 -              + "</strong>");
 -
 -    }
 -    else
 -    {
 -
 -      message.append("<strong>Version "
 -              + jalview.bin.Cache.getProperty("VERSION")
 -              + "; last updated: "
 -              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
 -    }
 +    StringBuilder message = new StringBuilder(1024);
 +    message.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>");
  
 -    if (jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
 -            .equals("Checking"))
 +    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 (!jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
 -            .equals(jalview.bin.Cache.getProperty("VERSION")))
 +    else if (!latestVersion.equals(Cache.getProperty("VERSION")))
      {
        boolean red = false;
 -      if (jalview.bin.Cache.getProperty("VERSION").toLowerCase()
 +      if (Cache.getProperty("VERSION").toLowerCase()
                .indexOf("automated build") == -1)
        {
          red = true;
          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")
 -              + " !!");
 +      message.append("<br>!! Version ")
 +              .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
 +              .append(" is available for download from ")
 +              .append(Cache.getDefault("www.jalview.org",
 +                      "http://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);
 +
 +    return message.toString();
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Action on requesting Help documentation
     */
    @Override
 -  public void documentationMenuItem_actionPerformed(ActionEvent e)
 +  public void documentationMenuItem_actionPerformed()
    {
      try
      {
 -      Help.showHelpWindow();
 +      if (Platform.isJS())
 +      {
 +        BrowserLauncher.openURL("http://www.jalview.org/help.html");
 +      }
 +      else
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
 +      {
 +        Help.showHelpWindow();
 +      }
      } catch (Exception ex)
      {
 +      System.err.println("Error opening help: " + ex.getMessage());
      }
    }
  
    protected void garbageCollect_actionPerformed(ActionEvent e)
    {
      // We simply collect the garbage
 -    jalview.bin.Cache.log.debug("Collecting garbage...");
 +    Cache.log.debug("Collecting garbage...");
      System.gc();
 -    jalview.bin.Cache.log.debug("Finished garbage collection.");
 +    Cache.log.debug("Finished garbage collection.");
    }
  
    /*
     */
    void showConsole(boolean selected)
    {
 -    showConsole.setSelected(selected);
      // TODO: decide if we should update properties file
 -    Cache.setProperty("SHOW_JAVA_CONSOLE",
 -            Boolean.valueOf(selected).toString());
 -    jconsole.setVisible(selected);
 +    if (jconsole != null) // BH 2018
 +    {
 +      showConsole.setSelected(selected);
 +      Cache.setProperty("SHOW_JAVA_CONSOLE",
 +              Boolean.valueOf(selected).toString());
 +      jconsole.setVisible(selected);
 +    }
    }
  
    void reorderAssociatedWindows(boolean minimize, boolean close)
    }
  
    /**
 -   * Shows a file chooser dialog and writes out the current session as a Jalview
 -   * project file
 +   * Prompts the user to choose a file and then saves the Jalview state as a
 +   * Jalview project file
     */
    @Override
    public void saveState_actionPerformed()
            setProgressBar(MessageManager.formatMessage(
                    "label.saving_jalview_project", new Object[]
                    { chosenFile.getName() }), chosenFile.hashCode());
 -          jalview.bin.Cache.setProperty("LAST_DIRECTORY",
 +          Cache.setProperty("LAST_DIRECTORY",
                    chosenFile.getParent());
            // TODO catch and handle errors for savestate
            // TODO prevent user from messing with the Desktop whilst we're saving
            try
            {
 -            BackupFiles backupfiles = new BackupFiles(chosenFile);
 +              boolean doBackup = BackupFiles.getEnabled();
 +            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
  
 -            new Jalview2XML().saveState(backupfiles.getTempFile());
 +            new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
  
 -            backupfiles.setWriteSuccess(true);
 -            backupfiles.rollBackupsAndRenameTempFile();
 +            if (doBackup)
 +            {
 +              backupfiles.setWriteSuccess(true);
 +              backupfiles.rollBackupsAndRenameTempFile();
 +            }
            } catch (OutOfMemoryError oom)
            {
              new OOMWarning("Whilst saving current state to "
            setProgressBar(null, chosenFile.hashCode());
          }
        }).start();
 -    }
 +      }
    }
  
    @Override
                                              // allowBackupFiles
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
 -
 -    int value = chooser.showOpenDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      final File selectedFile = chooser.getSelectedFile();
 -      setProjectFile(selectedFile);
 -      final String choice = selectedFile.getAbsolutePath();
 -      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 -      new Thread(new Runnable()
 +      @Override
 +      public void run()
        {
 -        @Override
 -        public void run()
 +        File selectedFile = chooser.getSelectedFile();
 +        setProjectFile(selectedFile);
 +        String choice = selectedFile.getAbsolutePath();
 +        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 +        new Thread(new Runnable()
          {
 -          setProgressBar(MessageManager.formatMessage(
 -                  "label.loading_jalview_project", new Object[]
 -                  { choice }), choice.hashCode());
 -          try
 -          {
 -            new Jalview2XML().loadJalviewAlign(choice);
 -          } catch (OutOfMemoryError oom)
 -          {
 -            new OOMWarning("Whilst loading project from " + choice, oom);
 -          } catch (Exception ex)
 +          @Override
 +          public void run()
            {
 -            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);
 +              try 
 +            {
 +              new 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);
 +              }
            }
 -          setProgressBar(null, choice.hashCode());
 -        }
 -      }).start();
 -    }
 +        }).start();
 +      }
 +    });
 +    
 +    chooser.showOpenDialog(this);
    }
  
    @Override
  
    ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
  
 -  public void startLoading(final String fileName)
 +  public void startLoading(final Object fileName)
    {
      if (fileLoadingCount == 0)
      {
  
    /**
     * 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.
 +   * 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
     */
          }
        }
      }
 +
      // refresh the feature setting UI for the source frame if it exists
      if (source.featureSettings != null
              && source.featureSettings.isOpen())
      {
        source.showFeatureSettingsUI();
      }
 -
    }
  
    public JInternalFrame[] getAllFrames()
  
    /**
     * Proxy class for JDesktopPane which optionally displays the current memory
 -   * usage and highlights the desktop area with a red bar if free memory runs
 -   * low.
 +   * usage and highlights the desktop area with a red bar if free memory runs low.
     * 
     * @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;
        openGroovyConsole();
      } catch (Exception ex)
      {
 -      jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
 +      Cache.log.error("Groovy Shell Creation failed.", ex);
        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
  
                MessageManager.getString("label.couldnt_create_groovy_shell"),
    }
  
    /**
 -   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
 -   * binding when opened
 +   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
 +   * when opened
     */
    protected void addQuitHandler()
    {
    @Override
    public void setProgressBar(String message, long id)
    {
 +    // Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);
 +
      if (progressBars == null)
      {
        progressBars = new Hashtable<>();
    }
  
    /**
 -   * This will return the first AlignFrame holding the given viewport instance.
 -   * It will break if there are more than one AlignFrames viewing a particular
 -   * av.
 +   * This will return the first AlignFrame holding the given viewport instance. It
 +   * will break if there are more than one AlignFrames viewing a particular av.
     * 
     * @param viewport
     * @return alignFrame for viewport
            } catch (InterruptedException x)
            {
            }
 -          ;
          }
          if (instance == null)
          {
      block.release();
    }
  
 +  /**
 +   * Outputs an image of the desktop to file in EPS format, after prompting the
 +   * user for choice of Text or Lineart character rendering (unless a preference
 +   * has been set). The file name is generated as
 +   * 
 +   * <pre>
 +   * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
 +   * </pre>
 +   */
    @Override
    protected void snapShotWindow_actionPerformed(ActionEvent e)
    {
 +    // currently the menu option to do this is not shown
      invalidate();
 -    File of;
 -    ImageMaker im = new jalview.util.ImageMaker(
 -            this, ImageMaker.TYPE.EPS, "View of Desktop", getWidth(),
 -            getHeight(), of = new File("Jalview_snapshot"
 -                    + System.currentTimeMillis() + ".eps"),
 -            "View of desktop", null, 0, false);
 -    try
 -    {
 -      paintAll(im.getGraphics());
 -      im.writeImage();
 -    } catch (Exception q)
 +
 +    int width = getWidth();
 +    int height = getHeight();
 +    File of = new File(
 +            "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
 +    ImageWriterI writer = new ImageWriterI()
      {
 -      Cache.log.error("Couldn't write snapshot to " + of.getAbsolutePath(),
 -              q);
 -      return;
 -    }
 -    Cache.log.info("Successfully written snapshot to file "
 -            + of.getAbsolutePath());
 +      @Override
 +      public void exportImage(Graphics g) throws Exception
 +      {
 +        paintAll(g);
 +        Cache.log.info("Successfully written snapshot to file "
 +                + of.getAbsolutePath());
 +      }
 +    };
 +    String title = "View of desktop";
 +    ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
 +            title);
 +    exporter.doExport(of, this, width, height, title);
    }
  
    /**
     * Explode the views in the given SplitFrame into separate SplitFrame windows.
 -   * This respects (remembers) any previous 'exploded geometry' i.e. the size
 -   * and location last time the view was expanded (if any). However it does not
 +   * This respects (remembers) any previous 'exploded geometry' i.e. the size and
 +   * location last time the view was expanded (if any). However it does not
     * remember the split pane divider location - this is set to match the
     * 'exploding' frame.
     * 
     *          - the payload from the drop event
     * @throws Exception
     */
 -  public static void transferFromDropTarget(List<String> files,
 +  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
          }
          else
          {
 -          if (Platform.isAMac())
 +          if (Platform.isAMacAndNotJS())
            {
              System.err.println(
                      "Please ignore plist error - occurs due to problem with java 8 on OSX");
            }
 -          ;
          }
        } catch (Throwable ex)
        {
        for (Object file : (List) t
                .getTransferData(DataFlavor.javaFileListFlavor))
        {
 -        files.add(((File) file).toString());
 +        files.add(file);
          protocols.add(DataSourceType.FILE);
        }
      }
          }
        }
      }
 -    if (Platform.isWindows())
 -
 +    if (Platform.isWindowsAndNotJS())
      {
        Cache.log.debug("Scanning dropped content for Windows Link Files");
  
        // resolve any .lnk files in the file drop
        for (int f = 0; f < files.size(); f++)
        {
 -        String source = files.get(f).toLowerCase();
 +        String source = files.get(f).toString().toLowerCase();
          if (protocols.get(f).equals(DataSourceType.FILE)
                  && (source.endsWith(".lnk") || source.endsWith(".url")
                          || source.endsWith(".site")))
          {
            try
            {
 -            File lf = new File(files.get(f));
 +            Object obj = files.get(f);
 +            File lf = (obj instanceof File ? (File) obj
 +                    : new File((String) obj));
              // process link file to get a URL
              Cache.log.debug("Found potential link file: " + lf);
              WindowsShortcut wscfile = new WindowsShortcut(lf);
    }
  
    /**
 -   * Answers a (possibly empty) list of any structure viewer frames (currently
 -   * for either Jmol or Chimera) which are currently open. This may optionally
 -   * be restricted to viewers of a specified class, or viewers linked to a
 -   * specified alignment panel.
 +   * Answers a (possibly empty) list of any structure viewer frames (currently for
 +   * either Jmol or Chimera) which are currently open. This may optionally be
 +   * 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