Merge branch 'Jalview-JS/develop' into merge_js_develop
authorJim Procter <jprocter@issues.jalview.org>
Mon, 14 Dec 2020 19:58:34 +0000 (19:58 +0000)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 14 Dec 2020 20:28:39 +0000 (20:28 +0000)
also patched new code from JAL-3690 refactorings

57 files changed:
1  2 
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/TitledPanel.java
src/jalview/bin/Jalview.java
src/jalview/datamodel/Alignment.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/Desktop.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/WebserviceInfo.java
src/jalview/gui/WsJobParameters.java
src/jalview/gui/WsParamSetManager.java
src/jalview/gui/WsPreferences.java
src/jalview/io/CountReader.java
src/jalview/io/FileFormat.java
src/jalview/io/FileLoader.java
src/jalview/io/IdentifyFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/StockholmFile.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/util/Platform.java
src/jalview/util/StringUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/AlignCalcManager.java
src/jalview/ws/jws1/Discoverer.java
src/jalview/ws/jws1/MsaWSClient.java
src/jalview/ws/jws1/SeqSearchWSClient.java
src/jalview/ws/jws2/JabaWsParamTest.java
src/jalview/ws/jws2/Jws2Discoverer.java
src/jalview/ws/jws2/MsaWSClient.java
src/jalview/ws/jws2/SeqAnnotationServiceCalcWorker.java
src/jalview/ws/jws2/SequenceAnnotationWSClient.java
src/jalview/ws/jws2/jabaws2/Jws2Instance.java
src/jalview/ws/jws2/jabaws2/Jws2InstanceFactory.java
src/jalview/ws/rest/RestClient.java
test/jalview/datamodel/SequenceTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
test/jalview/gui/PairwiseAlignmentPanelTest.java
test/jalview/gui/PopupMenuTest.java
test/jalview/hmmer/HMMERTest.java
test/jalview/io/FileFormatsTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java

@@@ -34,7 -34,7 +34,8 @@@ package ext.edu.ucsf.rbvi.strucviz2
  
  import jalview.bin.Cache;
  import jalview.gui.Preferences;
 +import jalview.util.FileUtils;
+ import jalview.util.Platform;
  
  import java.io.File;
  import java.io.IOException;
@@@ -932,20 -932,22 +933,20 @@@ public class StructureManage
        pathList.add("/usr/local/chimera/bin/chimera");
        pathList.add("/usr/local/bin/chimera");
        pathList.add("/usr/bin/chimera");
-       pathList.add(System.getProperty("user.home") + "/opt/bin/chimera");
+       pathList.add(Platform.getUserPath("opt/bin/chimera"));
      }
      else if (os.startsWith("Windows"))
      {
 -      for (String root : new String[] { "\\Program Files",
 -          "C:\\Program Files", "\\Program Files (x86)",
 -          "C:\\Program Files (x86)" })
 -      {
 -        for (String version : new String[] { "1.11", "1.11.1", "1.11.2",
 -            "1.12", "1.12.1", "1.12.2", "1.13" })
 -        {
 -          pathList.add(root + "\\Chimera " + version + "\\bin\\chimera");
 -          pathList.add(
 -                  root + "\\Chimera " + version + "\\bin\\chimera.exe");
 -        }
 -      }
 +      /*
 +       * typical Windows installation path is
 +       * C:\Program Files\Chimera 1.12\bin\chimera.exe
 +       */
 +      // current drive:
 +      pathList.addAll(FileUtils.findMatches("\\",
 +              "Program Files*/Chimera*/bin/{chimera,chimera.exe}"));
 +      // C: drive (note may add as duplicates)
 +      pathList.addAll(FileUtils.findMatches("C:\\",
 +              "Program Files*/Chimera*/bin/{chimera,chimera.exe}"));
      }
      else if (os.startsWith("Mac"))
      {
@@@ -22,6 -22,8 +22,8 @@@ package jalview.analysis
  
  import jalview.analysis.scoremodels.PIDModel;
  import jalview.analysis.scoremodels.SimilarityParams;
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentOrder;
@@@ -32,6 -34,7 +34,7 @@@ import jalview.datamodel.SequenceNode
  import jalview.util.QuickSort;
  
  import java.util.ArrayList;
+ import java.util.BitSet;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
   * from the first tobesorted position in the alignment. e.g. (a,tb2,b,tb1,c,tb3
   * becomes a,tb1,tb2,tb3,b,c)
   */
- public class AlignmentSorter
+ public class AlignmentSorter implements ApplicationSingletonI
  {
+   private AlignmentSorter()
+   {
+     // private singleton
+   }
+   public static AlignmentSorter getInstance()
+   {
+     return (AlignmentSorter) ApplicationSingletonProvider
+             .getInstance(AlignmentSorter.class);
+   }
+   /**
+    * types of feature ordering: Sort by score : average score - or total score -
+    * over all features in region Sort by feature label text: (or if null -
+    * feature type text) - numerical or alphabetical Sort by feature density:
+    * based on counts - ignoring individual text or scores for each feature
+    */
+   public static final String FEATURE_SCORE = "average_score";
+   public static final String FEATURE_LABEL = "text";
+   public static final String FEATURE_DENSITY = "density";
    /*
     * todo: refactor searches to follow a basic pattern: (search property, last
     * search state, current sort direction)
     */
-   static boolean sortIdAscending = true;
+   boolean sortIdAscending = true;
  
-   static int lastGroupHash = 0;
+   int lastGroupHash = 0;
  
-   static boolean sortGroupAscending = true;
+   boolean sortGroupAscending = true;
  
-   static AlignmentOrder lastOrder = null;
+   AlignmentOrder lastOrder = null;
  
-   static boolean sortOrderAscending = true;
+   boolean sortOrderAscending = true;
  
-   static TreeModel lastTree = null;
+   TreeModel lastTree = null;
  
-   static boolean sortTreeAscending = true;
+   boolean sortTreeAscending = true;
  
-   /*
+   /**
     * last Annotation Label used for sort by Annotation score
     */
-   private static String lastSortByAnnotation;
+   private String lastSortByAnnotation;
  
-   /*
-    * string hash of last arguments to sortByFeature
-    * (sort order toggles if this is unchanged between sorts)
+   /**
+    * string hash of last arguments to sortByFeature (sort order toggles if this
+    * is unchanged between sorts)
     */
-   private static String sortByFeatureCriteria;
+   private String sortByFeatureCriteria;
  
-   private static boolean sortByFeatureAscending = true;
+   private boolean sortByFeatureAscending = true;
  
-   private static boolean sortLengthAscending;
+   private boolean sortLengthAscending;
  
 +  private static boolean sortEValueAscending;
 +
 +  private static boolean sortBitScoreAscending;
 +
    /**
     * Sorts sequences in the alignment by Percentage Identity with the given
     * reference sequence, sorting the highest identity to the top
      }
  
      QuickSort.sort(scores, seqs);
      setReverseOrder(align, seqs);
    }
  
    /**
-    * Reverse the order of the sort
-    * 
-    * @param align
-    *          DOCUMENT ME!
-    * @param seqs
-    *          DOCUMENT ME!
-    */
-   private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
-   {
-     int nSeq = seqs.length;
-     int len = 0;
-     if ((nSeq % 2) == 0)
-     {
-       len = nSeq / 2;
-     }
-     else
-     {
-       len = (nSeq + 1) / 2;
-     }
-     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-     List<SequenceI> asq = align.getSequences();
-     synchronized (asq)
-     {
-       for (int i = 0; i < len; i++)
-       {
-         // SequenceI tmp = seqs[i];
-         asq.set(i, seqs[nSeq - i - 1]);
-         asq.set(nSeq - i - 1, seqs[i]);
-       }
-     }
-   }
-   /**
-    * Sets the Alignment object with the given sequences
-    * 
-    * @param align
-    *          Alignment object to be updated
-    * @param tmp
-    *          sequences as a vector
-    */
-   private static void setOrder(AlignmentI align, List<SequenceI> tmp)
-   {
-     setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
-   }
-   /**
-    * Sets the Alignment object with the given sequences
-    * 
-    * @param align
-    *          DOCUMENT ME!
-    * @param seqs
-    *          sequences as an array
-    */
-   public static void setOrder(AlignmentI align, SequenceI[] seqs)
-   {
-     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-     List<SequenceI> algn = align.getSequences();
-     synchronized (algn)
-     {
-       List<SequenceI> tmp = new ArrayList<>();
-       for (int i = 0; i < seqs.length; i++)
-       {
-         if (algn.contains(seqs[i]))
-         {
-           tmp.add(seqs[i]);
-         }
-       }
-       algn.clear();
-       // User may have hidden seqs, then clicked undo or redo
-       for (int i = 0; i < tmp.size(); i++)
-       {
-         algn.add(tmp.get(i));
-       }
-     }
-   }
-   /**
     * Sorts by ID. Numbers are sorted before letters.
     * 
     * @param align
      }
  
      QuickSort.sort(ids, seqs);
-     if (sortIdAscending)
-     {
-       setReverseOrder(align, seqs);
-     }
-     else
-     {
-       setOrder(align, seqs);
-     }
-     sortIdAscending = !sortIdAscending;
+     AlignmentSorter as = getInstance();
+     as.sortIdAscending = !as.sortIdAscending;
+     set(align, seqs, as.sortIdAscending);
    }
  
    /**
      }
  
      QuickSort.sort(length, seqs);
-     if (sortLengthAscending)
-     {
-       setReverseOrder(align, seqs);
-     }
-     else
-     {
-       setOrder(align, seqs);
-     }
-     sortLengthAscending = !sortLengthAscending;
+     AlignmentSorter as = getInstance();
+     as.sortLengthAscending = !as.sortLengthAscending;
+     set(align, seqs, as.sortLengthAscending);
    }
  
    /**
 +   * Sorts by sequence evalue. Currently moves all sequences without an evalue to
 +   * the top of the alignment.
 +   * 
 +   * @param align
 +   *                The alignment object to sort
 +   */
 +  public static void sortByEValue(AlignmentI align)
 +  {
 +    int nSeq = align.getHeight();
 +
 +    double[] evalue = new double[nSeq];
 +    SequenceI[] seqs = new SequenceI[nSeq];
 +
 +    for (int i = 0; i < nSeq; i++)
 +    {
 +      seqs[i] = align.getSequenceAt(i);
 +      AlignmentAnnotation[] ann = seqs[i].getAnnotation("Search Scores");
 +      if (ann != null)
 +      {
 +        evalue[i] = ann[0].getEValue();
 +      }
 +      else
 +      {
 +        evalue[i] = -1;
 +      }
 +    }
 +
 +    QuickSort.sort(evalue, seqs);
 +
 +    if (sortEValueAscending)
 +    {
 +      setReverseOrder(align, seqs);
 +    }
 +    else
 +    {
 +      setOrder(align, seqs);
 +    }
 +
 +    sortEValueAscending = !sortEValueAscending;
 +  }
 +
 +  /**
 +   * Sorts by sequence bit score. Currently moves all sequences without a bit
 +   * score to the top of the alignment
 +   * 
 +   * @param align
 +   *                The alignment object to sort
 +   */
 +  public static void sortByBitScore(AlignmentI align)
 +  {
 +    int nSeq = align.getHeight();
 +
 +    double[] score = new double[nSeq];
 +    SequenceI[] seqs = new SequenceI[nSeq];
 +
 +    for (int i = 0; i < nSeq; i++)
 +    {
 +      seqs[i] = align.getSequenceAt(i);
 +      AlignmentAnnotation[] ann = seqs[i].getAnnotation("Search Scores");
 +      if (ann != null)
 +      {
 +        score[i] = ann[0].getEValue();
 +      }
 +      else
 +      {
 +        score[i] = -1;
 +      }
 +    }
 +
 +    QuickSort.sort(score, seqs);
 +
 +    if (sortBitScoreAscending)
 +    {
 +      setReverseOrder(align, seqs);
 +    }
 +    else
 +    {
 +      setOrder(align, seqs);
 +    }
 +
 +    sortBitScoreAscending = !sortBitScoreAscending;
 +  }
 +
 +  /**
     * Sorts the alignment by size of group. <br>
     * Maintains the order of sequences in each group by order in given alignment
     * object.
      // ORDERS BY GROUP SIZE
      List<SequenceGroup> groups = new ArrayList<>();
  
-     if (groups.hashCode() != lastGroupHash)
+     AlignmentSorter as = getInstance();
+     if (groups.hashCode() != as.lastGroupHash)
      {
-       sortGroupAscending = true;
-       lastGroupHash = groups.hashCode();
+       as.sortGroupAscending = true;
+       as.lastGroupHash = groups.hashCode();
      }
      else
      {
-       sortGroupAscending = !sortGroupAscending;
+       as.sortGroupAscending = !as.sortGroupAscending;
      }
  
      // SORTS GROUPS BY SIZE
  
      // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
      // /////////////////////////////////////////////
-     List<SequenceI> seqs = new ArrayList<>();
+     List<SequenceI> tmp = new ArrayList<>();
  
      for (int i = 0; i < groups.size(); i++)
      {
  
        for (int j = 0; j < orderedseqs.length; j++)
        {
-         seqs.add(orderedseqs[j]);
-       }
-     }
-     if (sortGroupAscending)
-     {
-       setOrder(align, seqs);
-     }
-     else
-     {
-       setReverseOrder(align,
-               vectorSubsetToArray(seqs, align.getSequences()));
-     }
-   }
-   /**
-    * Select sequences in order from tmp that is present in mask, and any
-    * remaining sequences in mask not in tmp
-    * 
-    * @param tmp
-    *          thread safe collection of sequences
-    * @param mask
-    *          thread safe collection of sequences
-    * 
-    * @return intersect(tmp,mask)+intersect(complement(tmp),mask)
-    */
-   private static SequenceI[] vectorSubsetToArray(List<SequenceI> tmp,
-           List<SequenceI> mask)
-   {
-     // or?
-     // tmp2 = tmp.retainAll(mask);
-     // return tmp2.addAll(mask.removeAll(tmp2))
-     ArrayList<SequenceI> seqs = new ArrayList<>();
-     int i, idx;
-     boolean[] tmask = new boolean[mask.size()];
-     for (i = 0; i < mask.size(); i++)
-     {
-       tmask[i] = true;
-     }
-     for (i = 0; i < tmp.size(); i++)
-     {
-       SequenceI sq = tmp.get(i);
-       idx = mask.indexOf(sq);
-       if (idx > -1 && tmask[idx])
-       {
-         tmask[idx] = false;
-         seqs.add(sq);
-       }
-     }
-     for (i = 0; i < tmask.length; i++)
-     {
-       if (tmask[i])
-       {
-         seqs.add(mask.get(i));
+         tmp.add(orderedseqs[j]);
        }
      }
-     return seqs.toArray(new SequenceI[seqs.size()]);
+     set(align, tmp, as.sortGroupAscending);
    }
  
    /**
      // Get an ordered vector of sequences which may also be present in align
      List<SequenceI> tmp = order.getOrder();
  
-     if (lastOrder == order)
-     {
-       sortOrderAscending = !sortOrderAscending;
-     }
-     else
-     {
-       sortOrderAscending = true;
-     }
+     AlignmentSorter as = getInstance();
  
-     if (sortOrderAscending)
+     if (as.lastOrder == order)
      {
-       setOrder(align, tmp);
+       as.sortOrderAscending = !as.sortOrderAscending;
      }
      else
      {
-       setReverseOrder(align,
-               vectorSubsetToArray(tmp, align.getSequences()));
+       as.sortOrderAscending = true;
      }
+     set(align, tmp, as.sortOrderAscending);
    }
  
    /**
    {
      List<SequenceI> tmp = getOrderByTree(align, tree);
  
-     // tmp should properly permute align with tree.
-     if (lastTree != tree)
-     {
-       sortTreeAscending = true;
-       lastTree = tree;
-     }
-     else
-     {
-       sortTreeAscending = !sortTreeAscending;
-     }
+     AlignmentSorter as = getInstance();
  
-     if (sortTreeAscending)
+     // tmp should properly permute align with tree.
+     if (as.lastTree != tree)
      {
-       setOrder(align, tmp);
+       as.sortTreeAscending = true;
+       as.lastTree = tree;
      }
      else
      {
-       setReverseOrder(align,
-               vectorSubsetToArray(tmp, align.getSequences()));
+       as.sortTreeAscending = !as.sortTreeAscending;
      }
+     set(align, tmp, as.sortTreeAscending);
    }
  
    /**
      }
  
      jalview.util.QuickSort.sort(scores, seqs);
-     if (lastSortByAnnotation != scoreLabel)
+     AlignmentSorter as = getInstance();
+     if (as.lastSortByAnnotation != scoreLabel)
      {
-       lastSortByAnnotation = scoreLabel;
+       as.lastSortByAnnotation = scoreLabel;
        setOrder(alignment, seqs);
      }
      else
    }
  
    /**
-    * types of feature ordering: Sort by score : average score - or total score -
-    * over all features in region Sort by feature label text: (or if null -
-    * feature type text) - numerical or alphabetical Sort by feature density:
-    * based on counts - ignoring individual text or scores for each feature
-    */
-   public static String FEATURE_SCORE = "average_score";
-   public static String FEATURE_LABEL = "text";
-   public static String FEATURE_DENSITY = "density";
-   /**
     * Sort sequences by feature score or density, optionally restricted by
     * feature types, feature groups, or alignment start/end positions.
     * <p>
      if (method != FEATURE_SCORE && method != FEATURE_LABEL
              && method != FEATURE_DENSITY)
      {
-       String msg = String
-               .format("Implementation Error - sortByFeature method must be either '%s' or '%s'",
-                       FEATURE_SCORE, FEATURE_DENSITY);
+       String msg = String.format(
+               "Implementation Error - sortByFeature method must be either '%s' or '%s'",
+               FEATURE_SCORE, FEATURE_DENSITY);
        System.err.println(msg);
        return;
      }
  
-     flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol, endCol);
+     flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol,
+             endCol);
  
      SequenceI[] seqs = alignment.getSequencesArray();
  
         * get sequence residues overlapping column region
         * and features for residue positions and specified types
         */
-       String[] types = featureTypes == null ? null : featureTypes
-               .toArray(new String[featureTypes.size()]);
+       String[] types = featureTypes == null ? null
+               : featureTypes.toArray(new String[featureTypes.size()]);
        List<SequenceFeature> sfs = seqs[i].findFeatures(startCol + 1,
                endCol + 1, types);
  
        }
      }
  
+     boolean doSort = false;
      if (FEATURE_SCORE.equals(method))
      {
        if (hasScores == 0)
            }
          }
        }
-       QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
+       doSort = true;
      }
      else if (FEATURE_DENSITY.equals(method))
      {
          // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
          // " Feats: "+featureCount+" Score : "+scores[i]);
        }
-       QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
+       doSort = true;
+     }
+     if (doSort)
+     {
+       QuickSort.sortByDouble(scores, seqs,
+               getInstance().sortByFeatureAscending);
      }
      setOrder(alignment, seqs);
    }
  
      /*
       * if resorting on the same criteria, toggle sort order
       */
-     if (sortByFeatureCriteria == null
-             || !scoreCriteria.equals(sortByFeatureCriteria))
+     AlignmentSorter as = getInstance();
+     if (as.sortByFeatureCriteria == null
+             || !scoreCriteria.equals(as.sortByFeatureCriteria))
      {
-       sortByFeatureAscending = true;
+       as.sortByFeatureAscending = true;
      }
      else
      {
-       sortByFeatureAscending = !sortByFeatureAscending;
+       as.sortByFeatureAscending = !as.sortByFeatureAscending;
      }
-     sortByFeatureCriteria = scoreCriteria;
+     as.sortByFeatureCriteria = scoreCriteria;
+   }
+   /**
+    * Set the alignment's sequences list to contain the sequences from a
+    * temporary list, first adding all the elements from the tmp list, then adding all sequences in the alignment that
+    * are not in the list. Option to do the final sort either in order or in reverse order.
+    * 
+    * @param align The alignment being sorted
+    * @param tmp
+    *          the temporary sequence list
+    * @param ascending
+    *          false for reversed order; only sequences already in
+    *          the alignment will be used (which is actually already guaranteed
+    *          by vectorSubsetToArray)
+    */
+   private static void set(AlignmentI align, List<SequenceI> tmp,
+           boolean ascending)
+   {
+     set(align, vectorSubsetToArray(align.getSequences(), tmp), ascending);
+   }
+   /**
+    * Set the alignment's sequences list to contain these sequences, either in
+    * this order or its reverse.
+    * 
+    * @param align
+    * @param seqs
+    *          the new sequence array
+    * @param ascending
+    *          false for reversed order; if ascending, only sequences already in
+    *          the alignment will be used; if descending, then a direct 1:1
+    *          replacement is made
+    */
+   private static void set(AlignmentI align, SequenceI[] seqs,
+           boolean ascending)
+   {
+     if (ascending)
+     {
+       setOrder(align, seqs);
+     }
+     else
+     {
+       setReverseOrder(align, seqs);
+     }
+   }
+   /**
+    * Replace the alignment's sequences with values in an array, clearing the
+    * alignment's sequence list and filtering for sequences that are actually in
+    * the alignment already.
+    * 
+    * @param align
+    *          the Alignment
+    * @param seqs
+    *          the array of replacement values, of any length
+    */
+   public static void setOrder(AlignmentI align, SequenceI[] seqs)
+   {
+     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
+     List<SequenceI> seqList = align.getSequences();
+     synchronized (seqList)
+     {
+       List<SequenceI> tmp = new ArrayList<>();
+       for (int i = 0; i < seqs.length; i++)
+       {
+         if (seqList.contains(seqs[i]))
+         {
+           tmp.add(seqs[i]);
+         }
+       }
+       seqList.clear();
+       // User may have hidden seqs, then clicked undo or redo
+       for (int i = 0; i < tmp.size(); i++)
+       {
+         seqList.add(tmp.get(i));
+       }
+     }
+   }
+   /**
+    * Replace the alignment's sequences or a subset of those sequences with
+    * values in an array in reverse order. All sequences are replaced; no check
+    * is made that these sequences are in the alignment already.
+    * 
+    * @param align
+    *          the Alignment
+    * @param seqs
+    *          the array of replacement values, length must be less than or equal
+    *          to Alignment.sequences.size()
+    */
+   private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
+   {
+     int nSeq = seqs.length;
+     int len = (nSeq + (nSeq % 2)) / 2;
+     // int len = 0;
+     //
+     // if ((nSeq % 2) == 0)
+     // {
+     // len = nSeq / 2;
+     // }
+     // else
+     // {
+     // len = (nSeq + 1) / 2;
+     // }
+     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
+     List<SequenceI> seqList = align.getSequences();
+     synchronized (seqList)
+     {
+       for (int i = 0; i < len; i++)
+       {
+         // SequenceI tmp = seqs[i];
+         seqList.set(i, seqs[nSeq - i - 1]);
+         seqList.set(nSeq - i - 1, seqs[i]);
+       }
+     }
+   }
+   /**
+    * Create and array of reordered sequences in order first from tmp that are
+    * present in seqList already, then, after that, any remaining sequences in
+    * seqList not in tmp. Any sequences in tmp that are not in seqList already
+    * are discarded.
+    * 
+    * @param seqList
+    *          thread safe collection of sequences originally in the alignment
+    * @param tmp
+    *          thread safe collection of sequences or subsequences possibly in
+    *          seqList
+    * 
+    * @return intersect(tmp,seqList)+intersect(complement(tmp),seqList)
+    */
+   private static SequenceI[] vectorSubsetToArray(List<SequenceI> seqList,
+           List<SequenceI> tmp)
+   {
+     ArrayList<SequenceI> seqs = new ArrayList<>();
+     int n = seqList.size();
+     BitSet bs = new BitSet(n);
+     bs.set(0, n);
+     for (int i = 0, nt = tmp.size(); i < nt; i++)
+     {
+       SequenceI sq = tmp.get(i);
+       int idx = seqList.indexOf(sq);
+       if (idx >= 0 && bs.get(idx))
+       {
+         seqs.add(sq);
+         bs.clear(idx);
+       }
+     }
+     for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
+     {
+       seqs.add(seqList.get(i));
+     }
+     return seqs.toArray(new SequenceI[seqs.size()]);
    }
  
  }
@@@ -236,7 -236,6 +236,7 @@@ public class AlignFrame extends Embmenu
              alignPanel);
      viewport.updateConservation(alignPanel);
      viewport.updateConsensus(alignPanel);
 +    viewport.initInformationWorker(alignPanel);
  
      displayNonconservedMenuItem.setState(viewport.getShowUnconserved());
      followMouseOverFlag.setState(viewport.isFollowHighlight());
      }
      else if (source == autoCalculate)
      {
-       viewport.autoCalculateConsensus = autoCalculate.getState();
+       viewport.setAutoCalculateConsensusAndConservation(autoCalculate.getState());
      }
      else if (source == sortByTree)
      {
      {
        sortGroupMenuItem_actionPerformed();
      }
 +    else if (source == sortEValueMenuItem)
 +    {
 +      sortEValueMenuItem_actionPerformed();
 +    }
 +    else if (source == sortBitScoreMenuItem)
 +    {
 +      sortBitScoreMenuItem_actionPerformed();
 +    }
      else if (source == removeRedundancyMenuItem)
      {
        removeRedundancyMenuItem_actionPerformed();
                                            // viewport.getColumnSelection().getHiddenColumns()
                                            // != null;
      updateEditMenuBar();
-     originalSource.firePropertyChange("alignment", null,
-             originalSource.getAlignment().getSequences());
+     originalSource.notifyAlignment();
    }
  
    /**
                                            // != null;
  
      updateEditMenuBar();
-     originalSource.firePropertyChange("alignment", null,
-             originalSource.getAlignment().getSequences());
+     originalSource.notifyAlignment();
    }
  
    AlignmentViewport getOriginatingSource(CommandI command)
      viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight() - 1); // BH
                                                                               // 2019.04.18
      viewport.getAlignment().getWidth();
-     viewport.firePropertyChange("alignment", null,
-             viewport.getAlignment().getSequences());
+     viewport.notifyAlignment();
  
    }
  
      viewport.setSelectionGroup(null);
      viewport.getAlignment().deleteGroup(sg);
  
-     viewport.firePropertyChange("alignment", null,
-             viewport.getAlignment().getSequences());
+     viewport.notifyAlignment();
  
      if (viewport.getAlignment().getHeight() < 1)
      {
          }
        }
  
-       viewport.firePropertyChange("alignment", null, al.getSequences());
+       viewport.notifyAlignment();
      }
    }
  
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
      ranges.setStartRes(seq.findIndex(startRes) - 1);
-     viewport.firePropertyChange("alignment", null, al.getSequences());
+     viewport.notifyAlignment();
  
    }
  
  
      ranges.setStartRes(seq.findIndex(startRes) - 1);
  
-     viewport.firePropertyChange("alignment", null, al.getSequences());
+     viewport.notifyAlignment();
  
    }
  
  
    }
  
 +  public void sortEValueMenuItem_actionPerformed()
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByEValue(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
 +  public void sortBitScoreMenuItem_actionPerformed()
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByBitScore(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
    public void removeRedundancyMenuItem_actionPerformed()
    {
      new RedundancyPanel(alignPanel);
  
    MenuItem sortGroupMenuItem = new MenuItem();
  
 +  MenuItem sortEValueMenuItem = new MenuItem();
 +
 +  MenuItem sortBitScoreMenuItem = new MenuItem();
 +
    MenuItem removeRedundancyMenuItem = new MenuItem();
  
    MenuItem pairwiseAlignmentMenuItem = new MenuItem();
@@@ -1,6 -1,6 +1,6 @@@
  /*
 - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
   * Copyright (C) $$Year-Rel$$ The Jalview Authors
 + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
   * 
   * This file is part of Jalview.
   * 
@@@ -54,16 -54,10 +54,16 @@@ public class AlignViewport extends Alig
  
    private AnnotationColumnChooser annotationColumnSelectionState;
  
 +  java.awt.Frame nullFrame;
 +
 +  protected FeatureSettings featureSettings = null;
 +
 +  private float heightScale = 1, widthScale = 1;
 +
    public AlignViewport(AlignmentI al, JalviewLite applet)
    {
      super(al);
 -    calculator = new jalview.workers.AlignCalcManager();
 +    calculator = new jalview.workers.AlignCalcManager2();
      this.applet = applet;
  
      // we always pad gaps
                          colour));
          if (residueShading != null)
          {
 -          residueShading.setConsensus(hconsensus);
 +          residueShading.setConsensus(consensusProfiles);
          }
        }
  
        }
      }
      initAutoAnnotation();
 -
    }
  
 -  java.awt.Frame nullFrame;
 -
 -  protected FeatureSettings featureSettings = null;
 -
 -  private float heightScale = 1, widthScale = 1;
 -
    /**
     * {@inheritDoc}
     */
              .getStructureSelectionManager(applet);
    }
  
 -  @Override
 -  public boolean isNormaliseSequenceLogo()
 -  {
 -    return normaliseSequenceLogo;
 -  }
 -
 -  public void setNormaliseSequenceLogo(boolean state)
 -  {
 -    normaliseSequenceLogo = state;
 -  }
 -
    /**
     * 
     * @return true if alignment characters should be displayed
      if (mappedCommand != null)
      {
        mappedCommand.doCommand(null);
-       firePropertyChange("alignment", null, getAlignment().getSequences());
+       notifyAlignment();
  
        // ap.scalePanelHolder.repaint();
        // ap.repaint();
   */
  package jalview.appletgui;
  
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.SequenceI;
 +import jalview.renderer.AnnotationRenderer;
 +import jalview.renderer.AwtRenderPanelI;
 +import jalview.schemes.ResidueProperties;
 +import jalview.util.Comparison;
 +import jalview.util.MessageManager;
++import jalview.util.Platform;
 +import jalview.viewmodel.ViewportListenerI;
 +import jalview.viewmodel.ViewportRanges;
 +
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Font;
@@@ -50,6 -39,17 +51,17 @@@ import java.awt.event.MouseListener
  import java.awt.event.MouseMotionListener;
  import java.beans.PropertyChangeEvent;
  
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.SequenceI;
+ import jalview.renderer.AnnotationRenderer;
+ import jalview.renderer.AwtRenderPanelI;
+ import jalview.schemes.ResidueProperties;
+ import jalview.util.Comparison;
+ import jalview.util.MessageManager;
+ import jalview.viewmodel.ViewportListenerI;
+ import jalview.viewmodel.ViewportRanges;
  public class AnnotationPanel extends Panel
          implements AwtRenderPanelI, AdjustmentListener, ActionListener,
          MouseListener, MouseMotionListener, ViewportListenerI
@@@ -23,7 -23,6 +23,8 @@@ package jalview.appletgui
  import java.awt.Graphics;
  import java.awt.Insets;
  import java.awt.Panel;
++import java.awt.event.WindowAdapter;
++import java.awt.event.WindowEvent;
  
  public class TitledPanel extends Panel
  {
   */
  package jalview.bin;
  
- import jalview.ext.so.SequenceOntology;
- import jalview.gui.AlignFrame;
- import jalview.gui.Desktop;
- import jalview.gui.PromptUserConfig;
- import jalview.io.AppletFormatAdapter;
- import jalview.io.BioJsHTMLOutput;
- import jalview.io.DataSourceType;
- import jalview.io.FileFormat;
- import jalview.io.FileFormatException;
- import jalview.io.FileFormatI;
- import jalview.io.FileLoader;
- import jalview.io.HtmlSvgOutput;
- import jalview.io.IdentifyFile;
- import jalview.io.NewickFile;
- import jalview.io.gff.SequenceOntologyFactory;
- import jalview.schemes.ColourSchemeI;
- import jalview.schemes.ColourSchemeProperty;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
- import jalview.ws.jws2.Jws2Discoverer;
+ import java.awt.GraphicsEnvironment;
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileOutputStream;
@@@ -60,9 -40,6 +40,6 @@@ import java.security.Policy
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Vector;
- import java.util.logging.ConsoleHandler;
- import java.util.logging.Level;
- import java.util.logging.Logger;
  
  import javax.swing.LookAndFeel;
  import javax.swing.UIManager;
@@@ -71,6 -48,31 +48,31 @@@ import com.threerings.getdown.util.Laun
  
  import groovy.lang.Binding;
  import groovy.util.GroovyScriptEngine;
+ import jalview.api.AlignCalcWorkerI;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+ import jalview.ext.so.SequenceOntology;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.AlignViewport;
+ import jalview.gui.Desktop;
+ import jalview.gui.Preferences;
+ import jalview.gui.PromptUserConfig;
+ import jalview.io.AppletFormatAdapter;
+ import jalview.io.BioJsHTMLOutput;
+ 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.HtmlSvgOutput;
+ import jalview.io.IdentifyFile;
+ import jalview.io.NewickFile;
+ import jalview.io.gff.SequenceOntologyFactory;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemeProperty;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
+ import jalview.ws.jws2.Jws2Discoverer;
  
  /**
   * Main class for Jalview Application <br>
   * @author $author$
   * @version $Revision$
   */
- public class Jalview
+ public class Jalview implements ApplicationSingletonI
  {
-   static
+   // for testing those nasty messages you cannot ever find.
+   // static
+   // {
+   // System.setOut(new PrintStream(new ByteArrayOutputStream())
+   // {
+   // @Override
+   // public void println(Object o)
+   // {
+   // if (o != null)
+   // {
+   // System.err.println(o);
+   // }
+   // }
+   //
+   // });
+   // }
+   public static Jalview getInstance()
    {
-     Platform.getURLCommandArguments();
+     return (Jalview) ApplicationSingletonProvider
+             .getInstance(Jalview.class);
    }
  
-   // singleton instance of this class
+   private Jalview()
+   {
+   }
  
-   private static Jalview instance;
+   private boolean headless;
  
    private Desktop desktop;
  
-   public static AlignFrame currentAlignFrame;
+   public AlignFrame currentAlignFrame;
+   public String appletResourcePath;
+   public String j2sAppletID;
+   private boolean noCalculation, noMenuBar, noStatus;
+   private boolean noAnnotation;
+   public boolean getStartCalculations()
+   {
+     return !noCalculation;
+   }
+   public boolean getAllowMenuBar()
+   {
+     return !noMenuBar;
+   }
+   public boolean getShowStatus()
+   {
+     return !noStatus;
+   }
+   public boolean getShowAnnotation()
+   {
+     return !noAnnotation;
+   }
  
    static
    {
-     if (!Platform.isJS())
-     /**
-      * Java only
-      * 
-      * @j2sIgnore
-      */
+     if (Platform.isJS()) {
+         Platform.getURLCommandArguments();
+     } else /** @j2sIgnore */
      {
        // grab all the rights we can for the JVM
        Policy.setPolicy(new Policy()
            perms.add(new AllPermission());
            return (perms);
          }
-   
          @Override
          public void refresh()
          {
    class FeatureFetcher
    {
      /*
 -     * TODO: generalise to track all jalview events to orchestrate batch processing
 -     * events.
 +     * TODO: generalise to track all jalview events to orchestrate batch
 +     * processing events.
       */
  
      private int queued = 0;
  
    }
  
-   public static Jalview getInstance()
-   {
-     return instance;
-   }
+   private final static boolean doPlatformLogging = false;
  
    /**
     * main class for Jalview application
     * 
     * @param args
-    *               open <em>filename</em>
+    *          open <em>filename</em>
     */
    public static void main(String[] args)
    {
- //  setLogging(); // BH - for event debugging in JavaScript
-     instance = new Jalview();
-     instance.doMain(args);
- }
-   private static void logClass(String name) 
-   {  
-     // BH - for event debugging in JavaScript
-       ConsoleHandler consoleHandler = new ConsoleHandler();
-       consoleHandler.setLevel(Level.ALL);
-       Logger logger = Logger.getLogger(name);
-       logger.setLevel(Level.ALL);
-       logger.addHandler(consoleHandler);
-   }
-   @SuppressWarnings("unused")
-   private static void setLogging() 
-   {
-     /**
-      * @j2sIgnore
-      * 
-      */
+     if (doPlatformLogging)
      {
-       System.out.println("not in js");
+       Platform.startJavaLogging();
      }
-     // BH - for event debugging in JavaScript (Java mode only)
-     if (!Platform.isJS())
-     /**
-      * Java only
-      * 
-      * @j2sIgnore
-      */
-     {
-       Logger.getLogger("").setLevel(Level.ALL);
-         logClass("java.awt.EventDispatchThread");
-         logClass("java.awt.EventQueue");
-         logClass("java.awt.Component");
-         logClass("java.awt.focus.Component");
-         logClass("java.awt.focus.DefaultKeyboardFocusManager"); 
-     }  
+     getInstance().doMain(args);
    }
-   
-   
  
    /**
     * @param args
    void doMain(String[] args)
    {
  
-     if (!Platform.isJS())
+     boolean isJS = Platform.isJS();
+     if (!isJS)
      {
        System.setSecurityManager(null);
      }
  
      System.out
-             .println("Java version: "
-                     + System.getProperty("java.version"));
+             .println("Java version: " + System.getProperty("java.version"));
      System.out.println("Java Home: " + System.getProperty("java.home"));
      System.out.println(System.getProperty("os.arch") + " "
              + System.getProperty("os.name") + " "
              + System.getProperty("os.version"));
      String val = System.getProperty("sys.install4jVersion");
-     if (val != null) {
-     System.out.println("Install4j version: " + val);
+     if (val != null)
+     {
+       System.out.println("Install4j version: " + val);
      }
      val = System.getProperty("installer_template_version");
-     if (val != null) {
+     if (val != null)
+     {
        System.out.println("Install4j template version: " + val);
      }
      val = System.getProperty("launcher_version");
-     if (val != null) {
+     if (val != null)
+     {
        System.out.println("Launcher version: " + val);
      }
  
      // report Jalview version
-     Cache.loadBuildProperties(true);
+     Cache.getInstance().loadBuildProperties(true);
  
      ArgsParser aparser = new ArgsParser(args);
-     boolean headless = false;
+     headless = false;
  
      String usrPropsFile = aparser.getValue("props");
      Cache.loadProperties(usrPropsFile); // must do this before
-     if (usrPropsFile != null)
+     boolean allowServices = true;
+     
+     if (isJS)
      {
-       System.out.println(
-               "CMD [-props " + usrPropsFile + "] executed successfully!");
+       j2sAppletID = Platform.getAppID(null);
+       Preferences.setAppletDefaults();
+       Cache.loadProperties(usrPropsFile); // again, because we
+       // might be changing defaults here?
+       appletResourcePath = (String) aparser.getAppletValue("resourcepath",
+               null, true);
      }
-     if (!Platform.isJS())
+     else
      /**
       * Java only
       * 
       * @j2sIgnore
       */
      {
+       if (usrPropsFile != null)
+       {
+         System.out.println(
+                 "CMD [-props " + usrPropsFile + "] executed successfully!");
+       }
        if (aparser.contains("help") || aparser.contains("h"))
        {
          showUsage();
          System.exit(0);
        }
+       // BH note: Only -nodisplay is official; others are deprecated?
        if (aparser.contains("nodisplay") || aparser.contains("nogui")
-               || aparser.contains("headless"))
+               || aparser.contains("headless")
+               || GraphicsEnvironment.isHeadless())
        {
-         System.setProperty("java.awt.headless", "true");
+         if (!isJS) {
+           // BH Definitely not a good idea in JavaScript; 
+           // probably should not be here for Java, either.  
+           System.setProperty("java.awt.headless", "true");
+         }
          headless = true;
        }
-       // anything else!
  
-       final String jabawsUrl = aparser.getValue("jabaws");
-       if (jabawsUrl != null)
+       final String jabawsUrl = aparser.getValue(ArgsParser.JABAWS);
+       allowServices = !("none".equals(jabawsUrl));
+       if (allowServices && jabawsUrl != null)
        {
          try
          {
-           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
+           Jws2Discoverer.getInstance().setPreferredUrl(jabawsUrl);
            System.out.println(
                    "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
          } catch (MalformedURLException e)
        }
  
      }
-     String defs = aparser.getValue("setprop");
+     String defs = aparser.getValue(ArgsParser.SETPROP);
      while (defs != null)
      {
        int p = defs.indexOf('=');
        else
        {
          System.out.println("Executing setprop argument: " + defs);
-         if (Platform.isJS())
+         if (isJS)
          {
-           Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
+           Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
          }
        }
        defs = aparser.getValue("setprop");
      }
-     if (System.getProperty("java.awt.headless") != null
-             && System.getProperty("java.awt.headless").equals("true"))
-     {
-       headless = true;
-     }
      System.setProperty("http.agent",
              "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
      try
        System.exit(0);
      }
  
-     desktop = null;
-     try
-     {
-       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-     } catch (Exception ex)
+     if (!isJS)
+     /** @j2sIgnore */
      {
-       System.err.println("Unexpected Look and Feel Exception");
-       ex.printStackTrace();
-     }
-     if (Platform.isAMacAndNotJS())
-     {
-       LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
-               .getLookAndFeel();
-       System.setProperty("com.apple.mrj.application.apple.menu.about.name",
-               "Jalview");
-       System.setProperty("apple.laf.useScreenMenuBar", "true");
-       if (lookAndFeel != null)
+       try
        {
-         try
-         {
-           UIManager.setLookAndFeel(lookAndFeel);
-         } catch (Throwable e)
-         {
-           System.err.println(
-                   "Failed to set QuaQua look and feel: " + e.toString());
-         }
+         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+       } catch (Exception ex)
+       {
+         System.err.println("Unexpected Look and Feel Exception");
+         ex.printStackTrace();
        }
-       if (lookAndFeel == null
-               || !(lookAndFeel.getClass().isAssignableFrom(
-                       UIManager.getLookAndFeel().getClass()))
-               || !UIManager.getLookAndFeel().getClass().toString()
-                       .toLowerCase().contains("quaqua"))
+       if (Platform.isMac())
        {
-         try
+         LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
+                 .getLookAndFeel();
+         System.setProperty(
+                 "com.apple.mrj.application.apple.menu.about.name",
+                 "Jalview");
+         System.setProperty("apple.laf.useScreenMenuBar", "true");
+         if (lookAndFeel != null)
          {
-           System.err.println(
-                   "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
-           UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
-         } catch (Throwable e)
+           try
+           {
+             UIManager.setLookAndFeel(lookAndFeel);
+           } catch (Throwable e)
+           {
+             System.err.println(
+                     "Failed to set QuaQua look and feel: " + e.toString());
+           }
+         }
+         if (lookAndFeel == null
+                 || !(lookAndFeel.getClass().isAssignableFrom(
+                         UIManager.getLookAndFeel().getClass()))
+                 || !UIManager.getLookAndFeel().getClass().toString()
+                         .toLowerCase().contains("quaqua"))
          {
-           System.err.println(
-                   "Failed to reset look and feel: " + e.toString());
+           try
+           {
+             System.err.println(
+                     "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
+             UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
+           } catch (Throwable e)
+           {
+             System.err.println(
+                     "Failed to reset look and feel: " + e.toString());
+           }
          }
        }
      }
      /*
       * configure 'full' SO model if preferences say to, else use the default (full SO)
       * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
       */
-     boolean soDefault = !Platform.isJS();
+     boolean soDefault = !isJS;
      if (Cache.getDefault("USE_FULL_SO", soDefault))
      {
-       SequenceOntologyFactory.setInstance(new SequenceOntology());
+       SequenceOntologyFactory.setSequenceOntology(new SequenceOntology());
      }
  
+     desktop = null;
      if (!headless)
      {
-       desktop = new Desktop();
+       desktop = Desktop.getInstance();
        desktop.setInBatchMode(true); // indicate we are starting up
        try
        {
          JalviewTaskbar.setTaskbar(this);
        {
          System.out.println("Error setting Taskbar: " + t.getMessage());
        }
        desktop.setVisible(true);
 -
 +      if (Platform.isJS())
 +        Cache.setProperty("SHOW_JWS2_SERVICES", "false");
-       desktop.startServiceDiscovery();
-       if (!Platform.isJS())
++      if (allowServices)
++      {
++        desktop.startServiceDiscovery();
++      }
+       if (!isJS)
        /**
         * Java only
         * 
         * @j2sIgnore
         */
        {
 -        if (allowServices)
 -          desktop.startServiceDiscovery();
          if (!aparser.contains("nousagestats"))
          {
            startUsageStats(desktop);
          BioJsHTMLOutput.updateBioJS();
        }
      }
+     parseArguments(aparser, true);
+   }
  
-     // Move any new getdown-launcher-new.jar into place over old
-     // getdown-launcher.jar
-     String appdirString = System.getProperty("getdownappdir");
-     if (appdirString != null && appdirString.length() > 0)
+   /**
+    * Parse all command-line String[] arguments as well as all JavaScript-derived
+    * parameters from Info.
+    * 
+    * We allow for this method to be run from JavaScript. Basically allowing
+    * simple scripting.
+    * 
+    * @param aparser
+    * @param isStartup
+    */
+   public void parseArguments(ArgsParser aparser, boolean isStartup)
+   {
+     String groovyscript = null; // script to execute after all loading is
+     boolean isJS = Platform.isJS();
+     if (!isJS)
+     /** @j2sIgnore */
      {
-       final File appdir = new File(appdirString);
-       new Thread()
+       // Move any new getdown-launcher-new.jar into place over old
+       // getdown-launcher.jar
+       String appdirString = System.getProperty("getdownappdir");
+       if (appdirString != null && appdirString.length() > 0)
        {
-         @Override
-         public void run()
+         final File appdir = new File(appdirString);
+         new Thread()
          {
-           LaunchUtil.upgradeGetdown(
-                   new File(appdir, "getdown-launcher-old.jar"),
-                   new File(appdir, "getdown-launcher.jar"),
-                   new File(appdir, "getdown-launcher-new.jar"));
-         }
-       }.start();
-     }
+           @Override
+           public void run()
+           {
+             LaunchUtil.upgradeGetdown(
+                     new File(appdir, "getdown-launcher-old.jar"),
+                     new File(appdir, "getdown-launcher.jar"),
+                     new File(appdir, "getdown-launcher-new.jar"));
+           }
+         }.start();
+       }
  
-     String file = null, data = null;
-     FileFormatI format = null;
-     DataSourceType protocol = null;
-     FileLoader fileLoader = new FileLoader(!headless);
+       // completed one way or another
+       // extract groovy argument and execute if necessary
+       groovyscript = aparser.getValue("groovy", true);
  
-     String groovyscript = null; // script to execute after all loading is
-     // completed one way or another
-     // extract groovy argument and execute if necessary
-     groovyscript = aparser.getValue("groovy", true);
-     file = aparser.getValue("open", true);
+     }
+     String file = aparser.getValue("open", true);
  
-     if (file == null && desktop == null)
+     if (!isJS && file == null && desktop == null)
      {
        System.out.println("No files to open!");
        System.exit(1);
      }
+     setDisplayParameters(aparser);
+     
+     // time to open a file.
      long progress = -1;
+     DataSourceType protocol = null;
+     FileLoader fileLoader = new FileLoader(!headless);
+     FileFormatI format = null;
      // Finally, deal with the remaining input data.
-     if (file != null)
+     AlignFrame af = null;
+     JalviewJSApp jsApp = (isJS ? new JalviewJSApp(this, aparser) : null);
+     if (file == null)
+     {
+       if (isJS)
+       {
+         // JalviewJS allows sequence1 sequence2 ....
+         
+       }
+       else if (!headless && Cache.getDefault("SHOW_STARTUP_FILE", true))
+       /**
+        * Java only
+        * 
+        * @j2sIgnore
+        */
+       {
+         // We'll only open the default file if the desktop is visible.
+         // And the user
+         // ////////////////////
+         file = Cache.getDefault("STARTUP_FILE",
+                 Cache.getDefault("www.jalview.org",
+                         "http://www.jalview.org")
+                         + "/examples/exampleFile_2_7.jar");
+         if (file.equals(
+                 "http://www.jalview.org/examples/exampleFile_2_3.jar"))
+         {
+           // hardwire upgrade of the startup file
+           file.replace("_2_3.jar", "_2_7.jar");
+           // and remove the stale setting
+           Cache.removeProperty("STARTUP_FILE");
+         }
+         protocol = DataSourceType.FILE;
+         if (file.indexOf("http:") > -1)
+         {
+           protocol = DataSourceType.URL;
+         }
+         if (file.endsWith(".jar"))
+         {
+           format = FileFormat.Jalview;
+         }
+         else
+         {
+           try
+           {
+             format = new IdentifyFile().identify(file, protocol);
+           } catch (FileFormatException e)
+           {
+             // TODO what?
+           }
+         }
+         af = fileLoader.LoadFileWaitTillLoaded(file, protocol, format);
+       }
+     }
+     else
      {
        if (!headless)
        {
        System.out.println("CMD [-open " + file + "] executed successfully!");
  
        if (!Platform.isJS())
-         /**
-          * ignore in JavaScript -- can't just file existence - could load it?
-          * 
-          * @j2sIgnore
-          */
+       /**
+        * ignore in JavaScript -- can't just file existence - could load it?
+        * 
+        * @j2sIgnore
+        */
        {
          if (!file.startsWith("http://") && !file.startsWith("https://"))
          // BH 2019 added https check for Java
            }
          }
        }
-         protocol = AppletFormatAdapter.checkProtocol(file);
+       
+       String fileFormat = (isJS
+               ? (String) aparser.getAppletValue("format", null, true)
+               : null);
+       protocol = AppletFormatAdapter.checkProtocol(file);
        try
        {
-         format = new IdentifyFile().identify(file, protocol);
+         format = (fileFormat != null
+                 ? FileFormats.getInstance().forName(fileFormat)
+                 : null);
+         if (format == null)
+         {
+           format = new IdentifyFile().identify(file, protocol);
+         }
        } catch (FileFormatException e1)
        {
          // TODO ?
        }
  
-       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
+       af = new FileLoader(!headless).LoadFileWaitTillLoaded(file, protocol,
                format);
        if (af == null)
        {
-         System.out.println("error");
+         System.out.println("jalview error - AlignFrame was not created");
        }
        else
        {
-         setCurrentAlignFrame(af);
-         data = aparser.getValue("colour", true);
-         if (data != null)
-         {
-           data.replaceAll("%20", " ");
-           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
-                   af.getViewport(), af.getViewport().getAlignment(), data);
-           if (cs != null)
-           {
-             System.out.println(
-                     "CMD [-color " + data + "] executed successfully!");
-           }
-           af.changeColour(cs);
-         }
  
-         // Must maintain ability to use the groups flag
-         data = aparser.getValue("groups", true);
-         if (data != null)
-         {
-           af.parseFeaturesFile(data,
-                   AppletFormatAdapter.checkProtocol(data));
-           // System.out.println("Added " + data);
-           System.out.println(
-                   "CMD groups[-" + data + "]  executed successfully!");
-         }
-         data = aparser.getValue("features", true);
-         if (data != null)
-         {
-           af.parseFeaturesFile(data,
-                   AppletFormatAdapter.checkProtocol(data));
-           // System.out.println("Added " + data);
-           System.out.println(
-                   "CMD [-features " + data + "]  executed successfully!");
-         }
-         data = aparser.getValue("annotations", true);
-         if (data != null)
-         {
-           af.loadJalviewDataFile(data, null, null, null);
-           // System.out.println("Added " + data);
-           System.out.println(
-                   "CMD [-annotations " + data + "] executed successfully!");
-         }
-         // set or clear the sortbytree flag.
-         if (aparser.contains("sortbytree"))
+         // JalviewLite interface for JavaScript allows second file open
+         String file2 = aparser.getValue(ArgsParser.OPEN2, true);
+         if (file2 != null)
          {
-           af.getViewport().setSortByTree(true);
-           if (af.getViewport().getSortByTree())
+           protocol = AppletFormatAdapter.checkProtocol(file2);
+           try
            {
-             System.out.println("CMD [-sortbytree] executed successfully!");
-           }
-         }
-         if (aparser.contains("no-annotation"))
-         {
-           af.getViewport().setShowAnnotation(false);
-           if (!af.getViewport().isShowAnnotation())
+             format = new IdentifyFile().identify(file2, protocol);
+           } catch (FileFormatException e1)
            {
-             System.out.println("CMD no-annotation executed successfully!");
+             // TODO ?
            }
-         }
-         if (aparser.contains("nosortbytree"))
-         {
-           af.getViewport().setSortByTree(false);
-           if (!af.getViewport().getSortByTree())
+           AlignFrame af2 = new FileLoader(!headless)
+                   .LoadFileWaitTillLoaded(file2, protocol, format);
+           if (af2 == null)
            {
-             System.out
-                     .println("CMD [-nosortbytree] executed successfully!");
+             System.out.println("error");
            }
-         }
-         data = aparser.getValue("tree", true);
-         if (data != null)
-         {
-           try
+           else
            {
+             AlignViewport.openLinkedAlignmentAs(af,
+                     af.getViewport().getAlignment(),
+                     af2.getViewport().getAlignment(), "",
+                     AlignViewport.SPLIT_FRAME);
              System.out.println(
-                     "CMD [-tree " + data + "] executed successfully!");
-             NewickFile nf = new NewickFile(data,
-                     AppletFormatAdapter.checkProtocol(data));
-             af.getViewport()
-                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
-           } catch (IOException ex)
-           {
-             System.err.println("Couldn't add tree " + data);
-             ex.printStackTrace(System.err);
+                     "CMD [-open2 " + file2 + "] executed successfully!");
            }
          }
-         // TODO - load PDB structure(s) to alignment JAL-629
-         // (associate with identical sequence in alignment, or a specified
-         // sequence)
-         if (groovyscript != null)
+         setCurrentAlignFrame(af);
+         setFrameDependentProperties(aparser, af);
+         
+         if (isJS)
          {
-           // Execute the groovy script after we've done all the rendering stuff
-           // and before any images or figures are generated.
-           System.out.println("Executing script " + groovyscript);
-           executeGroovyScript(groovyscript, af);
-           System.out.println("CMD groovy[" + groovyscript
-                   + "] executed successfully!");
-           groovyscript = null;
+           jsApp.initFromParams(af);
          }
-         String imageName = "unnamed.png";
-         while (aparser.getSize() > 1)
+         else
+         /**
+          * Java only
+          * 
+          * @j2sIgnore
+          */
          {
-           String outputFormat = aparser.nextValue();
-           file = aparser.nextValue();
-           if (outputFormat.equalsIgnoreCase("png"))
-           {
-             af.createPNG(new File(file));
-             imageName = (new File(file)).getName();
-             System.out.println("Creating PNG image: " + file);
-             continue;
-           }
-           else if (outputFormat.equalsIgnoreCase("svg"))
-           {
-             File imageFile = new File(file);
-             imageName = imageFile.getName();
-             af.createSVG(imageFile);
-             System.out.println("Creating SVG image: " + file);
-             continue;
-           }
-           else if (outputFormat.equalsIgnoreCase("html"))
-           {
-             File imageFile = new File(file);
-             imageName = imageFile.getName();
-             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
-             htmlSVG.exportHTML(file);
-             System.out.println("Creating HTML image: " + file);
-             continue;
-           }
-           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
+           if (groovyscript != null)
            {
-             if (file == null)
-             {
-               System.err.println("The output html file must not be null");
-               return;
-             }
-             try
-             {
-               BioJsHTMLOutput.refreshVersionInfo(
-                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
-             } catch (URISyntaxException e)
-             {
-               e.printStackTrace();
-             }
-             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
-             bjs.exportHTML(file);
-             System.out
-                     .println("Creating BioJS MSA Viwer HTML file: " + file);
-             continue;
-           }
-           else if (outputFormat.equalsIgnoreCase("imgMap"))
-           {
-             af.createImageMap(new File(file), imageName);
-             System.out.println("Creating image map: " + file);
-             continue;
-           }
-           else if (outputFormat.equalsIgnoreCase("eps"))
-           {
-             File outputFile = new File(file);
-             System.out.println(
-                     "Creating EPS file: " + outputFile.getAbsolutePath());
-             af.createEPS(outputFile);
-             continue;
-           }
-           af.saveAlignment(file, format);
-           if (af.isSaveAlignmentSuccessful())
-           {
-             System.out.println("Written alignment in " + format
-                     + " format to " + file);
-           }
-           else
-           {
-             System.out.println("Error writing file " + file + " in "
-                     + format + " format!!");
+             // Execute the groovy script after we've done all the rendering
+             // stuff
+             // and before any images or figures are generated.
+             System.out.println("Executing script " + groovyscript);
+             executeGroovyScript(groovyscript, af);
+             System.out.println("CMD groovy[" + groovyscript
+                     + "] executed successfully!");
+             groovyscript = null;
            }
          }
-         while (aparser.getSize() > 0)
-         {
-           System.out.println("Unknown arg: " + aparser.nextValue());
+         if (!isJS || !isStartup) {
+           createOutputFiles(aparser, format);
          }
        }
 +      if (headless)
 +      {
 +        af.getViewport().getCalcManager().shutdown();
 +      }
      }
-     AlignFrame startUpAlframe = null;
-     // We'll only open the default file if the desktop is visible.
-     // And the user
-     // ////////////////////
+     // extract groovy arguments before anything else.
+     // Once all other stuff is done, execute any groovy scripts (in order)
+     if (!isJS && groovyscript != null)
+     {
+       if (Cache.groovyJarsPresent())
+       {
+         System.out.println("Executing script " + groovyscript);
+         executeGroovyScript(groovyscript, af);
+       }
+       else
+       {
+         System.err.println(
+                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
+                         + groovyscript);
+       }
+     }
  
-     if (!Platform.isJS() && !headless && file == null
-             && Cache.getDefault("SHOW_STARTUP_FILE", true))
-     /**
-      * Java only
-      * 
-      * @j2sIgnore
-      */
+     // and finally, turn off batch mode indicator - if the desktop still exists
+     if (desktop != null)
      {
-       file = Cache.getDefault("STARTUP_FILE",
-               Cache.getDefault("www.jalview.org",
-                       "http://www.jalview.org")
-                       + "/examples/exampleFile_2_7.jar");
-       if (file.equals(
-               "http://www.jalview.org/examples/exampleFile_2_3.jar"))
+       if (progress != -1)
        {
-         // hardwire upgrade of the startup file
-         file.replace("_2_3.jar", "_2_7.jar");
-         // and remove the stale setting
-         Cache.removeProperty("STARTUP_FILE");
+         desktop.setProgressBar(null, progress);
        }
+       desktop.setInBatchMode(false);
+     }
+     
+     if (jsApp != null) {
+       jsApp.callInitCallback();
+     }
+   }
+   
+   /**
+    * Set general display parameters irrespective of file loading or headlessness.
+    * 
+    * @param aparser
+    */
+   private void setDisplayParameters(ArgsParser aparser)
+   {
+     if (aparser.contains(ArgsParser.NOMENUBAR))
+     {
+       noMenuBar = true;
+       System.out.println("CMD [nomenu] executed successfully!");
+     }
+     if (aparser.contains(ArgsParser.NOSTATUS))
+     {
+       noStatus = true;
+       System.out.println("CMD [nostatus] executed successfully!");
+     }
+     if (aparser.contains(ArgsParser.NOANNOTATION)
+             || aparser.contains(ArgsParser.NOANNOTATION2))
+     {
+       noAnnotation = true;
+       System.out.println("CMD no-annotation executed successfully!");
+     }
+     if (aparser.contains(ArgsParser.NOCALCULATION))
+     {
+       noCalculation = true;
+       System.out.println("CMD [nocalculation] executed successfully!");
+     }
+   }
+   private void setFrameDependentProperties(ArgsParser aparser,
+           AlignFrame af)
+   {
+     String data = aparser.getValue(ArgsParser.COLOUR, true);
+     if (data != null)
+     {
+       data.replaceAll("%20", " ");
  
-       protocol = DataSourceType.FILE;
+       ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
+               af.getViewport(), af.getViewport().getAlignment(), data);
  
-       if (file.indexOf("http:") > -1)
+       if (cs != null)
        {
-         protocol = DataSourceType.URL;
+         System.out.println(
+                 "CMD [-color " + data + "] executed successfully!");
        }
+       af.changeColour(cs);
+     }
+     // Must maintain ability to use the groups flag
+     data = aparser.getValue(ArgsParser.GROUPS, true);
+     if (data != null)
+     {
+       af.parseFeaturesFile(data,
+               AppletFormatAdapter.checkProtocol(data));
+       // System.out.println("Added " + data);
+       System.out.println(
+               "CMD groups[-" + data + "]  executed successfully!");
+     }
+     data = aparser.getValue(ArgsParser.FEATURES, true);
+     if (data != null)
+     {
+       af.parseFeaturesFile(data,
+               AppletFormatAdapter.checkProtocol(data));
+       // System.out.println("Added " + data);
+       System.out.println(
+               "CMD [-features " + data + "]  executed successfully!");
+     }
+     data = aparser.getValue(ArgsParser.ANNOTATIONS, true);
+     if (data != null)
+     {
+       af.loadJalviewDataFile(data, null, null, null);
+       // System.out.println("Added " + data);
+       System.out.println(
+               "CMD [-annotations " + data + "] executed successfully!");
+     }
+     // JavaScript feature
  
-       if (file.endsWith(".jar"))
+     if (aparser.contains(ArgsParser.SHOWOVERVIEW))
+     {
+       af.overviewMenuItem_actionPerformed(null);
+       System.out.println("CMD [showoverview] executed successfully!");
+     }
+     // set or clear the sortbytree flag.
+     if (aparser.contains(ArgsParser.SORTBYTREE))
+     {
+       af.getViewport().setSortByTree(true);
+       if (af.getViewport().getSortByTree())
        {
-         format = FileFormat.Jalview;
+         System.out.println("CMD [-sortbytree] executed successfully!");
        }
-       else
+     }
+     boolean doUpdateAnnotation = false;
+     /**
+      * we do this earlier in JalviewJS because of a complication with
+      * SHOWOVERVIEW
+      * 
+      * For now, just fixing this in JalviewJS.
+      *
+      * 
+      * @j2sIgnore
+      * 
+      */
+     {
+       if (noAnnotation)
        {
-         try
-         {
-           format = new IdentifyFile().identify(file, protocol);
-         } catch (FileFormatException e)
+         af.getViewport().setShowAnnotation(false);
+         if (!af.getViewport().isShowAnnotation())
          {
-           // TODO what?
+           doUpdateAnnotation = true;
          }
        }
-       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
-               format);
-       // extract groovy arguments before anything else.
      }
  
-     // Once all other stuff is done, execute any groovy scripts (in order)
-     if (groovyscript != null)
+     if (aparser.contains(ArgsParser.NOSORTBYTREE))
      {
-       if (Cache.groovyJarsPresent())
+       af.getViewport().setSortByTree(false);
+       if (!af.getViewport().getSortByTree())
        {
-         System.out.println("Executing script " + groovyscript);
-         executeGroovyScript(groovyscript, startUpAlframe);
+         doUpdateAnnotation = true;
+         System.out
+                 .println("CMD [-nosortbytree] executed successfully!");
        }
-       else
+     }
+     if (doUpdateAnnotation)
+     { // BH 2019.07.24
+       af.setMenusForViewport();
+       af.alignPanel.updateLayout();
+     }
+     data = aparser.getValue(ArgsParser.TREE, true);
+     if (data != null)
+     {
+       try
        {
-         System.err.println(
-                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
-                         + groovyscript);
+         NewickFile nf = new NewickFile(data,
+                 AppletFormatAdapter.checkProtocol(data));
+         af.getViewport()
+                 .setCurrentTree(af.showNewickTree(nf, data).getTree());
+         System.out.println(
+                 "CMD [-tree " + data + "] executed successfully!");
+       } catch (IOException ex)
+       {
+         System.err.println("Couldn't add tree " + data);
+         ex.printStackTrace(System.err);
        }
      }
-     // and finally, turn off batch mode indicator - if the desktop still exists
-     if (desktop != null)
+     // TODO - load PDB structure(s) to alignment JAL-629
+     // (associate with identical sequence in alignment, or a specified
+     // sequence)
+   }
+   /**
+    * Writes an output file for each format (if any) specified in the
+    * command-line arguments. Supported formats are currently
+    * <ul>
+    * <li>png</li>
+    * <li>svg</li>
+    * <li>html</li>
+    * <li>biojsmsa</li>
+    * <li>imgMap</li>
+    * <li>eps</li>
+    * </ul>
+    * A format parameter should be followed by a parameter specifying the output
+    * file name. {@code imgMap} parameters should follow those for the
+    * corresponding alignment image output.
+    * 
+    * @param aparser
+    * @param format
+    */
+   private void createOutputFiles(ArgsParser aparser, FileFormatI format)
+   {
+     AlignFrame af = currentAlignFrame;
+     while (aparser.getSize() >= 2)
      {
-       if (progress != -1)
+       String outputFormat = aparser.nextValue();
+       File imageFile;
+       String fname;
+       switch (outputFormat.toLowerCase())
        {
-         desktop.setProgressBar(null, progress);
+       case "png":
+         imageFile = new File(aparser.nextValue());
+         af.createPNG(imageFile);
+         System.out.println(
+                 "Creating PNG image: " + imageFile.getAbsolutePath());
+         continue;
+       case "svg":
+         imageFile = new File(aparser.nextValue());
+         af.createSVG(imageFile);
+         System.out.println(
+                 "Creating SVG image: " + imageFile.getAbsolutePath());
+         continue;
+       case "eps":
+         imageFile = new File(aparser.nextValue());
+         System.out.println(
+                 "Creating EPS file: " + imageFile.getAbsolutePath());
+         af.createEPS(imageFile);
+         continue;
+       case "biojsmsa":
+         fname = new File(aparser.nextValue()).getAbsolutePath();
+         try
+         {
+           BioJsHTMLOutput.refreshVersionInfo(
+                   BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
+         } catch (URISyntaxException e)
+         {
+           e.printStackTrace();
+         }
+         BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
+         bjs.exportHTML(fname);
+         System.out.println("Creating BioJS MSA Viwer HTML file: " + fname);
+         continue;
+       case "html":
+         fname = new File(aparser.nextValue()).getAbsolutePath();
+         HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
+         htmlSVG.exportHTML(fname);
+         System.out.println("Creating HTML image: " + fname);
+         continue;
+       case "imgmap":
+         imageFile = new File(aparser.nextValue());
+         af.alignPanel.makePNGImageMap(imageFile, "unnamed.png");
+         System.out.println(
+                 "Creating image map: " + imageFile.getAbsolutePath());
+         continue;
+       default:
+         // fall through - try to parse as an alignment data export format
+         FileFormatI outFormat = null;
+         try
+         {
+           outFormat = FileFormats.getInstance().forName(outputFormat);
+         } catch (Exception formatP)
+         {
+         }
+         if (outFormat == null)
+         {
+           System.out.println("Couldn't parse " + outputFormat
+                   + " as a valid Jalview format string.");
+           continue;
+         }
+         if (!outFormat.isWritable())
+         {
+           System.out.println(
+                   "This version of Jalview does not support alignment export as "
+                           + outputFormat);
+           continue;
+         }
+         // record file as it was passed to Jalview so it is recognisable to the CLI
+         // caller
+         String file;
+         fname = new File(file = aparser.nextValue()).getAbsolutePath();
+         // JBPNote - yuck - really wish we did have a bean returned from this which gave
+         // success/fail like before !
+         af.saveAlignment(fname, outFormat);
+         if (!af.isSaveAlignmentSuccessful())
+         {
+           System.out.println("Written alignment in " + outputFormat
+                   + " format to " + file);
+           continue;
+         }
+         else
+         {
+           System.out.println("Error writing file " + file + " in "
+                   + outputFormat + " format!!");
+         }
        }
-       desktop.setInBatchMode(false);
+     }
+     // ??? Should report - 'ignoring' extra args here...
+     while (aparser.getSize() > 0)
+     {
+       System.out.println("Ignoring extra argument: " + aparser.nextValue());
      }
    }
  
      /**
       * start a User Config prompt asking if we can log usage statistics.
       */
-     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
+     PromptUserConfig prompter = new PromptUserConfig(Desktop.getDesktopPane(),
              "USAGESTATS", "Jalview Usage Statistics",
              "Do you want to help make Jalview better by enabling "
                      + "the collection of usage statistics with Google Analytics ?"
     * Locate the given string as a file and pass it to the groovy interpreter.
     * 
     * @param groovyscript
-    *                         the script to execute
+    *          the script to execute
     * @param jalviewContext
-    *                         the Jalview Desktop object passed in to the groovy
-    *                         binding as the 'Jalview' object.
+    *          the Jalview Desktop object passed in to the groovy binding as the
+    *          'Jalview' object.
     */
    private void executeGroovyScript(String groovyscript, AlignFrame af)
    {
    }
  
    /**
-    * Quit method delegates to Desktop.quit - unless running in headless mode when
-    * it just ends the JVM
+    * Quit method delegates to Desktop.quit - unless running in headless mode
+    * when it just ends the JVM
     */
    public void quit()
    {
  
    public static AlignFrame getCurrentAlignFrame()
    {
-     return Jalview.currentAlignFrame;
+     return Jalview.getInstance().currentAlignFrame;
    }
  
    public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
    {
-     Jalview.currentAlignFrame = currentAlignFrame;
+     Jalview.getInstance().currentAlignFrame = currentAlignFrame;
    }
+   
+   public void notifyWorker(AlignCalcWorkerI worker, String status)
+   {
+     // System.out.println("Jalview worker " + worker.getClass().getSimpleName()
+     // + " " + status);
+   }
+   private static boolean isInteractive = true;
+   public static boolean isInteractive()
+   {
+     return isInteractive;
+   }
+   public static void setInteractive(boolean tf)
+   {
+     isInteractive = tf;
+   }
  }
@@@ -292,32 -292,6 +292,32 @@@ public class Alignment implements Align
    }
  
    /**
 +   * Inserts a sequence at a point in the alignment.
 +   * 
 +   * @param i
 +   *          the index of the position the sequence is to be inserted in.
 +   */
 +  @Override
 +  public void insertSequenceAt(int i, SequenceI snew)
 +  {
 +    synchronized (sequences)
 +    {
 +      if (sequences.size() > i)
 +      {
 +        sequences.add(i, snew);
 +        return;
 +
 +      }
 +      else
 +      {
 +        sequences.add(snew);
 +        hiddenSequences.adjustHeightSequenceAdded();
 +      }
 +      return;
 +    }
 +  }
 +
 +  /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
    @Override
    public SequenceI findName(SequenceI startAfter, String token, boolean b)
    {
+     if (token == null)
+       return null;
      int i = 0;
      SequenceI sq = null;
      String sqname = null;
            String calcId, boolean autoCalc, SequenceI seqRef,
            SequenceGroup groupRef)
    {
 -    if (annotations != null)
 +    AlignmentAnnotation annot = annotations == null ? null
 +            : AlignmentAnnotation.findFirstAnnotation(
 +              Arrays.asList(getAlignmentAnnotation()), name, calcId,
 +              autoCalc, seqRef, groupRef);
 +
 +    if (annot == null)
      {
 -      for (AlignmentAnnotation annot : getAlignmentAnnotation())
 +
 +      annot = new AlignmentAnnotation(name, name, new Annotation[1], 0f, 0f,
 +              AlignmentAnnotation.BAR_GRAPH);
 +      annot.hasText = false;
 +      if (calcId != null)
        {
 -        if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
 -                && (calcId == null || annot.getCalcId().equals(calcId))
 -                && annot.sequenceRef == seqRef
 -                && annot.groupRef == groupRef)
 -        {
 -          return annot;
 -        }
 +        annot.setCalcId(calcId);
        }
 +      annot.autoCalculated = autoCalc;
 +      if (seqRef != null)
 +      {
 +        annot.setSequenceRef(seqRef);
 +      }
 +      annot.groupRef = groupRef;
 +      addAnnotation(annot);
      }
 -    AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
 -            new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
 -    annot.hasText = false;
 -    if (calcId != null)
 +    return annot;
 +  }
 +
 +
 +  @Override
 +  public AlignmentAnnotation updateFromOrCopyAnnotation(
 +          AlignmentAnnotation ala)
 +  {
 +    AlignmentAnnotation annot = AlignmentAnnotation.findFirstAnnotation(
 +            Arrays.asList(getAlignmentAnnotation()), ala.label, ala.calcId,
 +            ala.autoCalculated, ala.sequenceRef, ala.groupRef);
 +    if (annot == null)
      {
 -      annot.setCalcId(new String(calcId));
 +      annot = new AlignmentAnnotation(ala);
 +      addAnnotation(annot);
      }
 -    annot.autoCalculated = autoCalc;
 -    if (seqRef != null)
 +    else
      {
 -      annot.setSequenceRef(seqRef);
 +      annot.updateAlignmentAnnotationFrom(ala);
      }
 -    annot.groupRef = groupRef;
 -    addAnnotation(annot);
 -
 +    validateAnnotation(annot);
      return annot;
    }
  
      }
    }
  
 +  @Override
 +  public List<SequenceI> getHmmSequences()
 +  {
 +    List<SequenceI> result = new ArrayList<>();
 +    for (int i = 0; i < sequences.size(); i++)
 +    {
 +      SequenceI seq = sequences.get(i);
 +      if (seq.hasHMMProfile())
 +      {
 +        result.add(seq);
 +      }
 +    }
 +    return result;
 +  }
  }
   */
  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.PropertyChangeListener;
- 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.Collection;
- 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 javax.swing.event.InternalFrameAdapter;
- import javax.swing.event.InternalFrameEvent;
  import jalview.analysis.AlignmentSorter;
  import jalview.analysis.AlignmentUtils;
  import jalview.analysis.CrossRef;
@@@ -116,13 -64,6 +64,13 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.gui.ColourMenuHelper.ColourChangeListener;
  import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 +import jalview.hmmer.HMMAlign;
 +import jalview.hmmer.HMMBuild;
 +import jalview.hmmer.HMMERParamStore;
 +import jalview.hmmer.HMMERPreset;
 +import jalview.hmmer.HMMSearch;
 +import jalview.hmmer.HmmerCommand;
 +import jalview.hmmer.JackHMMER;
  import jalview.io.AlignmentProperties;
  import jalview.io.AnnotationFile;
  import jalview.io.BackupFiles;
@@@ -157,24 -98,65 +105,82 @@@ import jalview.viewmodel.AlignmentViewp
  import jalview.viewmodel.ViewportRanges;
  import jalview.ws.DBRefFetcher;
  import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
 +import jalview.ws.ServiceChangeListener;
 +import jalview.ws.WSDiscovererI;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws1.Discoverer;
  import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.jws2.PreferredServiceRegistry;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.ParamDatastoreI;
 +import jalview.ws.params.WsParamSetI;
  import jalview.ws.seqfetcher.DbSourceProxy;
 +import jalview.ws.slivkaws.SlivkaWSDiscoverer;
 +import java.io.IOException;
 +import java.util.HashSet;
 +import java.util.Set;
 +
 +import javax.swing.JFileChooser;
 +import javax.swing.JOptionPane;
  
+ import java.awt.BorderLayout;
+ import java.awt.Color;
+ import java.awt.Component;
+ import java.awt.Dimension;
+ 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.beans.PropertyChangeListener;
+ 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.Collection;
+ import java.util.Deque;
+ import java.util.Enumeration;
+ import java.util.Hashtable;
+ 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 javax.swing.event.InternalFrameAdapter;
++import javax.swing.event.InternalFrameEvent;
+ import ext.vamsas.ServiceHandle;
  /**
   * DOCUMENT ME!
   * 
   * @version $Revision$
   */
  @SuppressWarnings("serial")
 -public class AlignFrame extends GAlignFrame implements DropTargetListener,
 -        IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
 +public class AlignFrame extends GAlignFrame
 +        implements DropTargetListener, IProgressIndicator,
 +        AlignViewControllerGuiI, ColourChangeListener, ServiceChangeListener
  {
+   public static int frameCount;
    public static final int DEFAULT_WIDTH = 700;
  
    public static final int DEFAULT_HEIGHT = 500;
     */
    String fileName = null;
  
 +  /**
 +   * TODO: remove reference to 'FileObject' in AlignFrame - not correct mapping
 +   */
    File fileObject;
  
+   private int id;
+   private DataSourceType protocol ;
    /**
     * Creates a new AlignFrame object with specific width and height.
     * 
    public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
            int height, String sequenceSetId, String viewId)
    {
+     id = (++frameCount);
      setSize(width, height);
  
      if (al.getDataset() == null)
  
      viewport = new AlignViewport(al, hiddenColumns, sequenceSetId, viewId);
  
-     alignPanel = new AlignmentPanel(this, viewport);
-     addAlignmentPanel(alignPanel, true);
+     // JalviewJS needs to distinguish a new panel from an old one in init()
+     // alignPanel = new AlignmentPanel(this, viewport);
+     // addAlignmentPanel(alignPanel, true);
      init();
    }
  
      {
        viewport.hideSequence(hiddenSeqs);
      }
-     alignPanel = new AlignmentPanel(this, viewport);
-     addAlignmentPanel(alignPanel, true);
+     // alignPanel = new AlignmentPanel(this, viewport);
+     // addAlignmentPanel(alignPanel, true);
      init();
    }
  
    {
      viewport = ap.av;
      alignPanel = ap;
-     addAlignmentPanel(ap, false);
+     // addAlignmentPanel(ap, false);
      init();
    }
  
     * initalise the alignframe from the underlying viewport data and the
     * configurations
     */
    void init()
    {
 -
+     boolean newPanel = (alignPanel == null);
+     viewport.setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
+     if (newPanel)
+     {
+       if (Platform.isJS())
+       {
+         // need to set this up front if NOANNOTATION is
+         // used in conjunction with SHOWOVERVIEW.
+         // I have not determined if this is appropriate for
+         // Jalview/Java, as it means we are setting this flag
+         // for all subsequent AlignFrames. For now, at least,
+         // I am setting it to be JalviewJS-only.
+         boolean showAnnotation = Jalview.getInstance().getShowAnnotation();
+         viewport.setShowAnnotation(showAnnotation);
+       }
+       alignPanel = new AlignmentPanel(this, viewport);
+     }
+     addAlignmentPanel(alignPanel, newPanel);
      // setBackground(Color.white); // BH 2019
  
      if (!Jalview.isHeadlessMode())
      {
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
+       // JalviewJS options
+       statusPanel.setVisible(Jalview.getInstance().getShowStatus());
+       alignFrameMenuBar.setVisible(Jalview.getInstance().getAllowMenuBar());
      }
  
      avc = new jalview.controller.AlignViewController(this, viewport,
        // modifyPID.setEnabled(false);
      }
  
-     String sortby = jalview.bin.Cache.getDefault("SORT_ALIGNMENT",
+     String sortby = jalview.bin.Cache.getDefault(Preferences.SORT_ALIGNMENT,
              "No sort");
  
      if (sortby.equals("Id"))
        sortPairwiseMenuItem_actionPerformed(null);
      }
  
-     this.alignPanel.av
-             .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
+     // BH see above
+     //
+     // this.alignPanel.av
+     // .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
  
      setMenusFromViewport(viewport);
      buildSortByAnnotationScoresMenu();
      });
      buildColourMenu();
  
-     if (Desktop.desktop != null)
+     if (Desktop.getDesktopPane() != null)
      {
        this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
 +      addServiceListeners();
        if (!Platform.isJS())
        {
 -        addServiceListeners();
        }
        setGUINucleotide();
      }
        wrapMenuItem_actionPerformed(null);
      }
  
-     if (jalview.bin.Cache.getDefault("SHOW_OVERVIEW", false))
+     if (jalview.bin.Cache.getDefault(Preferences.SHOW_OVERVIEW, false))
      {
        this.overviewMenuItem_actionPerformed(null);
      }
      }
      addFocusListener(new FocusAdapter()
      {
        @Override
        public void focusGained(FocusEvent e)
        {
     * @param format
     *          format of file
     */
+   @Deprecated
    public void setFileName(String file, FileFormatI format)
    {
      fileName = file;
    }
  
    /**
+    * 
+    * @param fileName
+    * @param file  from SwingJS; may contain bytes -- for reload
+    * @param protocol from SwingJS; may be RELATIVE_URL
+    * @param format
+    */
+   public void setFile(String fileName, File file, DataSourceType protocol, FileFormatI format)
+   {
+     this.fileName = fileName;
+     this.fileObject = file;
+     this.protocol = protocol;
+     setFileFormat(format);
+     reload.setEnabled(true);
+   }
+   /**
     * 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
     */
    void addKeyListener()
    {
      addKeyListener(new KeyAdapter()
      {
        @Override
        public void keyPressed(KeyEvent evt)
        {
          switch (evt.getKeyCode())
          {
  
-         case 27: // escape key
-           deselectAllSequenceMenuItem_actionPerformed(null);
+         case KeyEvent.VK_ESCAPE: // escape key
+                                  // alignPanel.deselectAllSequences();
+           alignPanel.deselectAllSequences();
  
            break;
  
          case KeyEvent.VK_LEFT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
-             viewport.firePropertyChange("alignment", null,
-                     viewport.getAlignment().getSequences());
+             viewport.notifyAlignment();
            }
            break;
  
          case KeyEvent.VK_RIGHT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
-             viewport.firePropertyChange("alignment", null,
-                     viewport.getAlignment().getSequences());
+             viewport.notifyAlignment();
            }
            break;
          }
        {
          ap.av.getAlignment().padGaps();
        }
-       ap.av.updateConservation(ap);
-       ap.av.updateConsensus(ap);
-       ap.av.updateStrucConsensus(ap);
-       ap.av.initInformationWorker(ap);
+       if (Jalview.getInstance().getStartCalculations())
+       {
+         ap.av.updateConservation(ap);
+         ap.av.updateConsensus(ap);
+         ap.av.updateStrucConsensus(ap);
++        ap.av.initInformationWorker(ap);
+       }
      }
    }
  
      return viewport;
    }
  
 +  @Override
 +  public void servicesChanged(WSDiscovererI discoverer,
 +          Collection<? extends ServiceWithParameters> services)
 +  {
 +    buildWebServicesMenu();
 +  }
 +
    /* Set up intrinsic listeners for dynamically generated GUI bits. */
    private void addServiceListeners()
    {
 -    final java.beans.PropertyChangeListener thisListener;
 -    Desktop.getInstance().addJalviewPropertyChangeListener("services",
 -            thisListener = new java.beans.PropertyChangeListener()
 -            {
 -
 -              @Override
 -              public void propertyChange(PropertyChangeEvent evt)
 -              {
 -                // // System.out.println("Discoverer property change.");
 -                // if (evt.getPropertyName().equals("services"))
 -                {
 -                  SwingUtilities.invokeLater(new Runnable()
 -                  {
 -
 -                    @Override
 -                    public void run()
 -                    {
 -                      System.err.println(
 -                              "Rebuild WS Menu for service change");
 -                      BuildWebServiceMenu();
 -                    }
 -
 -                  });
 -                }
 -              }
 -            });
 -    addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
 +    if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
      {
 -
 +      WSDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
 +      discoverer.addServiceChangeListener(this);
 +    }
 +    if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
 +    {
-       WSDiscovererI discoverer = Jws2Discoverer.getDiscoverer();
++      WSDiscovererI discoverer = Jws2Discoverer.getInstance();
 +      discoverer.addServiceChangeListener(this);
 +    }
 +    // legacy event listener for compatibility with jws1
 +    PropertyChangeListener legacyListener = (changeEvent) -> {
 +      buildWebServicesMenu();
 +    };
-     Desktop.instance.addJalviewPropertyChangeListener("services",legacyListener);
++    Desktop.getInstance().addJalviewPropertyChangeListener("services",legacyListener);
 +    
 +    addInternalFrameListener(new InternalFrameAdapter() {
        @Override
 -      public void internalFrameClosed(
 -              javax.swing.event.InternalFrameEvent evt)
 -      {
 -        // System.out.println("deregistering discoverer listener");
 -        Desktop.getInstance().removeJalviewPropertyChangeListener(
 -                "services", thisListener);
 +      public void internalFrameClosed(InternalFrameEvent e) {
 +        System.out.println("deregistering discoverer listener");
 +        SlivkaWSDiscoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
-         Jws2Discoverer.getDiscoverer().removeServiceChangeListener(AlignFrame.this);
-         Desktop.instance.removeJalviewPropertyChangeListener("services", legacyListener);
++        Jws2Discoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
++        Desktop.getInstance().removeJalviewPropertyChangeListener("services", legacyListener);
          closeMenuItem_actionPerformed(true);
        }
      });
 -    // Finally, build the menu once to get current service state
 -    new Thread(new Runnable()
 -    {
 -
 -      @Override
 -      public void run()
 -      {
 -        BuildWebServiceMenu();
 -      }
 -    }).start();
 +    buildWebServicesMenu();
    }
  
    /**
     * Configure menu items that vary according to whether the alignment is
     * nucleotide or protein
     */
    public void setGUINucleotide()
    {
      AlignmentI al = getViewport().getAlignment();
     * operation that affects the data in the current view (selection changed,
     * etc) to update the menus to reflect the new state.
     */
    @Override
    public void setMenusForViewport()
    {
     * @param av
     *          AlignViewport
     */
    public void setMenusFromViewport(AlignViewport av)
    {
      padGapsMenuitem.setSelected(av.isPadGaps());
      scaleLeft.setVisible(av.getWrapAlignment());
      scaleRight.setVisible(av.getWrapAlignment());
      annotationPanelMenuItem.setState(av.isShowAnnotation());
-     /*
-      * Show/hide annotations only enabled if annotation panel is shown
-      */
-     showAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState());
-     hideAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState());
-     showAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
-     hideAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
+     // Show/hide annotations only enabled if annotation panel is shown
+     syncAnnotationMenuItems(av.isShowAnnotation());
      viewBoxesMenuItem.setSelected(av.getShowBoxes());
      viewTextMenuItem.setSelected(av.getShowText());
      showNonconservedMenuItem.setSelected(av.getShowUnconserved());
      showConsensusHistogram.setSelected(av.isShowConsensusHistogram());
      showSequenceLogo.setSelected(av.isShowSequenceLogo());
      normaliseSequenceLogo.setSelected(av.isNormaliseSequenceLogo());
 +    showInformationHistogram.setSelected(av.isShowInformationHistogram());
 +    showHMMSequenceLogo.setSelected(av.isShowHMMSequenceLogo());
 +    normaliseHMMSequenceLogo.setSelected(av.isNormaliseHMMSequenceLogo());
  
      ColourMenuHelper.setColourSelected(colourMenu,
              av.getGlobalColourScheme());
      applyToAllGroups.setState(av.getColourAppliesToAllGroups());
      showNpFeatsMenuitem.setSelected(av.isShowNPFeats());
      showDbRefsMenuitem.setSelected(av.isShowDBRefs());
-     autoCalculate.setSelected(av.autoCalculateConsensus);
+     autoCalculate
+             .setSelected(av.getAutoCalculateConsensusAndConservation());
      sortByTree.setSelected(av.sortByTree);
      listenToViewSelections.setSelected(av.followSelection);
  
     * 
     * @param b
     */
    public void setGroovyEnabled(boolean b)
    {
      runGroovy.setEnabled(b);
     * 
     * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
     */
    @Override
    public void setProgressBar(String message, long id)
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
     * 
     * @return true if any progress bars are still active
     */
    @Override
    public boolean operationInProgress()
    {
     * will cause the status bar to be hidden, with possibly undesirable flicker
     * of the screen layout.
     */
    @Override
    public void setStatus(String text)
    {
    /*
     * Added so Castor Mapping file can obtain Jalview Version
     */
    public String getVersion()
    {
      return jalview.bin.Cache.getProperty("VERSION");
    @Override
    public void addFromFile_actionPerformed(ActionEvent e)
    {
-     Desktop.instance.inputLocalFileMenuItem_actionPerformed(viewport);
+     Desktop.getInstance().inputLocalFileMenuItem_actionPerformed(viewport);
    }
  
    @Override
 +  public void hmmBuild_actionPerformed(boolean withDefaults)
 +  {
 +    if (!alignmentIsSufficient(1))
 +    {
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and optionally show a dialog
 +     * to allow them to be modified
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forBuild(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      if (params.showRunDialog())
 +      {
 +        args = params.getJobParams();
 +      }
 +      else
 +      {
 +        return; // user cancelled
 +      }
 +    }
 +    new Thread(new HMMBuild(this, args)).start();
 +  }
 +
 +  @Override
 +  public void hmmAlign_actionPerformed(boolean withDefaults)
 +  {
 +    if (!(checkForHMM() && alignmentIsSufficient(2)))
 +    {
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and optionally show a dialog
 +     * to allow them to be modified
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forAlign(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      if (params.showRunDialog())
 +      {
 +        args = params.getJobParams();
 +      }
 +      else
 +      {
 +        return; // user cancelled
 +      }
 +    }
 +    new Thread(new HMMAlign(this, args)).start();
 +  }
 +
 +  @Override
 +  public void hmmSearch_actionPerformed(boolean withDefaults)
 +  {
 +    if (!checkForHMM())
 +    {
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and (if requested) show 
 +     * dialog to allow modification
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forSearch(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      if (params.showRunDialog())
 +      {
 +        args = params.getJobParams();
 +      }
 +      else
 +      {
 +        return; // user cancelled
 +      }
 +    }
 +    new Thread(new HMMSearch(this, args)).start();
 +    alignPanel.repaint();
 +  }
 +
 +  @Override
 +  public void jackhmmer_actionPerformed(boolean withDefaults)
 +  {
 +
 +    /*
 +     * get default parameters, and (if requested) show 
 +     * dialog to allow modification
 +     */
 +
 +    ParamDatastoreI store = HMMERParamStore.forJackhmmer(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      if (params.showRunDialog())
 +      {
 +        args = params.getJobParams();
 +      }
 +      else
 +      {
 +        return; // user cancelled
 +      }
 +    }
 +    new Thread(new JackHMMER(this, args)).start();
 +    alignPanel.repaint();
 +
 +  }
 +
 +  /**
 +   * Checks if the alignment has at least one hidden Markov model, if not shows
 +   * a dialog advising to run hmmbuild or load an HMM profile
 +   * 
 +   * @return
 +   */
 +  private boolean checkForHMM()
 +  {
 +    if (viewport.getAlignment().getHmmSequences().isEmpty())
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("warn.no_hmm"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  @Override
 +  protected void filterByEValue_actionPerformed()
 +  {
 +    viewport.filterByEvalue(inputDouble("Enter E-Value Cutoff"));
 +  }
 +
 +  @Override
 +  protected void filterByScore_actionPerformed()
 +  {
 +    viewport.filterByScore(inputDouble("Enter Bit Score Threshold"));
 +  }
 +
 +  private double inputDouble(String message)
 +  {
 +    String str = null;
 +    Double d = null;
 +    while (d == null || d <= 0)
 +    {
 +      str = JOptionPane.showInputDialog(this.alignPanel, message);
 +      try
 +      {
 +        d = Double.valueOf(str);
 +      } catch (NumberFormatException e)
 +      {
 +      }
 +    }
 +    return d;
 +  }
 +
 +  /**
 +   * Checks if the alignment contains the required number of sequences.
 +   * 
 +   * @param required
 +   * @return
 +   */
 +  public boolean alignmentIsSufficient(int required)
 +  {
 +    if (getViewport().getSequenceSelection().length < required)
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("label.not_enough_sequences"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  /**
 +   * Opens a file browser and adds the selected file, if in Fasta, Stockholm or
 +   * Pfam format, to the list held under preference key "HMMSEARCH_DBS" (as a
 +   * comma-separated list)
 +   */
 +  @Override
 +  public void addDatabase_actionPerformed() throws IOException
 +  {
 +    if (Cache.getProperty(Preferences.HMMSEARCH_DBS) == null)
 +    {
 +      Cache.setProperty(Preferences.HMMSEARCH_DBS, "");
 +    }
 +
 +    String path = openFileChooser(false);
 +    if (path != null && new File(path).exists())
 +    {
 +      IdentifyFile identifier = new IdentifyFile();
 +      FileFormatI format = identifier.identify(path, DataSourceType.FILE);
 +      if (format == FileFormat.Fasta || format == FileFormat.Stockholm
 +              || format == FileFormat.Pfam)
 +      {
 +        String currentDbPaths = Cache
 +                .getProperty(Preferences.HMMSEARCH_DBS);
 +        currentDbPaths += Preferences.COMMA + path;
 +        Cache.setProperty(Preferences.HMMSEARCH_DBS, currentDbPaths);
 +      }
 +      else
 +      {
 +        JOptionPane.showMessageDialog(this,
 +                MessageManager.getString("warn.invalid_format"));
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Opens a file chooser, optionally restricted to selecting folders
 +   * (directories) only. Answers the path to the selected file or folder, or
 +   * null if none is chosen.
 +   * 
 +   * @param
 +   * @return
 +   */
 +  protected String openFileChooser(boolean forFolder)
 +  {
 +    // TODO duplicates GPreferences method - relocate to JalviewFileChooser?
 +    String choice = null;
 +    JFileChooser chooser = new JFileChooser();
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
 +    chooser.setDialogTitle(
 +            MessageManager.getString("label.open_local_file"));
 +    chooser.setToolTipText(MessageManager.getString("action.open"));
 +
 +    int value = chooser.showOpenDialog(this);
 +
 +    if (value == JFileChooser.APPROVE_OPTION)
 +    {
 +      choice = chooser.getSelectedFile().getPath();
 +    }
 +    return choice;
 +  }
 +
 +  @Override
    public void reload_actionPerformed(ActionEvent e)
    {
-     if (fileName != null)
+     if (fileName == null && fileObject == null)
+     {
+       return;
+     }
+     // TODO: JAL-1108 - ensure all associated frames are closed regardless of
+     // originating file's format
+     // TODO: work out how to recover feature settings for correct view(s) when
+     // file is reloaded.
+     if (FileFormat.Jalview.equals(currentFileFormat))
      {
-       // TODO: JAL-1108 - ensure all associated frames are closed regardless of
-       // originating file's format
-       // TODO: work out how to recover feature settings for correct view(s) when
-       // file is reloaded.
-       if (FileFormat.Jalview.equals(currentFileFormat))
+       JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
+       for (int i = 0; i < frames.length; i++)
        {
-         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
-         for (int i = 0; i < frames.length; i++)
+         if (frames[i] instanceof AlignFrame && frames[i] != this
+                 && ((AlignFrame) frames[i]).fileName != null
+                 && ((AlignFrame) frames[i]).fileName.equals(fileName))
          {
-           if (frames[i] instanceof AlignFrame && frames[i] != this
-                   && ((AlignFrame) frames[i]).fileName != null
-                   && ((AlignFrame) frames[i]).fileName.equals(fileName))
+           try
+           {
+             frames[i].setSelected(true);
+             Desktop.getInstance().closeAssociatedWindows();
+           } catch (java.beans.PropertyVetoException ex)
            {
-             try
-             {
-               frames[i].setSelected(true);
-               Desktop.instance.closeAssociatedWindows();
-             } catch (java.beans.PropertyVetoException ex)
-             {
-             }
            }
          }
-         Desktop.instance.closeAssociatedWindows();
  
-         FileLoader loader = new FileLoader();
-         DataSourceType protocol = fileName.startsWith("http:")
-                 ? DataSourceType.URL
-                 : DataSourceType.FILE;
-         loader.LoadFile(viewport, fileName, protocol, currentFileFormat);
        }
-       else
-       {
-         Rectangle bounds = this.getBounds();
+       Desktop.getInstance().closeAssociatedWindows();
  
-         FileLoader loader = new FileLoader();
+       FileLoader loader = new FileLoader();
+ //      DataSourceType protocol = fileName.startsWith("http:")
+ //              ? DataSourceType.URL
+ //              : DataSourceType.FILE;
+         loader.LoadFile(viewport, (fileObject == null ? fileName : fileObject), protocol, currentFileFormat);
+     }
+     else
+     {
+       Rectangle bounds = this.getBounds();
  
-         AlignFrame newframe = null;
+       FileLoader loader = new FileLoader();
  
-         if (fileObject == null)
-         {
+       AlignFrame newframe = null;
  
-           DataSourceType protocol = (fileName.startsWith("http:")
-                   ? DataSourceType.URL
-                   : DataSourceType.FILE);
-           newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
-                   currentFileFormat);
-         }
-         else
-         {
-           newframe = loader.LoadFileWaitTillLoaded(fileObject,
-                   DataSourceType.FILE, currentFileFormat);
-         }
+       if (fileObject == null)
+       {
+         newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
+                 currentFileFormat);
+       }
+       else
+       {
+         newframe = loader.LoadFileWaitTillLoaded(fileObject,
+                 DataSourceType.FILE, currentFileFormat);
+       }
  
-         newframe.setBounds(bounds);
-         if (featureSettings != null && featureSettings.isShowing())
+       newframe.setBounds(bounds);
+       if (featureSettings != null && featureSettings.isShowing())
+       {
+         final Rectangle fspos = featureSettings.frame.getBounds();
+         // TODO: need a 'show feature settings' function that takes bounds -
+         // need to refactor Desktop.addFrame
+         newframe.featureSettings_actionPerformed(null);
+         final FeatureSettings nfs = newframe.featureSettings;
+         SwingUtilities.invokeLater(new Runnable()
          {
-           final Rectangle fspos = featureSettings.frame.getBounds();
-           // TODO: need a 'show feature settings' function that takes bounds -
-           // need to refactor Desktop.addFrame
-           newframe.featureSettings_actionPerformed(null);
-           final FeatureSettings nfs = newframe.featureSettings;
-           SwingUtilities.invokeLater(new Runnable()
+           @Override
+           public void run()
            {
-             @Override
-             public void run()
-             {
-               nfs.frame.setBounds(fspos);
-             }
-           });
-           this.featureSettings.close();
-           this.featureSettings = null;
-         }
-         this.closeMenuItem_actionPerformed(true);
+             nfs.frame.setBounds(fspos);
+           }
+         });
+         this.featureSettings.close();
+         this.featureSettings = null;
        }
+       this.closeMenuItem_actionPerformed(true);
      }
    }
  
    @Override
    public void addFromText_actionPerformed(ActionEvent e)
    {
-     Desktop.instance
+     Desktop.getInstance()
              .inputTextboxMenuItem_actionPerformed(viewport.getAlignPanel());
    }
  
    @Override
    public void addFromURL_actionPerformed(ActionEvent e)
    {
-     Desktop.instance.inputURLMenuItem_actionPerformed(viewport);
+     Desktop.getInstance().inputURLMenuItem_actionPerformed(viewport);
    }
  
    @Override
     * 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()
    {
      // todo is this (2005) test now obsolete - value is never null?
      while (currentFileFormat == null)
      {
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("label.select_file_format_before_saving"),
                MessageManager.getString("label.file_format_not_specified"),
     *
     * @return true if last call to saveAlignment(file, format) was successful.
     */
    public boolean isSaveAlignmentSuccessful()
    {
  
     * @param file
     * @param format
     */
    public void saveAlignment(String file, FileFormatI format)
    {
      lastSaveSuccessful = true;
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
      Runnable cancelAction = new Runnable()
      {
        @Override
        public void run()
        {
      };
      Runnable outputAction = new Runnable()
      {
        @Override
        public void run()
        {
     * 
     * @param fileFormatName
     */
    @Override
    protected void outputText_actionPerformed(String fileFormatName)
    {
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
      Runnable outputAction = new Runnable()
      {
        @Override
        public void run()
        {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void htmlMenuItem_actionPerformed(ActionEvent e)
    {
      bjs.exportHTML(null);
    }
  
+   // ??
    public void createImageMap(File file, String image)
    {
      alignPanel.makePNGImageMap(file, image);
     * 
     * @param f
     */
    @Override
    public void createPNG(File f)
    {
     * 
     * @param f
     */
    @Override
    public void createEPS(File f)
    {
     * 
     * @param f
     */
    @Override
    public void createSVG(File f)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void printMenuItem_actionPerformed(ActionEvent e)
    {
  
    @Override
    public void associatedData_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      final JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setToolTipText(tooltip);
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
     * 
     * @param closeAllTabs
     */
    @Override
    public void closeMenuItem_actionPerformed(boolean closeAllTabs)
    {
     * 
     * @param panelToClose
     */
    public void closeView(AlignmentPanel panelToClose)
    {
      int index = tabbedPane.getSelectedIndex();
    /**
     * DOCUMENT ME!
     */
    void updateEditMenuBar()
    {
  
     * 
     * @return alignment objects for all views
     */
    AlignmentI[] getViewAlignments()
    {
      if (alignPanels != null)
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void undoMenuItem_actionPerformed(ActionEvent e)
    {
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
-       originalSource.firePropertyChange("alignment", null,
-               originalSource.getAlignment().getSequences());
+       originalSource.notifyAlignment();
      }
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void redoMenuItem_actionPerformed(ActionEvent e)
    {
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
-       originalSource.firePropertyChange("alignment", null,
-               originalSource.getAlignment().getSequences());
+       originalSource.notifyAlignment();
      }
    }
  
     * @param up
     *          DOCUMENT ME!
     */
    public void moveSelectedSequences(boolean up)
    {
      SequenceGroup sg = viewport.getSelectionGroup();
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void copy_actionPerformed()
    {
  
      StringSelection ss = new StringSelection(output);
  
+     Desktop d = Desktop.getInstance();
      try
      {
-       jalview.gui.Desktop.internalCopy = true;
+       d.internalCopy = true;
        // Its really worth setting the clipboard contents
        // to empty before setting the large StringSelection!!
        Toolkit.getDefaultToolkit().getSystemClipboard()
                .setContents(new StringSelection(""), null);
  
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,
-               Desktop.instance);
+               Desktop.getInstance());
      } catch (OutOfMemoryError er)
      {
        new OOMWarning("copying region", er);
                hiddenCutoff, hiddenOffset);
      }
  
-     Desktop.jalviewClipboard = new Object[] { seqs,
+     d.jalviewClipboard = new Object[] { seqs,
          viewport.getAlignment().getDataset(), hiddenColumns };
      setStatus(MessageManager.formatMessage(
              "label.copied_sequences_to_clipboard", new Object[]
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteNew_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(true);
    }
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteThis_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(false);
    }
     * 
     * @param newAlignment
     *          true to paste to a new alignment, otherwise add to this.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
 -
 -  void paste(boolean newAlignment)
 +  void paste(boolean newAlignment) throws IOException, InterruptedException
    {
      boolean externalPaste = true;
      try
        boolean annotationAdded = false;
        AlignmentI alignment = null;
  
-       if (Desktop.jalviewClipboard != null)
+       Desktop d = Desktop.getInstance();
+       if (d.jalviewClipboard != null)
        {
          // The clipboard was filled from within Jalview, we must use the
          // sequences
          // And dataset from the copied alignment
-         SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
+         SequenceI[] newseq = (SequenceI[]) d.jalviewClipboard[0];
          // be doubly sure that we create *new* sequence objects.
          sequences = new SequenceI[newseq.length];
          for (int i = 0; i < newseq.length; i++)
        if (newAlignment)
        {
  
-         if (Desktop.jalviewClipboard != null)
+         if (d.jalviewClipboard != null)
          {
            // dataset is inherited
-           alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
+           alignment.setDataset((Alignment) d.jalviewClipboard[1]);
          }
          else
          {
          alignment = viewport.getAlignment();
          alwidth = alignment.getWidth() + 1;
          // decide if we need to import sequences from an existing dataset
-         boolean importDs = Desktop.jalviewClipboard != null
-                 && Desktop.jalviewClipboard[1] != alignment.getDataset();
+         boolean importDs = d.jalviewClipboard != null
+                 && d.jalviewClipboard[1] != alignment.getDataset();
          // importDs==true instructs us to copy over new dataset sequences from
          // an existing alignment
          Vector<SequenceI> newDs = (importDs) ? new Vector<>() : null; // used to
            }
            buildSortByAnnotationScoresMenu();
          }
-         viewport.firePropertyChange("alignment", null,
-                 alignment.getSequences());
+         viewport.notifyAlignment();
          if (alignPanels != null)
          {
            for (AlignmentPanel ap : alignPanels)
                  DEFAULT_HEIGHT);
          String newtitle = new String("Copied sequences");
  
-         if (Desktop.jalviewClipboard != null
-                 && Desktop.jalviewClipboard[2] != null)
+         if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
          {
-           HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+           HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
            af.viewport.setHiddenColumns(hc);
          }
  
        System.out.println("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      }
 -
    }
  
    @Override
        AlignFrame af = new AlignFrame(alignment, DEFAULT_WIDTH,
                DEFAULT_HEIGHT);
        String newtitle = new String("Flanking alignment");
-       if (Desktop.jalviewClipboard != null
-               && Desktop.jalviewClipboard[2] != null)
+       Desktop d = Desktop.getInstance();
+       if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
        {
-         HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+         HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
          af.viewport.setHiddenColumns(hc);
        }
  
    /**
     * Action Cut (delete and copy) the selected region
     */
    @Override
    protected void cut_actionPerformed()
    {
    /**
     * Performs menu option to Delete the currently selected region
     */
    @Override
    protected void delete_actionPerformed()
    {
  
      Runnable okAction = new Runnable()
      {
        @Override
        public void run()
        {
          viewport.sendSelection();
          viewport.getAlignment().deleteGroup(sg);
  
-         viewport.firePropertyChange("alignment", null,
-                 viewport.getAlignment().getSequences());
+         viewport.notifyAlignment();
          if (viewport.getAlignment().getHeight() < 1)
          {
            try
            } catch (Exception ex)
            {
            }
+         } else {
+           updateAll(null);
          }
        }
      };
              + 1) == viewport.getAlignment().getWidth()) ? true : false;
      if (wholeHeight && wholeWidth)
      {
-       JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
+       JvOptionPane dialog = JvOptionPane
+               .newOptionDialog(Desktop.getDesktopPane());
        dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
        Object[] options = new Object[] {
            MessageManager.getString("action.ok"),
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void deleteGroups_actionPerformed(ActionEvent e)
    {
      if (avc.deleteGroups())
      {
-       PaintRefresher.Refresh(this, viewport.getSequenceSetId());
-       alignPanel.updateAnnotation();
+       updateAll(viewport.getSequenceSetId());
+     }
+   }
+   private void updateAll(String id)
+   {
+     if (id == null)
+     {
+       // this will force a non-fast repaint of both the IdPanel and SeqPanel
+       alignPanel.getIdPanel().getIdCanvas().setNoFastPaint();
+       alignPanel.getSeqPanel().seqCanvas.setNoFastPaint();
+       alignPanel.repaint();
+     }
+     else
+     {
+       // original version
+       PaintRefresher.Refresh(this, id);
        alignPanel.paintAlignment(true, true);
      }
+     alignPanel.updateAnnotation();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void selectAllSequenceMenuItem_actionPerformed(ActionEvent e)
    {
-     SequenceGroup sg = new SequenceGroup(
-             viewport.getAlignment().getSequences());
-     sg.setEndRes(viewport.getAlignment().getWidth() - 1);
-     viewport.setSelectionGroup(sg);
-     viewport.isSelectionGroupChanged(true);
-     viewport.sendSelection();
-     // JAL-2034 - should delegate to
-     // alignPanel to decide if overview needs
-     // updating.
-     alignPanel.paintAlignment(false, false);
-     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
+     alignPanel.selectAllSequences();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void deselectAllSequenceMenuItem_actionPerformed(ActionEvent e)
    {
-     if (viewport.cursorMode)
-     {
-       alignPanel.getSeqPanel().keyboardNo1 = null;
-       alignPanel.getSeqPanel().keyboardNo2 = null;
-     }
-     viewport.setSelectionGroup(null);
-     viewport.getColumnSelection().clear();
-     viewport.setSelectionGroup(null);
-     alignPanel.getIdPanel().getIdCanvas().searchResults = null;
-     // JAL-2034 - should delegate to
-     // alignPanel to decide if overview needs
-     // updating.
-     alignPanel.paintAlignment(false, false);
-     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
-     viewport.sendSelection();
+     alignPanel.deselectAllSequences();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void invertSequenceMenuItem_actionPerformed(ActionEvent e)
    {
  
      if (sg == null)
      {
-       selectAllSequenceMenuItem_actionPerformed(null);
+       alignPanel.selectAllSequences();
  
        return;
      }
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void remove2LeftMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void remove2RightMenuItem_actionPerformed(ActionEvent e)
    {
                  column, viewport.getAlignment());
        }
  
-       setStatus(MessageManager
-               .formatMessage("label.removed_columns", new String[]
+       setStatus(MessageManager.formatMessage("label.removed_columns",
+               new String[]
                { Integer.valueOf(trimRegion.getSize()).toString() }));
  
        addHistoryItem(trimRegion);
          }
        }
  
-       viewport.firePropertyChange("alignment", null,
-               viewport.getAlignment().getSequences());
+       viewport.notifyAlignment();
      }
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeGappedColumnMenuItem_actionPerformed(ActionEvent e)
    {
  
      addHistoryItem(removeGapCols);
  
-     setStatus(MessageManager
-             .formatMessage("label.removed_empty_columns", new Object[]
+     setStatus(MessageManager.formatMessage("label.removed_empty_columns",
+             new Object[]
              { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
      // This is to maintain viewport position on first residue
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
      ranges.setStartRes(seq.findIndex(startRes) - 1);
-     viewport.firePropertyChange("alignment", null,
-             viewport.getAlignment().getSequences());
+     viewport.notifyAlignment();
  
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeAllGapsMenuItem_actionPerformed(ActionEvent e)
    {
              viewport.getAlignment()));
  
      viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
-     viewport.firePropertyChange("alignment", null,
-             viewport.getAlignment().getSequences());
+     viewport.notifyAlignment();
  
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void padGapsMenuitem_actionPerformed(ActionEvent e)
    {
      viewport.setPadGaps(padGapsMenuitem.isSelected());
-     viewport.firePropertyChange("alignment", null,
-             viewport.getAlignment().getSequences());
+     viewport.notifyAlignment();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void findMenuItem_actionPerformed(ActionEvent e)
    {
    /**
     * Create a new view of the current alignment.
     */
    @Override
    public void newView_actionPerformed(ActionEvent e)
    {
     *          if true then duplicate all annnotation, groups and settings
     * @return new alignment panel, already displayed.
     */
    public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
    {
      /*
  
      if (viewport.getViewName() == null)
      {
-       viewport.setViewName(MessageManager
-               .getString("label.view_name_original"));
+       viewport.setViewName(
+               MessageManager.getString("label.view_name_original"));
      }
  
      /*
     * @param viewTitle
     * @return
     */
    protected String getNewViewName(String viewTitle)
    {
      int index = Desktop.getViewCount(viewport.getSequenceSetId());
     * @param comps
     * @return
     */
    protected List<String> getExistingViewNames(List<Component> comps)
    {
      List<String> existingNames = new ArrayList<>();
    /**
     * Explode tabbed views into separate windows.
     */
    @Override
    public void expandViews_actionPerformed(ActionEvent e)
    {
    /**
     * Gather views in separate windows back into a tabbed presentation.
     */
    @Override
    public void gatherViews_actionPerformed(ActionEvent e)
    {
-     Desktop.instance.gatherViews(this);
+     Desktop.getInstance().gatherViews(this);
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void font_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void seqLimit_actionPerformed(ActionEvent e)
    {
     * 
     * @see jalview.jbgui.GAlignFrame#followHighlight_actionPerformed()
     */
    @Override
    protected void followHighlight_actionPerformed()
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void colourTextMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void wrapMenuItem_actionPerformed(ActionEvent e)
    {
     * @param toggleSeqs
     * @param toggleCols
     */
    protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
    {
  
     * jalview.jbgui.GAlignFrame#hideAllButSelection_actionPerformed(java.awt.
     * event.ActionEvent)
     */
    @Override
    public void hideAllButSelection_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#hideAllSelection_actionPerformed(java.awt.event
     * .ActionEvent)
     */
    @Override
    public void hideAllSelection_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showAllhidden_actionPerformed(java.awt.event.
     * ActionEvent)
     */
    @Override
    public void showAllhidden_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleAbove_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleLeft_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleRight_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void viewTextMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
    {
     * @param evt
     *          DOCUMENT ME!
     */
    @Override
    public void showSeqFeatures_actionPerformed(ActionEvent evt)
    {
     * 
     * @param e
     */
    @Override
    public void annotationPanelMenuItem_actionPerformed(ActionEvent e)
    {
      final boolean setVisible = annotationPanelMenuItem.isSelected();
      viewport.setShowAnnotation(setVisible);
-     this.showAllSeqAnnotations.setEnabled(setVisible);
-     this.hideAllSeqAnnotations.setEnabled(setVisible);
-     this.showAllAlAnnotations.setEnabled(setVisible);
-     this.hideAllAlAnnotations.setEnabled(setVisible);
+     syncAnnotationMenuItems(setVisible);
      alignPanel.updateLayout();
+     repaint();
+     SwingUtilities.invokeLater(new Runnable() {
+       @Override
+       public void run()
+       {
+         alignPanel.updateScrollBarsFromRanges();
+       }
+       
+     });
+   }
+   private void syncAnnotationMenuItems(boolean setVisible)
+   {
+     showAllSeqAnnotations.setEnabled(setVisible);
+     hideAllSeqAnnotations.setEnabled(setVisible);
+     showAllAlAnnotations.setEnabled(setVisible);
+     hideAllAlAnnotations.setEnabled(setVisible);
    }
  
    @Override
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void overviewMenuItem_actionPerformed(ActionEvent e)
    {
      }
  
      JInternalFrame frame = new JInternalFrame();
-     final OverviewPanel overview = new OverviewPanel(alignPanel);
+     // BH 2019.07.26 we allow for an embedded
+     // undecorated overview with defined size
+     frame.setName(Platform.getAppID("overview"));
+     //
+     Dimension dim = Platform.getDimIfEmbedded(frame, -1, -1);
+     if (dim != null && dim.width == 0)
+     {
+       dim = null; // hidden, not embedded
+     }
+     OverviewPanel overview = new OverviewPanel(alignPanel, dim);
      frame.setContentPane(overview);
+     if (dim == null)
+     {
+       dim = new Dimension();
+       // was frame.getSize(), but that is 0,0 at this point;
+     }
+     else
+     {
+       // we are imbedding, and so we have an undecorated frame
+       // and we can set the the frame dimensions accordingly.
+     }
+     // allowing for unresizable option using, style="resize:none"
+     boolean resizable = (Platform.getEmbeddedAttribute(frame,
+             "resize") != "none");
      Desktop.addInternalFrame(frame, MessageManager
              .formatMessage("label.overview_params", new Object[]
-             { this.getTitle() }), true, frame.getWidth(), frame.getHeight(),
-             true, true);
+             { this.getTitle() }), Desktop.FRAME_MAKE_VISIBLE, dim.width,
+             dim.height, resizable, Desktop.FRAME_ALLOW_ANY_SIZE);
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      frame.addInternalFrameListener(
              new javax.swing.event.InternalFrameAdapter()
              {
                @Override
                public void internalFrameClosed(
                        javax.swing.event.InternalFrameEvent evt)
     * CovariationColourScheme(viewport.getAlignment().getAlignmentAnnotation
     * ()[0])); }
     */
    @Override
    public void annotationColour_actionPerformed()
    {
     * 
     * @param selected
     */
    @Override
    public void applyToAllGroups_actionPerformed(boolean selected)
    {
     * @param name
     *          the name (not the menu item label!) of the colour scheme
     */
    @Override
    public void changeColour_actionPerformed(String name)
    {
     * 
     * @param cs
     */
    @Override
    public void changeColour(ColourSchemeI cs)
    {
    /**
     * Show the PID threshold slider panel
     */
    @Override
    protected void modifyPID_actionPerformed()
    {
    /**
     * Show the Conservation slider panel
     */
    @Override
    protected void modifyConservation_actionPerformed()
    {
    /**
     * Action on selecting or deselecting (Colour) By Conservation
     */
    @Override
    public void conservationMenuItem_actionPerformed(boolean selected)
    {
    /**
     * Action on selecting or deselecting (Colour) Above PID Threshold
     */
    @Override
    public void abovePIDThreshold_actionPerformed(boolean selected)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortPairwiseMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortIDMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortLengthMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortGroupMenuItem_actionPerformed(ActionEvent e)
    {
      alignPanel.paintAlignment(true, false);
    }
  
 +  @Override
 +  public void sortEValueMenuItem_actionPerformed(ActionEvent e)
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByEValue(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
 +  @Override
 +  public void sortBitScoreMenuItem_actionPerformed(ActionEvent e)
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByBitScore(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
    /**
     * DOCUMENT ME!
     * 
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeRedundancyMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void pairwiseAlignmentMenuItem_actionPerformed(ActionEvent e)
    {
    @Override
    public void autoCalculate_actionPerformed(ActionEvent e)
    {
-     viewport.autoCalculateConsensus = autoCalculate.isSelected();
-     if (viewport.autoCalculateConsensus)
+     viewport.setAutoCalculateConsensusAndConservation(
+             autoCalculate.isSelected());
+     if (viewport.getAutoCalculateConsensusAndConservation())
+     // ??
+     // viewport.autoCalculateConsensus = autoCalculate.isSelected();
+     // if (viewport.autoCalculateConsensus)
      {
-       viewport.firePropertyChange("alignment", null,
-               viewport.getAlignment().getSequences());
+       viewport.notifyAlignment();
      }
    }
  
     * @param options
     *          parameters for the distance or similarity calculation
     */
    void newTreePanel(String type, String modelName,
            SimilarityParamsI options)
    {
        {
          if (_s.getLength() < sg.getEndRes())
          {
-           JvOptionPane.showMessageDialog(Desktop.desktop,
+           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                    MessageManager.getString(
                            "label.selected_region_to_tree_may_only_contain_residues_or_gaps"),
                    MessageManager.getString(
  
      frameTitle += this.title;
  
-     Desktop.addInternalFrame(tp, frameTitle, 600, 500);
+     Dimension dim = Platform.getDimIfEmbedded(tp, 600, 500);
+     Desktop.addInternalFrame(tp, frameTitle, dim.width, dim.height);
    }
  
    /**
     * @param order
     *          DOCUMENT ME!
     */
    public void addSortByOrderMenuItem(String title,
            final AlignmentOrder order)
    {
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
     *          the label used to retrieve scores for each sequence on the
     *          alignment
     */
    public void addSortByAnnotScoreMenuItem(JMenu sort,
            final String scoreLabel)
    {
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
     * rebuilding in subsequence calls.
     * 
     */
    @Override
    public void buildSortByAnnotationScoresMenu()
    {
      }
  
      if (viewport.getAlignment().getAlignmentAnnotation()
 -            .hashCode() != _annotationScoreVectorHash)
 +            .hashCode() == _annotationScoreVectorHash)
 +    {
 +      return;
 +    }
 +
 +    sortByAnnotScore.removeAll();
 +    Set<String> scoreSorts = new HashSet<>();
 +    for (SequenceI sqa : viewport.getAlignment().getSequences())
      {
 -      sortByAnnotScore.removeAll();
 -      // almost certainly a quicker way to do this - but we keep it simple
 -      Hashtable<String, String> scoreSorts = new Hashtable<>();
 -      AlignmentAnnotation aann[];
 -      for (SequenceI sqa : viewport.getAlignment().getSequences())
 +      AlignmentAnnotation[] anns = sqa.getAnnotation();
 +      for (int i = 0; anns != null && i < anns.length; i++)
        {
 -        aann = sqa.getAnnotation();
 -        for (int i = 0; aann != null && i < aann.length; i++)
 +        AlignmentAnnotation aa = anns[i];
 +        if (aa != null && aa.hasScore() && aa.sequenceRef != null)
          {
 -          if (aann[i].hasScore() && aann[i].sequenceRef != null)
 -          {
 -            scoreSorts.put(aann[i].label, aann[i].label);
 -          }
 +          scoreSorts.add(aa.label);
          }
        }
 -      Enumeration<String> labels = scoreSorts.keys();
 -      while (labels.hasMoreElements())
 -      {
 -        addSortByAnnotScoreMenuItem(sortByAnnotScore, labels.nextElement());
 -      }
 -      sortByAnnotScore.setVisible(scoreSorts.size() > 0);
 -      scoreSorts.clear();
 -
 -      _annotationScoreVectorHash = viewport.getAlignment()
 -              .getAlignmentAnnotation().hashCode();
      }
 +    for (String label : scoreSorts)
 +    {
 +      addSortByAnnotScoreMenuItem(sortByAnnotScore, label);
 +    }
 +    sortByAnnotScore.setVisible(!scoreSorts.isEmpty());
 +
 +    _annotationScoreVectorHash = viewport.getAlignment()
 +            .getAlignmentAnnotation().hashCode();
    }
  
    /**
+    * Enable (or, if desired, make visible) the By Tree 
+    * submenu only if it has at least one element (or will have).
+    * 
+    */
+   @Override
+   protected void enableSortMenuOptions()
+   {
+     List<TreePanel> treePanels = getTreePanels();
+     sortByTreeMenu.setEnabled(!treePanels.isEmpty());
+   }
+   
+   /**
     * Maintain the Order by->Displayed Tree menu. Creates a new menu item for a
     * TreePanel with an appropriate <code>jalview.analysis.AlignmentSorter</code>
     * call. Listeners are added to remove the menu item when the treePanel is
     * closed, and adjust the tree leaf to sequence mapping when the alignment is
     * modified.
     */
    @Override
    public void buildTreeSortMenu()
    {
      sortByTreeMenu.removeAll();
  
-     List<Component> comps = PaintRefresher.components
-             .get(viewport.getSequenceSetId());
-     List<TreePanel> treePanels = new ArrayList<>();
-     for (Component comp : comps)
-     {
-       if (comp instanceof TreePanel)
-       {
-         treePanels.add((TreePanel) comp);
-       }
-     }
-     if (treePanels.size() < 1)
-     {
-       sortByTreeMenu.setVisible(false);
-       return;
-     }
-     sortByTreeMenu.setVisible(true);
+     List<TreePanel> treePanels = getTreePanels();
  
      for (final TreePanel tp : treePanels)
      {
        final JMenuItem item = new JMenuItem(tp.getTitle());
        item.addActionListener(new java.awt.event.ActionListener()
        {
          @Override
          public void actionPerformed(ActionEvent e)
          {
      }
    }
  
+   private List<TreePanel> getTreePanels()
+   {
+     List<Component> comps = PaintRefresher.components
+             .get(viewport.getSequenceSetId());
+     List<TreePanel> treePanels = new ArrayList<>();
+     for (Component comp : comps)
+     {
+       if (comp instanceof TreePanel)
+       {
+         treePanels.add((TreePanel) comp);
+       }
+     }
+     return treePanels;
+   }
    public boolean sortBy(AlignmentOrder alorder, String undoname)
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
     * be submitted for multiple alignment.
     * 
     */
    public jalview.datamodel.AlignmentView gatherSequencesForAlignment()
    {
      // Now, check we have enough sequences
     * region or the whole alignment. (where the first sequence in the set is the
     * one that the prediction will be for).
     */
    public AlignmentView gatherSeqOrMsaForSecStrPrediction()
    {
      AlignmentView seqs = null;
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void loadTreeMenuItem_actionPerformed(ActionEvent e)
    {
  
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
            viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
          } catch (Exception ex)
          {
-           JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
+           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
+                   ex.getMessage(),
                    MessageManager
                            .getString("label.problem_reading_tree_file"),
                    JvOptionPane.WARNING_MESSAGE);
          }
          if (fin != null && fin.hasWarningMessage())
          {
-           JvOptionPane.showMessageDialog(Desktop.desktop,
+           JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                    fin.getWarningMessage(),
                    MessageManager.getString(
                            "label.possible_problem_with_tree_file"),
     *          position
     * @return TreePanel handle
     */
    public TreePanel showNewickTree(NewickFile nf, String treeTitle,
            AlignmentView input, int w, int h, int x, int y)
    {
        if (nf.getTree() != null)
        {
          tp = new TreePanel(alignPanel, nf, treeTitle, input);
-         tp.setSize(w, h);
+         Dimension dim = Platform.getDimIfEmbedded(tp, -1, -1);
+         if (dim == null)
+         {
+           dim = new Dimension(w, h);
+         }
+         else
+         {
+           // no offset, either
+           x = 0;
+         }
+         tp.setSize(dim.width, dim.height);
  
          if (x > 0 && y > 0)
          {
            tp.setLocation(x, y);
          }
  
-         Desktop.addInternalFrame(tp, treeTitle, w, h);
+         Desktop.addInternalFrame(tp, treeTitle, dim.width, dim.height);
        }
      } catch (Exception ex)
      {
      return tp;
    }
  
 -  private boolean buildingMenu = false;
 -
    /**
 -   * Generates menu items and listener event actions for web service clients
 -   * 
 +   * Schedule the web services menu rebuild to the event dispatch thread.
     */
 -
 -  public void BuildWebServiceMenu()
 +  public void buildWebServicesMenu()
    {
 -    while (buildingMenu)
 -    {
 -      try
 +    SwingUtilities.invokeLater(() -> {
 +      Cache.log.info("Rebuiling WS menu");
 +      webService.removeAll();
 +      if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
        {
 -        System.err.println("Waiting for building menu to finish.");
 -        Thread.sleep(10);
 -      } catch (Exception e)
 +        Cache.log.info("Building web service menu for slivka");
 +        SlivkaWSDiscoverer discoverer = SlivkaWSDiscoverer.getInstance();
 +        JMenu submenu = new JMenu("Slivka");
 +        buildWebServicesMenu(discoverer, submenu);
 +        webService.add(submenu);
 +      }
 +      if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
        {
-         WSDiscovererI jws2servs = Jws2Discoverer.getDiscoverer();
++        WSDiscovererI jws2servs = Jws2Discoverer.getInstance();
 +        JMenu submenu = new JMenu("JABAWS");
 +        buildLegacyWebServicesMenu(submenu);
 +        buildWebServicesMenu(jws2servs, submenu);
 +        webService.add(submenu);
        }
 -    }
 -    final AlignFrame me = this;
 -    buildingMenu = true;
 -    new Thread(new Runnable()
 -    {
 +    });
 +  }
  
 -      @Override
 -      public void run()
 +  private void buildLegacyWebServicesMenu(JMenu menu)
 +  {
 +    JMenu secstrmenu = new JMenu("Secondary Structure Prediction");
-     if (Discoverer.services != null && Discoverer.services.size() > 0) 
++    if (Discoverer.getServices() != null && Discoverer.getServices().size() > 0) 
 +    {
-       var secstrpred = Discoverer.services.get("SecStrPred");
++      var secstrpred = Discoverer.getServices().get("SecStrPred");
 +      if (secstrpred != null) 
        {
 -        final List<JMenuItem> legacyItems = new ArrayList<>();
 -        try
 -        {
 -          // System.err.println("Building ws menu again "
 -          // + Thread.currentThread());
 -          // TODO: add support for context dependent disabling of services based
 -          // on
 -          // alignment and current selection
 -          // TODO: add additional serviceHandle parameter to specify abstract
 -          // handler
 -          // class independently of AbstractName
 -          // TODO: add in rediscovery GUI function to restart discoverer
 -          // TODO: group services by location as well as function and/or
 -          // introduce
 -          // object broker mechanism.
 -          final Vector<JMenu> wsmenu = new Vector<>();
 -          final IProgressIndicator af = me;
 -
 -          /*
 -           * do not i18n these strings - they are hard-coded in class
 -           * compbio.data.msa.Category, Jws2Discoverer.isRecalculable() and
 -           * SequenceAnnotationWSClient.initSequenceAnnotationWSClient()
 -           */
 -          final JMenu msawsmenu = new JMenu("Alignment");
 -          final JMenu secstrmenu = new JMenu(
 -                  "Secondary Structure Prediction");
 -          final JMenu seqsrchmenu = new JMenu("Sequence Database Search");
 -          final JMenu analymenu = new JMenu("Analysis");
 -          final JMenu dismenu = new JMenu("Protein Disorder");
 -          // JAL-940 - only show secondary structure prediction services from
 -          // the legacy server
 -          Hashtable<String, Vector<ServiceHandle>> ds = Discoverer
 -                  .getInstance().getServices();
 -          if (// Cache.getDefault("SHOW_JWS1_SERVICES", true)
 -              // &&
 -          ds != null && (ds.size() > 0))
 -          {
 -            // TODO: refactor to allow list of AbstractName/Handler bindings to
 -            // be
 -            // stored or retrieved from elsewhere
 -            // No MSAWS used any more:
 -            // Vector msaws = null; // (Vector)
 -            // Discoverer.services.get("MsaWS");
 -            Vector<ServiceHandle> secstrpr = ds.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 = secstrpr.get(i);
 -                jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
 -                        .getServiceClient(sh);
 -                int p = secstrmenu.getItemCount();
 -                impl.attachWSMenuEntry(secstrmenu, me);
 -                int q = secstrmenu.getItemCount();
 -                for (int litm = p; litm < q; litm++)
 -                {
 -                  legacyItems.add(secstrmenu.getItem(litm));
 -                }
 -              }
 -            }
 -          }
 -
 -          // Add all submenus in the order they should appear on the web
 -          // services menu
 -          wsmenu.add(msawsmenu);
 -          wsmenu.add(secstrmenu);
 -          wsmenu.add(dismenu);
 -          wsmenu.add(analymenu);
 -          // No search services yet
 -          // wsmenu.add(seqsrchmenu);
 -
 -          javax.swing.SwingUtilities.invokeLater(new Runnable()
 -          {
 -
 -            @Override
 -            public void run()
 -            {
 -              try
 -              {
 -                webService.removeAll();
 -                // first, add discovered services onto the webservices menu
 -                if (wsmenu.size() > 0)
 -                {
 -                  for (int i = 0, j = wsmenu.size(); i < j; i++)
 -                  {
 -                    webService.add(wsmenu.get(i));
 -                  }
 -                }
 -                else
 -                {
 -                  webService.add(me.webServiceNoServices);
 -                }
 -                // TODO: move into separate menu builder class.
 -                // boolean new_sspred = false;
 -                if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
 -                {
 -                  Jws2Discoverer jws2servs = Jws2Discoverer.getInstance();
 -                  if (jws2servs != null)
 -                  {
 -                    if (jws2servs.hasServices())
 -                    {
 -                      jws2servs.attachWSMenuEntry(webService, me);
 -                      for (Jws2Instance sv : jws2servs.getServices())
 -                      {
 -                        if (sv.description.toLowerCase().contains("jpred"))
 -                        {
 -                          for (JMenuItem jmi : legacyItems)
 -                          {
 -                            jmi.setVisible(false);
 -                          }
 -                        }
 -                      }
 -
 -                    }
 -                    if (jws2servs.isRunning())
 -                    {
 -                      JMenuItem tm = new JMenuItem(
 -                              "Still discovering JABA Services");
 -                      tm.setEnabled(false);
 -                      webService.add(tm);
 -                    }
 -                  }
 -                }
 -                build_urlServiceMenu(me.webService);
 -                build_fetchdbmenu(webService);
 -                for (JMenu item : wsmenu)
 -                {
 -                  if (item.getItemCount() == 0)
 -                  {
 -                    item.setEnabled(false);
 -                  }
 -                  else
 -                  {
 -                    item.setEnabled(true);
 -                  }
 -                }
 -              } catch (Exception e)
 -              {
 -                Cache.log.debug(
 -                        "Exception during web service menu building process.",
 -                        e);
 -              }
 -            }
 -          });
 -        } catch (Exception e)
 +        for (ext.vamsas.ServiceHandle sh : secstrpred) 
          {
 +          var menuProvider = Discoverer.getServiceClient(sh);
 +          menuProvider.attachWSMenuEntry(secstrmenu, this);
          }
 -        buildingMenu = false;
        }
 -    }).start();
 +    }
 +    menu.add(secstrmenu);
 +  }
  
 +  /**
 +   * Constructs the web services menu for the given discoverer under the
 +   * specified menu. This method must be called on the EDT
 +   * 
 +   * @param discoverer
 +   *          the discoverer used to build the menu
 +   * @param menu
 +   *          parent component which the elements will be attached to
 +   */
 +  private void buildWebServicesMenu(WSDiscovererI discoverer, JMenu menu)
 +  {
 +    if (discoverer.hasServices())
 +    {
 +      PreferredServiceRegistry.getRegistry().populateWSMenuEntry(
 +              discoverer.getServices(), sv -> buildWebServicesMenu(), menu,
 +              this, null);
 +    }
 +    if (discoverer.isRunning())
 +    {
 +      JMenuItem item = new JMenuItem("Service discovery in progress.");
 +      item.setEnabled(false);
 +      menu.add(item);
 +    }
 +    else if (!discoverer.hasServices())
 +    {
 +      JMenuItem item = new JMenuItem("No services available.");
 +      item.setEnabled(false);
 +      menu.add(item);
 +    }
    }
  
    /**
     * 
     * @param webService
     */
    protected void build_urlServiceMenu(JMenu webService)
    {
      // TODO: remove this code when 2.7 is released
       * JMenuItem testAlView = new JMenuItem("Test AlignmentView"); final
       * AlignFrame af = this; testAlView.addActionListener(new ActionListener() {
       * 
-      * @Override public void actionPerformed(ActionEvent e) {
+      *  public void actionPerformed(ActionEvent e) {
       * jalview.datamodel.AlignmentView
       * .testSelectionViews(af.viewport.getAlignment(),
       * af.viewport.getColumnSelection(), af.viewport.selectionGroup); }
     * 
     * @return true if Show Cross-references menu should be enabled
     */
    public boolean canShowProducts()
    {
      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
          JMenuItem xtype = new JMenuItem(source);
          xtype.addActionListener(new ActionListener()
          {
            @Override
            public void actionPerformed(ActionEvent e)
            {
     * @param source
     *          the database to show cross-references for
     */
    protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
            final String source)
    {
     * Construct and display a new frame containing the translation of this
     * frame's DNA sequences to their aligned protein (amino acid) equivalents.
     */
    @Override
    public void showTranslation_actionPerformed(GeneticCodeI codeTable)
    {
        final String errorTitle = MessageManager
                .getString("label.implementation_error")
                + MessageManager.getString("label.translation_failed");
-       JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
-               JvOptionPane.ERROR_MESSAGE);
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
+               errorTitle, JvOptionPane.ERROR_MESSAGE);
        return;
      }
      if (al == null || al.getHeight() == 0)
                "label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
        final String errorTitle = MessageManager
                .getString("label.translation_failed");
-       JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
-               JvOptionPane.WARNING_MESSAGE);
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
+               errorTitle, JvOptionPane.WARNING_MESSAGE);
      }
      else
      {
        if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
        {
          final SequenceI[] seqs = viewport.getSelectionAsNewSequence();
-         viewport.openSplitFrame(af, new Alignment(seqs));
+         AlignViewport.openSplitFrame(this, af, new Alignment(seqs));
        }
        else
        {
     * 
     * @param format
     */
    public void setFileFormat(FileFormatI format)
    {
      this.currentFileFormat = format;
     *          access mode of file (see jalview.io.AlignFile)
     * @return true if features file was parsed correctly.
     */
    public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
    {
      // BH 2018
      return avc.parseFeaturesFile(file, sourceType,
-             Cache.getDefault("RELAXEDSEQIDMATCHING", false));
+             Cache.getDefault(Preferences.RELAXEDSEQIDMATCHING, false));
  
    }
  
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
  
-     final AlignFrame thisaf = this;
      final List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
      {
        Desktop.transferFromDropTarget(files, protocols, evt, t);
+       if (files.size() > 0)
+       {
+         new Thread(new Runnable()
+         {
+           @Override
+           public void run()
+           {
+             loadDroppedFiles(files, protocols, evt, t);
+           }
+         }).start();
+       }
      } catch (Exception e)
      {
        e.printStackTrace();
      }
-     if (files != null)
+   }
+   protected void loadDroppedFiles(List<Object> files,
+           List<DataSourceType> protocols, DropTargetDropEvent evt,
+           Transferable t)
+   {
+     try
      {
-       new Thread(new Runnable()
+       // check to see if any of these files have names matching sequences
+       // in
+       // the alignment
+       SequenceIdMatcher idm = new SequenceIdMatcher(
+               viewport.getAlignment().getSequencesArray());
+       /**
+        * Object[] { String,SequenceI}
+        */
+       ArrayList<Object[]> filesmatched = new ArrayList<>();
+       ArrayList<Object> filesnotmatched = new ArrayList<>();
+       for (int i = 0; i < files.size(); i++)
        {
-         @Override
-         public void run()
+         // BH 2018
+         Object fileObj = files.get(i);
+         String fileName = fileObj.toString();
+         String pdbfn = "";
+         DataSourceType protocol = (fileObj instanceof File
+                 ? DataSourceType.FILE
+                 : FormatAdapter.checkProtocol(fileName));
+         if (protocol == DataSourceType.FILE)
          {
-           try
+           File file;
+           if (fileObj instanceof File)
+           {
+             file = (File) fileObj;
+             Platform.cacheFileData(file);
+           }
+           else
            {
-             // check to see if any of these files have names matching sequences
-             // in
-             // the alignment
-             SequenceIdMatcher idm = new SequenceIdMatcher(
-                     viewport.getAlignment().getSequencesArray());
-             /**
-              * Object[] { String,SequenceI}
-              */
-             ArrayList<Object[]> filesmatched = new ArrayList<>();
-             ArrayList<Object> filesnotmatched = new ArrayList<>();
-             for (int i = 0; i < files.size(); i++)
+             file = new File(fileName);
+           }
+           pdbfn = file.getName();
+         }
+         else if (protocol == DataSourceType.URL)
+         {
+           URL url = new URL(fileName);
+           pdbfn = url.getFile();
+         }
+         if (pdbfn.length() > 0)
+         {
+           // attempt to find a match in the alignment
+           SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
+           int l = 0, c = pdbfn.indexOf(".");
+           while (mtch == null && c != -1)
+           {
+             do
              {
-               // BH 2018
-               Object file = files.get(i);
-               String fileName = file.toString();
-               String pdbfn = "";
-               DataSourceType protocol = (file instanceof File
-                       ? DataSourceType.FILE
-                       : FormatAdapter.checkProtocol(fileName));
-               if (protocol == DataSourceType.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(fileName);
-                 pdbfn = url.getFile();
-               }
-               if (pdbfn.length() > 0)
-               {
-                 // attempt to find a match in the alignment
-                 SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
-                 int l = 0, c = pdbfn.indexOf(".");
-                 while (mtch == null && c != -1)
-                 {
-                   do
-                   {
-                     l = c;
-                   } while ((c = pdbfn.indexOf(".", l)) > l);
-                   if (l > -1)
-                   {
-                     pdbfn = pdbfn.substring(0, l);
-                   }
-                   mtch = idm.findAllIdMatches(pdbfn);
-                 }
-                 if (mtch != null)
-                 {
-                   FileFormatI type;
-                   try
-                   {
-                     type = new IdentifyFile().identify(file, protocol);
-                   } catch (Exception ex)
-                   {
-                     type = null;
-                   }
-                   if (type != null && type.isStructureFile())
-                   {
-                     filesmatched.add(new Object[] { file, protocol, mtch });
-                     continue;
-                   }
-                 }
-                 // File wasn't named like one of the sequences or wasn't a PDB
-                 // file.
-                 filesnotmatched.add(file);
-               }
+               l = c;
+             } while ((c = pdbfn.indexOf(".", l)) > l);
+             if (l > -1)
+             {
+               pdbfn = pdbfn.substring(0, l);
              }
-             int assocfiles = 0;
-             if (filesmatched.size() > 0)
+             mtch = idm.findAllIdMatches(pdbfn);
+           }
+           if (mtch != null)
+           {
+             FileFormatI type;
+             try
              {
-               boolean autoAssociate = Cache
-                       .getDefault("AUTOASSOCIATE_PDBANDSEQS", false);
-               if (!autoAssociate)
-               {
-                 String msg = MessageManager.formatMessage(
-                         "label.automatically_associate_structure_files_with_sequences_same_name",
-                         new Object[]
-                         { Integer.valueOf(filesmatched.size())
-                                 .toString() });
-                 String ttl = MessageManager.getString(
-                         "label.automatically_associate_structure_files_by_name");
-                 int choice = JvOptionPane.showConfirmDialog(thisaf, msg,
-                         ttl, JvOptionPane.YES_NO_OPTION);
-                 autoAssociate = choice == JvOptionPane.YES_OPTION;
-               }
-               if (autoAssociate)
-               {
-                 for (Object[] fm : filesmatched)
-                 {
-                   // try and associate
-                   // TODO: may want to set a standard ID naming formalism for
-                   // associating PDB files which have no IDs.
-                   for (SequenceI toassoc : (SequenceI[]) fm[2])
-                   {
-                     PDBEntry pe = new AssociatePdbFileWithSeq()
-                             .associatePdbWithSeq(fm[0].toString(),
-                                     (DataSourceType) fm[1], toassoc, false,
-                                     Desktop.instance);
-                     if (pe != null)
-                     {
-                       System.err.println("Associated file : "
-                               + (fm[0].toString()) + " with "
-                               + toassoc.getDisplayId(true));
-                       assocfiles++;
-                     }
-                   }
-                   // TODO: do we need to update overview ? only if features are
-                   // shown I guess
-                   alignPanel.paintAlignment(true, false);
-                 }
-               }
-               else
-               {
-                 /*
-                  * add declined structures as sequences
-                  */
-                 for (Object[] o : filesmatched)
-                 {
-                   filesnotmatched.add(o[0]);
-                 }
-               }
+               type = new IdentifyFile().identify(fileObj, protocol);
+             } catch (Exception ex)
+             {
+               type = null;
              }
-             if (filesnotmatched.size() > 0)
+             if (type != null && type.isStructureFile())
              {
-               if (assocfiles > 0 && (Cache.getDefault(
-                       "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
-                       || JvOptionPane.showConfirmDialog(thisaf,
-                               "<html>" + MessageManager.formatMessage(
-                                       "label.ignore_unmatched_dropped_files_info",
-                                       new Object[]
-                                       { Integer.valueOf(
-                                               filesnotmatched.size())
-                                               .toString() })
-                                       + "</html>",
-                               MessageManager.getString(
-                                       "label.ignore_unmatched_dropped_files"),
-                               JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
-               {
-                 return;
-               }
-               for (Object fn : filesnotmatched)
+               filesmatched.add(new Object[] { fileObj, protocol, mtch });
+               continue;
+             }
+           }
+           // File wasn't named like one of the sequences or wasn't a PDB
+           // file.
+           filesnotmatched.add(fileObj);
+         }
+       }
+       int assocfiles = 0;
+       if (filesmatched.size() > 0)
+       {
+         boolean autoAssociate = Cache
+                 .getDefault(Preferences.AUTOASSOCIATE_PDBANDSEQS, false);
+         if (!autoAssociate)
+         {
+           String msg = MessageManager.formatMessage(
+                   "label.automatically_associate_structure_files_with_sequences_same_name",
+                   new Object[]
+                   { Integer.valueOf(filesmatched.size()).toString() });
+           String ttl = MessageManager.getString(
+                   "label.automatically_associate_structure_files_by_name");
+           int choice = JvOptionPane.showConfirmDialog(this, msg, ttl,
+                   JvOptionPane.YES_NO_OPTION);
+           autoAssociate = choice == JvOptionPane.YES_OPTION;
+         }
+         if (autoAssociate)
+         {
+           for (Object[] fm : filesmatched)
+           {
+             // try and associate
+             // TODO: may want to set a standard ID naming formalism for
+             // associating PDB files which have no IDs.
+             for (SequenceI toassoc : (SequenceI[]) fm[2])
+             {
+               PDBEntry pe = AssociatePdbFileWithSeq.associatePdbWithSeq(
+                       fm[0].toString(), (DataSourceType) fm[1], toassoc,
+                       false);
+               if (pe != null)
                {
-                 loadJalviewDataFile(fn, null, null, null);
+                 System.err.println("Associated file : " + (fm[0].toString())
+                         + " with " + toassoc.getDisplayId(true));
+                 assocfiles++;
                }
              }
-           } catch (Exception ex)
+             // TODO: do we need to update overview ? only if features are
+             // shown I guess
+             alignPanel.paintAlignment(true, false);
+           }
+         }
+         else
+         {
+           /*
+            * add declined structures as sequences
+            */
+           for (Object[] o : filesmatched)
            {
-             ex.printStackTrace();
+             filesnotmatched.add(o[0]);
            }
          }
-       }).start();
+       }
+       if (filesnotmatched.size() > 0)
+       {
+         if (assocfiles > 0 && (Cache
+                 .getDefault("AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
+                 || JvOptionPane.showConfirmDialog(this,
+                         "<html>" + MessageManager.formatMessage(
+                                 "label.ignore_unmatched_dropped_files_info",
+                                 new Object[]
+                                 { Integer.valueOf(filesnotmatched.size())
+                                         .toString() })
+                                 + "</html>",
+                         MessageManager.getString(
+                                 "label.ignore_unmatched_dropped_files"),
+                         JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
+         {
+           return;
+         }
+         for (Object fn : filesnotmatched)
+         {
+           loadJalviewDataFile(fn, null, null, null);
+         }
+       }
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
      }
    }
  
     * 
     * @param file
     *          either a filename or a URL string.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    public void loadJalviewDataFile(Object file, DataSourceType sourceType,
            FileFormatI format, SequenceI assocSeq)
    {
              {
                // some problem - if no warning its probable that the ID matching
                // process didn't work
-               JvOptionPane.showMessageDialog(Desktop.desktop,
+               JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                        tcf.getWarningMessage() == null
                                ? MessageManager.getString(
                                        "label.check_file_matches_sequence_ids_alignment")
        }
        if (isAnnotation)
        {
-         alignPanel.adjustAnnotationHeight();
-         viewport.updateSequenceIdColours();
-         buildSortByAnnotationScoresMenu();
-         alignPanel.paintAlignment(true, true);
+         updateForAnnotations();
        }
      } catch (Exception ex)
      {
                        + (format != null
                                ? "(parsing as '" + format + "' file)"
                                : ""),
-               oom, Desktop.desktop);
+               oom, Desktop.getDesktopPane());
+     }
+   }
+   /**
+    * Do all updates necessary after an annotation file such as jnet. Also called
+    * from Jalview.loadAppletParams for "annotations", "jnetFile"
+    */
+   public void updateForAnnotations()
+   {
+     alignPanel.adjustAnnotationHeight();
+     viewport.updateSequenceIdColours();
+     buildSortByAnnotationScoresMenu();
+     alignPanel.paintAlignment(true, true);
+   }
+   /**
+    * Change the display state for the given feature groups -- Added by BH from
+    * JalviewLite
+    * 
+    * @param groups
+    *          list of group strings
+    * @param state
+    *          visible or invisible
+    */
+   public void setFeatureGroupState(String[] groups, boolean state)
+   {
+     jalview.api.FeatureRenderer fr = null;
+     viewport.setShowSequenceFeatures(true);
+     if (alignPanel != null
+             && (fr = alignPanel.getFeatureRenderer()) != null)
+     {
+       fr.setGroupVisibility(Arrays.asList(groups), state);
+       alignPanel.getSeqPanel().seqCanvas.repaint();
+       if (alignPanel.overviewPanel != null)
+       {
+         alignPanel.overviewPanel.updateOverviewImage();
+       }
      }
    }
  
     * Method invoked by the ChangeListener on the tabbed pane, in other words
     * when a different tabbed pane is selected by the user or programmatically.
     */
    @Override
    public void tabSelectionChanged(int index)
    {
    /**
     * On right mouse click on view tab, prompt for and set new view name.
     */
    @Override
    public void tabbedPane_mousePressed(MouseEvent e)
    {
    /**
     * Open the dialog for regex description parsing.
     */
    @Override
    protected void extractScores_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showDbRefs_actionPerformed(java.awt.event.ActionEvent
     * )
     */
    @Override
    protected void showDbRefs_actionPerformed(ActionEvent e)
    {
     * @seejalview.jbgui.GAlignFrame#showNpFeats_actionPerformed(java.awt.event.
     * ActionEvent)
     */
    @Override
    protected void showNpFeats_actionPerformed(ActionEvent e)
    {
     * 
     * @param av
     */
    public boolean closeView(AlignViewportI av)
    {
      if (viewport == av)
              Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true));
      trimrs.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
        {
          new Thread(new Runnable()
          {
            @Override
            public void run()
            {
                      alignPanel.alignFrame.featureSettings, isNucleotide);
              dbRefFetcher.addListener(new FetchFinishedListenerI()
              {
                @Override
                public void finished()
                {
      rfetch.add(fetchr);
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
-         final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
-                 .getSequenceFetcherSingleton();
+         // ??
+         // final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
+         // .getSequenceFetcherSingleton();
          javax.swing.SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
            {
+             jalview.ws.SequenceFetcher sf = jalview.ws.SequenceFetcher
+                     .getInstance();
              String[] dbclasses = sf.getNonAlignmentSources();
              List<DbSourceProxy> otherdb;
              JMenu dfetch = new JMenu();
                }
                if (otherdb.size() == 1)
                {
-                 final DbSourceProxy[] dassource = otherdb
-                         .toArray(new DbSourceProxy[0]);
                  DbSourceProxy src = otherdb.get(0);
+                 DbSourceProxy[] dassource = new DbSourceProxy[] { src };
                  fetchr = new JMenuItem(src.getDbSource());
                  fetchr.addActionListener(new ActionListener()
                  {
                          dbRefFetcher
                                  .addListener(new FetchFinishedListenerI()
                                  {
                                    @Override
                                    public void finished()
                                    {
                          { src.getDbSource() }));
                  fetchr.addActionListener(new ActionListener()
                  {
                    @Override
                    public void actionPerformed(ActionEvent e)
                    {
                          dbRefFetcher
                                  .addListener(new FetchFinishedListenerI()
                                  {
                                    @Override
                                    public void finished()
                                    {
                            dbRefFetcher
                                    .addListener(new FetchFinishedListenerI()
                                    {
                                      @Override
                                      public void finished()
                                      {
    /**
     * Left justify the whole alignment.
     */
    @Override
    protected void justifyLeftMenuItem_actionPerformed(ActionEvent e)
    {
-     AlignmentI al = viewport.getAlignment();
-     al.justify(false);
-     viewport.firePropertyChange("alignment", null, al);
+     viewport.getAlignment().justify(false);
+     viewport.notifyAlignment();
    }
  
    /**
     * Right justify the whole alignment.
     */
    @Override
    protected void justifyRightMenuItem_actionPerformed(ActionEvent e)
    {
-     AlignmentI al = viewport.getAlignment();
-     al.justify(true);
-     viewport.firePropertyChange("alignment", null, al);
+     viewport.getAlignment().justify(true);
+     viewport.notifyAlignment();
    }
  
    @Override
     * jalview.jbgui.GAlignFrame#showUnconservedMenuItem_actionPerformed(java.
     * awt.event.ActionEvent)
     */
    @Override
    protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showGroupConsensus_actionPerformed(java.awt.event
     * .ActionEvent)
     */
    @Override
    protected void showGroupConsensus_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showGroupConservation_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showGroupConservation_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showConsensusHistogram_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showConsensusHistogram_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showConsensusProfile_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showSequenceLogo_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#makeGrpsFromSelection_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void makeGrpsFromSelection_actionPerformed(ActionEvent e)
    {
     * 
     * @param alignmentPanel
     */
    public void setDisplayedView(AlignmentPanel alignmentPanel)
    {
      if (!viewport.getSequenceSetId()
     * @param forAlignment
     *          update non-sequence-related annotations
     */
    @Override
    protected void setAnnotationsVisibility(boolean visible,
            boolean forSequences, boolean forAlignment)
    /**
     * Store selected annotation sort order for the view and repaint.
     */
    @Override
    protected void sortAnnotations_actionPerformed()
    {
     * 
     * @return alignment panels in this alignment frame
     */
    public List<? extends AlignmentViewPanel> getAlignPanels()
    {
      // alignPanels is never null
     * Open a new alignment window, with the cDNA associated with this (protein)
     * alignment, aligned as is the protein.
     */
    protected void viewAsCdna_actionPerformed()
    {
      // TODO no longer a menu action - refactor as required
     * 
     * @param show
     */
    @Override
    protected void showComplement_actionPerformed(boolean show)
    {
     * Generate the reverse (optionally complemented) of the selected sequences,
     * and add them to the alignment
     */
    @Override
    protected void showReverse_actionPerformed(boolean complement)
    {
     * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
     * be targeted at this alignment.
     */
    @Override
    protected void runGroovy_actionPerformed()
    {
        } catch (Exception ex)
        {
          System.err.println((ex.toString()));
-         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  MessageManager.getString("label.couldnt_run_groovy_script"),
                  MessageManager.getString("label.groovy_support_failed"),
                  JvOptionPane.ERROR_MESSAGE);
     * @param columnsContaining
     * @return
     */
    public boolean hideFeatureColumns(String featureType,
            boolean columnsContaining)
    {
     * Rebuilds the Colour menu, including any user-defined colours which have
     * been loaded either on startup or during the session
     */
    public void buildColourMenu()
    {
      colourMenu.removeAll();
     * Open a dialog (if not already open) that allows the user to select and
     * calculate PCA or Tree analysis
     */
    protected void openTreePcaDialog()
    {
      if (alignPanel.getCalculationDialog() == null)
      }
    }
  
 +  /**
 +   * Sets the status of the HMMER menu
 +   */
 +  public void updateHMMERStatus()
 +  {
 +    hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
 +  }
 +
    @Override
    protected void loadVcf_actionPerformed()
    {
      final AlignFrame us = this;
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
    {
      return lastFeatureSettingsBounds;
    }
- }
  
- class PrintThread extends Thread
- {
-   AlignmentPanel ap;
+   public void scrollTo(int row, int column)
+   {
+     alignPanel.getSeqPanel().scrollTo(row, column);
+   }
  
-   public PrintThread(AlignmentPanel ap)
+   public void scrollToRow(int row)
    {
-     this.ap = ap;
+     alignPanel.getSeqPanel().scrollToRow(row);
    }
  
-   static PageFormat pf;
+   public void scrollToColumn(int column)
+   {
+     alignPanel.getSeqPanel().scrollToColumn(column);
+   }
  
-   @Override
-   public void run()
+   /**
+    * BH 2019 from JalviewLite
+    * 
+    * get sequence feature groups that are hidden or shown
+    * 
+    * @param visible
+    *          true is visible
+    * @return list
+    */
+   public String[] getFeatureGroupsOfState(boolean visible)
    {
-     PrinterJob printJob = PrinterJob.getPrinterJob();
+     jalview.api.FeatureRenderer fr = null;
+     if (alignPanel != null
+             && (fr = alignPanel.getFeatureRenderer()) != null)
+     {
+       List<String> gps = fr.getGroups(visible);
+       String[] _gps = gps.toArray(new String[gps.size()]);
+       return _gps;
+     }
+     return null;
+   }
  
-     if (pf != null)
+   /**
+    * 
+    * @return list of feature groups on the view
+    */
+   public String[] getFeatureGroups()
+   {
+     jalview.api.FeatureRenderer fr = null;
+     if (alignPanel != null
+             && (fr = alignPanel.getFeatureRenderer()) != null)
      {
-       printJob.setPrintable(ap, pf);
+       List<String> gps = fr.getFeatureGroups();
+       String[] _gps = gps.toArray(new String[gps.size()]);
+       return _gps;
      }
-     else
+     return null;
+   }
+   public void select(SequenceGroup sel, ColumnSelection csel,
+           HiddenColumns hidden)
+   {
+     alignPanel.getSeqPanel().selection(sel, csel, hidden, null);
+   }
+   public int getID()
+   {
+     return id;
+   }
+   static class PrintThread extends Thread
+   {
+     AlignmentPanel ap;
+     public PrintThread(AlignmentPanel ap)
      {
-       printJob.setPrintable(ap);
+       this.ap = ap;
      }
  
-     if (printJob.printDialog())
+     static PageFormat pf;
+     @Override
+     public void run()
      {
-       try
+       PrinterJob printJob = PrinterJob.getPrinterJob();
+       if (pf != null)
        {
-         printJob.print();
-       } catch (Exception PrintException)
+         printJob.setPrintable(ap, pf);
+       }
+       else
+       {
+         printJob.setPrintable(ap);
+       }
+       if (printJob.printDialog())
        {
-         PrintException.printStackTrace();
+         try
+         {
+           printJob.print();
+         } catch (Exception PrintException)
+         {
+           PrintException.printStackTrace();
+         }
        }
      }
    }
  }
@@@ -39,7 -39,6 +39,7 @@@ import jalview.datamodel.SearchResults
  import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.renderer.ResidueShader;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
@@@ -74,6 -73,14 +74,14 @@@ import javax.swing.JInternalFrame
  public class AlignViewport extends AlignmentViewport
          implements SelectionSource
  {
+   public final static int NO_SPLIT = 0;
+   public final static int SPLIT_FRAME = 1;
+   public final static int NEW_WINDOW = 2;
    Font font;
  
    boolean cursorMode = false;
     * @param hiddenColumns
     * @param seqsetid
     *          (may be null)
-    */
+ f   */
    public AlignViewport(AlignmentI al, HiddenColumns hiddenColumns,
            String seqsetid)
    {
  
      setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
      setCentreColumnLabels(Cache.getDefault("CENTRE_COLUMN_LABELS", false));
-     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
+     autoCalculateConsensusAndConservation = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
  
      setPadGaps(Cache.getDefault("PAD_GAPS", true));
      setShowNPFeats(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
  
      setFont(new Font(fontName, style, Integer.parseInt(fontSize)), true);
  
 -    alignment
 -            .setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
 +              alignment.setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
  
      // We must set conservation and consensus before setting colour,
      // as Blosum and Clustal require this to be done
 -    if (hconsensus == null && !isDataset)
 +              if (hconsensus == null && !isDataset)
      {
 -      if (!alignment.isNucleotide())
 +                      if (!alignment.isNucleotide())
        {
          showConservation = Cache.getDefault("SHOW_CONSERVATION", true);
          showQuality = Cache.getDefault("SHOW_QUALITY", true);
        showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
        normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
                false);
 +      // for now, use consensus options for Information till it gets its own
 +      setShowHMMSequenceLogo(showSequenceLogo);
 +      setNormaliseHMMSequenceLogo(normaliseSequenceLogo);
 +      setShowInformationHistogram(showConsensusHistogram);
        showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
        showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
  
        showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true);
      }
      initAutoAnnotation();
 -    String colourProperty = alignment.isNucleotide()
 +    // initInformation();
 +
 +              String colourProperty = alignment.isNucleotide()
              ? Preferences.DEFAULT_COLOUR_NUC
              : Preferences.DEFAULT_COLOUR_PROT;
      String schemeName = Cache.getProperty(colourProperty);
  
      if (residueShading != null)
      {
 -      residueShading.setConsensus(hconsensus);
 +                      residueShading.setConsensus(hconsensus);
      }
      setColourAppliesToAllGroups(true);
    }
 -
 +  
    boolean validCharWidth;
  
    /**
      if (align != null)
      {
        StructureSelectionManager ssm = StructureSelectionManager
-               .getStructureSelectionManager(Desktop.instance);
+               .getStructureSelectionManager(Desktop.getInstance());
        ssm.registerMappings(align.getCodonFrames());
      }
  
      /*
       * replace mappings on our alignment
       */
 -    if (alignment != null && align != null)
 +              if (alignment != null && align != null)
      {
        alignment.setCodonFrames(align.getCodonFrames());
      }
        if (mappings != null)
        {
          StructureSelectionManager ssm = StructureSelectionManager
-                 .getStructureSelectionManager(Desktop.instance);
+                 .getStructureSelectionManager(Desktop.getInstance());
          for (AlignedCodonFrame acf : mappings)
          {
            if (noReferencesTo(acf))
    }
  
    /**
 -   * returns the visible column regions of the alignment
 +   * Returns an iterator over the visible column regions of the alignment
     * 
     * @param selectedRegionOnly
     *          true to just return the contigs intersecting with the selected
      {
        end = alignment.getWidth();
      }
 -    return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
 -            false));
 +
 +    return (alignment.getHiddenColumns().getVisContigsIterator(start,
 +            end, false));
    }
  
    /**
    }
  
    public boolean followSelection = true;
 -
 +  
    /**
     * @return true if view selection should always follow the selections
     *         broadcast by other selection sources
    public void sendSelection()
    {
      jalview.structure.StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance)
+             .getStructureSelectionManager(Desktop.getInstance())
              .sendSelection(new SequenceGroup(getSelectionGroup()),
                      new ColumnSelection(getColumnSelection()),
                      new HiddenColumns(getAlignment().getHiddenColumns()),
    public StructureSelectionManager getStructureSelectionManager()
    {
      return StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
    }
 -
 +  
    @Override
    public boolean isNormaliseSequenceLogo()
    {
      return normaliseSequenceLogo;
    }
  
 -  public void setNormaliseSequenceLogo(boolean state)
 +  @Override
 +public void setNormaliseSequenceLogo(boolean state)
    {
      normaliseSequenceLogo = state;
    }
  
 +
    /**
     * 
     * @return true if alignment characters should be displayed
    {
      return validCharWidth;
    }
 -
 +  
    private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<>();
  
    public AutoCalcSetting getCalcIdSettingsFor(String calcId)
      }
  
      ranges.setEndSeq(getAlignment().getHeight() - 1); // BH 2019.04.18
-     firePropertyChange("alignment", null, getAlignment().getSequences());
+     notifyAlignment();
    }
  
    /**
       * dialog responses 0, 1, 2 (even though JOptionPane shows them
       * in reverse order)
       */
-     JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
-             .setResponseHandler(0, new Runnable()
+     JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane())
+             .setResponseHandler(NO_SPLIT, new Runnable()
              {
                @Override
                public void run()
                {
                    addDataToAlignment(al);
                }
-             }).setResponseHandler(1, new Runnable()
+             }).setResponseHandler(SPLIT_FRAME, new Runnable()
              {
                @Override
                public void run()
                {
-                 us.openLinkedAlignmentAs(al, title, true);
+                 openLinkedAlignmentAs(getAlignPanel().alignFrame,
+                         new Alignment(getAlignment()), al, title,
+                         SPLIT_FRAME);
+ //                us.openLinkedAlignmentAs(al, title, true);
                }
-             }).setResponseHandler(2, new Runnable()
+             }).setResponseHandler(NEW_WINDOW, new Runnable()
              {
                @Override
                public void run()
                {
-                 us.openLinkedAlignmentAs(al, title, false);
+                 openLinkedAlignmentAs(null, getAlignment(), al, title,
+                         NEW_WINDOW);
                }
              });
-       dialog.showDialog(question,
+       dialog.showDialog(question,
              MessageManager.getString("label.open_split_window"),
              JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
              options, options[0]);
    }
-   protected void openLinkedAlignmentAs(AlignmentI al, String title,
-           boolean newWindowOrSplitPane)
-     {
+   /**
+    * Open a split frame or a new window
+    * 
+    * @param al
+    * @param title
+    * @param mode
+    *          SPLIT_FRAME or NEW_WINDOW
+    */
+   public static void openLinkedAlignmentAs(AlignFrame thisFrame,
+           AlignmentI thisAlignment, AlignmentI al, String title, int mode)
+   {
      /*
       * Identify protein and dna alignments. Make a copy of this one if opening
       * in a new split pane.
       */
-     AlignmentI thisAlignment = newWindowOrSplitPane
-             ? new Alignment(getAlignment())
-             : getAlignment();
      AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
-     final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
+     AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
      /*
       * Map sequences. At least one should get mapped as we have already passed
       * the test for 'mappability'. Any mappings made will be added to the
      // alignFrame.setFileName(file, format);
      // }
  
-     if (!newWindowOrSplitPane)
+     if (mode == NEW_WINDOW)
      {
        Desktop.addInternalFrame(newAlignFrame, title,
                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
      {
      }
  
-     if (newWindowOrSplitPane)
+     if (mode == SPLIT_FRAME)
      {
        al.alignAs(thisAlignment);
-       protein = openSplitFrame(newAlignFrame, thisAlignment);
+       openSplitFrame(thisFrame, newAlignFrame, thisAlignment);
      }
    }
  
     *          cdna/protein complement alignment to show in the other split half
     * @return the protein alignment in the split frame
     */
-   protected AlignmentI openSplitFrame(AlignFrame newAlignFrame,
-           AlignmentI complement)
+   static protected AlignmentI openSplitFrame(AlignFrame thisFrame,
+           AlignFrame newAlignFrame, AlignmentI complement)
    {
      /*
       * Make a new frame with a copy of the alignment we are adding to. If this
       */
      AlignFrame copyMe = new AlignFrame(complement, AlignFrame.DEFAULT_WIDTH,
              AlignFrame.DEFAULT_HEIGHT);
-     copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
+     copyMe.setTitle(thisFrame.getTitle());
  
      AlignmentI al = newAlignFrame.viewport.getAlignment();
      final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
      {
        FeatureColourI preferredColour = featureSettings
                .getFeatureColour(type);
 +      FeatureMatcherSetI preferredFilters = featureSettings
 +              .getFeatureFilters(type);
 +
        FeatureColourI origColour = fr.getFeatureStyle(type);
        if (!mergeOnly || (!origRenderOrder.contains(type)
                || origColour == null
          {
            fr.setColour(type, preferredColour);
          }
 +        if (preferredFilters != null
 +                && (!mergeOnly || fr.getFeatureFilter(type) != null))
 +        {
 +          fr.setFeatureFilter(type, preferredFilters);
 +        }
          if (featureSettings.isFeatureDisplayed(type))
          {
            displayed.setVisible(type);
    {
      this.viewName = viewName;
    }
  }
   */
  package jalview.gui;
  
- import jalview.analysis.AlignSeq;
- import jalview.analysis.AlignmentUtils;
- import jalview.datamodel.Alignment;
- import jalview.datamodel.AlignmentAnnotation;
- import jalview.datamodel.Annotation;
- import jalview.datamodel.HiddenColumns;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceGroup;
- import jalview.datamodel.SequenceI;
- import jalview.io.FileFormat;
- import jalview.io.FormatAdapter;
- import jalview.util.Comparison;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
- import jalview.workers.InformationThread;
  import java.awt.Color;
  import java.awt.Cursor;
  import java.awt.Dimension;
@@@ -63,6 -47,21 +47,22 @@@ import javax.swing.JPopupMenu
  import javax.swing.SwingUtilities;
  import javax.swing.ToolTipManager;
  
+ import jalview.analysis.AlignSeq;
+ import jalview.analysis.AlignmentUtils;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.FileFormat;
+ import jalview.io.FormatAdapter;
+ import jalview.util.Comparison;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
++import jalview.workers.InformationThread;
  /**
   * The panel that holds the labels for alignment annotations, providing
   * tooltips, context menus, drag to reorder rows, and drag to adjust panel
@@@ -353,10 -352,6 +353,10 @@@ public class AnnotationLabels extends J
        pop.show(this, evt.getX(), evt.getY());
        return;
      }
 +
 +    final AlignmentAnnotation ann = aa[selectedRow];
 +    final boolean isSequenceAnnotation = ann.sequenceRef != null;
 +
      item = new JMenuItem(EDITNAME);
      item.addActionListener(this);
      pop.add(item);
      if (selectedRow < aa.length)
      {
        final String label = aa[selectedRow].label;
 -      if (!aa[selectedRow].autoCalculated)
 +      if (!(aa[selectedRow].autoCalculated)
 +              && !(InformationThread.HMM_CALC_ID.equals(ann.getCalcId())))
        {
          if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
          {
            pop.addSeparator();
            // av and sequencegroup need to implement same interface for
            item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
 -                  aa[selectedRow].scaleColLabel);
 +                        aa[selectedRow].scaleColLabel);
            item.addActionListener(this);
            pop.add(item);
          }
          consclipbrd.addActionListener(this);
          pop.add(consclipbrd);
        }
 +      else if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
 +      {
 +        addHmmerMenu(pop, ann);
 +      }
      }
      pop.show(this, evt.getX(), evt.getY());
    }
  
    /**
 +   * Adds context menu options for (alignment or group) Hmmer annotation
 +   * 
 +   * @param pop
 +   * @param ann
 +   */
 +  protected void addHmmerMenu(JPopupMenu pop, final AlignmentAnnotation ann)
 +  {
 +    final boolean isGroupAnnotation = ann.groupRef != null;
 +    pop.addSeparator();
 +    final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
 +            MessageManager.getString(
 +                    "label.ignore_below_background_frequency"),
 +            isGroupAnnotation
 +                    ? ann.groupRef
 +                            .isIgnoreBelowBackground()
 +                    : ap.av.isIgnoreBelowBackground());
 +    cbmi.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        if (isGroupAnnotation)
 +        {
 +          if (!ann.groupRef.isUseInfoLetterHeight())
 +          {
 +            ann.groupRef.setIgnoreBelowBackground(cbmi.getState());
 +            // todo and recompute group annotation
 +          }
 +        }
 +        else if (!ap.av.isInfoLetterHeight())
 +        {
 +          ap.av.setIgnoreBelowBackground(cbmi.getState(), ap);
 +          // todo and recompute annotation
 +        }
 +        ap.alignmentChanged(); // todo not like this
 +      }
 +    });
 +    pop.add(cbmi);
 +    final JCheckBoxMenuItem letterHeight = new JCheckBoxMenuItem(
 +            MessageManager.getString("label.use_info_for_height"),
 +            isGroupAnnotation ? ann.groupRef.isUseInfoLetterHeight()
 +                    : ap.av.isInfoLetterHeight());
 +    letterHeight.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        if (isGroupAnnotation)
 +        {
 +          ann.groupRef.setInfoLetterHeight((letterHeight.getState()));
 +          ann.groupRef.setIgnoreBelowBackground(true);
 +          // todo and recompute group annotation
 +        }
 +        else
 +        {
 +          ap.av.setInfoLetterHeight(letterHeight.getState(), ap);
 +          ap.av.setIgnoreBelowBackground(true, ap);
 +          // todo and recompute annotation
 +        }
 +        ap.alignmentChanged();
 +      }
 +    });
 +    pop.add(letterHeight);
 +    if (isGroupAnnotation)
 +    {
 +      final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_group_histogram"),
 +              ann.groupRef.isShowInformationHistogram());
 +      chist.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef.setShowInformationHistogram(chist.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(chist);
 +      final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_group_logo"),
 +              ann.groupRef.isShowHMMSequenceLogo());
 +      cprofl.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef.setShowHMMSequenceLogo(cprofl.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprofl);
 +      final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.normalise_group_logo"),
 +              ann.groupRef.isNormaliseHMMSequenceLogo());
 +      cproflnorm.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef
 +                  .setNormaliseHMMSequenceLogo(cproflnorm.getState());
 +          // automatically enable logo display if we're clicked
 +          ann.groupRef.setShowHMMSequenceLogo(true);
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cproflnorm);
 +    }
 +    else
 +    {
 +      final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_histogram"),
 +              av.isShowInformationHistogram());
 +      chist.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowInformationHistogram(chist.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(chist);
 +      final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_logo"),
 +              av.isShowHMMSequenceLogo());
 +      cprof.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowHMMSequenceLogo(cprof.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprof);
 +      final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.normalise_logo"),
 +              av.isNormaliseHMMSequenceLogo());
 +      cprofnorm.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowHMMSequenceLogo(true);
 +          av.setNormaliseHMMSequenceLogo(cprofnorm.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprofnorm);
 +    }
 +  }
 +
 +  /**
     * A helper method that adds menu options for calculation and visualisation of
     * group and/or alignment consensus annotation to a popup menu. This is
     * designed to be reusable for either unwrapped mode (popup menu is shown on
              PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
              ap.av.sendSelection();
            }
 -
          }
        }
        return;
              seqs, omitHidden, alignmentStartEnd);
  
      Toolkit.getDefaultToolkit().getSystemClipboard()
-             .setContents(new StringSelection(output), Desktop.instance);
+             .setContents(new StringSelection(output), Desktop.getInstance());
  
      HiddenColumns hiddenColumns = null;
  
                av.getAlignment().getHiddenColumns());
      }
  
-     Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
-                                                         // of a consensus
-                                                         // sequence ? need to
-                                                         // flag
-         // sequence as special.
+     // what is the dataset of a consensus sequence? 
+     // need to flag sequence as special.
+     Desktop.getInstance().jalviewClipboard = new Object[] { seqs, ds, 
          hiddenColumns };
    }
  
    @Override
    public void paintComponent(Graphics g)
    {
 -
      int width = getWidth();
      if (width == 0)
      {
      }
  
      drawComponent(g2, true, width);
 -
    }
  
    /**
@@@ -23,12 -23,12 +23,12 @@@ package jalview.gui
  import jalview.analysis.TreeBuilder;
  import jalview.analysis.scoremodels.ScoreModels;
  import jalview.analysis.scoremodels.SimilarityParams;
 +import jalview.api.AlignViewportI;
  import jalview.api.analysis.ScoreModelI;
  import jalview.api.analysis.SimilarityParamsI;
  import jalview.bin.Cache;
  import jalview.datamodel.SequenceGroup;
  import jalview.util.MessageManager;
 -
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -62,8 -62,13 +62,13 @@@ import javax.swing.event.InternalFrameA
  import javax.swing.event.InternalFrameEvent;
  
  /**
-  * A dialog where a user can choose and action Tree or PCA calculation options
+  * A dialog where a user can choose and action Tree or PCA calculation options.
+  * 
+  * Allows also for dialog-free static methods openPCAPanel(...) and
+  * openTreePanel(...) for scripted use.
+  * 
   */
+ @SuppressWarnings("serial")
  public class CalculationChooser extends JPanel
  {
    /*
@@@ -74,7 -79,7 +79,7 @@@
     */
    private static boolean treeMatchGaps = true;
  
-   private static final Font VERDANA_11PT = new Font("Verdana", 0, 11);
+   private static Font VERDANA_11PT;
  
    private static final int MIN_TREE_SELECTION = 3;
  
  
    private JCheckBox shorterSequence;
  
-   final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
+   private static ComboBoxTooltipRenderer renderer; // BH was not static
  
    List<String> tips = new ArrayList<>();
  
    private PCAPanel pcaPanel;
  
    /**
+    * Open a new Tree panel on the desktop statically. Params are standard (not
+    * set by Groovy). No dialog is opened.
+    * 
+    * @param af
+    * @param treeType
+    * @param modelName
+    * @return null if successful; the string
+    *         "label.you_need_at_least_n_sequences" if number of sequences
+    *         selected is inappropriate
+    */
+   public static Object openTreePanel(AlignFrame af, String treeType,
+           String modelName)
+   {
+     return openTreePanel(af, treeType, modelName, null);
+   }
+   /**
+    * public static method for JalviewJS API to open a PCAPanel without
+    * necessarily using a dialog.
+    * 
+    * @param af
+    * @param modelName
+    * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
+    *         if number of sequences selected is inappropriate
+    */
+   public static Object openPcaPanel(AlignFrame af, String modelName)
+   {
+     return openPcaPanel(af, modelName, null);
+   }
+   /**
     * Constructor
     * 
     * @param af
      paramsPanel.add(includeGappedColumns);
      paramsPanel.add(shorterSequence);
  
+     if (VERDANA_11PT == null)
+     {
+       VERDANA_11PT = new Font("Verdana", 0, 11);
+     }
      /*
       * OK / Cancel buttons
       */
        title = title + " (" + af.getViewport().getViewName() + ")";
      }
  
-     Desktop.addInternalFrame(frame, title, width, height, false);
+     Desktop.addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, width, height, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_SET_MIN_SIZE_300);
      calcChoicePanel.doLayout();
      revalidate();
      /*
     */
    protected JComboBox<String> buildModelOptionsList()
    {
-     final JComboBox<String> scoreModelsCombo = new JComboBox<>();
+     JComboBox<String> scoreModelsCombo = new JComboBox<>();
+     if (renderer == null)
+     {
+       renderer = new ComboBoxTooltipRenderer();
+     }
      scoreModelsCombo.setRenderer(renderer);
  
      /*
       * for backwards compatibility with Jalview < 2.8 (JAL-2962)
       */
      if (nucleotide && forPca
-             && Cache.getDefault("BLOSUM62_PCA_FOR_NUCLEOTIDE", false))
+             && Cache.getDefault(Preferences.BLOSUM62_PCA_FOR_NUCLEOTIDE,
+                     false))
      {
        filtered.add(scoreModels.getBlosum62());
      }
     */
    protected void openTreePanel(String modelName, SimilarityParamsI params)
    {
+     Object ret = openTreePanel(af,
+             neighbourJoining.isSelected() ? TreeBuilder.NEIGHBOUR_JOINING
+                     : TreeBuilder.AVERAGE_DISTANCE,
+             modelName, params);
+     if (ret instanceof String)
+     {
+       JvOptionPane.showMessageDialog(this, // was opening on Desktop?
+               MessageManager.formatMessage(
+                       (String) ret,
+                       MIN_TREE_SELECTION),
+               MessageManager.getString("label.not_enough_sequences"),
+               JvOptionPane.WARNING_MESSAGE);
+     }
+   }
+   /**
+    * Open a new PCA panel on the desktop
+    * 
+    * @param modelName
+    * @param params
+    */
+   protected void openPcaPanel(String modelName, SimilarityParamsI params)
+   {
+     Object ret = openPcaPanel(af, modelName, params);
+     if (ret instanceof String)
+     {
+       JvOptionPane.showInternalMessageDialog(this,
+               MessageManager.formatMessage(
+                       (String) ret,
+                       MIN_PCA_SELECTION),
+               MessageManager
+                       .getString("label.sequence_selection_insufficient"),
+               JvOptionPane.WARNING_MESSAGE);
+     }
+     else
+     {
+       // only used for test suite
+       pcaPanel = (PCAPanel) ret;
+     }
+   }
+   /**
+    * Open a new Tree panel on the desktop statically
+    * 
+    * @param af
+    * @param treeType
+    * @param modelName
+    * @param params
+    * @return null, or the string "label.you_need_at_least_n_sequences" if number
+    *         of sequences selected is inappropriate
+    */
+   public static Object openTreePanel(AlignFrame af, String treeType,
+           String modelName, SimilarityParamsI params)
+   {
      /*
       * gui validation shouldn't allow insufficient sequences here, but leave
       * this check in in case this method gets exposed programmatically in future
       */
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
      SequenceGroup sg = viewport.getSelectionGroup();
      if (sg != null && sg.getSize() < MIN_TREE_SELECTION)
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop,
-               MessageManager.formatMessage(
-                       "label.you_need_at_least_n_sequences",
-                       MIN_TREE_SELECTION),
-               MessageManager.getString("label.not_enough_sequences"),
-               JvOptionPane.WARNING_MESSAGE);
-       return;
+       return "label.you_need_at_least_n_sequences";
+     }
+     if (params == null)
+     {
+       params = getSimilarityParameters(false);
      }
  
-     String treeType = neighbourJoining.isSelected()
-             ? TreeBuilder.NEIGHBOUR_JOINING
-             : TreeBuilder.AVERAGE_DISTANCE;
      af.newTreePanel(treeType, modelName, params);
+     return null;
    }
  
    /**
-    * Open a new PCA panel on the desktop
+    * public static method for JalviewJS API
     * 
+    * @param af
     * @param modelName
     * @param params
+    * @return the PCAPanel, or null if number of sequences selected is
+    *         inappropriate
     */
-   protected void openPcaPanel(String modelName, SimilarityParamsI params)
+   public static Object openPcaPanel(AlignFrame af, String modelName,
+           SimilarityParamsI params)
    {
 -
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
  
      /*
       * gui validation shouldn't allow insufficient sequences here, but leave
       * this check in in case this method gets exposed programmatically in future
+      * 
+      * 
       */
      if (((viewport.getSelectionGroup() != null)
              && (viewport.getSelectionGroup().getSize() < MIN_PCA_SELECTION)
              && (viewport.getSelectionGroup().getSize() > 0))
              || (viewport.getAlignment().getHeight() < MIN_PCA_SELECTION))
      {
-       JvOptionPane.showInternalMessageDialog(this,
-               MessageManager.formatMessage(
-                       "label.you_need_at_least_n_sequences",
-                       MIN_PCA_SELECTION),
-               MessageManager
-                       .getString("label.sequence_selection_insufficient"),
-               JvOptionPane.WARNING_MESSAGE);
-       return;
+       return "label.you_need_at_least_n_sequences";
+     }
+     if (params == null)
+     {
+       params = getSimilarityParameters(true);
      }
  
      /*
       * construct the panel and kick off its calculation thread
       */
-     pcaPanel = new PCAPanel(af.alignPanel, modelName, params);
-     new Thread(pcaPanel).start();
+     PCAPanel pcap = new PCAPanel(af.alignPanel, modelName, params);
+     new Thread(pcap).start();
+     return pcap;
    }
  
    /**
      }
    }
  
    /**
     * Returns a data bean holding parameters for similarity (or distance) model
     * calculation
     * @param doPCA
     * @return
     */
-   protected SimilarityParamsI getSimilarityParameters(boolean doPCA)
+   public static SimilarityParamsI getSimilarityParameters(
+           boolean doPCA)
    {
      // commented out: parameter choices read from gui widgets
      // SimilarityParamsI params = new SimilarityParams(
  
      return new SimilarityParams(includeGapGap, matchGap, includeGapResidue,
              matchOnShortestLength);
    }
  
    /**
@@@ -59,11 -59,8 +59,11 @@@ import java.util.Hashtable
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Vector;
 +import java.util.concurrent.ExecutionException;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
 +import java.util.concurrent.Future;
 +import java.util.concurrent.FutureTask;
  import java.util.concurrent.Semaphore;
  
  import javax.swing.AbstractAction;
@@@ -97,6 -94,9 +97,9 @@@ import org.stackoverflowusers.file.Wind
  
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
+ import jalview.api.StructureSelectionManagerProvider;
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
  import jalview.gui.ImageExporter.ImageWriterI;
@@@ -111,6 -111,7 +114,7 @@@ import jalview.io.FormatAdapter
  import jalview.io.IdentifyFile;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
+ import jalview.jbgui.GDesktop;
  import jalview.jbgui.GSplitFrame;
  import jalview.jbgui.GStructureViewer;
  import jalview.project.Jalview2XML;
@@@ -120,10 -121,8 +124,9 @@@ 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;
  
   * @author $author$
   * @version $Revision: 1.155 $
   */
- public class Desktop extends jalview.jbgui.GDesktop
+ @SuppressWarnings("serial")
+ public class Desktop extends GDesktop
          implements DropTargetListener, ClipboardOwner, IProgressIndicator,
-         jalview.api.StructureSelectionManagerProvider
+         StructureSelectionManagerProvider, ApplicationSingletonI
  {
    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"
  
    public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
  
 +  @SuppressWarnings("deprecation")
    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
  
    /**
     * @param listener
     * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
     */
 +  @Deprecated
    public void addJalviewPropertyChangeListener(
            PropertyChangeListener listener)
    {
     * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
     *      java.beans.PropertyChangeListener)
     */
 +  @Deprecated
    public void addJalviewPropertyChangeListener(String propertyName,
            PropertyChangeListener listener)
    {
     * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
     *      java.beans.PropertyChangeListener)
     */
 +  @Deprecated
    public void removeJalviewPropertyChangeListener(String propertyName,
            PropertyChangeListener listener)
    {
              listener);
    }
  
-   /** Singleton Desktop instance */
-   public static Desktop instance;
+   private MyDesktopPane desktopPane;
  
-   public static MyDesktopPane desktop;
+   public static MyDesktopPane getDesktopPane()
+   {
+     Desktop desktop = getInstance();
+     return desktop == null ? null : desktop.desktopPane;
+   }
  
-   public static MyDesktopPane getDesktop()
+   /**
+    * Answers an 'application scope' singleton instance of this class. Separate
+    * SwingJS 'applets' running in the same browser page will each have a
+    * distinct instance of Desktop.
+    * 
+    * @return
+    */
+   public static Desktop getInstance()
    {
-     // BH 2018 could use currentThread() here as a reference to a
-     // Hashtable<Thread, MyDesktopPane> in JavaScript
-     return desktop;
+     return Jalview.isHeadlessMode() ? null
+             : (Desktop) ApplicationSingletonProvider
+                     .getInstance(Desktop.class);
    }
  
-   static int openFrameCount = 0;
+   public static StructureSelectionManager getStructureSelectionManager()
+   {
+     return StructureSelectionManager
+             .getStructureSelectionManager(getInstance());
+   }
+   int openFrameCount = 0;
  
-   static final int xOffset = 30;
+   final int xOffset = 30;
  
-   static final int yOffset = 30;
+   final int yOffset = 30;
  
-   public static jalview.ws.jws1.Discoverer discoverer;
+   public jalview.ws.jws1.Discoverer discoverer;
  
-   public static Object[] jalviewClipboard;
+   public Object[] jalviewClipboard;
  
-   public static boolean internalCopy = false;
+   public boolean internalCopy = false;
  
-   static int fileLoadingCount = 0;
+   int fileLoadingCount = 0;
  
    class MyDesktopManager implements DesktopManager
    {
        } catch (NullPointerException npe)
        {
          Point p = getMousePosition();
-         instance.showPasteMenu(p.x, p.y);
+         showPasteMenu(p.x, p.y);
        }
      }
  
      public void endDraggingFrame(JComponent f)
      {
        delegate.endDraggingFrame(f);
-       desktop.repaint();
+       desktopPane.repaint();
      }
  
      @Override
      public void endResizingFrame(JComponent f)
      {
        delegate.endResizingFrame(f);
-       desktop.repaint();
+       desktopPane.repaint();
      }
  
      @Override
    }
  
    /**
-    * Creates a new Desktop object.
+    * Private constructor enforces singleton pattern. It is called by reflection
+    * from ApplicationSingletonProvider.getInstance().
     */
-   public Desktop()
+   private Desktop()
    {
-     super();
-     /**
-      * A note to implementors. It is ESSENTIAL that any activities that might
-      * block are spawned off as threads rather than waited for during this
-      * constructor.
-      */
-     instance = this;
-     doConfigureStructurePrefs();
-     setTitle("Jalview " + Cache.getProperty("VERSION"));
-     /*
-     if (!Platform.isAMac())
-     {
-       // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
-     }
-     else
-     {
-      this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
-     }
-     */
+     Cache.initLogger();
      try
      {
-       APQHandlers.setAPQHandlers(this);
-     } catch (Throwable t)
-     {
-       System.out.println("Error setting APQHandlers: " + t.toString());
-       // t.printStackTrace();
-     }
-     addWindowListener(new WindowAdapter()
-     {
+       /**
+        * A note to implementors. It is ESSENTIAL that any activities that might
+        * block are spawned off as threads rather than waited for during this
+        * constructor.
+        */
  
-       @Override
-       public void windowClosing(WindowEvent ev)
+       doConfigureStructurePrefs();
+       setTitle("Jalview " + Cache.getProperty("VERSION"));
+       /*
+       if (!Platform.isAMac())
        {
-         quit();
+       // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        }
-     });
+       else
+       {
+        this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+       }
+       */
  
-     boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE",
-             false);
+       try
+       {
+         APQHandlers.setAPQHandlers(this);
+       } catch (Throwable t)
+       {
+         System.out.println("Error setting APQHandlers: " + t.toString());
+         // t.printStackTrace();
+       }
  
-     boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE",
-             false);
-     desktop = new MyDesktopPane(selmemusage);
+       addWindowListener(new WindowAdapter()
+       {
  
-     showMemusage.setSelected(selmemusage);
-     desktop.setBackground(Color.white);
+         @Override
+         public void windowClosing(WindowEvent ev)
+         {
+           quit();
+         }
+       });
  
-     getContentPane().setLayout(new BorderLayout());
-     // alternate config - have scrollbars - see notes in JAL-153
-     // JScrollPane sp = new JScrollPane();
-     // sp.getViewport().setView(desktop);
-     // getContentPane().add(sp, BorderLayout.CENTER);
+       boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
  
-     // BH 2018 - just an experiment to try unclipped JInternalFrames.
-     if (Platform.isJS())
-     {
-       getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
-     }
+       boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
+       desktopPane = new MyDesktopPane(selmemusage);
  
-     getContentPane().add(desktop, BorderLayout.CENTER);
-     desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+       showMemusage.setSelected(selmemusage);
+       desktopPane.setBackground(Color.white);
  
-     // This line prevents Windows Look&Feel resizing all new windows to maximum
-     // if previous window was maximised
-     desktop.setDesktopManager(new MyDesktopManager(
-             (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
-                     : Platform.isAMacAndNotJS()
-                             ? new AquaInternalFrameManager(
-                                     desktop.getDesktopManager())
-                             : desktop.getDesktopManager())));
+       getContentPane().setLayout(new BorderLayout());
+       // alternate config - have scrollbars - see notes in JAL-153
+       // JScrollPane sp = new JScrollPane();
+       // sp.getViewport().setView(desktop);
+       // getContentPane().add(sp, BorderLayout.CENTER);
  
-     Rectangle dims = getLastKnownDimensions("");
-     if (dims != null)
-     {
-       setBounds(dims);
-     }
-     else
-     {
-       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
-       int xPos = Math.max(5, (screenSize.width - 900) / 2);
-       int yPos = Math.max(5, (screenSize.height - 650) / 2);
-       setBounds(xPos, yPos, 900, 650);
-     }
+       // BH 2018 - just an experiment to try unclipped JInternalFrames.
+       if (Platform.isJS())
+       {
+         getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
+       }
  
-     if (!Platform.isJS())
-     /**
-      * Java only
-      * 
-      * @j2sIgnore
-      */
-     {
-       jconsole = new Console(this, showjconsole);
-       jconsole.setHeader(Cache.getVersionDetailsForConsole());
-       showConsole(showjconsole);
+       getContentPane().add(desktopPane, BorderLayout.CENTER);
+       desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
  
-       showNews.setVisible(false);
+       // This line prevents Windows Look&Feel resizing all new windows to
+       // maximum
+       // if previous window was maximised
+       desktopPane.setDesktopManager(new MyDesktopManager(
+               (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
+                       : Platform.isAMacAndNotJS()
+                               ? new AquaInternalFrameManager(
+                                       desktopPane.getDesktopManager())
+                               : desktopPane.getDesktopManager())));
  
-       experimentalFeatures.setSelected(showExperimental());
+       Rectangle dims = getLastKnownDimensions("");
+       if (dims != null)
+       {
+         setBounds(dims);
+       }
+       else
+       {
+         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+         int xPos = Math.max(5, (screenSize.width - 900) / 2);
+         int yPos = Math.max(5, (screenSize.height - 650) / 2);
+         setBounds(xPos, yPos, 900, 650);
+       }
  
        getIdentifiersOrgData();
  
-       checkURLLinks();
+       if (!Platform.isJS())
+       /**
+        * Java only
+        * 
+        * @j2sIgnore
+        */
+       {
+         jconsole = new Console(this, showjconsole);
+         jconsole.setHeader(Cache.getVersionDetailsForConsole());
+         showConsole(showjconsole);
  
-       // Spawn a thread that shows the splashscreen
+         showNews.setVisible(false);
  
-       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()
+         if (Jalview.isInteractive())
          {
-           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()
-               {
+           // disabled for SeqCanvasTest
+           checkURLLinks();
  
-                 @Override
-                 public void propertyChange(PropertyChangeEvent evt)
-                 {
-                   Cache.log.debug("Firing service changed event for "
-                           + evt.getNewValue());
-                   JalviewServicesChanged(evt);
-                 }
-               });
-     }
+           // Spawn a thread that shows the splashscreen
  
-     this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
+           SwingUtilities.invokeLater(new Runnable()
+           {
+             @Override
+             public void run()
+             {
+               new SplashScreen(true);
+             }
+           });
  
-     this.addWindowListener(new WindowAdapter()
-     {
-       @Override
-       public void windowClosing(WindowEvent evt)
-       {
-         quit();
+           // Thread off a new instance of the file chooser - this reduces the
+           // time
+           // it
+           // takes to open it later on.
+           new Thread(new Runnable()
+           {
+             @Override
+             public void run()
+             {
+               Cache.log.debug("Filechooser init thread started.");
+               String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
+               JalviewFileChooser.forRead(
+                       Cache.getProperty("LAST_DIRECTORY"), fileFormat);
+               Cache.log.debug("Filechooser init thread finished.");
+             }
+           }).start();
+           // Add the service change listener
+           changeSupport.addJalviewPropertyChangeListener("services",
+                   new PropertyChangeListener()
+                   {
+                     @Override
+                     public void propertyChange(PropertyChangeEvent evt)
+                     {
+                       Cache.log.debug("Firing service changed event for "
+                               + evt.getNewValue());
+                       JalviewServicesChanged(evt);
+                     }
+                   });
+         }
        }
-     });
+       this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
  
-     MouseAdapter ma;
-     this.addMouseListener(ma = new MouseAdapter()
-     {
-       @Override
-       public void mousePressed(MouseEvent evt)
+       this.addWindowListener(new WindowAdapter()
        {
-         if (evt.isPopupTrigger()) // Mac
+         @Override
+         public void windowClosing(WindowEvent evt)
          {
-           showPasteMenu(evt.getX(), evt.getY());
+           quit();
          }
-       }
+       });
  
-       @Override
-       public void mouseReleased(MouseEvent evt)
+       MouseAdapter ma;
+       this.addMouseListener(ma = new MouseAdapter()
        {
-         if (evt.isPopupTrigger()) // Windows
+         @Override
+         public void mousePressed(MouseEvent evt)
          {
-           showPasteMenu(evt.getX(), evt.getY());
+           if (evt.isPopupTrigger()) // Mac
+           {
+             showPasteMenu(evt.getX(), evt.getY());
+           }
          }
-       }
-     });
-     desktop.addMouseListener(ma);
+         @Override
+         public void mouseReleased(MouseEvent evt)
+         {
+           if (evt.isPopupTrigger()) // Windows
+           {
+             showPasteMenu(evt.getX(), evt.getY());
+           }
+         }
+       });
+       desktopPane.addMouseListener(ma);
+     } catch (Throwable t)
+     {
+       t.printStackTrace();
+     }
  
    }
  
              .getStructureSelectionManager(this);
      if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
      {
-       ssm.setAddTempFacAnnot(Cache
-               .getDefault(Preferences.ADD_TEMPFACT_ANN, true));
-       ssm.setProcessSecondaryStructure(Cache
-               .getDefault(Preferences.STRUCT_FROM_PDB, true));
+       ssm.setAddTempFacAnnot(
+               Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
+       ssm.setProcessSecondaryStructure(
+               Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
        ssm.setSecStructServices(
                Cache.getDefault(Preferences.USE_RNAVIEW, true));
      }
          }
        }
      }).start();
-     
    }
  
    @Override
          public void run()
          {
            long now = System.currentTimeMillis();
-           Desktop.instance.setProgressBar(
-                   MessageManager.getString("status.refreshing_news"), now);
+           setProgressBar(MessageManager.getString("status.refreshing_news"),
+                   now);
            jvnews.refreshNews();
-           Desktop.instance.setProgressBar(null, now);
+           setProgressBar(null, now);
            jvnews.showNews();
          }
        }).start();
      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
      String x = Cache.getProperty(windowName + "SCREEN_X");
      String y = Cache.getProperty(windowName + "SCREEN_Y");
-     String width = Cache
-             .getProperty(windowName + "SCREEN_WIDTH");
-     String height = Cache
-             .getProperty(windowName + "SCREEN_HEIGHT");
+     String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
+     String height = Cache.getProperty(windowName + "SCREEN_HEIGHT");
      if ((x != null) && (y != null) && (width != null) && (height != null))
      {
        int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
          // 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(
-                 Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
-         double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(
-                 Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
+         double sw = ((1f * screenSize.width) / (1f * Integer
+                 .parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
+         double sh = ((1f * screenSize.height) / (1f * Integer
+                 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
          // rescale the bounds depending upon the current screen geometry.
          ix = (int) (ix * sw);
          iw = (int) (iw * sw);
      }
    }
  
-   /**
-    * Adds and opens the given frame to the desktop
-    * 
-    * @param frame
-    *          Frame to show
-    * @param title
-    *          Visible Title
-    * @param w
-    *          width
-    * @param h
-    *          height
-    */
-   public static synchronized void addInternalFrame(
-           final JInternalFrame frame, String title, int w, int h)
-   {
-     addInternalFrame(frame, title, true, w, h, true, false);
-   }
-   /**
-    * Add an internal frame to the Jalview desktop
-    * 
-    * @param frame
-    *          Frame to show
-    * @param title
-    *          Visible Title
-    * @param makeVisible
-    *          When true, display frame immediately, otherwise, caller must call
-    *          setVisible themselves.
-    * @param w
-    *          width
-    * @param h
-    *          height
-    */
-   public static synchronized void addInternalFrame(
-           final JInternalFrame frame, String title, boolean makeVisible,
-           int w, int h)
-   {
-     addInternalFrame(frame, title, makeVisible, w, h, true, false);
-   }
+ //  /**
+ //   * Add an internal frame to the Jalview desktop that is allowed to be resized,
+ //   * has a minimum size of 300px and might or might not be visible
+ //   * 
+ //   * @param frame
+ //   *          Frame to show
+ //   * @param title
+ //   *          Visible Title
+ //   * @param makeVisible
+ //   *          When true, display frame immediately, otherwise, caller must call
+ //   *          setVisible themselves.
+ //   * @param w
+ //   *          width
+ //   * @param h
+ //   *          height
+ //   */
+ //  @Deprecated
+ //  public static synchronized void addInternalFrame(
+ //          final JInternalFrame frame, String title, boolean makeVisible,
+ //          int w, int h)
+ //  {
+ //    // textbox, web services, sequenceFetcher, featureSettings
+ //    getInstance().addFrame(frame, title, makeVisible, w, h,
+ //            FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
+ //  }
+ //
+ //  /**
+ //   * Add an internal frame to the Jalview desktop that is visible, has a minimum
+ //   * size of 300px, and may or may not be resizable
+ //   * 
+ //   * @param frame
+ //   *          Frame to show
+ //   * @param title
+ //   *          Visible Title
+ //   * @param w
+ //   *          width
+ //   * @param h
+ //   *          height
+ //   * @param resizable
+ //   *          Allow resize
+ //   */
+ //  @Deprecated
+ //  public static synchronized void addInternalFrame(
+ //          final JInternalFrame frame, String title, int w, int h,
+ //          boolean resizable)
+ //  {
+ //    // annotation, font, calculation, user-defined colors
+ //    getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
+ //            resizable, FRAME_SET_MIN_SIZE_300);
+ //  }
  
    /**
-    * Add an internal frame to the Jalview desktop and make it visible
+    * Adds and opens the given frame to the desktop that is visible, allowed to
+    * resize, and has a 300px minimum width.
     * 
     * @param frame
     *          Frame to show
     *          width
     * @param h
     *          height
-    * @param resizable
-    *          Allow resize
     */
    public static synchronized void addInternalFrame(
-           final JInternalFrame frame, String title, int w, int h,
-           boolean resizable)
+           final JInternalFrame frame, String title, int w, int h)
    {
-     addInternalFrame(frame, title, true, w, h, resizable, false);
+     // 58 classes
+     
+     addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h, 
+             FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
    }
  
    /**
-    * Add an internal frame to the Jalview desktop
+    * Add an internal frame to the Jalview desktop that may optionally be
+    * visible, resizable, and allowed to be any size
     * 
     * @param frame
     *          Frame to show
            final JInternalFrame frame, String title, boolean makeVisible,
            int w, int h, boolean resizable, boolean ignoreMinSize)
    {
+     // 15 classes call this method directly.
+     
      // TODO: allow callers to determine X and Y position of frame (eg. via
      // bounds object).
      // TODO: consider fixing method to update entries in the window submenu with
      {
        frame.setSize(w, h);
      }
-     // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
-     // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
-     // IF JALVIEW IS RUNNING HEADLESS
-     // ///////////////////////////////////////////////
-     if (instance == null || (System.getProperty("java.awt.headless") != null
-             && System.getProperty("java.awt.headless").equals("true")))
-     {
-       return;
-     }
+     if (getInstance() != null)
+       getInstance().addFrame(frame, makeVisible, resizable,
+             ignoreMinSize);
+   }
  
-     openFrameCount++;
+   // These can now by put into a single int flag, if desired:
+   
+   public final static boolean FRAME_MAKE_VISIBLE = true;
+   public final static boolean FRAME_NOT_VISIBLE = false;
+   public final static boolean FRAME_ALLOW_RESIZE = true;
+   public final static boolean FRAME_NOT_RESIZABLE = false;
+   public final static boolean FRAME_ALLOW_ANY_SIZE = true;
  
+   public final static boolean FRAME_SET_MIN_SIZE_300 = false;
+   
+   private void addFrame(JInternalFrame frame,
+           boolean makeVisible, boolean resizable,
+           boolean ignoreMinSize)
+   {
+     openFrameCount++;
+     
+     boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
+     boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
+     // Web page embedding allows us to ignore minimum size
+     ignoreMinSize |= hasEmbeddedSize;
+     
      if (!ignoreMinSize)
      {
-       frame.setMinimumSize(
-               new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
        // Set default dimension for Alignment Frame window.
        // The Alignment Frame window could be added from a number of places,
        // hence,
        {
          frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
                  ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
+       } else {
+         frame.setMinimumSize(
+                 new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
+         
        }
      }
  
      frame.setMaximizable(resizable);
      frame.setIconifiable(resizable);
      frame.setOpaque(Platform.isJS());
-     if (frame.getX() < 1 && frame.getY() < 1)
+     if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
      {
        frame.setLocation(xOffset * openFrameCount,
                yOffset * ((openFrameCount - 1) % 10) + yOffset);
       * add an entry for the new frame in the Window menu 
       * (and remove it when the frame is closed)
       */
-     final JMenuItem menuItem = new JMenuItem(title);
+     final JMenuItem menuItem = new JMenuItem(frame.getTitle());
      frame.addInternalFrameListener(new InternalFrameAdapter()
      {
        @Override
        public void internalFrameActivated(InternalFrameEvent evt)
        {
-         JInternalFrame itf = desktop.getSelectedFrame();
+         JInternalFrame itf = getDesktopPane().getSelectedFrame();
          if (itf != null)
          {
            if (itf instanceof AlignFrame)
          {
            menuItem.removeActionListener(menuItem.getActionListeners()[0]);
          }
-         windowMenu.remove(menuItem);
+         getInstance().windowMenu.remove(menuItem);
        }
      });
  
  
      setKeyBindings(frame);
  
-     desktop.add(frame);
+     getDesktopPane().add(frame);
  
-     windowMenu.add(menuItem);
+     getInstance().windowMenu.add(menuItem);
  
      frame.toFront();
      try
    }
  
    /**
-    * 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
     */
    private static void setKeyBindings(JInternalFrame frame)
    {
-     @SuppressWarnings("serial")
      final Action closeAction = new AbstractAction()
      {
        @Override
      KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
              InputEvent.CTRL_DOWN_MASK);
      KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
+             Platform.SHORTCUT_KEY_MASK);
  
      InputMap inputMap = frame
              .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    {
      if (!internalCopy)
      {
-       Desktop.jalviewClipboard = null;
+       jalviewClipboard = null;
      }
  
      internalCopy = false;
  
      try
      {
-       Desktop.transferFromDropTarget(files, protocols, evt, t);
+       transferFromDropTarget(files, protocols, evt, t);
      } catch (Exception e)
      {
        e.printStackTrace();
    public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
    {
      String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
-     JalviewFileChooser chooser = JalviewFileChooser
-             .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, BackupFiles.getEnabled());
+     JalviewFileChooser chooser = JalviewFileChooser.forRead(
+             Cache.getProperty("LAST_DIRECTORY"), fileFormat,
+             BackupFiles.getEnabled());
  
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
            {
              String msg = MessageManager
                      .formatMessage("label.couldnt_locate", url);
-             JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
+             JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
                      MessageManager.getString("label.url_not_found"),
                      JvOptionPane.WARNING_MESSAGE);
  
      };
      String dialogOption = MessageManager
              .getString("label.input_alignment_from_url");
-     JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
+     JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
              .showInternalDialog(panel, dialogOption,
                      JvOptionPane.YES_NO_CANCEL_OPTION,
                      JvOptionPane.PLAIN_MESSAGE, null, options,
    {
      CutAndPasteTransfer cap = new CutAndPasteTransfer();
      cap.setForInput(viewPanel);
-     Desktop.addInternalFrame(cap,
-             MessageManager.getString("label.cut_paste_alignmen_file"), true,
-             600, 500);
+     addInternalFrame(cap,
+             MessageManager.getString("label.cut_paste_alignmen_file"),
+             FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
+             FRAME_SET_MIN_SIZE_300);
    }
  
    /*
    public void quit()
    {
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
-     Cache.setProperty("SCREENGEOMETRY_WIDTH",
-             screen.width + "");
-     Cache.setProperty("SCREENGEOMETRY_HEIGHT",
-             screen.height + "");
+     Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
+     Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
      storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
              getWidth(), getHeight()));
  
  
    private void storeLastKnownDimensions(String string, Rectangle jc)
    {
-     Cache.log.debug("Storing last known dimensions for "
-             + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
-             + " height:" + jc.height);
+     Cache.log.debug("Storing last known dimensions for " + string + ": x:"
+             + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
+             + jc.height);
  
      Cache.setProperty(string + "SCREEN_X", jc.x + "");
      Cache.setProperty(string + "SCREEN_Y", jc.y + "");
    public void closeAll_actionPerformed(ActionEvent e)
    {
      // TODO show a progress bar while closing?
-     JInternalFrame[] frames = desktop.getAllFrames();
+     JInternalFrame[] frames = desktopPane.getAllFrames();
      for (int i = 0; i < frames.length; i++)
      {
        try
    @Override
    protected void showMemusage_actionPerformed(ActionEvent e)
    {
-     desktop.showMemoryUsage(showMemusage.isSelected());
+     desktopPane.showMemoryUsage(showMemusage.isSelected());
    }
  
    /*
  
    void reorderAssociatedWindows(boolean minimize, boolean close)
    {
-     JInternalFrame[] frames = desktop.getAllFrames();
+     JInternalFrame[] frames = desktopPane.getAllFrames();
      if (frames == null || frames.length < 1)
      {
        return;
      }
  
 -    AlignmentViewport source = null, target = null;
 +    AlignViewportI source = null;
 +    AlignViewportI target = null;
      if (frames[0] instanceof AlignFrame)
      {
        source = ((AlignFrame) frames[0]).getCurrentView();
            setProgressBar(MessageManager.formatMessage(
                    "label.saving_jalview_project", new Object[]
                    { chosenFile.getName() }), chosenFile.hashCode());
-           Cache.setProperty("LAST_DIRECTORY",
-                   chosenFile.getParent());
+           Cache.setProperty("LAST_DIRECTORY", chosenFile.getParent());
            // TODO catch and handle errors for savestate
            // TODO prevent user from messing with the Desktop whilst we're saving
            try
            {
-               boolean doBackup = BackupFiles.getEnabled();
-             BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
+             boolean doBackup = BackupFiles.getEnabled();
+             BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
+                     : null;
  
-             new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
+             new Jalview2XML().saveState(
+                     doBackup ? backupfiles.getTempFile() : chosenFile);
  
              if (doBackup)
              {
            setProgressBar(null, chosenFile.hashCode());
          }
        }).start();
-       }
+     }
    }
  
    @Override
          "Jalview Project (old)" };
      JalviewFileChooser chooser = new JalviewFileChooser(
              Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
-             "Jalview Project", true, BackupFiles.getEnabled()); // last two booleans: allFiles,
-                                             // allowBackupFiles
+             "Jalview Project", true, BackupFiles.getEnabled()); // last two
+                                                                 // booleans:
+                                                                 // allFiles,
+     // allowBackupFiles
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
      chooser.setResponseHandler(0, new Runnable()
            @Override
            public void run()
            {
-               try 
+             try
              {
                new Jalview2XML().loadJalviewAlign(selectedFile);
              } catch (OutOfMemoryError oom)
-               {
-                 new OOMWarning("Whilst loading project from " + choice, oom);
-               } catch (Exception ex)
-               {
-                 Cache.log.error(
-                         "Problems whilst loading project from " + choice, ex);
-                 JvOptionPane.showMessageDialog(Desktop.desktop,
-                         MessageManager.formatMessage(
-                                 "label.error_whilst_loading_project_from",
-                               new Object[]
-                                   { choice }),
-                         MessageManager.getString("label.couldnt_load_project"),
-                         JvOptionPane.WARNING_MESSAGE);
-               }
+             {
+               new OOMWarning("Whilst loading project from " + choice, oom);
+             } catch (Exception ex)
+             {
+               Cache.log.error(
+                       "Problems whilst loading project from " + choice, ex);
+               JvOptionPane.showMessageDialog(getDesktopPane(),
+                       MessageManager.formatMessage(
+                               "label.error_whilst_loading_project_from",
+                               new Object[]
+                               { choice }),
+                       MessageManager
+                               .getString("label.couldnt_load_project"),
+                       JvOptionPane.WARNING_MESSAGE);
+             }
            }
          }).start();
        }
      });
-     
      chooser.showOpenDialog(this);
    }
  
      {
        progressPanel = new JPanel(new GridLayout(1, 1));
        totalProgressCount = 0;
-       instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
+       getContentPane().add(progressPanel, BorderLayout.SOUTH);
      }
      JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
      JProgressBar progressBar = new JProgressBar();
      ((GridLayout) progressPanel.getLayout()).setRows(
              ((GridLayout) progressPanel.getLayout()).getRows() + 1);
      ++totalProgressCount;
-     instance.validate();
+     validate();
      return thisprogress;
    }
  
     */
    public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
    {
-     if (Desktop.desktop == null)
+     if (getDesktopPane() == null)
      {
        // no frames created and in headless mode
        // TODO: verify that frames are recoverable when in headless mode
    public static AlignmentViewport[] getViewports(String sequenceSetId)
    {
      List<AlignmentViewport> viewp = new ArrayList<>();
-     if (desktop != null)
+     if (getDesktopPane() != null)
      {
-       AlignFrame[] frames = Desktop.getAlignFrames();
+       AlignFrame[] frames = getAlignFrames();
  
        for (AlignFrame afr : frames)
        {
  
      // FIXME: ideally should use UI interface API
      FeatureSettings viewFeatureSettings = (af.featureSettings != null
-             && af.featureSettings.isOpen())
-             ? af.featureSettings
-             : null;
+             && af.featureSettings.isOpen()) ? af.featureSettings : null;
      Rectangle fsBounds = af.getFeatureSettingsGeometry();
      for (int i = 0; i < size; i++)
      {
  
        addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
                AlignFrame.DEFAULT_HEIGHT);
-       // and materialise a new feature settings dialog instance for the new alignframe
+       // 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()
  
    /**
     * 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.setGatherViewsHere(true);
      source.viewport.setExplodedGeometry(source.getBounds());
-     JInternalFrame[] frames = desktop.getAllFrames();
+     JInternalFrame[] frames = desktopPane.getAllFrames();
      String viewId = source.viewport.getSequenceSetId();
      for (int t = 0; t < frames.length; t++)
      {
      }
  
      // refresh the feature setting UI for the source frame if it exists
-     if (source.featureSettings != null
-             && source.featureSettings.isOpen())
+     if (source.featureSettings != null && source.featureSettings.isOpen())
      {
        source.showFeatureSettingsUI();
      }
  
    public JInternalFrame[] getAllFrames()
    {
-     return desktop.getAllFrames();
+     return desktopPane.getAllFrames();
    }
  
    /**
            });
            msgPanel.add(jcb);
  
-           JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
+           JvOptionPane.showMessageDialog(desktopPane, msgPanel,
                    MessageManager
                            .getString("label.SEQUENCE_ID_no_longer_used"),
                    JvOptionPane.WARNING_MESSAGE);
  
    /**
     * 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;
  
    {
      if (Jalview.isHeadlessMode())
      {
-       // Desktop.desktop is null in headless mode
-       return new AlignFrame[] { Jalview.currentAlignFrame };
+       return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
      }
  
-     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+     JInternalFrame[] frames = getDesktopPane().getAllFrames();
  
      if (frames == null)
      {
     */
    public GStructureViewer[] getJmols()
    {
-     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+     JInternalFrame[] frames = desktopPane.getAllFrames();
  
      if (frames == null)
      {
      } catch (Exception ex)
      {
        Cache.log.error("Groovy Shell Creation failed.", ex);
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(desktopPane,
  
                MessageManager.getString("label.couldnt_create_groovy_shell"),
                MessageManager.getString("label.groovy_support_failed"),
    }
  
    /**
-    * 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()
    {
      getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
              .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
-                     jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
+                     Platform.SHORTCUT_KEY_MASK),
                      "Quit");
      getRootPane().getActionMap().put("Quit", new AbstractAction()
      {
        progressBars.put(Long.valueOf(id), addProgressPanel(message));
      }
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    //TODO
 +    throw new UnsupportedOperationException("not implemented");
 +  }
  
    /*
     * (non-Javadoc)
    }
  
    /**
-    * 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 static AlignFrame getAlignFrameFor(AlignViewportI viewport)
    {
-     if (desktop != null)
+     if (getDesktopPane() != null)
      {
        AlignmentPanel[] aps = getAlignmentPanels(
                viewport.getSequenceSetId());
  
    public void startServiceDiscovery(boolean blocking)
    {
 -    boolean alive = true;
 -    Thread t0 = null, t1 = null, t2 = null;
 +    System.out.println("Starting service discovery");
 +    var tasks = new ArrayList<Future<?>>();
      // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
 -    if (true)
 +
 +    System.out.println("loading services");
 +    
 +    /** @j2sIgnore */
      {
        // todo: changesupport handlers need to be transferred
        if (discoverer == null)
        {
-         discoverer = new jalview.ws.jws1.Discoverer();
+         discoverer = jalview.ws.jws1.Discoverer.getInstance();
          // register PCS handler for desktop.
          discoverer.addPropertyChangeListener(changeSupport);
        }
        // JAL-940 - disabled JWS1 service configuration - always start discoverer
        // until we phase out completely
 -      (t0 = new Thread(discoverer)).start();
 +      var f = new FutureTask<Void>(discoverer, null);
 +      new Thread(f).start();
 +      tasks.add(f);
      }
  
      if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
      {
-       tasks.add(jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer());
 -      t2 = jalview.ws.jws2.Jws2Discoverer.getInstance()
 -              .startDiscoverer(changeSupport);
++      tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
      }
 -    Thread t3 = null;
 +    if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
      {
 -      // TODO: do rest service discovery
 +      tasks.add(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance().startDiscoverer());
      }
      if (blocking)
      {
 -      while (alive)
 -      {
 +      for (Future<?> task : tasks) {
          try
          {
 -          Thread.sleep(15);
 +          // block until all discovery tasks are done
 +          task.get();
          } catch (Exception e)
          {
 +          e.printStackTrace();
          }
 -        alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive())
 -                || (t3 != null && t3.isAlive())
 -                || (t0 != null && t0.isAlive());
        }
      }
    }
    {
      if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
      {
 -      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
 -              .getErrorMessages();
 +      final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
-           .getDiscoverer();
++          .getInstance();
 +      final String ermsg = discoverer.getErrorMessages();
++      // CONFLICT:ALT:?     final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
        if (ermsg != null)
        {
          if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
                   * 
                   * jd.waitForInput();
                   */
-                 JvOptionPane.showConfirmDialog(Desktop.desktop,
+                 JvOptionPane.showConfirmDialog(desktopPane,
                          new JLabel("<html><table width=\"450\"><tr><td>"
                                  + ermsg + "</td></tr></table>"
                                  + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
     */
    public static void showUrl(final String url)
    {
-     showUrl(url, Desktop.instance);
+     showUrl(url, getInstance());
    }
  
    /**
            jalview.util.BrowserLauncher.openURL(url);
          } catch (Exception ex)
          {
-           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+           JvOptionPane.showInternalMessageDialog(getDesktopPane(),
                    MessageManager
                            .getString("label.web_browser_not_found_unix"),
                    MessageManager.getString("label.web_browser_not_found"),
        try
        {
          url = e.getURL().toString();
-         Desktop.showUrl(url);
+         showUrl(url);
        } catch (Exception x)
        {
          if (url != null)
            {
            }
          }
-         if (instance == null)
+         if (Jalview.isHeadlessMode())
          {
            return;
          }
  
    /**
     * 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.
     * 
        {
          splitFrame.setLocation(geometry.getLocation());
        }
-       Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
+       addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
      }
  
      /*
      String topViewId = myTopFrame.viewport.getSequenceSetId();
      String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
  
-     JInternalFrame[] frames = desktop.getAllFrames();
+     JInternalFrame[] frames = desktopPane.getAllFrames();
      for (JInternalFrame frame : frames)
      {
        if (frame instanceof SplitFrame && frame != source)
     *          - the payload from the drop event
     * @throws Exception
     */
+   @SuppressWarnings("unchecked")
    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
+     // 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++) {
      // byte[] data = getDroppedFileBytes(file);
      // fileName.setText(file.getName() + " - " + data.length + " " +
      // evt.getLocation());
-     // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
+     // JTextArea target = (JTextArea) ((DropTarget)
+     // evt.getSource()).getComponent();
      // target.setText(new String(data));
      // }
      // dtde.dropComplete(true);
      {
        // Works on Windows and MacOSX
        Cache.log.debug("Drop handled as javaFileListFlavor");
-       for (Object file : (List) t
+       for (File file : (List<File>) t
                .getTransferData(DataFlavor.javaFileListFlavor))
        {
          files.add(file);
    }
  
    /**
-    * 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
            Class<? extends StructureViewerBase> structureViewerClass)
    {
      List<StructureViewerBase> result = new ArrayList<>();
-     JInternalFrame[] frames = Desktop.instance.getAllFrames();
+     JInternalFrame[] frames = getAllFrames();
  
      for (JInternalFrame frame : frames)
      {
      }
      return result;
    }
  }
   */
  package jalview.gui;
  
- import jalview.util.MessageManager;
 -import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
 +import java.awt.Container;
  import java.awt.Font;
 -import java.awt.GridLayout;
 -import java.awt.Rectangle;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
@@@ -37,13 -37,18 +35,16 @@@ import javax.swing.BorderFactory
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JComponent;
 -import javax.swing.JLabel;
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
 -import javax.swing.JPanel;
  import javax.swing.JScrollBar;
  import javax.swing.SwingConstants;
  import javax.swing.border.Border;
  import javax.swing.border.TitledBorder;
  
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
  /**
   * useful functions for building Swing GUIs
   * 
   */
  public final class JvSwingUtils
  {
+   static final String HTML_PREFIX = (Platform.isJS() ? 
+           "<html><div style=\"max-width:350px;overflow-wrap:break-word;display:inline-block\">"
+           : "<html><div style=\"width:350; text-align: justify; word-wrap: break-word;\">"
+             );
    /**
     * wrap a bare html safe string to around 60 characters per line using a CSS
     * style class specifying word-wrap and break-word
     * 
     * @param enclose
-    *          if true, add &lt;html&gt; wrapper tags
+    *          if true, add &lt;html&gt; wrapper tags (currently false for only
+    *          two references -- both in Jws2Discoverer --
     * @param ttext
     * 
     * @return
    {
      Objects.requireNonNull(ttext,
              "Tootip text to format must not be null!");
-     ttext = ttext.trim();
-     boolean maxLengthExceeded = false;
+     ttext = ttext.trim().replaceAll("<br/>", "<br>");
  
-     if (ttext.contains("<br>"))
+     boolean maxLengthExceeded = false;
+     boolean isHTML = ttext.startsWith("<html>");
+     if (isHTML)
      {
-       String[] htmllines = ttext.split("<br>");
-       for (String line : htmllines)
-       {
-         maxLengthExceeded = line.length() > 60;
-         if (maxLengthExceeded)
-         {
+       ttext = ttext.substring(6);
+     }
+     if (ttext.endsWith("</html>"))
+     {
+       isHTML = true;
+       ttext = ttext.substring(0, ttext.length() - 7);
+     }
+     boolean hasBR = ttext.contains("<br>");
+     enclose |= isHTML || hasBR;
+     if (hasBR)
+     {  
+       int pt = -1, ptlast = -4;
+       while ((pt = ttext.indexOf("<br>", pt + 1)) >= 0) {
+         if (pt - ptlast - 4 > 60) {
+           maxLengthExceeded = true;
            break;
          }
        }
      }
-     else
+     else  
      {
        maxLengthExceeded = ttext.length() > 60;
      }
  
-     if (!maxLengthExceeded)
-     {
-       return enclose ? "<html>" + ttext + "</html>" : ttext;
-     }
-     return (enclose ? "<html>" : "")
-      // BH 2018
-             + "<style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style><div class=\"ttip\">"
- //            + "<style> p.ttip {width:350px;margin:-14px 0px -14px 0px;padding:2px;overflow-wrap:break-word;}"
- //            + "</style><p class=\"ttip\">"
-             + ttext
-             + " </div>"
- //            + "</p>"
-             + ((enclose ? "</html>" : ""));
+     String ret = (!enclose ? ttext : maxLengthExceeded ? HTML_PREFIX + ttext + "</div></html>" :
+       "<html>" + ttext + "</html>");
+     //System.out.println("JvSwUtil " + enclose + " " + maxLengthExceeded + " " + ret);
+     return ret;
    }
  
    public static JButton makeButton(String label, String tooltip,
    }
  
    /**
 +   * A convenience method that that adds a component with label to a container,
 +   * sets a tooltip on both component and label, and optionally specifies layout
 +   * constraints for the added component (but not the label)
     * 
 -   * @param panel
 +   * @param container
     * @param tooltip
     * @param label
 -   * @param valBox
 -   * @return the GUI element created that was added to the layout so it's
 -   *         attributes can be changed.
 +   * @param comp
 +   * @param constraints
     */
 -  public static JPanel addtoLayout(JPanel panel, String tooltip,
 -          JComponent label, JComponent valBox)
 -  {
 -    JPanel laypanel = new JPanel(new GridLayout(1, 2));
 -    JPanel labPanel = new JPanel(new BorderLayout());
 -    JPanel valPanel = new JPanel();
 -    labPanel.setBounds(new Rectangle(7, 7, 158, 23));
 -    valPanel.setBounds(new Rectangle(172, 7, 270, 23));
 -    labPanel.add(label, BorderLayout.WEST);
 -    valPanel.add(valBox);
 -    laypanel.add(labPanel);
 -    laypanel.add(valPanel);
 -    valPanel.setToolTipText(tooltip);
 -    labPanel.setToolTipText(tooltip);
 -    valBox.setToolTipText(tooltip);
 -    panel.add(laypanel);
 -    panel.validate();
 -    return laypanel;
 -  }
 -
 -  public static void mgAddtoLayout(JPanel cpanel, String tooltip,
 -          JLabel jLabel, JComponent name)
 +  public static void addtoLayout(Container container, String tooltip,
 +          JComponent label, JComponent comp, String constraints)
    {
 -    mgAddtoLayout(cpanel, tooltip, jLabel, name, null);
 -  }
 -
 -  public static void mgAddtoLayout(JPanel cpanel, String tooltip,
 -          JLabel jLabel, JComponent name, String params)
 -  {
 -    cpanel.add(jLabel);
 -    if (params == null)
 -    {
 -      cpanel.add(name);
 -    }
 -    else
 -    {
 -      cpanel.add(name, params);
 -    }
 -    name.setToolTipText(tooltip);
 -    jLabel.setToolTipText(tooltip);
 +    container.add(label);
 +    container.add(comp, constraints);
 +    comp.setToolTipText(tooltip); // this doesn't seem to show?
 +    label.setToolTipText(tooltip);
    }
  
    /**
  
    /**
     * Adds a titled border to the component in the default font and position (top
 -   * left), optionally witht italic text
 +   * left), optionally with italic text
     * 
     * @param comp
     * @param title
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.gui;
  
 +import jalview.api.AlignViewportI;
  import jalview.bin.Cache;
  import jalview.renderer.OverviewRenderer;
  import jalview.util.MessageManager;
@@@ -64,7 -63,7 +64,7 @@@ public class OverviewPanel extends JPan
  
    private OverviewCanvas oviewCanvas;
  
 -  protected AlignViewport av;
 +  private AlignViewportI av;
  
    private AlignmentPanel ap;
  
  
    protected boolean draggingBox = false;
  
+   private Dimension dim;
+   
+   private boolean showProgress = !Platform.isJS();
    protected ProgressPanel progressPanel;
  
+   
    /**
-    * Creates a new OverviewPanel object.
-    * 
-    * @param alPanel
-    *          The alignment panel which is shown in the overview panel
+    * Creates the appropriate type of OverviewDimensions, with the desired size
     */
-   public OverviewPanel(AlignmentPanel alPanel)
+   private void createOverviewDimensions()
    {
-     this.av = alPanel.av;
-     this.ap = alPanel;
-     showHidden = Cache.getDefault(Preferences.SHOW_OV_HIDDEN_AT_START,
-             false);
+     boolean showAnnotation = (av.isShowAnnotation()
+             && av.getAlignmentConservationAnnotation() != null);
      if (showHidden)
      {
-       od = new OverviewDimensionsShowHidden(av.getRanges(),
-             (av.isShowAnnotation()
-                     && av.getAlignmentConservationAnnotation() != null));
+       od = new OverviewDimensionsShowHidden(av.getRanges(), showAnnotation,
+               dim);
      }
      else
      {
-       od = new OverviewDimensionsHideHidden(av.getRanges(),
-               (av.isShowAnnotation()
-                       && av.getAlignmentConservationAnnotation() != null));
+       od = new OverviewDimensionsHideHidden(av.getRanges(), showAnnotation,
+               dim);
      }
+   }
  
+   public OverviewPanel(AlignmentPanel alPanel, Dimension dim)
+   {
+     this.av = alPanel.av;
+     this.ap = alPanel;
+     this.dim = dim;
+     showHidden = Cache.getDefault(Preferences.SHOW_OV_HIDDEN_AT_START,
+             false);
+     createOverviewDimensions();
      setLayout(new BorderLayout());
      progressPanel = new ProgressPanel(OverviewRenderer.UPDATE,
              MessageManager.getString("label.oview_calc"), getWidth());
     */
    protected void toggleHiddenColumns()
    {
-     if (showHidden)
-     {
-       showHidden = false;
-       od = new OverviewDimensionsHideHidden(av.getRanges(),
-               (av.isShowAnnotation()
-                       && av.getAlignmentConservationAnnotation() != null));
-     }
-     else
-     {
-       showHidden = true;
-       od = new OverviewDimensionsShowHidden(av.getRanges(),
-               (av.isShowAnnotation()
-                       && av.getAlignmentConservationAnnotation() != null));
-     }
+     showHidden = !showHidden;
+     createOverviewDimensions();
      oviewCanvas.resetOviewDims(od);
      updateOverviewImage();
      setBoxPosition();
@@@ -36,6 -36,7 +36,7 @@@ import jalview.jbgui.GPCAPanel
  import jalview.math.RotatableMatrix.Axis;
  import jalview.util.ImageMaker;
  import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.PCAModel;
  
@@@ -200,10 -201,7 +201,7 @@@ public class PCAPanel extends GPCAPane
      repaint();
      if (getParent() == null)
      {
-       Desktop.addInternalFrame(this,
-               MessageManager.formatMessage("label.calc_title", "PCA",
-                       getPcaModel().getScoreModelName()),
-               475, 450);
+       addToDesktop(this, getPcaModel().getScoreModelName());
        this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
      }
      working = false;
      // // setMenusForViewport();
      // validate();
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
      getRotatableCanvas().ap = panel;
      PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());
    }
+   public static void addToDesktop(PCAPanel panel, String modelName)
+   {
+     Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
+     Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+             "label.calc_title", "PCA", modelName), dim.width,
+             dim.height);
+   }
  }
@@@ -64,13 -64,11 +64,13 @@@ import jalview.datamodel.DBRefEntry
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.MappedFeatures;
  import jalview.datamodel.PDBEntry;
 +import jalview.datamodel.ResidueCount;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.gui.ColourMenuHelper.ColourChangeListener;
  import jalview.gui.JalviewColourChooser.ColourChooserListener;
 +import jalview.io.CountReader;
  import jalview.io.FileFormatI;
  import jalview.io.FileFormats;
  import jalview.io.FormatAdapter;
@@@ -89,9 -87,6 +89,9 @@@ import jalview.util.StringUtils
  import jalview.util.UrlLink;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  
 +import java.io.IOException;
 +import java.net.MalformedURLException;
 +
  /**
   * The popup menu that is displayed on right-click on a sequence id, or in the
   * sequence alignment.
@@@ -306,7 -301,7 +306,7 @@@ public class PopupMenu extends JPopupMe
        jalview.util.BrowserLauncher.openURL(url);
      } catch (Exception ex)
      {
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager.getString("label.web_browser_not_found_unix"),
                MessageManager.getString("label.web_browser_not_found"),
                JvOptionPane.WARNING_MESSAGE);
          }
        }
  
 +      if (seq.hasHMMProfile())
 +      {
 +        menuItem = new JMenuItem(MessageManager
 +                .getString("action.add_background_frequencies"));
 +        menuItem.addActionListener(new ActionListener()
 +        {
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +            try
 +            {
 +              ResidueCount counts = CountReader.getBackgroundFrequencies(ap,
 +                      seq);
 +              if (counts != null)
 +              {
 +                seq.getHMM().setBackgroundFrequencies(counts);
 +                ap.alignFrame.buildColourMenu();
 +              }
 +            } catch (MalformedURLException e1)
 +            {
 +              e1.printStackTrace();
 +            } catch (IOException e1)
 +            {
 +              e1.printStackTrace();
 +            }
 +          }
 +        });
 +        add(menuItem);
 +      }
 +
        menuItem = new JMenuItem(
                MessageManager.getString("action.hide_sequences"));
        menuItem.addActionListener(new ActionListener()
          buildGroupURLMenu(sg, groupLinks);
        }
        // Add a 'show all structures' for the current selection
 -      Hashtable<String, PDBEntry> pdbe = new Hashtable<>(), reppdb = new Hashtable<>();
 +      Hashtable<String, PDBEntry> pdbe = new Hashtable<>();
 +      Hashtable<String, PDBEntry> reppdb = new Hashtable<>();
  
        SequenceI sqass = null;
        for (SequenceI sq : alignPanel.av.getSequenceSelection())
        menuItem.setEnabled(true);
        for (String calcId : tipEntries.keySet())
        {
-         tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
+         tooltip.append("<br>" + calcId + "/" + tipEntries.get(calcId));
        }
        String tooltipText = JvSwingUtils.wrapTooltip(true,
                tooltip.toString());
                    ap.paintAlignment(false, false);
                  }
                  sequence.setDescription(dialog.getDescription());
-                 ap.av.firePropertyChange("alignment", null,
-                         ap.av.getAlignment().getSequences());
+                 ap.av.notifyAlignment();
                }
              });
    }
          refresh();
        }
      };
-     JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
+     JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(),
              title, Color.BLUE, listener);
    }
  
                startEnd, caseChange);
  
        ap.alignFrame.addHistoryItem(caseCommand);
+       ap.av.notifyAlignment();
  
-       ap.av.firePropertyChange("alignment", null,
-               ap.av.getAlignment().getSequences());
  
      }
    }
                            sg.getStartRes(), sg.getEndRes() + 1,
                            ap.av.getAlignment());
                    ap.alignFrame.addHistoryItem(editCommand);
-                   ap.av.firePropertyChange("alignment", null,
-                           ap.av.getAlignment().getSequences());
+                   ap.av.notifyAlignment();
                  }
                });
      }
   */
  package jalview.gui;
  
- import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
- import jalview.bin.Cache;
- import jalview.gui.Help.HelpId;
- import jalview.gui.StructureViewer.ViewerType;
- import jalview.hmmer.HmmerCommand;
- import jalview.io.BackupFiles;
- import jalview.io.BackupFilesPresetEntry;
- import jalview.io.FileFormatI;
- import jalview.io.JalviewFileChooser;
- import jalview.io.JalviewFileView;
- import jalview.jbgui.GPreferences;
- import jalview.jbgui.GSequenceLink;
- import jalview.schemes.ColourSchemeI;
- import jalview.schemes.ColourSchemes;
- import jalview.schemes.ResidueColourScheme;
- import jalview.urls.UrlLinkTableModel;
- import jalview.urls.api.UrlProviderFactoryI;
- import jalview.urls.api.UrlProviderI;
- import jalview.urls.desktop.DesktopUrlProviderFactory;
- import jalview.util.FileUtils;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
- import jalview.util.UrlConstants;
- import jalview.ws.sifts.SiftsSettings;
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -52,8 -27,6 +27,8 @@@ import java.awt.Dimension
  import java.awt.Font;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
  import java.awt.event.MouseEvent;
  import java.io.File;
  import java.util.ArrayList;
@@@ -64,7 -37,6 +39,7 @@@ import javax.swing.JComboBox
  import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JPanel;
 +import javax.swing.JTextField;
  import javax.swing.ListSelectionModel;
  import javax.swing.RowFilter;
  import javax.swing.RowSorter;
@@@ -78,7 -50,29 +53,32 @@@ import javax.swing.table.TableColumn
  import javax.swing.table.TableModel;
  import javax.swing.table.TableRowSorter;
  
++import jalview.hmmer.HmmerCommand;
++import jalview.util.FileUtils;
++
  import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+ import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+ import jalview.bin.Cache;
+ import jalview.gui.Help.HelpId;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.io.BackupFiles;
+ import jalview.io.BackupFilesPresetEntry;
+ import jalview.io.FileFormatI;
+ import jalview.io.JalviewFileChooser;
+ import jalview.io.JalviewFileView;
+ import jalview.jbgui.GPreferences;
+ import jalview.jbgui.GSequenceLink;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemes;
+ import jalview.schemes.ResidueColourScheme;
+ import jalview.urls.UrlLinkTableModel;
+ import jalview.urls.api.UrlProviderFactoryI;
+ import jalview.urls.api.UrlProviderI;
+ import jalview.urls.desktop.DesktopUrlProviderFactory;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
+ import jalview.util.UrlConstants;
+ import jalview.ws.sifts.SiftsSettings;
  
  /**
   * DOCUMENT ME!
   */
  public class Preferences extends GPreferences
  {
 +  // suggested list delimiter character
 +  public static final String COMMA = ",";
 +
 +  public static final String HMMSEARCH_SEQCOUNT = "HMMSEARCH_SEQCOUNT";
 +
 +  public static final String HMMINFO_GLOBAL_BACKGROUND = "HMMINFO_GLOBAL_BACKGROUND";
 +
 +  public static final String HMMALIGN_TRIM_TERMINI = "HMMALIGN_TRIM_TERMINI";
+   
+   public static final String ADD_SS_ANN = "ADD_SS_ANN";
  
-   public static final String ENABLE_SPLIT_FRAME = "ENABLE_SPLIT_FRAME";
+   public static final String ADD_TEMPFACT_ANN = "ADD_TEMPFACT_ANN";
  
-   public static final String SCALE_PROTEIN_TO_CDNA = "SCALE_PROTEIN_TO_CDNA";
+   public static final String ALLOW_UNPUBLISHED_PDB_QUERYING = "ALLOW_UNPUBLISHED_PDB_QUERYING";
+   public static final String ANNOTATIONCOLOUR_MAX = "ANNOTATIONCOLOUR_MAX";
+   public static final String ANNOTATIONCOLOUR_MIN = "ANNOTATIONCOLOUR_MIN";
+   public static final String ANTI_ALIAS = "ANTI_ALIAS";
+   public static final String AUTO_CALC_CONSENSUS = "AUTO_CALC_CONSENSUS";
+   public static final String AUTOASSOCIATE_PDBANDSEQS = "AUTOASSOCIATE_PDBANDSEQS";
+   public static final String BLOSUM62_PCA_FOR_NUCLEOTIDE = "BLOSUM62_PCA_FOR_NUCLEOTIDE";
+   public static final String CENTRE_COLUMN_LABELS = "CENTRE_COLUMN_LABELS";
+   public static final String CHIMERA_PATH = "CHIMERA_PATH";
+   public static final String DBREFFETCH_USEPICR = "DBREFFETCH_USEPICR";
  
    public static final String DEFAULT_COLOUR = "DEFAULT_COLOUR";
  
+   public static final String DEFAULT_COLOUR_NUC = "DEFAULT_COLOUR_NUC";
    public static final String DEFAULT_COLOUR_PROT = "DEFAULT_COLOUR_PROT";
  
-   public static final String DEFAULT_COLOUR_NUC = "DEFAULT_COLOUR_NUC";
+   public static final String ENABLE_SPLIT_FRAME = "ENABLE_SPLIT_FRAME";
  
-   public static final String ADD_TEMPFACT_ANN = "ADD_TEMPFACT_ANN";
+   public static final String FIGURE_AUTOIDWIDTH = "FIGURE_AUTOIDWIDTH";
  
-   public static final String ADD_SS_ANN = "ADD_SS_ANN";
+   public static final String FIGURE_FIXEDIDWIDTH = "FIGURE_FIXEDIDWIDTH";
  
-   public static final String USE_RNAVIEW = "USE_RNAVIEW";
+   public static final String FOLLOW_SELECTIONS = "FOLLOW_SELECTIONS";
  
-   public static final String STRUCT_FROM_PDB = "STRUCT_FROM_PDB";
+   public static final String FONT_NAME = "FONT_NAME";
  
-   public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
+   public static final String FONT_SIZE = "FONT_SIZE";
  
-   public static final String CHIMERA_PATH = "CHIMERA_PATH";
+   public static final String FONT_STYLE = "FONT_STYLE";
 +  
 +  public static final String HMMER_PATH = "HMMER_PATH";
 +
 +  public static final String CYGWIN_PATH = "CYGWIN_PATH";
 +
 +  public static final String HMMSEARCH_DBS = "HMMSEARCH_DBS";
  
-   public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
+   public static final String GAP_COLOUR = "GAP_COLOUR";
+   public static final String GAP_SYMBOL = "GAP_SYMBOL";
+   public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
+   public static final String HIDE_INTRONS = "HIDE_INTRONS";
+   public static final String ID_ITALICS = "ID_ITALICS";
+   public static final String ID_ORG_HOSTURL = "ID_ORG_HOSTURL";
+   public static final String MAP_WITH_SIFTS = "MAP_WITH_SIFTS";
+   public static final String NOQUESTIONNAIRES = "NOQUESTIONNAIRES";
+   public static final String NORMALISE_CONSENSUS_LOGO = "NORMALISE_CONSENSUS_LOGO";
+   public static final String NORMALISE_LOGO = "NORMALISE_LOGO";
+   public static final String PAD_GAPS = "PAD_GAPS";
+   public static final String PDB_DOWNLOAD_FORMAT = "PDB_DOWNLOAD_FORMAT";
+   public static final String QUESTIONNAIRE = "QUESTIONNAIRE";
+   public static final String RELAXEDSEQIDMATCHING = "RELAXEDSEQIDMATCHING";
+   public static final String RIGHT_ALIGN_IDS = "RIGHT_ALIGN_IDS";
+   public static final String SCALE_PROTEIN_TO_CDNA = "SCALE_PROTEIN_TO_CDNA";
+   public static final String SHOW_ANNOTATIONS = "SHOW_ANNOTATIONS";
  
    public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE";
  
+   public static final String SHOW_CONSENSUS = "SHOW_CONSENSUS";
+   public static final String SHOW_CONSENSUS_HISTOGRAM = "SHOW_CONSENSUS_HISTOGRAM";
+   public static final String SHOW_CONSENSUS_LOGO = "SHOW_CONSENSUS_LOGO";
+   public static final String SHOW_CONSERVATION = "SHOW_CONSERVATION";
+   public static final String SHOW_DBREFS_TOOLTIP = "SHOW_DBREFS_TOOLTIP";
+   public static final String SHOW_GROUP_CONSENSUS = "SHOW_GROUP_CONSENSUS";
+   public static final String SHOW_GROUP_CONSERVATION = "SHOW_GROUP_CONSERVATION";
+   public static final String SHOW_JVSUFFIX = "SHOW_JVSUFFIX";
+   public static final String SHOW_NPFEATS_TOOLTIP = "SHOW_NPFEATS_TOOLTIP";
    public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY";
  
    public static final String SHOW_OV_HIDDEN_AT_START = "SHOW_OV_HIDDEN_AT_START";
  
+   public static final String SHOW_OVERVIEW = "SHOW_OVERVIEW";
+   public static final String SHOW_QUALITY = "SHOW_QUALITY";
+   public static final String SHOW_UNCONSERVED = "SHOW_UNCONSERVED";
+   public static final String SORT_ALIGNMENT = "SORT_ALIGNMENT";
+   public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
+   public static final String SORT_BY_TREE = "SORT_BY_TREE";
+   public static final String STRUCT_FROM_PDB = "STRUCT_FROM_PDB";
+   public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
+   public static final String STRUCTURE_DIMENSIONS = "STRUCTURE_DIMENSIONS";
+   public static final String UNIPROT_DOMAIN = "UNIPROT_DOMAIN";
+   public static final String USE_FULL_SO = "USE_FULL_SO";
    public static final String USE_LEGACY_GAP = "USE_LEGACY_GAP";
  
-   public static final String GAP_COLOUR = "GAP_COLOUR";
+   public static final String USE_RNAVIEW = "USE_RNAVIEW";
+   public static final String USER_DEFINED_COLOURS = "USER_DEFINED_COLOURS";
+   public static final String WRAP_ALIGNMENT = "WRAP_ALIGNMENT";
  
-   public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
  
    private static final int MIN_FONT_SIZE = 1;
  
  
    private WsPreferences wsPrefs;
  
 +  private SlivkaPreferences slivkaPrefs;
 +
    private OptionsParam promptEachTimeOpt = new OptionsParam(
            MessageManager.getString("label.prompt_each_time"),
            "Prompt each time");
      {
        wsPrefs = new WsPreferences();
        wsTab.add(wsPrefs, BorderLayout.CENTER);
 +      slivkaPrefs = new SlivkaPreferences();
 +      slivkaTab.add(slivkaPrefs, BorderLayout.CENTER);
      }
      int width = 500, height = 450;
      if (Platform.isAMacAndNotJS())
      frame.setMinimumSize(new Dimension(width, height));
  
      /*
 +     * Set HMMER tab defaults
 +     */
 +    hmmrTrimTermini.setSelected(Cache.getDefault(HMMALIGN_TRIM_TERMINI, false));
 +    if (Cache.getDefault(HMMINFO_GLOBAL_BACKGROUND, false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty(HMMSEARCH_SEQCOUNT));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +    hmmerPath.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        validateHmmerPath();
 +      }
 +    });
 +    hmmerPath.addFocusListener(new FocusAdapter()
 +    {
 +      @Override
 +      public void focusLost(FocusEvent e)
 +      {
 +        validateHmmerPath();
 +      }
 +    });
 +    if (cygwinPath != null)
 +    {
 +      String path = Cache.getProperty(CYGWIN_PATH);
 +      if (path == null)
 +      {
 +        path = FileUtils.getPathTo("bash");
 +      }
 +      cygwinPath.setText(path);
 +      cygwinPath.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          validateCygwinPath();
 +        }
 +      });
 +      cygwinPath.addFocusListener(new FocusAdapter()
 +      {
 +        @Override
 +        public void focusLost(FocusEvent e)
 +        {
 +          validateCygwinPath();
 +        }
 +      });
 +    }
 +
 +    /*
       * Set Visual tab defaults
       */
      seqLimit.setSelected(Cache.getDefault("SHOW_JVSUFFIX", true));
              Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM", true));
      showConsensLogo
              .setSelected(Cache.getDefault("SHOW_CONSENSUS_LOGO", false));
 +    showInformationHistogram.setSelected(
 +            Cache.getDefault("SHOW_INFORMATION_HISTOGRAM", true));
 +    showHMMLogo.setSelected(Cache.getDefault("SHOW_HMM_LOGO", false));
      showNpTooltip
              .setSelected(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
      showDbRefTooltip
      addSecondaryStructure.setEnabled(structSelected);
      addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, false));
      addTempFactor.setEnabled(structSelected);
-     structViewer.setSelectedItem(
+     if (!Platform.isJS())
+     {
+       structViewer.setSelectedItem(
              Cache.getDefault(STRUCTURE_DISPLAY, ViewerType.JMOL.name()));
+     }
      chimeraPath.setText(Cache.getDefault(CHIMERA_PATH, ""));
      chimeraPath.addActionListener(new ActionListener()
      {
      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(
      /*
       * Save Visual settings
       */
-     Cache.applicationProperties.setProperty("SHOW_JVSUFFIX",
+     Cache.setPropertyNoSave("SHOW_JVSUFFIX",
              Boolean.toString(seqLimit.isSelected()));
-     Cache.applicationProperties.setProperty("RIGHT_ALIGN_IDS",
+     Cache.setPropertyNoSave("RIGHT_ALIGN_IDS",
              Boolean.toString(rightAlign.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_FULLSCREEN",
+     Cache.setPropertyNoSave("SHOW_FULLSCREEN",
              Boolean.toString(fullScreen.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_OVERVIEW",
+     Cache.setPropertyNoSave("SHOW_OVERVIEW",
              Boolean.toString(openoverv.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+     Cache.setPropertyNoSave("SHOW_ANNOTATIONS",
              Boolean.toString(annotations.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
+     Cache.setPropertyNoSave("SHOW_CONSERVATION",
              Boolean.toString(conservation.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_QUALITY",
+     Cache.setPropertyNoSave("SHOW_QUALITY",
              Boolean.toString(quality.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+     Cache.setPropertyNoSave("SHOW_IDENTITY",
              Boolean.toString(identity.isSelected()));
  
-     Cache.applicationProperties.setProperty("GAP_SYMBOL",
+     Cache.setPropertyNoSave("GAP_SYMBOL",
              gapSymbolCB.getSelectedItem().toString());
  
-     Cache.applicationProperties.setProperty("FONT_NAME",
+     Cache.setPropertyNoSave("FONT_NAME",
              fontNameCB.getSelectedItem().toString());
-     Cache.applicationProperties.setProperty("FONT_STYLE",
+     Cache.setPropertyNoSave("FONT_STYLE",
              fontStyleCB.getSelectedItem().toString());
-     Cache.applicationProperties.setProperty("FONT_SIZE",
+     Cache.setPropertyNoSave("FONT_SIZE",
              fontSizeCB.getSelectedItem().toString());
  
-     Cache.applicationProperties.setProperty("ID_ITALICS",
+     Cache.setPropertyNoSave("ID_ITALICS",
              Boolean.toString(idItalics.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_UNCONSERVED",
+     Cache.setPropertyNoSave("SHOW_UNCONSERVED",
              Boolean.toString(showUnconserved.isSelected()));
-     Cache.applicationProperties.setProperty(SHOW_OCCUPANCY,
+     Cache.setPropertyNoSave(SHOW_OCCUPANCY,
              Boolean.toString(showOccupancy.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSENSUS",
+     Cache.setPropertyNoSave("SHOW_GROUP_CONSENSUS",
              Boolean.toString(showGroupConsensus.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSERVATION",
+     Cache.setPropertyNoSave("SHOW_GROUP_CONSERVATION",
              Boolean.toString(showGroupConservation.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_CONSENSUS_HISTOGRAM",
+     Cache.setPropertyNoSave("SHOW_CONSENSUS_HISTOGRAM",
              Boolean.toString(showConsensHistogram.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_CONSENSUS_LOGO",
+     Cache.setPropertyNoSave("SHOW_CONSENSUS_LOGO",
              Boolean.toString(showConsensLogo.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_INFORMATION_HISTOGRAM",
++    Cache.setPropertyNoSave("SHOW_INFORMATION_HISTOGRAM",
 +            Boolean.toString(showConsensHistogram.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_HMM_LOGO",
++    Cache.setPropertyNoSave("SHOW_HMM_LOGO",
 +            Boolean.toString(showHMMLogo.isSelected()));
-     Cache.applicationProperties.setProperty("ANTI_ALIAS",
+     Cache.setPropertyNoSave("ANTI_ALIAS",
              Boolean.toString(smoothFont.isSelected()));
-     Cache.applicationProperties.setProperty(SCALE_PROTEIN_TO_CDNA,
+     Cache.setPropertyNoSave(SCALE_PROTEIN_TO_CDNA,
              Boolean.toString(scaleProteinToCdna.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_NPFEATS_TOOLTIP",
+     Cache.setPropertyNoSave("SHOW_NPFEATS_TOOLTIP",
              Boolean.toString(showNpTooltip.isSelected()));
-     Cache.applicationProperties.setProperty("SHOW_DBREFS_TOOLTIP",
+     Cache.setPropertyNoSave("SHOW_DBREFS_TOOLTIP",
              Boolean.toString(showDbRefTooltip.isSelected()));
  
-     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT",
+     Cache.setPropertyNoSave("WRAP_ALIGNMENT",
              Boolean.toString(wrap.isSelected()));
  
-     Cache.applicationProperties.setProperty("STARTUP_FILE",
+     Cache.setPropertyNoSave("STARTUP_FILE",
              startupFileTextfield.getText());
-     Cache.applicationProperties.setProperty("SHOW_STARTUP_FILE",
+     Cache.setPropertyNoSave("SHOW_STARTUP_FILE",
              Boolean.toString(startupCheckbox.isSelected()));
  
-     Cache.applicationProperties.setProperty("SORT_ALIGNMENT",
+     Cache.setPropertyNoSave("SORT_ALIGNMENT",
              sortby.getSelectedItem().toString());
  
      // convert description of sort order to enum name for save
              .forDescription(sortAnnBy.getSelectedItem().toString());
      if (annSortOrder != null)
      {
-       Cache.applicationProperties.setProperty(SORT_ANNOTATIONS,
+       Cache.setPropertyNoSave(SORT_ANNOTATIONS,
                annSortOrder.name());
      }
  
      final boolean showAutocalcFirst = sortAutocalc.getSelectedIndex() == 0;
-     Cache.applicationProperties.setProperty(SHOW_AUTOCALC_ABOVE,
+     Cache.setPropertyNoSave(SHOW_AUTOCALC_ABOVE,
              Boolean.valueOf(showAutocalcFirst).toString());
  
      /*
       * Save Colours settings
       */
-     Cache.applicationProperties.setProperty(DEFAULT_COLOUR_PROT,
+     Cache.setPropertyNoSave(DEFAULT_COLOUR_PROT,
              protColour.getSelectedItem().toString());
-     Cache.applicationProperties.setProperty(DEFAULT_COLOUR_NUC,
+     Cache.setPropertyNoSave(DEFAULT_COLOUR_NUC,
              nucColour.getSelectedItem().toString());
-     Cache.setColourProperty("ANNOTATIONCOLOUR_MIN",
+     Cache.setColourPropertyNoSave("ANNOTATIONCOLOUR_MIN",
              minColour.getBackground());
-     Cache.setColourProperty("ANNOTATIONCOLOUR_MAX",
+     Cache.setColourPropertyNoSave("ANNOTATIONCOLOUR_MAX",
              maxColour.getBackground());
  
      /*
 +     * Save HMMER settings
 +     */
-     Cache.applicationProperties.setProperty(HMMALIGN_TRIM_TERMINI,
++    Cache.setPropertyNoSave(HMMALIGN_TRIM_TERMINI,
 +            Boolean.toString(hmmrTrimTermini.isSelected()));
-     Cache.applicationProperties.setProperty(HMMINFO_GLOBAL_BACKGROUND,
++    Cache.setPropertyNoSave(HMMINFO_GLOBAL_BACKGROUND,
 +            Boolean.toString(hmmerBackgroundUniprot.isSelected()));
-     Cache.applicationProperties.setProperty(HMMSEARCH_SEQCOUNT,
++    Cache.setPropertyNoSave(HMMSEARCH_SEQCOUNT,
 +            hmmerSequenceCount.getText());
 +    Cache.setOrRemove(HMMER_PATH, hmmerPath.getText());
 +    if (cygwinPath != null)
 +    {
 +      Cache.setOrRemove(CYGWIN_PATH, cygwinPath.getText());
 +    }
 +    AlignFrame[] frames = Desktop.getAlignFrames();
 +    if (frames != null && frames.length > 0)
 +    {
 +      for (AlignFrame f : frames)
 +      {
 +        f.updateHMMERStatus();
 +      }
 +    }
 +    
 +    hmmrTrimTermini.setSelected(Cache.getDefault(HMMALIGN_TRIM_TERMINI, false));
 +    if (Cache.getDefault(HMMINFO_GLOBAL_BACKGROUND, false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty(HMMSEARCH_SEQCOUNT));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +
 +    /*
       * Save Overview settings
       */
-     Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground());
-     Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground());
-     Cache.applicationProperties.setProperty(USE_LEGACY_GAP,
+     Cache.setColourPropertyNoSave(GAP_COLOUR, gapColour.getBackground());
+     Cache.setColourPropertyNoSave(HIDDEN_COLOUR, hiddenColour.getBackground());
+     Cache.setPropertyNoSave(USE_LEGACY_GAP,
              Boolean.toString(useLegacyGap.isSelected()));
-     Cache.applicationProperties.setProperty(SHOW_OV_HIDDEN_AT_START,
+     Cache.setPropertyNoSave(SHOW_OV_HIDDEN_AT_START,
              Boolean.toString(showHiddenAtStart.isSelected()));
  
      /*
       * Save Structure settings
       */
-     Cache.applicationProperties.setProperty(ADD_TEMPFACT_ANN,
+     Cache.setPropertyNoSave(ADD_TEMPFACT_ANN,
              Boolean.toString(addTempFactor.isSelected()));
-     Cache.applicationProperties.setProperty(ADD_SS_ANN,
+     Cache.setPropertyNoSave(ADD_SS_ANN,
              Boolean.toString(addSecondaryStructure.isSelected()));
-     Cache.applicationProperties.setProperty(USE_RNAVIEW,
+     Cache.setPropertyNoSave(USE_RNAVIEW,
              Boolean.toString(useRnaView.isSelected()));
-     Cache.applicationProperties.setProperty(STRUCT_FROM_PDB,
+     Cache.setPropertyNoSave(STRUCT_FROM_PDB,
              Boolean.toString(structFromPdb.isSelected()));
-     Cache.applicationProperties.setProperty(STRUCTURE_DISPLAY,
-             structViewer.getSelectedItem().toString());
+     if (!Platform.isJS())
+     {
+       Cache.setPropertyNoSave(STRUCTURE_DISPLAY,
+               structViewer.getSelectedItem().toString());
+     }
      Cache.setOrRemove(CHIMERA_PATH, chimeraPath.getText());
-     Cache.applicationProperties.setProperty("MAP_WITH_SIFTS",
+     Cache.setPropertyNoSave("MAP_WITH_SIFTS",
              Boolean.toString(siftsMapping.isSelected()));
      SiftsSettings.setMapWithSifts(siftsMapping.isSelected());
  
      /*
       * Save Output settings
       */
-     Cache.applicationProperties.setProperty("EPS_RENDERING",
+     Cache.setPropertyNoSave("EPS_RENDERING",
              ((OptionsParam) epsRendering.getSelectedItem()).getCode());
-     Cache.applicationProperties.setProperty("HTML_RENDERING",
+     Cache.setPropertyNoSave("HTML_RENDERING",
              ((OptionsParam) htmlRendering.getSelectedItem()).getCode());
-     Cache.applicationProperties.setProperty("SVG_RENDERING",
+     Cache.setPropertyNoSave("SVG_RENDERING",
              ((OptionsParam) svgRendering.getSelectedItem()).getCode());
  
      /*
      String menuLinks = sequenceUrlLinks.writeUrlsAsString(true);
      if (menuLinks.isEmpty())
      {
-       Cache.applicationProperties.remove("SEQUENCE_LINKS");
+       Cache.removePropertyNoSave("SEQUENCE_LINKS");
      }
      else
      {
-       Cache.applicationProperties.setProperty("SEQUENCE_LINKS",
+       Cache.setPropertyNoSave("SEQUENCE_LINKS",
                menuLinks.toString());
      }
  
      String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false);
      if (nonMenuLinks.isEmpty())
      {
-       Cache.applicationProperties.remove("STORED_LINKS");
+       Cache.removePropertyNoSave("STORED_LINKS");
      }
      else
      {
-       Cache.applicationProperties.setProperty("STORED_LINKS",
+       Cache.setPropertyNoSave("STORED_LINKS",
                nonMenuLinks.toString());
      }
  
-     Cache.applicationProperties.setProperty("DEFAULT_URL",
+     Cache.setPropertyNoSave("DEFAULT_URL",
              sequenceUrlLinks.getPrimaryUrlId());
  
-     Cache.applicationProperties.setProperty("USE_PROXY",
+     Cache.setPropertyNoSave("USE_PROXY",
              Boolean.toString(useProxy.isSelected()));
  
      Cache.setOrRemove("PROXY_SERVER", proxyServerTB.getText());
      /*
       * Save Output settings
       */
-     Cache.applicationProperties.setProperty("BLC_JVSUFFIX",
+     Cache.setPropertyNoSave("BLC_JVSUFFIX",
              Boolean.toString(blcjv.isSelected()));
-     Cache.applicationProperties.setProperty("CLUSTAL_JVSUFFIX",
+     Cache.setPropertyNoSave("CLUSTAL_JVSUFFIX",
              Boolean.toString(clustaljv.isSelected()));
-     Cache.applicationProperties.setProperty("FASTA_JVSUFFIX",
+     Cache.setPropertyNoSave("FASTA_JVSUFFIX",
              Boolean.toString(fastajv.isSelected()));
-     Cache.applicationProperties.setProperty("MSF_JVSUFFIX",
+     Cache.setPropertyNoSave("MSF_JVSUFFIX",
              Boolean.toString(msfjv.isSelected()));
-     Cache.applicationProperties.setProperty("PFAM_JVSUFFIX",
+     Cache.setPropertyNoSave("PFAM_JVSUFFIX",
              Boolean.toString(pfamjv.isSelected()));
-     Cache.applicationProperties.setProperty("PILEUP_JVSUFFIX",
+     Cache.setPropertyNoSave("PILEUP_JVSUFFIX",
              Boolean.toString(pileupjv.isSelected()));
-     Cache.applicationProperties.setProperty("PIR_JVSUFFIX",
+     Cache.setPropertyNoSave("PIR_JVSUFFIX",
              Boolean.toString(pirjv.isSelected()));
-     Cache.applicationProperties.setProperty("PIR_MODELLER",
+     Cache.setPropertyNoSave("PIR_MODELLER",
              Boolean.toString(modellerOutput.isSelected()));
-     Cache.applicationProperties.setProperty("EXPORT_EMBBED_BIOJSON",
+     Cache.setPropertyNoSave("EXPORT_EMBBED_BIOJSON",
              Boolean.toString(embbedBioJSON.isSelected()));
      jalview.io.PIRFile.useModellerOutput = modellerOutput.isSelected();
  
-     Cache.applicationProperties.setProperty("FIGURE_AUTOIDWIDTH",
+     Cache.setPropertyNoSave("FIGURE_AUTOIDWIDTH",
              Boolean.toString(autoIdWidth.isSelected()));
      userIdWidth_actionPerformed();
-     Cache.applicationProperties.setProperty("FIGURE_FIXEDIDWIDTH",
+     Cache.setPropertyNoSave("FIGURE_FIXEDIDWIDTH",
              userIdWidth.getText());
  
      /*
       * Save Editing settings
       */
-     Cache.applicationProperties.setProperty("AUTO_CALC_CONSENSUS",
+     Cache.setPropertyNoSave("AUTO_CALC_CONSENSUS",
              Boolean.toString(autoCalculateConsCheck.isSelected()));
-     Cache.applicationProperties.setProperty("SORT_BY_TREE",
+     Cache.setPropertyNoSave("SORT_BY_TREE",
              Boolean.toString(sortByTree.isSelected()));
-     Cache.applicationProperties.setProperty("PAD_GAPS",
+     Cache.setPropertyNoSave("PAD_GAPS",
              Boolean.toString(padGaps.isSelected()));
  
      if (!Platform.isJS())
      /*
       * Save Backups settings
       */
-     Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
+     Cache.setPropertyNoSave(BackupFiles.ENABLED,
              Boolean.toString(enableBackupFiles.isSelected()));
      int preset = getComboIntStringKey(backupfilesPresetsCombo);
-     Cache.applicationProperties.setProperty(BackupFiles.NS + "_PRESET", Integer.toString(preset));
+     Cache.setPropertyNoSave(BackupFiles.NS + "_PRESET", Integer.toString(preset));
  
      if (preset == BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM)
      {
        BackupFilesPresetEntry customBFPE = getBackupfilesCurrentEntry();
        BackupFilesPresetEntry.backupfilesPresetEntriesValues.put(
                BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM, customBFPE);
-       Cache.applicationProperties
-               .setProperty(BackupFilesPresetEntry.CUSTOMCONFIG,
+       Cache.setPropertyNoSave(BackupFilesPresetEntry.CUSTOMCONFIG,
                        customBFPE.toString());
      }
  
      BackupFilesPresetEntry savedBFPE = BackupFilesPresetEntry.backupfilesPresetEntriesValues
              .get(preset);
-     Cache.applicationProperties.setProperty(
+     Cache.setPropertyNoSave(
              BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString());
  
      Cache.saveProperties();
-     Desktop.instance.doConfigureStructurePrefs();
+     Desktop.getInstance().doConfigureStructurePrefs();
      try
      {
        frame.setClosed(true);
      }
    }
  
-   /**
+   public static void setAppletDefaults()
+   {
+     // http://www.jalview.org/old/v2_8/examples/appletParameters.html
+     // showConservation true or false Default is true.
+     // showQuality true or false Default is true.
+     // showConsensus true or false Default is true.
+     // showFeatureSettings true or false Shows the feature settings window when
+     // startin
+     // showTreeBootstraps true or false (default is true) show or hide branch
+     // bootstraps
+     // showTreeDistances true or false (default is true) show or hide branch
+     // lengths
+     // showUnlinkedTreeNodes true or false (default is false) indicate if
+     // unassociated nodes should be highlighted in the tree view
+     // showUnconserved true of false (default is false) When true, only gaps and
+     // symbols different to the consensus sequence ions of the alignment
+     // showGroupConsensus true of false (default is false) When true, shows
+     // consensus annotation row for any groups on the alignment. (since 2.7)
+     // showGroupConservation true of false (default is false) When true, shows
+     // amino-acid property conservation annotation row for any groups on the
+     // showConsensusHistogram true of false (default is true) When true, shows
+     // the percentage occurence of the consensus symbol for each column as a
+     // showSequenceLogo true of false (default is false) When true, shows a
+     // sequence logo above the consensus sequence (overlaid above the Consensus
+     Cache.setPropertyNoSave(SHOW_CONSERVATION, "true");
+     Cache.setPropertyNoSave(SHOW_QUALITY, "false");
+     Cache.setPropertyNoSave(SHOW_CONSENSUS, "true");
+     Cache.setPropertyNoSave(SHOW_UNCONSERVED, "false");
+     Cache.setPropertyNoSave(SHOW_GROUP_CONSERVATION, "false");
+     Cache.setPropertyNoSave(SHOW_GROUP_CONSENSUS, "false");
+     // TODO -- just a start here
+   }
+  /**
     * Do any necessary validation before saving settings. Return focus to the
     * first tab which fails validation.
     * 
        FileFormatI format = chooser.getSelectedFormat();
        if (format != null)
        {
-         Cache.applicationProperties.setProperty("DEFAULT_FILE_FORMAT",
+         Cache.setPropertyNoSave("DEFAULT_FILE_FORMAT",
                  format.getName());
        }
        startupFileTextfield
              && (identity.isSelected() || showGroupConsensus.isSelected()));
      showConsensLogo.setEnabled(annotations.isSelected()
              && (identity.isSelected() || showGroupConsensus.isSelected()));
 +    showInformationHistogram.setEnabled(annotations.isSelected());
 +    showHMMLogo.setEnabled(annotations.isSelected());
    }
  
    @Override
      boolean valid = false;
      while (!valid)
      {
-       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
+       if (JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(), link,
                MessageManager.getString("label.new_sequence_url_link"),
                JvOptionPane.OK_CANCEL_OPTION, -1,
                null) == JvOptionPane.OK_OPTION)
      boolean valid = false;
      while (!valid)
      {
-       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
+       if (JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(), link,
                MessageManager.getString("label.edit_sequence_url_link"),
                JvOptionPane.OK_CANCEL_OPTION, -1,
                null) == JvOptionPane.OK_OPTION)
      } catch (NumberFormatException x)
      {
        userIdWidth.setText("");
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("warn.user_defined_width_requirements"),
                MessageManager.getString("label.invalid_id_column_width"),
        File f = new File(chimeraPath.getText());
        if (!f.canExecute())
        {
-         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  MessageManager.getString("label.invalid_chimera_path"),
                  MessageManager.getString("label.invalid_name"),
                  JvOptionPane.ERROR_MESSAGE);
      }
      return true;
    }
 +  
 +  /**
 +   * Returns true if the given text field contains a path to a folder that
 +   * contains an executable with the given name, else false (after showing a
 +   * warning dialog). The executable name will be tried with .exe appended if not
 +   * found.
 +   * 
 +   * @param textField
 +   * @param executable
 +   */
 +  protected boolean validateExecutablePath(JTextField textField, String executable)
 +  {
 +    String folder = textField.getText().trim();
 +
 +    if (FileUtils.getExecutable(executable, folder) != null)
 +    {
 +      return true;
 +    }
 +    if (folder.length() > 0)
 +    {
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
++      JvOptionPane.showInternalMessageDialog(Desktop.getInstance(),
 +              MessageManager.formatMessage("label.executable_not_found",
 +                      executable),
 +              MessageManager.getString("label.invalid_folder"),
 +              JvOptionPane.ERROR_MESSAGE);
 +    }
 +    return false;
 +  }
 +
 +  /**
 +   * Checks if a file can be executed
 +   * 
 +   * @param path
 +   *          the path to the file
 +   * @return
 +   */
 +  public boolean canExecute(String path)
 +  {
 +    File file = new File(path);
 +    if (!file.canExecute())
 +    {
 +      file = new File(path + ".exe");
 +      {
 +        if (!file.canExecute())
 +        {
 +          return false;
 +        }
 +      }
 +    }
 +    return true;
 +  }
  
    /**
     * If Chimera is selected, check it can be found on default or user-specified
      if (!found)
      {
        String[] options = { "OK", "Help" };
-       int showHelp = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
+       int showHelp = JvOptionPane.showInternalOptionDialog(Desktop.getDesktopPane(),
                JvSwingUtils.wrapTooltip(true,
                        MessageManager.getString("label.chimera_missing")),
                "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
      }
    }
  
 +  @Override
 +  protected void validateHmmerPath()
 +  {
 +    validateExecutablePath(hmmerPath, HmmerCommand.HMMBUILD);
 +  }
 +
 +  @Override
 +  protected void validateCygwinPath()
 +  {
 +    validateExecutablePath(cygwinPath, "run");
 +  }
 +
    public class OptionsParam
    {
      private String name;
   */
  package jalview.gui;
  
 +import jalview.api.AlignViewportI;
 +import jalview.api.AlignViewControllerGuiI;
 +import jalview.api.FeatureSettingsControllerI;
 +import jalview.api.SplitContainerI;
 +import jalview.controller.FeatureSettingsControllerGuiI;
 +import jalview.datamodel.AlignmentI;
 +import jalview.jbgui.GAlignFrame;
 +import jalview.jbgui.GSplitFrame;
 +import jalview.structure.StructureSelectionManager;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
 +import jalview.viewmodel.AlignmentViewport;
 +
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Dimension;
@@@ -62,6 -49,18 +62,6 @@@ import javax.swing.event.ChangeListener
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  
 -import jalview.api.AlignViewControllerGuiI;
 -import jalview.api.FeatureSettingsControllerI;
 -import jalview.api.SplitContainerI;
 -import jalview.controller.FeatureSettingsControllerGuiI;
 -import jalview.datamodel.AlignmentI;
 -import jalview.jbgui.GAlignFrame;
 -import jalview.jbgui.GSplitFrame;
 -import jalview.structure.StructureSelectionManager;
 -import jalview.util.MessageManager;
 -import jalview.util.Platform;
 -import jalview.viewmodel.AlignmentViewport;
 -
  /**
   * An internal frame on the desktop that hosts a horizontally split view of
   * linked DNA and Protein alignments. Additional views can be created in linked
@@@ -153,7 -152,7 +153,7 @@@ public class SplitFrame extends GSplitF
      // allow about 65 pixels for Desktop decorators on Windows
  
      int newHeight = Math.min(height,
-             Desktop.instance.getHeight() - DESKTOP_DECORATORS_HEIGHT);
+             Desktop.getInstance().getHeight() - DESKTOP_DECORATORS_HEIGHT);
      if (newHeight != height)
      {
        int oldDividerLocation = getDividerLocation();
      // TODO if CommandListener is only ever 1:1 for complementary views,
      // may change broadcast pattern to direct messaging (more efficient)
      final StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
      ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
    }
      topFrame.alignPanel.adjustAnnotationHeight();
      bottomFrame.alignPanel.adjustAnnotationHeight();
  
 -    final AlignViewport topViewport = topFrame.viewport;
 -    final AlignViewport bottomViewport = bottomFrame.viewport;
 +    final AlignViewportI topViewport = topFrame.viewport;
 +    final AlignViewportI bottomViewport = bottomFrame.viewport;
      final AlignmentI topAlignment = topViewport.getAlignment();
      final AlignmentI bottomAlignment = bottomViewport.getAlignment();
      boolean topAnnotations = topViewport.isShowAnnotation();
       * Ctrl-W / Cmd-W - close view or window
       */
      KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      action = new AbstractAction()
      {
        @Override
       * Ctrl-T / Cmd-T open new view
       */
      KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      AbstractAction action = new AbstractAction()
      {
        @Override
      adjustLayout();
  
      final StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      ssm.addCommandListener(newTopPanel.av);
      ssm.addCommandListener(newBottomPanel.av);
    }
     */
    protected void expandViews_actionPerformed()
    {
-     Desktop.instance.explodeViews(this);
+     Desktop.getInstance().explodeViews(this);
    }
  
    /**
     */
    protected void gatherViews_actionPerformed()
    {
-     Desktop.instance.gatherViews(this);
+     Desktop.getInstance().gatherViews(this);
    }
  
    /**
       * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
       */
      KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      AbstractAction action = new AbstractAction()
      {
        @Override
    {
      return featureSettingsUI != null && !featureSettingsUI.isClosed();
    }
 -}
 +}
  
  package jalview.gui;
  
- import jalview.api.structures.JalviewStructureDisplayI;
- import jalview.bin.Cache;
- import jalview.bin.Jalview;
- import jalview.datamodel.DBRefEntry;
- import jalview.datamodel.DBRefSource;
- import jalview.datamodel.PDBEntry;
- import jalview.datamodel.SequenceI;
- import jalview.fts.api.FTSData;
- import jalview.fts.api.FTSDataColumnI;
- import jalview.fts.api.FTSRestClientI;
- import jalview.fts.core.FTSRestRequest;
- import jalview.fts.core.FTSRestResponse;
- import jalview.fts.service.pdb.PDBFTSRestClient;
- import jalview.io.DataSourceType;
- import jalview.jbgui.GStructureChooser;
- import jalview.structure.StructureMapping;
- import jalview.structure.StructureSelectionManager;
- import jalview.util.MessageManager;
- import jalview.ws.DBRefFetcher;
- import jalview.ws.sifts.SiftsSettings;
  import java.awt.event.ItemEvent;
  import java.util.ArrayList;
  import java.util.Collection;
@@@ -59,6 -38,23 +38,23 @@@ import javax.swing.JTable
  import javax.swing.SwingUtilities;
  import javax.swing.table.AbstractTableModel;
  
+ import jalview.api.structures.JalviewStructureDisplayI;
+ import jalview.bin.Cache;
+ import jalview.bin.Jalview;
+ import jalview.datamodel.DBRefEntry;
+ import jalview.datamodel.DBRefSource;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.fts.api.FTSData;
+ import jalview.fts.api.FTSDataColumnI;
+ import jalview.fts.api.FTSRestClientI;
+ import jalview.fts.core.FTSRestRequest;
+ import jalview.fts.core.FTSRestResponse;
+ import jalview.fts.service.pdb.PDBFTSRestClient;
+ import jalview.io.DataSourceType;
+ import jalview.jbgui.GStructureChooser;
+ import jalview.util.MessageManager;
  /**
   * Provides the behaviors for the Structure chooser Panel
   * 
@@@ -69,7 -65,7 +65,7 @@@
  public class StructureChooser extends GStructureChooser
          implements IProgressIndicator
  {
-   private static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
+   static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
  
    private static int MAX_QLENGTH = 7820;
  
@@@ -91,7 -87,7 +87,7 @@@
  
    private boolean cachedPDBExists;
  
-   private static StructureViewer lastTargetedView = null;
+   static StructureViewer lastTargetedView = null;
  
    public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
            AlignmentPanel ap)
     */
    private void discoverStructureViews()
    {
-     if (Desktop.instance != null)
+     if (Desktop.getInstance() != null)
      {
        targetView.removeAllItems();
        if (lastTargetedView != null && !lastTargetedView.isVisible())
          lastTargetedView = null;
        }
        int linkedViewsAt = 0;
-       for (StructureViewerBase view : Desktop.instance
+       for (StructureViewerBase view : Desktop.getInstance()
                .getStructureViewers(null, null))
        {
          StructureViewer viewHandler = (lastTargetedView != null
  
          if (view.isLinkedWith(ap))
          {
-           targetView.insertItemAt(viewHandler,
-                   linkedViewsAt++);
+           targetView.insertItemAt(viewHandler, linkedViewsAt++);
          }
          else
          {
      boolean isUniProtRefsFound = false;
      StringBuilder queryBuilder = new StringBuilder();
      Set<String> seqRefs = new LinkedHashSet<>();
-     
      /*
       * note PDBs as DBRefEntry so they are not duplicated in query
       */
      {
        for (int ib = 0, nb = refs.size(); ib < nb; ib++)
        {
-         DBRefEntry dbRef = refs.get(ib);
+         DBRefEntry dbRef = refs.get(ib);
          if (isValidSeqName(getDBRefId(dbRef))
                  && queryBuilder.length() < MAX_QLENGTH)
          {
    @Override
    protected void pdbFromFile_actionPerformed()
    {
-     // TODO: JAL-3048 not needed for Jalview-JS until JSmol dep and StructureChooser
+     // TODO: JAL-3048 not needed for Jalview-JS until JSmol dep and
+     // StructureChooser
      // works
      jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      if (cachedPDBExist)
      {
        FilterOption cachedOption = new FilterOption(
-               MessageManager.getString("label.cached_structures"),
-               "-", VIEWS_LOCAL_PDB, false);
+               MessageManager.getString("label.cached_structures"), "-",
+               VIEWS_LOCAL_PDB, false);
        cmb_filterOption.addItem(cachedOption);
        cmb_filterOption.setSelectedItem(cachedOption);
      }
      }
      return found;
    }
-   
    /**
     * Handles the 'New View' action
     */
    public void showStructures(boolean waitUntilFinished)
    {
  
-     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
      final int preferredHeight = pnl_filter.getHeight();
  
+     final StructureViewer theViewer = getTargetedStructureViewer();
+     boolean superimpose = chk_superpose.isSelected();
      Runnable viewStruc = new Runnable()
      {
        @Override
  
          if (currentView == VIEWS_FILTER)
          {
-           int pdbIdColIndex = restable.getColumn("PDB Id")
-                   .getModelIndex();
+           int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex();
            int refSeqColIndex = restable.getColumn("Ref Sequence")
                    .getModelIndex();
            int[] selectedRows = restable.getSelectedRows();
            List<SequenceI> selectedSeqsToView = new ArrayList<>();
            for (int row : selectedRows)
            {
-             String pdbIdStr = restable
-                     .getValueAt(row, pdbIdColIndex).toString();
-             SequenceI selectedSeq = (SequenceI) restable
-                     .getValueAt(row, refSeqColIndex);
+             String pdbIdStr = restable.getValueAt(row, pdbIdColIndex)
+                     .toString();
+             SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
+                     refSeqColIndex);
              selectedSeqsToView.add(selectedSeq);
              PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
              if (pdbEntry == null)
  
              if (pdbEntry == null)
              {
-               pdbEntry = new PDBEntry();
-               pdbEntry.setId(pdbIdStr);
-               pdbEntry.setType(PDBEntry.Type.PDB);
+               pdbEntry = new PDBEntry(pdbIdStr, null, "pdb");
                selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
              }
              pdbEntriesToView[count++] = pdbEntry;
            }
            SequenceI[] selectedSeqs = selectedSeqsToView
                    .toArray(new SequenceI[selectedSeqsToView.size()]);
-           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
-                   selectedSeqs);
+           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
+                   selectedSeqs, superimpose, theViewer, progressBar);
          }
          else if (currentView == VIEWS_LOCAL_PDB)
          {
            }
            SequenceI[] selectedSeqs = selectedSeqsToView
                    .toArray(new SequenceI[selectedSeqsToView.size()]);
-           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
-                   selectedSeqs);
+           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
+                   selectedSeqs, superimpose, theViewer, progressBar);
          }
          else if (currentView == VIEWS_ENTER_ID)
          {
            }
  
            PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
-           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
+           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
                    new SequenceI[]
-                   { selectedSequence });
+                   { selectedSequence }, superimpose, theViewer,
+                   progressBar);
          }
          else if (currentView == VIEWS_FROM_FILE)
          {
            {
              selectedSequence = userSelectedSeq;
            }
-           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
-                   .associatePdbWithSeq(selectedPdbFileName,
-                           DataSourceType.FILE, selectedSequence, true,
-                           Desktop.instance);
-           sViewer = launchStructureViewer(
-                   ssm, new PDBEntry[]
-                   { fileEntry }, ap,
+           PDBEntry fileEntry = AssociatePdbFileWithSeq.associatePdbWithSeq(selectedPdbFileName,
+                           DataSourceType.FILE, selectedSequence, true);
+           sViewer = StructureViewer.launchStructureViewer(ap, new PDBEntry[] { fileEntry },
                    new SequenceI[]
-                   { selectedSequence });
+                   { selectedSequence }, superimpose, theViewer,
+                   progressBar);
          }
          SwingUtilities.invokeLater(new Runnable()
          {
     * @param ssm
     * @return
     */
-   StructureViewer getTargetedStructureViewer(
-           StructureSelectionManager ssm)
-   {
-     Object sv = targetView.getSelectedItem();
-     return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
-   }
-   /**
-    * Adds PDB structures to a new or existing structure viewer
-    * 
-    * @param ssm
-    * @param pdbEntriesToView
-    * @param alignPanel
-    * @param sequences
-    * @return
-    */
-   private StructureViewer launchStructureViewer(
-           StructureSelectionManager ssm,
-           final PDBEntry[] pdbEntriesToView,
-           final AlignmentPanel alignPanel, SequenceI[] sequences)
+   StructureViewer getTargetedStructureViewer()
    {
-     long progressId = sequences.hashCode();
-     setProgressBar(MessageManager
-             .getString("status.launching_3d_structure_viewer"), progressId);
-     final StructureViewer theViewer = getTargetedStructureViewer(ssm);
-     boolean superimpose = chk_superpose.isSelected();
-     theViewer.setSuperpose(superimpose);
-     /*
-      * remember user's choice of superimpose or not
-      */
-     Cache.setProperty(AUTOSUPERIMPOSE,
-             Boolean.valueOf(superimpose).toString());
-     setProgressBar(null, progressId);
-     if (SiftsSettings.isMapWithSifts())
-     {
-       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
-       int p = 0;
-       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
-       // real PDB ID. For moment, we can also safely do this if there is already
-       // a known mapping between the PDBEntry and the sequence.
-       for (SequenceI seq : sequences)
-       {
-         PDBEntry pdbe = pdbEntriesToView[p++];
-         if (pdbe != null && pdbe.getFile() != null)
-         {
-           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
-           if (smm != null && smm.length > 0)
-           {
-             for (StructureMapping sm : smm)
-             {
-               if (sm.getSequence() == seq)
-               {
-                 continue;
-               }
-             }
-           }
-         }
-         if (seq.getPrimaryDBRefs().isEmpty())
-         {
-           seqsWithoutSourceDBRef.add(seq);
-           continue;
-         }
-       }
-       if (!seqsWithoutSourceDBRef.isEmpty())
-       {
-         int y = seqsWithoutSourceDBRef.size();
-         setProgressBar(MessageManager.formatMessage(
-                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
-                 y), progressId);
-         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
-                 .toArray(new SequenceI[y]);
-         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
-         dbRefFetcher.fetchDBRefs(true);
-         setProgressBar("Fetch complete.", progressId); // todo i18n
-       }
-     }
-     if (pdbEntriesToView.length > 1)
-     {
-       setProgressBar(MessageManager.getString(
-               "status.fetching_3d_structures_for_selected_entries"),
-               progressId);
-       theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
-     }
-     else
-     {
-       setProgressBar(MessageManager.formatMessage(
-               "status.fetching_3d_structures_for",
-               pdbEntriesToView[0].getId()),progressId);
-       theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
-     }
-     setProgressBar(null, progressId);
-     // remember the last viewer we used...
-     lastTargetedView = theViewer;
-     return theViewer;
+     return (StructureViewer) targetView.getSelectedItem();
    }
  
    /**
              && !discoveredStructuresSet.isEmpty();
    }
  
-   protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes this. 
+   protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes
+                                // this.
    // Doing a search for "1" or "1c" is valuable?
    // Those work but are enormously slow.
  
    protected void txt_search_ActionPerformed()
    {
      String text = txt_search.getText().trim();
-       if (text.length() >= PDB_ID_MIN) 
-     new Thread()
-     {
-       @Override
-       public void run()
+     if (text.length() >= PDB_ID_MIN)
+       new Thread()
        {
-         errorWarning.setLength(0);
-         isValidPBDEntry = false;
-         if (text.length() > 0)
+         @Override
+         public void run()
          {
-           String searchTerm = text.toLowerCase();
-           searchTerm = searchTerm.split(":")[0];
-           // System.out.println(">>>>> search term : " + searchTerm);
-           List<FTSDataColumnI> wantedFields = new ArrayList<>();
-           FTSRestRequest pdbRequest = new FTSRestRequest();
-           pdbRequest.setAllowEmptySeq(false);
-           pdbRequest.setResponseSize(1);
-           pdbRequest.setFieldToSearchBy("(pdb_id:");
-           pdbRequest.setWantedFields(wantedFields);
-           pdbRequest.setSearchTerm(searchTerm + ")");
-           pdbRequest.setAssociatedSequence(selectedSequence);
-           pdbRestClient = PDBFTSRestClient.getInstance();
-           wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
-           FTSRestResponse resultList;
-           try
-           {
-             resultList = pdbRestClient.executeRequest(pdbRequest);
-           } catch (Exception e)
-           {
-             errorWarning.append(e.getMessage());
-             return;
-           } finally
-           {
-             validateSelections();
-           }
-           if (resultList.getSearchSummary() != null
-                   && resultList.getSearchSummary().size() > 0)
+           errorWarning.setLength(0);
+           isValidPBDEntry = false;
+           if (text.length() > 0)
            {
-             isValidPBDEntry = true;
+             String searchTerm = text.toLowerCase();
+             searchTerm = searchTerm.split(":")[0];
+             // System.out.println(">>>>> search term : " + searchTerm);
+             List<FTSDataColumnI> wantedFields = new ArrayList<>();
+             FTSRestRequest pdbRequest = new FTSRestRequest();
+             pdbRequest.setAllowEmptySeq(false);
+             pdbRequest.setResponseSize(1);
+             pdbRequest.setFieldToSearchBy("(pdb_id:");
+             pdbRequest.setWantedFields(wantedFields);
+             pdbRequest.setSearchTerm(searchTerm + ")");
+             pdbRequest.setAssociatedSequence(selectedSequence);
+             pdbRestClient = PDBFTSRestClient.getInstance();
+             wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
+             FTSRestResponse resultList;
+             try
+             {
+               resultList = pdbRestClient.executeRequest(pdbRequest);
+             } catch (Exception e)
+             {
+               errorWarning.append(e.getMessage());
+               return;
+             } finally
+             {
+               validateSelections();
+             }
+             if (resultList.getSearchSummary() != null
+                     && resultList.getSearchSummary().size() > 0)
+             {
+               isValidPBDEntry = true;
+             }
            }
+           validateSelections();
          }
-         validateSelections();
-       }
-     }.start();
+       }.start();
    }
  
    @Override
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(long id, IProgressIndicatorHandler handler)
    {
      return sViewer == null ? null : sViewer.sview;
    }
  }
@@@ -259,6 -259,7 +259,7 @@@ public class WebserviceInfo extends GWe
    public WebserviceInfo(String title, String info, int width, int height,
            boolean makeVisible)
    {
+     // no references
      init(title, info, width, height, makeVisible);
    }
  
    {
      frame = new JInternalFrame();
      frame.setContentPane(this);
-     Desktop.addInternalFrame(frame, title, makeVisible, width, height);
+     Desktop.addInternalFrame(frame, title, makeVisible, width, height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
      frame.setClosable(false);
  
      progressBar = new ProgressBar(statusPanel, statusBar);
        @Override
        public void run()
        {
-         JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
+         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), message,
                  title, JvOptionPane.WARNING_MESSAGE);
  
        }
@@@ -928,12 -929,6 +929,12 @@@ public void hyperlinkUpdate(HyperlinkEv
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
@@@ -23,7 -23,10 +23,7 @@@ package jalview.gui
  import jalview.gui.OptsAndParamsPage.OptionBox;
  import jalview.gui.OptsAndParamsPage.ParamBox;
  import jalview.util.MessageManager;
 -import jalview.ws.jws2.JabaParamStore;
 -import jalview.ws.jws2.JabaPreset;
 -import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.api.UIinfo;
  import jalview.ws.params.ArgumentI;
  import jalview.ws.params.OptionI;
  import jalview.ws.params.ParamDatastoreI;
@@@ -45,21 -48,33 +45,21 @@@ import java.awt.event.HierarchyBoundsLi
  import java.awt.event.HierarchyEvent;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
 -import java.awt.event.WindowEvent;
 -import java.awt.event.WindowListener;
 -import java.net.URL;
  import java.util.Hashtable;
 -import java.util.Iterator;
  import java.util.List;
  import java.util.Vector;
  
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JDialog;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
 -import javax.swing.JSplitPane;
  import javax.swing.JTextArea;
  import javax.swing.border.TitledBorder;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  
 -import compbio.metadata.Argument;
 -import compbio.metadata.Option;
 -import compbio.metadata.Parameter;
 -import compbio.metadata.Preset;
 -import compbio.metadata.PresetManager;
 -import compbio.metadata.RunnerConfig;
  import net.miginfocom.swing.MigLayout;
  
  /**
  public class WsJobParameters extends JPanel implements ItemListener,
          ActionListener, DocumentListener, OptsParametersContainerI
  {
 -  URL linkImageURL = getClass().getResource("/images/link.gif");
 +  private static final int PREFERRED_WIDTH = 540;
  
 -  private static final String SVC_DEF = "Defaults"; // this is the null
 -                                                    // parameter set as shown to
 -                                                    // user
 +  private static final int DEFAULT_HEIGHT = 640;
 +
 +  // the default parameter set shown to the user
 +  private static final String SVC_DEF = "Defaults";
 +
 +  private int maxOptWidth = 200;
 +
 +  // URL linkImageURL = getClass().getResource("/images/link.gif");
 +
 +  // TODO ABSRACT FROM JABAWS CLASSES
  
    /**
     * manager for options and parameters.
     */
 -  OptsAndParamsPage opanp = new OptsAndParamsPage(this);
 +  OptsAndParamsPage opanp;
  
 -  /**
 +  /*
     * panel containing job options
     */
 -  JPanel jobOptions = new JPanel();
 +  JPanel optionsPanel = new JPanel();
  
 -  /**
 +  /*
     * panel containing job parameters
     */
 -  JPanel paramList = new JPanel();
 -
 -  JPanel SetNamePanel = new JPanel();
 -
 -  JPanel setDetails = new JPanel();
 -
 -  JSplitPane settingsPanel = new JSplitPane();
 -
 -  JPanel jobPanel = new JPanel();
 +  JPanel paramsPanel = new JPanel();
  
 -  JScrollPane jobOptionsPane = new JScrollPane();
 +  JPanel setNamePanel = new JPanel();
  
    JButton createpref = new JButton();
  
  
    JButton updatepref = new JButton();
  
 -  JButton startjob = new JButton();
 -
 -  JButton canceljob = new JButton();
 -
 -  JComboBox setName = new JComboBox();
 +  JComboBox<String> setName = new JComboBox<>();
  
    JTextArea setDescr = new JTextArea();
  
    JScrollPane paramPane = new JScrollPane();
  
 -  // ScrollablePanel optsAndparams = new ScrollablePanel();
 -  JPanel optsAndparams = new JPanel();
 -
 -  RunnerConfig serviceOptions;
 -
    ParamDatastoreI paramStore;
  
 -  private int MAX_OPTWIDTH = 200;
 +  // set true when 'Start Job' is clicked
 +  boolean startJob = false;
  
 -  WsJobParameters(Jws2Instance service)
 -  {
 -    this(service, null);
 -  }
 +  JDialog frame = null;
  
 -  public WsJobParameters(Jws2Instance service, WsParamSetI preset)
 -  {
 -    this(null, service, preset, null);
 -  }
 +  UIinfo service;
  
 -  /**
 -   * 
 -   * @param desktop
 -   *          - if null, create new JFrame outside of desktop
 -   * @param service
 -   * @param preset
 +  /*
 +   * list of service presets in the gui
 +   */
 +  Hashtable<String, String> servicePresets = null;
 +
 +  /*
 +   * set if dialog is being set - so handlers will avoid spurious events
     */
 -  public WsJobParameters(JFrame parent, Jws2Instance service,
 -          WsParamSetI preset, List<Argument> jobArgset)
 +  boolean settingDialog = false;
 +
 +  private Hashtable<Object, Object> modifiedElements = new Hashtable<>();
 +
 +  String lastParmSet = null;
 +
 +  public WsJobParameters(ParamDatastoreI store, WsParamSetI preset,
 +          List<ArgumentI> args)
    {
 -    this(parent, null, service, preset, jobArgset);
 +    super();
 +
 +    // parameters dialog in 'compact' format (help as tooltips)
 +    opanp = new OptsAndParamsPage(this, true);
 +    jbInit();
 +    this.paramStore = store;
 +    this.service = null;
 +    init(preset, args);
 +    validate();
    }
  
    /**
 +   * Constructor given a set of parameters and presets, a service to be invoked,
 +   * and a list of (Jabaws client) arguments
     * 
 -   * @param parent
     * @param paramStorei
     * @param service
     * @param preset
     * @param jobArgset
     */
 -  public WsJobParameters(JFrame parent, ParamDatastoreI paramStorei,
 -          Jws2Instance service, WsParamSetI preset,
 -          List<Argument> jobArgset)
 +  public WsJobParameters(ParamDatastoreI paramStorei, UIinfo service,
 +          WsParamSetI preset, List<ArgumentI> jobArgset)
    {
      super();
 +
 +    // parameters dialog in 'expanded' format (help text boxes)
 +    opanp = new OptsAndParamsPage(this, false);
 +
      jbInit();
      this.paramStore = paramStorei;
 -    if (paramStore == null)
 +    if (paramStore == null && service != null)
      {
        paramStore = service.getParamStore();
      }
      this.service = service;
 -    // argSetModified(false);
 -    // populate parameter table
 -    initForService(service, preset, jobArgset);
 -    // display in new JFrame attached to parent.
 +    initForService(preset, jobArgset);
      validate();
    }
  
 -  int response = -1;
 -
 -  JDialog frame = null;
 -
    /**
 -   * shows a modal dialog containing the parameters.
 +   * Shows a modal dialog containing the parameters and Start or Cancel options.
 +   * Answers true if the job is started, false if cancelled.
     * 
     * @return
     */
    public boolean showRunDialog()
    {
  
-     frame = new JDialog(Desktop.instance, true);
+     frame = new JDialog(Desktop.getInstance(), true);
 -
 +    if (service != null)
 +    {
 +      frame.setTitle(MessageManager.formatMessage("label.edit_params_for",
 +              new String[]
 +      { service.getActionText() }));
 +    }
-     Rectangle deskr = Desktop.instance.getBounds();
+     frame.setTitle(MessageManager.formatMessage("label.edit_params_for",
+             new String[]
+             { service.getActionText() }));
+     Rectangle deskr = Desktop.getInstance().getBounds();
      Dimension pref = this.getPreferredSize();
      frame.setBounds(
              new Rectangle((int) (deskr.getCenterX() - pref.width / 2),
      });
      frame.setVisible(true);
  
 -    if (response > 0)
 -    {
 -      return true;
 -    }
 -    return false;
 +    return startJob;
    }
  
    private void jbInit()
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                update_actionPerformed(e);
 +                update_actionPerformed();
                }
              });
      deletepref = JvSwingUtils.makeButton(
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                delete_actionPerformed(e);
 +                delete_actionPerformed();
                }
              });
      createpref = JvSwingUtils.makeButton(
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                create_actionPerformed(e);
 +                create_actionPerformed();
                }
              });
      revertpref = JvSwingUtils.makeButton(
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                revert_actionPerformed(e);
 +                revert_actionPerformed();
                }
              });
 -    startjob = JvSwingUtils.makeButton(
 +
 +    JButton startjob = JvSwingUtils.makeButton(
              MessageManager.getString("action.start_job"),
              MessageManager.getString("label.start_job_current_settings"),
              new ActionListener()
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                startjob_actionPerformed(e);
 +                startjob_actionPerformed();
                }
              });
 -    canceljob = JvSwingUtils.makeButton(
 +    JButton canceljob = JvSwingUtils.makeButton(
              MessageManager.getString("action.cancel_job"),
              MessageManager.getString("label.cancel_job_close_dialog"),
              new ActionListener()
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                canceljob_actionPerformed(e);
 +                canceljob_actionPerformed();
                }
              });
  
 +    JPanel setDetails = new JPanel();
      setDetails.setBorder(
              new TitledBorder(MessageManager.getString("label.details")));
      setDetails.setLayout(new BorderLayout());
      setName.getEditor().addActionListener(this);
      JPanel setNameInfo = new JPanel(new FlowLayout(FlowLayout.LEFT));
      GridBagLayout gbl = new GridBagLayout();
 -    SetNamePanel.setLayout(gbl);
 +    setNamePanel.setLayout(gbl);
  
      JLabel setNameLabel = new JLabel(
              MessageManager.getString("label.current_parameter_set_name"));
      revertpref.setVisible(false);
      createpref.setVisible(false);
      JPanel setsavebuts = new JPanel();
 -    setsavebuts.setLayout(new FlowLayout(FlowLayout.LEFT)); // GridLayout(1,2));
 -    ((FlowLayout) setsavebuts.getLayout()).setHgap(10);
 -    ((FlowLayout) setsavebuts.getLayout()).setVgap(0);
 +    setsavebuts.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0)); // GridLayout(1,2));
      JPanel spacer = new JPanel();
      spacer.setPreferredSize(new Dimension(2, 30));
      setsavebuts.add(spacer);
      // setsavebuts.setSize(new Dimension(150, 30));
      JPanel buttonArea = new JPanel(new GridLayout(1, 1));
      buttonArea.add(setsavebuts);
 -    SetNamePanel.add(setNameInfo);
 +    setNamePanel.add(setNameInfo);
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.gridheight = 2;
      gbl.setConstraints(setNameInfo, gbc);
 -    SetNamePanel.add(buttonArea);
 +    setNamePanel.add(buttonArea);
      gbc = new GridBagConstraints();
      gbc.gridx = 0;
      gbc.gridy = 2;
  
      // paramPane.setPreferredSize(new Dimension(360, 400));
      // paramPane.setPreferredSize(null);
 -    jobOptions.setBorder(
 +    optionsPanel.setBorder(
              new TitledBorder(MessageManager.getString("label.options")));
 -    jobOptions.setOpaque(true);
 -    paramList.setBorder(
 +    optionsPanel.setOpaque(true);
 +    paramsPanel.setBorder(
              new TitledBorder(MessageManager.getString("label.parameters")));
 -    paramList.setOpaque(true);
 -    JPanel bjo = new JPanel(new BorderLayout()),
 -            bjp = new JPanel(new BorderLayout());
 -    bjo.add(jobOptions, BorderLayout.CENTER);
 -    bjp.add(paramList, BorderLayout.CENTER);
 -    bjp.setOpaque(true);
 -    bjo.setOpaque(true);
 +    paramsPanel.setOpaque(true);
      // optsAndparams.setScrollableWidth(ScrollableSizeHint.FIT);
      // optsAndparams.setScrollableHeight(ScrollableSizeHint.NONE);
      // optsAndparams.setLayout(new BorderLayout());
 +    JPanel optsAndparams = new JPanel();
      optsAndparams.setLayout(new BorderLayout());
 -    optsAndparams.add(jobOptions, BorderLayout.NORTH);
 -    optsAndparams.add(paramList, BorderLayout.CENTER);
 +    optsAndparams.add(optionsPanel, BorderLayout.NORTH);
 +    optsAndparams.add(paramsPanel, BorderLayout.CENTER);
      JPanel jp = new JPanel(new BorderLayout());
      jp.add(optsAndparams, BorderLayout.CENTER);
      paramPane.getViewport().setView(jp);
      paramPane.setBorder(null);
      setLayout(new BorderLayout());
 +
 +    JPanel jobPanel = new JPanel();
      jobPanel.setPreferredSize(null);
      jobPanel.setLayout(new BorderLayout());
      jobPanel.add(setDetails, BorderLayout.NORTH);
      jobPanel.add(paramPane, BorderLayout.CENTER);
      // jobPanel.setOrientation(JSplitPane.VERTICAL_SPLIT);
  
 -    add(SetNamePanel, BorderLayout.NORTH);
 +    add(setNamePanel, BorderLayout.NORTH);
      add(jobPanel, BorderLayout.CENTER);
  
      JPanel dialogpanel = new JPanel();
      dialogpanel.add(canceljob);
      // JAL-1580: setMaximumSize() doesn't work, so just size for the worst case:
      // check for null is for JUnit usage
-     final int windowHeight = Desktop.instance == null ? DEFAULT_HEIGHT
-             : Desktop.instance.getHeight();
-     // setPreferredSize(new Dimension(PREFERRED_WIDTH, windowHeight));
 -    final int windowHeight = Desktop.getInstance() == null ? 540
++    final int windowHeight = Desktop.getInstance() == null ? DEFAULT_HEIGHT
+             : Desktop.getInstance().getHeight();
+     setPreferredSize(new Dimension(540, windowHeight));
      add(dialogpanel, BorderLayout.SOUTH);
      validate();
    }
  
 -  protected void revert_actionPerformed(ActionEvent e)
 +  protected void revert_actionPerformed()
    {
      reInitDialog(lastParmSet);
      updateWebServiceMenus();
    }
  
 -  protected void update_actionPerformed(ActionEvent e)
 +  protected void update_actionPerformed()
    {
      if (isUserPreset)
      {
      paramStore.deletePreset(lastParmSet2);
    }
  
 -  protected void delete_actionPerformed(ActionEvent e)
 +  protected void delete_actionPerformed()
    {
      if (isUserPreset)
      {
      updateWebServiceMenus();
    }
  
 -  protected void create_actionPerformed(ActionEvent e)
 +  protected void create_actionPerformed()
    {
      String curname = ((String) setName.getSelectedItem()).trim();
      if (curname.length() > 0)
      }
    }
  
 -  protected void canceljob_actionPerformed(ActionEvent e)
 +  protected void canceljob_actionPerformed()
    {
 -    response = 0;
 +    startJob = false;
      if (frame != null)
      {
        frame.setVisible(false);
      }
    }
  
 -  protected void startjob_actionPerformed(ActionEvent e)
 +  protected void startjob_actionPerformed()
    {
 -    response = 1;
 +    startJob = true;
      if (frame != null)
      {
        frame.setVisible(false);
      }
    }
  
 -  Jws2Instance service;
 +  void initForService(WsParamSetI paramSet, List<ArgumentI> jobArgset)
 +  {
 +    settingDialog = true;
  
 -  /**
 -   * list of service presets in the gui
 -   */
 -  Hashtable servicePresets = null;
 +    init(paramSet, jobArgset);
  
 -  /**
 -   * set if dialog is being set - so handlers will avoid spurious events
 -   */
 -  boolean settingDialog = false;
 +  }
  
 -  void initForService(Jws2Instance service, WsParamSetI jabap,
 -          List<Argument> jabajobArgset)
 +  void init(WsParamSetI p, List<ArgumentI> jobArgset)
    {
 -    WsParamSetI p = null;
 -    List<ArgumentI> jobArgset = null;
 -    settingDialog = true;
 -    { // instantiate the abstract proxy for Jaba objects
 -      jobArgset = jabajobArgset == null ? null
 -              : JabaParamStore.getJwsArgsfromJaba(jabajobArgset);
 -      p = jabap; // (jabap != null) ? paramStore.getPreset(jabap.getName()) :
 -                 // null;
 -    }
 -
 -    Hashtable exnames = new Hashtable();
 +    Hashtable<String, String> exnames = new Hashtable<>();
      for (int i = 0, iSize = setName.getItemCount(); i < iSize; i++)
      {
        exnames.put(setName.getItemAt(i), setName.getItemAt(i));
      }
 -    servicePresets = new Hashtable();
 +    servicePresets = new Hashtable<>();
      // Add the default entry - if not present already.
      if (!exnames.contains(SVC_DEF))
      {
        exnames.put(SVC_DEF, SVC_DEF);
        servicePresets.put(SVC_DEF, SVC_DEF);
      }
 -    String curname = (p == null ? "" : p.getName());
 +
 +    // String curname = (p == null ? "" : p.getName());
      for (WsParamSetI pr : paramStore.getPresets())
      {
        if (!pr.isModifiable())
        }
      }
      settingDialog = false;
 -
    }
  
 -  @SuppressWarnings("unchecked")
    private void updateTable(WsParamSetI p, List<ArgumentI> jobArgset)
    {
      boolean setDefaultParams = false;
              OptionI opt = (OptionI) myarg;
              OptionBox ob = opanp.addOption(opt);
              ob.resetToDefault(setDefaultParams);
 -            if (MAX_OPTWIDTH < ob.getPreferredSize().width)
 +            if (maxOptWidth < ob.getPreferredSize().width)
              {
 -              MAX_OPTWIDTH = ob.getPreferredSize().width;
 +              maxOptWidth = ob.getPreferredSize().width;
              }
              ob.validate();
              cw += ob.getPreferredSize().width + 5;
      return modifiedElements.size() > 0;
    }
  
 -  private Hashtable modifiedElements = new Hashtable();
 -
    /**
     * reset gui and modification state settings
     */
      if (b && modifiedElements.size() > 0)
      {
        makeSetNameValid(!isUserPreset);
 -      SetNamePanel.revalidate();
 +      setNamePanel.revalidate();
      }
      updateButtonDisplay();
    }
      // sync the gui with the preset database
      for (int i = 0, iS = setName.getItemCount(); i < iS; i++)
      {
 -      String snm = (String) setName.getItemAt(i);
 +      String snm = setName.getItemAt(i);
        if (snm.equals(nm))
        {
          makeupdate = true;
      settingDialog = stn;
    }
  
 +  /**
 +   * Rebuilds the Options and Parameters panels
 +   */
    @Override
    public void refreshParamLayout()
    {
 -    // optsAndparams.setPreferredSize(null);
 -    FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
 -    int sep = fl.getVgap();
 -    boolean fh = true;
 -    int os = 0,
 -            s = jobOptions.getBorder().getBorderInsets(jobOptions).bottom
 -                    + jobOptions.getBorder().getBorderInsets(jobOptions).top
 -                    + 2 * sep;
 -    /**
 -     * final height for viewport
 -     */
 -    int finalh = s;
 -    int panewidth = paramPane.getViewport().getSize().width - 120
 -            - jobOptions.getBorder().getBorderInsets(jobOptions).left
 -            + jobOptions.getBorder().getBorderInsets(jobOptions).right;
 -
 -    int w = 2 * fl.getHgap()
 -            + (MAX_OPTWIDTH > OptsAndParamsPage.PARAM_WIDTH ? MAX_OPTWIDTH
 -                    : OptsAndParamsPage.PARAM_WIDTH);
 -    int hgap = fl.getHgap(), cw = hgap;
 +    final int rightMargin = 40;
 +    final int availableWidth = paramPane.getViewport().getSize().width
 +            - rightMargin
 +            - optionsPanel.getBorder().getBorderInsets(optionsPanel).left
 +            + optionsPanel.getBorder().getBorderInsets(optionsPanel).right;
  
      if (opanp.getOptSet().size() > 0)
      {
 +      int hgap = 5;
 +      int currentWidth = hgap;
  
 -      jobOptions.setLayout(new MigLayout("", "", ""));
 -      jobOptions.removeAll();
 +      /*
 +       * layout constraint 'nogrid' prevents vertical column alignment,
 +       * allowing controls to flow without extra space inserted to align
 +       */
 +      optionsPanel.setLayout(new MigLayout("nogrid", "", ""));
 +      optionsPanel.removeAll();
 +      JPanel lastAdded = null;
  
 +      /*
 +       * add each control in turn; if adding would overflow the right margin,
 +       * remove and re-add the previous parameter with "wrap" (after) 
 +       * in order to start a new row
 +       */
        for (OptionBox pbox : opanp.getOptSet().values())
        {
          pbox.validate();
 -        cw += pbox.getSize().width + hgap;
 -        if (cw + 120 > panewidth)
 -        {
 -          jobOptions.add(pbox, "wrap");
 -          // System.out.println("Wrap on "+pbox.option.getName());
 -          cw = hgap + pbox.getSize().width;
 -          fh = true;
 -        }
 -        else
 -        {
 -          jobOptions.add(pbox);
 -        }
 -        if (fh)
 +        int boxWidth = pbox.getSize().width;
 +        currentWidth += boxWidth + hgap;
 +        boolean wrapAfterLast = currentWidth > availableWidth
 +                && lastAdded != null;
 +        // System.out.println(String.format(
 +        // "%s width=%d, paneWidth=%d, currentWidth=%d, wrapAfterLast=%s",
 +        // pbox.toString(), boxWidth, panewidth, currentWidth,
 +        // wrapAfterLast));
 +        if (wrapAfterLast)
          {
 -          finalh += pbox.getSize().height + fl.getVgap();
 -          fh = false;
 +          optionsPanel.remove(lastAdded);
 +          optionsPanel.add(lastAdded, "wrap");
 +          currentWidth = hgap + boxWidth;
          }
 +        optionsPanel.add(pbox);
 +        lastAdded = pbox;
        }
 -      jobOptions.revalidate();
 +      optionsPanel.revalidate();
      }
      else
      {
 -      jobOptions.setVisible(false);
 +      optionsPanel.setVisible(false);
      }
  
 -    // Now layout the parameters assuming they occupy one column - to calculate
 -    // total height of options+parameters
 -    fl = new FlowLayout(FlowLayout.LEFT);
 -    // helpful hint from
 -    // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
 -    fl.setAlignOnBaseline(true);
      if (opanp.getParamSet().size() > 0)
      {
 -      paramList.removeAll();
 -      paramList.setLayout(new MigLayout("", "", ""));
 -      fh = true;
 +      paramsPanel.removeAll();
 +      paramsPanel.setLayout(new MigLayout("", "", ""));
 +      int hgap = 5;
 +      int currentWidth = hgap;
 +
 +      JPanel lastAdded = null;
        for (ParamBox pbox : opanp.getParamSet().values())
        {
          pbox.validate();
 -        cw += pbox.getSize().width + hgap;
 -        if (cw + 160 > panewidth)
 +        int boxWidth = pbox.getSize().width;
 +        currentWidth += boxWidth + hgap;
 +        boolean wrapAfterLast = currentWidth > availableWidth
 +                && lastAdded != null;
 +        if (wrapAfterLast)
          {
 -          paramList.add(pbox, "wrap");
 -          cw = pbox.getSize().width + hgap;
 -          fh = true;
 +          paramsPanel.remove(lastAdded);
 +          paramsPanel.add(lastAdded, "wrap");
 +          currentWidth = pbox.getSize().width + hgap;
          }
 -        else
 -        {
 -          paramList.add(pbox);
 -        }
 -        if (fh)
 -        {
 -          finalh += pbox.getSize().height + fl.getVgap();
 -          fh = false;
 -        }
 -
 +        paramsPanel.add(pbox);
 +        lastAdded = pbox;
        }
 +
        /*
         * s = 2 * sep; for (ParamBox pbox : opanp.getParamSet().values()) {
         * pbox.validate(); s += sep +
         * .getBorder().getBorderInsets(paramList).bottom+paramList
         * .getBorder().getBorderInsets(paramList).top;
         */
 -      paramList.revalidate();
 +      paramsPanel.revalidate();
      }
      else
      {
 -      paramList.setVisible(false);
 +      paramsPanel.setVisible(false);
      }
      // TODO: waste some time trying to eliminate any unnecessary .validate calls
      // here
      paramPane.revalidate();
      revalidate();
    }
 -
 -  /**
 -   * testing method - grab a service and parameter set and show the window
 -   * 
 -   * @param args
 -   * @j2sIgnore
 -   */
 -  public static void main(String[] args)
 -  {
 -    jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
 -            .getInstance();
 -    int p = 0;
 -    if (args.length > 0)
 -    {
 -      Vector<String> services = new Vector<>();
 -      services.addElement(args[p++]);
 -      Jws2Discoverer.getInstance().setServiceUrls(services);
 -    }
 -    try
 -    {
 -      disc.run();
 -    } catch (Exception e)
 -    {
 -      System.err.println("Aborting. Problem discovering services.");
 -      e.printStackTrace();
 -      return;
 -    }
 -    Jws2Instance lastserv = null;
 -    for (Jws2Instance service : disc.getServices())
 -    {
 -      lastserv = service;
 -      if (p >= args.length || service.serviceType.equalsIgnoreCase(args[p]))
 -      {
 -        if (lastserv != null)
 -        {
 -          List<Preset> prl = null;
 -          Preset pr = null;
 -          if (++p < args.length)
 -          {
 -            PresetManager prman = lastserv.getPresets();
 -            if (prman != null)
 -            {
 -              pr = prman.getPresetByName(args[p]);
 -              if (pr == null)
 -              {
 -                // just grab the last preset.
 -                prl = prman.getPresets();
 -              }
 -            }
 -          }
 -          else
 -          {
 -            PresetManager prman = lastserv.getPresets();
 -            if (prman != null)
 -            {
 -              prl = prman.getPresets();
 -            }
 -          }
 -          Iterator<Preset> en = (prl == null) ? null : prl.iterator();
 -          while (en != null && en.hasNext())
 -          {
 -            if (en != null)
 -            {
 -              if (!en.hasNext())
 -              {
 -                en = prl.iterator();
 -              }
 -              pr = en.next();
 -            }
 -            {
 -              System.out.println("Testing opts dupes for "
 -                      + lastserv.getUri() + " : " + lastserv.getActionText()
 -                      + ":" + pr.getName());
 -              List<Option> rg = lastserv.getRunnerConfig().getOptions();
 -              for (Option o : rg)
 -              {
 -                try
 -                {
 -                  Option cpy = jalview.ws.jws2.ParameterUtils.copyOption(o);
 -                } catch (Exception e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                } catch (Error e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                }
 -              }
 -            }
 -            {
 -              System.out.println("Testing param dupes:");
 -              List<Parameter> rg = lastserv.getRunnerConfig()
 -                      .getParameters();
 -              for (Parameter o : rg)
 -              {
 -                try
 -                {
 -                  Parameter cpy = jalview.ws.jws2.ParameterUtils
 -                          .copyParameter(o);
 -                } catch (Exception e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                } catch (Error e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                }
 -              }
 -            }
 -            {
 -              System.out.println("Testing param write:");
 -              List<String> writeparam = null, readparam = null;
 -              try
 -              {
 -                writeparam = jalview.ws.jws2.ParameterUtils
 -                        .writeParameterSet(
 -                                pr.getArguments(lastserv.getRunnerConfig()),
 -                                " ");
 -                System.out.println("Testing param read :");
 -                List<Option> pset = jalview.ws.jws2.ParameterUtils
 -                        .processParameters(writeparam,
 -                                lastserv.getRunnerConfig(), " ");
 -                readparam = jalview.ws.jws2.ParameterUtils
 -                        .writeParameterSet(pset, " ");
 -                Iterator<String> o = pr.getOptions().iterator(),
 -                        s = writeparam.iterator(), t = readparam.iterator();
 -                boolean failed = false;
 -                while (s.hasNext() && t.hasNext())
 -                {
 -                  String on = o.next(), sn = s.next(), st = t.next();
 -                  if (!sn.equals(st))
 -                  {
 -                    System.out.println(
 -                            "Original was " + on + " Phase 1 wrote " + sn
 -                                    + "\tPhase 2 wrote " + st);
 -                    failed = true;
 -                  }
 -                }
 -                if (failed)
 -                {
 -                  System.out.println(
 -                          "Original parameters:\n" + pr.getOptions());
 -                  System.out.println(
 -                          "Wrote parameters in first set:\n" + writeparam);
 -                  System.out.println(
 -                          "Wrote parameters in second set:\n" + readparam);
 -
 -                }
 -              } catch (Exception e)
 -              {
 -                e.printStackTrace();
 -              }
 -            }
 -            WsJobParameters pgui = new WsJobParameters(lastserv,
 -                    new JabaPreset(lastserv, pr));
 -            JFrame jf = new JFrame(MessageManager
 -                    .formatMessage("label.ws_parameters_for", new String[]
 -                    { lastserv.getActionText() }));
 -            JPanel cont = new JPanel(new BorderLayout());
 -            pgui.validate();
 -            cont.setPreferredSize(pgui.getPreferredSize());
 -            cont.add(pgui, BorderLayout.CENTER);
 -            jf.setLayout(new BorderLayout());
 -            jf.add(cont, BorderLayout.CENTER);
 -            jf.validate();
 -            final Thread thr = Thread.currentThread();
 -            jf.addWindowListener(new WindowListener()
 -            {
 -
 -              @Override
 -              public void windowActivated(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowClosed(WindowEvent e)
 -              {
 -              }
 -
 -              @Override
 -              public void windowClosing(WindowEvent e)
 -              {
 -                thr.interrupt();
 -
 -              }
 -
 -              @Override
 -              public void windowDeactivated(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowDeiconified(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowIconified(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowOpened(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -            });
 -            jf.setVisible(true);
 -            boolean inter = false;
 -            while (!inter)
 -            {
 -              try
 -              {
 -                Thread.sleep(10000);
 -              } catch (Exception e)
 -              {
 -                inter = true;
 -              }
 -            }
 -            jf.dispose();
 -          }
 -        }
 -      }
 -    }
 -  }
 -
    public boolean isServiceDefaults()
    {
      return (!isModified()
      return opanp.getCurrentSettings();
    }
  
 -  String lastParmSet = null;
 -
    /*
     * Hashtable<String, Object[]> editedParams = new Hashtable<String,
     * Object[]>();
      int n = 0;
      // remove any set names in the drop down menu that aren't either a reserved
      // setting, or a user defined or service preset.
 -    Vector items = new Vector();
 +    Vector<String> items = new Vector<>();
      while (n < setName.getItemCount())
      {
 -      String item = (String) setName.getItemAt(n);
 +      String item = setName.getItemAt(n);
        if (!item.equals(SVC_DEF) && !paramStore.presetExists(item))
        {
          setName.removeItemAt(n);
      initArgSetModified();
      syncSetNamesWithStore();
      setName.setSelectedItem(lastParmSet);
 -    SetNamePanel.validate();
 +    setNamePanel.validate();
      validate();
      settingDialog = false;
    }
     */
    protected void updateWebServiceMenus()
    {
-     if (Desktop.instance == null)
++    if (Desktop.getInstance() == null)
 +    {
 +      return;
 +    }
      for (AlignFrame alignFrame : Desktop.getAlignFrames())
      {
 -      alignFrame.BuildWebServiceMenu();
 +      alignFrame.buildWebServicesMenu();
      }
    }
  
    @Override
    public void itemStateChanged(ItemEvent e)
    {
 -    if (e.getSource() == setName && e.getStateChange() == e.SELECTED)
 +    if (e.getSource() == setName
 +            && e.getStateChange() == ItemEvent.SELECTED)
      {
        final String setname = (String) setName.getSelectedItem();
 -      System.out.println("Item state changed for " + setname
 -              + " (handling ? " + !settingDialog + ")");
 +      // System.out.println("Item state changed for " + setname
 +      // + " (handling ? " + !settingDialog + ")");
        if (settingDialog)
        {
          // ignore event
  
    }
  
 -  private void _renameExistingPreset(String oldName, String curSetName2)
 -  {
 -    paramStore.updatePreset(oldName, curSetName2, setDescr.getText(),
 -            getJobParams());
 -  }
 -
    /**
     * store current settings as given name. You should then reset gui.
     * 
@@@ -56,14 -56,13 +56,14 @@@ import javax.xml.stream.XMLStreamReader
   */
  public class WsParamSetManager implements ParamManager
  {
 +  private static final String WS_PARAM_FILES = "WS_PARAM_FILES";
    Hashtable<String, ParamDatastoreI> paramparsers = new Hashtable<>();
  
    @Override
    public WsParamSetI[] getParameterSet(String name, String serviceUrl,
            boolean modifiable, boolean unmodifiable)
    {
 -    String files = Cache.getProperty("WS_PARAM_FILES");
 +    String files = Cache.getProperty(WS_PARAM_FILES);
      if (files == null)
      {
        return null;
        } catch (IOException e)
        {
          Cache.log.info("Failed to parse parameter file " + pfile
 -                + " (Check that all JALVIEW_WSPARAMFILES entries are valid!)",
 +                + " (Check that all " + WS_PARAM_FILES
 +                + " entries are valid!)",
                  e);
        }
      }
        chooser.setDialogTitle(MessageManager
                .getString("label.choose_filename_for_param_file"));
        chooser.setToolTipText(MessageManager.getString("action.save"));
-       int value = chooser.showSaveDialog(Desktop.instance);
+       int value = chooser.showSaveDialog(Desktop.getInstance());
        if (value == JalviewFileChooser.APPROVE_OPTION)
        {
          outfile = chooser.getSelectedFile();
      }
      if (outfile != null)
      {
 -      String paramFiles = jalview.bin.Cache.getDefault("WS_PARAM_FILES",
 +      String paramFiles = jalview.bin.Cache.getDefault(WS_PARAM_FILES,
                filename);
        if (paramFiles.indexOf(filename) == -1)
        {
          }
          paramFiles = paramFiles.concat(filename);
        }
 -      Cache.setProperty("WS_PARAM_FILES", paramFiles);
 +
 +      Cache.setProperty(WS_PARAM_FILES, paramFiles);
  
        WebServiceParameterSet paramxml = new WebServiceParameterSet();
  
      {
        return;
      }
 -    String paramFiles = jalview.bin.Cache.getDefault("WS_PARAM_FILES", "");
 +    String paramFiles = jalview.bin.Cache.getDefault(WS_PARAM_FILES, "");
      if (paramFiles.indexOf(filename) > -1)
      {
        String nparamFiles = new String();
            nparamFiles = nparamFiles.concat("|").concat(fl);
          }
        }
 -      jalview.bin.Cache.setProperty("WS_PARAM_FILES", nparamFiles);
 +      jalview.bin.Cache.setProperty(WS_PARAM_FILES, nparamFiles);
      }
  
      try
        File pfile = new File(filename);
        if (pfile.exists() && pfile.canWrite())
        {
-         if (JvOptionPane.showConfirmDialog(Desktop.instance,
+         if (JvOptionPane.showConfirmDialog(Desktop.getInstance(),
                  "Delete the preset's file, too ?", "Delete User Preset ?",
                  JvOptionPane.OK_CANCEL_OPTION) == JvOptionPane.OK_OPTION)
          {
   */
  package jalview.gui;
  
- import jalview.bin.Cache;
- import jalview.jbgui.GWsPreferences;
- import jalview.util.MessageManager;
- import jalview.ws.WSDiscovererI;
- import jalview.ws.jws2.Jws2Discoverer;
- import jalview.ws.rest.RestServiceDescription;
 +
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -34,7 -27,6 +28,7 @@@ import java.awt.Dimension
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.net.URL;
 +import java.util.ArrayList;
  import java.util.List;
  import java.util.Vector;
  
@@@ -45,6 -37,12 +39,13 @@@ import javax.swing.JTextField
  import javax.swing.table.AbstractTableModel;
  import javax.swing.table.TableCellRenderer;
  
+ import jalview.bin.Cache;
+ import jalview.jbgui.GWsPreferences;
+ import jalview.util.MessageManager;
++import jalview.ws.WSDiscovererI;
+ import jalview.ws.jws2.Jws2Discoverer;
+ import jalview.ws.rest.RestServiceDescription;
  public class WsPreferences extends GWsPreferences
  {
  
@@@ -66,7 -64,7 +67,7 @@@
    private void initFromPreferences()
    {
  
-     wsUrls = Jws2Discoverer.getDiscoverer().getServiceUrls();
+     wsUrls = Jws2Discoverer.getInstance().getServiceUrls();
      if (!wsUrls.isEmpty())
      {
        oldUrls = new Vector<String>(wsUrls);
      int r = 0;
      for (String url : wsUrls)
      {
-       int status = Jws2Discoverer.getDiscoverer().getServerStatusFor(url);
+       int status = Jws2Discoverer.getInstance().getServerStatusFor(url);
        tdat[r][1] = Integer.valueOf(status);
        tdat[r++][0] = url;
      }
        String t = new String("");
        switch (((Integer) status).intValue())
        {
 -      case 1:
 +      case WSDiscovererI.STATUS_OK:
          // cb.setSelected(true);
          // cb.setBackground(
          c = Color.green;
          break;
 -      case 0:
 +      case WSDiscovererI.STATUS_NO_SERVICES:
          // cb.setSelected(true);
          // cb.setBackground(
          c = Color.lightGray;
          break;
 -      case -1:
 +      case WSDiscovererI.STATUS_INVALID:
          // cb.setSelected(false);
          // cb.setBackground(
          c = Color.red;
          break;
 +      case WSDiscovererI.STATUS_UNKNOWN:
        default:
          // cb.setSelected(false);
          // cb.setBackground(
  
    private void updateServiceList()
    {
-     Jws2Discoverer.getDiscoverer().setServiceUrls(wsUrls);
+     Jws2Discoverer.getInstance().setServiceUrls(wsUrls);
    }
  
    private void updateRsbsServiceList()
      boolean valid = false;
      int resp = JvOptionPane.CANCEL_OPTION;
      while (!valid && (resp = JvOptionPane.showInternalConfirmDialog(
-             Desktop.desktop, panel, title,
+             Desktop.getDesktopPane(), panel, title,
              JvOptionPane.OK_CANCEL_OPTION)) == JvOptionPane.OK_OPTION)
      {
        try
        } catch (Exception e)
        {
          valid = false;
-         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  MessageManager.getString("label.invalid_url"));
        }
      }
      if (valid && resp == JvOptionPane.OK_OPTION)
      {
-       int validate = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
+       int validate = JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(),
                MessageManager.getString("info.validate_jabaws_server"),
                MessageManager.getString("label.test_server"),
                JvOptionPane.YES_NO_OPTION);
  
        if (validate == JvOptionPane.OK_OPTION)
        {
-         if (Jws2Discoverer.getDiscoverer().testServiceUrl(foo))
 -        if (Jws2Discoverer.testServiceUrl(foo))
++        if (Jws2Discoverer.getInstance().testServiceUrl(foo))
          {
            return foo.toString();
          }
          else
          {
-           int opt = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
+           int opt = JvOptionPane.showInternalOptionDialog(Desktop.getDesktopPane(),
                    "The Server  '" + foo.toString()
                            + "' failed validation,\ndo you want to add it anyway? ",
                    "Server Validation Failed", JvOptionPane.YES_NO_OPTION,
            }
            else
            {
-             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.getString(
                              "warn.server_didnt_pass_validation"));
            }
            if (lastrefresh != update)
            {
              lastrefresh = update;
-             Desktop.instance.startServiceDiscovery(true); // wait around for all
+             Desktop.getInstance().startServiceDiscovery(true); // wait around for all
                                                            // threads to complete
              updateList();
  
          public void run()
          {
            long ct = System.currentTimeMillis();
-           Desktop.instance.setProgressBar(MessageManager
+           Desktop.getInstance().setProgressBar(MessageManager
                    .getString("status.refreshing_web_service_menus"), ct);
            if (lastrefresh != update)
            {
              lastrefresh = update;
-             Desktop.instance.startServiceDiscovery(true);
+             Desktop.getInstance().startServiceDiscovery(true);
              updateList();
            }
-           Desktop.instance.setProgressBar(null, ct);
+           Desktop.getInstance().setProgressBar(null, ct);
          }
  
        }).start();
    @Override
    protected void resetWs_actionPerformed(ActionEvent e)
    {
-     Jws2Discoverer.getDiscoverer().setServiceUrls(null);
-     List<String> nwsUrls = Jws2Discoverer.getDiscoverer().getServiceUrls();
+     Jws2Discoverer.getInstance().setServiceUrls(null);
+     List<String> nwsUrls = Jws2Discoverer.getInstance().getServiceUrls();
      if (!wsUrls.equals(nwsUrls))
      {
        update++;
index eea3dae,0000000..a691c53
mode 100644,000000..100644
--- /dev/null
@@@ -1,63 -1,0 +1,63 @@@
 +package jalview.io;
 +
 +import jalview.bin.Jalview;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.ResidueCount;
 +import jalview.datamodel.SequenceI;
 +import jalview.gui.AlignmentPanel;
 +import jalview.gui.Desktop;
 +import jalview.gui.JvOptionPane;
 +import jalview.util.MessageManager;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.net.MalformedURLException;
 +
 +import javax.swing.JFileChooser;
 +
 +public class CountReader
 +{
 +  public static ResidueCount getBackgroundFrequencies(AlignmentPanel ap, SequenceI seq) throws MalformedURLException, IOException
 +  {
 +    JFileChooser bkgdFreqChooser = new JFileChooser();
 +    
 +    bkgdFreqChooser.showOpenDialog(ap);
 +    
 +    File file = bkgdFreqChooser.getSelectedFile();
 +    if (file == null)
 +    {
 +      return null;
 +    }
 +    
 +    IdentifyFile identifier = new IdentifyFile();
 +    FileFormatI format = null;
 +    try
 +    {
 +      format = identifier.identify(file.getPath(), DataSourceType.FILE);
 +    } catch (Exception e)
 +    {
 +
 +    }
 +    
 +    if (format == null)
 +    {
 +      if (!Jalview.isHeadlessMode())
 +      {
-         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
++        JvOptionPane.showInternalMessageDialog(Desktop.getInstance(),
 +                MessageManager.getString("label.couldnt_read_data") + " in "
 +                        + file + "\n"
 +                        + AppletFormatAdapter.getSupportedFormats(),
 +                MessageManager.getString("label.couldnt_read_data"),
 +                JvOptionPane.WARNING_MESSAGE);
 +      }
 +    }
 +
 +    FileParse parser = new FileParse(file.getPath(), DataSourceType.FILE);
 +    AlignmentI al = new FormatAdapter().readFromFile(parser, format);
 +    parser.close();
 +    
 +    ResidueCount counts = new ResidueCount(al.getSequences());
 +    
 +    return counts;
 +  }
 +}
@@@ -373,24 -373,22 +373,38 @@@ public enum FileFormat implements FileF
      {
        return true;
      }
 +  },
 +  HMMER3("HMMER3", "hmm", true, true)
 +  {
 +    @Override
 +    public AlignmentFileReaderI getReader(FileParse source)
 +            throws IOException
 +    {
 +      return new HMMFile(source);
 +    }
 +
 +    @Override
 +    public AlignmentFileWriterI getWriter(AlignmentI al)
 +    {
 +      return new HMMFile();
 +    }
+   },   BSML("BSML", "bbb", true, false)
+   {
+     @Override
+     public AlignmentFileReaderI getReader(FileParse source)
+             throws IOException
+     {
+       return new BSMLFile(source);
+     }
+     @Override
+     public AlignmentFileWriterI getWriter(AlignmentI al)
+     {
+       return null;
+     }
    };
  
 +
    private boolean writable;
  
    private boolean readable;
@@@ -20,6 -20,7 +20,7 @@@
   */
  package jalview.io;
  
+ import java.awt.Dimension;
  import java.io.File;
  import java.io.IOException;
  import java.util.StringTokenizer;
@@@ -46,15 -47,11 +47,16 @@@ import jalview.project.Jalview2XML
  import jalview.schemes.ColourSchemeI;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import jalview.ws.utils.UrlDownloadClient;
  
 +import java.util.ArrayList;
 +import java.util.List;
 +
  public class FileLoader implements Runnable
  {
 +  private static final String TAB = "\t";
 +
    String file;
  
    DataSourceType protocol;
      return alignFrame;
    }
  
 -  public void updateRecentlyOpened()
 +  public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
 +          String file, DataSourceType sourceType, FileFormatI format)
    {
      Vector<String> recent = new Vector<>();
      if (protocol == DataSourceType.PASTE)
 +    this.viewport = viewport;
 +    this.file = file;
 +    this.protocol = sourceType;
 +    this.format = format;
 +    _LoadFileWaitTillLoaded();
 +  }
 +
 +
 +  /**
 +   * Updates (or creates) the tab-separated list of recently opened files held
 +   * under the given property name by inserting the filePath at the front of the
 +   * list. Duplicates are removed, and the list is limited to 11 entries. The
 +   * method returns the updated value of the property.
 +   * 
 +   * @param filePath
 +   * @param sourceType
 +   */
 +  public static String updateRecentlyOpened(String filePath,
 +          DataSourceType sourceType)
 +  {
 +    if (sourceType != DataSourceType.FILE
 +            && sourceType != DataSourceType.URL)
      {
 -      // do nothing if the file was pasted in as text... there is no filename to
 -      // refer to it as.
 -      return;
 +      return null;
      }
 -    if (file != null
 -            && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
 +
 +    String propertyName = sourceType == DataSourceType.FILE ? "RECENT_FILE"
 +            : "RECENT_URL";
 +    String historyItems = Cache.getProperty(propertyName);
 +    if (filePath != null
 +            && filePath.indexOf(System.getProperty("java.io.tmpdir")) > -1)
      {
        // ignore files loaded from the system's temporary directory
 -      return;
 +      return null;
      }
 -    String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
 -            : "RECENT_URL";
  
 -    String historyItems = Cache.getProperty(type);
 -
 -    StringTokenizer st;
 +    List<String> recent = new ArrayList<>();
  
      if (historyItems != null)
      {
 -      st = new StringTokenizer(historyItems, "\t");
 +      StringTokenizer st = new StringTokenizer(historyItems, TAB);
  
        while (st.hasMoreTokens())
        {
 -        recent.addElement(st.nextToken().trim());
 +        String trimmed = st.nextToken().trim();
 +      recent.add(trimmed);
        }
      }
  
 -    if (recent.contains(file))
 +    /*
 +     * if file was already in the list, it moves to the top
 +     */
 +    if (recent.contains(filePath))
      {
 -      recent.remove(file);
 +      recent.remove(filePath);
      }
  
 -    StringBuffer newHistory = new StringBuffer(file);
 +    StringBuilder newHistory = new StringBuilder(filePath);
      for (int i = 0; i < recent.size() && i < 10; i++)
      {
 -      newHistory.append("\t");
 -      newHistory.append(recent.elementAt(i));
 +      newHistory.append(TAB);
 +      newHistory.append(recent.get(i));
      }
  
 -    Cache.setProperty(type, newHistory.toString());
 +    String newProperty = newHistory.toString();
 +    Cache.setProperty(propertyName, newProperty);
  
 -    if (protocol == DataSourceType.FILE)
 -    {
 -      Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
 -    }
 +    return newProperty;
    }
  
    @Override
      Runtime rt = Runtime.getRuntime();
      try
      {
-       if (Desktop.instance != null)
+       if (Desktop.getInstance() != null)
        {
-         Desktop.instance.startLoading(file);
+         Desktop.getInstance().startLoading(file);
        }
        if (format == null)
        {
  
        if (format == null)
        {
-         Desktop.instance.stopLoading();
+         Desktop.getInstance().stopLoading();
          System.err.println("The input file \"" + file
                  + "\" has null or unidentifiable data content!");
          if (!Jalview.isHeadlessMode())
          {
-           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+           JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                    MessageManager.getString("label.couldnt_read_data")
                            + " in " + file + "\n"
                            + AppletFormatAdapter.getSupportedFormats(),
        }
        // TODO: cache any stream datasources as a temporary file (eg. PDBs
        // retrieved via URL)
-       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
+       if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
        {
          System.gc();
          memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
                  // register PDB entries with desktop's structure selection
                  // manager
                  StructureSelectionManager
-                         .getStructureSelectionManager(Desktop.instance)
+                         .getStructureSelectionManager(Desktop.getInstance())
                          .registerPDBEntry(pdbe);
                }
              }
              }
              // append to existing alignment
              viewport.addAlignment(al, title);
 +            if (source instanceof HMMFile)
 +            {
 +              AlignmentI alignment = viewport.getAlignment();
 +              SequenceI seq = alignment
 +                      .getSequenceAt(alignment.getHeight() - 1);
 +              if (seq.hasHMMProfile())
 +              {
 +                /* 
 +                 * fudge: move HMM consensus sequence from last to first
 +                 */
 +                alignment.deleteSequence(alignment.getAbsoluteHeight() - 1);
 +                alignment.insertSequenceAt(0, seq);
 +              }
 +              viewport.getAlignPanel().adjustAnnotationHeight();
 +              viewport.updateSequenceIdColours();
 +            }
            }
            else
            {
              // add metadata and update ui
              if (!(protocol == DataSourceType.PASTE))
              {
-               alignFrame.setFileName(file, format);
-               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
+               alignFrame.setFile(file, selectedFile, protocol, format);
              }
              if (proxyColourScheme != null)
              {
                // status in Jalview 3
                // TODO: define 'virtual desktop' for benefit of headless scripts
                // that perform queries to find the 'current working alignment'
-               Desktop.addInternalFrame(alignFrame, title,
+               
+               Dimension dim = Platform.getDimIfEmbedded(alignFrame,
                        AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+               alignFrame.setSize(dim);
+               Desktop.addInternalFrame(alignFrame, title, dim.width,
+                       dim.height);
              }
  
              try
          }
          else
          {
-           if (Desktop.instance != null)
+           if (Desktop.getInstance() != null)
            {
-             Desktop.instance.stopLoading();
+             Desktop.getInstance().stopLoading();
            }
  
            final String errorMessage = MessageManager.getString(
                    "label.couldnt_load_file") + " " + title + "\n" + error;
            // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
            // bits ?
-           if (raiseGUI && Desktop.desktop != null)
+           if (raiseGUI && Desktop.getDesktopPane() != null)
            {
              javax.swing.SwingUtilities.invokeLater(new Runnable()
              {
                @Override
                public void run()
                {
-                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                          errorMessage,
                          MessageManager
                                  .getString("label.error_loading_file"),
          }
        }
  
 -      updateRecentlyOpened();
 +      updateRecentlyOpened(file, protocol);
 +
 +      if (protocol == DataSourceType.FILE && format != null)
 +      {
 +        Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
 +      }
  
      } catch (Exception er)
      {
            @Override
            public void run()
            {
-             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.formatMessage(
                              "label.problems_opening_file", new String[]
                              { file }),
            @Override
            public void run()
            {
-             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.formatMessage(
                              "warn.out_of_memory_loading_file", new String[]
                              { file }),
      // memory
      // after
      // load
-     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
+     if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
      {
        if (alignFrame != null)
        {
        }
      }
      // remove the visual delay indicator
-     if (Desktop.instance != null)
+     if (Desktop.getInstance() != null)
      {
-       Desktop.instance.stopLoading();
+       Desktop.getInstance().stopLoading();
      }
  
    }
@@@ -185,11 -185,6 +185,11 @@@ public class IdentifyFil
            reply = FileFormat.ScoreMatrix;
            break;
          }
 +        if (data.startsWith("HMMER3"))
 +        {
 +          reply = FileFormat.HMMER3;
 +          break;
 +        }
          if (data.startsWith("H ") && !aaIndexHeaderRead)
          {
            aaIndexHeaderRead = true;
              reply = FileFormat.Rnaml;
              break;
            }
+           if (upper.substring(lessThan).startsWith("<BSML"))
+           {
+             reply = FileFormat.BSML;
+             break;
+           }
          }
  
          if ((data.length() < 1) || (data.indexOf("#") == 0))
@@@ -27,7 -27,6 +27,7 @@@ import java.util.List
  import java.util.Map;
  
  import jalview.api.FeatureColourI;
 +import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.DBRefSource;
  import jalview.datamodel.GeneLociI;
@@@ -238,7 -237,7 +238,7 @@@ public class SequenceAnnotationRepor
        {
          if (sb0.length() > 6)
          {
-           sb.append("<br/>");
+           sb.append("<br>");
          }
          sb.append(feature.getType()).append(" ").append(begin).append(":")
                  .append(end);
  
      if (sb0.length() > 6)
      {
-       sb.append("<br/>");
+       sb.append("<br>");
      }
      // TODO: remove this hack to display link only features
      boolean linkOnly = feature.getValue("linkonly") != null;
          int linkindex = description.toLowerCase().indexOf("<a ");
          boolean hasLink = linkindex > -1
                  && linkindex < MAX_DESCRIPTION_LENGTH;
-         if (description.length() > MAX_DESCRIPTION_LENGTH && !hasLink)
+         if (
+                 // BH suggestion maxlength == 0 && 
+                 description.length() > MAX_DESCRIPTION_LENGTH && !hasLink)
          {
            description = description.substring(0, MAX_DESCRIPTION_LENGTH)
                    + ELLIPSIS;
            {
              for (List<String> urllink : createLinksFrom(null, urlstring))
              {
-               sb.append("<br/> <a href=\""
+               sb.append("<br> <a href=\""
                        + urllink.get(3)
                        + "\" target=\""
                        + urllink.get(0)
                                .equals(urllink.get(1).toLowerCase()) ? urllink
                                .get(0) : (urllink.get(0) + ":" + urllink
                                                .get(1)))
-                       + "</a><br/>");
+                       + "</a><br>");
              }
            } catch (Exception x)
            {
        sb.append(tmp);
        maxWidth = Math.max(maxWidth, tmp.length());
      }
 +
      SequenceI ds = sequence;
      while (ds.getDatasetSequence() != null)
      {
        ds = ds.getDatasetSequence();
      }
  
 +    /*
 +     * add any annotation scores
 +     */
 +    AlignmentAnnotation[] anns = ds.getAnnotation();
 +    for (int i = 0; anns != null && i < anns.length; i++)
 +    {
 +      AlignmentAnnotation aa = anns[i];
 +      if (aa != null && aa.hasScore() && aa.sequenceRef != null)
 +      {
 +        sb.append("<br>").append(aa.label).append(": ")
 +                .append(aa.getScore());
 +      }
 +    }
 +
      if (showDbRefs)
      {
        maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
          maxWidth = Math.max(maxWidth, sz);
        }
      }
 +
 +
 +    if (sequence.getAnnotation("Search Scores") != null)
 +    {
 +      sb.append("<br>");
 +      String eValue = " E-Value: "
 +              + sequence.getAnnotation("Search Scores")[0].getEValue();
 +      String bitScore = " Bit Score: "
 +              + sequence.getAnnotation("Search Scores")[0].getBitScore();
 +      sb.append(eValue);
 +      sb.append("<br>");
 +      sb.append(bitScore);
 +      maxWidth = Math.max(maxWidth, eValue.length());
 +      maxWidth = Math.max(maxWidth, bitScore.length());
 +    }
 +    sb.append("<br>");
      sb.append("</i>");
 +
      return maxWidth;
    }
  
        countForSource++;
        if (countForSource == 1 || !summary)
        {
-         sb.append("<br/>");
+         sb.append("<br>");
        }
        if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
        {
      }
      if (moreSources)
      {
-       sb.append("<br/>").append(source).append(COMMA).append(ELLIPSIS);
+       sb.append("<br>").append(source).append(COMMA).append(ELLIPSIS);
      }
      if (ellipsis)
      {
-       sb.append("<br/>(");
+       sb.append("<br>(");
        sb.append(MessageManager.getString("label.output_seq_details"));
        sb.append(")");
      }
   */
  package jalview.io;
  
+ import jalview.analysis.Rna;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.DBRefEntry;
+ import jalview.datamodel.DBRefSource;
+ import jalview.datamodel.Mapping;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceI;
+ import jalview.schemes.ResidueProperties;
+ import jalview.util.Comparison;
+ import jalview.util.DBRefUtils;
+ import jalview.util.Format;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import java.io.BufferedReader;
  import java.io.FileReader;
  import java.io.IOException;
@@@ -39,21 -56,6 +56,6 @@@ import com.stevesoft.pat.Regex
  import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
  import fr.orsay.lri.varna.factories.RNAFactory;
  import fr.orsay.lri.varna.models.rna.RNA;
- import jalview.analysis.Rna;
- import jalview.datamodel.AlignmentAnnotation;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.Annotation;
- import jalview.datamodel.DBRefEntry;
- import jalview.datamodel.DBRefSource;
- import jalview.datamodel.Mapping;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceFeature;
- import jalview.datamodel.SequenceI;
- import jalview.schemes.ResidueProperties;
- import jalview.util.Comparison;
- import jalview.util.DBRefUtils;
- import jalview.util.Format;
- import jalview.util.MessageManager;
  
  // import org.apache.log4j.*;
  
@@@ -77,26 -79,116 +79,117 @@@ public class StockholmFile extends Alig
  {
    private static final String ANNOTATION = "annotation";
  
 -  // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using
 -  // NOT_RNASS first.
 +  private static final char UNDERSCORE = '_';
 +  
 +  // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
    public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
  
+   public static final int REGEX_STOCKHOLM = 0;
+   public static final int REGEX_BRACKETS = 1;
    // use the following regex to decide an annotations (whole) line is NOT an RNA
    // SS (it contains only E,H,e,h and other non-brace/non-alpha chars)
-   private static final Regex NOT_RNASS = new Regex(
-           "^[^<>[\\](){}A-DF-Za-df-z]*$");
+   public static final int REGEX_NOT_RNASS = 2;
+   private static final int REGEX_ANNOTATION = 3;
+   private static final int REGEX_PFAM = 4;
+   private static final int REGEX_RFAM = 5;
+   private static final int REGEX_ALIGN_END = 6;
+   private static final int REGEX_SPLIT_ID = 7;
+   private static final int REGEX_SUBTYPE = 8;
+   private static final int REGEX_ANNOTATION_LINE = 9;
+   private static final int REGEX_REMOVE_ID = 10;
+   private static final int REGEX_OPEN_PAREN = 11;
+   private static final int REGEX_CLOSE_PAREN = 12;
+   public static final int REGEX_MAX = 13;
+   private static Regex REGEX[] = new Regex[REGEX_MAX];
+   /**
+    * Centralize all actual Regex instantialization in Platform.
 -   * 
++   * // JBPNote: Why is this 'centralisation' better ?
+    * @param id
+    * @return
+    */
+   private static Regex getRegex(int id)
+   {
+     if (REGEX[id] == null)
+     {
+       String pat = null, pat2 = null;
+       switch (id)
+       {
+       case REGEX_STOCKHOLM:
+         pat = "# STOCKHOLM ([\\d\\.]+)";
+         break;
+       case REGEX_BRACKETS:
+         // for reference; not used
+         pat = "(<|>|\\[|\\]|\\(|\\)|\\{|\\})";
+         break;
+       case REGEX_NOT_RNASS:
+         pat = "^[^<>[\\](){}A-DF-Za-df-z]*$";
+         break;
+       case REGEX_ANNOTATION:
+         pat = "(\\w+)\\s*(.*)";
+         break;
+       case REGEX_PFAM:
+         pat = "PF[0-9]{5}(.*)";
+         break;
+       case REGEX_RFAM:
+         pat = "RF[0-9]{5}(.*)";
+         break;
+       case REGEX_ALIGN_END:
+         pat = "^\\s*\\/\\/";
+         break;
+       case REGEX_SPLIT_ID:
+         pat = "(\\S+)\\/(\\d+)\\-(\\d+)";
+         break;
+       case REGEX_SUBTYPE:
+         pat = "(\\S+)\\s+(\\S*)\\s+(.*)";
+         break;
+       case REGEX_ANNOTATION_LINE:
+         pat = "#=(G[FSRC]?)\\s+(.*)";
+         break;
+       case REGEX_REMOVE_ID:
+         pat = "(\\S+)\\s+(\\S+)";
+         break;
+       case REGEX_OPEN_PAREN:
+         pat = "(<|\\[)";
+         pat2 = "(";
+         break;
+       case REGEX_CLOSE_PAREN:
+         pat = "(>|\\])";
+         pat2 = ")";
+         break;
+       default:
+         return null;
+       }
+       REGEX[id] = Platform.newRegex(pat, pat2);
+     }
+     return REGEX[id];
+   }
  
    StringBuffer out; // output buffer
  
 -  AlignmentI al;
 +  private AlignmentI al;
  
    public StockholmFile()
    {
    }
  
    /**
 -   * Creates a new StockholmFile object for output.
 +   * Creates a new StockholmFile object for output
     */
    public StockholmFile(AlignmentI al)
    {
      // First, we have to check that this file has STOCKHOLM format, i.e. the
      // first line must match
  
-     r = new Regex("# STOCKHOLM ([\\d\\.]+)");
+     r = getRegex(REGEX_STOCKHOLM);
      if (!r.search(nextLine()))
      {
        throw new IOException(MessageManager
        // logger.debug("Stockholm version: " + version);
      }
  
-     // We define some Regexes here that will be used regularly later
-     rend = new Regex("^\\s*\\/\\/"); // Find the end of an alignment
-     p = new Regex("(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in
+     // We define some Regexes here that will be used regularily later
+     rend = getRegex(REGEX_ALIGN_END);//"^\\s*\\/\\/"); // Find the end of an alignment
+     p = getRegex(REGEX_SPLIT_ID);//"(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in
      // id/from/to
-     s = new Regex("(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses annotation subtype
-     r = new Regex("#=(G[FSRC]?)\\s+(.*)"); // Finds any annotation line
-     x = new Regex("(\\S+)\\s+(\\S+)"); // split id from sequence
+     s = getRegex(REGEX_SUBTYPE);// "(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses
+                                 // annotation subtype
+     r = getRegex(REGEX_ANNOTATION_LINE);// "#=(G[FSRC]?)\\s+(.*)"); // Finds any
+                                         // annotation line
+     x = getRegex(REGEX_REMOVE_ID);// "(\\S+)\\s+(\\S+)"); // split id from
+                                   // sequence
  
      // Convert all bracket types to parentheses (necessary for passing to VARNA)
-     Regex openparen = new Regex("(<|\\[)", "(");
-     Regex closeparen = new Regex("(>|\\])", ")");
+     Regex openparen = getRegex(REGEX_OPEN_PAREN);//"(<|\\[)", "(");
+     Regex closeparen = getRegex(REGEX_CLOSE_PAREN);//"(>|\\])", ")");
  
  //    // Detect if file is RNA by looking for bracket types
- //    Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");
+     // Regex detectbrackets = getRegex("(<|>|\\[|\\]|\\(|\\))");
  
      rend.optimize();
      p.optimize();
          this.noSeqs = seqs.size();
  
          String dbsource = null;
-         Regex pf = new Regex("PF[0-9]{5}(.*)"); // Finds AC for Pfam
-         Regex rf = new Regex("RF[0-9]{5}(.*)"); // Finds AC for Rfam
+         Regex pf = getRegex(REGEX_PFAM); // Finds AC for Pfam
+         Regex rf = getRegex(REGEX_RFAM); // Finds AC for Rfam
          if (getAlignmentProperty("AC") != null)
          {
            String dbType = getAlignmentProperty("AC").toString();
  
            if (accAnnotations != null && accAnnotations.containsKey("AC"))
            {
-             String dbr = (String) accAnnotations.get("AC");
-             if (dbr != null)
-             {
-               // we could get very clever here - but for now - just try to
+               String dbr = (String) accAnnotations.get("AC");
+               if (dbr != null)
+               {
+                 // we could get very clever here - but for now - just try to
                // guess accession type from type of sequence, source of alignment plus
                // structure
-               // of accession
-               guessDatabaseFor(seqO, dbr, dbsource);
+                 // of accession
+                 guessDatabaseFor(seqO, dbr, dbsource);
              }
              // else - do what ? add the data anyway and prompt the user to
              // specify what references these are ?
             */
            // Let's save the annotations, maybe we'll be able to do something
            // with them later...
-           Regex an = new Regex("(\\w+)\\s*(.*)");
+           Regex an = getRegex(REGEX_ANNOTATION);
            if (an.search(annContent))
            {
              if (an.stringMatched(1).equals("NH"))
              if (features.containsKey(this.id2type(type)))
              {
                // logger.debug("Found content for " + this.id2type(type));
 -              content = (Hashtable) features.get(this.id2type(type));
 +              content = (Hashtable) features
 +                      .get(this.id2type(type));
              }
              else
              {
                // logger.debug("Creating new content holder for " +
                // this.id2type(type));
                content = new Hashtable();
 -              features.put(this.id2type(type), content);
 +              features.put(id2type(type), content);
              }
              String ns = (String) content.get(ANNOTATION);
  
            Vector<AlignmentAnnotation> annotation, String label,
            String annots)
    {
 -    String convert1, convert2 = null;
 -
 -    // convert1 = OPEN_PAREN.replaceAll(annots);
 -    // convert2 = CLOSE_PAREN.replaceAll(convert1);
 +        String convert1, convert2 = null;
 +    // String convert1 = OPEN_PAREN.replaceAll(annots);
 +    // String convert2 = CLOSE_PAREN.replaceAll(convert1);
      // annots = convert2;
  
      String type = label;
      type = id2type(type);
  
      boolean isrnass = false;
 +
      if (type.equalsIgnoreCase("secondary structure"))
      {
        ss = true;
-       isrnass = !NOT_RNASS.search(annots); // sorry about the double negative
+       isrnass = !getRegex(REGEX_NOT_RNASS).search(annots); // sorry about the double
+                                                      // negative
                                             // here (it's easier for dealing with
                                             // other non-alpha-non-brace chars)
      }
      for (int i = 0; i < annots.length(); i++)
      {
        String pos = annots.substring(i, i + 1);
 +      if (UNDERSCORE == pos.charAt(0))
 +      {
 +        pos = " ";
 +      }
        Annotation ann;
        ann = new Annotation(pos, "", ' ', 0f); // 0f is 'valid' null - will not
        // be written out
      return ref.getSource().toString() + " ; "
              + ref.getAccessionId().toString();
    }
    @Override
    public String print(SequenceI[] s, boolean jvSuffix)
    {
          }
          else
          {
-           for (int idb = 0; idb < seq.getDBRefs().size(); idb++)
+           for (int idb = 0; idb < ndb; idb++)
            {
-             DBRefEntry dbref = seq.getDBRefs().get(idb);
+             DBRefEntry dbref = seqrefs.get(idb);
              dataRef.put(tmp, dbref_to_ac_record(dbref));
              // if we put in a uniprot or EMBL record then we're done:
-             if (isAA && DBRefSource.UNIPROT
-                     .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
-             {
-               break;
-             }
-             if (!isAA && DBRefSource.EMBL
+             if ((isAA ? DBRefSource.UNIPROT : DBRefSource.EMBL)
                      .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
              {
                break;
        if (alAnot != null)
        {
          Annotation[] ann;
 -        for (int j = 0, nj = alAnot.length; j < nj; j++)
 +        for (int j = 0; j < alAnot.length; j++)
          {
 -
 -          String key = type2id(alAnot[j].label);
 -          boolean isrna = alAnot[j].isValidStruc();
 -
 -          if (isrna)
 -          {
 -            // hardwire to secondary structure if there is RNA secondary
 -            // structure on the annotation
 -            key = "SS";
 -          }
 -          if (key == null)
 +          if (alAnot[j].annotations != null)
            {
 +            String key = type2id(alAnot[j].label);
 +            boolean isrna = alAnot[j].isValidStruc();
  
 -            continue;
 -          }
 +            if (isrna)
 +            {
 +              // hardwire to secondary structure if there is RNA secondary
 +              // structure on the annotation
 +              key = "SS";
 +            }
 +            if (key == null)
 +            {
 +              continue;
 +            }
  
 -          // out.append("#=GR ");
 -          out.append(new Format("%-" + maxid + "s").form(
 -                  "#=GR " + printId(seq, jvSuffix) + " " + key + " "));
 -          ann = alAnot[j].annotations;
 -          String sseq = "";
 -          for (int k = 0, nk = ann.length; k < nk; k++)
 -          {
 -            sseq += outputCharacter(key, k, isrna, ann, seq);
 -          }
 -          out.append(sseq);
 -          out.append(newline);
 +            // out.append("#=GR ");
 +            out.append(new Format("%-" + maxid + "s").form(
 +                    "#=GR " + printId(s[i], jvSuffix) + " " + key + " "));
 +            ann = alAnot[j].annotations;
 +            String sseq = "";
 +            for (int k = 0; k < ann.length; k++)
 +            {
 +              sseq += outputCharacter(key, k, isrna, ann, s[i]);
 +            }
 +            out.append(sseq);
 +            out.append(newline);
 +        }
          }
 +
        }
  
        out.append(new Format("%-" + maxid + "s")
      return out.toString();
    }
  
 +
    /**
     * add an annotation character to the output row
     * 
              : seq;
    }
  
 +  /**
 +   * make a friendly ID string.
 +   * 
 +   * @param dataName
 +   * @return truncated dataName to after last '/'
 +   */
 +  private String safeName(String dataName)
 +  {
 +    int b = 0;
 +    while ((b = dataName.indexOf("/")) > -1 && b < dataName.length())
 +    {
 +      dataName = dataName.substring(b + 1).trim();
 +
 +    }
 +    int e = (dataName.length() - dataName.indexOf(".")) + 1;
 +    dataName = dataName.substring(1, e).trim();
 +    return dataName;
 +  }
 +  
 +  
    public String print()
    {
      out = new StringBuffer();
  
      }
    }
 -
 +  
    protected static String id2type(String id)
    {
      if (typeIds.containsKey(id))
              "Warning : Unknown Stockholm annotation type: " + type);
      return key;
    }
 -
 -  /**
 -   * make a friendly ID string.
 -   * 
 -   * @param dataName
 -   * @return truncated dataName to after last '/'
 -   */
 -  private String safeName(String dataName)
 -  {
 -    int b = 0;
 -    while ((b = dataName.indexOf("/")) > -1 && b < dataName.length())
 -    {
 -      dataName = dataName.substring(b + 1).trim();
 -
 -    }
 -    int e = (dataName.length() - dataName.indexOf(".")) + 1;
 -    dataName = dataName.substring(1, e).trim();
 -    return dataName;
 -  }
  }
   */
  package jalview.jbgui;
  
- import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
- import jalview.analysis.GeneticCodeI;
- import jalview.analysis.GeneticCodes;
- import jalview.api.SplitContainerI;
- import jalview.bin.Cache;
- import jalview.gui.JvSwingUtils;
- import jalview.gui.Preferences;
- import jalview.hmmer.HmmerCommand;
- import jalview.io.FileFormatException;
- import jalview.io.FileFormats;
- import jalview.schemes.ResidueColourScheme;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.GridLayout;
@@@ -41,10 -27,10 +27,11 @@@ import java.awt.event.ActionEvent
  import java.awt.event.ActionListener;
  import java.awt.event.FocusAdapter;
  import java.awt.event.FocusEvent;
+ import java.awt.event.InputEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
 +import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  
@@@ -64,6 -50,18 +51,21 @@@ import javax.swing.event.ChangeEvent
  import javax.swing.event.MenuEvent;
  import javax.swing.event.MenuListener;
  
+ import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+ import jalview.analysis.GeneticCodeI;
+ import jalview.analysis.GeneticCodes;
+ import jalview.api.SplitContainerI;
+ import jalview.bin.Cache;
+ import jalview.gui.JvSwingUtils;
+ import jalview.gui.Preferences;
++import jalview.hmmer.HmmerCommand;
++import jalview.io.FileFormatException;
+ import jalview.io.FileFormats;
+ import jalview.schemes.ResidueColourScheme;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
++
  @SuppressWarnings("serial")
  public class GAlignFrame extends JInternalFrame
  {
  
    public JMenu webService = new JMenu();// BH 2019 was protected, but not
                                          // sufficient for AlignFrame thread run
 +    // JBP - followed suite for these other service related GUI elements.
 +    // TODO: check we really need these to be public
 +  public JMenu hmmerMenu = new JMenu();
  
 -  public JMenuItem webServiceNoServices;// BH 2019 was protected, but not
 -                                        // sufficient for AlignFrame thread run
 +  public JMenuItem webServiceNoServices;
  
    protected JCheckBoxMenuItem viewBoxesMenuItem = new JCheckBoxMenuItem();
  
  
    protected JCheckBoxMenuItem normaliseSequenceLogo = new JCheckBoxMenuItem();
  
 +  protected JCheckBoxMenuItem showInformationHistogram = new JCheckBoxMenuItem();
 +
 +  protected JCheckBoxMenuItem showHMMSequenceLogo = new JCheckBoxMenuItem();
 +
 +  protected JCheckBoxMenuItem normaliseHMMSequenceLogo = new JCheckBoxMenuItem();
 +
    protected JCheckBoxMenuItem applyAutoAnnotationSettings = new JCheckBoxMenuItem();
  
    protected JMenuItem openFeatureSettings;
      {
  
        // for Web-page embedding using id=align-frame-div
-       setName("jalview-alignment");
+       setName(Platform.getAppID("alignment"));
  
        jbInit();
        setJMenuBar(alignFrameMenuBar);
    private void jbInit() throws Exception
    {
      initColourMenu();
 -
 +  
      JMenuItem saveAs = new JMenuItem(
              MessageManager.getString("action.save_as"));
      ActionListener al = new ActionListener()
          saveAs_actionPerformed();
        }
      };
 -
 +  
      // FIXME getDefaultToolkit throws an exception in Headless mode
-     KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
-                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
+     KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, Platform.SHORTCUT_KEY_MASK | InputEvent.SHIFT_DOWN_MASK,
              false);
      addMenuActionAndAccelerator(keyStroke, saveAs, al);
 -
 +  
      closeMenuItem.setText(MessageManager.getString("action.close"));
-     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W, Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, closeMenuItem, al);
 -
 +  
      JMenu editMenu = new JMenu(MessageManager.getString("action.edit"));
      JMenu viewMenu = new JMenu(MessageManager.getString("action.view"));
      JMenu annotationsMenu = new JMenu(
      JMenu calculateMenu = new JMenu(
              MessageManager.getString("action.calculate"));
      webService.setText(MessageManager.getString("action.web_service"));
 +
 +    initHMMERMenu();
 +
      JMenuItem selectAllSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.select_all"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, selectAllSequenceMenuItem, al);
 -
 +  
      JMenuItem deselectAllSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.deselect_all"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, deselectAllSequenceMenuItem, al);
 -
 +  
      JMenuItem invertSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.invert_sequence_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, invertSequenceMenuItem, al);
 -
 +  
      JMenuItem grpsFromSelection = new JMenuItem(
              MessageManager.getString("action.make_groups_selection"));
      grpsFromSelection.addActionListener(new ActionListener()
      JMenuItem remove2LeftMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_left"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_L,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, remove2LeftMenuItem, al);
 -
 +  
      JMenuItem remove2RightMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_right"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, remove2RightMenuItem, al);
 -
 +  
      JMenuItem removeGappedColumnMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_empty_columns"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, removeGappedColumnMenuItem, al);
 -
 +  
      JMenuItem removeAllGapsMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_all_gaps"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
-                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
+             Platform.SHORTCUT_KEY_MASK
+                     | InputEvent.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
        }
      };
      addMenuActionAndAccelerator(keyStroke, removeAllGapsMenuItem, al);
 -
 +  
      JMenuItem justifyLeftMenuItem = new JMenuItem(
              MessageManager.getString("action.left_justify_alignment"));
      justifyLeftMenuItem.addActionListener(new ActionListener()
          sortGroupMenuItem_actionPerformed(e);
        }
      });
 -
 +    JMenuItem sortEValueMenuItem = new JMenuItem(
 +            MessageManager.getString("action.by_evalue"));
 +    sortEValueMenuItem.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        sortEValueMenuItem_actionPerformed(e);
 +      }
 +    });
 +    JMenuItem sortBitScoreMenuItem = new JMenuItem(
 +            MessageManager.getString("action.by_bit_score"));
 +    sortBitScoreMenuItem.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        sortBitScoreMenuItem_actionPerformed(e);
 +      }
 +    });
 +  
      JMenuItem removeRedundancyMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_redundancy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
      };
      addMenuActionAndAccelerator(keyStroke, removeRedundancyMenuItem, al);
  
 +    JMenuItem filterByEValue = new JMenuItem(
 +            MessageManager.getString("action.filter_by_evalue"));
 +    filterByEValue.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        filterByEValue_actionPerformed();
 +      }
 +
 +    });
 +
 +    JMenuItem filterByScore = new JMenuItem(
 +            MessageManager.getString("action.filter_by_score"));
 +    filterByScore.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        filterByScore_actionPerformed();
 +      }
 +
 +    });
 +  
      JMenuItem pairwiseAlignmentMenuItem = new JMenuItem(
              MessageManager.getString("action.pairwise_alignment"));
      pairwiseAlignmentMenuItem.addActionListener(new ActionListener()
          pairwiseAlignmentMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      this.getContentPane().setLayout(new BorderLayout());
      alignFrameMenuBar.setFont(new java.awt.Font("Verdana", 0, 11));
      statusBar.setBackground(Color.white);
      statusBar.setFont(new java.awt.Font("Verdana", 0, 11));
      statusBar.setBorder(BorderFactory.createLineBorder(Color.black));
      statusBar.setText(MessageManager.getString("label.status_bar"));
 +
      outputTextboxMenu
              .setText(MessageManager.getString("label.out_to_textbox"));
  
 +
      annotationPanelMenuItem.setActionCommand("");
      annotationPanelMenuItem
              .setText(MessageManager.getString("label.show_annotations"));
      final JCheckBoxMenuItem sortAnnByLabel = new JCheckBoxMenuItem(
              MessageManager.getString("label.sort_annotations_by_label"));
  
 +
      sortAnnBySequence.setSelected(
              sortAnnotationsBy == SequenceAnnotationOrder.SEQUENCE_AND_LABEL);
      sortAnnBySequence.addActionListener(new ActionListener()
          colourTextMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem htmlMenuItem = new JMenuItem(
              MessageManager.getString("label.html"));
      htmlMenuItem.addActionListener(new ActionListener()
          htmlMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem createBioJS = new JMenuItem(
              MessageManager.getString("label.biojs_html_export"));
      createBioJS.addActionListener(new java.awt.event.ActionListener()
          bioJSMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem overviewMenuItem = new JMenuItem(
              MessageManager.getString("label.overview_window"));
      overviewMenuItem.addActionListener(new ActionListener()
          overviewMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      undoMenuItem.setEnabled(false);
      undoMenuItem.setText(MessageManager.getString("action.undo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, undoMenuItem, al);
 -
 +  
      redoMenuItem.setEnabled(false);
      redoMenuItem.setText(MessageManager.getString("action.redo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, redoMenuItem, al);
 -
 +  
      wrapMenuItem.setText(MessageManager.getString("label.wrap"));
      wrapMenuItem.addActionListener(new ActionListener()
      {
          wrapMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem printMenuItem = new JMenuItem(
              MessageManager.getString("action.print"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, printMenuItem, al);
 -
 +  
      renderGapsMenuItem
              .setText(MessageManager.getString("action.show_gaps"));
      renderGapsMenuItem.setState(true);
          renderGapsMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem findMenuItem = new JMenuItem(
              MessageManager.getString("action.find"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      findMenuItem.setToolTipText(JvSwingUtils.wrapTooltip(true,
              MessageManager.getString("label.find_tip")));
      al = new ActionListener()
  
      showSeqFeatures.setText(
              MessageManager.getString("label.show_sequence_features"));
 +
      showSeqFeatures.addActionListener(new ActionListener()
      {
        @Override
              .setText(MessageManager.getString("label.show_database_refs"));
      showDbRefsMenuitem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showDbRefs_actionPerformed(e);
        }
 -
 +  
      });
      showNpFeatsMenuitem.setText(
              MessageManager.getString("label.show_non_positional_features"));
      showNpFeatsMenuitem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showNpFeats_actionPerformed(e);
        }
 -
 +  
      });
      showGroupConservation
              .setText(MessageManager.getString("label.group_conservation"));
      showGroupConservation.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showGroupConservation_actionPerformed(e);
        }
 -
 +  
      });
  
      showGroupConsensus
              .setText(MessageManager.getString("label.group_consensus"));
      showGroupConsensus.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showGroupConsensus_actionPerformed(e);
        }
 -
 +  
      });
      showConsensusHistogram.setText(
              MessageManager.getString("label.show_consensus_histogram"));
      showConsensusHistogram.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showConsensusHistogram_actionPerformed(e);
        }
 -
 +  
      });
      showSequenceLogo
              .setText(MessageManager.getString("label.show_consensus_logo"));
      showSequenceLogo.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          showSequenceLogo_actionPerformed(e);
        }
 -
 +  
      });
      normaliseSequenceLogo
              .setText(MessageManager.getString("label.norm_consensus_logo"));
      normaliseSequenceLogo.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          normaliseSequenceLogo_actionPerformed(e);
        }
 -
 +  
      });
      applyAutoAnnotationSettings
              .setText(MessageManager.getString("label.apply_all_groups"));
          applyAutoAnnotationSettings_actionPerformed(e);
        }
      });
 -
 +  
      ButtonGroup buttonGroup = new ButtonGroup();
      final JRadioButtonMenuItem showAutoFirst = new JRadioButtonMenuItem(
              MessageManager.getString("label.show_first"));
          sortAnnotations_actionPerformed();
        }
      });
 -
 +  
      JMenuItem deleteGroups = new JMenuItem(
              MessageManager.getString("action.undefine_groups"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, deleteGroups, al);
 -
 +  
      JMenuItem annotationColumn = new JMenuItem(
              MessageManager.getString("action.select_by_annotation"));
      annotationColumn.addActionListener(new ActionListener()
          annotationColumn_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem createGroup = new JMenuItem(
              MessageManager.getString("action.create_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, createGroup, al);
 -
 +  
      JMenuItem unGroup = new JMenuItem(
              MessageManager.getString("action.remove_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
-                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
+             Platform.SHORTCUT_KEY_MASK
+                     | InputEvent.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
        }
      };
      addMenuActionAndAccelerator(keyStroke, unGroup, al);
 -
 +  
      copy.setText(MessageManager.getString("action.copy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
  
      al = new ActionListener()
      {
        }
      };
      addMenuActionAndAccelerator(keyStroke, copy, al);
 -
 +  
      cut.setText(MessageManager.getString("action.cut"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, cut, al);
 -
 +  
      JMenuItem delete = new JMenuItem(
              MessageManager.getString("action.delete"));
      delete.addActionListener(new ActionListener()
          delete_actionPerformed();
        }
      });
 -
 +  
      pasteMenu.setText(MessageManager.getString("action.paste"));
      JMenuItem pasteNew = new JMenuItem(
              MessageManager.getString("label.to_new_alignment"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
-                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
+             Platform.SHORTCUT_KEY_MASK
+                     | InputEvent.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        pasteNew_actionPerformed(e);
 +        try
 +        {
 +          pasteNew_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      };
      addMenuActionAndAccelerator(keyStroke, pasteNew, al);
 -
 +  
      JMenuItem pasteThis = new JMenuItem(
              MessageManager.getString("label.to_this_alignment"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        pasteThis_actionPerformed(e);
 +        try
 +        {
 +          pasteThis_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      };
      addMenuActionAndAccelerator(keyStroke, pasteThis, al);
 -
 +  
      JMenuItem createPNG = new JMenuItem("PNG");
      createPNG.addActionListener(new ActionListener()
      {
      });
      createPNG.setActionCommand(
              MessageManager.getString("label.save_png_image"));
 -
      JMenuItem font = new JMenuItem(MessageManager.getString("action.font"));
      font.addActionListener(new ActionListener()
      {
          createEPS(null);
        }
      });
 -
 +  
      JMenuItem createSVG = new JMenuItem("SVG");
      createSVG.addActionListener(new ActionListener()
      {
          createSVG(null);
        }
      });
 -
 +  
      JMenuItem loadTreeMenuItem = new JMenuItem(
              MessageManager.getString("label.load_associated_tree"));
      loadTreeMenuItem.setActionCommand(
          loadTreeMenuItem_actionPerformed(e);
        }
      });
 -
 +  
      scaleAbove.setVisible(false);
      scaleAbove.setText(MessageManager.getString("action.scale_above"));
      scaleAbove.addActionListener(new ActionListener()
              .setText(MessageManager.getString("label.automatic_scrolling"));
      followHighlightMenuItem.addActionListener(new ActionListener()
      {
 -
 +  
        @Override
        public void actionPerformed(ActionEvent e)
        {
          followHighlight_actionPerformed();
        }
 -
 +  
      });
 -
 +  
      sortByTreeMenu
              .setText(MessageManager.getString("action.by_tree_order"));
      sort.setText(MessageManager.getString("action.sort"));
        @Override
        public void menuSelected(MenuEvent e)
        {
+         enableSortMenuOptions();
+       }
+       @Override
+       public void menuDeselected(MenuEvent e)
+       {
+       }
+       @Override
+       public void menuCanceled(MenuEvent e)
+       {
+       }
+     });
+     sortByTreeMenu.addMenuListener(new MenuListener()
+     {
+       @Override
+       public void menuSelected(MenuEvent e)
+       {
          buildTreeSortMenu();
        }
 -
 +  
        @Override
        public void menuDeselected(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuCanceled(MenuEvent e)
        {
      sort.add(sortByAnnotScore);
      sort.addMenuListener(new javax.swing.event.MenuListener()
      {
 -
 +  
        @Override
        public void menuCanceled(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuDeselected(MenuEvent e)
        {
        }
 -
 +  
        @Override
        public void menuSelected(MenuEvent e)
        {
          showReverse_actionPerformed(true);
        }
      });
 -
 +  
      JMenuItem extractScores = new JMenuItem(
              MessageManager.getString("label.extract_scores"));
      extractScores.addActionListener(new ActionListener()
      });
      extractScores.setVisible(true);
      // JBPNote: TODO: make gui for regex based score extraction
 -
 +  
      // for show products actions see AlignFrame.canShowProducts
      showProducts.setText(MessageManager.getString("label.get_cross_refs"));
 -
 +  
      runGroovy.setText(MessageManager.getString("label.run_groovy"));
      runGroovy.setToolTipText(
              MessageManager.getString("label.run_groovy_tip"));
          fetchSequence_actionPerformed();
        }
      });
 -
 +  
      JMenuItem associatedData = new JMenuItem(
              MessageManager.getString("label.load_features_annotations"));
      associatedData.addActionListener(new ActionListener()
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        associatedData_actionPerformed(e);
 +        try
 +        {
 +          associatedData_actionPerformed(e);
 +        } catch (IOException | InterruptedException e1)
 +        {
 +          // TODO Auto-generated catch block
 +          e1.printStackTrace();
 +        }
        }
      });
      loadVcf = new JMenuItem(
          listenToViewSelections_actionPerformed(e);
        }
      });
 -
 +  
      JMenu addSequenceMenu = new JMenu(
              MessageManager.getString("label.add_sequences"));
      JMenuItem addFromFile = new JMenuItem(
          hiddenMarkers_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem invertColSel = new JMenuItem(
              MessageManager.getString("action.invert_column_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
-                     | jalview.util.ShortcutKeyMaskExWrapper.ALT_DOWN_MASK,
+             Platform.SHORTCUT_KEY_MASK
+                     | InputEvent.ALT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
        }
      };
      addMenuActionAndAccelerator(keyStroke, invertColSel, al);
 -
 +  
      showComplementMenuItem.setVisible(false);
      showComplementMenuItem.addActionListener(new ActionListener()
      {
          showComplement_actionPerformed(showComplementMenuItem.getState());
        }
      });
 -
 +  
      tabbedPane.addChangeListener(new javax.swing.event.ChangeListener()
      {
        @Override
            tabbedPane_mousePressed(e);
          }
        }
 -
 +  
        @Override
        public void mouseReleased(MouseEvent e)
        {
          tabbedPane_focusGained(e);
        }
      });
 -
 +  
      JMenuItem save = new JMenuItem(MessageManager.getString("action.save"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, save, al);
 -
 +  
      reload.setEnabled(false);
      reload.setText(MessageManager.getString("action.reload"));
      reload.addActionListener(new ActionListener()
          reload_actionPerformed(e);
        }
      });
 -
 +  
      JMenuItem newView = new JMenuItem(
              MessageManager.getString("action.new_view"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
+             Platform.SHORTCUT_KEY_MASK, false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, newView, al);
 -
 +  
      tabbedPane.setToolTipText("<html><i>"
              + MessageManager.getString("label.rename_tab_eXpand_reGroup")
              + "</i></html>");
 -
 +  
      formatMenu.setText(MessageManager.getString("action.format"));
      JMenu selectMenu = new JMenu(MessageManager.getString("action.select"));
  
          idRightAlign_actionPerformed(e);
        }
      });
 -
 +  
      gatherViews.setEnabled(false);
      gatherViews.setText(MessageManager.getString("action.gather_views"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, gatherViews, al);
 -
 +  
      expandViews.setEnabled(false);
      expandViews.setText(MessageManager.getString("action.expand_views"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
        }
      };
      addMenuActionAndAccelerator(keyStroke, expandViews, al);
 -
 +  
      JMenuItem pageSetup = new JMenuItem(
              MessageManager.getString("action.page_setup"));
      pageSetup.addActionListener(new ActionListener()
      });
      JMenuItem selectHighlighted = new JMenuItem(
              MessageManager.getString("action.select_highlighted_columns"));
-     selectHighlighted.setToolTipText(
-             MessageManager.getString("tooltip.select_highlighted_columns"));
+     selectHighlighted.setToolTipText(JvSwingUtils.wrapTooltip(true, 
+             MessageManager.getString("tooltip.select_highlighted_columns")));
      al = new ActionListener()
      {
        @Override
          selectHighlightedColumns_actionPerformed(actionEvent);
        }
      };
 +    JMenuItem Filter = new JMenuItem(
 +            MessageManager.getString("action.select_highlighted_columns"));
 +    selectHighlighted.setToolTipText(
 +            MessageManager.getString("tooltip.select_highlighted_columns"));
 +    al = new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent actionEvent)
 +      {
 +        selectHighlightedColumns_actionPerformed(actionEvent);
 +      }
 +    };
      selectHighlighted.addActionListener(al);
      JMenu tooltipSettingsMenu = new JMenu(
              MessageManager.getString("label.sequence_id_tooltip"));
      JMenu autoAnnMenu = new JMenu(
              MessageManager.getString("label.autocalculated_annotation"));
 -
 +  
      JMenu exportImageMenu = new JMenu(
              MessageManager.getString("label.export_image"));
      JMenu fileMenu = new JMenu(MessageManager.getString("action.file"));
      alignFrameMenuBar.add(formatMenu);
      alignFrameMenuBar.add(colourMenu);
      alignFrameMenuBar.add(calculateMenu);
 +    alignFrameMenuBar.add(webService);
      if (!Platform.isJS())
      {
 -      alignFrameMenuBar.add(webService);
 +      alignFrameMenuBar.add(hmmerMenu);
      }
 -
 +  
      fileMenu.add(fetchSequence);
      fileMenu.add(addSequenceMenu);
      fileMenu.add(reload);
      }
      fileMenu.addSeparator();
      fileMenu.add(closeMenuItem);
 -
 +  
      pasteMenu.add(pasteNew);
      pasteMenu.add(pasteThis);
      editMenu.add(undoMenuItem);
      // editMenu.add(justifyRightMenuItem);
      // editMenu.addSeparator();
      editMenu.add(padGapsMenuitem);
 -
 +    editMenu.addSeparator();
 +    editMenu.add(filterByEValue);
 +    editMenu.add(filterByScore);
 +  
      showMenu.add(showAllColumns);
      showMenu.add(showAllSeqs);
      showMenu.add(showAllhidden);
      viewMenu.add(alignmentProperties);
      viewMenu.addSeparator();
      viewMenu.add(overviewMenuItem);
 -
 +  
      annotationsMenu.add(annotationPanelMenuItem);
      annotationsMenu.addSeparator();
      annotationsMenu.add(showAllAlAnnotations);
      sort.add(sortLengthMenuItem);
      sort.add(sortGroupMenuItem);
      sort.add(sortPairwiseMenuItem);
 +    sort.add(sortEValueMenuItem);
 +    sort.add(sortBitScoreMenuItem);
      sort.add(sortByTreeMenu);
      calculateMenu.add(sort);
      calculateMenu.add(calculateTree);
        calculateMenu.addSeparator();
        calculateMenu.add(runGroovy);
      }
 -
      webServiceNoServices = new JMenuItem(
              MessageManager.getString("label.no_services"));
      webService.add(webServiceNoServices);
      this.getContentPane().add(statusPanel, java.awt.BorderLayout.SOUTH);
      statusPanel.add(statusBar, null);
      this.getContentPane().add(tabbedPane, java.awt.BorderLayout.CENTER);
 -
 +  
      formatMenu.add(font);
      formatMenu.addSeparator();
      formatMenu.add(wrapMenuItem);
      // selectMenu.add(listenToViewSelections);
    }
  
 +  /**
 +   * Constructs the entries on the HMMER menu
 +   */
 +  protected void initHMMERMenu()
 +  {
 +    /*
 +     * hmmbuild
 +     */
 +    JMenu hmmBuild = new JMenu(MessageManager.getString("label.hmmbuild"));
 +    JMenuItem hmmBuildSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmBuildSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmBuild_actionPerformed(false);
 +      }
 +    });
 +    JMenuItem hmmBuildRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmbuild"));
 +    hmmBuildRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmBuild_actionPerformed(true);
 +      }
 +    });
 +    hmmBuild.add(hmmBuildRun);
 +    hmmBuild.add(hmmBuildSettings);
 +
 +    /*
 +     * hmmalign
 +     */
 +    JMenu hmmAlign = new JMenu(MessageManager.getString("label.hmmalign"));
 +    JMenuItem hmmAlignRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmalign"));
 +    hmmAlignRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmAlign_actionPerformed(true);
 +      }
 +    });
 +    JMenuItem hmmAlignSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmAlignSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmAlign_actionPerformed(false);
 +      }
 +    });
 +    hmmAlign.add(hmmAlignRun);
 +    hmmAlign.add(hmmAlignSettings);
 +
 +    /*
 +     * hmmsearch
 +     */
 +    JMenu hmmSearch = new JMenu(
 +            MessageManager.getString("label.hmmsearch"));
 +    JMenuItem hmmSearchSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    hmmSearchSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmSearch_actionPerformed(false);
 +      }
 +    });
 +    JMenuItem hmmSearchRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "hmmsearch"));
 +    hmmSearchRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        hmmSearch_actionPerformed(true);
 +      }
 +    });
 +    JMenuItem addDatabase = new JMenuItem(
 +            MessageManager.getString("label.add_database"));
 +    addDatabase.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        try
 +        {
 +          addDatabase_actionPerformed();
 +        } catch (IOException e1)
 +        {
 +          e1.printStackTrace();
 +        }
 +      }
 +    });
 +    hmmSearch.add(hmmSearchRun);
 +    hmmSearch.add(hmmSearchSettings);
 +    // hmmSearch.add(addDatabase);
 +
 +    /*
 +     * jackhmmer
 +     */
 +    JMenu jackhmmer = new JMenu(
 +            MessageManager.getString("label.jackhmmer"));
 +    JMenuItem jackhmmerSettings = new JMenuItem(
 +            MessageManager.getString("label.edit_settings_and_run"));
 +    jackhmmerSettings.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        jackhmmer_actionPerformed(false);
 +      }
 +    });
 +    JMenuItem jackhmmerRun = new JMenuItem(MessageManager.formatMessage(
 +            "label.action_with_default_settings", "jackhmmer"));
 +    jackhmmerRun.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        jackhmmer_actionPerformed(true);
 +      }
 +
 +    });
 +    /*
 +    JMenuItem addDatabase = new JMenuItem(
 +            MessageManager.getString("label.add_database"));
 +    addDatabase.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        try
 +        {
 +          addDatabase_actionPerformed();
 +        } catch (IOException e1)
 +        {
 +          e1.printStackTrace();
 +        }
 +      }
 +    });
 +    */
 +    jackhmmer.add(jackhmmerRun);
 +    jackhmmer.add(jackhmmerSettings);
 +    // hmmSearch.add(addDatabase);
 +
 +    /*
 +     * top level menu
 +     */
 +    hmmerMenu.setText(MessageManager.getString("action.hmmer"));
 +    hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
 +    hmmerMenu.add(hmmBuild);
 +    hmmerMenu.add(hmmAlign);
 +    hmmerMenu.add(hmmSearch);
 +    hmmerMenu.add(jackhmmer);
 +
 +  }
 +
+   protected void enableSortMenuOptions()
+   {
+   }
+   
    protected void loadVcf_actionPerformed()
    {
    }
    {
    }
  
 +  protected void sortEValueMenuItem_actionPerformed(ActionEvent e)
 +  {
 +  }
 +
 +  protected void sortBitScoreMenuItem_actionPerformed(ActionEvent e)
 +  {
 +  }
 +
    protected void removeRedundancyMenuItem_actionPerformed(ActionEvent e)
    {
    }
    }
  
    protected void pasteNew_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
    }
  
    protected void pasteThis_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
    }
  
    {
    }
  
 +  protected void hmmBuild_actionPerformed(boolean withDefaults)
 +  {
 +  }
 +
 +  protected void hmmSearch_actionPerformed(boolean withDefaults)
 +  {
 +  }
 +
 +  protected void jackhmmer_actionPerformed(boolean b)
 +  {
 +  }
 +
 +  protected void addDatabase_actionPerformed()
 +          throws FileFormatException, IOException
 +  {
 +  }
 +
 +  protected void hmmAlign_actionPerformed(boolean withDefaults)
 +  {
 +  }
 +
    public void createPNG(java.io.File f)
    {
    }
    {
    }
  
 +  protected void filterByEValue_actionPerformed()
 +  {
 +  }
 +
 +  protected void filterByScore_actionPerformed()
 +  {
 +  }
 +
    protected void scaleRight_actionPerformed(ActionEvent e)
    {
    }
    }
  
    public void associatedData_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
  
    }
    protected void showComplement_actionPerformed(boolean complement)
    {
    }
+   
  }
@@@ -49,6 -49,7 +49,6 @@@ import java.awt.Insets
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 -import java.awt.event.FocusEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.KeyListener;
  import java.awt.event.MouseAdapter;
@@@ -56,16 -57,13 +56,16 @@@ import java.awt.event.MouseEvent
  import java.util.Arrays;
  import java.util.List;
  
 +import javax.swing.AbstractButton;
  import javax.swing.AbstractCellEditor;
  import javax.swing.BorderFactory;
 +import javax.swing.BoxLayout;
  import javax.swing.ButtonGroup;
  import javax.swing.DefaultListCellRenderer;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
 +import javax.swing.JComponent;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
@@@ -89,8 -87,6 +89,8 @@@ import javax.swing.event.ChangeListener
  import javax.swing.table.TableCellEditor;
  import javax.swing.table.TableCellRenderer;
  
 +import net.miginfocom.swing.MigLayout;
 +
  /**
   * Base class for the Preferences panel.
   * 
@@@ -165,15 -161,10 +165,15 @@@ public class GPreferences extends JPane
  
    protected JCheckBox showConsensLogo = new JCheckBox();
  
 +  protected JCheckBox showInformationHistogram = new JCheckBox();
 +
 +  protected JCheckBox showHMMLogo = new JCheckBox();
 +
    protected JCheckBox showDbRefTooltip = new JCheckBox();
  
    protected JCheckBox showNpTooltip = new JCheckBox();
  
 +
    /*
     * Structure tab and components
     */
    protected JCheckBox sortByTree = new JCheckBox();
  
    /*
 +   * hmmer tab and components
 +   */
 +  protected JPanel hmmerTab;
 +
 +  protected JCheckBox hmmrTrimTermini;
 +
 +  protected AbstractButton hmmerBackgroundUniprot;
 +
 +  protected AbstractButton hmmerBackgroundAlignment;
 +
 +  protected JTextField hmmerSequenceCount;
 +
 +  protected JTextField hmmerPath;
 +
 +  protected JTextField cygwinPath;
 +
 +  /*
     * Web Services tab
     */
    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(initEditingTab(),
              MessageManager.getString("label.editing"));
  
 +    tabbedPane.add(initHMMERTab(), MessageManager.getString("label.hmmer"));
 +
      /*
       * 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");
      }
  
      /*
       * Handler to validate a tab before leaving it - currently only for
 -     * Structure.
 +     * Structure
       */
      tabbedPane.addChangeListener(new ChangeListener()
      {
    }
  
    /**
 -   * Initialises the Output tab
 +   * Initialises the hmmer tabbed panel
 +   * 
 +   * @return
 +   */
 +  private JPanel initHMMERTab()
 +  {
 +    hmmerTab = new JPanel();
 +    hmmerTab.setLayout(new BoxLayout(hmmerTab, BoxLayout.Y_AXIS));
 +    hmmerTab.setLayout(new MigLayout("flowy"));
 +
 +    /*
 +     * path to hmmer binaries folder
 +     */
 +    JPanel installationPanel = new JPanel(new MigLayout("flowy"));
 +    // new FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(installationPanel,
 +            MessageManager.getString("label.installation"), true);
 +    hmmerTab.add(installationPanel);
 +    JLabel hmmerLocation = new JLabel(
 +            MessageManager.getString("label.hmmer_location"));
 +    hmmerLocation.setFont(LABEL_FONT);
 +    final int pathFieldLength = 40;
 +    hmmerPath = new JTextField(pathFieldLength);
 +    hmmerPath.addMouseListener(new MouseAdapter()
 +    {
 +      @Override
 +      public void mouseClicked(MouseEvent e)
 +      {
 +        if (e.getClickCount() == 2)
 +        {
 +          String chosen = openFileChooser(true);
 +          if (chosen != null)
 +          {
 +            hmmerPath.setText(chosen);
 +            validateHmmerPath();
 +          }
 +        }
 +      }
 +    });
 +    installationPanel.add(hmmerLocation);
 +    installationPanel.add(hmmerPath);
 +
 +    /*
 +     * path to Cygwin binaries folder (for Windows)
 +     */
 +    if (Platform.isWindowsAndNotJS())
 +    {
 +      JLabel cygwinLocation = new JLabel(
 +              MessageManager.getString("label.cygwin_location"));
 +      cygwinLocation.setFont(LABEL_FONT);
 +      cygwinPath = new JTextField(pathFieldLength);
 +      cygwinPath.addMouseListener(new MouseAdapter()
 +      {
 +        @Override
 +        public void mouseClicked(MouseEvent e)
 +        {
 +          if (e.getClickCount() == 2)
 +          {
 +            String chosen = openFileChooser(true);
 +            if (chosen != null)
 +            {
 +              cygwinPath.setText(chosen);
 +              validateCygwinPath();
 +            }
 +          }
 +        }
 +      });
 +      installationPanel.add(cygwinLocation);
 +      installationPanel.add(cygwinPath);
 +    }
 +
 +    /*
 +     * preferences for hmmalign
 +     */
 +    JPanel alignOptionsPanel = new JPanel(new MigLayout());
 +    // new FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(alignOptionsPanel,
 +            MessageManager.getString("label.hmmalign_options"), true);
 +    hmmerTab.add(alignOptionsPanel);
 +    hmmrTrimTermini = new JCheckBox();
 +    hmmrTrimTermini.setFont(LABEL_FONT);
 +    hmmrTrimTermini.setText(MessageManager.getString("label.trim_termini"));
 +    alignOptionsPanel.add(hmmrTrimTermini);
 +
 +    /*
 +     * preferences for hmmsearch
 +     */
 +    JPanel searchOptions = new JPanel(new MigLayout());
 +    // FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(searchOptions,
 +            MessageManager.getString("label.hmmsearch_options"), true);
 +    hmmerTab.add(searchOptions);
 +    JLabel sequencesToKeep = new JLabel(
 +            MessageManager.getString("label.no_of_sequences"));
 +    sequencesToKeep.setFont(LABEL_FONT);
 +    searchOptions.add(sequencesToKeep);
 +    hmmerSequenceCount = new JTextField(5);
 +    searchOptions.add(hmmerSequenceCount);
 +
 +    /*
 +     * preferences for Information Content annotation
 +     */
 +    // JPanel dummy = new JPanel(new FlowLayout(FlowLayout.LEFT));
 +    JPanel annotationOptions = new JPanel(new MigLayout("left"));
 +    JvSwingUtils.createTitledBorder(annotationOptions,
 +            MessageManager.getString("label.information_annotation"), true);
 +    // dummy.add(annotationOptions);
 +    hmmerTab.add(annotationOptions);
 +    ButtonGroup backgroundOptions = new ButtonGroup();
 +    hmmerBackgroundUniprot = new JRadioButton(
 +            MessageManager.getString("label.freq_uniprot"));
 +    hmmerBackgroundUniprot.setFont(LABEL_FONT);
 +    hmmerBackgroundAlignment = new JRadioButton(
 +            MessageManager.getString("label.freq_alignment"));
 +    hmmerBackgroundAlignment.setFont(LABEL_FONT);
 +    backgroundOptions.add(hmmerBackgroundUniprot);
 +    backgroundOptions.add(hmmerBackgroundAlignment);
 +    backgroundOptions.setSelected(hmmerBackgroundUniprot.getModel(), true);
 +    // disable buttons for now as annotation only uses Uniprot background
 +    hmmerBackgroundAlignment.setEnabled(false);
 +    hmmerBackgroundUniprot.setEnabled(false);
 +    annotationOptions.add(hmmerBackgroundUniprot, "wrap");
 +    annotationOptions.add(hmmerBackgroundAlignment);
 +
 +    return hmmerTab;
 +  }
 +
 +  /**
 +   * Initialises the Output tabbed panel.
     * 
     * @return
     */
      protColourLabel.setHorizontalAlignment(SwingConstants.LEFT);
      protColourLabel.setText(
              MessageManager.getString("label.prot_alignment_colour") + " ");
 -    JvSwingUtils.addtoLayout(coloursTab,
 +    GPreferences.addtoLayout(coloursTab,
              MessageManager
                      .getString("label.default_colour_scheme_for_alignment"),
              protColourLabel, protColour);
      nucColourLabel.setHorizontalAlignment(SwingConstants.LEFT);
      nucColourLabel.setText(
              MessageManager.getString("label.nuc_alignment_colour") + " ");
 -    JvSwingUtils.addtoLayout(coloursTab,
 +    GPreferences.addtoLayout(coloursTab,
              MessageManager
                      .getString("label.default_colour_scheme_for_alignment"),
              nucColourLabel, nucColour);
      annotationShding.setBorder(new TitledBorder(
              MessageManager.getString("label.annotation_shading_default")));
      annotationShding.setLayout(new GridLayout(1, 2));
 -    JvSwingUtils.addtoLayout(annotationShding,
 +    GPreferences.addtoLayout(annotationShding,
              MessageManager.getString(
                      "label.default_minimum_colour_annotation_shading"),
              mincolourLabel, minColour);
 -    JvSwingUtils.addtoLayout(annotationShding,
 +    GPreferences.addtoLayout(annotationShding,
              MessageManager.getString(
                      "label.default_maximum_colour_annotation_shading"),
              maxcolourLabel, maxColour);
      viewerLabel.setBounds(new Rectangle(10, ypos, 200, height));
      structureTab.add(viewerLabel);
  
-     structViewer.setFont(LABEL_FONT);
-     structViewer.setBounds(new Rectangle(160, ypos, 120, height));
-     structViewer.addItem(ViewerType.JMOL.name());
-     structViewer.addItem(ViewerType.CHIMERA.name());
-     structViewer.addActionListener(new ActionListener()
+     if (!Platform.isJS())
      {
-       @Override
-       public void actionPerformed(ActionEvent e)
+       structViewer.setFont(LABEL_FONT);
+       structViewer.setBounds(new Rectangle(160, ypos, 120, height));
+       structViewer.addItem(ViewerType.JMOL.name());
+       structViewer.addItem(ViewerType.CHIMERA.name());
+       structViewer.addActionListener(new ActionListener()
        {
-         structureViewer_actionPerformed(
-                 (String) structViewer.getSelectedItem());
-       }
-     });
-     structureTab.add(structViewer);
+         @Override
+         public void actionPerformed(ActionEvent e)
+         {
+           structureViewer_actionPerformed(
+                   (String) structViewer.getSelectedItem());
+         }
+       });
+       structureTab.add(structViewer);
+     }
  
      ypos += lineSpacing;
      JLabel pathLabel = new JLabel();
        {
          if (e.getClickCount() == 2)
          {
 -          String chosen = openFileChooser();
 +          String chosen = openFileChooser(false);
            if (chosen != null)
            {
              chimeraPath.setText(chosen);
      /*
       * hide Chimera options in JalviewJS
       */
-     if (Platform.isJS()) 
+     if (Platform.isJS())
      {
        pathLabel.setVisible(false);
        chimeraPath.setVisible(false);
        viewerLabel.setVisible(false);
        structViewer.setVisible(false);
      }
-     
      return structureTab;
    }
  
     * 
     * @return
     */
 -  protected String openFileChooser()
 +  protected String openFileChooser(boolean forFolder)
    {
      String choice = null;
      JFileChooser chooser = new JFileChooser();
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
  
      // chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
      return choice;
    }
  
 -  /**
 -   * Validate the structure tab preferences; if invalid, set focus on this tab.
 -   * 
 -   * @param e
 -   */
 -  protected boolean validateStructure(FocusEvent e)
 -  {
 -    if (!validateStructure())
 -    {
 -      e.getComponent().requestFocusInWindow();
 -      return false;
 -    }
 -    return true;
 -  }
 -
    protected boolean validateStructure()
    {
      return false;
      boolean ret = false;
      String warningMessage = MessageManager
              .getString("label.warning_confirm_change_reverse");
-     int confirm = JvOptionPane.showConfirmDialog(Desktop.desktop,
+     int confirm = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
              warningMessage,
              MessageManager.getString("label.change_increment_decrement"),
              JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE);
      }
  
    }
 +
 +  protected void validateHmmerPath()
 +  {
 +  }
 +
 +  protected void validateCygwinPath()
 +  {
 +  }
 +
 +  /**
 +   * A helper method to add a panel containing a label and a component to a
 +   * panel
 +   * 
 +   * @param panel
 +   * @param tooltip
 +   * @param label
 +   * @param valBox
 +   */
 +  protected static void addtoLayout(JPanel panel, String tooltip,
 +          JComponent label, JComponent valBox)
 +  {
 +    JPanel laypanel = new JPanel(new GridLayout(1, 2));
 +    JPanel labPanel = new JPanel(new BorderLayout());
 +    JPanel valPanel = new JPanel();
 +    labPanel.setBounds(new Rectangle(7, 7, 158, 23));
 +    valPanel.setBounds(new Rectangle(172, 7, 270, 23));
 +    labPanel.add(label, BorderLayout.WEST);
 +    valPanel.add(valBox);
 +    laypanel.add(labPanel);
 +    laypanel.add(valPanel);
 +    valPanel.setToolTipText(tooltip);
 +    labPanel.setToolTipText(tooltip);
 +    valBox.setToolTipText(tooltip);
 +    panel.add(laypanel);
 +    panel.validate();
 +  }
  }
  
@@@ -28,6 -28,7 +28,7 @@@ import jalview.analysis.Conservation
  import jalview.analysis.PCA;
  import jalview.analysis.scoremodels.ScoreModels;
  import jalview.analysis.scoremodels.SimilarityParams;
+ import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureColourI;
  import jalview.api.ViewStyleI;
  import jalview.api.analysis.ScoreModelI;
@@@ -41,7 -42,6 +42,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.GeneLocus;
  import jalview.datamodel.GraphLine;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Point;
  import jalview.datamodel.RnaViewerModel;
@@@ -61,6 -61,7 +62,7 @@@ import jalview.gui.AlignmentPanel
  import jalview.gui.AppVarna;
  import jalview.gui.ChimeraViewFrame;
  import jalview.gui.Desktop;
+ import jalview.gui.FeatureRenderer;
  import jalview.gui.JvOptionPane;
  import jalview.gui.OOMWarning;
  import jalview.gui.PCAPanel;
@@@ -73,7 -74,6 +75,7 @@@ import jalview.gui.TreePanel
  import jalview.io.BackupFiles;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
 +import jalview.io.HMMFile;
  import jalview.io.NewickFile;
  import jalview.math.Matrix;
  import jalview.math.MatrixI;
@@@ -84,7 -84,6 +86,6 @@@ import jalview.schemes.ColourSchemeProp
  import jalview.schemes.FeatureColour;
  import jalview.schemes.ResidueProperties;
  import jalview.schemes.UserColourScheme;
- import jalview.structure.StructureSelectionManager;
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.Format;
  import jalview.util.MessageManager;
@@@ -98,9 -97,9 +99,9 @@@ import jalview.viewmodel.ViewportRanges
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
  import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 -import jalview.ws.jws2.Jws2Discoverer;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.jws2.PreferredServiceRegistry;
  import jalview.ws.jws2.dm.AAConSettings;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
  import jalview.ws.params.ArgumentI;
  import jalview.ws.params.AutoCalcSetting;
  import jalview.ws.params.WsParamSetI;
@@@ -153,6 -152,7 +154,7 @@@ import jalview.xml.binding.jalview.Thre
  import jalview.xml.binding.jalview.VAMSAS;
  
  import java.awt.Color;
+ import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.Rectangle;
  import java.io.BufferedReader;
@@@ -163,10 -163,10 +165,10 @@@ import java.io.File
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
+ import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
- import java.lang.reflect.InvocationTargetException;
  import java.math.BigInteger;
  import java.net.MalformedURLException;
  import java.net.URL;
@@@ -226,8 -226,6 +228,8 @@@ public class Jalview2XM
  
    private static final String RNA_PREFIX = "rna_";
  
 +  private static final String HMMER_PREFIX = "hmmer_";
 +
    private static final String UTF_8 = "UTF-8";
  
    /**
    private Map<RnaModel, String> rnaSessions = new HashMap<>();
  
    /**
+    * contains last error message (if any) encountered by XML loader.
+    */
+   String errorMessage = null;
+   /**
+    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
+    * exceptions are raised during project XML parsing
+    */
+   public boolean attemptversion1parse = false;
+   /*
+    * JalviewJS only -- to allow read file bytes to be saved in the
+    * created AlignFrame, allowing File | Reload of a project file to work
+    * 
+    * BH 2019 JAL-3436
+    */
+   private File jarFile;
+   /**
     * A helper method for safely using the value of an optional attribute that
     * may be null if not present in the XML. Answers the boolean value, or false
     * if null.
     * @param _jmap
     * @return
     */
-   public SeqFref newMappingRef(final String sref,
+   protected SeqFref newMappingRef(final String sref,
            final jalview.datamodel.Mapping _jmap)
    {
      SeqFref fref = new SeqFref(sref, "Mapping")
      return fref;
    }
  
-   public SeqFref newAlcodMapRef(final String sref,
+   protected SeqFref newAlcodMapRef(final String sref,
            final AlignedCodonFrame _cf,
            final jalview.datamodel.Mapping _jmap)
    {
      return fref;
    }
  
-   public void resolveFrefedSequences()
+   protected void resolveFrefedSequences()
    {
      Iterator<SeqFref> nextFref = frefedSequence.iterator();
      int toresolve = frefedSequence.size();
     * core method for storing state for a set of AlignFrames.
     * 
     * @param frames
-    *          - frames involving all data to be exported (including containing
-    *          splitframes)
+    *          - frames involving all data to be exported (including those
+    *          contained in splitframes, though not the split frames themselves)
     * @param jout
     *          - project output stream
     */
    private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
    {
      Hashtable<String, AlignFrame> dsses = new Hashtable<>();
  
      /*
        for (int i = frames.size() - 1; i > -1; i--)
        {
          AlignFrame af = frames.get(i);
+         AlignViewport vp = af.getViewport();
          // skip ?
          if (skipList != null && skipList
-                 .containsKey(af.getViewport().getSequenceSetId()))
+                 .containsKey(vp.getSequenceSetId()))
          {
            continue;
          }
  
          String shortName = makeFilename(af, shortNames);
  
-         int apSize = af.getAlignPanels().size();
+         AlignmentI alignment = vp.getAlignment();
+         List<? extends AlignmentViewPanel> panels = af.getAlignPanels();
+         int apSize = panels.size();
          for (int ap = 0; ap < apSize; ap++)
-         {
-           AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
-                   .get(ap);
+           {
+           AlignmentPanel apanel = (AlignmentPanel) panels.get(ap);
            String fileName = apSize == 1 ? shortName : ap + shortName;
            if (!fileName.endsWith(".xml"))
            {
            }
  
            saveState(apanel, fileName, jout, viewIds);
-           String dssid = getDatasetIdRef(
-                   af.getViewport().getAlignment().getDataset());
+         }
+         if (apSize > 0)
+         {
+           // BH moved next bit out of inner loop, not that it really matters.
+           // so we are testing to make sure we actually have an alignment,
+           // apparently.
+           String dssid = getDatasetIdRef(alignment.getDataset());
            if (!dsses.containsKey(dssid))
            {
+             // We have not already covered this data by reference from another
+             // frame.
              dsses.put(dssid, af);
            }
          }
      }
    }
  
+   /**
+    * Each AlignFrame has a single data set associated with it. Note that none of
+    * these frames are split frames, because Desktop.getAlignFrames() collects
+    * top and bottom separately here.
+    * 
+    * @param dsses
+    * @param fileName
+    * @param jout
+    */
    private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
            String fileName, JarOutputStream jout)
    {
  
+     // Note that in saveAllFrames we have associated each specific dataset to
+     // ONE of its associated frames.
      for (String dssids : dsses.keySet())
      {
        AlignFrame _af = dsses.get(dssids);
     * @param out
     *          jar entry name
     */
-   public JalviewModel saveState(AlignmentPanel ap, String fileName,
+   protected JalviewModel saveState(AlignmentPanel ap, String fileName,
            JarOutputStream jout, List<String> viewIds)
    {
      return saveState(ap, fileName, false, jout, viewIds);
     * @param out
     *          jar entry name
     */
-   public JalviewModel saveState(AlignmentPanel ap, String fileName,
+   protected JalviewModel saveState(AlignmentPanel ap, String fileName,
            boolean storeDS, JarOutputStream jout, List<String> viewIds)
    {
      if (viewIds == null)
          jseq.getFeatures().add(features);
        }
  
 +      /*
 +       * save PDB entries for sequence
 +       */
        if (jdatasq.getAllPDBEntries() != null)
        {
          Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
             * only view *should* be coped with sensibly.
             */
            // This must have been loaded, is it still visible?
-           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+           JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
            String matchedFile = null;
            for (int f = frames.length - 1; f > -1; f--)
            {
  
        saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
  
 +      if (jds.hasHMMProfile())
 +      {
 +        saveHmmerProfile(jout, jseq, jds);
 +      }
 +
        // jms.addJSeq(jseq);
        object.getJSeq().add(jseq);
      }
      {
        // FIND ANY ASSOCIATED TREES
        // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
-       if (Desktop.desktop != null)
+       if (Desktop.getDesktopPane() != null)
        {
-         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+         JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
  
          for (int t = 0; t < frames.length; t++)
          {
      /*
       * save PCA viewers
       */
-     if (!storeDS && Desktop.desktop != null)
+     if (!storeDS && Desktop.getDesktopPane() != null)
      {
-       for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+       for (JInternalFrame frame : Desktop.getDesktopPane().getAllFrames())
        {
          if (frame instanceof PCAPanel)
          {
      }
      return object;
    }
 +  /**
 +   * Saves the HMMER profile associated with the sequence as a file in the jar,
 +   * in HMMER format, and saves the name of the file as a child element of the
 +   * XML sequence element
 +   * 
 +   * @param jout
 +   * @param xmlSeq
 +   * @param seq
 +   */
 +  protected void saveHmmerProfile(JarOutputStream jout, JSeq xmlSeq,
 +          SequenceI seq)
 +  {
 +    HiddenMarkovModel profile = seq.getHMM();
 +    if (profile == null)
 +    {
 +      warn("Want to save HMM profile for " + seq.getName()
 +              + " but none found");
 +      return;
 +    }
 +    HMMFile hmmFile = new HMMFile(profile);
 +    String hmmAsString = hmmFile.print();
 +    String jarEntryName = HMMER_PREFIX + nextCounter();
 +    try
 +    {
 +      writeJarEntry(jout, jarEntryName, hmmAsString.getBytes());
 +      xmlSeq.setHmmerProfile(jarEntryName);
 +    } catch (IOException e)
 +    {
 +      warn("Error saving HMM profile: " + e.getMessage());
 +    }
 +  }
  
 +    
    /**
     * Writes PCA viewer attributes and computed values to an XML model object and
     * adds it to the JalviewModel. Any exceptions are reported by logging.
            final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
            boolean storeDataset)
    {
-     if (Desktop.desktop == null)
+     if (Desktop.getDesktopPane() == null)
      {
        return;
      }
-     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+     JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
      for (int f = frames.length - 1; f > -1; f--)
      {
        if (frames[f] instanceof AppVarna)
        }
        else if (!matchedFile.equals(pdbentry.getFile()))
        {
 -        Cache.log.warn(
 -                "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
 -                        + pdbentry.getFile());
 +        Cache.log.warn(
 +                  "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
 +                          + pdbentry.getFile());
        }
        // record the
        // file so we
      if (calcIdParam.getVersion().equals("1.0"))
      {
        final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
 -      Jws2Instance service = Jws2Discoverer.getInstance()
 +      ServiceWithParameters service = PreferredServiceRegistry.getRegistry()
                .getPreferredServiceFor(calcIds);
        if (service != null)
        {
            argList = parmSet.getArguments();
            parmSet = null;
          }
 -        AAConSettings settings = new AAConSettings(
 +        AutoCalcSetting settings = new AAConSettings(
                  calcIdParam.isAutoUpdate(), service, parmSet, argList);
          av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
                  calcIdParam.isNeedsUpdate());
        }
        else
        {
 -        warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
 +        warn("Cannot resolve a service for the parameters used in this project. Try configuring a server in the Web Services preferences tab.");
          return false;
        }
      }
    }
  
    /**
-    * contains last error message (if any) encountered by XML loader.
-    */
-   String errorMessage = null;
-   /**
-    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
-    * exceptions are raised during project XML parsing
-    */
-   public boolean attemptversion1parse = false;
-   /**
     * Load a jalview project archive from a jar file
     * 
     * @param file
      {
        try
        {
-         SwingUtilities.invokeAndWait(new Runnable()
+ // was invokeAndWait
+         
+         // BH 2019 -- can't wait
+         SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
          System.err.println("Error loading alignment: " + x.getMessage());
        }
      }
+     this.jarFile = null;
      return af;
    }
  
        @SuppressWarnings("unused")
-       private jarInputStreamProvider createjarInputStreamProvider(final Object ofile) throws MalformedURLException {
-               // BH 2018 allow for bytes already attached to File object
-               try {
-                       String file = (ofile instanceof File ? ((File) ofile).getCanonicalPath() : ofile.toString());
+   private jarInputStreamProvider createjarInputStreamProvider(
+           final Object ofile) throws MalformedURLException
+   {
+     try
+     {
+       String file = (ofile instanceof File
+               ? ((File) ofile).getCanonicalPath()
+               : ofile.toString());
        byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
                : null;
-                       URL url = null;
-                       errorMessage = null;
-                       uniqueSetSuffix = null;
-                       seqRefIds = null;
-                       viewportsAdded.clear();
-                       frefedSequence = null;
-                       if (file.startsWith("http://")) {
-                               url = new URL(file);
-                       }
-                       final URL _url = url;
-                       return new jarInputStreamProvider() {
-                               @Override
-                               public JarInputStream getJarInputStream() throws IOException {
-                                       if (bytes != null) {
- //                                            System.out.println("Jalview2XML: opening byte jarInputStream for bytes.length=" + bytes.length);
-                                               return new JarInputStream(new ByteArrayInputStream(bytes));
-                                       }
-                                       if (_url != null) {
- //                                            System.out.println("Jalview2XML: opening url jarInputStream for " + _url);
-                                               return new JarInputStream(_url.openStream());
-                                       } else {
- //                                            System.out.println("Jalview2XML: opening file jarInputStream for " + file);
-                                               return new JarInputStream(new FileInputStream(file));
-                                       }
-                               }
-                               @Override
-                               public String getFilename() {
-                                       return file;
-                               }
-                       };
-               } catch (IOException e) {
-                       e.printStackTrace();
-                       return null;
-               }
-       }
+       if (bytes != null)
+       {
+         this.jarFile = (File) ofile;
+       }
+       errorMessage = null;
+       uniqueSetSuffix = null;
+       seqRefIds = null;
+       viewportsAdded.clear();
+       frefedSequence = null;
+       URL url = file.startsWith("http://") ? new URL(file) : null;
+       return new jarInputStreamProvider()
+       {
+         @Override
+         public JarInputStream getJarInputStream() throws IOException
+         {
+           InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
+                   : (url != null ? url.openStream()
+                           : new FileInputStream(file));
+           return new JarInputStream(is);
+         }
+         @Override
+         public File getFile()
+         {
+           return jarFile;
+         }
+         @Override
+         public String getFilename()
+         {
+           return file;
+         }
+       };
+     } catch (IOException e)
+     {
+       e.printStackTrace();
+       return null;
+     }
+   }
  
    /**
     * Recover jalview session from a jalview project archive. Caller may
      AlignFrame af = null, _af = null;
      IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
      Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
-     final String file = jprovider.getFilename();
+     String fileName = jprovider.getFilename();
+     File file = jprovider.getFile();
+     List<AlignFrame> alignFrames = new ArrayList<>();
      try
      {
        JarInputStream jin = null;
        JarEntry jarentry = null;
        int entryCount = 1;
  
+       // Look for all the entry names ending with ".xml"
+       // This includes all panels and at least one frame.
+ //      Platform.timeCheck(null, Platform.TIME_MARK);
        do
        {
          jin = jprovider.getJarInputStream();
          {
            jarentry = jin.getNextJarEntry();
          }
+         String name = (jarentry == null ? null : jarentry.getName());
  
-         if (jarentry != null && jarentry.getName().endsWith(".xml"))
+ //        System.out.println("Jalview2XML opening " + name);
+         if (name != null && name.endsWith(".xml"))
          {
+           // DataSet for.... is read last.
+           
+           
+           // The question here is what to do with the two
+           // .xml files in the jvp file.
+           // Some number of them, "...Dataset for...", will be the
+           // Only AlignPanels and will have Viewport.
+           // One or more will be the source data, with the DBRefs.
+           //
+           // JVP file writing (above) ensures tha the AlignPanels are written
+           // first, then all relevant datasets (which are
+           // Jalview.datamodel.Alignment).
+           //
+ //          Platform.timeCheck("Jalview2XML JAXB " + name, Platform.TIME_MARK);
            JAXBContext jc = JAXBContext
                    .newInstance("jalview.xml.binding.jalview");
            XMLStreamReader streamReader = XMLInputFactory.newInstance()
            javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
            JAXBElement<JalviewModel> jbe = um
                    .unmarshal(streamReader, JalviewModel.class);
-           JalviewModel object = jbe.getValue();
+           JalviewModel model = jbe.getValue();
  
            if (true) // !skipViewport(object))
            {
-             _af = loadFromObject(object, file, true, jprovider);
-             if (_af != null && object.getViewport().size() > 0)
-             // getJalviewModelSequence().getViewportCount() > 0)
+             // Q: Do we have to load from the model, even if it
+             // does not have a viewport, could we discover that early on?
+             // Q: Do we need to load this object?
+             _af = loadFromObject(model, fileName, file, true, jprovider);
+ //            Platform.timeCheck("Jalview2XML.loadFromObject",
+             // Platform.TIME_MARK);
+             if (_af != null)
+             {
+               alignFrames.add(_af);
+             }
+             if (_af != null && model.getViewport().size() > 0)
              {
+               // That is, this is one of the AlignmentPanel models
                if (af == null)
                {
                  // store a reference to the first view
      } catch (IOException ex)
      {
        ex.printStackTrace();
-       errorMessage = "Couldn't locate Jalview XML file : " + file;
+       errorMessage = "Couldn't locate Jalview XML file : " + fileName;
        System.err.println(
                "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (Exception ex)
        {
          // used to attempt to parse as V1 castor-generated xml
        }
-       if (Desktop.instance != null)
+       if (Desktop.getInstance() != null)
        {
-         Desktop.instance.stopLoading();
+         Desktop.getInstance().stopLoading();
        }
        if (af != null)
        {
        errorMessage = "Out of memory loading jalview XML file";
        System.err.println("Out of memory whilst loading jalview XML file");
        e.printStackTrace();
+     } finally
+     {
+       for (AlignFrame alf : alignFrames)
+       {
+         alf.alignPanel.setHoldRepaint(false);
+       }
      }
  
      /*
       */
      for (AlignFrame fr : gatherToThisFrame.values())
      {
-       Desktop.instance.gatherViews(fr);
+       Desktop.getInstance().gatherViews(fr);
      }
  
      restoreSplitFrames();
      {
        if (ds.getCodonFrames() != null)
        {
-         StructureSelectionManager
-                 .getStructureSelectionManager(Desktop.instance)
+         Desktop.getStructureSelectionManager()
                  .registerMappings(ds.getCodonFrames());
        }
      }
        reportErrors();
      }
  
-     if (Desktop.instance != null)
+     if (Desktop.getInstance() != null)
      {
-       Desktop.instance.stopLoading();
+       Desktop.getInstance().stopLoading();
      }
  
      return af;
       */
      for (SplitFrame sf : gatherTo)
      {
-       Desktop.instance.gatherViews(sf);
+       Desktop.getInstance().gatherViews(sf);
      }
  
      splitFrameCandidates.clear();
            @Override
            public void run()
            {
-             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      finalErrorMessage,
                      "Error " + (saving ? "saving" : "loading")
                              + " Jalview file",
     * @param prefix
     *          a prefix for the temporary file name, must be at least three
     *          characters long
-    * @param suffixModel
+    * @param origFile
     *          null or original file - so new file can be given the same suffix
     *          as the old one
     * @return
     */
    protected String copyJarEntry(jarInputStreamProvider jprovider,
-           String jarEntryName, String prefix, String suffixModel)
+           String jarEntryName, String prefix, String origFile)
    {
      BufferedReader in = null;
      PrintWriter out = null;
      String suffix = ".tmp";
-     if (suffixModel == null)
+     if (origFile == null)
      {
-       suffixModel = jarEntryName;
+       origFile = jarEntryName;
      }
-     int sfpos = suffixModel.lastIndexOf(".");
-     if (sfpos > -1 && sfpos < (suffixModel.length() - 1))
+     int sfpos = origFile.lastIndexOf(".");
+     if (sfpos > -1 && sfpos < (origFile.length() - 3))
      {
-       suffix = "." + suffixModel.substring(sfpos + 1);
+       suffix = "." + origFile.substring(sfpos + 1);
      }
      try
      {
    }
  
    /**
-    * Load alignment frame from jalview XML DOM object
+    * Load alignment frame from jalview XML DOM object. For a DOM object that
+    * includes one or more Viewport elements (one with a title that does NOT
+    * contain "Dataset for"), create the frame.
     * 
     * @param jalviewModel
     *          DOM
-    * @param file
+    * @param fileName
     *          filename source string
+    * @param file 
     * @param loadTreesAndStructures
     *          when false only create Viewport
     * @param jprovider
     *          data source provider
     * @return alignment frame created from view stored in DOM
     */
-   AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
-           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
+   AlignFrame loadFromObject(JalviewModel jalviewModel, String fileName,
+           File file, boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
    {
      SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
      List<Sequence> vamsasSeqs = vamsasSet.getSequence();
  
      // JalviewModelSequence jms = object.getJalviewModelSequence();
  
      // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
              {
                entry.setProperty(prop.getName(), prop.getValue());
              }
-             StructureSelectionManager
-                     .getStructureSelectionManager(Desktop.instance)
+             Desktop.getStructureSelectionManager()
                      .registerPDBEntry(entry);
              // adds PDBEntry to datasequence's set (since Jalview 2.10)
              if (al.getSequenceAt(i).getDatasetSequence() != null)
            }
          }
  
 +        /*
 +         * load any HMMER profile
 +         */
 +        // TODO fix this
 +
 +        String hmmJarFile = jseqs.get(i).getHmmerProfile();
 +        if (hmmJarFile != null && jprovider != null)
 +        {
 +          loadHmmerProfile(jprovider, hmmJarFile, al.getSequenceAt(i));
 +        }
        }
      } // end !multipleview
  
              }
            }
          }
+         // create the new AlignmentAnnotation
          jalview.datamodel.AlignmentAnnotation jaa = null;
  
          if (annotation.isGraph())
            jaa._linecolour = firstColour;
          }
          // register new annotation
+         // Annotation graphs such as Conservation will not have id.
          if (annotation.getId() != null)
          {
            annotationIds.put(annotation.getId(), jaa);
      // ///////////////////////////////
      // LOAD VIEWPORT
  
-     AlignFrame af = null;
-     AlignViewport av = null;
      // now check to see if we really need to create a new viewport.
      if (multipleView && viewportsAdded.size() == 0)
      {
      boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
              jalviewModel.getVersion());
  
+     AlignFrame af = null;
      AlignmentPanel ap = null;
-     boolean isnewview = true;
+     AlignViewport av = null;
      if (viewId != null)
      {
        // Check to see if this alignment already has a view id == viewId
        {
          for (int v = 0; v < views.length; v++)
          {
-           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
+           ap = views[v];
+           av = ap.av;
+           if (av.getViewId().equalsIgnoreCase(viewId))
            {
              // recover the existing alignpanel, alignframe, viewport
-             af = views[v].alignFrame;
-             av = views[v].av;
-             ap = views[v];
+             af = ap.alignFrame;
+             break;
              // TODO: could even skip resetting view settings if we don't want to
              // change the local settings from other jalview processes
-             isnewview = false;
            }
          }
        }
      }
  
-     if (isnewview)
+     if (af == null)
      {
-       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
+       af = loadViewport(fileName, file, jseqs, hiddenSeqs, al, jalviewModel, view,
                uniqueSeqSetId, viewId, autoAlan);
        av = af.getViewport();
+       // note that this only retrieves the most recently accessed
+       // tab of an AlignFrame.
        ap = af.alignPanel;
      }
  
       * 
       * Not done if flag is false (when this method is used for New View)
       */
+     final AlignFrame af0 = af;
+     final AlignViewport av0 = av;
+     final AlignmentPanel ap0 = ap;
+ //    Platform.timeCheck("Jalview2XML.loadFromObject-beforetree",
+ //            Platform.TIME_MARK);
      if (loadTreesAndStructures)
      {
-       loadTrees(jalviewModel, view, af, av, ap);
-       loadPCAViewers(jalviewModel, ap);
-       loadPDBStructures(jprovider, jseqs, af, ap);
-       loadRnaViewers(jprovider, jseqs, ap);
+       if (!jalviewModel.getTree().isEmpty())
+       {
+         SwingUtilities.invokeLater(new Runnable()
+         {
+           @Override
+           public void run()
+           {
+ //            Platform.timeCheck(null, Platform.TIME_MARK);
+             loadTrees(jalviewModel, view, af0, av0, ap0);
+ //            Platform.timeCheck("Jalview2XML.loadTrees", Platform.TIME_MARK);
+           }
+         });
+       }
+       if (!jalviewModel.getPcaViewer().isEmpty())
+       {
+         SwingUtilities.invokeLater(new Runnable()
+         {
+           @Override
+           public void run()
+           {
+ //            Platform.timeCheck(null, Platform.TIME_MARK);
+             loadPCAViewers(jalviewModel, ap0);
+ //            Platform.timeCheck("Jalview2XML.loadPCA", Platform.TIME_MARK);
+           }
+         });
+       }
+       SwingUtilities.invokeLater(new Runnable()
+       {
+         @Override
+         public void run()
+         {
+ //          Platform.timeCheck(null, Platform.TIME_MARK);
+           loadPDBStructures(jprovider, jseqs, af0, ap0);
+ //          Platform.timeCheck("Jalview2XML.loadPDB", Platform.TIME_MARK);
+         }
+       });
+       SwingUtilities.invokeLater(new Runnable()
+       {
+         @Override
+         public void run()
+         {
+           loadRnaViewers(jprovider, jseqs, ap0);
+         }
+       });
      }
      // and finally return.
+     // but do not set holdRepaint true just yet, because this could be the
+     // initial frame with just its dataset.
      return af;
    }
  
    /**
 +   * Loads a HMMER profile from a file stored in the project, and associates it
 +   * with the specified sequence
 +   * 
 +   * @param jprovider
 +   * @param hmmJarFile
 +   * @param seq
 +   */
 +  protected void loadHmmerProfile(jarInputStreamProvider jprovider,
 +          String hmmJarFile, SequenceI seq)
 +  {
 +    try
 +    {
 +      String hmmFile = copyJarEntry(jprovider, hmmJarFile, "hmm", null);
 +      HMMFile parser = new HMMFile(hmmFile, DataSourceType.FILE);
 +      HiddenMarkovModel hmmModel = parser.getHMM();
 +      hmmModel = new HiddenMarkovModel(hmmModel, seq);
 +      seq.setHMM(hmmModel);
 +    } catch (IOException e)
 +    {
 +      warn("Error loading HMM profile for " + seq.getName() + ": "
 +              + e.getMessage());
 +    }
 +  }
 +
 +  /**
     * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
     * panel is restored from separate jar entries, two (gapped and trimmed) per
     * sequence and secondary structure.
     * @param jseqs
     * @param ap
     */
-   private void loadRnaViewers(jarInputStreamProvider jprovider,
+   protected void loadRnaViewers(jarInputStreamProvider jprovider,
            List<JSeq> jseqs, AlignmentPanel ap)
    {
      /*
                    tree.getTitle(), safeInt(tree.getWidth()),
                    safeInt(tree.getHeight()), safeInt(tree.getXpos()),
                    safeInt(tree.getYpos()));
+           if (tp == null)
+           {
+             warn("There was a problem recovering stored Newick tree: \n"
+                     + tree.getNewick());
+             continue;
+           }
            if (tree.getId() != null)
            {
              // perhaps bind the tree id to something ?
            tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
          }
          tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
-         if (tp == null)
-         {
-           warn("There was a problem recovering stored Newick tree: \n"
-                   + tree.getNewick());
-           continue;
-         }
          tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
          tp.fitToWindow_actionPerformed(null);
  
              int height = safeInt(structureState.getHeight());
  
              // Probably don't need to do this anymore...
-             // Desktop.desktop.getComponentAt(x, y);
+             // Desktop.getDesktop().getComponentAt(x, y);
              // TODO: NOW: check that this recovers the PDB file correctly.
              String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
                      pdbid.getFile());
            String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
            filedat = oldFiles.get(new File(reformatedOldFilename));
          }
-         newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
+         newFileLoc
+                 .append(Platform.escapeBackslashes(filedat.getFilePath()));
          pdbfilenames.add(filedat.getFilePath());
          pdbids.add(filedat.getPdbId());
          seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
      final AlignFrame alf = af;
      final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
              svattrib.getWidth(), svattrib.getHeight());
-     try
-     {
-       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+     
+     // BH again was invokeAndWait
+     // try
+     // {
+       javax.swing.SwingUtilities.invokeLater(new Runnable()
        {
          @Override
          public void run()
            }
          }
        });
-     } catch (InvocationTargetException ex)
-     {
-       warn("Unexpected error when opening Jmol view.", ex);
-     } catch (InterruptedException e)
-     {
-       // e.printStackTrace();
-     }
+     // } catch (InvocationTargetException ex)
+     // {
+     // warn("Unexpected error when opening Jmol view.", ex);
+     //
+     // } catch (InterruptedException e)
+     // {
+     // // e.printStackTrace();
+     // }
  
    }
  
      {
        try
        {
-         frames = Desktop.desktop.getAllFrames();
+         frames = Desktop.getDesktopPane().getAllFrames();
        } catch (ArrayIndexOutOfBoundsException e)
        {
          // occasional No such child exceptions are thrown here...
      }
    }
  
-   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
-           List<SequenceI> hiddenSeqs, AlignmentI al,
-           JalviewModel jm, Viewport view, String uniqueSeqSetId,
-           String viewId, List<JvAnnotRow> autoAlan)
+   AlignFrame loadViewport(String fileName, File file, List<JSeq> JSEQ,
+           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
+           Viewport view, String uniqueSeqSetId, String viewId,
+           List<JvAnnotRow> autoAlan)
    {
      AlignFrame af = null;
      af = new AlignFrame(al, safeInt(view.getWidth()),
-             safeInt(view.getHeight()), uniqueSeqSetId, viewId) 
- //    {
- //            
- //            @Override
- //            protected void processKeyEvent(java.awt.event.KeyEvent e) {
- //                    System.out.println("Jalview2XML   AF " + e);
- //                    super.processKeyEvent(e);
- //                    
- //            }
- //            
- //    }
+             safeInt(view.getHeight()), uniqueSeqSetId, viewId)
+     // {
+     //
+     // @Override
+     // protected void processKeyEvent(java.awt.event.KeyEvent e) {
+     // System.out.println("Jalview2XML AF " + e);
+     // super.processKeyEvent(e);
+     //
+     // }
+     //
+     // }
      ;
-     af.setFileName(file, FileFormat.Jalview);
+     af.alignPanel.setHoldRepaint(true);
+     af.setFile(fileName, file, null, FileFormat.Jalview);
+     af.setFileObject(jarFile); // BH 2019 JAL-3436
  
      final AlignViewport viewport = af.getViewport();
      for (int i = 0; i < JSEQ.size(); i++)
  
      viewport.setColourText(safeBoolean(view.isShowColourText()));
  
-     viewport
-             .setConservationSelected(
-                     safeBoolean(view.isConservationSelected()));
+     viewport.setConservationSelected(
+             safeBoolean(view.isConservationSelected()));
      viewport.setIncrement(safeInt(view.getConsThreshold()));
      viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
      viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
        viewport.setViewName(view.getViewName());
        af.setInitialTabVisible();
      }
-     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
-             safeInt(view.getWidth()), safeInt(view.getHeight()));
+     int x = safeInt(view.getXpos());
+     int y = safeInt(view.getYpos());
+     int w = safeInt(view.getWidth());
+     int h = safeInt(view.getHeight());
+     // // BH we cannot let the title bar go off the top
+     // if (Platform.isJS())
+     // {
+     // x = Math.max(50 - w, x);
+     // y = Math.max(0, y);
+     // }
+     af.setBounds(x, y, w, h);
      // startSeq set in af.alignPanel.updateLayout below
      af.alignPanel.updateLayout();
      ColourSchemeI cs = null;
      af.changeColour(cs);
      viewport.setColourAppliesToAllGroups(true);
  
-     viewport
-             .setShowSequenceFeatures(
-                     safeBoolean(view.isShowSequenceFeatures()));
+     viewport.setShowSequenceFeatures(
+             safeBoolean(view.isShowSequenceFeatures()));
  
      viewport.setCentreColumnLabels(view.isCentreColumnLabels());
      viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
      // recover feature settings
      if (jm.getFeatureSettings() != null)
      {
-       FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
+       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
                .getFeatureRenderer();
        FeaturesDisplayed fdi;
        viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
-       String[] renderOrder = new String[jm.getFeatureSettings()
-               .getSetting().size()];
+       String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
+               .size()];
        Map<String, FeatureColourI> featureColours = new Hashtable<>();
        Map<String, Float> featureOrder = new Hashtable<>();
  
-       for (int fs = 0; fs < jm.getFeatureSettings()
-               .getSetting().size(); fs++)
+       for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
+               .size(); fs++)
        {
          Setting setting = jm.getFeatureSettings().getSetting().get(fs);
          String featureType = setting.getType();
                  .getMatcherSet();
          if (filters != null)
          {
-           FeatureMatcherSetI filter = Jalview2XML
-                   .parseFilter(featureType, filters);
+           FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
+                   filters);
            if (!filter.isEmpty())
            {
              fr.setFeatureFilter(featureType, filter);
            float max = setting.getMax() == null ? 1f
                    : setting.getMax().floatValue();
            FeatureColourI gc = new FeatureColour(maxColour, minColour,
-                   maxColour,
-                   noValueColour, min, max);
+                   maxColour, noValueColour, min, max);
            if (setting.getAttributeName().size() > 0)
            {
              gc.setAttributeName(setting.getAttributeName().toArray(
          }
          else
          {
-           featureColours.put(featureType,
-                   new FeatureColour(maxColour));
+           featureColours.put(featureType, new FeatureColour(maxColour));
          }
          renderOrder[fs] = featureType;
          if (setting.getOrder() != null)
      String complementaryViewId = view.getComplementId();
      if (complementaryViewId == null)
      {
-       Desktop.addInternalFrame(af, view.getTitle(),
+       Dimension dim = Platform.getDimIfEmbedded(af,
                safeInt(view.getWidth()), safeInt(view.getHeight()));
+       Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
        // recompute any autoannotation
        af.alignPanel.updateAnnotation(false, true);
        reorderAutoannotation(af, al, autoAlan);
      String id = object.getViewport().get(0).getSequenceSetId();
      if (skipList.containsKey(id))
      {
 -      if (Cache.log != null && Cache.log.isDebugEnabled())
 -      {
 -        Cache.log.debug("Skipping seuqence set id " + id);
 -      }
 +      if (Cache.log != null && Cache.log.isDebugEnabled())
 +        {
 +          Cache.log.debug("Skipping seuqence set id " + id);
 +        }
        return true;
      }
      return false;
    }
  
-   public void addToSkipList(AlignFrame af)
+   protected void addToSkipList(AlignFrame af)
    {
      if (skipList == null)
      {
      skipList.put(af.getViewport().getSequenceSetId(), af);
    }
  
-   public void clearSkipList()
+   protected void clearSkipList()
    {
      if (skipList != null)
      {
        SequenceI[] dsseqs = new SequenceI[dseqs.size()];
        dseqs.copyInto(dsseqs);
        ds = new jalview.datamodel.Alignment(dsseqs);
-       debug("Created new dataset " + vamsasSet.getDatasetId()
-               + " for alignment " + System.identityHashCode(al));
+ //      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
+ //              + " for alignment " + System.identityHashCode(al));
        addDatasetRef(vamsasSet.getDatasetId(), ds);
      }
      // set the dataset for the newly imported alignment.
          jmap.setTo(djs);
          incompleteSeqs.put(sqid, djs);
          seqRefIds.put(sqid, djs);
 -
        }
        jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
        addDBRefs(djs, ms);
  
      viewportsAdded.clear();
  
-     AlignFrame af = loadFromObject(jm, null, false, null);
+     AlignFrame af = loadFromObject(jm, null, null, false, null);
      af.getAlignPanels().clear();
      af.closeMenuItem_actionPerformed(true);
+     af.alignPanel.setHoldRepaint(false);
+     this.jarFile = null;
  
      /*
       * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
        }
        else
        {
 -        Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
 +          Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
        }
      }
    }
                    axis.getXPos(), axis.getYPos(), axis.getZPos());
          }
  
+         Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
          Desktop.addInternalFrame(panel, MessageManager.formatMessage(
-                 "label.calc_title", "PCA", modelName), 475, 450);
+                 "label.calc_title", "PCA", modelName), dim.width,
+                 dim.height);
        }
      } catch (Exception ex)
      {
          maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
        } catch (Exception e)
        {
 -        Cache.log.warn("Couldn't parse out graduated feature color.", e);
 +          Cache.log.warn("Couldn't parse out graduated feature color.", e);
        }
    
        NoValueColour noCol = colourModel.getNoValueColour();
   */
  package jalview.util;
  
- import jalview.javascript.json.JSON;
+ import java.awt.Component;
+ import java.awt.Dimension;
+ import java.awt.GraphicsEnvironment;
  import java.awt.Toolkit;
+ import java.awt.event.KeyEvent;
  import java.awt.event.MouseEvent;
  import java.io.BufferedReader;
  import java.io.File;
@@@ -32,14 -34,34 +34,34 @@@ import java.io.IOException
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.Reader;
+ import java.lang.reflect.Method;
  import java.net.URL;
+ import java.nio.channels.Channels;
+ import java.nio.channels.ReadableByteChannel;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+ import java.nio.file.Paths;
+ import java.nio.file.StandardCopyOption;
+ import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.Date;
+ import java.util.Locale;
+ import java.util.Map;
  import java.util.Properties;
+ import java.util.logging.ConsoleHandler;
+ import java.util.logging.Level;
+ import java.util.logging.Logger;
  
  import javax.swing.SwingUtilities;
  
  import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
+ import com.stevesoft.pat.Regex;
+ import jalview.bin.Jalview;
+ import jalview.javascript.json.JSON;
+ import swingjs.api.JSUtilI;
  /**
   * System platform information used by Applet and Application
   * 
@@@ -54,7 -76,25 +76,25 @@@ public class Platfor
    private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
            isWin = null;
  
-   private static Boolean isHeadless = null;
+   private static swingjs.api.JSUtilI jsutil;
+   static
+   {
+     if (isJS)
+     {
+       try
+       {
+         // this is ok - it's a highly embedded method in Java; the deprecation
+         // is
+         // really a recommended best practice.
+         jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
+       } catch (InstantiationException | IllegalAccessException
+               | ClassNotFoundException e)
+       {
+         e.printStackTrace();
+       }
+     }
+   }
  
    /**
     * added to group mouse events into Windows and nonWindows (mac, unix, linux)
              : isMac);
    }
  
+   public static int SHORTCUT_KEY_MASK = (Platform.isMac()
+           ? KeyEvent.META_DOWN_MASK
+           : KeyEvent.CTRL_DOWN_MASK);
+   static
+   {
+     if (!GraphicsEnvironment.isHeadless())
+     {
+       // Using non-deprecated Extended key mask modifiers, but Java 8 has no
+       // getMenuShortcutKeyMaskEx method
+       Toolkit tk = Toolkit.getDefaultToolkit();
+       Method method = null;
+       try
+       {
+         method = tk.getClass().getMethod("getMenuShortcutKeyMaskEx");
+       } catch (Exception e)
+       {
+         System.err.println(
+                 "Could not find Toolkit method getMenuShortcutKeyMaskEx. Trying getMenuShortcutKeyMask.");
+       }
+       if (method == null)
+       {
+         try
+         {
+           method = tk.getClass().getMethod("getMenuShortcutKeyMask");
+         } catch (Exception e)
+         {
+           System.err.println(
+                   "Could not find Toolkit method getMenuShortcutKeyMaskEx or getMenuShortcutKeyMask.");
+           e.printStackTrace();
+         }
+       }
+       if (method != null)
+       {
+         try
+         {
+           method.setAccessible(true);
+           SHORTCUT_KEY_MASK = ((int) method.invoke(tk, new Object[0]));
+         } catch (Exception e)
+         {
+           e.printStackTrace();
+         }
+       }
+       if (SHORTCUT_KEY_MASK <= 0xF)
+       {
+         // shift this into the extended region (was Java 8)
+         SHORTCUT_KEY_MASK = SHORTCUT_KEY_MASK << 6;
+       }
+     }
+   }
    /**
     * added to group mouse events into Windows and nonWindows (mac, unix, linux)
     * 
    }
  
    /**
 -   * Check if we are on a Microsoft plaform...
 +   * Check if we are on a Microsoft platform...
     * 
     * @return true if we have to cope with another platform variation
     */
      return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
    }
  
-   /**
-    * 
-    * @return true if we are running in non-interactive no UI mode
-    */
-   public static boolean isHeadless()
-   {
-     if (isHeadless == null)
-     {
-       isHeadless = "true".equals(System.getProperty("java.awt.headless"));
-     }
-     return isHeadless;
-   }
  
    /**
     * 
     */
    protected static boolean isControlDown(MouseEvent e, boolean aMac)
    {
-     if (!aMac)
-     {
-       return e.isControlDown();
-       // Jalview 2.11 code below: above is as amended for JalviewJS
-       // /*
-       // * answer false for right mouse button
-       // */
-       // if (e.isPopupTrigger())
-       // {
-       // return false;
-       // }
-       // return
-       // (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() //
-       // .getMenuShortcutKeyMaskEx()
-       // & jalview.util.ShortcutKeyMaskExWrapper
-       // .getModifiersEx(e)) != 0; // getModifiers()) != 0;
-     }
-     // answer false for right mouse button
-     // shortcut key will be META for a Mac
-     return !e.isPopupTrigger()
-             && (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     & e.getModifiers()) != 0;
-     // could we use e.isMetaDown() here?
+     //
+     // System.out.println(e.isPopupTrigger()
+     // + " " + ((SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0)
+     // + " " + e.isControlDown());
+     return (aMac
+             ? !e.isPopupTrigger()
+                     && (SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0
+             : e.isControlDown());
    }
  
    // BH: I don't know about that previous method. Here is what SwingJS uses.
  
    public static long time, mark, set, duration;
  
+   /**
+    * typical usage:
+    * 
+    * Platform.timeCheck(null, Platform.TIME_MARK);
+    * 
+    * ...
+    * 
+    * Platform.timeCheck("some message", Platform.TIME_MARK);
+    * 
+    * reset...[set/mark]n...get
+    * 
+    * @param msg
+    * @param mode
+    */
    public static void timeCheck(String msg, int mode)
    {
      long t = System.currentTimeMillis();
      {
      case TIME_RESET:
        time = mark = t;
+       duration = 0;
        if (msg != null)
        {
          System.err.println("Platform: timer reset\t\t\t" + msg);
      case TIME_MARK:
        if (set > 0)
        {
+         // total time between set/mark points
          duration += (t - set);
        }
        else
  
    public static void cacheFileData(String path, Object data)
    {
-     if (!isJS() || data == null)
+     if (isJS && data != null)
      {
-       return;
+       jsutil.cachePathData(path, data);
      }
-     /**
-      * @j2sNative
-      * 
-      *            swingjs.JSUtil.cacheFileData$S$O(path, data);
-      * 
-      */
    }
  
    public static void cacheFileData(File file)
    {
-     byte[] data;
-     if (!isJS() || (data = Platform.getFileBytes(file)) == null)
+     if (isJS)
      {
-       return;
+       byte[] data = Platform.getFileBytes(file);
+       {
+         if (data != null)
+         {
+           cacheFileData(file.toString(), data);
+         }
+       }
      }
-     cacheFileData(file.toString(), data);
    }
  
    public static byte[] getFileBytes(File f)
    {
-     return /** @j2sNative f && swingjs.JSUtil.getFileBytes$java_io_File(f) || */
-     null;
+     return (isJS && f != null ? jsutil.getBytes(f) : null);
    }
  
    public static byte[] getFileAsBytes(String fileStr)
    {
-     byte[] bytes = null;
-     // BH 2018 hack for no support for access-origin
-     /**
-      * @j2sNative bytes = swingjs.JSUtil.getFileAsBytes$O(fileStr)
-      */
-     cacheFileData(fileStr, bytes);
-     return bytes;
+     if (isJS && fileStr != null)
+     {
+       byte[] bytes = (byte[]) jsutil.getFile(fileStr, false);
+       cacheFileData(fileStr, bytes);
+       return bytes;
+     }
+     return null;
    }
  
-   @SuppressWarnings("unused")
    public static String getFileAsString(String url)
    {
-     String ret = null;
-     /**
-      * @j2sNative
-      * 
-      *            ret = swingjs.JSUtil.getFileAsString$S(url);
-      * 
-      * 
-      */
-     cacheFileData(url, ret);
-     return ret;
+     if (isJS && url != null)
+     {
+       String ret = (String) jsutil.getFile(url, true);
+       cacheFileData(url, ret);
+       return ret;
+     }
+     return null;
    }
  
    public static boolean setFileBytes(File f, String urlstring)
    {
-     if (!isJS())
+     if (isJS && f != null && urlstring != null)
      {
-       return false;
+       @SuppressWarnings("unused")
+       byte[] bytes = getFileAsBytes(urlstring);
+       jsutil.setFileBytes(f, bytes);
+       return true;
      }
-     @SuppressWarnings("unused")
-     byte[] bytes = getFileAsBytes(urlstring);
-     // TODO temporary doubling of ç§˜bytes and _bytes;
-     // just remove _bytes when new transpiler has been installed
-     /**
-      * @j2sNative f.\u79d8bytes = f._bytes = bytes;
-      */
-     return true;
+     return false;
    }
  
    public static void addJ2SBinaryType(String ext)
    {
-     /**
-      * @j2sNative
-      * 
-      *            J2S._binaryTypes.push("." + ext + "?");
-      * 
-      */
+     if (isJS)
+     {
+       jsutil.addBinaryFileType(ext);
+     }
    }
  
    /**
     * @param url
     * @return true if window has been opened
     */
-   public static boolean openURL(String url)
+   public static boolean openURL(String url) throws IOException
    {
      if (!isJS())
      {
  
    public static String getUniqueAppletID()
    {
-     /**
-      * @j2sNative return swingjs.JSUtil.getApplet$()._uniqueId;
-      *
-      */
-     return null;
+     return (isJS ? (String) jsutil.getAppletAttribute("_uniqueId") : null);
    }
  
    /**
     */
    public static void readInfoProperties(String prefix, Properties p)
    {
-     if (!isJS())
+     if (isJS)
      {
-       return;
-     }
-     String id = getUniqueAppletID();
-     String key = "", value = "";
-     /**
-      * @j2sNative var info = swingjs.JSUtil.getApplet$().__Info || {}; for (var
-      *            key in info) { if (key.indexOf(prefix) == 0) { value = "" +
-      *            info[key];
-      */
+       String id = getUniqueAppletID();
  
-     System.out.println(
-             "Platform id=" + id + " reading Info." + key + " = " + value);
-     p.put(id + "_" + key, value);
+       String key = "";
+       String value = "";
+       @SuppressWarnings("unused")
+       Object info = jsutil.getAppletAttribute("__Info");
+       /**
+        * @j2sNative for (key in info) { value = info[key];
+        */
  
-     /**
-      * @j2sNative
-      * 
-      * 
-      *            } }
-      */
+       if (key.indexOf(prefix) == 0)
+       {
+         System.out.println("Platform id=" + id + " reading Info." + key
+                 + " = " + value);
+         p.put(key, value);
+       }
+       /**
+        * @j2sNative }
+        */
+     }
    }
  
    public static void setAjaxJSON(URL url)
  
    public static Object parseJSON(String json) throws ParseException
    {
-     return (isJS() ? JSON.parse(json)
-             : new JSONParser().parse(json));
+     return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
    }
  
    public static Object parseJSON(Reader r)
                "StringJS does not support FileReader parsing for JSON -- but it could...");
      }
      return JSON.parse(r);
    }
  
    /**
     * @param is
     * @param outFile
     * @throws IOException
-    *                       if the file cannot be created or there is a problem
-    *                       reading the input stream.
+    *           if the file cannot be created or there is a problem reading the
+    *           input stream.
     */
    public static void streamToFile(InputStream is, File outFile)
            throws IOException
    {
-     if (isJS() && /**
-                    * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
-                    */
-             true)
+     if (isJS)
      {
+       jsutil.setFileBytes(outFile, is);
        return;
      }
      FileOutputStream fio = new FileOutputStream(outFile);
      try
      {
    public static void addJ2SDirectDatabaseCall(String domain)
    {
  
-     if (isJS())
+     if (isJS)
      {
+       jsutil.addDirectDatabaseCall(domain);
        System.out.println(
-             "Platform adding known access-control-allow-origin * for domain "
-                     + domain);
-       /**
-        * @j2sNative
-        * 
-        *            J2S.addDirectDatabaseCall(domain);
-        */
+               "Platform adding known access-control-allow-origin * for domain "
+                       + domain);
      }
  
    }
  
+   /**
+    * Allow for URL-line command arguments. Untested.
+    * 
+    */
    public static void getURLCommandArguments()
    {
  
-     /**
-      * Retrieve the first query field as command arguments to Jalview. Include
-      * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's __Info.args
-      * element to this value.
-      * 
-      * @j2sNative var a =
-      *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
-      *            + "?").split("?")[1].split("#")[0]); a &&
-      *            (J2S.thisApplet.__Info.args = a.split(" "));
-      */
+     try
+     {
+       /**
+        * Retrieve the first query field as command arguments to Jalview. Include
+        * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
+        * __Info.args element to this value.
+        * 
+        * @j2sNative var a =
+        *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
+        *            + "?").split("?")[1].split("#")[0]); a &&
+        *            (J2S.thisApplet.__Info.args = a.split(" "));
+        * 
+        *            System.out.println("URL arguments: " + a);
+        */
+     } catch (Throwable t)
+     {
+     }
    }
  
    /**
-    * A (case sensitive) file path comparator that ignores the difference between /
-    * and \
+    * A (case sensitive) file path comparator that ignores the difference between
+    * / and \
     * 
     * @param path1
     * @param path2
      String p2 = path2.replace('\\', '/');
      return p1.equals(p2);
    }
+   ///////////// JAL-3253 Applet additions //////////////
+   /**
+    * Retrieve the object's embedded size from a div's style on a page if
+    * embedded in SwingJS.
+    * 
+    * @param frame
+    *          JFrame or JInternalFrame
+    * @param defaultWidth
+    *          use -1 to return null (no default size)
+    * @param defaultHeight
+    * @return the embedded dimensions or null (no default size or not embedded)
+    */
+   public static Dimension getDimIfEmbedded(Component frame,
+           int defaultWidth, int defaultHeight)
+   {
+     Dimension d = null;
+     if (isJS)
+     {
+       d = (Dimension) getEmbeddedAttribute(frame, "dim");
+     }
+     return (d == null && defaultWidth >= 0
+             ? new Dimension(defaultWidth, defaultHeight)
+             : d);
+   }
+   public static Regex newRegex(String regex)
+   {
+     return newRegex(regex, null);
+   }
+   public static Regex newRegex(String searchString, String replaceString)
+   {
+     ensureRegex();
+     return (replaceString == null ? new Regex(searchString)
+             : new Regex(searchString, replaceString));
+   }
+   public static Regex newRegexPerl(String code)
+   {
+     ensureRegex();
+     return Regex.perlCode(code);
+   }
+   /**
+    * Initialize Java debug logging. A representative sample -- adapt as desired.
+    */
+   public static void startJavaLogging()
+   {
+     /**
+      * @j2sIgnore
+      */
+     {
+       logClass("java.awt.EventDispatchThread", "java.awt.EventQueue",
+               "java.awt.Component", "java.awt.focus.Component",
+               "java.awt.event.Component",
+               "java.awt.focus.DefaultKeyboardFocusManager");
+     }
+   }
+   /**
+    * Initiate Java logging for a given class. Only for Java, not JavaScript;
+    * Allows debugging of complex event processing.
+    * 
+    * @param className
+    */
+   public static void logClass(String... classNames)
+   {
+     /**
+      * @j2sIgnore
+      * 
+      * 
+      */
+     {
+       Logger rootLogger = Logger.getLogger("");
+       rootLogger.setLevel(Level.ALL);
+       ConsoleHandler consoleHandler = new ConsoleHandler();
+       consoleHandler.setLevel(Level.ALL);
+       for (int i = classNames.length; --i >= 0;)
+       {
+         Logger logger = Logger.getLogger(classNames[i]);
+         logger.setLevel(Level.ALL);
+         logger.addHandler(consoleHandler);
+       }
+     }
+   }
+   /**
+    * load a resource -- probably a core file -- if and only if a particular
+    * class has not been instantialized. We use a String here because if we used
+    * a .class object, that reference itself would simply load the class, and we
+    * want the core package to include that as well.
+    * 
+    * @param resourcePath
+    * @param className
+    */
+   public static void loadStaticResource(String resourcePath,
+           String className)
+   {
+     if (isJS)
+     {
+       jsutil.loadResourceIfClassUnknown(resourcePath, className);
+     }
+   }
+   public static void ensureRegex()
+   {
+     if (isJS)
+     {
+       loadStaticResource("core/core_stevesoft.z.js",
+               "com.stevesoft.pat.Regex");
+     }
+   }
+   /**
+    * Set the "app" property of the HTML5 applet object, for example,
+    * "testApplet.app", to point to the Jalview instance. This will be the object
+    * that page developers use that is similar to the original Java applet object
+    * that was accessed via LiveConnect.
+    * 
+    * @param j
+    */
+   public static void setAppClass(Object j)
+   {
+     if (isJS)
+     {
+       jsutil.setAppClass(j);
+     }
+   }
+   /**
+    *
+    * If this frame is embedded in a web page, return a known type.
+    * 
+    * @param frame
+    *          a JFrame or JInternalFrame
+    * @param type
+    *          "name", "node", "init", "dim", or any DOM attribute, such as "id"
+    * @return null if frame is not embedded.
+    */
+   public static Object getEmbeddedAttribute(Component frame, String type)
+   {
+     return (isJS ? jsutil.getEmbeddedAttribute(frame, type) : null);
+   }
+   public static void stackTrace()
+   {
+     try
+     {
+       throw new NullPointerException();
+     } catch (Exception e)
+     {
+       e.printStackTrace();
+     }
+   }
+   public static URL getDocumentBase()
+   {
+     return (isJS ? jsutil.getDocumentBase() : null);
+   }
+   public static URL getCodeBase()
+   {
+     return (isJS ? jsutil.getCodeBase() : null);
+   }
+   public static String getUserPath(String subpath)
+   {
+     char sep = File.separatorChar;
+     return System.getProperty("user.home") + sep
+             + subpath.replace('/', sep);
+   }
+   /**
+    * This method enables checking if a cached file has exceeded a certain
+    * threshold(in days)
+    * 
+    * @param file
+    *          the cached file
+    * @param noOfDays
+    *          the threshold in days
+    * @return
+    */
+   public static boolean isFileOlderThanThreshold(File file, int noOfDays)
+   {
+     if (isJS())
+     {
+       // not meaningful in SwingJS -- this is a session-specific temp file. It
+       // doesn't have a timestamp.
+       return false;
+     }
+     Path filePath = file.toPath();
+     BasicFileAttributes attr;
+     int diffInDays = 0;
+     try
+     {
+       attr = Files.readAttributes(filePath, BasicFileAttributes.class);
+       diffInDays = (int) ((new Date().getTime()
+               - attr.lastModifiedTime().toMillis())
+               / (1000 * 60 * 60 * 24));
+       // System.out.println("Diff in days : " + diffInDays);
+     } catch (IOException e)
+     {
+       e.printStackTrace();
+     }
+     return noOfDays <= diffInDays;
+   }
+   /**
+    * Get the leading integer part of a string that begins with an integer.
+    * 
+    * @param input
+    *          - the string input to process
+    * @param failValue
+    *          - value returned if unsuccessful
+    * @return
+    */
+   public static int getLeadingIntegerValue(String input, int failValue)
+   {
+     if (input == null)
+     {
+       return failValue;
+     }
+     if (isJS)
+     {
+       int val = /** @j2sNative 1 ? parseInt(input) : */
+               0;
+       return (val == val + 0 ? val : failValue);
+     }
+     // JavaScript does not support Regex ? lookahead
+     String[] parts = input.split("(?=\\D)(?<=\\d)");
+     if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
+     {
+       return Integer.valueOf(parts[0]);
+     }
+     return failValue;
+   }
+   public static Map<String, Object> getAppletInfoAsMap()
+   {
+     return (isJS ? jsutil.getAppletInfoAsMap() : null);
+   }
+   /**
+    * Get the SwingJS applet ID and combine that with the frameType
+    * 
+    * @param frameType
+    *          "alignment", "desktop", etc., or null
+    * @return
+    */
+   public static String getAppID(String frameType)
+   {
+     String id = Jalview.getInstance().j2sAppletID;
+     if (id == null)
+     {
+       Jalview.getInstance().j2sAppletID = id = (isJS
+               ? (String) jsutil.getAppletAttribute("_id")
+               : "jalview");
+     }
+     return id + (frameType == null ? "" : "-" + frameType);
+   }
+   /**
+    * Option to avoid unnecessary seeking of nonexistent resources in JavaScript.
+    * Works in Java as well.
+    * 
+    * @param loc
+    * @return
+    */
+   public static Locale getLocaleOrNone(Locale loc)
+   {
+     return (isJS && loc.getLanguage() == "en" ? new Locale("") : loc);
+   }
+   /**
+    * From UrlDownloadClient; trivial in JavaScript; painful in Java.
+    * 
+    * @param urlstring
+    * @param outfile
+    * @throws IOException
+    */
+   public static void download(String urlstring, String outfile)
+           throws IOException
+   {
+     Path temp = null;
+     try (InputStream is = new URL(urlstring).openStream())
+     {
+       if (isJS)
+       { // so much easier!
+         streamToFile(is, new File(outfile));
+         return;
+       }
+       temp = Files.createTempFile(".jalview_", ".tmp");
+       try (FileOutputStream fos = new FileOutputStream(temp.toString());
+               ReadableByteChannel rbc = Channels.newChannel(is))
+       {
+         fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+         // copy tempfile to outfile once our download completes
+         // incase something goes wrong
+         Files.copy(temp, Paths.get(outfile),
+                 StandardCopyOption.REPLACE_EXISTING);
+       }
+     } catch (IOException e)
+     {
+       throw e;
+     } finally
+     {
+       try
+       {
+         if (temp != null)
+         {
+           Files.deleteIfExists(temp);
+         }
+       } catch (IOException e)
+       {
+         System.out.println("Exception while deleting download temp file: "
+                 + e.getMessage());
+       }
+     }
+   }
  }
@@@ -117,6 -117,29 +117,6 @@@ public class StringUtil
    }
  
    /**
 -   * Returns the last part of 'input' after the last occurrence of 'token'. For
 -   * example to extract only the filename from a full path or URL.
 -   * 
 -   * @param input
 -   * @param token
 -   *          a delimiter which must be in regular expression format
 -   * @return
 -   */
 -  public static String getLastToken(String input, String token)
 -  {
 -    if (input == null)
 -    {
 -      return null;
 -    }
 -    if (token == null)
 -    {
 -      return input;
 -    }
 -    String[] st = input.split(token);
 -    return st[st.length - 1];
 -  }
 -
 -  /**
     * Parses the input string into components separated by the delimiter. Unlike
     * String.split(), this method will ignore occurrences of the delimiter which
     * are nested within single quotes in name-value pair values, e.g. a='b,c'.
      }
      return enc;
    }
+   /**
+    * Answers true if the string is not empty and consists only of digits, or
+    * characters 'a'-'f' or 'A'-'F', else false
+    * 
+    * @param s
+    * @return
+    */
+   public static boolean isHexString(String s)
+   {
+     int j = s.length();
+     if (j == 0)
+     {
+       return false;
+     }
+     for (int i = 0; i < j; i++)
+     {
+       int c = s.charAt(i);
+       if (!(c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'))
+       {
+         return false;
+       }
+     }
+     return true;
+   }
  }
@@@ -24,8 -24,6 +24,8 @@@ import jalview.analysis.AnnotationSorte
  import jalview.analysis.Conservation;
  import jalview.analysis.TreeModel;
  import jalview.api.AlignCalcManagerI;
 +import jalview.api.AlignCalcManagerI2;
 +import jalview.api.AlignCalcWorkerI;
  import jalview.api.AlignExportSettingsI;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
@@@ -59,10 -57,8 +59,10 @@@ import jalview.util.MappingUtils
  import jalview.util.MessageManager;
  import jalview.viewmodel.styles.ViewStyle;
  import jalview.workers.AlignCalcManager;
 +import jalview.workers.AlignCalcManager2;
  import jalview.workers.ComplementConsensusThread;
  import jalview.workers.ConsensusThread;
 +import jalview.workers.InformationThread;
  import jalview.workers.StrucConsensusThread;
  
  import java.awt.Color;
@@@ -87,6 -83,9 +87,9 @@@ import java.util.Map
  public abstract class AlignmentViewport
          implements AlignViewportI, CommandListener, VamsasSource
  {
+   public static final String PROPERTY_ALIGNMENT = "alignment";
+   public static final String PROPERTY_SEQUENCE = "sequence";
    protected ViewportRanges ranges;
  
    protected ViewStyleI viewStyle = new ViewStyle();
     * alignment displayed in the viewport. Please use get/setter
     */
    protected AlignmentI alignment;
 +  
 +  /*
 +   * probably unused indicator that view is of a dataset rather than an
 +   * alignment
 +   */
 +
 +  protected boolean ignoreBelowBackGroundFrequencyCalculation = false;
 +
 +  protected boolean infoLetterHeight = false;
 +
 +  protected AlignmentAnnotation occupancy;
 +  
 +  /**
 +   * results of alignment consensus analysis for visible portion of view
 +   */
 +  protected ProfilesI consensusProfiles;
 +
 +  /**
 +   * HMM profile for the alignment
 +   */
 +  protected ProfilesI hmmProfiles;
  
    public AlignmentViewport(AlignmentI al)
    {
     * alignment
     */
    protected boolean isDataset = false;
 -
 +  
    public void setDataset(boolean b)
    {
      isDataset = b;
  
    protected ColumnSelection colSel = new ColumnSelection();
  
-   public boolean autoCalculateConsensus = true;
+   protected boolean autoCalculateConsensusAndConservation = true;
+   public boolean getAutoCalculateConsensusAndConservation()
+   { // BH 2019.07.24
+     return autoCalculateConsensusAndConservation;
+   }
+   public void setAutoCalculateConsensusAndConservation(boolean b)
+   {
+     autoCalculateConsensusAndConservation = b;
+   }
  
    protected boolean autoCalculateStrucConsensus = true;
  
+   public boolean getAutoCalculateStrucConsensus()
+   { // BH 2019.07.24
+     return autoCalculateStrucConsensus;
+   }
+   public void setAutoCalculateStrucConsensus(boolean b)
+   {
+     autoCalculateStrucConsensus = b;
+   }
    protected boolean ignoreGapsInConsensusCalculation = false;
  
    protected ResidueShaderI residueShading = new ResidueShader();
 -
 +  
    @Override
    public void setGlobalColourScheme(ColourSchemeI cs)
    {
    {
      return residueShading;
    }
 -
 +  
    protected AlignmentAnnotation consensus;
  
    protected AlignmentAnnotation complementConsensus;
    protected Hashtable<String, Object>[] hStrucConsensus = null;
  
    protected Conservation hconservation = null;
 -
 +  
    @Override
    public void setConservation(Conservation cons)
    {
    }
  
    @Override
 +  public void setHmmProfiles(ProfilesI info)
 +  {
 +    hmmProfiles = info;
 +  }
 +
 +  @Override
 +  public ProfilesI getHmmProfiles()
 +  {
 +    return hmmProfiles;
 +  }
 +
 +  @Override
    public Hashtable<String, Object>[] getComplementConsensusHash()
    {
      return hcomplementConsensus;
      return strucConsensus;
    }
  
 -  protected AlignCalcManagerI calculator = new AlignCalcManager();
 +  protected AlignCalcManagerI2 calculator = new AlignCalcManager2();
  
    /**
     * trigger update of conservation annotation
      // see note in mantis : issue number 8585
      if (alignment.isNucleotide()
              || (conservation == null && quality == null)
-             || !autoCalculateConsensus)
+             || !autoCalculateConsensusAndConservation)
      {
        return;
      }
 -    if (calculator.getRegisteredWorkersOfClass(
 -            jalview.workers.ConservationThread.class) == null)
 +    if (calculator.getWorkersOfClass(
 +            jalview.workers.ConservationThread.class).isEmpty())
      {
        calculator.registerWorker(
                new jalview.workers.ConservationThread(this, ap));
    public void updateConsensus(final AlignmentViewPanel ap)
    {
      // see note in mantis : issue number 8585
-     if (consensus == null || !autoCalculateConsensus)
+     if (consensus == null || !autoCalculateConsensusAndConservation)
      {
        return;
      }
 -    if (calculator
 -            .getRegisteredWorkersOfClass(ConsensusThread.class) == null)
 +    if (calculator.getWorkersOfClass(ConsensusThread.class).isEmpty())
      {
        calculator.registerWorker(new ConsensusThread(this, ap));
      }
        }
        if (doConsensus)
        {
 -        if (calculator.getRegisteredWorkersOfClass(
 -                ComplementConsensusThread.class) == null)
 +        if (calculator.getWorkersOfClass(ComplementConsensusThread.class).isEmpty())
          {
 -          calculator
 -                  .registerWorker(new ComplementConsensusThread(this, ap));
 +          calculator.registerWorker(new ComplementConsensusThread(this, ap));
          }
        }
      }
    }
  
 +  @Override
 +  public void initInformationWorker(final AlignmentViewPanel ap)
 +  {
 +    if (calculator.getWorkersOfClass(InformationThread.class).isEmpty())
 +    {
 +      calculator.registerWorker(new InformationThread(this, ap));
 +    }
 +  }
 +
    // --------START Structure Conservation
    public void updateStrucConsensus(final AlignmentViewPanel ap)
    {
      {
        return;
      }
 -    if (calculator.getRegisteredWorkersOfClass(
 -            StrucConsensusThread.class) == null)
 +    if (calculator.getWorkersOfClass(StrucConsensusThread.class).isEmpty())
      {
        calculator.registerWorker(new StrucConsensusThread(this, ap));
      }
      {
        return false;
      }
 -    if (calculator.workingInvolvedWith(alignmentAnnotation))
 +    if (calculator.isWorkingWithAnnotation(alignmentAnnotation))
      {
        // System.err.println("grey out ("+alignmentAnnotation.label+")");
        return true;
      strucConsensus = null;
      conservation = null;
      quality = null;
 +    consensusProfiles = null;
      groupConsensus = null;
      groupConservation = null;
      hconsensus = null;
      hconservation = null;
      hcomplementConsensus = null;
      gapcounts = null;
 +    calculator.shutdown();
      calculator = null;
      residueShading = null; // may hold a reference to Consensus
      changeSupport = null;
    }
  
    @Override
 -  public AlignCalcManagerI getCalcManager()
 +  public AlignCalcManagerI2 getCalcManager()
    {
      return calculator;
    }
    protected boolean showConsensusHistogram = true;
  
    /**
 +   * should hmm profile be rendered by default
 +   */
 +  protected boolean hmmShowSequenceLogo = false;
 +
 +  /**
 +   * should hmm profile be rendered normalised to row height
 +   */
 +  protected boolean hmmNormaliseSequenceLogo = false;
 +
 +  /**
 +   * should information histograms be rendered by default
 +   */
 +  protected boolean hmmShowHistogram = true;
 +
 +  /**
     * @return the showConsensusProfile
     */
    @Override
    }
  
    /**
 +   * @return the showInformationProfile
 +   */
 +  @Override
 +  public boolean isShowHMMSequenceLogo()
 +  {
 +    return hmmShowSequenceLogo;
 +  }
 +
 +  /**
     * @param showSequenceLogo
     *          the new value
     */
        // TODO: decouple settings setting from calculation when refactoring
        // annotation update method from alignframe to viewport
        this.showSequenceLogo = showSequenceLogo;
 -      calculator.updateAnnotationFor(ConsensusThread.class);
 -      calculator.updateAnnotationFor(ComplementConsensusThread.class);
 -      calculator.updateAnnotationFor(StrucConsensusThread.class);
 +      for (AlignCalcWorkerI worker : calculator.getWorkers())
 +      {
 +        if (worker.getClass().equals(ConsensusThread.class) ||
 +                worker.getClass().equals(ComplementConsensusThread.class) ||
 +                worker.getClass().equals(StrucConsensusThread.class))
 +        {
 +          worker.updateAnnotation();
 +        }
 +      }
      }
      this.showSequenceLogo = showSequenceLogo;
    }
  
 +  public void setShowHMMSequenceLogo(boolean showHMMSequenceLogo)
 +  {
 +    if (showHMMSequenceLogo != this.hmmShowSequenceLogo)
 +    {
 +      this.hmmShowSequenceLogo = showHMMSequenceLogo;
 +      // TODO: updateAnnotation if description (tooltip) will show
 +      // profile in place of information content?
 +      // calculator.updateAnnotationFor(InformationThread.class);
 +    }
 +    this.hmmShowSequenceLogo = showHMMSequenceLogo;
 +  }
 +
    /**
     * @param showConsensusHistogram
     *          the showConsensusHistogram to set
    }
  
    /**
 +   * @param showInformationHistogram
 +   */
 +  public void setShowInformationHistogram(boolean showInformationHistogram)
 +  {
 +    this.hmmShowHistogram = showInformationHistogram;
 +  }
 +
 +  /**
     * @return the showGroupConservation
     */
    public boolean isShowGroupConservation()
    }
  
    /**
 +   * 
 +   * @return flag to indicate if the information content histogram should be
 +   *         rendered by default
 +   */
 +  @Override
 +  public boolean isShowInformationHistogram()
 +  {
 +    return this.hmmShowHistogram;
 +  }
 +
 +  /**
     * when set, updateAlignment will always ensure sequences are of equal length
     */
    private boolean padGaps = false;
                  ignoreGapsInConsensusCalculation);
        }
      }
 +  }
 +
 +  public void setIgnoreBelowBackground(boolean b, AlignmentViewPanel ap)
 +  {
 +    ignoreBelowBackGroundFrequencyCalculation = b;
 +  }
  
 +  public void setInfoLetterHeight(boolean b, AlignmentViewPanel ap)
 +  {
 +    infoLetterHeight = b;
    }
  
    private long sgrouphash = -1, colselhash = -1;
     * checks current colsel against record of last hash value, and optionally
     * updates record.
     * 
-    * @param b
+    * @param updateHash
     *          update the record of last hash value
     * @return true if colsel changed since last call (when b is true)
     */
-   public boolean isColSelChanged(boolean b)
+   public boolean isColSelChanged(boolean updateHash)
    {
      int hc = (colSel == null || colSel.isEmpty()) ? -1 : colSel.hashCode();
      if (hc != -1 && hc != colselhash)
      {
-       if (b)
+       if (updateHash)
        {
          colselhash = hc;
        }
        return true;
      }
+     notifySequence();
      return false;
    }
  
      return ignoreGapsInConsensusCalculation;
    }
  
 +  @Override
 +  public boolean isIgnoreBelowBackground()
 +  {
 +    return ignoreBelowBackGroundFrequencyCalculation;
 +  }
 +
 +  @Override
 +  public boolean isInfoLetterHeight()
 +  {
 +    return infoLetterHeight;
 +  }
 +
    // property change stuff
    // JBPNote Prolly only need this in the applet version.
    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
      }
    }
  
-   /**
-    * Property change listener for changes in alignment
-    * 
-    * @param prop
-    *          DOCUMENT ME!
-    * @param oldvalue
-    *          DOCUMENT ME!
-    * @param newvalue
-    *          DOCUMENT ME!
-    */
-   public void firePropertyChange(String prop, Object oldvalue,
-           Object newvalue)
-   {
-     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
-   }
    // common hide/show column stuff
  
    public void hideSelectedColumns()
  
        ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
  
-       firePropertyChange("alignment", null, alignment.getSequences());
        // used to set hasHiddenRows/hiddenRepSequences here, after the property
        // changed event
+       notifySequence();
        sendSelection();
      }
    }
  
    public void showSequence(int index)
    {
      int startSeq = ranges.getStartSeq();
        }
  
        ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
-       firePropertyChange("alignment", null, alignment.getSequences());
+       notifyAlignment();
        sendSelection();
      }
    }
          setSequenceAnnotationsVisible(seq[i], false);
        }
        ranges.setStartSeq(startSeq);
-       firePropertyChange("alignment", null, alignment.getSequences());
+       notifyAlignment();
      }
    }
  
      {
        alignment.padGaps();
      }
-     if (autoCalculateConsensus)
+     if (autoCalculateConsensusAndConservation)
      {
        updateConsensus(ap);
      }
-     if (hconsensus != null && autoCalculateConsensus)
+     if (hconsensus != null && autoCalculateConsensusAndConservation)
      {
        updateConservation(ap);
      }
  
      updateAllColourSchemes();
      calculator.restartWorkers();
 -    // alignment.adjustSequenceAnnotations();
    }
  
    /**
                MessageManager.getString("label.consensus_descr"),
                new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
        initConsensus(consensus);
 +
        initGapCounts();
  
        initComplementConsensus();
      boolean showprf = isShowSequenceLogo();
      boolean showConsHist = isShowConsensusHistogram();
      boolean normLogo = isNormaliseSequenceLogo();
 +    boolean showHMMPrf = isShowHMMSequenceLogo();
 +    boolean showInfoHist = isShowInformationHistogram();
 +    boolean normHMMLogo = isNormaliseHMMSequenceLogo();
  
      /**
       * TODO reorder the annotation rows according to group/sequence ordering on
            sg.setshowSequenceLogo(showprf);
            sg.setShowConsensusHistogram(showConsHist);
            sg.setNormaliseSequenceLogo(normLogo);
 +          sg.setShowHMMSequenceLogo(showHMMPrf);
 +          sg.setShowInformationHistogram(showInfoHist);
 +          sg.setNormaliseHMMSequenceLogo(normHMMLogo);
          }
          if (conv)
          {
      return sq;
    }
  
 +  public boolean hasReferenceAnnotation()
 +  {
 +    AlignmentAnnotation[] annots = this.alignment.getAlignmentAnnotation();
 +    for (AlignmentAnnotation annot : annots)
 +    {
 +      if ("RF".equals(annot.label) || annot.label.contains("Reference"))
 +      {
 +        return true;
 +      }
 +    }
 +    return false;
 +  }
 +
    @Override
    public void setCurrentTree(TreeModel tree)
    {
      return ed;
    }
    
 +  @Override
 +  public boolean isNormaliseSequenceLogo()
 +  {
 +    return normaliseSequenceLogo;
 +  }
 +
 +  public void setNormaliseSequenceLogo(boolean state)
 +  {
 +    normaliseSequenceLogo = state;
 +  }
 +
 +  @Override
 +  public boolean isNormaliseHMMSequenceLogo()
 +  {
 +    return hmmNormaliseSequenceLogo;
 +  }
 +
 +  public void setNormaliseHMMSequenceLogo(boolean state)
 +  {
 +    hmmNormaliseSequenceLogo = state;
 +  }
    /**
     * flag set to indicate if structure views might be out of sync with sequences
     * in the alignment
        codingComplement.setUpdateStructures(needToUpdateStructureViews);
      }
    }
 -  
 +  /**
 +   * Filters out sequences with an eValue higher than the specified value. The
 +   * filtered sequences are hidden or deleted. Sequences with no eValues are also
 +   * filtered out.
 +   * 
 +   * @param eValue
 +   * @param delete
 +   */
 +  public void filterByEvalue(double eValue)
 +  {
 +    for (SequenceI seq : alignment.getSequencesArray())
 +    {
 +      if ((seq.getAnnotation("Search Scores") == null
 +              || seq.getAnnotation("Search Scores")[0].getEValue() > eValue)
 +              && seq.getHMM() == null)
 +      {
 +        hideSequence(new SequenceI[] { seq });
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Filters out sequences with an score lower than the specified value. The
 +   * filtered sequences are hidden or deleted.
 +   * 
 +   * @param score
 +   * @param delete
 +   */
 +  public void filterByScore(double score)
 +  {
 +    for (SequenceI seq : alignment.getSequencesArray())
 +    {
 +      if ((seq.getAnnotation("Search Scores") == null
 +              || seq.getAnnotation("Search Scores")[0]
 +                      .getBitScore() < score)
 +              && seq.getHMM() == null)
 +      {
 +        hideSequence(new SequenceI[] { seq });
 +      }
 +    }
++  }  
+   /**
+    * Notify TreePanel and AlignmentPanel of some sort of alignment change.
+    */
+   public void notifyAlignment()
+   {
+     changeSupport.firePropertyChange(PROPERTY_ALIGNMENT, null, alignment.getSequences());
+   }
+   
+   /**
+    * Notify AlignmentPanel of a sequence column selection or visibility changes.
+    */
+   public void notifySequence()
+   {
+     changeSupport.firePropertyChange(PROPERTY_SEQUENCE, null, null);
    }
 -
  }
@@@ -22,12 -22,10 +22,12 @@@ package jalview.workers
  
  import jalview.api.AlignCalcManagerI;
  import jalview.api.AlignCalcWorkerI;
 +import jalview.bin.Cache;
  import jalview.datamodel.AlignmentAnnotation;
  
  import java.util.ArrayList;
  import java.util.Collections;
 +import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Hashtable;
  import java.util.List;
@@@ -39,49 -37,48 +39,49 @@@ public class AlignCalcManager implement
    /*
     * list of registered workers
     */
 -  private volatile List<AlignCalcWorkerI> restartable;
 +  private final List<AlignCalcWorkerI> restartable = Collections
 +          .synchronizedList(new ArrayList<AlignCalcWorkerI>());
  
    /*
     * types of worker _not_ to run (for example, because they have
     * previously thrown errors)
     */
 -  private volatile List<Class<? extends AlignCalcWorkerI>> blackList;
 +  private final List<Class<? extends AlignCalcWorkerI>> blackList = Collections
 +          .synchronizedList(new ArrayList<Class<? extends AlignCalcWorkerI>>());
  
    /*
     * global record of calculations in progress
     */
 -  private volatile List<AlignCalcWorkerI> inProgress;
 +  private final List<AlignCalcWorkerI> inProgress = Collections
 +          .synchronizedList(new ArrayList<AlignCalcWorkerI>());
  
    /*
     * record of calculations pending or in progress in the current context
     */
 -  private volatile Map<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>> updating;
 +  private final Map<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>> updating =
 +          new Hashtable<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>>();
  
    /*
     * workers that have run to completion so are candidates for visual-only 
     * update of their results
     */
 -  private HashSet<AlignCalcWorkerI> canUpdate;
 +  private HashSet<AlignCalcWorkerI> canUpdate = new HashSet<>();;
  
 -  /**
 -   * Constructor
 -   */
 -  public AlignCalcManager()
 +  private static boolean listContains(List<AlignCalcWorkerI> upd,
 +          AlignCalcWorkerI worker)
    {
 -    restartable = Collections
 -            .synchronizedList(new ArrayList<AlignCalcWorkerI>());
 -    blackList = Collections.synchronizedList(
 -            new ArrayList<Class<? extends AlignCalcWorkerI>>());
 -    inProgress = Collections
 -            .synchronizedList(new ArrayList<AlignCalcWorkerI>());
 -    updating = Collections.synchronizedMap(
 -            new Hashtable<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>>());
 -    canUpdate = new HashSet<AlignCalcWorkerI>();
 +    // avoid use of 'Contains' in case
 +    for (AlignCalcWorkerI _otherworker : upd)
 +    {
 +      if (_otherworker == upd)
 +      {
 +        return true;
 +      }
 +    }
 +    return false;
    }
 -
    @Override
 -  public void notifyStart(AlignCalcWorkerI worker)
 +  public void notifyStarted(AlignCalcWorkerI worker)
    {
      synchronized (updating)
      {
        }
        synchronized (upd)
        {
 -        upd.add(worker);
 +        if (listContains(upd, worker))
 +        {
 +          Cache.log.debug(
 +                    "Ignoring second call to notifyStart for worker "
 +                            + worker);
 +        }
 +        else
 +        {
 +          upd.add(worker);
 +        }
        }
      }
    }
    @Override
    public boolean isPending(AlignCalcWorkerI workingClass)
    {
 -    List<AlignCalcWorkerI> upd;
      synchronized (updating)
      {
 -      upd = updating.get(workingClass.getClass());
 -      if (upd == null)
 -      {
 -        return false;
 -      }
 -      synchronized (upd)
 -      {
 -        if (upd.size() > 1)
 -        {
 -          return true;
 -        }
 -      }
 -      return false;
 +      List<AlignCalcWorkerI> upd = updating.get(workingClass.getClass());
 +      return upd != null && upd.size() > 1;
      }
    }
  
    {
      synchronized (inProgress)
      {
 -      if (inProgress.contains(worker))
 +      if (listContains(inProgress, worker))
        {
          return false; // worker is already working, so ask caller to wait around
        }
    {
      synchronized (inProgress)
      {
 -      // System.err.println("Worker " + worker + " marked as complete.");
 +      Cache.log.debug("Worker " + worker + " marked as complete.");
        inProgress.remove(worker);
        List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
        if (upd != null)
    {
      if (!isDisabled(worker))
      {
 -      Thread tw = new Thread(worker);
 +      Thread tw = new Thread(() -> {
 +        try
 +        {
 +          worker.run();
 +        } catch (Throwable e)
 +        {
 +          e.printStackTrace();
 +        }
 +      });
        tw.setName(worker.getClass().toString());
        tw.start();
      }
      }
    }
  
+   public int getQueueLength() {
+     return inProgress.size();
+   }
+   
    @Override
    public void registerWorker(AlignCalcWorkerI worker)
    {
      synchronized (restartable)
      {
 -      if (!restartable.contains(worker))
 +      if (!listContains(restartable, worker))
        {
          restartable.add(worker);
        }
    public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
            Class<? extends AlignCalcWorkerI> workerClass)
    {
 -    List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
 +    List<AlignCalcWorkerI> workingClass = new ArrayList<>();
      synchronized (canUpdate)
      {
        for (AlignCalcWorkerI worker : canUpdate)
    }
  
    @Override
 -  public void removeRegisteredWorkersOfClass(
 +  public void removeWorkersOfClass(
            Class<? extends AlignCalcWorkerI> typeToRemove)
    {
 -    List<AlignCalcWorkerI> removable = new ArrayList<AlignCalcWorkerI>();
 -    Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
 +    List<AlignCalcWorkerI> removable = new ArrayList<>();
 +    Set<AlignCalcWorkerI> toremovannot = new HashSet<>();
      synchronized (restartable)
      {
        for (AlignCalcWorkerI worker : restartable)
       * first just find those to remove (to avoid
       * ConcurrentModificationException)
       */
 -    List<AlignCalcWorkerI> toRemove = new ArrayList<AlignCalcWorkerI>();
 +    List<AlignCalcWorkerI> toRemove = new ArrayList<>();
      for (AlignCalcWorkerI worker : restartable)
      {
        if (worker.involves(ann))
   */
  package jalview.ws.jws1;
  
- import jalview.bin.Cache;
- import jalview.gui.JvOptionPane;
- import jalview.util.MessageManager;
  import java.net.URL;
  import java.util.Hashtable;
  import java.util.StringTokenizer;
@@@ -34,9 -30,24 +30,25 @@@ import ext.vamsas.IRegistryServiceLocat
  import ext.vamsas.RegistryServiceSoapBindingStub;
  import ext.vamsas.ServiceHandle;
  import ext.vamsas.ServiceHandles;
++import jalview.bin.Cache;
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+ import jalview.gui.JvOptionPane;
+ import jalview.util.MessageManager;
  
- public class Discoverer implements Runnable
+ public class Discoverer implements Runnable, ApplicationSingletonI
  {
+   public static Discoverer getInstance()
+   {
+     return (Discoverer) ApplicationSingletonProvider.getInstance(Discoverer.class);
+   }
+   private Discoverer()
+   {
+     // use getInstance()
+   }
    ext.vamsas.IRegistry registry; // the root registry service.
  
    private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
      return server;
    }
  
-   static private java.net.URL RootServiceURL = null;
+   private java.net.URL RootServiceURL = null;
  
-   static public Vector<URL> ServiceURLList = null;
+   private Vector<URL> ServiceURLList = null;
  
-   static private boolean reallyDiscoverServices = true;
+   public Vector<URL> getServiceURLList() {
+     return ServiceURLList;
+   }
+   
+   private boolean reallyDiscoverServices = true;
  
-   public static java.util.Hashtable<String, Vector<ServiceHandle>> services = null;
+   private java.util.Hashtable<String, Vector<ServiceHandle>> services = null;
    // stored by
    // abstractServiceType
    // string
  
-   public static java.util.Vector<ServiceHandle> serviceList = null;
+   public java.util.Vector<ServiceHandle> serviceList = null;
  
-   static private Vector<URL> getDiscoveryURLS()
+   private Vector<URL> getDiscoveryURLS()
    {
      Vector<URL> urls = new Vector<>();
      String RootServiceURLs = jalview.bin.Cache.getDefault("DISCOVERY_URLS",
     */
    static public void doDiscovery()
    {
+     getInstance().discovery();
+   }
+   private void discovery()
+   {
      jalview.bin.Cache.log
              .debug("(Re)-Initialising the discovery URL list.");
      try
      {
+       Discoverer d = getInstance();
        reallyDiscoverServices = jalview.bin.Cache
                .getDefault("DISCOVERY_START", false);
        if (reallyDiscoverServices)
        // JBPNote - should do this a better way!
        if (f.getFaultReason().indexOf("(407)") > -1)
        {
-         if (jalview.gui.Desktop.desktop != null)
+         if (jalview.gui.Desktop.getDesktopPane() != null)
          {
-           JvOptionPane.showMessageDialog(jalview.gui.Desktop.desktop,
+           JvOptionPane.showMessageDialog(jalview.gui.Desktop.getDesktopPane(),
                    MessageManager.getString("label.set_proxy_settings"),
                    MessageManager
                            .getString("label.proxy_authorization_failed"),
     *          Hashtable
     * @return boolean
     */
-   static private boolean buildServiceLists(ServiceHandle[] sh,
+   private boolean buildServiceLists(ServiceHandle[] sh,
            Vector<ServiceHandle> cat,
            Hashtable<String, Vector<ServiceHandle>> sscat)
    {
    @Override
    public void run()
    {
 -    final Discoverer discoverer = this;
 -    Thread discoverThread = new Thread()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        Discoverer.doDiscovery();
 -        discoverer.discoverServices();
 -      }
 -    };
 -    discoverThread.start();
 +    Cache.log.info("Discovering jws1 services");
 +    Discoverer.doDiscovery();
 +    discoverServices();
    }
  
    /**
     * binding service abstract name to handler class
     */
-   private static Hashtable<String, WS1Client> serviceClientBindings;
+   private Hashtable<String, WS1Client> serviceClientBindings;
  
    public static WS1Client getServiceClient(ServiceHandle sh)
    {
+     return getInstance().getClient(sh);
+   }
+   
+   /**
+    * notes on discovery service 1. need to allow multiple discovery source urls.
+    * 2. user interface to add/control list of urls in preferences notes on
+    * wsclient discovery 1. need a classpath property with list of additional
+    * plugin directories 2. optional config to cite specific bindings between
+    * class name and Abstract service name. 3. precedence for automatic discovery
+    * by using getAbstractName for WSClient - user added plugins override default
+    * plugins ? notes on wsclient gui code for gui attachment now moved to
+    * wsclient implementation. Needs more abstraction but approach seems to work.
+    * is it possible to 'generalise' the data retrieval calls further ? current
+    * methods are very specific (gatherForMSA or gatherForSeqOrMsaSecStrPred),
+    * new methods for conservation (group or alignment), treecalc (aligned
+    * profile), seqannot (sequences selected from dataset, annotation back to
+    * dataset).
+    * 
+    */
+   private WS1Client getClient(ServiceHandle sh)
+   {
      if (serviceClientBindings == null)
      {
        // get a list from Config or create below
      }
      return instance;
    }
-   /**
-    * notes on discovery service 1. need to allow multiple discovery source urls.
-    * 2. user interface to add/control list of urls in preferences notes on
-    * wsclient discovery 1. need a classpath property with list of additional
-    * plugin directories 2. optional config to cite specific bindings between
-    * class name and Abstract service name. 3. precedence for automatic discovery
-    * by using getAbstractName for WSClient - user added plugins override default
-    * plugins ? notes on wsclient gui code for gui attachment now moved to
-    * wsclient implementation. Needs more abstraction but approach seems to work.
-    * is it possible to 'generalise' the data retrieval calls further ? current
-    * methods are very specific (gatherForMSA or gatherForSeqOrMsaSecStrPred),
-    * new methods for conservation (group or alignment), treecalc (aligned
-    * profile), seqannot (sequences selected from dataset, annotation back to
-    * dataset).
-    * 
-    */
+   public static Hashtable<String, Vector<ServiceHandle>> getServices()
+   {
+     return getInstance().services;
+   }
  }
@@@ -51,6 -51,8 +51,6 @@@ public class MsaWSClient extends WS1Cli
     */
    ext.vamsas.MuscleWS server;
  
 -  AlignFrame alignFrame;
 -
    /**
     * Creates a new MsaWSClient object that uses a service given by an externally
     * retrieved ServiceHandle
@@@ -76,7 -78,7 +76,7 @@@
      alignFrame = _alignFrame;
      if (!sh.getAbstractName().equals("MsaWS"))
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop,
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.service_called_is_not_msa_service",
                        new String[]
@@@ -89,7 -91,7 +89,7 @@@
  
      if ((wsInfo = setWebService(sh)) == null)
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), MessageManager
                .formatMessage("label.msa_service_is_unknown", new String[]
                { sh.getName() }),
                MessageManager.getString("label.internal_jalview_error"),
@@@ -56,6 -56,8 +56,6 @@@ public class SeqSearchWSClient extends 
     */
    ext.vamsas.SeqSearchI server;
  
 -  AlignFrame alignFrame;
 -
    /**
     * Creates a new MsaWSClient object that uses a service given by an externally
     * retrieved ServiceHandle
@@@ -82,7 -84,7 +82,7 @@@
      // name to service client name
      if (!sh.getAbstractName().equals(this.getServiceActionKey()))
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop,
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.service_called_is_not_seq_search_service",
                        new String[]
@@@ -95,7 -97,7 +95,7 @@@
  
      if ((wsInfo = setWebService(sh)) == null)
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop,
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.seq_search_service_is_unknown", new String[]
                        { sh.getName() }),
index 98282fa,0000000..bef7e29
mode 100644,000000..100644
--- /dev/null
@@@ -1,264 -1,0 +1,264 @@@
 +package jalview.ws.jws2;
 +
 +import jalview.gui.WsJobParameters;
 +import jalview.util.MessageManager;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.jws2.jabaws2.Jws2Instance;
 +
 +import java.awt.BorderLayout;
 +import java.awt.event.WindowEvent;
 +import java.awt.event.WindowListener;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Vector;
 +
 +import javax.swing.JFrame;
 +import javax.swing.JPanel;
 +
 +import compbio.metadata.Option;
 +import compbio.metadata.Parameter;
 +import compbio.metadata.Preset;
 +import compbio.metadata.PresetManager;
 +
 +public class JabaWsParamTest
 +{
 +
 +  /**
 +   * testing method - grab a service and parameter set and show the window
 +   * 
 +   * @param args
 +   */
 +  public static void main(String[] args)
 +  {
 +    jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
-             .getDiscoverer();
++            .getInstance();
 +    int p = 0;
 +    if (args.length > 0)
 +    {
 +      Vector<String> services = new Vector<>();
 +      services.addElement(args[p++]);
-       Jws2Discoverer.getDiscoverer().setServiceUrls(services);
++      Jws2Discoverer.getInstance().setServiceUrls(services);
 +    }
 +    try
 +    {
 +      disc.run();
 +    } catch (Exception e)
 +    {
 +      System.err.println("Aborting. Problem discovering services.");
 +      e.printStackTrace();
 +      return;
 +    }
 +    Jws2Instance lastserv = null;
 +    for (ServiceWithParameters service : disc.getServices())
 +    {
 +      // this will fail for non-JABAWS services !
 +      lastserv = (Jws2Instance) service;
 +      if (p >= args.length || service.getName().equalsIgnoreCase(args[p]))
 +      {
 +        if (lastserv != null)
 +        {
 +          List<Preset> prl = null;
 +          Preset pr = null;
 +          if (++p < args.length)
 +          {
 +            PresetManager prman = lastserv.getPresets();
 +            if (prman != null)
 +            {
 +              pr = prman.getPresetByName(args[p]);
 +              if (pr == null)
 +              {
 +                // just grab the last preset.
 +                prl = prman.getPresets();
 +              }
 +            }
 +          }
 +          else
 +          {
 +            PresetManager prman = lastserv.getPresets();
 +            if (prman != null)
 +            {
 +              prl = prman.getPresets();
 +            }
 +          }
 +          Iterator<Preset> en = (prl == null) ? null : prl.iterator();
 +          while (en != null && en.hasNext())
 +          {
 +            if (en != null)
 +            {
 +              if (!en.hasNext())
 +              {
 +                en = prl.iterator();
 +              }
 +              pr = en.next();
 +            }
 +            {
 +              System.out.println("Testing opts dupes for "
 +                      + lastserv.getUri() + " : " + lastserv.getActionText()
 +                      + ":" + pr.getName());
 +              List<Option> rg = lastserv.getRunnerConfig().getOptions();
 +              for (Option o : rg)
 +              {
 +                try
 +                {
 +                  Option cpy = jalview.ws.jws2.ParameterUtils.copyOption(o);
 +                } catch (Exception e)
 +                {
 +                  System.err.println("Failed to copy " + o.getName());
 +                  e.printStackTrace();
 +                } catch (Error e)
 +                {
 +                  System.err.println("Failed to copy " + o.getName());
 +                  e.printStackTrace();
 +                }
 +              }
 +            }
 +            {
 +              System.out.println("Testing param dupes:");
 +              List<Parameter> rg = lastserv.getRunnerConfig()
 +                      .getParameters();
 +              for (Parameter o : rg)
 +              {
 +                try
 +                {
 +                  Parameter cpy = jalview.ws.jws2.ParameterUtils
 +                          .copyParameter(o);
 +                } catch (Exception e)
 +                {
 +                  System.err.println("Failed to copy " + o.getName());
 +                  e.printStackTrace();
 +                } catch (Error e)
 +                {
 +                  System.err.println("Failed to copy " + o.getName());
 +                  e.printStackTrace();
 +                }
 +              }
 +            }
 +            {
 +              System.out.println("Testing param write:");
 +              List<String> writeparam = null, readparam = null;
 +              try
 +              {
 +                writeparam = jalview.ws.jws2.ParameterUtils
 +                        .writeParameterSet(
 +                                pr.getArguments(lastserv.getRunnerConfig()),
 +                                " ");
 +                System.out.println("Testing param read :");
 +                List<Option> pset = jalview.ws.jws2.ParameterUtils
 +                        .processParameters(writeparam,
 +                                lastserv.getRunnerConfig(), " ");
 +                readparam = jalview.ws.jws2.ParameterUtils
 +                        .writeParameterSet(pset, " ");
 +                Iterator<String> o = pr.getOptions().iterator(),
 +                        s = writeparam.iterator(), t = readparam.iterator();
 +                boolean failed = false;
 +                while (s.hasNext() && t.hasNext())
 +                {
 +                  String on = o.next(), sn = s.next(), st = t.next();
 +                  if (!sn.equals(st))
 +                  {
 +                    System.out.println(
 +                            "Original was " + on + " Phase 1 wrote " + sn
 +                                    + "\tPhase 2 wrote " + st);
 +                    failed = true;
 +                  }
 +                }
 +                if (failed)
 +                {
 +                  System.out.println(
 +                          "Original parameters:\n" + pr.getOptions());
 +                  System.out.println(
 +                          "Wrote parameters in first set:\n" + writeparam);
 +                  System.out.println(
 +                          "Wrote parameters in second set:\n" + readparam);
 +
 +                }
 +              } catch (Exception e)
 +              {
 +                e.printStackTrace();
 +              }
 +            }
 +            WsJobParameters pgui = new WsJobParameters(null, lastserv,
 +                    new JabaPreset(lastserv, pr), null);
 +            JFrame jf = new JFrame(MessageManager
 +                    .formatMessage("label.ws_parameters_for", new String[]
 +                    { lastserv.getActionText() }));
 +            JPanel cont = new JPanel(new BorderLayout());
 +            pgui.validate();
 +            cont.setPreferredSize(pgui.getPreferredSize());
 +            cont.add(pgui, BorderLayout.CENTER);
 +            jf.setLayout(new BorderLayout());
 +            jf.add(cont, BorderLayout.CENTER);
 +            jf.validate();
 +            final Thread thr = Thread.currentThread();
 +            jf.addWindowListener(new WindowListener()
 +            {
 +
 +              @Override
 +              public void windowActivated(WindowEvent e)
 +              {
 +                // TODO Auto-generated method stub
 +
 +              }
 +
 +              @Override
 +              public void windowClosed(WindowEvent e)
 +              {
 +              }
 +
 +              @Override
 +              public void windowClosing(WindowEvent e)
 +              {
 +                thr.interrupt();
 +
 +              }
 +
 +              @Override
 +              public void windowDeactivated(WindowEvent e)
 +              {
 +                // TODO Auto-generated method stub
 +
 +              }
 +
 +              @Override
 +              public void windowDeiconified(WindowEvent e)
 +              {
 +                // TODO Auto-generated method stub
 +
 +              }
 +
 +              @Override
 +              public void windowIconified(WindowEvent e)
 +              {
 +                // TODO Auto-generated method stub
 +
 +              }
 +
 +              @Override
 +              public void windowOpened(WindowEvent e)
 +              {
 +                // TODO Auto-generated method stub
 +
 +              }
 +
 +            });
 +            jf.setVisible(true);
 +            boolean inter = false;
 +            while (!inter)
 +            {
 +              try
 +              {
 +                Thread.sleep(10000);
 +              } catch (Exception e)
 +              {
 +                inter = true;
 +              }
 +              ;
 +            }
 +            jf.dispose();
 +          }
 +        }
 +      }
 +    }
 +  }
 +
 +}
  package jalview.ws.jws2;
  
  import jalview.bin.Cache;
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.gui.AlignFrame;
 -import jalview.gui.Desktop;
 -import jalview.gui.JvSwingUtils;
  import jalview.util.MessageManager;
 -import jalview.ws.WSMenuEntryProviderI;
 +import jalview.ws.ServiceChangeListener;
 +import jalview.ws.WSDiscovererI;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
  import jalview.ws.params.ParamDatastoreI;
  
 -import java.awt.Color;
 -import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.HashMap;
 +import java.util.Collections;
  import java.util.HashSet;
 -import java.util.Hashtable;
  import java.util.List;
  import java.util.Set;
  import java.util.StringTokenizer;
  import java.util.Vector;
 +import java.util.concurrent.CompletableFuture;
 +import java.util.concurrent.CopyOnWriteArraySet;
 +import java.util.concurrent.ExecutionException;
 +import java.util.concurrent.Future;
 +import java.util.concurrent.FutureTask;
  
  import javax.swing.JMenu;
 -import javax.swing.JMenuItem;
  
  import compbio.ws.client.Services;
  
   * @author JimP
   * 
   */
- public class Jws2Discoverer implements WSDiscovererI, Runnable
 -public class Jws2Discoverer
 -        implements Runnable, WSMenuEntryProviderI, ApplicationSingletonI
++public class Jws2Discoverer implements WSDiscovererI, Runnable, ApplicationSingletonI
  {
+   /**
+    * Returns the singleton instance of this class.
+    * 
+    * @return
+    */
+   public static Jws2Discoverer getInstance()
+   {
+     return (Jws2Discoverer) ApplicationSingletonProvider
+             .getInstance(Jws2Discoverer.class);
+   }
 -  /**
 -   * Private constructor enforces use of singleton via getDiscoverer()
 -   */
 -  private Jws2Discoverer()
 -  {
 -    // use getInstance();
 -  }
 -
    public static final String COMPBIO_JABAWS = "http://www.compbio.dundee.ac.uk/jabaws";
  
    /*
    private final static String JWS2HOSTURLS = "JWS2HOSTURLS";
  
    /*
-    * Singleton instance
-    */
-   private static Jws2Discoverer discoverer;
-   /*
     * Override for testing only
     */
-   private static List<String> testUrls = null;
+   private List<String> testUrls = null;
  
    // preferred url has precedence over others
    private String preferredUrl;
 -
 -  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
 -          this);
 -
 +  
 +  private Set<ServiceChangeListener> serviceListeners = new CopyOnWriteArraySet<>();
    private Vector<String> invalidServiceUrls = null;
  
    private Vector<String> urlsWithoutServices = null;
    private volatile boolean running = false;
  
    private volatile boolean aborted = false;
 -
 -  private Thread oldthread = null;
 +  
 +  private volatile Thread oldthread = null;
  
    /**
     * holds list of services.
    protected Vector<Jws2Instance> services;
  
    /**
 -   * change listeners are notified of "services" property changes
 -   * 
 -   * @param listener
 -   *          to be added that consumes new services Hashtable object.
 +   * Private constructor enforces use of singleton via getDiscoverer()
     */
 -  public void addPropertyChangeListener(
 -          java.beans.PropertyChangeListener listener)
 +  private Jws2Discoverer()
    {
 -    changeSupport.addPropertyChangeListener(listener);
    }
  
 -  /**
 -   * 
 -   * 
 -   * @param listener
 -   *          to be removed
 -   */
 -  public void removePropertyChangeListener(
 -          java.beans.PropertyChangeListener listener)
 +
 +  @Override
 +  public void addServiceChangeListener(ServiceChangeListener listener)
 +  {
 +    serviceListeners.add(listener);
 +  }
 +
 +  @Override
 +  public void removeServiceChangeListener(ServiceChangeListener listener)
    {
 -    changeSupport.removePropertyChangeListener(listener);
 +    serviceListeners.remove(listener);
 +  }
 +
 +  private void notifyServiceListeners(List<? extends ServiceWithParameters> services) 
 +  {
 +    if (services == null) services = this.services;
 +    for (var listener : serviceListeners) {
 +      listener.servicesChanged(this, services);
 +    }
    }
  
    /**
        ignoredServices.add(ignored);
      }
  
 -    changeSupport.firePropertyChange("services", services,
 -            new Vector<Jws2Instance>());
 +    notifyServiceListeners(Collections.emptyList());
      oldthread = Thread.currentThread();
      try
      {
 -      getClass().getClassLoader()
 -              .loadClass("compbio.ws.client.Jws2Client");
 +      getClass().getClassLoader().loadClass("compbio.ws.client.Jws2Client");
      } catch (ClassNotFoundException e)
      {
        System.err.println(
          for (Jws2Instance svc : services)
          {
            svcs[ipos] = svc;
 -          spos[ipos++] = 1000 * svcUrls.indexOf(svc.getHost()) + 1
 -                  + svctypes.indexOf(svc.serviceType);
 +          spos[ipos++] = 1000 * svcUrls.indexOf(svc.getHostURL()) + 1
 +                  + svctypes.indexOf(svc.getName());
          }
          jalview.util.QuickSort.sort(spos, svcs);
          services = new Vector<>();
          for (Jws2Instance svc : svcs)
          {
 -          if (!ignoredServices.contains(svc.serviceType))
 +          if (!ignoredServices.contains(svc.getName()))
            {
              services.add(svc);
            }
      }
      oldthread = null;
      running = false;
 -    changeSupport.firePropertyChange("services", new Vector<Jws2Instance>(),
 -            services);
 +    notifyServiceListeners(services);
    }
  
    /**
    }
  
    /**
 -   * attach all available web services to the appropriate submenu in the given
 -   * JMenu
 -   */
 -  @Override
 -  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
 -  {
 -    // dynamically regenerate service list.
 -    populateWSMenuEntry(wsmenu, alignFrame, null);
 -  }
 -
 -  private boolean isRecalculable(String action)
 -  {
 -    return (action != null && action.equalsIgnoreCase("conservation"));
 -  }
 -
 -  private void populateWSMenuEntry(JMenu jws2al,
 -          final AlignFrame alignFrame, String typeFilter)
 -  {
 -    if (running || services == null || services.size() == 0)
 -    {
 -      return;
 -    }
 -
 -    /**
 -     * eventually, JWS2 services will appear under the same align/etc submenus.
 -     * for moment we keep them separate.
 -     */
 -    JMenu atpoint;
 -    List<Jws2Instance> enumerableServices = new ArrayList<>();
 -    // jws2al.removeAll();
 -    Map<String, Jws2Instance> preferredHosts = new HashMap<>();
 -    Map<String, List<Jws2Instance>> alternates = new HashMap<>();
 -    for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
 -    {
 -      if (!isRecalculable(service.action))
 -      {
 -        // add 'one shot' services to be displayed using the classic menu
 -        // structure
 -        enumerableServices.add(service);
 -      }
 -      else
 -      {
 -        if (!preferredHosts.containsKey(service.serviceType))
 -        {
 -          Jws2Instance preferredInstance = getPreferredServiceFor(
 -                  alignFrame, service.serviceType);
 -          if (preferredInstance != null)
 -          {
 -            preferredHosts.put(service.serviceType, preferredInstance);
 -          }
 -          else
 -          {
 -            preferredHosts.put(service.serviceType, service);
 -          }
 -        }
 -        List<Jws2Instance> ph = alternates.get(service.serviceType);
 -        if (preferredHosts.get(service.serviceType) != service)
 -        {
 -          if (ph == null)
 -          {
 -            ph = new ArrayList<>();
 -          }
 -          ph.add(service);
 -          alternates.put(service.serviceType, ph);
 -        }
 -      }
 -
 -    }
 -
 -    // create GUI element for classic services
 -    addEnumeratedServices(jws2al, alignFrame, enumerableServices);
 -    // and the instantaneous services
 -    for (final Jws2Instance service : preferredHosts.values())
 -    {
 -      atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
 -      JMenuItem hitm;
 -      if (atpoint.getItemCount() > 1)
 -      {
 -        // previous service of this type already present
 -        atpoint.addSeparator();
 -      }
 -      atpoint.add(hitm = new JMenuItem(service.getHost()));
 -      hitm.setForeground(Color.blue);
 -      hitm.addActionListener(new ActionListener()
 -      {
 -
 -        @Override
 -        public void actionPerformed(ActionEvent e)
 -        {
 -          Desktop.showUrl(service.getHost());
 -        }
 -      });
 -      hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
 -              MessageManager.getString("label.open_jabaws_web_page")));
 -
 -      service.attachWSMenuEntry(atpoint, alignFrame);
 -      if (alternates.containsKey(service.serviceType))
 -      {
 -        atpoint.add(hitm = new JMenu(
 -                MessageManager.getString("label.switch_server")));
 -        hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
 -                MessageManager.getString("label.choose_jabaws_server")));
 -        for (final Jws2Instance sv : alternates.get(service.serviceType))
 -        {
 -          JMenuItem itm;
 -          hitm.add(itm = new JMenuItem(sv.getHost()));
 -          itm.setForeground(Color.blue);
 -          itm.addActionListener(new ActionListener()
 -          {
 -
 -            @Override
 -            public void actionPerformed(ActionEvent arg0)
 -            {
 -              new Thread(new Runnable()
 -              {
 -                @Override
 -                public void run()
 -                {
 -                  setPreferredServiceFor(alignFrame, sv.serviceType,
 -                          sv.action, sv);
 -                  changeSupport.firePropertyChange("services",
 -                          new Vector<Jws2Instance>(), services);
 -                }
 -              }).start();
 -
 -            }
 -          });
 -        }
 -      }
 -    }
 -  }
 -
 -  /**
 -   * add services using the Java 2.5/2.6/2.7 system which optionally creates
 -   * submenus to index by host and service program type
 -   */
 -  private void addEnumeratedServices(final JMenu jws2al,
 -          final AlignFrame alignFrame,
 -          List<Jws2Instance> enumerableServices)
 -  {
 -    boolean byhost = Cache.getDefault("WSMENU_BYHOST", false),
 -            bytype = Cache.getDefault("WSMENU_BYTYPE", false);
 -    /**
 -     * eventually, JWS2 services will appear under the same align/etc submenus.
 -     * for moment we keep them separate.
 -     */
 -    JMenu atpoint;
 -
 -    List<String> hostLabels = new ArrayList<>();
 -    Hashtable<String, String> lasthostFor = new Hashtable<>();
 -    Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<>();
 -    ArrayList<String> hostlist = new ArrayList<>();
 -    for (Jws2Instance service : enumerableServices)
 -    {
 -      ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
 -      if (hostservices == null)
 -      {
 -        hosts.put(service.getHost(),
 -                hostservices = new ArrayList<>());
 -        hostlist.add(service.getHost());
 -      }
 -      hostservices.add(service);
 -    }
 -    // now add hosts in order of the given array
 -    for (String host : hostlist)
 -    {
 -      Jws2Instance orderedsvcs[] = hosts.get(host)
 -              .toArray(new Jws2Instance[1]);
 -      String sortbytype[] = new String[orderedsvcs.length];
 -      for (int i = 0; i < sortbytype.length; i++)
 -      {
 -        sortbytype[i] = orderedsvcs[i].serviceType;
 -      }
 -      jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
 -      for (final Jws2Instance service : orderedsvcs)
 -      {
 -        atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
 -        String type = service.serviceType;
 -        if (byhost)
 -        {
 -          atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
 -          if (atpoint.getToolTipText() == null)
 -          {
 -            atpoint.setToolTipText(MessageManager
 -                    .formatMessage("label.services_at", new String[]
 -                    { host }));
 -          }
 -        }
 -        if (bytype)
 -        {
 -          atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
 -          if (atpoint.getToolTipText() == null)
 -          {
 -            atpoint.setToolTipText(service.getActionText());
 -          }
 -        }
 -        if (!byhost && !hostLabels.contains(
 -                host + service.serviceType + service.getActionText()))
 -        // !hostLabels.contains(host + (bytype ?
 -        // service.serviceType+service.getActionText() : "")))
 -        {
 -          // add a marker indicating where this service is hosted
 -          // relies on services from the same host being listed in a
 -          // contiguous
 -          // group
 -          JMenuItem hitm;
 -          if (hostLabels.contains(host))
 -          {
 -            atpoint.addSeparator();
 -          }
 -          else
 -          {
 -            hostLabels.add(host);
 -          }
 -          if (lasthostFor.get(service.action) == null
 -                  || !lasthostFor.get(service.action).equals(host))
 -          {
 -            atpoint.add(hitm = new JMenuItem(host));
 -            hitm.setForeground(Color.blue);
 -            hitm.addActionListener(new ActionListener()
 -            {
 -
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                Desktop.showUrl(service.getHost());
 -              }
 -            });
 -            hitm.setToolTipText(
 -                    JvSwingUtils.wrapTooltip(true, MessageManager
 -                            .getString("label.open_jabaws_web_page")));
 -            lasthostFor.put(service.action, host);
 -          }
 -          hostLabels.add(
 -                  host + service.serviceType + service.getActionText());
 -        }
 -
 -        service.attachWSMenuEntry(atpoint, alignFrame);
 -      }
 -    }
 -  }
 -
 -  /**
     * 
     * @param args
     * @j2sIgnore
     */
    public static void main(String[] args)
    {
+     Jws2Discoverer instance = getInstance();
      if (args.length > 0)
      {
-       testUrls = new ArrayList<>();
+       instance.testUrls = new ArrayList<>();
        for (String url : args)
        {
-         testUrls.add(url);
+         instance.testUrls.add(url);
        }
      }
-     var discoverer = getDiscoverer();
 -    Thread runner = instance.startDiscoverer(new PropertyChangeListener()
 -    {
 -
 -      @Override
 -      public void propertyChange(PropertyChangeEvent evt)
++    var discoverer = getInstance();
 +    discoverer.addServiceChangeListener((_discoverer, _services) -> {
 +      if (discoverer.services != null)
        {
 -        if (getInstance().services != null)
 +        System.out.println("Changesupport: There are now "
 +                + discoverer.services.size() + " services");
 +        int i = 1;
-         for (ServiceWithParameters instance : discoverer.services)
++        for (ServiceWithParameters s_instance : discoverer.services)
          {
 -          System.out.println("Changesupport: There are now "
 -                  + getInstance().services.size() + " services");
 -          int i = 1;
 -          for (Jws2Instance instance : getInstance().services)
 -          {
 -            System.out.println("Service " + i++ + " " + instance.getClass()
 -                    + "@" + instance.getHost() + ": "
 -                    + instance.getActionText());
 -          }
 -
 +          System.out.println(
-                   "Service " + i++ + " " + instance.getClass()
-                           + "@" + instance.getHostURL() + ": "
-                           + instance.getActionText());
++                  "Service " + i++ + " " + s_instance.getClass()
++                          + "@" + s_instance.getHostURL() + ": "
++                          + s_instance.getActionText());
          }
 +
        }
      });
 -    while (runner.isAlive())
 +    try
 +    {
 +      discoverer.startDiscoverer().get();
 +    } catch (InterruptedException | ExecutionException e)
      {
 -      try
 -      {
 -        Thread.sleep(50);
 -      } catch (InterruptedException e)
 -      {
 -      }
      }
      try
      {
      }
    }
  
-   /**
-    * Returns the singleton instance of this class.
-    * 
-    * @return
-    */
-   public static Jws2Discoverer getDiscoverer()
-   {
-     if (discoverer == null)
-     {
-       discoverer = new Jws2Discoverer();
-     }
-     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;
    }
  
 -  public Vector<Jws2Instance> getServices()
 +  @Override
 +  public Vector<ServiceWithParameters> getServices()
    {
 -    return (services == null) ? new Vector<>()
 -            : new Vector<>(services);
 +    return (services == null) ? new Vector<>() : new Vector<>(services);
    }
  
    /**
     * @param foo
     * @return
     */
 -  public static boolean testServiceUrl(URL foo)
 +  @Override
 +  public boolean testServiceUrl(URL foo)
    {
      try
      {
     * @param changeSupport2
     * @return new thread
     */
 -  public Thread startDiscoverer(PropertyChangeListener changeSupport2)
 +  @Override
 +  public CompletableFuture<WSDiscovererI> startDiscoverer()
    {
      /*    if (restart())
          {
      {
        setAborted(true);
      }
 -    addPropertyChangeListener(changeSupport2);
 -    Thread thr = new Thread(this);
 -    thr.start();
 -    return thr;
 +    CompletableFuture<WSDiscovererI> task = CompletableFuture
 +            .supplyAsync(() -> {
 +              run();
 +              return Jws2Discoverer.this;
 +            });
 +    return task;
    }
  
    /**
     * @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 -2;
 -  }
 -
 -  /**
 -   * pick the user's preferred service based on a set of URLs (jaba server
 -   * locations) and service URIs (specifying version and service interface
 -   * class)
 -   * 
 -   * @param serviceURL
 -   * @return null or best match for given uri/ls.
 -   */
 -  public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
 -  {
 -    HashSet<String> urls = new HashSet<>();
 -    urls.addAll(Arrays.asList(serviceURLs));
 -    Jws2Instance match = null;
 -    if (services != null)
 -    {
 -      for (Jws2Instance svc : services)
 -      {
 -        if (urls.contains(svc.getServiceTypeURI()))
 -        {
 -          if (match == null)
 -          {
 -            // for moment we always pick service from server ordered first in
 -            // user's preferences
 -            match = svc;
 -          }
 -          if (urls.contains(svc.getUri()))
 -          {
 -            // stop and return - we've matched type URI and URI for service
 -            // endpoint
 -            return svc;
 -          }
 -        }
 -      }
 -    }
 -    return match;
 -  }
 -
 -  Map<String, Map<String, String>> preferredServiceMap = new HashMap<>();
 -
 -  /**
 -   * get current preferred service of the given type, or global default
 -   * 
 -   * @param af
 -   *          null or a specific alignFrame
 -   * @param serviceType
 -   *          Jws2Instance.serviceType for service
 -   * @return null if no service of this type is available, the preferred service
 -   *         for the serviceType and af if specified and if defined.
 -   */
 -  public Jws2Instance getPreferredServiceFor(AlignFrame af,
 -          String serviceType)
 -  {
 -    String serviceurl = null;
 -    synchronized (preferredServiceMap)
 -    {
 -      String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
 -      Map<String, String> prefmap = preferredServiceMap.get(afid);
 -      if (afid.length() > 0 && prefmap == null)
 -      {
 -        // recover global setting, if any
 -        prefmap = preferredServiceMap.get("");
 -      }
 -      if (prefmap != null)
 -      {
 -        serviceurl = prefmap.get(serviceType);
 -      }
 -
 +      return STATUS_INVALID;
      }
 -    Jws2Instance response = null;
 -    for (Jws2Instance svc : services)
 -    {
 -      if (svc.serviceType.equals(serviceType))
 -      {
 -        if (serviceurl == null || serviceurl.equals(svc.getHost()))
 -        {
 -          response = svc;
 -          break;
 -        }
 -      }
 -    }
 -    return response;
 -  }
 -
 -  public void setPreferredServiceFor(AlignFrame af, String serviceType,
 -          String serviceAction, Jws2Instance selectedServer)
 -  {
 -    String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
 -    if (preferredServiceMap == null)
 -    {
 -      preferredServiceMap = new HashMap<>();
 -    }
 -    Map<String, String> prefmap = preferredServiceMap.get(afid);
 -    if (prefmap == null)
 -    {
 -      prefmap = new HashMap<>();
 -      preferredServiceMap.put(afid, prefmap);
 -    }
 -    prefmap.put(serviceType, selectedServer.getHost());
 -    prefmap.put(serviceAction, selectedServer.getHost());
 -  }
 -
 -  public void setPreferredServiceFor(String serviceType,
 -          String serviceAction, Jws2Instance selectedServer)
 -  {
 -    setPreferredServiceFor(null, serviceType, serviceAction,
 -            selectedServer);
 +    return STATUS_UNKNOWN;
    }
  
    /**
@@@ -27,12 -27,7 +27,12 @@@ import jalview.gui.Desktop
  import jalview.gui.JvOptionPane;
  import jalview.gui.JvSwingUtils;
  import jalview.util.MessageManager;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.WSMenuEntryProviderI;
 +import jalview.ws.api.JalviewServiceEndpointProviderI;
 +import jalview.ws.api.MultipleSequenceAlignmentI;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.gui.MsaWSThread;
 +import jalview.ws.params.ArgumentI;
  import jalview.ws.params.WsParamSetI;
  
  import java.awt.event.ActionEvent;
@@@ -45,28 -40,23 +45,28 @@@ import javax.swing.JMenu
  import javax.swing.JMenuItem;
  import javax.swing.ToolTipManager;
  
 -import compbio.data.msa.MsaWS;
 -import compbio.metadata.Argument;
 -
  /**
 - * DOCUMENT ME!
 + * MsaWSClient
 + * 
 + * Instantiates web service menu items for multiple alignment services, and
 + * holds logic for constructing a web service thread.
   * 
 - * @author $author$
 + * TODO remove dependency on Jws2Client methods for creating AACon service UI
 + * elements.
 + * 
 + * @author Jim Procter et al
   * @version $Revision$
   */
 -public class MsaWSClient extends Jws2Client
 +public class MsaWSClient extends Jws2Client implements WSMenuEntryProviderI
  {
    /**
 -   * server is a WSDL2Java generated stub for an archetypal MsaWSI service.
 +   * server is a proxy class implementing the core methods for submitting,
 +   * monitoring and retrieving results from a multiple sequence alignment
 +   * service
     */
 -  MsaWS server;
 +  MultipleSequenceAlignmentI server;
  
 -  public MsaWSClient(Jws2Instance sh, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
@@@ -76,8 -66,7 +76,8 @@@
      // TODO Auto-generated constructor stub
    }
  
 -  public MsaWSClient(Jws2Instance sh, WsParamSetI preset, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, WsParamSetI preset,
 +          String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
     *          DOCUMENT ME!
     */
  
 -  public MsaWSClient(Jws2Instance sh, WsParamSetI preset,
 -          List<Argument> arguments, boolean editParams, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, WsParamSetI preset,
 +          List<ArgumentI> arguments, boolean editParams, String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
        return;
      }
  
 -    if (!(sh.service instanceof MsaWS))
 +    if (!(sh instanceof JalviewServiceEndpointProviderI
 +            && ((JalviewServiceEndpointProviderI) sh)
 +                    .getEndpoint() instanceof MultipleSequenceAlignmentI))
      {
        // redundant at mo - but may change
-       JvOptionPane.showMessageDialog(Desktop.desktop,
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.service_called_is_not_msa_service",
                        new String[]
 -                      { sh.serviceType }),
 +                      { sh.getName() }),
                MessageManager.getString("label.internal_jalview_error"),
                JvOptionPane.WARNING_MESSAGE);
  
        return;
      }
 -    server = (MsaWS) sh.service;
 +    serviceHandle = sh;
 +    server = (MultipleSequenceAlignmentI) ((JalviewServiceEndpointProviderI) sh)
 +            .getEndpoint();
      if ((wsInfo = setWebService(sh, false)) == null)
      {
-       JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), MessageManager
                .formatMessage("label.msa_service_is_unknown", new String[]
 -              { sh.serviceType }),
 +              { sh.getName() }),
                MessageManager.getString("label.internal_jalview_error"),
                JvOptionPane.WARNING_MESSAGE);
  
  
    @Override
    public void attachWSMenuEntry(JMenu rmsawsmenu,
 -          final Jws2Instance service, final AlignFrame alignFrame)
 +          final ServiceWithParameters service, final AlignFrame alignFrame)
    {
 -    if (registerAAConWSInstance(rmsawsmenu, service, alignFrame))
 +    if (Jws2ClientFactory.registerAAConWSInstance(rmsawsmenu,
 +                    service, alignFrame))
      {
        // Alignment dependent analysis calculation WS gui
        return;
      }
 +    serviceHandle = service;
      setWebService(service, true); // headless
 +    attachWSMenuEntry(rmsawsmenu, alignFrame);
 +  }
 +
 +  @Override
 +  public void attachWSMenuEntry(JMenu wsmenu, AlignFrame alignFrame)
 +  {
      boolean finished = true, submitGaps = false;
 -    JMenu msawsmenu = rmsawsmenu;
 +    /**
 +     * temp variables holding msa service submenu or root service menu
 +     */
 +    JMenu msawsmenu = wsmenu;
 +    JMenu rmsawsmenu = wsmenu;
      String svcname = WebServiceName;
      if (svcname.endsWith("WS"))
      {
        rmsawsmenu.add(msawsmenu);
        calcName = "";
      }
 -    boolean hasparams = service.hasParameters();
 +    boolean hasparams = serviceHandle.hasParameters();
 +    ServiceWithParameters service = (ServiceWithParameters) serviceHandle;
      do
      {
        String action = "Align ";
index 53b790d,0000000..4c807e1
mode 100644,000000..100644
--- /dev/null
@@@ -1,843 -1,0 +1,843 @@@
 +/*
 + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
 + * Copyright (C) $$Year-Rel$$ The Jalview Authors
 + * 
 + * This file is part of Jalview.
 + * 
 + * Jalview is free software: you can redistribute it and/or
 + * modify it under the terms of the GNU General Public License 
 + * as published by the Free Software Foundation, either version 3
 + * of the License, or (at your option) any later version.
 + *  
 + * Jalview is distributed in the hope that it will be useful, but 
 + * WITHOUT ANY WARRANTY; without even the implied warranty 
 + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
 + * PURPOSE.  See the GNU General Public License for more details.
 + * 
 + * You should have received a copy of the GNU General Public License
 + * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
 + * The Jalview Authors are detailed in the 'AUTHORS' file.
 + */
 +package jalview.ws.jws2;
 +
 +import jalview.analysis.AlignSeq;
 +import jalview.analysis.AlignmentAnnotationUtils;
 +import jalview.analysis.SeqsetUtils;
 +import jalview.api.AlignViewportI;
 +import jalview.api.AlignmentViewPanel;
 +import jalview.api.FeatureColourI;
 +import jalview.api.PollableAlignCalcWorkerI;
 +import jalview.bin.Cache;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.AnnotatedCollectionI;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.ContiguousI;
 +import jalview.datamodel.Mapping;
 +import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.FeatureMatcherSetI;
 +import jalview.gui.AlignFrame;
 +import jalview.gui.Desktop;
 +import jalview.gui.IProgressIndicator;
 +import jalview.gui.IProgressIndicatorHandler;
 +import jalview.gui.JvOptionPane;
 +import jalview.gui.WebserviceInfo;
 +import jalview.schemes.FeatureSettingsAdapter;
 +import jalview.schemes.ResidueProperties;
 +import jalview.util.MapList;
 +import jalview.util.MessageManager;
 +import jalview.workers.AlignCalcWorker;
 +import jalview.ws.JobStateSummary;
 +import jalview.ws.api.CancellableI;
 +import jalview.ws.api.JalviewServiceEndpointProviderI;
 +import jalview.ws.api.JobId;
 +import jalview.ws.api.SequenceAnnotationServiceI;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.api.WSAnnotationCalcManagerI;
 +import jalview.ws.gui.AnnotationWsJob;
 +import jalview.ws.jws2.dm.AAConSettings;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.WsParamSetI;
 +
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +public class SeqAnnotationServiceCalcWorker extends AlignCalcWorker
 +        implements WSAnnotationCalcManagerI, PollableAlignCalcWorkerI
 +{
 +
 +  protected ServiceWithParameters service;
 +
 +  protected WsParamSetI preset;
 +
 +  protected List<ArgumentI> arguments;
 +
 +  protected IProgressIndicator guiProgress;
 +
 +  protected boolean submitGaps = true;
 +
 +  /**
 +   * by default, we filter out non-standard residues before submission
 +   */
 +  protected boolean filterNonStandardResidues = true;
 +
 +  /**
 +   * Recover any existing parameters for this service
 +   */
 +  protected void initViewportParams()
 +  {
 +    if (getCalcId() != null)
 +    {
 +      ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
 +              getCalcId(),
 +              new AAConSettings(true, service, this.preset, arguments),
 +              true);
 +    }
 +  }
 +
 +  /**
 +   * 
 +   * @return null or a string used to recover all annotation generated by this
 +   *         worker
 +   */
 +  public String getCalcId()
 +  {
 +    return service.getAlignAnalysisUI() == null ? null
 +            : service.getAlignAnalysisUI().getCalcId();
 +  }
 +
 +  public WsParamSetI getPreset()
 +  {
 +    return preset;
 +  }
 +
 +  public List<ArgumentI> getArguments()
 +  {
 +    return arguments;
 +  }
 +
 +  /**
 +   * reconfigure and restart the AAConClient. This method will spawn a new
 +   * thread that will wait until any current jobs are finished, modify the
 +   * parameters and restart the conservation calculation with the new values.
 +   * 
 +   * @param newpreset
 +   * @param newarguments
 +   */
 +  public void updateParameters(final WsParamSetI newpreset,
 +          final List<ArgumentI> newarguments)
 +  {
 +    preset = newpreset;
 +    arguments = newarguments;
 +    calcMan.startWorker(this);
 +    initViewportParams();
 +  }
 +  protected boolean alignedSeqs = true;
 +
 +  protected boolean nucleotidesAllowed = false;
 +
 +  protected boolean proteinAllowed = false;
 +
 +  /**
 +   * record sequences for mapping result back to afterwards
 +   */
 +  protected boolean bySequence = false;
 +
 +  protected Map<String, SequenceI> seqNames;
 +
 +  // TODO: convert to bitset
 +  protected boolean[] gapMap;
 +
 +  int realw;
 +
 +  protected int start;
 +
 +  int end;
 +
 +  private AlignFrame alignFrame;
 +
 +  public boolean[] getGapMap()
 +  {
 +    return gapMap;
 +  }
 +
 +  public SeqAnnotationServiceCalcWorker(ServiceWithParameters service,
 +          AlignFrame alignFrame,
 +          WsParamSetI preset, List<ArgumentI> paramset)
 +  {
 +    super(alignFrame.getCurrentView(), alignFrame.alignPanel);
 +    // TODO: both these fields needed ?
 +    this.alignFrame = alignFrame;
 +    this.guiProgress = alignFrame;
 +    this.preset = preset;
 +    this.arguments = paramset;
 +    this.service = service;
 +    try
 +    {
 +      annotService = (jalview.ws.api.SequenceAnnotationServiceI) ((JalviewServiceEndpointProviderI) service)
 +              .getEndpoint();
 +    } catch (ClassCastException cce)
 +    {
 +      annotService = null;
-       JvOptionPane.showMessageDialog(Desktop.desktop,
++      JvOptionPane.showMessageDialog(Desktop.getInstance(),
 +              MessageManager.formatMessage(
 +                      "label.service_called_is_not_an_annotation_service",
 +                      new String[]
 +                      { service.getName() }),
 +              MessageManager.getString("label.internal_jalview_error"),
 +              JvOptionPane.WARNING_MESSAGE);
 +
 +    }
 +    cancellable = CancellableI.class.isInstance(annotService);
 +    // configure submission flags
 +    proteinAllowed = service.isProteinService();
 +    nucleotidesAllowed = service.isNucleotideService();
 +    alignedSeqs = service.isNeedsAlignedSequences();
 +    bySequence = !service.isAlignmentAnalysis();
 +    filterNonStandardResidues = service.isFilterSymbols();
 +    min_valid_seqs = service.getMinimumInputSequences();
 +    submitGaps = service.isAlignmentAnalysis();
 +
 +    if (service.isInteractiveUpdate())
 +    {
 +      initViewportParams();
 +    }
 +  }
 +
 +  /**
 +   * 
 +   * @return true if the submission thread should attempt to submit data
 +   */
 +  public boolean hasService()
 +  {
 +    return annotService != null;
 +  }
 +
 +  protected SequenceAnnotationServiceI annotService;
 +  protected final boolean cancellable;
 +
 +  volatile JobId rslt = null;
 +
 +  AnnotationWsJob running = null;
 +
 +  private int min_valid_seqs;
 +
 +
 +  private long progressId = -1;
 +  JobStateSummary job = null;
 +  WebserviceInfo info = null;
 +  List<SequenceI> seqs = null;
 +  
 +  @Override public void startUp() throws Throwable
 +  {
 +    if (alignViewport.isClosed())
 +    {
 +      abortAndDestroy();
 +      return;
 +    }
 +    if (!hasService())
 +    {
 +      return;
 +    }
 +
 +    StringBuffer msg = new StringBuffer();
 +    job = new JobStateSummary();
 +    info = new WebserviceInfo("foo", "bar", false);
 +
 +    seqs = getInputSequences(
 +            alignViewport.getAlignment(),
 +            bySequence ? alignViewport.getSelectionGroup() : null);
 +
 +    if (seqs == null || !checkValidInputSeqs(seqs))
 +    {
 +      jalview.bin.Cache.log.debug(
 +              "Sequences for analysis service were null or not valid");
 +      return;
 +    }
 +
 +    if (guiProgress != null)
 +    {
 +      guiProgress.setProgressBar(service.getActionText(),
 +              progressId = System.currentTimeMillis());
 +    }
 +    jalview.bin.Cache.log.debug("submitted " + seqs.size()
 +            + " sequences to " + service.getActionText());
 +
 +    rslt = annotService.submitToService(seqs, getPreset(),
 +            getArguments());
 +    if (rslt == null)
 +    {
 +      return;
 +    }
 +    // TODO: handle job submission error reporting here.
 +    Cache.log.debug("Service " + service.getUri() + "\nSubmitted job ID: "
 +            + rslt);
 +    ;
 +    // ///
 +    // otherwise, construct WsJob and any UI handlers
 +    running = new AnnotationWsJob();
 +    running.setJobHandle(rslt);
 +    running.setSeqNames(seqNames);
 +    running.setStartPos(start);
 +    running.setSeqs(seqs);
 +    job.updateJobPanelState(info, "", running);
 +    if (guiProgress != null)
 +    {
 +      guiProgress.registerHandler(progressId,
 +              new IProgressIndicatorHandler()
 +              {
 +
 +                @Override
 +                public boolean cancelActivity(long id)
 +                {
 +                  calcMan.cancelWorker(SeqAnnotationServiceCalcWorker.this);
 +                  return true;
 +                }
 +
 +                @Override
 +                public boolean canCancel()
 +                {
 +                  return cancellable;
 +                }
 +              });
 +    }
 +  }
 +  
 +  @Override public boolean poll() throws Throwable
 +  {
 +    boolean finished = false;
 +    
 +    Cache.log.debug("Updating status for annotation service.");
 +    annotService.updateStatus(running);
 +    job.updateJobPanelState(info, "", running);
 +    if (running.isSubjobComplete())
 +    {
 +      Cache.log.debug(
 +              "Finished polling analysis service job: status reported is "
 +                      + running.getState());
 +      finished = true;
 +    }
 +    else
 +    {
 +      Cache.log.debug("Status now " + running.getState());
 +    }
 +
 +    // pull any stats - some services need to flush log output before
 +    // results are available
 +    Cache.log.debug("Updating progress log for annotation service.");
 +
 +    try
 +    {
 +      annotService.updateJobProgress(running);
 +    } catch (Throwable thr)
 +    {
 +      Cache.log.debug("Ignoring exception during progress update.",
 +              thr);
 +    }
 +    Cache.log.trace("Result of poll: " + running.getStatus());
 +    
 +    
 +    if (finished)
 +    {
 +      Cache.log.debug("Job poll loop exited. Job is " + running.getState());
 +      if (running.isFinished())
 +      {
 +        // expect there to be results to collect
 +        // configure job with the associated view's feature renderer, if one
 +        // exists.
 +        // TODO: here one would also grab the 'master feature renderer' in order
 +        // to enable/disable
 +        // features automatically according to user preferences
 +        running.setFeatureRenderer(
 +                ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
 +        Cache.log.debug("retrieving job results.");
 +        final Map<String, FeatureColourI> featureColours = new HashMap<>();
 +        final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
 +        List<AlignmentAnnotation> returnedAnnot = annotService
 +                .getAnnotationResult(running.getJobHandle(), seqs,
 +                        featureColours, featureFilters);
 +
 +        Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
 +                : ("" + returnedAnnot.size())));
 +        Cache.log.debug("There were " + featureColours.size()
 +                + " feature colours and " + featureFilters.size()
 +                + " filters defined.");
 +
 +        // TODO
 +        // copy over each annotation row reurned and also defined on each
 +        // sequence, excluding regions not annotated due to gapMap/column
 +        // visibility
 +
 +        // update calcId if it is not already set on returned annotation
 +        if (returnedAnnot != null)
 +        {
 +          for (AlignmentAnnotation aa : returnedAnnot)
 +          {
 +            // assume that any CalcIds already set
 +            if (getCalcId() != null && aa.getCalcId() == null
 +                    || "".equals(aa.getCalcId()))
 +            {
 +              aa.setCalcId(getCalcId());
 +            }
 +            // autocalculated annotation are created by interactive alignment
 +            // analysis services
 +            aa.autoCalculated = service.isAlignmentAnalysis()
 +                    && service.isInteractiveUpdate();
 +          }
 +        }
 +
 +        running.setAnnotation(returnedAnnot);
 +
 +        if (running.hasResults())
 +        {
 +          jalview.bin.Cache.log.debug("Updating result annotation from Job "
 +                  + rslt + " at " + service.getUri());
 +          updateResultAnnotation(true);
 +          if (running.isTransferSequenceFeatures())
 +          {
 +            // TODO
 +            // look at each sequence and lift over any features, excluding
 +            // regions
 +            // not annotated due to gapMap/column visibility
 +
 +            jalview.bin.Cache.log.debug(
 +                    "Updating feature display settings and transferring features from Job "
 +                            + rslt + " at " + service.getUri());
 +            // TODO: consider merge rather than apply here
 +            alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
 +            {
 +              @Override
 +              public FeatureColourI getFeatureColour(String type)
 +              {
 +                return featureColours.get(type);
 +              }
 +
 +              @Override
 +              public FeatureMatcherSetI getFeatureFilters(String type)
 +              {
 +                return featureFilters.get(type);
 +              }
 +
 +              @Override
 +              public boolean isFeatureDisplayed(String type)
 +              {
 +                return featureColours.containsKey(type);
 +              }
 +
 +            });
 +            // TODO: JAL-1150 - create sequence feature settings API for
 +            // defining
 +            // styles and enabling/disabling feature overlay on alignment panel
 +
 +            if (alignFrame.alignPanel == ap)
 +            {
 +              alignViewport.setShowSequenceFeatures(true);
 +              alignFrame.setMenusForViewport();
 +            }
 +          }
 +          ap.adjustAnnotationHeight();
 +        }
 +      }
 +      Cache.log.debug("Annotation Service Worker thread finished.");
 +
 +    }
 +    
 +    return finished;
 +  }
 +  
 +  @Override public void cancel()
 +  {
 +    cancelCurrentJob();
 +  }
 +  
 +  @Override public void done()
 +  {
 +    if (ap != null)
 +    {
 +      if (guiProgress != null && progressId != -1)
 +      {
 +        guiProgress.removeProgressBar(progressId);
 +      }
 +      // TODO: may not need to paintAlignment again !
 +      ap.paintAlignment(false, false);
 +    }
 +  }
 +
 +  /**
 +   * validate input for dynamic/non-dynamic update context TODO: move to
 +   * analysis interface ?
 +   * @param seqs
 +   * 
 +   * @return true if input is valid
 +   */
 +  boolean checkValidInputSeqs(List<SequenceI> seqs)
 +  {
 +    int nvalid = 0;
 +    for (SequenceI sq : seqs)
 +    {
 +      if (sq.getStart() <= sq.getEnd()
 +              && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
 +      {
 +        if (submitGaps
 +                || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
 +        {
 +          nvalid++;
 +        }
 +      }
 +    }
 +    return nvalid >= min_valid_seqs;
 +  }
 +
 +  public void cancelCurrentJob()
 +  {
 +    try
 +    {
 +      String id = running.getJobId();
 +      if (cancellable && ((CancellableI) annotService).cancel(running))
 +      {
 +        System.err.println("Cancelled job " + id);
 +      }
 +      else
 +      {
 +        System.err.println("Job " + id + " couldn't be cancelled.");
 +      }
 +    } catch (Exception q)
 +    {
 +      q.printStackTrace();
 +    }
 +  }
 +
 +  /**
 +   * Interactive updating. Analysis calculations that work on the currently
 +   * displayed alignment data should cancel existing jobs when the input data
 +   * has changed.
 +   * 
 +   * @return true if a running job should be cancelled because new input data is
 +   *         available for analysis
 +   */
 +  boolean isInteractiveUpdate()
 +  {
 +    return service.isInteractiveUpdate();
 +  }
 +
 +  /**
 +   * decide what sequences will be analysed TODO: refactor to generate
 +   * List<SequenceI> for submission to service interface
 +   * 
 +   * @param alignment
 +   * @param inputSeqs
 +   * @return
 +   */
 +  public List<SequenceI> getInputSequences(AlignmentI alignment,
 +          AnnotatedCollectionI inputSeqs)
 +  {
 +    if (alignment == null || alignment.getWidth() <= 0
 +            || alignment.getSequences() == null || alignment.isNucleotide()
 +                    ? !nucleotidesAllowed
 +                    : !proteinAllowed)
 +    {
 +      return null;
 +    }
 +    if (inputSeqs == null || inputSeqs.getWidth() <= 0
 +            || inputSeqs.getSequences() == null
 +            || inputSeqs.getSequences().size() < 1)
 +    {
 +      inputSeqs = alignment;
 +    }
 +
 +    List<SequenceI> seqs = new ArrayList<>();
 +
 +    int minlen = 10;
 +    int ln = -1;
 +    if (bySequence)
 +    {
 +      seqNames = new HashMap<>();
 +    }
 +    gapMap = new boolean[0];
 +    start = inputSeqs.getStartRes();
 +    end = inputSeqs.getEndRes();
 +    // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
 +    // correctly
 +    // TODO: push attributes into WsJob instance (so they can be safely
 +    // persisted/restored
 +    for (SequenceI sq : (inputSeqs.getSequences()))
 +    {
 +      if (bySequence
 +              ? sq.findPosition(end + 1)
 +                      - sq.findPosition(start + 1) > minlen - 1
 +              : sq.getEnd() - sq.getStart() > minlen - 1)
 +      {
 +        String newname = SeqsetUtils.unique_name(seqs.size() + 1);
 +        // make new input sequence with or without gaps
 +        if (seqNames != null)
 +        {
 +          seqNames.put(newname, sq);
 +        }
 +        SequenceI seq;
 +        if (submitGaps)
 +        {
 +          seqs.add(seq = new jalview.datamodel.Sequence(newname,
 +                  sq.getSequenceAsString()));
 +          if (gapMap == null || gapMap.length < seq.getLength())
 +          {
 +            boolean[] tg = gapMap;
 +            gapMap = new boolean[seq.getLength()];
 +            System.arraycopy(tg, 0, gapMap, 0, tg.length);
 +            for (int p = tg.length; p < gapMap.length; p++)
 +            {
 +              gapMap[p] = false; // init as a gap
 +            }
 +          }
 +          for (int apos : sq.gapMap())
 +          {
 +            char sqc = sq.getCharAt(apos);
 +            if (!filterNonStandardResidues
 +                    || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
 +                            : ResidueProperties.nucleotideIndex[sqc] < 5))
 +            {
 +              gapMap[apos] = true; // aligned and real amino acid residue
 +            }
 +            ;
 +          }
 +        }
 +        else
 +        {
 +          // TODO: add ability to exclude hidden regions
 +          seqs.add(seq = new jalview.datamodel.Sequence(newname,
 +                  AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
 +                          sq.getSequenceAsString(start, end + 1))));
 +          // for annotation need to also record map to sequence start/end
 +          // position in range
 +          // then transfer back to original sequence on return.
 +        }
 +        if (seq.getLength() > ln)
 +        {
 +          ln = seq.getLength();
 +        }
 +      }
 +    }
 +    if (alignedSeqs && submitGaps)
 +    {
 +      realw = 0;
 +      for (int i = 0; i < gapMap.length; i++)
 +      {
 +        if (gapMap[i])
 +        {
 +          realw++;
 +        }
 +      }
 +      // try real hard to return something submittable
 +      // TODO: some of AAcon measures need a minimum of two or three amino
 +      // acids at each position, and AAcon doesn't gracefully degrade.
 +      for (int p = 0; p < seqs.size(); p++)
 +      {
 +        SequenceI sq = seqs.get(p);
 +        // strip gapped columns
 +        char[] padded = new char[realw],
 +                orig = sq.getSequence();
 +        for (int i = 0, pp = 0; i < realw; pp++)
 +        {
 +          if (gapMap[pp])
 +          {
 +            if (orig.length > pp)
 +            {
 +              padded[i++] = orig[pp];
 +            }
 +            else
 +            {
 +              padded[i++] = '-';
 +            }
 +          }
 +        }
 +        seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
 +                new String(padded)));
 +      }
 +    }
 +    return seqs;
 +  }
 +
 +  @Override
 +  public void updateAnnotation()
 +  {
 +    updateResultAnnotation(false);
 +  }
 +
 +  public void updateResultAnnotation(boolean immediate)
 +  {
 +    if ((immediate || !calcMan.isWorking(this)) && running != null
 +            && running.hasResults())
 +    {
 +      List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
 +              newAnnots = new ArrayList<>();
 +      //
 +      // update graphGroup for all annotation
 +      //
 +      /**
 +       * find a graphGroup greater than any existing ones this could be a method
 +       * provided by alignment Alignment.getNewGraphGroup() - returns next
 +       * unused graph group
 +       */
 +      int graphGroup = 1;
 +      if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
 +      {
 +        for (AlignmentAnnotation ala : alignViewport.getAlignment()
 +                .getAlignmentAnnotation())
 +        {
 +          if (ala.graphGroup > graphGroup)
 +          {
 +            graphGroup = ala.graphGroup;
 +          }
 +        }
 +      }
 +      /**
 +       * update graphGroup in the annotation rows returned from service
 +       */
 +      // TODO: look at sequence annotation rows and update graph groups in the
 +      // case of reference annotation.
 +      for (AlignmentAnnotation ala : ourAnnot)
 +      {
 +        if (ala.graphGroup > 0)
 +        {
 +          ala.graphGroup += graphGroup;
 +        }
 +        SequenceI aseq = null;
 +
 +        /**
 +         * transfer sequence refs and adjust gapmap
 +         */
 +        if (ala.sequenceRef != null)
 +        {
 +          SequenceI seq = running.getSeqNames()
 +                  .get(ala.sequenceRef.getName());
 +          aseq = seq;
 +          while (seq.getDatasetSequence() != null)
 +          {
 +            seq = seq.getDatasetSequence();
 +          }
 +        }
 +        Annotation[] resAnnot = ala.annotations,
 +                gappedAnnot = new Annotation[Math.max(
 +                        alignViewport.getAlignment().getWidth(),
 +                        gapMap.length)];
 +        for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
 +        {
 +          if (gapMap != null && gapMap.length > ap && !gapMap[ap])
 +          {
 +            gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
 +          }
 +          else if (p < resAnnot.length)
 +          {
 +            gappedAnnot[ap] = resAnnot[p++];
 +          }
 +        }
 +        ala.sequenceRef = aseq;
 +        ala.annotations = gappedAnnot;
 +        AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
 +                .updateFromOrCopyAnnotation(ala);
 +        if (aseq != null)
 +        {
 +
 +          aseq.addAlignmentAnnotation(newAnnot);
 +          newAnnot.adjustForAlignment();
 +
 +          AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
 +                  newAnnot, newAnnot.label, newAnnot.getCalcId());
 +        }
 +        newAnnots.add(newAnnot);
 +
 +      }
 +      for (SequenceI sq : running.getSeqs())
 +      {
 +        if (!sq.getFeatures().hasFeatures()
 +                && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
 +        {
 +          continue;
 +        }
 +        running.setTransferSequenceFeatures(true);
 +        SequenceI seq = running.getSeqNames().get(sq.getName());
 +        SequenceI dseq;
 +        ContiguousI seqRange = seq.findPositions(start, end);
 +
 +        while ((dseq = seq).getDatasetSequence() != null)
 +        {
 +          seq = seq.getDatasetSequence();
 +        }
 +        List<ContiguousI> sourceRange = new ArrayList();
 +        if (gapMap != null && gapMap.length >= end)
 +        {
 +          int lastcol = start, col = start;
 +          do
 +          {
 +            if (col == end || !gapMap[col])
 +            {
 +              if (lastcol <= (col - 1))
 +              {
 +                seqRange = seq.findPositions(lastcol, col);
 +                sourceRange.add(seqRange);
 +              }
 +              lastcol = col + 1;
 +            }
 +          } while (++col <= end);
 +        }
 +        else
 +        {
 +          sourceRange.add(seq.findPositions(start, end));
 +        }
 +        int i = 0;
 +        int source_startend[] = new int[sourceRange.size() * 2];
 +
 +        for (ContiguousI range : sourceRange)
 +        {
 +          source_startend[i++] = range.getBegin();
 +          source_startend[i++] = range.getEnd();
 +        }
 +        Mapping mp = new Mapping(
 +                new MapList(source_startend, new int[]
 +                { seq.getStart(), seq.getEnd() }, 1, 1));
 +        dseq.transferAnnotation(sq, mp);
 +
 +      }
 +      updateOurAnnots(newAnnots);
 +    }
 +  }
 +
 +  protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
 +  {
 +    List<AlignmentAnnotation> our = ourAnnots;
 +    ourAnnots = ourAnnot;
 +    AlignmentI alignment = alignViewport.getAlignment();
 +    if (our != null)
 +    {
 +      if (our.size() > 0)
 +      {
 +        for (AlignmentAnnotation an : our)
 +        {
 +          if (!ourAnnots.contains(an))
 +          {
 +            // remove the old annotation
 +            alignment.deleteAnnotation(an);
 +          }
 +        }
 +      }
 +      our.clear();
 +    }
 +
 +    // validate rows and update Alignmment state
 +    for (AlignmentAnnotation an : ourAnnots)
 +    {
 +      alignViewport.getAlignment().validateAnnotation(an);
 +    }
 +    // TODO: may need a menu refresh after this
 +    // af.setMenusForViewport();
 +    ap.adjustAnnotationHeight();
 +
 +  }
 +
 +  public SequenceAnnotationServiceI getService()
 +  {
 +    return annotService;
 +  }
 +
 +}
@@@ -25,8 -25,8 +25,8 @@@ import jalview.gui.AlignFrame
  import jalview.gui.Desktop;
  import jalview.gui.JvSwingUtils;
  import jalview.util.MessageManager;
 -import jalview.ws.jws2.dm.AAConSettings;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.params.AutoCalcSetting;
  import jalview.ws.params.WsParamSetI;
  import jalview.ws.uimodel.AlignAnalysisUIText;
  
@@@ -51,7 -51,7 +51,7 @@@ public class SequenceAnnotationWSClien
      // TODO Auto-generated constructor stub
    }
  
 -  public SequenceAnnotationWSClient(final Jws2Instance sh,
 +  public SequenceAnnotationWSClient(final ServiceWithParameters sh,
            AlignFrame alignFrame, WsParamSetI preset, boolean editParams)
    {
      super(alignFrame, preset, null);
@@@ -61,7 -61,7 +61,7 @@@
    // dan think. Do I need to change this method to run RNAalifold through the
    // GUI
  
 -  public void initSequenceAnnotationWSClient(final Jws2Instance sh,
 +  public void initSequenceAnnotationWSClient(final ServiceWithParameters sh,
            AlignFrame alignFrame, WsParamSetI preset, boolean editParams)
    {
      // dan changed! dan test. comment out if conditional
        // columns
  
        List<AlignCalcWorkerI> clnts = alignFrame.getViewport()
 -              .getCalcManager().getRegisteredWorkersOfClass(clientClass);
 -      AbstractJabaCalcWorker worker;
 -      if (clnts == null || clnts.size() == 0)
 +              .getCalcManager()
 +              .getWorkersOfClass(SeqAnnotationServiceCalcWorker.class);
 +
 +      SeqAnnotationServiceCalcWorker worker = null;
 +      if (clnts != null)
 +      {
 +        for (AlignCalcWorkerI _worker : clnts)
 +        {
 +          worker = (SeqAnnotationServiceCalcWorker) _worker;
 +          if (worker.hasService()
 +                  && worker.getService().getClass().equals(clientClass))
 +          {
 +            break;
 +          }
 +          worker = null;
 +        }
 +      }
 +      if (worker == null)
        {
          if (!processParams(sh, editParams))
          {
          }
          try
          {
 -          worker = (AbstractJabaCalcWorker) (clientClass
 -                  .getConstructor(new Class[]
 -                  { Jws2Instance.class, AlignFrame.class, WsParamSetI.class,
 -                      List.class })
 -                  .newInstance(new Object[]
 -                  { sh, alignFrame, this.preset, paramset }));
 +          worker = new SeqAnnotationServiceCalcWorker(sh, alignFrame, this.preset,
 +                  paramset);
          } catch (Exception x)
          {
            x.printStackTrace();
                    MessageManager.getString("error.implementation_error"),
                    x);
          }
 -        alignFrame.getViewport().getCalcManager().registerWorker(worker);
 -        alignFrame.getViewport().getCalcManager().startWorker(worker);
 -
 +        alignFrame.getViewport().getCalcManager().registerWorker(worker); // also
 +                                                                          // starts
 +                                                                          // the
 +                                                                          // worker
        }
        else
        {
 -        worker = (AbstractJabaCalcWorker) clnts.get(0);
          if (editParams)
          {
            paramset = worker.getArguments();
          worker.updateParameters(this.preset, paramset);
        }
      }
 -    if (sh.action.toLowerCase().contains("disorder"))
 +    if (!sh.isInteractiveUpdate())
      {
        // build IUPred style client. take sequences, returns annotation per
        // sequence.
        }
  
        alignFrame.getViewport().getCalcManager().startWorker(
 -              new AADisorderClient(sh, alignFrame, preset, paramset));
 +              new SeqAnnotationServiceCalcWorker(sh, alignFrame, preset, paramset));
      }
    }
  
 -  public SequenceAnnotationWSClient(AAConSettings fave,
 +  public SequenceAnnotationWSClient(AutoCalcSetting fave,
            AlignFrame alignFrame, boolean b)
    {
 -    super(alignFrame, fave.getPreset(), fave.getJobArgset());
 +    super(alignFrame, fave.getPreset(), fave.getArgumentSet());
      initSequenceAnnotationWSClient(fave.getService(), alignFrame,
              fave.getPreset(), b);
    }
     * @see jalview.ws.jws2.Jws2Client#attachWSMenuEntry(javax.swing.JMenu,
     * jalview.ws.jws2.jabaws2.Jws2Instance, jalview.gui.AlignFrame)
     */
 -  public void attachWSMenuEntry(JMenu wsmenu, final Jws2Instance service,
 +  @Override
 +  public void attachWSMenuEntry(JMenu wsmenu,
 +          final ServiceWithParameters service,
            final AlignFrame alignFrame)
    {
 -    if (registerAAConWSInstance(wsmenu, service, alignFrame))
 +    if (Jws2ClientFactory.registerAAConWSInstance(wsmenu,
 +            service, alignFrame))
      {
        // Alignment dependent analysis calculation WS gui
        return;
      }
      boolean hasparams = service.hasParameters();
 -    // Assume name ends in WS
 -    String calcName = service.serviceType.substring(0,
 -            service.serviceType.length() - 2);
 +    String calcName = service.getName();
 +    if (calcName.endsWith("WS"))
 +    {
 +      // Remove "WS" suffix
 +      calcName = calcName.substring(0, calcName.length() - 2);
 +    }
  
      JMenuItem annotservice = new JMenuItem(MessageManager.formatMessage(
              "label.calcname_with_default_settings", new String[]
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        new SequenceAnnotationWSClient(service, alignFrame, null, false);
 +        new SequenceAnnotationWSClient(service, alignFrame,
 +                null, false);
        }
      });
      wsmenu.add(annotservice);
  
        annotservice.addActionListener(new ActionListener()
        {
 +        @Override
          public void actionPerformed(ActionEvent e)
          {
 -          new SequenceAnnotationWSClient(service, alignFrame, null, true);
 +          new SequenceAnnotationWSClient(service, alignFrame,
 +                  null, true);
          }
        });
        wsmenu.add(annotservice);
                    + "</strong><br/>" + preset.getDescription()));
            methodR.addActionListener(new ActionListener()
            {
 +            @Override
              public void actionPerformed(ActionEvent e)
              {
 -              new SequenceAnnotationWSClient(service, alignFrame, preset,
 +              new SequenceAnnotationWSClient(service,
 +                      alignFrame, preset,
                        false);
              }
  
      {
        annotservice = new JMenuItem(
                MessageManager.getString("label.view_documentation"));
 -      if (service.docUrl != null)
 +      if (service != null && service.hasDocumentationUrl())
        {
          annotservice.addActionListener(new ActionListener()
          {
            @Override
            public void actionPerformed(ActionEvent arg0)
            {
-             Desktop.instance.showUrl(service.getDocumentationUrl());
 -            Desktop.getInstance().showUrl(service.docUrl);
++            Desktop.getInstance().showUrl(service.getDocumentationUrl());
            }
          });
          annotservice.setToolTipText(
                  JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage(
                          "label.view_service_doc_url", new String[]
 -                        { service.docUrl, service.docUrl })));
 +                        { service.getDocumentationUrl(),
 +                            service.getDocumentationUrl() })));
          wsmenu.add(annotservice);
        }
      }
   */
  package jalview.ws.jws2.jabaws2;
  
 -import jalview.gui.AlignFrame;
  import jalview.gui.Desktop;
  import jalview.util.MessageManager;
 +import jalview.ws.api.JalviewServiceEndpointProviderI;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws2.JabaParamStore;
 -import jalview.ws.jws2.MsaWSClient;
 -import jalview.ws.jws2.SequenceAnnotationWSClient;
  import jalview.ws.params.ParamDatastoreI;
 +import jalview.ws.params.ParamManager;
  
  import java.io.Closeable;
 -
 -import javax.swing.JMenu;
 +import java.net.URL;
  
  import compbio.data.msa.JABAService;
  import compbio.data.msa.MsaWS;
@@@ -37,12 -38,20 +37,12 @@@ import compbio.data.msa.SequenceAnnotat
  import compbio.metadata.PresetManager;
  import compbio.metadata.RunnerConfig;
  
 -public class Jws2Instance implements AutoCloseable
 +public class Jws2Instance extends ServiceWithParameters
 +        implements JalviewServiceEndpointProviderI, AutoCloseable
  {
  
    public JABAService service;
  
 -  public String description;
 -
 -  public String docUrl;
 -
    /**
     * 
     * @param hosturl
    public Jws2Instance(String hosturl, String serviceType, String action,
            String description, JABAService service)
    {
 -    super();
 -    this.hosturl = hosturl;
 -    this.serviceType = serviceType;
 +    super(action, action, serviceType, description, hosturl);
      this.service = service;
 -    this.action = action;
 -    this.description = description;
 +    if (service instanceof MsaWS<?>)
 +    {
 +      style = ServiceClient.MSAWSCLIENT;
 +    }
 +    else if (service instanceof SequenceAnnotation<?>)
 +    {
 +      style = ServiceClient.SEQUENCEANNOTATIONWSCLIENT;
 +    }
 +
      int p = description.indexOf("MORE INFORMATION:");
      if (p > -1)
      {
 -      docUrl = description.substring(description.indexOf("http", p)).trim();
 +      String docUrl = description.substring(description.indexOf("http", p))
 +              .trim();
        if (docUrl.indexOf('\n') > -1)
        {
          docUrl = docUrl.substring(0, docUrl.indexOf("\n")).trim();
        }
 +      if (docUrl.length() > 0)
 +      {
 +        try
 +        {
 +          URL url = new URL(docUrl);
 +          if (url != null)
 +          {
 +            setDocumentationUrl(docUrl);
 +          }
 +        } catch (Exception x)
 +        {
 +
 +        }
 +      }
  
      }
    }
        } catch (Exception ex)
        {
          System.err.println("Exception when retrieving presets for service "
 -                + serviceType + " at " + hosturl);
 +                + getServiceType() + " at " + getHostURL());
        }
      }
      return presets;
    }
  
 -  public String getHost()
 -  {
 -    return hosturl;
 -    /*
 -     * try { URL serviceurl = new URL(hosturl); if (serviceurl.getPort()!=80) {
 -     * return serviceurl.getHost()+":"+serviceurl.getPort(); } return
 -     * serviceurl.getHost(); } catch (Exception e) {
 -     * System.err.println("Failed to parse service URL '" + hosturl +
 -     * "' as a valid URL!"); } return null;
 -     */
 -  }
 -
 -  /**
 -   * @return short description of what the service will do
 -   */
 -  public String getActionText()
 -  {
 -    return action + " with " + serviceType;
 -  }
 -
    /**
     * non-thread safe - blocks whilst accessing service to get complete set of
     * available options and parameters
      throw new Error(MessageManager.formatMessage(
              "error.implementation_error_runner_config_not_available",
              new String[]
 -            { serviceType, service.getClass().toString() }));
 +            { getServiceType(), service.getClass().toString() }));
    }
  
    @Override
      // super.finalize();
    }
  
 +  @Override
    public ParamDatastoreI getParamStore()
    {
      if (paramStore == null)
        try
        {
          paramStore = new JabaParamStore(this,
-                 (Desktop.instance != null ? Desktop.getUserParameterStore()
+                 (Desktop.getInstance() != null ? Desktop.getUserParameterStore()
                          : null));
        } catch (Exception ex)
        {
      return paramStore;
    }
  
 -  public String getUri()
 -  {
 -    // this is only valid for Jaba 1.0 - this formula might have to change!
 -    return hosturl
 -            + (hosturl.lastIndexOf("/") == (hosturl.length() - 1) ? ""
 -                    : "/")
 -            + serviceType;
 -  }
 -
    private boolean hasParams = false, lookedForParams = false;
  
 +  @Override
    public boolean hasParameters()
    {
      if (!lookedForParams)
      return hasParams;
    }
  
 -  public void attachWSMenuEntry(JMenu atpoint, AlignFrame alignFrame)
 +  /**
 +   * initialise a parameter store for this service
 +   * 
 +   * @param userParameterStore
 +   *          - the user ParamManager (e.g. Desktop.getUserParameterStore() )
 +   */
 +  @Override
 +  public void initParamStore(ParamManager userParameterStore)
    {
 -    if (service instanceof MsaWS<?>)
 -    {
 -      new MsaWSClient().attachWSMenuEntry(atpoint, this, alignFrame);
 -    }
 -    else if (service instanceof SequenceAnnotation<?>)
 +    if (paramStore == null)
      {
 -      new SequenceAnnotationWSClient().attachWSMenuEntry(atpoint, this,
 -              alignFrame);
 +      paramStore = new JabaParamStore(this, userParameterStore);
      }
    }
  
 -  public String getServiceTypeURI()
 +  /**
 +   * an object that implements one or more interfaces in jalview.ws.api
 +   * 
 +   * @return
 +   */
 +  @Override
 +  public Object getEndpoint()
    {
 -    return "java:" + serviceType;
 -  }
 -
 -  jalview.ws.uimodel.AlignAnalysisUIText aaui;
 +    if (service instanceof MsaWS<?>)
 +    {
 +      if (aaui != null)
 +      {
 +        throw new Error(
 +                "JABAWS MsaWS based instant calculation not implemented.");
  
 -  public jalview.ws.uimodel.AlignAnalysisUIText getAlignAnalysisUI()
 -  {
 -    return aaui;
 +      }
 +      else
 +      {
 +        return new JabawsMsaInstance(this);
 +      }
 +    }
 +    else
 +    {
 +      if (service instanceof compbio.data.msa.SequenceAnnotation)
 +      {
 +        if (aaui != null)
 +        {
 +          try
 +          {
 +            // probably a factory would be nicer but..
 +            return aaui.getClient().getConstructor(getClass())
 +                    .newInstance(this);
 +          } catch (Throwable t)
 +          {
 +            throw new Error("Implementation Error in web service framework",
 +                    t);
 +          }
 +        }
 +        return new AADisorderClient(this);
 +      }
 +      return null;
 +    }
    }
  }
   */
  package jalview.ws.jws2.jabaws2;
  
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 -import jalview.ws.jws2.AAConClient;
 -import jalview.ws.jws2.RNAalifoldClient;
++
  import jalview.ws.uimodel.AlignAnalysisUIText;
  
  import java.util.HashMap;
@@@ -27,11 -31,23 +30,23 @@@ import java.util.HashSet
  
  import compbio.data.msa.JABAService;
  
- public class Jws2InstanceFactory
+ public class Jws2InstanceFactory implements ApplicationSingletonI
  {
-   private static HashMap<String, AlignAnalysisUIText> aaConGUI;
  
-   private static HashSet<String> ignoreGUI;
+   private Jws2InstanceFactory()
+   {
+     // private singleton
+   }
+   private static Jws2InstanceFactory getInstance()
+   {
+     return (Jws2InstanceFactory) ApplicationSingletonProvider
+             .getInstance(Jws2InstanceFactory.class);
+   }
+   private HashMap<String, AlignAnalysisUIText> aaConGUI;
+   private HashSet<String> ignoreGUI;
  
    private static String category_rewrite(String cat_name)
    {
              : cat_name;
    }
  
-   private static void init()
+   private void init()
    {
      if (aaConGUI == null)
      {
        aaConGUI = new HashMap<>();
        aaConGUI.put(compbio.ws.client.Services.AAConWS.toString(),
 -              AAConClient.getAlignAnalysisUITest());
 +              AAConClient.getAlignAnalysisUIText());
        aaConGUI.put(compbio.ws.client.Services.RNAalifoldWS.toString(),
 -              RNAalifoldClient.getAlignAnalysisUITest());
 +              RNAalifoldClient.getAlignAnalysisUIText());
        // ignore list for JABAWS services not supported in jalview ...
        ignoreGUI = new HashSet<>();
      }
@@@ -63,8 -79,8 +78,8 @@@
     */
    public static boolean ignoreService(String serviceType)
    {
-     init();
-     return (ignoreGUI.contains(serviceType.toString()));
+     getInstance().init();
+     return (getInstance().ignoreGUI.contains(serviceType.toString()));
    }
  
    /**
            String serviceType, String name, String description,
            JABAService service)
    {
-     init();
+     getInstance().init();
      Jws2Instance svc = new Jws2Instance(jwsservers, serviceType,
              category_rewrite(name), description, service);
-     svc.setAlignAnalysisUI(aaConGUI.get(serviceType.toString()));
 -    svc.aaui = getInstance().aaConGUI.get(serviceType.toString());
++    svc.setAlignAnalysisUI(getInstance().aaConGUI.get(serviceType.toString()));
      return svc;
    }
  
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.rest;
  
+ import jalview.bin.ApplicationSingletonProvider;
+ import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentView;
  import jalview.gui.AlignFrame;
@@@ -28,14 -30,15 +30,14 @@@ import jalview.gui.AlignmentPanel
  import jalview.gui.Desktop;
  import jalview.gui.JvOptionPane;
  import jalview.gui.WebserviceInfo;
 -import jalview.io.packed.DataProvider.JvDataType;
  import jalview.util.MessageManager;
  import jalview.ws.WSClient;
  import jalview.ws.WSClientI;
  import jalview.ws.WSMenuEntryProviderI;
 +import jalview.ws.rest.clientdefs.ShmrRestClient;
  
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 -import java.util.Hashtable;
  import java.util.Vector;
  
  import javax.swing.JMenu;
@@@ -48,8 -51,27 +50,27 @@@ import javax.swing.event.MenuListener
   * 
   */
  public class RestClient extends WSClient
-         implements WSClientI, WSMenuEntryProviderI
+ implements WSClientI, WSMenuEntryProviderI, ApplicationSingletonI
  {
+   @SuppressWarnings("unused")
+   private RestClient()
+   {
+     // accessed by ApplicationSingletonProvider
+   }
+   
+ private static RestClient getInstance()
+ {
+ return (RestClient) ApplicationSingletonProvider.getInstance(RestClient.class);
+ }
+ public static final String RSBS_SERVICES = "RSBS_SERVICES";
+   protected Vector<String> services = null;
    RestServiceDescription service;
  
    public RestClient(RestServiceDescription rsd)
    {
      WebServiceJobTitle = MessageManager
              .formatMessage("label.webservice_job_title", new String[]
 -            { service.details.Action, service.details.Name });
 -    WebServiceName = service.details.Name;
 +            { service.details.getAction(), service.details.getName() });
 +    WebServiceName = service.details.getName();
      WebServiceReference = "No reference - go to url for more info";
 -    if (service.details.description != null)
 +    if (service.details.getDescription() != null)
      {
 -      WebServiceReference = service.details.description;
 +      WebServiceReference = service.details.getDescription();
      }
      if (!headless)
      {
        wsInfo = new WebserviceInfo(WebServiceJobTitle,
-               WebServiceName + "\n" + WebServiceReference, true);
+               WebServiceName + "\n" + WebServiceReference, Desktop.FRAME_MAKE_VISIBLE);
        wsInfo.setRenderAsHtml(true);
      }
  
    public void attachWSMenuEntry(final JMenu wsmenu,
            final AlignFrame alignFrame)
    {
 -    JMenuItem submit = new JMenuItem(service.details.Name);
 +    JMenuItem submit = new JMenuItem(service.details.getName());
      submit.setToolTipText(MessageManager
              .formatMessage("label.rest_client_submit", new String[]
 -            { service.details.Action, service.details.Name }));
 +            { service.details.getAction(), service.details.getName() }));
      submit.addActionListener(new ActionListener()
      {
  
      else
      {
        // TODO: try to tell the user why the job couldn't be started.
-       JvOptionPane.showMessageDialog(Desktop.desktop,
+       JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                (jobsthread.hasWarnings() ? jobsthread.getWarnings()
                        : MessageManager.getString(
                                "label.job_couldnt_be_started_check_input")),
      }
    }
  
 -  public static RestClient makeShmmrRestClient()
 -  {
 -    String action = "Analysis",
 -            description = "Sequence Harmony and Multi-Relief (Brandt et al. 2010)",
 -            name = MessageManager.getString("label.multiharmony");
 -    Hashtable<String, InputType> iparams = new Hashtable<String, InputType>();
 -    jalview.ws.rest.params.JobConstant toolp;
 -    // toolp = new jalview.ws.rest.JobConstant("tool","jalview");
 -    // iparams.put(toolp.token, toolp);
 -    // toolp = new jalview.ws.rest.params.JobConstant("mbjob[method]","shmr");
 -    // iparams.put(toolp.token, toolp);
 -    // toolp = new
 -    // jalview.ws.rest.params.JobConstant("mbjob[description]","step 1");
 -    // iparams.put(toolp.token, toolp);
 -    // toolp = new jalview.ws.rest.params.JobConstant("start_search","1");
 -    // iparams.put(toolp.token, toolp);
 -    // toolp = new jalview.ws.rest.params.JobConstant("blast","0");
 -    // iparams.put(toolp.token, toolp);
 -
 -    jalview.ws.rest.params.Alignment aliinput = new jalview.ws.rest.params.Alignment();
 -    // SHMR server has a 65K limit for content pasted into the 'ali' parameter,
 -    // so we always upload our files.
 -    aliinput.token = "ali_file";
 -    aliinput.writeAsFile = true;
 -    iparams.put(aliinput.token, aliinput);
 -    jalview.ws.rest.params.SeqGroupIndexVector sgroups = new jalview.ws.rest.params.SeqGroupIndexVector();
 -    sgroups.setMinsize(2);
 -    sgroups.min = 2;// need at least two group defined to make a partition
 -    iparams.put("groups", sgroups);
 -    sgroups.token = "groups";
 -    sgroups.sep = " ";
 -    RestServiceDescription shmrService = new RestServiceDescription(action,
 -            description, name,
 -            "http://zeus.few.vu.nl/programs/shmrwww/index.php?tool=jalview", // ?tool=jalview&mbjob[method]=shmr&mbjob[description]=step1",
 -            "?tool=jalview", iparams, true, false, '-');
 -    // a priori knowledge of the data returned from the service
 -    shmrService.addResultDatatype(JvDataType.ANNOTATION);
 -    return new RestClient(shmrService);
 -  }
 -
    public AlignmentPanel recoverAlignPanelForView()
    {
      AlignmentPanel[] aps = Desktop
      return true;
    }
  
-   protected static Vector<String> services = null;
-   public static final String RSBS_SERVICES = "RSBS_SERVICES";
    public static RestClient[] getRestClients()
    {
+     return getInstance().getClients();
+   }
+     
+   private RestClient[] getClients()
+   {
      if (services == null)
      {
 -      services = new Vector<String>();
 +      services = new Vector<>();
        try
        {
          for (RestServiceDescription descr : RestServiceDescription
 -                .parseDescriptions(
 -                        jalview.bin.Cache.getDefault(RSBS_SERVICES,
 -                                makeShmmrRestClient().service.toString())))
 +                .parseDescriptions(jalview.bin.Cache.getDefault(
 +                        RSBS_SERVICES,
 +                        ShmrRestClient.makeShmmrRestClient().service.toString())))
          {
            services.add(descr.toString());
          }
  
    public String getAction()
    {
 -    return service.details.Action;
 +    return service.details.getAction();
    }
  
    public RestServiceDescription getRestDescription()
  
    public static Vector<String> getRsbsDescriptions()
    {
 -    Vector<String> rsbsDescrs = new Vector<String>();
 +    Vector<String> rsbsDescrs = new Vector<>();
      for (RestClient rsbs : getRestClients())
      {
        rsbsDescrs.add(rsbs.getRestDescription().toString());
    {
      if (rsbsUrls != null)
      {
+       
        // TODO: consider validating services ?
-       services = new Vector<>(rsbsUrls);
+       getInstance().services = new Vector<String>(rsbsUrls);
        StringBuffer sprop = new StringBuffer();
-       for (String s : services)
+       for (String s : getInstance().services)
        {
          sprop.append(s);
        }
@@@ -28,13 -28,6 +28,6 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertSame;
  import static org.testng.AssertJUnit.assertTrue;
  
- import jalview.analysis.AlignmentGenerator;
- import jalview.commands.EditCommand;
- import jalview.commands.EditCommand.Action;
- import jalview.datamodel.PDBEntry.Type;
- import jalview.gui.JvOptionPane;
- import jalview.util.MapList;
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Arrays;
@@@ -48,6 -41,12 +41,13 @@@ import org.testng.annotations.BeforeCla
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
+ import jalview.analysis.AlignmentGenerator;
+ import jalview.commands.EditCommand;
+ import jalview.commands.EditCommand.Action;
+ import jalview.datamodel.PDBEntry.Type;
+ import jalview.gui.JvOptionPane;
+ import jalview.util.MapList;
++
  import junit.extensions.PA;
  
  public class SequenceTest
       * invalid inputs
       */
      assertNull(sq.findPositions(6, 5));
 -    assertNull(sq.findPositions(0, 5));
 -    assertNull(sq.findPositions(-1, 5));
  
      /*
       * all gapped ranges
      assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
      assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
      assertEquals(new Range(8, 13), sq.findPositions(1, 99));
 +
 +    /**
 +     * now try on a sequence with no gaps
 +     */
 +    sq.createDatasetSequence();
 +    assertEquals(new Range(8, 13),
 +            sq.getDatasetSequence().findPositions(1, 99));
 +    assertEquals(new Range(8, 13),
 +            sq.getDatasetSequence().findPositions(0, 99));
 +
    }
  
    /**
@@@ -35,7 -35,6 +35,7 @@@ import org.testng.annotations.BeforeCla
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
 +import jalview.api.AlignViewportI;
  import jalview.api.FeatureColourI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
@@@ -57,7 -56,6 +57,7 @@@ import jalview.schemes.JalviewColourSch
  import jalview.schemes.StrandColourScheme;
  import jalview.schemes.TurnColourScheme;
  import jalview.util.MessageManager;
 +import jalview.viewmodel.AlignmentViewport;
  
  public class AlignFrameTest
  {
@@@ -77,7 -75,7 +77,7 @@@
    @AfterMethod(alwaysRun = true)
    public void tearDown()
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
    }
  
    /**
@@@ -88,7 -86,7 +88,7 @@@
    public void setUp()
    {
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
-     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+     Cache.setPropertyNoSave("SHOW_IDENTITY",
              Boolean.TRUE.toString());
      af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
              DataSourceType.FILE);
    @Test(groups = "Functional")
    public void testChangeColour_background_groupsAndThresholds()
    {
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      AlignmentI al = av.getAlignment();
  
      /*
    @Test(groups = "Functional")
    public void testColourThresholdActions()
    {
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      AlignmentI al = av.getAlignment();
  
      /*
    @Test(groups = "Functional")
    public void testNewView_colourThresholds()
    {
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      AlignmentI al = av.getAlignment();
  
      /*
       */
      af.newView_actionPerformed(null);
      assertEquals(af.alignPanel.getViewName(), "View 1");
 -    AlignViewport av2 = af.getViewport();
 +    AlignmentViewport av2 = af.getViewport();
      assertNotSame(av, av2);
      assertSame(av2, af.alignPanel.av);
      rs = av2.getResidueShading();
@@@ -27,7 -27,6 +27,7 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertSame;
  import static org.testng.AssertJUnit.assertTrue;
  
 +import jalview.api.AlignViewportI;
  import java.util.ArrayList;
  import java.util.List;
  
@@@ -55,8 -54,8 +55,9 @@@ import jalview.schemes.ColourSchemeI
  import jalview.schemes.PIDColourScheme;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.MapList;
 +import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.ViewportRanges;
+ import jalview.workers.AlignCalcManager;
  
  public class AlignViewportTest
  {
  
    AlignmentI al;
  
 -  AlignViewport testee;
 +  AlignmentViewport testee;
  
    @BeforeClass(alwaysRun = true)
    public static void setUpBeforeClass() throws Exception
    {
-     Jalview.main(new String[] { "-nonews", "-props",
+     Jalview.main(new String[] {
+         //"-jabaws", "none", 
+         "-nonews", "-props",
          "test/jalview/testProps.jvprops" });
  
      /*
       * remove any sequence mappings left lying around by other tests
       */
      StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      ssm.resetAll();
    }
  
       * mappings
       */
      StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      List<AlignedCodonFrame> sequenceMappings = ssm.getSequenceMappings();
      assertEquals(2, sequenceMappings.size());
      assertTrue(sequenceMappings.contains(acf1));
    @Test(groups = { "Functional" })
    public void testDeregisterMapping_withNoReference()
    {
-     Desktop d = Desktop.instance;
+     Desktop d = Desktop.getInstance();
      assertNotNull(d);
      StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      ssm.resetAll();
  
      AlignFrame af1 = new FileLoader().LoadFileWaitTillLoaded(
    @Test(groups = { "Functional" })
    public void testDeregisterMapping_withReference()
    {
-     Desktop d = Desktop.instance;
+     Desktop d = Desktop.getInstance();
      assertNotNull(d);
      StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(Desktop.instance);
+             .getStructureSelectionManager(Desktop.getInstance());
      ssm.resetAll();
  
      AlignFrame af1 = new FileLoader().LoadFileWaitTillLoaded(
    @Test(groups = { "Functional" }, timeOut=2000)
    public void testUpdateConservation_qualityOnly()
    {
-     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+     Cache.setPropertyNoSave("SHOW_ANNOTATIONS",
              Boolean.TRUE.toString());
-     Cache.applicationProperties.setProperty("SHOW_QUALITY",
+     Cache.setPropertyNoSave("SHOW_QUALITY",
              Boolean.TRUE.toString());
-     Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
+     Cache.setPropertyNoSave("SHOW_CONSERVATION",
              Boolean.FALSE.toString());
-     Cache.applicationProperties.setProperty("SHOW_OCCUPANCY",
+     Cache.setPropertyNoSave("SHOW_OCCUPANCY",
              Boolean.FALSE.toString());
-     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+     Cache.setPropertyNoSave("SHOW_IDENTITY",
              Boolean.FALSE.toString());
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
    {
      synchronized (this)
      {
-       while (viewport.getCalcManager().isWorking())
+       System.out.print("waiting...");
+       int n = 3;
+       while (--n >= 0 || viewport.getCalcManager().isWorking())
        {
          try
          {
+           System.out.print(((AlignCalcManager) viewport.getCalcManager()).getQueueLength());
            wait(50);
          } catch (InterruptedException e)
          {
          }
        }
+            System.out.println("...done");
      }
    }
  
      /*
       * test for JAL-2283: don't inadvertently turn on colour by conservation
       */
-     Cache.applicationProperties.setProperty("DEFAULT_COLOUR_PROT", "None");
-     Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
+     Cache.setPropertyNoSave("DEFAULT_COLOUR_PROT", "None");
+     Cache.setPropertyNoSave("SHOW_CONSERVATION",
              Boolean.TRUE.toString());
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
    {
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      SequenceGroup sg1 = new SequenceGroup();
      SequenceGroup sg2 = new SequenceGroup();
      SequenceGroup sg3 = new SequenceGroup();
      jalview.bin.Cache.setProperty("SHOW_OCCUPANCY", Boolean.FALSE.toString());
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
 -    AlignViewport av = af.getViewport();
 -    Assert.assertNull(av.getAlignmentGapAnnotation(), "Preference did not disable occupancy row.");
 +    AlignViewportI av = af.getViewport();
 +    Assert.assertNull(av.getAlignmentGapAnnotation(),
 +            "Preference did not disable occupancy row.");
      int c = 0;
      for (AlignmentAnnotation aa : av.getAlignment().findAnnotations(null,
              null, "Occupancy"))
      af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      av = af.getViewport();
 -    Assert.assertNotNull(av.getAlignmentGapAnnotation(), "Preference did not enable occupancy row.");
 +    Assert.assertNotNull(av.getAlignmentGapAnnotation(),
 +            "Preference did not enable occupancy row.");
      c = 0;
      for (AlignmentAnnotation aa : av.getAlignment().findAnnotations(null,
              null, av.getAlignmentGapAnnotation().label))
      AlignViewport testme = af.getViewport();
      waitForCalculations(testme);
      SequenceI cons = testme.getConsensusSeq();
-     assertEquals("A-C", cons.getSequenceAsString());
+     String s = cons.getSequenceAsString();
+     System.out.println("s is " + s);
+     
+     assertEquals("A-C", s);
    }
  
    @Test(groups = { "Functional" })
@@@ -2,17 -2,17 +2,17 @@@ package jalview.gui
  
  import static org.testng.Assert.assertEquals;
  
 +import jalview.api.AlignViewportI;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.SequenceGroup;
  import jalview.io.DataSourceType;
  import jalview.io.FileLoader;
 -
  import javax.swing.JTextArea;
  
 -import junit.extensions.PA;
 -
  import org.testng.annotations.Test;
  
 +import junit.extensions.PA;
 +
  public class PairwiseAlignmentPanelTest
  {
    @Test(groups = "Functional")
@@@ -20,7 -20,7 +20,7 @@@
    {
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
      AlignmentI al = viewport.getAlignment();
  
      /*
@@@ -37,7 -37,7 +37,7 @@@
  
      PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
  
-     String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+     String text = ((JTextArea) PA.getValue(testee, "textarea")).getText().replace("\r\n", "\n");
      String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
              + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
              + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
      String seqs = ">Q93XJ9_SOLTU/23-29\nL-KAISNV\n>FER1_PEA/26-32\nV-TTTKAF\n";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqs,
              DataSourceType.PASTE);
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
  
      PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
  
-     String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+     String text = ((JTextArea) PA.getValue(testee, "textarea")).getText().replace("\r\n", "\n");
      String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
              + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
              + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
@@@ -201,9 -201,10 +201,10 @@@ public class PopupMenuTes
      testee.configureReferenceAnnotationsMenu(menu, seqs);
      assertTrue(menu.isEnabled());
      String s = MessageManager.getString("label.add_annotations_for");
-     String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
-             + "<div class=\"ttip\">" + s
-             + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
+ //    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
+ //            + "<div class=\"ttip\">" + s
+ //            + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
+     String expected = "<html>" + s + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
      assertEquals(expected, menu.getToolTipText());
    }
  
      testee.configureReferenceAnnotationsMenu(menu, seqs);
      assertTrue(menu.isEnabled());
      String s = MessageManager.getString("label.add_annotations_for");
-     String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
-             + "<div class=\"ttip\">" + s
-             + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
-     assertEquals(expected, menu.getToolTipText());
+ //    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
+ //            + "<div class=\"ttip\">" + s
+ //            + "<br/>Jmol/secondary structure<br/>PDB/Temp</html>";
+     String expected = "<html>" + s
+             + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
+     s = menu.getToolTipText();
+     assertEquals(expected, s);
    }
  
    /**
      // PDB.secondary structure on Sequence0
      AlignmentAnnotation annotation = new AlignmentAnnotation(
              "secondary structure", "", 0);
 +    annotation.annotations = new Annotation[] { new Annotation(2f) };
      annotation.setCalcId("PDB");
      seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
      if (addToSequence)
      // PDB.Temp on Sequence1
      annotation = new AlignmentAnnotation("Temp", "", 0);
      annotation.setCalcId("PDB");
 +    annotation.annotations = new Annotation[] { new Annotation(2f) };
      seqs.get(1).getDatasetSequence().addAlignmentAnnotation(annotation);
      if (addToSequence)
      {
      // JMOL.secondary structure on Sequence0
      annotation = new AlignmentAnnotation("secondary structure", "", 0);
      annotation.setCalcId("Jmol");
 +    annotation.annotations = new Annotation[] { new Annotation(2f) };
      seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation);
      if (addToSequence)
      {
index e3b7067,0000000..04cc3be
mode 100644,000000..100644
--- /dev/null
@@@ -1,134 -1,0 +1,134 @@@
 +package jalview.hmmer;
 +
 +import static org.testng.Assert.assertEquals;
 +import static org.testng.Assert.assertFalse;
 +import static org.testng.Assert.assertNotNull;
 +import static org.testng.Assert.assertTrue;
 +import static org.testng.Assert.fail;
 +
 +import jalview.bin.Jalview;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.HiddenMarkovModel;
 +import jalview.datamodel.SequenceI;
 +import jalview.gui.AlignFrame;
 +import jalview.gui.Desktop;
 +import jalview.io.HMMFile;
 +import jalview.util.MessageManager;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.simple.Option;
 +
 +import java.io.IOException;
 +import java.net.MalformedURLException;
 +import java.util.ArrayList;
 +import java.util.List;
 +
 +import org.testng.annotations.AfterClass;
 +import org.testng.annotations.BeforeClass;
 +import org.testng.annotations.Test;
 +
 +public class HMMERTest {
 +
 +  AlignFrame frame;
 +
 +  @BeforeClass(alwaysRun = true)
 +  public void setUpBeforeClass() throws Exception
 +  {
 +    /*
 +     * NB: check HMMER_PATH in testProps.jvprops is valid for
 +     * the machine on which this runs
 +     */
 +    Jalview.main(
 +            new String[]
 +    { "-noquestionnaire", "-nonews", "-props",
 +                "test/jalview/hmmer/testProps.jvprops", "-open",
 +                "examples/uniref50.fa" });
 +    frame = Desktop.getAlignFrames()[0];
 +  }
 +
 +  @AfterClass(alwaysRun = true)
 +  public static void tearDownAfterClass() throws Exception
 +  {
-     Desktop.instance.closeAll_actionPerformed(null);
++    Desktop.getInstance().closeAll_actionPerformed(null);
 +  }
 +
 +  /**
 +   * Test with a dependency on locally installed hmmbuild binaries
 +   * 
 +   * @throws MalformedURLException
 +   * @throws IOException
 +   */
 +  @Test(groups = "External")
 +  public void testHMMBuildThenHMMAlign()
 +          throws MalformedURLException, IOException
 +  {
 +    /*
 +     * run hmmbuild
 +     */
 +    testHMMBuild();
 +    List<SequenceI> hmms = frame.getViewport().getAlignment()
 +            .getHmmSequences();
 +    assertFalse(hmms.isEmpty());
 +
 +    /*
 +     * now run hmmalign - by default with respect to the added HMM profile
 +     */
 +    testHMMAlign();
 +  }
 +
 +  public void testHMMBuild()
 +  {
 +    /*
 +     * set up argument to run hmmbuild for the alignment
 +     */
 +    ArrayList<ArgumentI> params = new ArrayList<>();
 +    String argName = MessageManager.getString("label.hmmbuild_for");
 +    String argValue = MessageManager.getString("label.alignment");
 +    params.add(
 +            new Option(argName, null, false, null, argValue, null, null));
 +
 +    HMMBuild builder = new HMMBuild(frame, params);
 +    builder.run();
 +
 +    SequenceI seq = frame.getViewport().getAlignment().getSequenceAt(0);
 +    HiddenMarkovModel hmm = seq.getHMM();
 +    assertNotNull(hmm);
 +
 +    assertEquals(hmm.getLength(), 148);
 +    assertEquals(hmm.getAlphabetType(), "amino");
 +    assertEquals(hmm.getName(), "Alignment_HMM");
 +    assertEquals(hmm.getProperty(HMMFile.EFF_NUMBER_OF_SEQUENCES),
 +            "0.648193");
 +  }
 +
 +  public void testHMMAlign()
 +  {
 +    HmmerCommand thread = new HMMAlign(frame,
 +            new ArrayList<ArgumentI>());
 +    thread.run();
 +
 +    AlignFrame[] alignFrames = Desktop.getAlignFrames();
 +    if (alignFrames == null)
 +    {
 +      fail("No align frame loaded");
 +    }
 +
 +    /*
 +     * now have the original align frame, and another for realigned sequences
 +     */
 +    assertEquals(alignFrames.length, 2);
 +    AlignmentI original = alignFrames[0].getViewport().getAlignment();
 +    assertNotNull(original);
 +    AlignmentI realigned = alignFrames[1].getViewport().getAlignment();
 +    assertNotNull(realigned);
 +    assertFalse(original.getHmmSequences().isEmpty());
 +    assertFalse(realigned.getHmmSequences().isEmpty());
 +
 +    SequenceI ferCapan = original.findName("FER_CAPAN");
 +    assertTrue(ferCapan.getSequenceAsString().startsWith("MA------SVSAT"));
 +
 +    SequenceI ferCapanRealigned = realigned.findName("FER_CAPAN");
 +    assertTrue(ferCapanRealigned.getSequenceAsString()
 +            .startsWith("-------m-A----SVSAT"));
 +  }
 +}
 +
@@@ -55,7 -55,7 +55,7 @@@ public class FileFormatsTes
    @Test(groups = "Functional")
    public void testGetReadableFormats()
    {
-     String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3]";
 -    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, BSML]";
++    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
      FileFormats formats = FileFormats.getInstance();
      assertEquals(formats.getReadableFormats().toString(), expected);
    }
    @Test(groups = "Functional")
    public void testGetWritableFormats()
    {
 -    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 +    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
      FileFormats formats = FileFormats.getInstance();
      assertEquals(formats.getWritableFormats(true).toString(), expected);
 -    expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Jalview]";
 +    expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Jalview, HMMER3]";
      assertEquals(formats.getWritableFormats(false).toString(), expected);
    }
  
    @Test(groups = "Functional")
    public void testDeregisterFileFormat()
    {
 -    String writable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 -    String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, BSML]";
 +    String writable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
-     String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3]";
++    String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
      FileFormats formats = FileFormats.getInstance();
+     System.out.println(formats.getReadableFormats().toString());
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
  
      formats.deregisterFileFormat(FileFormat.Fasta.getName());
 -    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 -    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, BSML]";
 +    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
-     readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3]";
++    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
  
@@@ -89,8 -90,8 +90,8 @@@
       * re-register the format: it gets added to the end of the list
       */
      formats.registerFileFormat(FileFormat.Fasta);
 -    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Fasta]";
 -    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, BSML, Fasta]";
 +    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3, Fasta]";
-     readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, Fasta]";
++    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML, Fasta]";
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
    }
@@@ -39,6 -39,7 +39,7 @@@ import javax.swing.JInternalFrame
  
  import org.testng.Assert;
  import org.testng.AssertJUnit;
+ import org.testng.annotations.AfterMethod;
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
@@@ -51,7 -52,6 +52,7 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.GeneLocus;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.Mapping;
  import jalview.datamodel.PDBEntry;
@@@ -65,6 -65,7 +66,6 @@@ import jalview.datamodel.features.Featu
  import jalview.datamodel.features.FeatureMatcherSet;
  import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.gui.AlignFrame;
 -import jalview.gui.AlignViewport;
  import jalview.gui.AlignmentPanel;
  import jalview.gui.Desktop;
  import jalview.gui.JvOptionPane;
@@@ -91,11 -92,14 +92,16 @@@ import jalview.util.matcher.Condition
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  
 +import junit.extensions.PA;
 +
  @Test(singleThreaded = true)
  public class Jalview2xmlTests extends Jalview2xmlBase
  {
+   @AfterMethod(alwaysRun = true)
+   public void tearDown()
+   {
+     Desktop.getInstance().closeAll_actionPerformed(null);
+   }
  
    @Override
    @BeforeClass(alwaysRun = true)
              DataSourceType.FILE);
      assertNotNull(af, "Didn't read input file " + inFile);
      af.loadJalviewDataFile(inAnnot, DataSourceType.FILE, null, null);
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
      assertSame(viewport.getGlobalColourScheme().getClass(),
              TCoffeeColourScheme.class, "Didn't set T-coffee colourscheme");
      assertNotNull(
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
-     assertTrue(Desktop.getAlignFrames().length == 1 + origCount,
+     assertEquals(Desktop.getAlignFrames().length,
+             1 + origCount,
              "Didn't gather the views in the example file.");
    }
  
    /**
    @Test(groups = { "Functional" }, enabled = true)
    public void testStoreAndRecoverExpandedviews() throws Exception
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
  
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverReferenceSeqSettings() throws Exception
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverGroupRepSeqs() throws Exception
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverPDBEntry() throws Exception
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      String exampleFile = "examples/3W5V.pdb";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(exampleFile,
              DataSourceType.FILE);
      {
        Assert.fail("Didn't save the state", e);
      }
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverColourThresholds() throws IOException
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
  
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      AlignmentI al = av.getAlignment();
  
      /*
              ".jvp");
      tfile.deleteOnExit();
      new Jalview2XML(false).saveState(tfile);
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
      Assert.assertNotNull(af, "Failed to reload project");
    }
  
    /**
 +   * Load an HMM profile to an alignment, and confirm it is correctly restored
 +   * when reloaded from project
 +   * 
 +   * @throws IOException
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testStoreAndRecoverHmmProfile() throws IOException
 +  {
-     Desktop.instance.closeAll_actionPerformed(null);
++    Desktop.getInstance().closeAll_actionPerformed(null);
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
 +            "examples/uniref50.fa", DataSourceType.FILE);
 +  
 +    AlignViewportI av = af.getViewport();
 +    AlignmentI al = av.getAlignment();
 +
 +    /*
 +     * mimic drag and drop of hmm file on to alignment
 +     */
 +    AlignFrame af2 = new FileLoader().LoadFileWaitTillLoaded(
 +            "examples/uniref50.hmm", DataSourceType.FILE);
 +    al.insertSequenceAt(0,
 +            af2.getViewport().getAlignment().getSequenceAt(0));
 +
 +    /*
 +     * check it loaded in
 +     */
 +    SequenceI hmmSeq = al.getSequenceAt(0);
 +    assertTrue(hmmSeq.hasHMMProfile());
 +    HiddenMarkovModel hmm = hmmSeq.getHMM();
 +    assertSame(hmm.getConsensusSequence(), hmmSeq);
 +
 +    /*
 +     * save project, close windows, reload project, verify
 +     */
 +    File tfile = File.createTempFile("testStoreAndRecoverHmmProfile",
 +            ".jvp");
 +    tfile.deleteOnExit();
 +    new Jalview2XML(false).saveState(tfile);
-     Desktop.instance.closeAll_actionPerformed(null);
++    Desktop.getInstance().closeAll_actionPerformed(null);
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
 +            DataSourceType.FILE);
 +    Assert.assertNotNull(af, "Failed to reload project");
 +
 +    hmmSeq = al.getSequenceAt(0);
 +    assertTrue(hmmSeq.hasHMMProfile());
 +    assertSame(hmm.getConsensusSequence(), hmmSeq);
 +    Mapping mapToHmmConsensus = (Mapping) PA.getValue(hmm,
 +            "mapToHmmConsensus");
 +    assertNotNull(mapToHmmConsensus);
 +    assertSame(mapToHmmConsensus.getTo(), hmmSeq.getDatasetSequence());
 +  }
 +
 +  /**
     * pre 2.11 - jalview 2.10 erroneously created new dataset entries for each
     * view (JAL-3171) this test ensures we can import and merge those views
     */
    @Test(groups = { "Functional" })
    public void testMergeDatasetsforManyViews() throws IOException
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
  
      // complex project - one dataset, several views on several alignments
      AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
    @Test(groups = "Functional")
    public void testPcaViewAssociation() throws IOException
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      final String PCAVIEWNAME = "With PCA";
      // create a new tempfile
      File tempfile = File.createTempFile("jvPCAviewAssoc", "jvp");
      }
  
      // load again.
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              tempfile.getCanonicalPath(), DataSourceType.FILE);
-     JInternalFrame[] frames = Desktop.instance.getAllFrames();
+     JInternalFrame[] frames = Desktop.getInstance().getAllFrames();
      // PCA and the tabbed alignment view should be the only two windows on the
      // desktop
      assertEquals(frames.length, 2,
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverGeneLocus() throws Exception
    {
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
      String seqData = ">P30419\nACDE\n>X1235\nGCCTGTGACGAA";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
              DataSourceType.PASTE);
      {
        Assert.fail("Didn't save the state", e);
      }
-     Desktop.instance.closeAll_actionPerformed(null);
+     Desktop.getInstance().closeAll_actionPerformed(null);
    
      new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
@@@ -6,24 -6,24 +6,24 @@@ import static org.testng.Assert.assertN
  import static org.testng.Assert.assertNull;
  import static org.testng.Assert.assertTrue;
  
- import jalview.api.AlignViewportI;
+ import java.awt.Color;
+ import java.util.List;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
  import jalview.api.FeatureColourI;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
 -import jalview.gui.AlignViewport;
++import jalview.api.AlignViewportI;
  import jalview.io.DataSourceType;
  import jalview.io.FileLoader;
  import jalview.schemes.FeatureColour;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
  
- import java.awt.Color;
- import java.util.List;
- import org.testng.annotations.BeforeMethod;
- import org.testng.annotations.BeforeTest;
- import org.testng.annotations.Test;
  /**
   * Unit tests for feature colour determination, including but not limited to
   * <ul>
@@@ -43,7 -43,7 +43,7 @@@
   */
  public class FeatureColourFinderTest
  {
 -  private AlignViewport av;
 +  private AlignViewportI av;
  
    private SequenceI seq;
  
@@@ -53,7 -53,7 +53,7 @@@
  
    private FeatureRendererModel fr;
  
-   @BeforeTest(alwaysRun = true)
+   @BeforeClass(alwaysRun = true)
    public void setUp()
    {
      // aligned column 8 is sequence position 6