Merge branch 'feature/JAL-3686_slivka_client_js_update' into alpha/merge_212_JalviewJ... alpha/merge_212_JalviewJS_2112
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 9 Jul 2020 14:43:01 +0000 (15:43 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 9 Jul 2020 14:43:01 +0000 (15:43 +0100)
Conflicts:
src/jalview/gui/Desktop.java
src/jalview/gui/Preferences.java
src/jalview/jbgui/GPreferences.java

1  2 
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/Preferences.java
src/jalview/jbgui/GPreferences.java
src/jalview/ws/jws2/Jws2Discoverer.java

   */
  package jalview.gui;
  
 +import java.awt.BorderLayout;
 +import java.awt.Color;
 +import java.awt.Component;
 +import java.awt.Rectangle;
 +import java.awt.Toolkit;
 +import java.awt.datatransfer.Clipboard;
 +import java.awt.datatransfer.DataFlavor;
 +import java.awt.datatransfer.StringSelection;
 +import java.awt.datatransfer.Transferable;
 +import java.awt.dnd.DnDConstants;
 +import java.awt.dnd.DropTargetDragEvent;
 +import java.awt.dnd.DropTargetDropEvent;
 +import java.awt.dnd.DropTargetEvent;
 +import java.awt.dnd.DropTargetListener;
 +import java.awt.event.ActionEvent;
 +import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
 +import java.awt.event.ItemEvent;
 +import java.awt.event.ItemListener;
 +import java.awt.event.KeyAdapter;
 +import java.awt.event.KeyEvent;
 +import java.awt.event.MouseEvent;
 +import java.awt.print.PageFormat;
 +import java.awt.print.PrinterJob;
 +import java.beans.PropertyChangeEvent;
 +import java.io.File;
 +import java.io.FileWriter;
 +import java.io.PrintWriter;
 +import java.net.URL;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Deque;
 +import java.util.List;
 +import java.util.Vector;
 +
 +import javax.swing.ButtonGroup;
 +import javax.swing.JCheckBoxMenuItem;
 +import javax.swing.JComponent;
 +import javax.swing.JEditorPane;
 +import javax.swing.JInternalFrame;
 +import javax.swing.JLabel;
 +import javax.swing.JLayeredPane;
 +import javax.swing.JMenu;
 +import javax.swing.JMenuItem;
 +import javax.swing.JPanel;
 +import javax.swing.JScrollPane;
 +import javax.swing.SwingUtilities;
 +
 +import ext.vamsas.ServiceHandle;
  import jalview.analysis.AlignmentSorter;
  import jalview.analysis.AlignmentUtils;
  import jalview.analysis.CrossRef;
@@@ -77,7 -27,7 +77,7 @@@ import jalview.analysis.Dna
  import jalview.analysis.GeneticCodeI;
  import jalview.analysis.ParseProperties;
  import jalview.analysis.SequenceIdMatcher;
 -import jalview.api.AlignExportSettingI;
 +import jalview.api.AlignExportSettingsI;
  import jalview.api.AlignViewControllerGuiI;
  import jalview.api.AlignViewControllerI;
  import jalview.api.AlignViewportI;
@@@ -97,7 -47,6 +97,7 @@@ import jalview.commands.RemoveGapColCom
  import jalview.commands.RemoveGapsCommand;
  import jalview.commands.SlideSequencesCommand;
  import jalview.commands.TrimRegionCommand;
 +import jalview.datamodel.AlignExportSettingsAdapter;
  import jalview.datamodel.AlignedCodonFrame;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -107,6 -56,7 +107,6 @@@ import jalview.datamodel.AlignmentOrder
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
 -import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SeqCigar;
  import jalview.datamodel.Sequence;
@@@ -143,18 -93,16 +143,19 @@@ import jalview.io.ScoreMatrixFile
  import jalview.io.TCoffeeScoreFile;
  import jalview.io.vcf.VCFLoader;
  import jalview.jbgui.GAlignFrame;
 +import jalview.project.Jalview2XML;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemes;
  import jalview.schemes.ResidueColourScheme;
  import jalview.schemes.TCoffeeColourScheme;
 +import jalview.util.ImageMaker.TYPE;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.ViewportRanges;
  import jalview.ws.DBRefFetcher;
  import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
+ import jalview.ws.WSDiscovererI;
  import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws1.Discoverer;
  import jalview.ws.jws2.Jws2Discoverer;
@@@ -163,12 -111,56 +164,12 @@@ import jalview.ws.params.ParamDatastore
  import jalview.ws.params.WsParamSetI;
  import jalview.ws.seqfetcher.DbSourceProxy;
  import jalview.ws.slivkaws.SlivkaWSDiscoverer;
 -
 -import java.awt.BorderLayout;
 -import java.awt.Component;
 -import java.awt.Rectangle;
 -import java.awt.Toolkit;
 -import java.awt.datatransfer.Clipboard;
 -import java.awt.datatransfer.DataFlavor;
 -import java.awt.datatransfer.StringSelection;
 -import java.awt.datatransfer.Transferable;
 -import java.awt.dnd.DnDConstants;
 -import java.awt.dnd.DropTargetDragEvent;
 -import java.awt.dnd.DropTargetDropEvent;
 -import java.awt.dnd.DropTargetEvent;
 -import java.awt.dnd.DropTargetListener;
 -import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
 -import java.awt.event.FocusAdapter;
 -import java.awt.event.FocusEvent;
 -import java.awt.event.ItemEvent;
 -import java.awt.event.ItemListener;
 -import java.awt.event.KeyAdapter;
 -import java.awt.event.KeyEvent;
 -import java.awt.event.MouseEvent;
 -import java.awt.print.PageFormat;
 -import java.awt.print.PrinterJob;
 -import java.beans.PropertyChangeEvent;
 -import java.io.File;
 -import java.io.FileWriter;
  import java.io.IOException;
 -import java.io.PrintWriter;
 -import java.net.URL;
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.Deque;
  import java.util.HashSet;
 -import java.util.List;
  import java.util.Set;
 -import java.util.Vector;
  
 -import javax.swing.ButtonGroup;
 -import javax.swing.JCheckBoxMenuItem;
 -import javax.swing.JEditorPane;
  import javax.swing.JFileChooser;
 -import javax.swing.JInternalFrame;
 -import javax.swing.JLayeredPane;
 -import javax.swing.JMenu;
 -import javax.swing.JMenuItem;
  import javax.swing.JOptionPane;
 -import javax.swing.JScrollPane;
 -import javax.swing.SwingUtilities;
  
  /**
   * DOCUMENT ME!
   * @author $author$
   * @version $Revision$
   */
 +@SuppressWarnings("serial")
  public class AlignFrame extends GAlignFrame implements DropTargetListener,
          IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
  {
     */
    String fileName = null;
  
 +  /**
 +       * TODO: remove reference to 'FileObject' in AlignFrame - not correct mapping
 +       */
 +  File fileObject;
  
    /**
     * Creates a new AlignFrame object with specific width and height.
     */
    void init()
    {
 +//      setBackground(Color.white); // BH 2019
 +                
      if (!Jalview.isHeadlessMode())
      {
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
      if (Desktop.desktop != null)
      {
        this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
 -      addServiceListeners();
 +      if (!Platform.isJS())
 +      {
 +        addServiceListeners();
 +      }
        setGUINucleotide();
      }
  
    }
  
    /**
 +   * JavaScript will have this, maybe others. More dependable than a file name
 +   * and maintains a reference to the actual bytes loaded.
 +   * 
 +   * @param file
 +   */
 +  public void setFileObject(File file)
 +  {
 +    this.fileObject = file;
 +  }
 +
 +  /**
     * Add a KeyListener with handlers for various KeyPressed and KeyReleased
     * events
     */
          case KeyEvent.VK_BACK_SPACE:
            if (!viewport.cursorMode)
            {
 -            cut_actionPerformed(null);
 +            cut_actionPerformed();
            }
            else
            {
  
          case KeyEvent.VK_F2:
            viewport.cursorMode = !viewport.cursorMode;
 -          statusBar.setText(MessageManager
 +          setStatus(MessageManager
                    .formatMessage("label.keyboard_editing_mode", new String[]
                    { (viewport.cursorMode ? "on" : "off") }));
            if (viewport.cursorMode)
          Desktop.instance.removeJalviewPropertyChangeListener("services",
                  thisListener);
          closeMenuItem_actionPerformed(true);
 -      };
 +      }
      });
      // Finally, build the menu once to get current service state
      new Thread(new Runnable()
    }
  
    @Override
 -  public void fetchSequence_actionPerformed(ActionEvent e)
 +  public void fetchSequence_actionPerformed()
    {
 -    new jalview.gui.SequenceFetcher(this);
 +    new SequenceFetcher(this);
    }
  
    @Override
          Rectangle bounds = this.getBounds();
  
          FileLoader loader = new FileLoader();
 -        DataSourceType protocol = fileName.startsWith("http:")
 -                ? DataSourceType.URL
 -                : DataSourceType.FILE;
 -        AlignFrame newframe = loader.LoadFileWaitTillLoaded(fileName,
 -                protocol, currentFileFormat);
 +
 +        AlignFrame newframe = null;
 +
 +        if (fileObject == null)
 +        {
 +
 +          DataSourceType protocol = (fileName.startsWith("http:")
 +                  ? DataSourceType.URL
 +                  : DataSourceType.FILE);
 +          newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
 +                  currentFileFormat);
 +        }
 +        else
 +        {
 +          newframe = loader.LoadFileWaitTillLoaded(fileObject,
 +                  DataSourceType.FILE, currentFileFormat);
 +        }
  
          newframe.setBounds(bounds);
          if (featureSettings != null && featureSettings.isShowing())
      if (fileName == null || (currentFileFormat == null)
              || fileName.startsWith("http"))
      {
 -      saveAs_actionPerformed(null);
 +      saveAs_actionPerformed();
      }
      else
      {
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Saves the alignment to a file with a name chosen by the user, if necessary
 +   * warning if a file would be overwritten
     */
    @Override
 -  public void saveAs_actionPerformed(ActionEvent e)
 +  public void saveAs_actionPerformed()
    {
      String format = currentFileFormat == null ? null
              : currentFileFormat.getName();
  
      int value = chooser.showSaveDialog(this);
  
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    if (value != JalviewFileChooser.APPROVE_OPTION)
 +    {
 +      return;
 +    }
 +    currentFileFormat = chooser.getSelectedFormat();
 +    // todo is this (2005) test now obsolete - value is never null?
 +    while (currentFileFormat == null)
      {
 +      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +              MessageManager
 +                      .getString("label.select_file_format_before_saving"),
 +              MessageManager.getString("label.file_format_not_specified"),
 +              JvOptionPane.WARNING_MESSAGE);
        currentFileFormat = chooser.getSelectedFormat();
 -      while (currentFileFormat == null)
 +      value = chooser.showSaveDialog(this);
 +      if (value != JalviewFileChooser.APPROVE_OPTION)
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -                MessageManager.getString(
 -                        "label.select_file_format_before_saving"),
 -                MessageManager.getString("label.file_format_not_specified"),
 -                JvOptionPane.WARNING_MESSAGE);
 -        currentFileFormat = chooser.getSelectedFormat();
 -        value = chooser.showSaveDialog(this);
 -        if (value != JalviewFileChooser.APPROVE_OPTION)
 -        {
 -          return;
 -        }
 +        return;
        }
 +    }
  
 -      fileName = chooser.getSelectedFile().getPath();
 +    fileName = chooser.getSelectedFile().getPath();
  
 -      Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
 +    Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
 +    Cache.setProperty("LAST_DIRECTORY", fileName);
 +    saveAlignment(fileName, currentFileFormat);
 +  }
 +
 +  boolean lastSaveSuccessful = false;
 +
 +  FileFormatI lastFormatSaved;
 +
 +  String lastFilenameSaved;
 +
 +  /**
 +   * Raise a dialog or status message for the last call to saveAlignment.
 +   *
 +   * @return true if last call to saveAlignment(file, format) was successful.
 +   */
 +  public boolean isSaveAlignmentSuccessful()
 +  {
 +
 +    if (!lastSaveSuccessful)
 +    {
 +      JvOptionPane.showInternalMessageDialog(this, MessageManager
 +              .formatMessage("label.couldnt_save_file", new Object[]
 +              { lastFilenameSaved }),
 +              MessageManager.getString("label.error_saving_file"),
 +              JvOptionPane.WARNING_MESSAGE);
 +    }
 +    else
 +    {
 +
 +      setStatus(MessageManager.formatMessage(
 +              "label.successfully_saved_to_file_in_format", new Object[]
 +              { lastFilenameSaved, lastFormatSaved }));
  
 -      Cache.setProperty("LAST_DIRECTORY", fileName);
 -      saveAlignment(fileName, currentFileFormat);
      }
 +    return lastSaveSuccessful;
    }
  
 -  public boolean saveAlignment(String file, FileFormatI format)
 +  /**
 +   * Saves the alignment to the specified file path, in the specified format,
 +   * which may be an alignment format, or Jalview project format. If the
 +   * alignment has hidden regions, or the format is one capable of including
 +   * non-sequence data (features, annotations, groups), then the user may be
 +   * prompted to specify what to include in the output.
 +   * 
 +   * @param file
 +   * @param format
 +   */
 +  public void saveAlignment(String file, FileFormatI format)
    {
 -    boolean success = true;
 +    lastSaveSuccessful = true;
 +    lastFilenameSaved = file;
 +    lastFormatSaved = format;
  
      if (FileFormat.Jalview.equals(format))
      {
        String shortName = title;
 -
 -      if (shortName.indexOf(java.io.File.separatorChar) > -1)
 +      if (shortName.indexOf(File.separatorChar) > -1)
        {
          shortName = shortName.substring(
 -                shortName.lastIndexOf(java.io.File.separatorChar) + 1);
 +                shortName.lastIndexOf(File.separatorChar) + 1);
        }
 -
 -      success = new jalview.project.Jalview2XML().saveAlignment(this, file,
 -              shortName);
 -
 +      lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file, shortName);
 +      
        statusBar.setText(MessageManager.formatMessage(
                "label.successfully_saved_to_file_in_format", new Object[]
                { fileName, format }));
 -
 +      
 +      return;
      }
 -    else
 +
 +    AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 +    Runnable cancelAction = new Runnable()
      {
 -      AlignmentExportData exportData = getAlignmentForExport(format,
 -              viewport, null);
 -      if (exportData.getSettings().isCancelled())
 -      {
 -        return false;
 -      }
 -      FormatAdapter f = new FormatAdapter(alignPanel,
 -              exportData.getSettings());
 -      String output = f.formatSequences(format, exportData.getAlignment(), // class
 -                                                                           // cast
 -                                                                           // exceptions
 -                                                                           // will
 -              // occur in the distant future
 -              exportData.getOmitHidden(), exportData.getStartEndPostions(),
 -              f.getCacheSuffixDefault(format),
 -              viewport.getAlignment().getHiddenColumns());
 -
 -      if (output == null)
 +      @Override
 +      public void run()
        {
 -        success = false;
 +        lastSaveSuccessful = false;
        }
 -      else
 +    };
 +    Runnable outputAction = new Runnable()
 +    {
 +      @Override
 +      public void run()
        {
 -        // create backupfiles object and get new temp filename destination
 -        BackupFiles backupfiles = new BackupFiles(file);
 -
 -        try
 +        // todo defer this to inside formatSequences (or later)
 +        AlignmentExportData exportData = viewport
 +                .getAlignExportData(options);
 +        String output = new FormatAdapter(alignPanel, options)
 +                .formatSequences(format, exportData.getAlignment(),
 +                        exportData.getOmitHidden(),
 +                        exportData.getStartEndPostions(),
 +                        viewport.getAlignment().getHiddenColumns());
 +        if (output == null)
 +        {
 +          lastSaveSuccessful = false;
 +        }
 +        else
          {
 -          PrintWriter out = new PrintWriter(
 -                  new FileWriter(backupfiles.getTempFilePath()));
 +          // create backupfiles object and get new temp filename destination
 +          boolean doBackup = BackupFiles.getEnabled();
 +          BackupFiles backupfiles = doBackup ? new BackupFiles(file) : null;
 +          try
 +          {
 +            String tempFilePath = doBackup ? backupfiles.getTempFilePath() : file;
 +                      PrintWriter out = new PrintWriter(
 +                    new FileWriter(tempFilePath));
  
 -          out.print(output);
 -          out.close();
 -          this.setTitle(file);
 -          statusBar.setText(MessageManager.formatMessage(
 +            out.print(output);
 +            out.close();
 +            AlignFrame.this.setTitle(file);
 +            statusBar.setText(MessageManager.formatMessage(
                    "label.successfully_saved_to_file_in_format", new Object[]
                    { fileName, format.getName() }));
 -        } catch (Exception ex)
 -        {
 -          success = false;
 -          ex.printStackTrace();
 -        }
 -
 -        backupfiles.setWriteSuccess(success);
 -        // do the backup file roll and rename the temp file to actual file
 -        success = backupfiles.rollBackupsAndRenameTempFile();
 +            lastSaveSuccessful = true;
 +          } catch (Exception ex)
 +          {
 +            lastSaveSuccessful = false;
 +            ex.printStackTrace();
 +          }
  
 +          if (doBackup)
 +          {
 +            backupfiles.setWriteSuccess(lastSaveSuccessful);
 +            // do the backup file roll and rename the temp file to actual file
 +            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
 +          }
 +        }
        }
 -    }
 +    };
  
 -    if (!success)
 -    {
 -      JvOptionPane.showInternalMessageDialog(this, MessageManager
 -              .formatMessage("label.couldnt_save_file", new Object[]
 -              { fileName }),
 -              MessageManager.getString("label.error_saving_file"),
 -              JvOptionPane.WARNING_MESSAGE);
 -    }
 -
 -    return success;
 -  }
 -
 -  private void warningMessage(String warning, String title)
 -  {
 -    if (new jalview.util.Platform().isHeadless())
 +    /*
 +     * show dialog with export options if applicable; else just do it
 +     */
 +    if (AlignExportOptions.isNeeded(viewport, format))
      {
 -      System.err.println("Warning: " + title + "\nWarning: " + warning);
 -
 +      AlignExportOptions choices = new AlignExportOptions(
 +              alignPanel.getAlignViewport(), format, options);
 +      choices.setResponseAction(0, outputAction);
 +      choices.setResponseAction(1, cancelAction);
 +      choices.showDialog();
      }
      else
      {
 -      JvOptionPane.showInternalMessageDialog(this, warning, title,
 -              JvOptionPane.WARNING_MESSAGE);
 +      outputAction.run();
      }
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Outputs the alignment to textbox in the requested format, if necessary
 +   * first prompting the user for whether to include hidden regions or
 +   * non-sequence data
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param fileFormatName
     */
    @Override
 -  protected void outputText_actionPerformed(ActionEvent e)
 +  protected void outputText_actionPerformed(String fileFormatName)
    {
      FileFormatI fileFormat = FileFormats.getInstance()
 -            .forName(e.getActionCommand());
 -    AlignmentExportData exportData = getAlignmentForExport(fileFormat,
 -            viewport, null);
 -    if (exportData.getSettings().isCancelled())
 -    {
 -      return;
 -    }
 -    CutAndPasteTransfer cap = new CutAndPasteTransfer();
 -    cap.setForInput(null);
 -    try
 -    {
 -      FileFormatI format = fileFormat;
 -      cap.setText(new FormatAdapter(alignPanel, exportData.getSettings())
 -              .formatSequences(format, exportData.getAlignment(),
 -                      exportData.getOmitHidden(),
 -                      exportData.getStartEndPostions(),
 -                      viewport.getAlignment().getHiddenColumns()));
 -      Desktop.addInternalFrame(cap, MessageManager
 -              .formatMessage("label.alignment_output_command", new Object[]
 -              { e.getActionCommand() }), 600, 500);
 -    } catch (OutOfMemoryError oom)
 -    {
 -      new OOMWarning("Outputting alignment as " + e.getActionCommand(),
 -              oom);
 -      cap.dispose();
 -    }
 -
 -  }
 -
 -  public static AlignmentExportData getAlignmentForExport(
 -          FileFormatI format, AlignViewportI viewport,
 -          AlignExportSettingI exportSettings)
 -  {
 -    AlignmentI alignmentToExport = null;
 -    AlignExportSettingI settings = exportSettings;
 -    String[] omitHidden = null;
 -
 -    HiddenSequences hiddenSeqs = viewport.getAlignment()
 -            .getHiddenSequences();
 -
 -    alignmentToExport = viewport.getAlignment();
 -
 -    boolean hasHiddenSeqs = hiddenSeqs.getSize() > 0;
 -    if (settings == null)
 +            .forName(fileFormatName);
 +    AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 +    Runnable outputAction = new Runnable()
      {
 -      settings = new AlignExportSettings(hasHiddenSeqs,
 -              viewport.hasHiddenColumns(), format);
 -    }
 -    // settings.isExportAnnotations();
 -
 -    if (viewport.hasHiddenColumns() && !settings.isExportHiddenColumns())
 -    {
 -      omitHidden = viewport.getViewAsString(false,
 -              settings.isExportHiddenSequences());
 -    }
 +      @Override
 +      public void run()
 +      {
 +        // todo defer this to inside formatSequences (or later)
 +        AlignmentExportData exportData = viewport
 +                .getAlignExportData(options);
 +        CutAndPasteTransfer cap = new CutAndPasteTransfer();
 +        cap.setForInput(null);
 +        try
 +        {
 +          FileFormatI format = fileFormat;
 +          cap.setText(new FormatAdapter(alignPanel, options)
 +                  .formatSequences(format, exportData.getAlignment(),
 +                          exportData.getOmitHidden(),
 +                          exportData.getStartEndPostions(),
 +                          viewport.getAlignment().getHiddenColumns()));
 +          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
 +                  "label.alignment_output_command", new Object[]
 +                  { fileFormat.getName() }), 600, 500);
 +        } catch (OutOfMemoryError oom)
 +        {
 +          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
 +                  oom);
 +          cap.dispose();
 +        }
 +      }
 +    };
  
 -    int[] alignmentStartEnd = new int[2];
 -    if (hasHiddenSeqs && settings.isExportHiddenSequences())
 +    /*
 +     * show dialog with export options if applicable; else just do it
 +     */
 +    if (AlignExportOptions.isNeeded(viewport, fileFormat))
      {
 -      alignmentToExport = hiddenSeqs.getFullAlignment();
 +      AlignExportOptions choices = new AlignExportOptions(
 +              alignPanel.getAlignViewport(), fileFormat, options);
 +      choices.setResponseAction(0, outputAction);
 +      choices.showDialog();
      }
      else
      {
 -      alignmentToExport = viewport.getAlignment();
 +      outputAction.run();
      }
 -    alignmentStartEnd = viewport.getAlignment().getHiddenColumns()
 -            .getVisibleStartAndEndIndex(alignmentToExport.getWidth());
 -    AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
 -            omitHidden, alignmentStartEnd, settings);
 -    return ed;
    }
  
    /**
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Creates a PNG image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param f
     */
    @Override
    public void createPNG(File f)
    {
 -    alignPanel.makePNG(f);
 +    alignPanel.makeAlignmentImage(TYPE.PNG, f);
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Creates an EPS image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param f
     */
    @Override
    public void createEPS(File f)
    {
 -    alignPanel.makeEPS(f);
 +    alignPanel.makeAlignmentImage(TYPE.EPS, f);
    }
  
 +  /**
 +   * Creates an SVG image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
 +   * 
 +   * @param f
 +   */
    @Override
    public void createSVG(File f)
    {
 -    alignPanel.makeSVG(f);
 +    alignPanel.makeAlignmentImage(TYPE.SVG, f);
    }
  
    @Override
    public void associatedData_actionPerformed(ActionEvent e)
            throws IOException, InterruptedException
    {
 -    // Pick the tree file
 -    JalviewFileChooser chooser = new JalviewFileChooser(
 +    final JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new JalviewFileView());
 -    chooser.setDialogTitle(
 -            MessageManager.getString("label.load_jalview_annotations"));
 -    chooser.setToolTipText(
 -            MessageManager.getString("label.load_jalview_annotations"));
 -
 -    int value = chooser.showOpenDialog(null);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    String tooltip = MessageManager.getString("label.load_jalview_annotations");
 +    chooser.setDialogTitle(tooltip);
 +    chooser.setToolTipText(tooltip);
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      String choice = chooser.getSelectedFile().getPath();
 -      jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
 -      loadJalviewDataFile(choice, null, null, null);
 -    }
 +      @Override
 +      public void run()
 +      {
 +        String choice = chooser.getSelectedFile().getPath();
 +        jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
 +        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
 +      }
 +    });
  
 +    chooser.showOpenDialog(this);
    }
  
    /**
            closeView(alignPanel);
          }
        }
 -
        if (closeAllTabs)
        {
 +        if (featureSettings != null && featureSettings.isOpen())
 +        {
 +          featureSettings.close();
 +          featureSettings = null;
 +        }
          /*
           * this will raise an INTERNAL_FRAME_CLOSED event and this method will
           * be called recursively, with the frame now in 'closed' state
     *          DOCUMENT ME!
     */
    @Override
 -  protected void copy_actionPerformed(ActionEvent e)
 +  protected void copy_actionPerformed()
    {
      if (viewport.getSelectionGroup() == null)
      {
  
      Desktop.jalviewClipboard = new Object[] { seqs,
          viewport.getAlignment().getDataset(), hiddenColumns };
 -    statusBar.setText(MessageManager.formatMessage(
 +    setStatus(MessageManager.formatMessage(
              "label.copied_sequences_to_clipboard", new Object[]
              { Integer.valueOf(seqs.length).toString() }));
    }
                  && Desktop.jalviewClipboard[1] != alignment.getDataset();
          // importDs==true instructs us to copy over new dataset sequences from
          // an existing alignment
 -        Vector newDs = (importDs) ? new Vector() : null; // used to create
 +        Vector<SequenceI> newDs = (importDs) ? new Vector<>() : null; // used to
 +                                                                      // create
          // minimum dataset set
  
          for (int i = 0; i < sequences.length; i++)
        {
  
          // propagate alignment changed.
 -        viewport.getRanges().setEndSeq(alignment.getHeight());
 +        viewport.getRanges().setEndSeq(alignment.getHeight() - 1);
          if (annotationAdded)
          {
            // Duplicate sequence annotation in all views.
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Action Cut (delete and copy) the selected region
     */
    @Override
 -  protected void cut_actionPerformed(ActionEvent e)
 +  protected void cut_actionPerformed()
    {
 -    copy_actionPerformed(null);
 -    delete_actionPerformed(null);
 +    copy_actionPerformed();
 +    delete_actionPerformed();
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Performs menu option to Delete the currently selected region
     */
    @Override
 -  protected void delete_actionPerformed(ActionEvent evt)
 +  protected void delete_actionPerformed()
    {
  
      SequenceGroup sg = viewport.getSelectionGroup();
        return;
      }
  
 +    Runnable okAction = new Runnable() 
 +    {
 +              @Override
 +              public void run() 
 +              {
 +                  SequenceI[] cut = sg.getSequences()
 +                          .toArray(new SequenceI[sg.getSize()]);
 +
 +                  addHistoryItem(new EditCommand(
 +                          MessageManager.getString("label.cut_sequences"), Action.CUT,
 +                          cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
 +                          viewport.getAlignment()));
 +
 +                  viewport.setSelectionGroup(null);
 +                  viewport.sendSelection();
 +                  viewport.getAlignment().deleteGroup(sg);
 +
 +                  viewport.firePropertyChange("alignment", null,
 +                          viewport.getAlignment().getSequences());
 +                  if (viewport.getAlignment().getHeight() < 1)
 +                  {
 +                    try
 +                    {
 +                      AlignFrame.this.setClosed(true);
 +                    } catch (Exception ex)
 +                    {
 +                    }
 +                  }
 +              }};
 +
      /*
 -     * If the cut affects all sequences, warn, remove highlighted columns
 +     * If the cut affects all sequences, prompt for confirmation
       */
 -    if (sg.getSize() == viewport.getAlignment().getHeight())
 -    {
 -      boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes())
 -              + 1) == viewport.getAlignment().getWidth()) ? true : false;
 -      if (isEntireAlignWidth)
 -      {
 -        int confirm = JvOptionPane.showConfirmDialog(this,
 -                MessageManager.getString("warn.delete_all"), // $NON-NLS-1$
 -                MessageManager.getString("label.delete_all"), // $NON-NLS-1$
 -                JvOptionPane.OK_CANCEL_OPTION);
 -
 -        if (confirm == JvOptionPane.CANCEL_OPTION
 -                || confirm == JvOptionPane.CLOSED_OPTION)
 -        {
 -          return;
 -        }
 -      }
 -      viewport.getColumnSelection().removeElements(sg.getStartRes(),
 -              sg.getEndRes() + 1);
 -    }
 -    SequenceI[] cut = sg.getSequences()
 -            .toArray(new SequenceI[sg.getSize()]);
 -
 -    addHistoryItem(new EditCommand(
 -            MessageManager.getString("label.cut_sequences"), Action.CUT,
 -            cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
 -            viewport.getAlignment()));
 -
 -    viewport.setSelectionGroup(null);
 -    viewport.sendSelection();
 -    viewport.getAlignment().deleteGroup(sg);
 -
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 -    if (viewport.getAlignment().getHeight() < 1)
 -    {
 -      try
 -      {
 -        this.setClosed(true);
 -      } catch (Exception ex)
 -      {
 -      }
 -    }
 +    boolean wholeHeight = sg.getSize() == viewport.getAlignment().getHeight();
 +    boolean wholeWidth = (((sg.getEndRes() - sg.getStartRes())
 +            + 1) == viewport.getAlignment().getWidth()) ? true : false;
 +      if (wholeHeight && wholeWidth)
 +      {
 +          JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
 +              dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
 +          Object[] options = new Object[] { MessageManager.getString("action.ok"),
 +                  MessageManager.getString("action.cancel") };
 +              dialog.showDialog(MessageManager.getString("warn.delete_all"),
 +                  MessageManager.getString("label.delete_all"),
 +                  JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
 +                  options, options[0]);
 +      } else 
 +      {
 +              okAction.run();
 +      }
    }
  
    /**
                  column, viewport.getAlignment());
        }
  
 -      statusBar.setText(MessageManager
 +      setStatus(MessageManager
                .formatMessage("label.removed_columns", new String[]
                { Integer.valueOf(trimRegion.getSize()).toString() }));
  
  
      addHistoryItem(removeGapCols);
  
 -    statusBar.setText(MessageManager
 +    setStatus(MessageManager
              .formatMessage("label.removed_empty_columns", new Object[]
              { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
     * @param toggleSeqs
     * @param toggleCols
     */
 -  private void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
 +  protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
    {
  
      boolean hide = false;
    @Override
    public void featureSettings_actionPerformed(ActionEvent e)
    {
 +    showFeatureSettingsUI();
 +  }
 +
 +  @Override
 +  public FeatureSettingsControllerI showFeatureSettingsUI()
 +  {
      if (featureSettings != null)
      {
 -      featureSettings.close();
 +      featureSettings.closeOldSettings();
        featureSettings = null;
      }
      if (!showSeqFeatures.isSelected())
        showSeqFeatures_actionPerformed(null);
      }
      featureSettings = new FeatureSettings(this);
 +    return featureSettings;
    }
  
    /**
    @Override
    public void alignmentProperties()
    {
 -    JEditorPane editPane = new JEditorPane("text/html", "");
 -    editPane.setEditable(false);
 +    JComponent pane;
      StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
 +
              .formatAsHtml();
 -    editPane.setText(
 -            MessageManager.formatMessage("label.html_content", new Object[]
 -            { contents.toString() }));
 +    String content = MessageManager.formatMessage("label.html_content",
 +            new Object[]
 +            { contents.toString() });
 +    contents = null;
 +
 +    if (Platform.isJS())
 +    {
 +      JLabel textLabel = new JLabel();
 +      textLabel.setText(content);
 +      textLabel.setBackground(Color.WHITE);
 +      
 +      pane = new JPanel(new BorderLayout());
 +      ((JPanel) pane).setOpaque(true);
 +      pane.setBackground(Color.WHITE);
 +      ((JPanel) pane).add(textLabel, BorderLayout.NORTH);
 +    }
 +    else
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      JEditorPane editPane = new JEditorPane("text/html", "");
 +      editPane.setEditable(false);
 +      editPane.setText(content);
 +      pane = editPane;
 +    }
 +
      JInternalFrame frame = new JInternalFrame();
 -    frame.getContentPane().add(new JScrollPane(editPane));
 +
 +    frame.getContentPane().add(new JScrollPane(pane));
  
      Desktop.addInternalFrame(frame, MessageManager
              .formatMessage("label.alignment_properties", new Object[]
                {
                  overview.dispose();
                  alignPanel.setOverviewPanel(null);
 -              };
 +              }
              });
      if (getKeyListeners().length > 0)
      {
      chooser.setToolTipText(
              MessageManager.getString("label.load_tree_file"));
  
 -    int value = chooser.showOpenDialog(null);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0,new Runnable()
      {
 -      String filePath = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY", filePath);
 -      NewickFile fin = null;
 -      try
 -      {
 -        fin = new NewickFile(filePath, DataSourceType.FILE);
 -        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
 -      } catch (Exception ex)
 -      {
 -        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
 -                MessageManager.getString("label.problem_reading_tree_file"),
 -                JvOptionPane.WARNING_MESSAGE);
 -        ex.printStackTrace();
 -      }
 -      if (fin != null && fin.hasWarningMessage())
 +      @Override
 +      public void run()
        {
 -        JvOptionPane.showMessageDialog(Desktop.desktop,
 -                fin.getWarningMessage(),
 -                MessageManager
 -                        .getString("label.possible_problem_with_tree_file"),
 -                JvOptionPane.WARNING_MESSAGE);
 +        String filePath = chooser.getSelectedFile().getPath();
 +        Cache.setProperty("LAST_DIRECTORY", filePath);
 +        NewickFile fin = null;
 +        try
 +        {
 +          fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
 +                  DataSourceType.FILE));
 +          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
 +        } catch (Exception ex)
 +        {
 +          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
 +                  MessageManager
 +                          .getString("label.problem_reading_tree_file"),
 +                  JvOptionPane.WARNING_MESSAGE);
 +          ex.printStackTrace();
 +        }
 +        if (fin != null && fin.hasWarningMessage())
 +        {
 +          JvOptionPane.showMessageDialog(Desktop.desktop,
 +                  fin.getWarningMessage(),
 +                  MessageManager.getString(
 +                          "label.possible_problem_with_tree_file"),
 +                  JvOptionPane.WARNING_MESSAGE);
 +        }
        }
 -    }
 +    });
 +    chooser.showOpenDialog(this);
    }
  
    public TreePanel showNewickTree(NewickFile nf, String treeTitle)
              // No MSAWS used any more:
              // Vector msaws = null; // (Vector)
              // Discoverer.services.get("MsaWS");
 -            Vector secstrpr = (Vector) Discoverer.services
 +            Vector<ServiceHandle> secstrpr = Discoverer.services
                      .get("SecStrPred");
              if (secstrpr != null)
              {
                // Add any secondary structure prediction services
                for (int i = 0, j = secstrpr.size(); i < j; i++)
                {
 -                final ext.vamsas.ServiceHandle sh = (ext.vamsas.ServiceHandle) secstrpr
 +                final ext.vamsas.ServiceHandle sh = secstrpr
                          .get(i);
                  jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
                          .getServiceClient(sh);
                  }
                  // TODO: move into separate menu builder class.
                  boolean new_sspred = false;
                  if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
                  {
-                   Jws2Discoverer jws2servs = Jws2Discoverer.getDiscoverer();
+                   WSDiscovererI jws2servs = Jws2Discoverer.getDiscoverer();
                    if (jws2servs != null)
                    {
                      if (jws2servs.hasServices())
                      }
                    }
                  }
-                 build_urlServiceMenu(me.webService);
-                 // TODO Mateusz - follow pattern for adding web service
-                 // JMenuItems for slivka-based services
  
-                 SlivkaWSDiscoverer slivkaDiscoverer = SlivkaWSDiscoverer.getInstance();
-                 if (slivkaDiscoverer.hasServices())
+                 if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
                  {
-                 slivkaDiscoverer.attachWSMenuEntry(webService, me);
-                 } else {
-                   if (slivkaDiscoverer.isRunning())
+                   WSDiscovererI discoverer = SlivkaWSDiscoverer
+                           .getInstance();
+                   if (discoverer != null)
                    {
+                     if (discoverer.hasServices())
+                     {
+                       discoverer.attachWSMenuEntry(webService, me);
+                     }
+                     if (discoverer.isRunning())
                      {
                        JMenuItem tm = new JMenuItem(
                                "Still discovering Slivka Services");
                        tm.setEnabled(false);
                        webService.add(tm);
                      }
                    }
                  }
-               
  
+                 build_urlServiceMenu(me.webService);
                  build_fetchdbmenu(webService);
                  for (JMenu item : wsmenu)
                  {
     * 
     * @param webService
     */
 -  private void build_urlServiceMenu(JMenu webService)
 +  protected void build_urlServiceMenu(JMenu webService)
    {
      // TODO: remove this code when 2.7 is released
      // DEBUG - alignmentView
     * Try to load a features file onto the alignment.
     * 
     * @param file
 -   *          contents or path to retrieve file
 +   *          contents or path to retrieve file or a File object
     * @param sourceType
     *          access mode of file (see jalview.io.AlignFile)
     * @return true if features file was parsed correctly.
     */
 -  public boolean parseFeaturesFile(String file, DataSourceType sourceType)
 +  public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
    {
 +    // BH 2018
      return avc.parseFeaturesFile(file, sourceType,
              Cache.getDefault("RELAXEDSEQIDMATCHING", false));
  
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
 +
      final AlignFrame thisaf = this;
 -    final List<String> files = new ArrayList<>();
 +    final List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
               * Object[] { String,SequenceI}
               */
              ArrayList<Object[]> filesmatched = new ArrayList<>();
 -            ArrayList<String> filesnotmatched = new ArrayList<>();
 +            ArrayList<Object> filesnotmatched = new ArrayList<>();
              for (int i = 0; i < files.size(); i++)
              {
 -              String file = files.get(i).toString();
 +              // BH 2018
 +              Object file = files.get(i);
 +              String fileName = file.toString();
                String pdbfn = "";
 -              DataSourceType protocol = FormatAdapter.checkProtocol(file);
 +              DataSourceType protocol = (file instanceof File
 +                      ? DataSourceType.FILE
 +                      : FormatAdapter.checkProtocol(fileName));
                if (protocol == DataSourceType.FILE)
                {
 -                File fl = new File(file);
 +                File fl;
 +                if (file instanceof File) {
 +                  fl = (File) file;
 +                  Platform.cacheFileData(fl);
 +                } else {
 +                  fl = new File(fileName);
 +                }
                  pdbfn = fl.getName();
                }
                else if (protocol == DataSourceType.URL)
                {
 -                URL url = new URL(file);
 +                URL url = new URL(fileName);
                  pdbfn = url.getFile();
                }
                if (pdbfn.length() > 0)
                  }
                  if (mtch != null)
                  {
 -                  FileFormatI type = null;
 +                  FileFormatI type;
                    try
                    {
                      type = new IdentifyFile().identify(file, protocol);
                    for (SequenceI toassoc : (SequenceI[]) fm[2])
                    {
                      PDBEntry pe = new AssociatePdbFileWithSeq()
 -                            .associatePdbWithSeq((String) fm[0],
 +                            .associatePdbWithSeq(fm[0].toString(),
                                      (DataSourceType) fm[1], toassoc, false,
                                      Desktop.instance);
                      if (pe != null)
                      {
                        System.err.println("Associated file : "
 -                              + ((String) fm[0]) + " with "
 +                              + (fm[0].toString()) + " with "
                                + toassoc.getDisplayId(true));
                        assocfiles++;
                      }
                   */
                  for (Object[] o : filesmatched)
                  {
 -                  filesnotmatched.add((String) o[0]);
 +                  filesnotmatched.add(o[0]);
                  }
                }
              }
                {
                  return;
                }
 -              for (String fn : filesnotmatched)
 +              for (Object fn : filesnotmatched)
                {
                  loadJalviewDataFile(fn, null, null, null);
                }
     * @throws InterruptedException
     * @throws IOException
     */
 -  public void loadJalviewDataFile(String file, DataSourceType sourceType,
 +  public void loadJalviewDataFile(Object file, DataSourceType sourceType,
            FileFormatI format, SequenceI assocSeq)
    {
 +    // BH 2018 was String file
      try
      {
        if (sourceType == null)
                changeColour(
                        new TCoffeeColourScheme(viewport.getAlignment()));
                isAnnotation = true;
 -              statusBar.setText(MessageManager.getString(
 +              setStatus(MessageManager.getString(
                        "label.successfully_pasted_tcoffee_scores_to_alignment"));
              }
              else
                      new FileParse(file, sourceType));
              sm.parse();
              // todo: i18n this message
 -            statusBar.setText(MessageManager.formatMessage(
 +            setStatus(MessageManager.formatMessage(
                      "label.successfully_loaded_matrix",
                      sm.getMatrixName()));
            }
            {
              if (parseFeaturesFile(file, sourceType))
              {
 -              alignPanel.paintAlignment(true, true);
 +              SplitFrame splitFrame = (SplitFrame) getSplitViewContainer();
 +              if (splitFrame != null)
 +              {
 +                splitFrame.repaint();
 +              }
 +              else
 +              {
 +                alignPanel.paintAlignment(true, true);
 +              }
              }
            }
            else
        viewport = alignPanel.av;
        avc.setViewportAndAlignmentPanel(viewport, alignPanel);
        setMenusFromViewport(viewport);
 +      if (featureSettings != null && featureSettings.isOpen()
 +              && featureSettings.fr.getViewport() != viewport)
 +      {
 +        if (viewport.isShowSequenceFeatures())
 +        {
 +          // refresh the featureSettings to reflect UI change
 +          showFeatureSettingsUI();
 +        }
 +        else
 +        {
 +          // close feature settings for this view.
 +          featureSettings.close();
 +        }
 +      }
 +
      }
  
      /*
      if (e.isPopupTrigger())
      {
        String msg = MessageManager.getString("label.enter_view_name");
 -      String reply = JvOptionPane.showInternalInputDialog(this, msg, msg,
 -              JvOptionPane.QUESTION_MESSAGE);
 +      String ttl = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
 +      String reply = JvOptionPane.showInputDialog(msg, ttl);
  
        if (reply != null)
        {
          trimrs.setSelected(trimrs.isSelected());
          Cache.setProperty(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES,
                  Boolean.valueOf(trimrs.isSelected()).toString());
 -      };
 +      }
      });
      rfetch.add(trimrs);
      JMenuItem fetchr = new JMenuItem(
  
      });
      rfetch.add(fetchr);
 -    final AlignFrame me = this;
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
          final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
 -                .getSequenceFetcherSingleton(me);
 +                .getSequenceFetcherSingleton();
          javax.swing.SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
            {
 -            String[] dbclasses = sf.getOrderedSupportedSources();
 -            // sf.getDbInstances(jalview.ws.dbsources.DasSequenceSource.class);
 -            // jalview.util.QuickSort.sort(otherdb, otherdb);
 +            String[] dbclasses = sf.getNonAlignmentSources();
              List<DbSourceProxy> otherdb;
              JMenu dfetch = new JMenu();
              JMenu ifetch = new JMenu();
                {
                  continue;
                }
 -              // List<DbSourceProxy> dbs=otherdb;
 -              // otherdb=new ArrayList<DbSourceProxy>();
 -              // for (DbSourceProxy db:dbs)
 -              // {
 -              // if (!db.isA(DBRefSource.ALIGNMENTDB)
 -              // }
                if (mname == null)
                {
                  mname = "From " + dbclass;
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
      chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
 +    final AlignFrame us = this;
 +    chooser.setResponseHandler(0, new Runnable()
 +    {
 +      @Override
 +      public void run()
 +      {
 +        String choice = chooser.getSelectedFile().getPath();
 +        Cache.setProperty("LAST_DIRECTORY", choice);
 +        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 +        new VCFLoader(choice).loadVCF(seqs, us);
 +      }
 +    });
 +    chooser.showOpenDialog(null);
  
 -    int value = chooser.showOpenDialog(null);
 +  }
  
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 -    {
 -      String choice = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY", choice);
 -      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 -      new VCFLoader(choice).loadVCF(seqs, this);
 -    }
 +  private Rectangle lastFeatureSettingsBounds = null;
 +  @Override
 +  public void setFeatureSettingsGeometry(Rectangle bounds)
 +  {
 +    lastFeatureSettingsBounds = bounds;
 +  }
  
 +  @Override
 +  public Rectangle getFeatureSettingsGeometry()
 +  {
 +    return lastFeatureSettingsBounds;
    }
  }
  
   */
  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.WSDiscovererI;
 -import jalview.ws.params.ParamManager;
 -import jalview.ws.utils.UrlDownloadClient;
 -
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
@@@ -58,6 -87,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 -112,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 -121,6 +92,38 @@@ 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.WSDiscovererI;
 +import jalview.ws.params.ParamManager;
 +import jalview.ws.utils.UrlDownloadClient;
 +
  /**
   * Jalview Desktop
   * 
@@@ -134,15 -132,6 +135,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);
 +
 +      experimentalFeatures.setSelected(showExperimental());
 +
 +      getIdentifiersOrgData();
  
 -    showNews.setVisible(false);
 +      checkURLLinks();
 +
 +      // Spawn a thread that shows the splashscreen
 +
 +      SwingUtilities.invokeLater(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          new SplashScreen(true);
 +        }
 +      });
  
 -    experimentalFeatures.setSelected(showExperimental());
 +      // 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()
 +              {
  
 -    getIdentifiersOrgData();
 +                @Override
 +                public void propertyChange(PropertyChangeEvent evt)
 +                {
 +                  Cache.log.debug("Firing service changed event for "
 +                          + evt.getNewValue());
 +                  JalviewServicesChanged(evt);
 +                }
 +              });
 +    }
  
 -    checkURLLinks();
 +    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>");
 +    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>");
  
 -    }
 -    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"))
 +    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(selectedFile);
 +            } catch (OutOfMemoryError oom)
 +              {
 +                new OOMWarning("Whilst loading project from " + choice, oom);
 +              } catch (Exception ex)
 +              {
 +                Cache.log.error(
 +                        "Problems whilst loading project from " + choice, ex);
 +                JvOptionPane.showMessageDialog(Desktop.desktop,
 +                        MessageManager.formatMessage(
 +                                "label.error_whilst_loading_project_from",
 +                              new Object[]
 +                                  { choice }),
 +                        MessageManager.getString("label.couldnt_load_project"),
 +                        JvOptionPane.WARNING_MESSAGE);
 +              }
            }
 -          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)
      {
        return;
      }
  
 +    // 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);
 +
        AlignFrame newaf = new AlignFrame(ap);
  
 +      // 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);
 +      }
 +
        /*
         * Restore the view's last exploded frame geometry if known. Multiple
         * views from one exploded frame share and restore the same (frame)
  
        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();
 +      }
      }
  
 +    af.featureSettings = null;
      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.
 +   * 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
     */
      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)
  
          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);
          }
        }
      }
  
 +    // 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
    public void startServiceDiscovery(boolean blocking)
    {
      boolean alive = true;
-     Thread t0 = null, t1 = null, t2 = null;
+     Thread t0 = null, t1 = null, t2 = null, t3 = null;
      // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
      if (true)
      {
  
      if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
      {
-       t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
-               .startDiscoverer(changeSupport);
+       t2 = startServiceDiscovery(
+           jalview.ws.jws2.Jws2Discoverer.getDiscoverer(), false);
      }
-     Thread t3 = null;
+     if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
      {
        // start slivka discovery
-       t3 = new Thread(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance());
-       t3.start();
+       t3 = startServiceDiscovery(
+           jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance(), false);
      }
      if (blocking)
      {
          } catch (Exception e)
          {
          }
+         // FIXME: Condition should check the discoverer's isRunning rather than
+         // threads
          alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
-                 || (t3 != null && t3.isAlive())
-                 || (t0 != null && t0.isAlive());
+             || (t3 != null && t3.isAlive()) || (t0 != null && t0.isAlive());
        }
      }
    }
  
+   public Thread startServiceDiscovery(WSDiscovererI discoverer,
+       boolean blocking)
+   {
+     Thread thread = discoverer.startDiscoverer(changeSupport);
+     if (blocking)
+     {
+       try
+       {
+         thread.join();
+       } catch (InterruptedException e)
+       {
+         e.printStackTrace();
+       }
+     }
+     return thread;
+   }
    /**
     * called to check if the service discovery process completed successfully.
     * 
    {
      if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
      {
-       final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
-               .getErrorMessages();
+       final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
+           .getDiscoverer();
+       final String ermsg = discoverer.getErrorMessages();
        if (ermsg != null)
        {
          if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
            } 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
@@@ -60,7 -60,7 +60,7 @@@ import java.util.ArrayList
  import java.util.List;
  
  import javax.help.HelpSetException;
 -import javax.swing.JColorChooser;
 +import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JPanel;
@@@ -192,6 -192,8 +192,8 @@@ public class Preferences extends GPrefe
  
    private WsPreferences wsPrefs;
  
+   private SlivkaPreferences slivkaPrefs;
    private OptionsParam promptEachTimeOpt = new OptionsParam(
            MessageManager.getString("label.prompt_each_time"),
            "Prompt each time");
      super();
      frame = new JInternalFrame();
      frame.setContentPane(this);
 -    wsPrefs = new WsPreferences();
 -    wsTab.add(wsPrefs, BorderLayout.CENTER);
 -    slivkaTab.add(slivkaPrefs = new SlivkaPreferences(),
 -        BorderLayout.CENTER);
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      wsPrefs = new WsPreferences();
 +      wsTab.add(wsPrefs, BorderLayout.CENTER);
++      slivkaPrefs = new SlivkaPreferences();
++      slivkaTab.add(slivkaPrefs, BorderLayout.CENTER);
 +    }
      int width = 500, height = 450;
 -    new jalview.util.Platform();
 -    if (Platform.isAMac())
 +    if (Platform.isAMacAndNotJS())
      {
        width = 570;
        height = 480;
      fontStyleCB.setSelectedItem(
              Cache.getDefault("FONT_STYLE", Font.PLAIN + ""));
  
 -    smoothFont.setSelected(Cache.getDefault("ANTI_ALIAS", false));
 +    smoothFont.setSelected(Cache.getDefault("ANTI_ALIAS", true));
      scaleProteinToCdna
              .setSelected(Cache.getDefault(SCALE_PROTEIN_TO_CDNA, false));
  
              new RowSorter.SortKey(m.getNameColumn(), SortOrder.ASCENDING));
  
      sorter.setSortKeys(sortKeys);
 -    sorter.sort();
 +    // BH 2018 setSortKeys will do the sort
 +    // sorter.sort();
  
      // set up filtering
      ActionListener onReset;
      doReset.addActionListener(onReset);
  
      // filter to display only custom urls
-     final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
+     final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<>()
      {
        @Override
        public boolean include(
      /*
       * Set Output tab defaults
       */
 -    epsRendering.addItem(promptEachTimeOpt);
 -    epsRendering.addItem(lineArtOpt);
 -    epsRendering.addItem(textOpt);
 -    String defaultEPS = Cache.getDefault("EPS_RENDERING",
 -            "Prompt each time");
 -    if (defaultEPS.equalsIgnoreCase("Text"))
 -    {
 -      epsRendering.setSelectedItem(textOpt);
 -    }
 -    else if (defaultEPS.equalsIgnoreCase("Lineart"))
 -    {
 -      epsRendering.setSelectedItem(lineArtOpt);
 -    }
 -    else
 -    {
 -      epsRendering.setSelectedItem(promptEachTimeOpt);
 -    }
 +    setupOutputCombo(epsRendering, "EPS_RENDERING");
 +    setupOutputCombo(htmlRendering, "HTML_RENDERING");
 +    setupOutputCombo(svgRendering, "SVG_RENDERING");
      autoIdWidth.setSelected(Cache.getDefault("FIGURE_AUTOIDWIDTH", false));
      userIdWidth.setEnabled(!autoIdWidth.isSelected());
      userIdWidthlabel.setEnabled(!autoIdWidth.isSelected());
    }
  
    /**
 +   * A helper method that sets the items and initial selection in a character
 +   * rendering option list (Prompt each time/Lineart/Text)
 +   * 
 +   * @param comboBox
 +   * @param propertyKey
 +   */
 +  protected void setupOutputCombo(JComboBox<Object> comboBox,
 +          String propertyKey)
 +  {
 +    comboBox.addItem(promptEachTimeOpt);
 +    comboBox.addItem(lineArtOpt);
 +    comboBox.addItem(textOpt);
 +    
 +    /*
 +     * JalviewJS doesn't support Lineart so force it to Text
 +     */
 +    String defaultOption = Platform.isJS() ? "Text"
 +            : Cache.getDefault(propertyKey, "Prompt each time");
 +    if (defaultOption.equalsIgnoreCase("Text"))
 +    {
 +      comboBox.setSelectedItem(textOpt);
 +    }
 +    else if (defaultOption.equalsIgnoreCase("Lineart"))
 +    {
 +      comboBox.setSelectedItem(lineArtOpt);
 +    }
 +    else
 +    {
 +      comboBox.setSelectedItem(promptEachTimeOpt);
 +    }
 +  }
 +
 +  /**
     * Save user selections on the Preferences tabs to the Cache and write out to
     * file.
     * 
       */
      Cache.applicationProperties.setProperty("EPS_RENDERING",
              ((OptionsParam) epsRendering.getSelectedItem()).getCode());
 +    Cache.applicationProperties.setProperty("HTML_RENDERING",
 +            ((OptionsParam) htmlRendering.getSelectedItem()).getCode());
 +    Cache.applicationProperties.setProperty("SVG_RENDERING",
 +            ((OptionsParam) svgRendering.getSelectedItem()).getCode());
  
      /*
       * Save Connections settings
      Cache.applicationProperties.setProperty("PAD_GAPS",
              Boolean.toString(padGaps.isSelected()));
  
 -    wsPrefs.updateAndRefreshWsMenuConfig(false);
 +    if (!Platform.isJS())
 +    {
 +      wsPrefs.updateAndRefreshWsMenuConfig(false);
 +    }
  
      /*
       * Save Backups settings
    @Override
    public void startupFileTextfield_mouseClicked()
    {
 +    // TODO: JAL-3048 not needed for Jalview-JS
      String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
      JalviewFileChooser chooser = JalviewFileChooser
              .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
    {
      try
      {
 -      wsPrefs.updateWsMenuConfig(true);
 -      wsPrefs.refreshWs_actionPerformed(e);
 +      if (!Platform.isJS())
 +      {
 +        wsPrefs.updateWsMenuConfig(true);
 +        wsPrefs.refreshWs_actionPerformed(e);
 +      }
        frame.setClosed(true);
      } catch (Exception ex)
      {
    @Override
    public void defaultBrowser_mouseClicked(MouseEvent e)
    {
 -    JFileChooser chooser = new JFileChooser(".");
 -    chooser.setDialogTitle(
 -            MessageManager.getString("label.select_default_browser"));
 +    // TODO: JAL-3048 not needed for j2s
 +    if (!Platform.isJS()) // BH 2019
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      JFileChooser chooser = new JFileChooser(".");
 +      chooser.setDialogTitle(
 +              MessageManager.getString("label.select_default_browser"));
  
 -    int value = chooser.showOpenDialog(this);
 +      int value = chooser.showOpenDialog(this);
  
 -    if (value == JFileChooser.APPROVE_OPTION)
 -    {
 -      defaultBrowser.setText(chooser.getSelectedFile().getAbsolutePath());
 +      if (value == JFileChooser.APPROVE_OPTION)
 +      {
 +        defaultBrowser.setText(chooser.getSelectedFile().getAbsolutePath());
 +      }
      }
 -
    }
  
    /*
    @Override
    public void minColour_actionPerformed(JPanel panel)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_colour_minimum_value"),
 -            minColour.getBackground());
 -    if (col != null)
 -    {
 -      panel.setBackground(col);
 -    }
 -    panel.repaint();
 +            panel);
    }
  
    @Override
    public void maxColour_actionPerformed(JPanel panel)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_colour_maximum_value"),
 -            maxColour.getBackground());
 -    if (col != null)
 -    {
 -      panel.setBackground(col);
 -    }
 -    panel.repaint();
 +            panel);
    }
  
    @Override
    {
      if (!useLegacyGap.isSelected())
      {
 -      Color col = JColorChooser.showDialog(this,
 +      JalviewColourChooser.showColourChooser(this,
                MessageManager.getString("label.select_gap_colour"),
 -              gapColour.getBackground());
 -      if (col != null)
 -      {
 -        gap.setBackground(col);
 -      }
 -      gap.repaint();
 +              gap);
      }
    }
  
    @Override
    public void hiddenColour_actionPerformed(JPanel hidden)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_hidden_colour"),
 -            hiddenColour.getBackground());
 -    if (col != null)
 -    {
 -      hidden.setBackground(col);
 -    }
 -    hidden.repaint();
 +            hidden);
    }
  
    @Override
        }
      } catch (NumberFormatException x)
      {
 +      userIdWidth.setText("");
        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                MessageManager
                        .getString("warn.user_defined_width_requirements"),
                MessageManager.getString("label.invalid_id_column_width"),
                JvOptionPane.WARNING_MESSAGE);
 -      userIdWidth.setText("");
      }
    }
  
@@@ -259,10 -259,6 +259,10 @@@ public class GPreferences extends JPane
     */
    protected JComboBox<Object> epsRendering = new JComboBox<>();
  
 +  protected JComboBox<Object> htmlRendering = new JComboBox<>();
 +
 +  protected JComboBox<Object> svgRendering = new JComboBox<>();
 +
    protected JLabel userIdWidthlabel = new JLabel();
  
    protected JCheckBox autoIdWidth = new JCheckBox();
     */
    protected JPanel wsTab = new JPanel();
  
+   protected JPanel slivkaTab = new JPanel();
    /*
     * Backups tab components
     * a lot of these are member variables instead of local variables only so that they
      tabbedPane.add(initConnectionsTab(),
              MessageManager.getString("label.connections"));
  
 -    tabbedPane.add(initBackupsTab(),
 -            MessageManager.getString("label.backups"));
 +      if (!Platform.isJS()) 
 +      {
 +        tabbedPane.add(initBackupsTab(), 
 +                      MessageManager.getString("label.backups"));
 +      }
  
      tabbedPane.add(initLinksTab(),
              MessageManager.getString("label.urllinks"));
      /*
       * See WsPreferences for the real work of configuring this tab.
       */
 -    wsTab.setLayout(new BorderLayout());
 -    tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
 -    slivkaTab.setLayout(new BorderLayout());
 -    tabbedPane.add(slivkaTab, "Slivka Services");
 +    if (!Platform.isJS())
 +    {
 +      wsTab.setLayout(new BorderLayout());
 +      tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
++      slivkaTab.setLayout(new BorderLayout());
++      tabbedPane.add(slivkaTab, "Slivka Services");
 +    }
  
      /*
       * Handler to validate a tab before leaving it - currently only for
      /*
       * path to Cygwin binaries folder (for Windows)
       */
 -    if (Platform.isWindows())
 +    if (Platform.isWindowsAndNotJS())
      {
        JLabel cygwinLocation = new JLabel(
                MessageManager.getString("label.cygwin_location"));
    {
      JPanel outputTab = new JPanel();
      outputTab.setLayout(null);
 -    JLabel epsLabel = new JLabel();
 +
 +    JLabel epsLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "EPS"));
      epsLabel.setFont(LABEL_FONT);
      epsLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 -    epsLabel.setText(MessageManager.getString("label.eps_rendering_style"));
 -    epsLabel.setBounds(new Rectangle(9, 31, 140, 24));
 +    epsLabel.setBounds(new Rectangle(9, 31, 160, 24));
      epsRendering.setFont(LABEL_FONT);
 -    epsRendering.setBounds(new Rectangle(154, 34, 187, 21));
 +    epsRendering.setBounds(new Rectangle(174, 34, 187, 21));
 +    JLabel htmlLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "HTML"));
 +    htmlLabel.setFont(LABEL_FONT);
 +    htmlLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 +    htmlLabel.setBounds(new Rectangle(9, 55, 160, 24));
 +    htmlRendering.setFont(LABEL_FONT);
 +    htmlRendering.setBounds(new Rectangle(174, 58, 187, 21));
 +    JLabel svgLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "SVG"));
 +    svgLabel.setFont(LABEL_FONT);
 +    svgLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 +    svgLabel.setBounds(new Rectangle(9, 79, 160, 24));
 +    svgRendering.setFont(LABEL_FONT);
 +    svgRendering.setBounds(new Rectangle(174, 82, 187, 21));
 +
      JLabel jLabel1 = new JLabel();
      jLabel1.setFont(LABEL_FONT);
      jLabel1.setHorizontalAlignment(SwingConstants.CENTER);
      jLabel1.setText(MessageManager.getString("label.append_start_end"));
      jLabel1.setFont(LABEL_FONT);
 +
      fastajv.setFont(LABEL_FONT);
      fastajv.setHorizontalAlignment(SwingConstants.LEFT);
      clustaljv.setText(MessageManager.getString("label.clustal") + "     ");
      TitledBorder titledBorder2 = new TitledBorder(
              MessageManager.getString("label.file_output"));
      jPanel11.setBorder(titledBorder2);
 -    jPanel11.setBounds(new Rectangle(30, 72, 196, 182));
 +    jPanel11.setBounds(new Rectangle(30, 120, 196, 182));
      GridLayout gridLayout3 = new GridLayout();
      jPanel11.setLayout(gridLayout3);
      gridLayout3.setRows(8);
              MessageManager.getString("label.automatically_set_id_width"));
      autoIdWidth.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager
              .getString("label.adjusts_width_generated_eps_png")));
 -    autoIdWidth.setBounds(new Rectangle(228, 96, 188, 23));
 +    autoIdWidth.setBounds(new Rectangle(228, 144, 320, 23));
      autoIdWidth.addActionListener(new ActionListener()
      {
  
      userIdWidthlabel.setToolTipText(
              JvSwingUtils.wrapTooltip(true, MessageManager.getString(
                      "label.manually_specify_width_left_column")));
 -    userIdWidthlabel.setBounds(new Rectangle(236, 120, 168, 23));
 +    userIdWidthlabel.setBounds(new Rectangle(236, 168, 320, 23));
      userIdWidth.setFont(JvSwingUtils.getTextAreaFont());
      userIdWidth.setText("");
 -    userIdWidth.setBounds(new Rectangle(232, 144, 84, 23));
 +    userIdWidth.setBounds(new Rectangle(232, 192, 84, 23));
      userIdWidth.addActionListener(new ActionListener()
      {
  
      modellerOutput.setFont(LABEL_FONT);
      modellerOutput
              .setText(MessageManager.getString("label.use_modeller_output"));
 -    modellerOutput.setBounds(new Rectangle(228, 226, 168, 23));
 +    modellerOutput.setBounds(new Rectangle(228, 274, 320, 23));
      embbedBioJSON.setFont(LABEL_FONT);
      embbedBioJSON.setText(MessageManager.getString("label.embbed_biojson"));
 -    embbedBioJSON.setBounds(new Rectangle(228, 200, 250, 23));
 +    embbedBioJSON.setBounds(new Rectangle(228, 248, 250, 23));
  
      jPanel11.add(jLabel1);
      jPanel11.add(blcjv);
      outputTab.add(userIdWidth);
      outputTab.add(userIdWidthlabel);
      outputTab.add(modellerOutput);
 -    outputTab.add(embbedBioJSON);
 -    outputTab.add(epsLabel);
 -    outputTab.add(epsRendering);
 +    if (!Platform.isJS())
 +    {
 +      /*
 +       * JalviewJS doesn't support Lineart option or SVG output
 +       */
 +      outputTab.add(embbedBioJSON);
 +      outputTab.add(epsLabel);
 +      outputTab.add(epsRendering);
 +      outputTab.add(htmlLabel);
 +      outputTab.add(htmlRendering);
 +      outputTab.add(svgLabel);
 +      outputTab.add(svgRendering);
 +    }
      outputTab.add(jPanel11);
      return outputTab;
    }
      docFieldPref.setBounds(new Rectangle(10, ypos, 450, 120));
      structureTab.add(docFieldPref);
  
 +    /*
 +     * hide Chimera options in JalviewJS
 +     */
 +    if (Platform.isJS()) 
 +    {
 +      pathLabel.setVisible(false);
 +      chimeraPath.setVisible(false);
 +      viewerLabel.setVisible(false);
 +      structViewer.setVisible(false);
 +    }
 +    
      return structureTab;
    }
  
      visualTab.add(fontNameCB);
      visualTab.add(fontSizeCB);
      visualTab.add(fontStyleCB);
 +    
 +    if (Platform.isJS())
 +    {
 +      startupCheckbox.setVisible(false);
 +      startupFileTextfield.setVisible(false);
 +    }
 +    
      return visualTab;
    }
  
      BackupFilesPresetEntry savedPreset = BackupFilesPresetEntry
              .getSavedBackupEntry();
      enableBackupFiles
 -            .setSelected(Cache.getDefault(BackupFiles.ENABLED, true));
 +            .setSelected(Cache.getDefault(BackupFiles.ENABLED, !Platform.isJS()));
  
      BackupFilesPresetEntry backupfilesCustomEntry = BackupFilesPresetEntry
              .createBackupFilesPresetEntry(Cache
      {
        try
        {
 -        i = Integer.parseInt((String) s.getValue());
 +        i = ((Integer) s.getValue()).intValue();
        } catch (Exception e)
        {
          Cache.log.error(
      {
        max = def;
      }
 +    if (def < min)
 +    {
 +      def = min;
 +    }
      SpinnerModel sModel = new SpinnerNumberModel(def, min, max, 1);
      s.setModel(sModel);
  
@@@ -23,7 -23,7 +23,7 @@@ package jalview.ws.jws2
  import jalview.bin.Cache;
  import jalview.gui.AlignFrame;
  import jalview.util.MessageManager;
- import jalview.ws.WSMenuEntryProviderI;
+ import jalview.ws.WSDiscovererI;
  import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
  import jalview.ws.params.ParamDatastoreI;
@@@ -51,7 -51,7 +51,7 @@@ import compbio.ws.client.Services
   * @author JimP
   * 
   */
- public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
+ public class Jws2Discoverer implements WSDiscovererI, Runnable
  {
    public static final String COMPBIO_JABAWS = "http://www.compbio.dundee.ac.uk/jabaws";
  
        } catch (Exception e)
        {
        }
 -      ;
        for (JabaWsServerQuery squery : qrys)
        {
          if (squery.isRunning())
              alignFrame, typeFilter);
    }
  
 +  /**
 +   * 
 +   * @param args
 +   * @j2sIgnore
 +   */
    public static void main(String[] args)
    {
      if (args.length > 0)
        {
          testUrls.add(url);
        }
 -      ;
      }
      Thread runner = getDiscoverer()
              .startDiscoverer(new PropertyChangeListener()
        } catch (InterruptedException e)
        {
        }
 -      ;
      }
      try
      {
      return discoverer;
    }
  
+   @Override
    public boolean hasServices()
    {
      return !running && services != null && services.size() > 0;
    }
  
+   @Override
    public boolean isRunning()
    {
      return running;
    }
  
+   @Override
    public void setServiceUrls(List<String> wsUrls)
    {
      if (wsUrls != null && !wsUrls.isEmpty())
     * 
     * @return
     */
+   @Override
    public List<String> getServiceUrls()
    {
      if (testUrls != null)
      return urls;
    }
  
+   @Override
    public Vector<ServiceWithParameters> getServices()
    {
      return (services == null) ? new Vector<>()
     * @param foo
     * @return
     */
-   public static boolean testServiceUrl(URL foo)
+   @Override
+   public boolean testServiceUrl(URL foo)
    {
      try
      {
     * @param changeSupport2
     * @return new thread
     */
+   @Override
    public Thread startDiscoverer(PropertyChangeListener changeSupport2)
    {
      /*    if (restart())
     * @return a human readable report of any problems with the service URLs used
     *         for discovery
     */
+   @Override
    public String getErrorMessages()
    {
      if (!isRunning() && !isAborted())
      return null;
    }
  
+   @Override
    public int getServerStatusFor(String url)
    {
      if (validServiceUrls != null && validServiceUrls.contains(url))
      {
-       return 1;
+       return STATUS_OK;
      }
      if (urlsWithoutServices != null && urlsWithoutServices.contains(url))
      {
-       return 0;
+       return STATUS_NO_SERVICES;
      }
      if (invalidServiceUrls != null && invalidServiceUrls.contains(url))
      {
-       return -1;
+       return STATUS_INVALID;
      }
-     return -2;
+     return STATUS_UNKNOWN;
    }
  
    /**