Merge branch 'develop' into developtomchmmer
authorJim Procter <jprocter@issues.jalview.org>
Tue, 4 Jun 2019 17:22:21 +0000 (18:22 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Tue, 4 Jun 2019 17:22:21 +0000 (18:22 +0100)
JAL-3285 merged 2.11 develop into the features/mchmmer branch
- need to fix code for migration from Castor to JAXB
- need to check Stockholm file IO and tests
Good luck !

58 files changed:
1  2 
help/help/help.jhm
help/help/helpTOC.xml
help/help/html/features/preferences.html
help/help/html/menus/alwhmmer.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
schemas/jalview.xsd
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/SeqsetUtils.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/Jalview.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/AlignmentView.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceCollectionI.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.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/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/WsParamSetManager.java
src/jalview/io/FileFormat.java
src/jalview/io/FileLoader.java
src/jalview/io/StockholmFile.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/schemes/JalviewColourScheme.java
src/jalview/schemes/ResidueProperties.java
src/jalview/util/Platform.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/ConsensusThread.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/SequenceGroupTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
test/jalview/io/StockholmFileTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
utils/i18nAnt.xml

index 0000000,1666cc6..8ffef1e
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,168 +1,169 @@@
+ <?xml version="1.0" encoding="ISO-8859-1" ?>
+ <!--
+  * 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.
+ -->
+    <!DOCTYPE map PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN" "http://java.sun.com/products/javahelp/map_1_0.dtd">
+ <map version="1.0">
+    <mapID target="home" url="html/index.html" />
+    
+    <mapID target="new" url="html/whatsNew.html"/>
+    <mapID target="release" url="html/releases.html#Jalview.$$Version-Rel$$"/>
+    <mapID target="alannotation" url="html/features/annotation.html"/>
+    <mapID target="keys" url="html/keys.html"/>
+    <mapID target="newkeys" url="html/features/newkeystrokes.html"/>
+    <mapID target="cursor" url="html/features/cursorMode.html"/>
+    <mapID target="search" url="html/features/search.html"/>   
+    <mapID target="webservice" url="html/webServices/index.html"/>
+    <mapID target="jabaws" url="html/webServices/JABAWS.html"/>
+    <mapID target="wsparams" url="html/webServices/webServicesParams.html"/>
+    <mapID target="wsprefs" url="html/webServices/webServicesPrefs.html"/>
+    <mapID target="msaservice" url="html/webServices/msaclient.html"/>
+    <mapID target="jpred" url="html/webServices/jnet.html"/>
+    <mapID target="shmrws" url="html/webServices/shmr.html"/>
+    <mapID target="newsreader" url="html/webServices/newsreader.html"/>
+    <mapID target="disorder" url="html/webServices/proteinDisorder.html"/>
+    <mapID target="aacon" url="html/webServices/AACon.html"/>
+    <mapID target="rnaalifold" url="html/webServices/RNAalifold.html"/>
+    <mapID target="seqannots" url="html/features/annotation.html#seqannots"/>
+    <mapID target="seqfetch" url="html/features/seqfetch.html"/>
+    <mapID target="dbreffetcher" url="html/webServices/dbreffetcher.html"/>
+    <mapID target="seqmappings" url="html/features/seqmappings.html"/>
+    <mapID target="cdnafeatures" url="html/features/codingfeatures.html"/>
+    <mapID target="seqfeatures" url="html/features/seqfeatures.html"/>
+    <mapID target="seqfeatedit" url="html/features/editingFeatures.html"/>
+    <mapID target="seqfeatcreat" url="html/features/creatinFeatures.html"/>
+    <mapID target="seqfeatures.settings" url="html/features/featuresettings.html"/>
+    <mapID target="seqfeatures.settings.selcols" url="html/features/featuresettings.html#selectbyfeature"/>
+    <mapID target="viewingpdbs" url="html/features/viewingpdbs.html"/>
+    <mapID target="viewingpdbs.reps" url="html/features/viewingpdbs.html#viewreps"/>
+    <mapID target="pdbmcviewer" url="html/features/pdbviewer.html"/>
+    <mapID target="pdbjmol" url="html/features/jmol.html"/>
+    <mapID target="chimera" url="html/features/chimera.html"/>
+    <mapID target="chimera.annotxfer" url="html/features/chimera.html#annotxfer"/>
+    <mapID target="varna" url="html/features/varna.html"/>
+    <mapID target="xsspannotation" url="html/features/xsspannotation.html"/>
+    <mapID target="preferences" url="html/features/preferences.html"/>     
+    <mapID target="strucprefs" url="html/features/preferences.html#structure"/>     
+    <mapID target="commandline" url="html/features/commandline.html"/>
+    <mapID target="clarguments" url="html/features/clarguments.html"/>
+    <mapID target="io" url="html/io/index.html"/>
+    <mapID target="io.modellerpir" url="html/io/modellerpir.html"/>
+    <mapID target="io.seqreport" url="html/io/exportseqreport.html"/>
+    <mapID target="io.tcoffeescores" url="html/io/tcoffeescores.html"/>
+    <mapID target="export" url="html/io/export.html"/>
+    <mapID target="alformats" url="html/io/fileformats.html"/>
+    <mapID target="annotations.fileformat" url="html/features/annotationsFormat.html"/>
+    <mapID target="features.fileformat" url="html/features/featuresFormat.html"/>
+    <mapID target="features.featureschemes" url="html/features/featureschemes.html"/>
+    <mapID target="edit" url="html/editing/index.html"/>
+    <mapID target="jalarchive" url="html/features/jalarchive.html"/>
+    <mapID target="multipleviews" url="html/features/multipleViews.html"/>
+    <mapID target="splitframe" url="html/features/splitView.html"/>
+    <mapID target="splitframe.mirrorfonts" url="html/features/splitView.html#mirror"/>
+    <mapID target="trees" url="html/calculations/tree.html"/>
+    <mapID target="treeviewer" url="html/calculations/treeviewer.html"/>
+    <mapID target="sorting" url="html/calculations/sorting.html"/>
+    <mapID target="pca" url="html/calculations/pca.html"/>
+    <mapID target="pairwise" url="html/calculations/pairwise.html"/>
+    <mapID target="redundancy" url="html/calculations/redundancy.html"/>
+    <mapID target="referenceseq" url="html/calculations/referenceseq.html"/>
+    <mapID target="hiddenRegions" url="html/features/hiddenRegions.html"/>
+    <mapID target="recoverInputdata" url="html/calculations/recoverInputdata.html"/>
+    <mapID target="colours" url="html/colourSchemes/index.html"/>
+    <mapID target="backdeptextcol" url="html/colourSchemes/textcolour.html"/>
+    <mapID target="colours.clustal" url="html/colourSchemes/clustal.html"/>
+    <mapID target="colours.zappo" url="html/colourSchemes/zappo.html" />
+    <mapID target="colours.taylor" url="html/colourSchemes/taylor.html" />
+    <mapID target="colours.hydro" url="html/colourSchemes/hydrophobic.html" />
+    <mapID target="colours.helix" url="html/colourSchemes/helix.html" />
+    <mapID target="colours.strand" url="html/colourSchemes/strand.html" />
+    <mapID target="colours.turn" url="html/colourSchemes/turn.html" />
+    <mapID target="colours.buried" url="html/colourSchemes/buried.html" />
+    <mapID target="colours.nucleotide" url="html/colourSchemes/nucleotide.html" />
+    <mapID target="colours.blosum" url="html/colourSchemes/blosum.html" />
+    <mapID target="colours.pid" url="html/colourSchemes/pid.html" />
+    <mapID target="colours.user" url="html/colourSchemes/user.html"/>
+    <mapID target="colours.abovepid" url="html/colourSchemes/abovePID.html"/>
+    <mapID target="colours.conservation" url="html/colourSchemes/conservation.html"/>
+    <mapID target="colours.annotation" url="html/colourSchemes/annotationColouring.html"/>
+    <mapID target="colours.purinepyrimidine" url="html/colourSchemes/purinepyrimidine.html"/>
+    <mapID target="colours.rnahelices" url="html/colourSchemes/rnahelicesColouring.html"/>
+    
+    <mapID target="calcs.alquality" url="html/calculations/quality.html"/>   
+    <mapID target="calcs.alconserv" url="html/calculations/conservation.html"/>
+    <mapID target="calcs.alstrconsensus" url="html/calculations/structureconsensus.html"/>  
+    <mapID target="calcs.consensus" url="html/calculations/consensus.html"/>   
+    <mapID target="calcs.dialog" url="html/calculations/calculations.html"/>
+    
+    <mapID target="nucleicAcids" url="html/na/index.html"/>
+    
+    <mapID target="menus" url="html/menus/index.html"/>
+    <mapID target="desktopMenu" url="html/menus/desktopMenu.html"/>
+    <mapID target="alMenu" url="html/menus/alignmentMenu.html"/>
+    <mapID target="alwFile" url="html/menus/alwfile.html"/>
+    <mapID target="alwEdit" url="html/menus/alwedit.html"/>
+    <mapID target="alwSelect" url="html/menus/alwselect.html"/>
+    <mapID target="alwView" url="html/menus/alwview.html"/>
+    <mapID target="alwAnnotations" url="html/menus/alwannotation.html"/>
+    <mapID target="alwFormat" url="html/menus/alwformat.html"/>
+    <mapID target="alwColour" url="html/menus/alwcolour.html"/>
+    <mapID target="alwCalc" url="html/menus/alwcalculate.html"/>
+    
+    <mapID target="wsMenu" url="html/menus/wsmenu.html"/>
++   <mapID target="alwHmmer" url="html/menus/alwhmmer.html"/>
+    <mapID target="popMenu" url="html/menus/popupMenu.html"/>
+    <mapID target="popMenuAddref" url="html/menus/popupMenu.html#addrefannot"/>
+    <mapID target="annotPanelMenu" url="html/menus/alwannotationpanel.html"/>
+    
+    <mapID target="memory" url="html/memory.html" />
+    <mapID target="groovy" url="html/features/groovy.html" />
+    <mapID target="groovy.colours" url="html/features/groovy.html#groovyColours" />
+    <mapID target="groovy.featurescounter" url="html/groovy/featuresCounter.html" />
+    <mapID target="privacy" url="html/privacy.html" />
+    <mapID target="vamsas" url="html/vamsas/index.html"/>
+    <mapID target="aminoAcids" url="html/misc/aminoAcids.html" />
+    <mapID target="aaProperties" url="html/misc/aaproperties.html" />
+    <mapID target="geneticCode" url="html/misc/geneticCode.html" />
+    <mapID target="subtMatrices" url="html/calculations/scorematrices.html" />
+    <mapID target="subtMatrices.pam250" url="html/calculations/scorematrices.html#pam250" />
+    
+    <mapID target="biojson" url="html/features/bioJsonFormat.html" />
+    <mapID target="pdbfetcher" url="html/features/pdbsequencefetcher.html" />
+    <mapID target="pdbfts" url="html/features/pdbsequencefetcher.html#pdbfts" />
+    <mapID target="siftsmapping" url="html/features/siftsmapping.html" />
+    <mapID target="pdbchooser" url="html/features/structurechooser.html" />
+    <mapID target="selectcolbyannot" url="html/features/columnFilterByAnnotation.html" />
+    <mapID target="biojsmsa" url="html/features/biojsmsa.html" />
+    
+    <mapID target="ensemblfetch" url="html/features/ensemblsequencefetcher.html" />
+    
+    <mapID target="uniprotfetcher" url="html/features/uniprotsequencefetcher.html" />
+    <mapID target="uniprotfts" url="html/features/uniprotsequencefetcher.html#uniprotfts" />
+    
+    <mapID target="urllinks" url="html/webServices/urllinks.html" />
+    <mapID target="linksprefs" url="html/features/preferences.html#links" />
+    <mapID target="backIcon" url="icons/back.png" />
+    <mapID target="forwardIcon" url="icons/forward.png" />
+    <mapID target="homeIcon" url="icons/Home.png" />
+    <mapID target="printIcon" url="icons/print.png" />
+    <mapID target="printSetupIcon" url="icons/setup.png" />
+    
+    <mapID target="overview" url="html/features/overview.html" />
+    <mapID target="overviewprefs" url="html/features/preferences.html#overview" />
+ </map>
index 0000000,77ddd88..0bf615b
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,175 +1,176 @@@
+ <?xml version="1.0" encoding="ISO-8859-1"  ?>
+ <!--
+  * 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.
+  -->
+ <toc version="1.0">
+ <!-- DO NOT WRAP THESE LINES - help2Website relies on each item being on one line! -->
+       <tocitem text="Jalview Documentation" target="home" expand="true">
+                       <tocitem text="What's new" target="new" expand="true">
+                               <tocitem text="Latest Release Notes" target="release"/>
+                               <tocitem text="Structure Chooser" target="pdbchooser"/>
+                               <tocitem text="Chimera Annotation Exchange" target="chimera.annotxfer"/>
+               </tocitem>
+               
+               <tocitem text="Editing Alignments" target="edit" />
+               <tocitem text="Cursor Mode" target="cursor" />
+               <tocitem text="Key Strokes" target="keys" />
+               
+               <tocitem text="Input / Output" target="io" expand="false">
+                       <tocitem text="BioJSON v1.0" target="biojson" />
+               </tocitem>
+               
+               <tocitem text="Making Figures" target="export" expand="false">
+                       <tocitem text="BioJS MSA Viewer" target="biojsmsa" />
+               </tocitem>
+               
+               <tocitem text="Hidden Regions" target="hiddenRegions" />
+               <tocitem text="Multiple Views" target="multipleviews" />
+               <tocitem text="Split Frame View" target="splitframe" />
+               <tocitem text="Viewing Trees" target="treeviewer" expand="false" />
+               <tocitem text="Fetching Sequences" target="seqfetch" />         
+               
+               <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
+               
+               <tocitem text="Nucleic Acid Support" target="nucleicAcids" expand="false">
+                       <tocitem text="Viewing RNA structure" target="varna" />
+                       <tocitem text="RNA Structure Consensus" target="calcs.alstrconsensus" />
+                       <tocitem text="RNA Helices coloring" target="colours.rnahelices" />
+               </tocitem>
+               
+               <tocitem text="Sequence Features" target="seqfeatures" expand="false">
+                       <tocitem text="Sequence Feature Settings" target="seqfeatures.settings" />
+                       <tocitem text="Sequence Features File" target="features.fileformat" />
+                       <tocitem text="Feature Colourschemes" target="features.featureschemes" />
+                       <tocitem text="User Defined Sequence Features" target="seqfeatcreat" />
+                       <tocitem text="Editing Sequence Features" target="seqfeatedit" />
+                       <tocitem text="HTML annotation report" target="io.seqreport" />
+               </tocitem>
+               
+               <tocitem text="Web Services" target="webservice" expand="false">
+                       <tocitem text="JABAWS" target="jabaws" />
+                       <tocitem text="Web Service Preferences" target="wsprefs" />
+                       <tocitem text="Web Service Parameters" target="wsparams" />
+                       <tocitem text="Sequence Alignment" target="msaservice"  expand="false">
+                               <tocitem text="Multiple Alignment Subjobs" target="msaservice" />
+                       </tocitem>
+                       <tocitem text="Secondary Structure Prediction" target="jpred" />
+                       <tocitem text="RNAalifold RNA Secondary Structure Prediction" target="rnaalifold" />
+                       <tocitem text="Protein Disorder Prediction" target="disorder" />
+                       <tocitem text="Alignment Conservation Analysis" target="aacon" />
+                       <tocitem text="Multi-Harmony Alignment Analysis" target="shmrws" />
+                       <tocitem text="Sequence Retrieval" target="seqfetch" />
+                       <tocitem text="Database Reference Retrieval" target="dbreffetcher" />                   
+               </tocitem>
+               
+               <tocitem text="Colour Schemes" target="colours" expand="false">
+                       <tocitem text="Background Dependent Text Colour" target="backdeptextcol" />
+                       <tocitem text="ClustalX" target="colours.clustal" />
+                       <tocitem text="Zappo" target="colours.zappo" />
+                       <tocitem text="Taylor" target="colours.taylor" />
+                       <tocitem text="Hydrophobicity" target="colours.hydro" />
+                       <tocitem text="Helix propensity" target="colours.helix" />
+                       <tocitem text="Strand propensity" target="colours.strand" />
+                       <tocitem text="Turn propensity" target="colours.turn" />
+                       <tocitem text="Buried index" target="colours.buried" />
+                       <tocitem text="Nucleotide colours" target="colours.nucleotide" />
+                       <tocitem text="Purine/Pyrimidine colours" target="colours.purinepyrimidine" />
+                       <tocitem text="Blosum62" target="colours.blosum" />
+                       <tocitem text="by Percentage Identity" target="colours.pid" />
+                       <tocitem text="User Defined" target="colours.user" />
+                       <tocitem text="Above Percentage Identity" target="colours.abovepid" />
+                       <tocitem text="By conservation" target="colours.conservation" />
+                       <tocitem text="T-COFFEE Scores" target="io.tcoffeescores" />
+                       <tocitem text="By Annotation" target="colours.annotation" />
+                       <tocitem text="By RNA Helices" target="colours.rnahelices" />
+               </tocitem>
+               
+               <tocitem text="Calculations" expand="false">
+                       <tocitem text="Sorting alignments" target="sorting" />
+                       <tocitem text="Trees and PCA" target="calcs.dialog" expand="false">
+                         <tocitem text="Calculating trees" target="trees" />
+                         <tocitem text="Principal Component Analysis" target="pca" />
+                       </tocitem>
+                       <tocitem text="Tree/PCA Input Data" target="recoverInputdata" />
+                       <tocitem text="Pairwise Alignments" target="pairwise" />
+                       <tocitem text="Remove Redundancy" target="redundancy" />
+                       <tocitem text="Reference Sequences" target="referenceseq" />
+               </tocitem>
+               
+               <tocitem text="Sequence Annotations" target="seqannots" expand="true">
+                       <tocitem text="Annotation from Structure" target="xsspannotation" expand="false" />
+               </tocitem>
+               
+               <tocitem text="Alignment Annotations" target="alannotation" expand="false">
+                       <tocitem text="Conservation" target="calcs.alconserv" />
+                       <tocitem text="Quality" target="calcs.alquality" />
+                       <tocitem text="Consensus" target="calcs.consensus" />
+                       <tocitem text="RNA Structure Consensus" target="calcs.alstrconsensus" />
+                       <tocitem text="Annotations File Format" target="annotations.fileformat" />
+                       <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
+               </tocitem>
+               <tocitem text="3D Structure Data" target="viewingpdbs" expand="false">
+                       <tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
+                       <tocitem text="PDB Structure Chooser" target="pdbchooser" />
+                       <tocitem text="Jmol Viewer" target="pdbjmol" />
+                       <tocitem text="Chimera Viewer" target="chimera" />                      
+               </tocitem>
+               <tocitem text="Viewing RNA structures" target="varna" expand="false"/>
+               <tocitem text="Opening URLs from Jalview" target="urllinks" expand="true">
+                   <tocitem text="Configuring URL Links" target="linksprefs" />
+               </tocitem>
+               <tocitem text="VAMSAS Data Exchange" target="vamsas">
+                       <!-- what can Jalview share with other apps -->
+                       <!-- what other apps exist -->
+               </tocitem>
+               <tocitem text="Window Menus" target="menus" expand="false">
+                       <tocitem text="Desktop Window" target="desktopMenu" />
+                       <tocitem text="Alignment Window" target="alMenu">
+                               <tocitem text="File Menu" target="alwFile" />
+                               <tocitem text="Edit Menu" target="alwEdit" />
+                               <tocitem text="Select Menu" target="alwSelect" />
+                               <tocitem text="View Menu" target="alwView" />
+                               <tocitem text="Annotations Menu" target="alwAnnotations" />
+                               <tocitem text="Format Menu" target="alwFormat" />
+                               <tocitem text="Colour Menu" target="alwColour" />
+                               <tocitem text="Calculate Menu" target="alwCalc" />
+                               <tocitem text="Web Service Menu" target="wsMenu" />
++                              <tocitem text="HMMER Menu" target="alwHmmer" />
+                               <tocitem text="Annotation Panel Menu" target="annotPanelMenu" />
+                               <tocitem text="Popup Menu" target="popMenu" />
+                       </tocitem>
+               </tocitem>
+               <tocitem text="Preferences" target="preferences" />
+               <tocitem text="Memory Settings" target="memory" expand="false"/>
+               <tocitem text="Scripting with Groovy" target="groovy">
+                       <tocitem text="Groovy Features Counter example" target="groovy.featurescounter"/>
+               </tocitem>
+               <tocitem text="Command Line" target="commandline" expand="false">
+                       <tocitem text="Command Line Arguments" target="clarguments" />
+               </tocitem>
+               <tocitem text="Privacy" target="privacy" />
+       </tocitem>
+       <tocitem text="Useful information" expand="true">
+               <tocitem text="Amino Acid Table" target="aminoAcids" />
+               <tocitem text="Amino Acid Properties" target="aaProperties" />
+               <tocitem text="The Genetic Code" target="geneticCode" />
+               <tocitem text="Sequence Substitution Matrices" target="subtMatrices" />
+       </tocitem>
+ </toc>
index 0000000,8c6b699..5bb0b8a
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,388 +1,397 @@@
+ <html>
+ <!--
+  * 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.
+  -->
+ <head>
+ <title>Preferences</title>
+ </head>
+ <body>
+   <p>
+     <strong>Preferences</strong>
+   </p>
+   <p>
+     The preferences panel is opened from the Jalview Desktop&rsquo;s <strong><em>Tools</em></strong>
+     menu.
+   </p>
+   <p>There are eight tabs in the Preferences dialog box:
+   <ul>
+     <li>The <a href="#visual"><strong>&quot;Visual&quot;</strong>
+         Preferences</a> tab allows you to configure the default display for
+       a new alignment window.
+     </li>
+     <li>The <a href="#colours"><strong>&quot;Colours&quot;</strong>
+         Preferences</a> tab allows you to configure default colourschemes
+       for a new alignment window.
+     </li>
+     <li>The <a href="#overview"><strong>&quot;Overview&quot;</strong>
+         Preferences</a> tab configures defaults for the overview window.
+     </li>
+     <li>The <a href="#structure"><strong>&quot;Structure&quot;</strong>
+         Preferences</a> tab allows you to configure options for obtaining
+       and displaying structure information.
+     </li>
+     <li>The <a href="#connections"><strong>&quot;Connections&quot;</strong>
+         Preferences</a> tab allows you to configure Jalview's internet
+       settings and specify your default web browser.
+     </li>
+     <li>The <a href="#links"><strong>&quot;Links&quot;</strong>
+         Preferences</a> tab shows the currently configured <em>URL
+         Links</em> shown in the <strong>Link</strong> submenu in the Sequence
+       ID popup menu.
+     </li>
+     <li>The <a href="#output"><strong>&quot;Output&quot;</strong>
+         Preferences</a> tab contains settings affecting the export of
+       sequence alignments and EPS files.
+     </li>
+     <li>The <a href="#editing"><strong>&quot;Editing&quot;</strong>
+         Preferences</a> tab contains settings affecting behaviour when editing alignments.
+     </li>
++    <li>The <a href="#hmmer"><strong>&quot;HMMER&quot;</strong>
++        Preferences</a> tab allows you to configure locally installed HMMER tools.
++    </li>
+     <li>The <a href="../webServices/webServicesPrefs.html"><strong>&quot;Web
+           Service&quot;</strong> Preferences</a> tab allows you to configure the <a
+       href="http://www.compbio.dundee.ac.uk/jabaws">JABAWS</a>
+       servers that Jalview uses, and change the layout of the
+       alignment's Web Services menu.
+     </li>
+   </ul>
+   </p>
+   <p>
+     <strong><a name="visual">Visual</a> Preferences tab</strong>
+   </p>
+   <p>
+     <em>Maximise Window</em> - If this is selected, a new alignment
+     window will stretch to fit the available space.
+   </p>
+   <p>
+     <em>Open Overview Window</em> - When this is selected, the <a
+       href="overview.html">alignment overview</a> panel is opened
+     by default for a new alignment window.
+   </p>
+   <p>
+     <em>Show Annotations</em> - If this is selected the new window will
+     display an annotation panel below the sequences. This annotation
+     panel may have several rows describing the whole alignment. The 4
+     standard annotations <em>Conservation</em>, <em>Quality</em>, 
+     <em>Occupancy</em> and <em>Consensus</em> for the alignment may 
+     be shown or hidden by default using the checkboxes adjacent and
+     below.
+   </p>
+   <p>
+     <em>Show group: Conservation and Consensus</em> controls the display
+     of per-group automatic annotation.
+   </p>
+   <p>
+     <em>Consensus: Histogram and Logo</em> checkboxes control the
+     display of the consensus histogram and sequence logo for consensus
+     annotation rows.
+   </p>
+   <p>
+     <em>Full Sequence ID</em> - If selected the ID panel will display
+     the name of a sequence plus the start and end residues in the format
+     name/start-end. If not selected, the displayed ID will be the name
+     of the sequence.
+   </p>
+   <p>
+     <em>Right Align IDs</em> - select to align all sequence IDs to the
+     left-hand edge of the sequence alignment, rather than the left-hand
+     edge of the alignment display window.
+   </p>
+   <p>
+     <em>Font</em> - The default font name, size and style can be set for
+     a new alignment window.
+   </p>
+   <p>
+     <em>Sequence ID Tooltip</em>: Control the display of Database
+     References and Non-positional annotation in the tooltip displayed
+     when the mouse is over a sequence's ID.
+   </p>
+   <p>
+     <em>Show Unconserved</em> - When this is selected, all consensus
+     sequence symbols will be rendered as a '.', highlighting mutations
+     in highly conserved alignments.
+   </p>
+   <p>
+     <em>Sequence Name Italics</em> - select to apply the italicised
+     version of the font to sequence labels.
+   </p>
+   <p>
+     <em>Smooth Font</em> - Toggles anti-aliasing on / off for faster
+     rendering of the alignment.
+   </p>
+   <p>
+     <em>Gap Symbol</em> - The default gap symbol may be set to either
+     &quot;-&quot; or &quot;.&quot;
+   </p>
+   <p>
+     <em>Wrap Alignment</em> - Select whether to open new alignment
+     windows in wrapped mode or not.
+   </p>
+   <p>
+     <em>Sort alignment by</em> - When the alignment is loaded in, it can
+     be ordered as read (No sort), or sorted by Id or pairwise identity.
+   </p>
+   <p>
+     <em>Sort annotations by</em> - Annotations can be unsorted, sorted
+     by the order of the related sequences in the alignment, or by label.
+     Autocalculated annotations (e.g. Consensus) can be shown either last
+     (below sequence annotations) or first (above sequence annotations).
+     <em>Since Jalview 2.8.2.</em>
+   </p>
+   <p>
+     <em>Open file</em> - If this is selected then the default alignment
+     file will be opened when Jalview is started. You can change the
+     default file by clicking on file name and either typing in the file
+     path or selecting it from the file chooser window.<br /> <em>Note:
+       The default example alignment is updated periodically to
+       demonstrate new features in Jalview.</em>
+   </p>
+   <p>
+     <a name="colours"><strong>&quot;Colours&quot;
+         Preferences tab</strong>
+   </p>
+   <p>
+     <em>Alignment Colour</em> - The default colour scheme for a new
+     alignment window. If the chosen option is &quot;User Defined&quot;
+     then the last User Defined Colour loaded or saved via the User
+     Defined Colours panel will be loaded.
+   </p>
+   <p>
+     <em>Annotation Shading Default</em> - set the default minimum and
+     maximum colours used when <a
+       href="../colourSchemes/annotationColouring.html">Colour
+       by Annotation...</a> is selected from the alignment window's colours
+     menu.
+   </p>
+    <p>
+     <a name="overview"><strong>&quot;Overview&quot;
+         Preferences tab</strong>
+   </p>
+   <p>
+     <em>Use legacy gap colouring (gaps are white)</em> - when enabled,
+     Jalview's overview shows gaps as white, and sequences with no
+     colourscheme applied as grey.
+   </p>
+   <p>
+     <em>Show Hidden regions when opening overview</em> - default setting
+     for inclusion of hidden regions.
+   </p>
+   <p>
+     <em>Gap Colour</em> - When legacy gap colouring is not enabled, this
+     configures the default colour for gaps in the overview.
+   </p>
+   <p>
+     <em>Hidden Colour</em> - colour used to highlight regions in the
+     overview that are hidden in the alignment.
+   </p>
+   <p>
+     <em>Gap Colour</em> - The default colour scheme for a new alignment
+     window. If the chosen option is &quot;User Defined&quot; then the
+     last User Defined Colour loaded or saved via the User Defined
+     Colours panel will be loaded.
+   </p>
+   <p>
+     <a name="structure"><strong>&quot;Structure&quot;
+         Preferences tab</strong></a><em> added in Jalview 2.8.2</em>
+   </p>
+   <p>
+     <em>Process secondary structure from PDB</em> - if selected, then
+     structure information read from PDB will be processed and annotation
+     added to associated sequences.
+   <p>
+     <em>Use RNAView for secondary structure</em> - if selected, the
+     pyRNA RNAView service (<a href="https://github.com/fjossinet/PyRNA">https://github.com/fjossinet/PyRNA</a>)
+     will be called to derive secondary structure information for RNA
+     chains.
+   <p>
+     <em>Add secondary structure annotation to alignment</em> - if
+     selected, <a href="http://swift.cmbi.ru.nl/gv/dssp/">Jmol's
+       implementation DSSP</a> will be used to add annotation to polypeptide
+     chains in the structure.
+   <p>
+     <em>Add Temperature Factor annotation to alignment</em> - if
+     selected, values extracted from the Temperature Factor column for
+     the backbone atoms in the PDB file will be extracted as annotation
+     lines shown on the alignment.
+   <p>
+     <em>Default structure viewer</em> - choose Jmol or CHIMERA for
+     viewing 3D structures.
+   <p>
+     <em>Path to Chimera program</em> - Optional, as Jalview will search
+     standard installation paths for Windows, Linux or MacOS. If you have
+     installed Chimera in a non-standard location, you can specify it
+     here, by entering the full path to the Chimera executable program.
+     Double-click this field to open a file chooser dialog.
+   <p>
+     <a name="connections"><strong>&quot;Connections&quot;
+         Preferences tab</strong></a>
+   </p>
+   <p>
+     <em>Default Browser (Unix)</em><br> It's difficult in Java to
+     detect the default web browser for Unix users. If Jalview can't find
+     your default web browser, enter the name or full path to your web
+     browser application.
+   </p>
+   <p>
+     <em>Proxy Server</em><br> If you normally use a proxy server
+     for using the internet, you must tick the box &quot;Use a Proxy
+     Server&quot; and enter the address and port details as necessary.
+     Web Services will not work if you are using a proxy server and do
+     not enter the settings here.
+   </p>
+   <p>
+     <em>Usage statistics, Questionnaire and Version checks</em><br>
+     Uncheck these options to prevent Jalview from submitting usage
+     statistics to google analytics, checking for Jalview questionnaires
+     or retrieving details of the latest release version (at
+     www.jalview.org). See the <a href="../privacy.html">user privacy
+       statement</a> for more information.
+   </p>
+   <p>
+     <a name="links"><strong>The &quot;Links&quot; Preferences
+         tab</strong></a>
+   </p>
+   <p>
+     This panel shows a table, and two sections - <em>Edit</em> and <em>Filter</em>.
+     The table shows the available URL link definitions (consisting of a
+     database, Name, and URL template string), a checkbox <em>In
+       Menu</em> which indicates if the link is enabled, and <em>Double
+       Click</em> which marks the link that will be opened if a sequence's ID
+     is double clicked. The table can be sorted by clicking on the column headers.
+   </p>
+   <p><em>Edit Links</em><br /> This section contains three buttons,
+     <em>New</em>, <em>Edit</em> and <em>Delete</em>, which allow you to
+     create, modify and remove user-defined URL links from the Sequence
+     ID's links submenu.
+   </p>
+   <p>
+     <em>Filter</em><br /> The <em>Filter text</em> box allows you to
+     quickly show rows in the table containing a particular text string.
+     The <em>Custom only</em> button limits the entries in the table to
+     just those you have configured yourself <em>via</em> the <em>Edit
+       Links</em> buttons. Press <em>Show all</em> to clear any filters.
+   </p>
+   <p>The links table is prepopulated with persistent URLs for many common
+     bioinformatics databases (since 2.10.2). These links are downloaded by Jalview from
+     the <em>identifiers.org</em> website, and the names and URLs are not
+     user editable.
+     <a href="../webServices/urllinks.html#urllinks">Read more about configuring
+       URL links.</a>
+   </p>
+   <p>
+     <a name="output"><strong>Output Preferences tab</strong></a>
+   </p>
+   <p>
+     <em>EPS Rendering Style</em><br> This is a selection box which
+     allows the user to set a default rendering style for EPS export:
+   <ul>
+     <li>&quot;Prompt each time&quot;<br> Choose this to be
+       asked to select between Lineart and Text each time you make an EPS
+       file.
+     </li>
+     <li>&quot;Lineart&quot;<br> EPS files will accurately
+       reproduce the alignment view in Jalview and all characters will be
+       converted into line art. Files generated in this way are large and
+       are not easily editable, but have no font table dependencies.
+     </li>
+     <li>&quot;Text&quot;<br> EPS files will be a mixture of
+       text and lineart. This produces compact files that can be edited
+       easily in programs like Microsoft Word and Adobe Illustrator, but
+       can be problematic if the fonts available to Jalview are not
+       accessible by the program reading the EPS file.
+   </ul>
+   <p>
+     <em>Automatically set ID width</em><br> When enabled, the
+     column containing sequence and annotation labels at the left hand
+     side of an exported figure will be made large enough to display each
+     sequence ID and annotation label in its own line. Enable this if you
+     have particularly long sequence IDs and need to generate EPS or PNG
+     figures or web pages.
+   </p>
+   <p>
+     <em>Figure ID column width</em><br> Manually specify the width
+     of the left hand column where sequence IDs and annotation labels
+     will be rendered in exported alignment figures. This setting will be
+     ignored if <em>&quot;Automatically set ID width&quot;</em> is set.
+   </p>
+   <p>
+     <em>Sequence/Start-End Numbering</em><br> The output tab also
+     has a group of checkboxes for each file format. If these are ticked,
+     then Jalview will write files with the start and end sequence
+     positions appended to each sequence id:
+   <pre>
+   >ID/1-10
+   AACDEAAFEA
+ </pre>
+   <p>If the boxes are left unchecked for a particular format, the
+     sequence limits will not be appended to the sequence id.</p>
+   <p>
+     <em>Embed BioJSON to HTML export</em>
+   </p>
+   <p>
+     When this option is enabled, Jalview embeds <a
+       href="bioJsonFormat.html">BioJSON</a> data within HTML files
+     exported from Jalview at generation time. This enables the exported
+     HTML files to be extracted and imported back into the Jalview
+     desktop application at a later time.
+   <p>
+     <em>Use Modeller Output</em>
+   </p>
+   <p>
+     This option only applies to PIR format output. Jalview automatically
+     reads PIR files with sequence descriptions compatible with the
+     program <a href="http://salilab.org/modeller/">Modeller</a>. If this
+     option is selected <a href="../io/modellerpir.html">Jalview will
+       write Modeller style PIR files</a> with correct start/end numbering
+     and PDB file association (if available). The Jalview id/start-end
+     option is ignored if Modeller output is selected.
+   <p>
+     <a name="editing"><strong>&quot;Editing&quot; Preferences tab</strong></a>
+   </p>
+   <p>There are currently three options available which can be
+     selected / deselected.</p>
+   <p>
+     <em>AutoCalculate Consensus</em> - For large alignments it can be
+     useful to deselect &quot;Autocalculate Consensus&quot; when editing.
+     This prevents lengthy calculations which are performed after each
+     sequence edit. New alignment windows will have their
+     &quot;Autocalculate Consensus&quot; option set according to this
+     setting.
+   </p>
+   <p>
+     <em>Pad Gaps when Editing</em> - New alignment windows will
+     &quot;Pad Gaps&quot; according to this setting.
+   </p>
+   <p>
+     <em>Sort with New Tree</em> - When selected, any trees calculated or
+     loaded onto the alignment will automatically sort the alignment.
+   </p>
 -  <p>&nbsp;</p>
 -  <p>&nbsp;</p>
++  <p>
++    <a name="hmmer"><strong>&quot;HMMER&quot; Preferences tab</strong></a>
++  </p>
++  <p>If you have installed HMMER tools (available from <a href="http://hmmerorg">hmmer.org</a>),
++  then you should specify on this screen the location of the installation (the path to the folder 
++  containing binary executable programs). Double-click in the input field to open a file browser.</p>
++  <p>When this path is configured, the <a href="../menus/alwhmmer.html">HMMER menu</a> will be
++  enabled in the Alignment window.</p>
+ </body>
+ </html>
index ce23c4b,0000000..ce23c4b
mode 100644,000000..100644
--- /dev/null
@@@ -196,16 -192,15 +193,17 @@@ label.colourScheme_%identity = Percenta
  label.colourScheme_zappo = Zappo
  label.colourScheme_taylor = Taylor
  label.colourScheme_hydrophobic = Hydrophobicity
- label.colourScheme_helix_propensity = Helix Propensity
- label.colourScheme_strand_propensity = Strand Propensity
- label.colourScheme_turn_propensity = Turn Propensity
- label.colourScheme_buried_index = Buried Index
+ label.colourScheme_helixpropensity = Helix Propensity
+ label.colourScheme_strandpropensity = Strand Propensity
+ label.colourScheme_turnpropensity = Turn Propensity
+ label.colourScheme_buriedindex = Buried Index
  label.colourScheme_purine/pyrimidine = Purine/Pyrimidine
  label.colourScheme_nucleotide = Nucleotide
- label.colourScheme_t-coffee_scores = T-Coffee Scores
- label.colourScheme_rna_helices = By RNA Helices
 +label.colourScheme_hmmer-uniprot = HMMER profile v global background
 +label.colourScheme_hmmer-alignment = HMMER profile v alignment background
+ label.colourScheme_t-coffeescores = T-Coffee Scores
+ label.colourScheme_rnahelices = By RNA Helices
+ label.colourScheme_sequenceid = Sequence ID Colour
  label.blc = BLC
  label.fasta = Fasta
  label.msf = MSF
@@@ -1367,58 -1339,69 +1344,127 @@@ label.most_bound_molecules = Most Boun
  label.most_polymer_residues = Most Polymer Residues
  label.cached_structures = Cached Structures
  label.free_text_search = Free Text Search
 +label.hmmalign = hmmalign
 +label.use_hmm = HMM profile to use
 +label.hmmbuild = hmmbuild
 +label.hmmsearch = hmmsearch
 +label.installation = Installation
 +label.hmmer_location = HMMER Binaries Installation Location
 +label.cygwin_location = Cygwin Binaries Installation Location (Windows)
 +label.information_annotation = Information Annotation
 +label.ignore_below_background_frequency = Ignore Below Background Frequency
 +label.information_description = Information content, measured in bits
 +warn.no_hmm = No Hidden Markov model found.\nRun hmmbuild or load an HMM file first.
 +label.no_sequences_found = No matching sequences, or an error occurred.
 +label.hmmer = HMMER
 +label.trim_termini = Trim Non-Matching Termini
 +label.trim_termini_desc = If true, non-matching regions on either end of the resulting alignment are removed.
 +label.no_of_sequences = Number of sequences returned
 +label.reporting_cutoff = Reporting Cut-off
 +label.freq_alignment = Use alignment background frequencies
 +label.freq_uniprot = Use Uniprot background frequencies
 +label.hmmalign_options = hmmalign options
 +label.hmmsearch_options = hmmsearch options
 +label.executable_not_found = The ''{0}'' executable file was not found
 +warn.command_failed = {0} failed
 +label.invalid_folder = Invalid Folder
 +label.number_of_results = Number of Results to Return
 +label.auto_align_seqs = Automatically Align Fetched Sequences
 +label.use_accessions = Return Accessions
 +label.seq_evalue = Sequence E-value Cut-off
 +label.seq_score = Sequence Score Threshold
 +label.dom_evalue = Domain E-value Cut-off
 +label.dom_score = Domain Score Threshold
 +label.number_of_results_desc = The maximum number of hmmsearch results to display
 +label.auto_align_seqs_desc = If true, all fetched sequences will be aligned to the hidden Markov model with which the search was performed
 +label.use_accessions_desc = If true, the accession number of each sequence is returned, rather than that sequence's name
 +label.seq_e_value_desc = The E-value cutoff for returned sequences (hmmsearch -E)
 +label.seq_score_desc = The score threshold for returned sequences (hmmsearch -T)
 +label.dom_e_value_desc = The E-value cutoff for returned domains (hmmsearch --domE)
 +label.dom_score_desc = The score threshold for returned domains (hmmsearch --domT)
 +label.add_database = Add Database
 +label.this_alignment = This alignment
 +warn.invalid_format = This is not a valid database file format. The current supported formats are Fasta, Stockholm and Pfam.
 +label.database_for_hmmsearch = The database hmmsearch will search through
 +label.use_reference = Use Reference Annotation
 +label.use_reference_desc = If true, hmmbuild will keep all columns defined as a reference position by the reference annotation
 +label.hmm_name = Alignment HMM Name
 +label.hmm_name_desc = The name given to the HMM for the alignment
 +warn.no_reference_annotation = No reference annotation found
 +label.hmmbuild_for = Build HMM for
 +label.hmmbuild_for_desc = Build an HMM for the selected sets of sequences
 +label.alignment = Alignment
 +label.groups_and_alignment = All groups and alignment
 +label.groups = All groups
 +label.selected_group = Selected group
 +label.use_info_for_height = Use Information Content as Letter Height
- action.search = Search
++action.search = Search
++||||||| merged common ancestors
++=======
+ label.backupfiles_confirm_delete = Confirm delete
+ label.backupfiles_confirm_delete_old_files = Delete the following older backup files? (see the Backups tab in Preferences for more options)
+ label.backupfiles_confirm_save_file = Confirm save file
+ label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Something possibly went wrong with the backups of this file.
+ label.backupfiles_confirm_save_new_saved_file_ok = The new saved file seems okay.
+ label.backupfiles_confirm_save_new_saved_file_not_ok = The new saved file might not be okay.
+ label.backups = Backups
+ label.backup = Backup
+ label.backup_files = Backup Files
+ label.enable_backupfiles = Enable backup files
+ label.backup_filename_strategy = Backup filename strategy
+ label.append_to_filename = Append to filename (%n is replaced by the backup number)
+ label.append_to_filename_tooltip = %n in the text will be replaced by the backup number. The text will appear after the filename. See the summary box above.
+ label.index_digits = Number of digits to use for the backup number (%n)
+ label.summary_of_backups_scheme = Summary of backup scheme
+ label.scheme_examples = Scheme examples
+ label.increment_index = Increase appended text numbers - newest file has largest number.
+ label.reverse_roll = "Roll" appended text numbers - newest backup file is always number 1.
+ label.keep_files = Deleting old backup files
+ label.keep_all_backup_files = Do not delete old backup files
+ label.keep_only_this_number_of_backup_files = Keep only this number of most recent backup files
+ label.autodelete_old_backup_files = Auto-delete old backup files:
+ label.always_ask = Always ask
+ label.auto_delete = Automatically delete
+ label.filename = filename
+ label.braced_oldest = (oldest)
+ label.braced_newest = (most recent)
+ label.configuration = Configuration
+ label.configure_feature_tooltip = Click to configure variable colour or filters
+ label.schemes = Schemes
+ label.customise = Customise
+ label.custom = Custom
+ label.default = Default
+ label.single_file = Single backup
+ label.keep_all_versions = Keep all versions
+ label.rolled_backups = Rolled backup files
+ label.customise_description = Select Customise, make changes, and click on OK to save your own custom scheme
+ label.custom_description = Your own saved scheme
+ label.default_description = Keep the last three versions of the file
+ label.single_file_description = Keep the last version of the file
+ label.keep_all_versions_description = Keep all previous versions of the file
+ label.rolled_backups_description = Keep the last nine versions of the file from _bak.1 (newest) to _bak.9 (oldest)
+ label.cancel_changes_description = Cancel changes made to your last saved Custom scheme
+ label.previously_saved_scheme = Previously saved scheme
+ label.no_backup_files = NO BACKUP FILES
+ label.include_backup_files = Include backup files
+ label.cancel_changes = Cancel changes
+ label.warning_confirm_change_reverse = Warning!\nIf you change the increment/decrement of the backup filename number, without changing the suffix or number of digits,\nthis may cause loss of backup files created with the previous backup filename scheme.\nAre you sure you wish to do this?
+ label.change_increment_decrement = Change increment/decrement?
+ label.was_previous = was {0}
+ label.newerdelete_replacement_line = Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted and replaced by apparently older file\n''{1}''\t(modified {3}, size {5}).
+ label.confirm_deletion_or_rename = Confirm deletion of ''{0}'' or rename to ''{1}''?
+ label.newerdelete_line = Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted but is newer than the oldest remaining backup file\n''{1}''\t(modified {3}, size {5}).
+ label.confirm_deletion = Confirm deletion of ''{0}''?
+ label.delete = Delete
+ label.rename = Rename
+ label.keep = Keep
+ label.file_info = (modified {0}, size {1})
+ label.annotation_name = Annotation Name
+ label.annotation_description = Annotation Description 
+ label.edit_annotation_name_description = Edit Annotation Name/Description
+ label.alignment = alignment
+ label.pca = PCA
+ label.create_image_of = Create {0} image of {1}
+ label.click_to_edit = Click to edit, right-click for menu
+ label.by_annotation_tooltip = Annotation Colour is configured from the main Colour menu
++>>>>>>> develop
@@@ -1362,4 -1340,69 +1339,70 @@@ label.most_bound_molecules = Más Molécu
  label.most_polymer_residues = Más Residuos de Polímeros
  label.cached_structures = Estructuras en Caché
  label.free_text_search = Búsqueda de texto libre
- action.search = Buscar
++action.search = Buscar
+ label.backupfiles_confirm_delete = Confirmar borrar
+ label.backupfiles_confirm_delete_old_files = Â¿Borrar los siguientes archivos? (ver la pestaña 'Copias' de la ventana de Preferencias para más opciones)
+ label.backupfiles_confirm_save_file = Confirmar guardar archivo
+ label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Posiblemente algo está mal con los archivos de respaldos.
+ label.backupfiles_confirm_save_new_saved_file_ok = El nuevo archivo guardado parece estar bien.
+ label.backupfiles_confirm_save_new_saved_file_not_ok = El nuevo archivo guardado podría no estar bien.
+ label.backups = Respaldos
+ label.backup = Respaldo
+ label.backup_files = Archivos de respaldos
+ label.enable_backupfiles = Habilitar archivos de respaldos
+ label.backup_filename_strategy = Estrategia de nombres de archivo de respaldos
+ label.append_to_filename = Adjuntar texto (%n es reemplazado por el número de respaldo)
+ label.append_to_filename_tooltip = %n en el texto será reemplazado por el número de respaldo. El texto será después del nombre del archivo. Vea el cuadro de resumen arriba.
+ label.index_digits = Número de dígitos a utilizar para el número de respaldo.
+ label.summary_of_backups_scheme = Resumen del esquema de copias de seguridad
+ label.scheme_examples = Ejemplos de esquema
+ label.increment_index = Aumente los números de texto adjuntos: el archivo más nuevo tiene el número más grande
+ label.reverse_roll = Ciclos de texto adjuntos: el respaldo más reciente es siempre el número 1
+ label.keep_files = Borrando los respaldos antiguos
+ label.keep_all_backup_files = No borrar respaldos antiguas
+ label.keep_only_this_number_of_backup_files = Mantenga solo este número de respaldos más recientes
+ label.autodelete_old_backup_files = Borrer automáticamente respaldos antiguos:
+ label.always_ask = Pregunta siempre
+ label.auto_delete = Borrer automáticamente
+ label.filename = nombre_de_archivo
+ label.braced_oldest = (mas antiguo)
+ label.braced_newest = (mas nuevo)
+ label.configuration = Configuración
+ label.configure_feature_tooltip = Haga clic para configurar el color o los filtros
+ label.schemes = Esquemas
+ label.customise = Personalizar
+ label.custom = Personal
+ label.default = Defecto
+ label.single_file = Solo uno respaldo
+ label.keep_all_versions = Mantener todas las versiones
+ label.rolled_backups = Ciclos respaldos
+ label.customise_description = Seleccione Personalizar, haga cambios y haga clic en OK para guardar su propio esquema personalizado
+ label.custom_description = Tu propio esquema guardado
+ label.default_description = Conserve las Ãºltimas tres versiones del archivo
+ label.single_file_description = Conserve la Ãºltima versión del archivo
+ label.keep_all_versions_description = Mantener todas las versiones anteriores del archivo
+ label.rolled_backups_description = Mantenga las Ãºltimas nueve versiones del archivo desde _bak.1 (más reciente) a _bak.9 (más antigua)
+ label.cancel_changes_description = Cancelar los cambios realizados en su Ãºltimo esquema personalizado guardado
+ label.previously_saved_scheme = Esquema previamente guardado
+ label.no_backup_files = NO ARCHIVOS DE RESPALDOS
+ label.include_backup_files = Incluir archivos de respaldos
+ label.cancel_changes = Cancelar cambios
+ label.warning_confirm_change_reverse = Â¡Advertencia!\nSi cambia el incremento/decremento del número de archivos de respaldos, sin cambiar el sufijo o número de dígitos,\nesto puede causar la pérdida de los archivos de respaldos creados con el esquema anterior de nombre de archivo de respaldos.\n¿Está seguro de que desea hacer esto?
+ label.change_increment_decrement = Â¿Cambiar de incremento/decremento?
+ label.was_previous = era {0}
+ label.newerdelete_replacement_line = El archivo de respaldo\n''{0}''\t(modificado {2}, tamaño {4})\nserá borrado y reemplazarse por un archivo aparentemente más antiguo\n''{1}''\t(modificado {3}, tamaño {5}).
+ label.confirm_deletion_or_rename = Confirmar borrar ''{0}'', o cambiar el nombre a ''{1}''?
+ label.newerdelete_line = El archivo de respaldo\n''{0}''\t(modificado {2}, tamaño {4})\nserá borrado pero es mas nuevo que el archivo de respaldo restante más antiguo\n''{1}''\t(modified {3}, size {5}).
+ label.confirm_deletion = Confirmar eliminar ''{0}''?
+ label.delete = Borrar
+ label.rename = Cambiar
+ label.keep = Mantener
+ label.file_info = (modificado {0}, tamaño {1})
+ label.annotation_name = Nombre de la anotación
+ label.annotation_description = Descripción de la anotación 
+ label.edit_annotation_name_description = Editar el nombre/descripción de la anotación
+ label.alignment = alineamiento
+ label.pca = ACP
+ label.create_image_of = Crear imagen {0} de {1}
+ label.click_to_edit = Haga clic para editar, clic en el botón derecho para ver el menú  
+ label.by_annotation_tooltip = El color de anotación se configura desde el menú principal de colores
Simple merge
Simple merge
@@@ -495,21 -487,35 +495,49 @@@ public interface AlignViewportI extend
    @Override
    void setProteinFontAsCdna(boolean b);
  
 -  public abstract TreeModel getCurrentTree();
 +  void setHmmProfiles(ProfilesI info);
 +
 +  ProfilesI getHmmProfiles();
 +
 +  /**
 +   * Registers and starts a worker thread to calculate Information Content
 +   * annotation, if it is not already registered
 +   * 
 +   * @param ap
 +   */
 +  void initInformationWorker(AlignmentViewPanel ap);
 +
 +  boolean isInfoLetterHeight();
 +
 +  abstract TreeModel getCurrentTree();
  
 -  public abstract void setCurrentTree(TreeModel tree);
 +  abstract void setCurrentTree(TreeModel tree);
+   /**
+    * @param update
+    *          - set the flag for updating structures on next repaint
+    */
+   void setUpdateStructures(boolean update);
+   /**
+    *
+    * @return true if structure views will be updated on next refresh
+    */
+   boolean isUpdateStructures();
+   /**
+    * check if structure views need to be updated, and clear the flag afterwards.
+    * 
+    * @return if an update is needed
+    */
+   boolean needToUpdateStructureViews();
+   /**
+    * Adds sequencegroup to the alignment in the view. Also adds a group to the
+    * complement view if one is defined.
+    * 
+    * @param sequenceGroup
+    *          - a group defined on sequences in the alignment held by the view
+    */
+   void addSequenceGroup(SequenceGroup sequenceGroup);
  }
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -579,23 -595,15 +593,25 @@@ public interface AlignmentI extends Ann
    AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo);
  
    /**
-    * Set the hidden columns collection on the alignment
+    * Set the hidden columns collection on the alignment. Answers true if the
+    * hidden column selection changed, else false.
     * 
     * @param cols
+    * @return
     */
-   public void setHiddenColumns(HiddenColumns cols);
+   public boolean setHiddenColumns(HiddenColumns cols);
  
    /**
 +   * Insert a sequence at a position in an alignment
 +   * 
 +   * @param i
 +   *          The index of the position.
 +   * @param snew
 +   *          The new sequence.
 +   */
 +  void insertSequenceAt(int i, SequenceI snew);
 +
 +  /**
     * Set the first sequence as representative and hide its insertions. Typically
     * used when loading JPred files.
     */
Simple merge
@@@ -45,53 -43,62 +45,53 @@@ import java.util.Map
  public class SequenceGroup implements AnnotatedCollectionI
  {
    // TODO ideally this event notification functionality should be separated into
 -  // a
 -  // subclass of ViewportProperties similarly to ViewportRanges. Done here as
 -  // quick fix for JAL-2665
 +  // a subclass of ViewportProperties similarly to ViewportRanges.
 +  // Done here as a quick fix for JAL-2665
    public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
  
 -  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
 -          this);
 +  private String groupName;
  
 -  public void addPropertyChangeListener(PropertyChangeListener listener)
 -  {
 -    changeSupport.addPropertyChangeListener(listener);
 -  }
 +  private String description;
  
 -  public void removePropertyChangeListener(PropertyChangeListener listener)
 -  {
 -    changeSupport.removePropertyChangeListener(listener);
 -  }
 -  // end of event notification functionality initialisation
 +  private AnnotatedCollectionI context;
  
 -  String groupName;
 +  private Conservation conservationData;
  
 -  String description;
 +  private ProfilesI consensusProfiles;
  
 -  Conservation conserve;
 +  private ProfilesI hmmProfiles;
  
 -  boolean displayBoxes = true;
 +  private boolean displayBoxes = true;
  
 -  boolean displayText = true;
 +  private boolean displayText = true;
  
 -  boolean colourText = false;
 +  private boolean colourText = false;
  
 -  /**
 -   * True if the group is defined as a group on the alignment, false if it is
 -   * just a selection.
 +  /*
 +   * true if the group is defined as a group on the alignment, false if it is
 +   * just a selection
     */
 -  boolean isDefined = false;
 +  private boolean isDefined;
  
 -  /**
 +  /*
     * after Olivier's non-conserved only character display
     */
 -  boolean showNonconserved = false;
 +  private boolean showNonconserved;
  
 -  /**
 -   * group members
 +  /*
 +   * sequences in the group
     */
-   private List<SequenceI> sequences = new ArrayList<>();
+   private List<SequenceI> sequences;
  
 -  /**
 +  /*
     * representative sequence for this group (if any)
     */
 -  private SequenceI seqrep = null;
 +  private SequenceI seqrep;
  
 -  int width = -1;
 +  private int width = -1;
  
 -  /**
 -   * Colourscheme applied to group if any
 +  /*
 +   * colour scheme applied to group if any
     */
    public ResidueShaderI cs;
  
      }
    }
  
 +  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
 +          this);
 +
 +  public void addPropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.addPropertyChangeListener(listener);
 +  }
 +
 +  public void removePropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.removePropertyChangeListener(listener);
 +  }
 +
+   /**
+    * Constructor that copies the given list of sequences
+    * 
+    * @param seqs
+    */
+   public SequenceGroup(List<SequenceI> seqs)
+   {
+     this();
+     this.sequences.addAll(seqs);
+   }
    public boolean isShowSequenceLogo()
    {
      return showSequenceLogo;
Simple merge
@@@ -62,14 -63,9 +63,15 @@@ 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.io.AlignmentProperties;
  import jalview.io.AnnotationFile;
+ import jalview.io.BackupFiles;
  import jalview.io.BioJsHTMLOutput;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
@@@ -139,14 -131,14 +141,15 @@@ import java.net.URL
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Deque;
 -import java.util.Enumeration;
 -import java.util.Hashtable;
 +import java.util.HashSet;
  import java.util.List;
 +import java.util.Set;
  import java.util.Vector;
  
+ import javax.swing.ButtonGroup;
  import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JEditorPane;
 +import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JLayeredPane;
  import javax.swing.JMenu;
@@@ -318,10 -302,13 +318,11 @@@ public class AlignViewport extends Alig
  
      if (residueShading != null)
      {
 -      residueShading.setConsensus(hconsensus);
 +      residueShading.setConsensus(consensusProfiles);
      }
+     setColourAppliesToAllGroups(true);
    }
  
 -  boolean validCharWidth;
 -
    /**
     * {@inheritDoc}
     */
@@@ -420,180 -414,31 +420,189 @@@ public class AnnotationLabels extends J
        }
        else if (label.indexOf("Consensus") > -1)
        {
-         addConsensusMenu(pop, ann);
+         addConsensusMenuOptions(ap, aa[selectedRow], pop);
+         final JMenuItem consclipbrd = new JMenuItem(COPYCONS_SEQ);
+         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);
 +    }
 +  }
 +
 +  /**
-    * Adds context menu options for (alignment or group) Consensus annotation
+    * 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
+    * component AnnotationLabels), or wrapped mode (popup menu is shown on
+    * IdPanel when the mouse is over an annotation label).
     * 
-    * @param pop
+    * @param ap
     * @param ann
+    * @param pop
     */
-   protected void addConsensusMenu(JPopupMenu pop,
-           final AlignmentAnnotation ann)
+   static void addConsensusMenuOptions(AlignmentPanel ap,
+           AlignmentAnnotation ann,
+           JPopupMenu pop)
    {
-     final boolean isGroupAnnotation = ann.groupRef != null;
      pop.addSeparator();
  
      final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
@@@ -23,12 -23,11 +23,13 @@@ 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 jalview.viewmodel.AlignmentViewport;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -24,7 -24,8 +24,9 @@@ import jalview.analysis.AnnotationSorte
  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;
Simple merge
Simple merge
@@@ -214,9 -226,9 +228,10 @@@ public class WsParamSetManager implemen
          }
          paramFiles = paramFiles.concat(filename);
        }
-       jalview.bin.Cache.setProperty(WS_PARAM_FILES, paramFiles);
 -      Cache.setProperty("WS_PARAM_FILES", paramFiles);
 +
-       jalview.schemabinding.version2.WebServiceParameterSet paramxml = new jalview.schemabinding.version2.WebServiceParameterSet();
++      Cache.setProperty(WS_PARAM_FILES, paramFiles);
+       WebServiceParameterSet paramxml = new WebServiceParameterSet();
  
        paramxml.setName(parameterSet.getName());
        paramxml.setDescription(parameterSet.getDescription());
Simple merge
Simple merge
@@@ -87,33 -83,17 +87,43 @@@ public class StockholmFile extends Alig
    public static final Regex DETECT_BRACKETS = new Regex(
            "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
  
 +  /*
 +   * lookup table of Stockholm 'feature' (annotation) types
 +   * see http://sonnhammer.sbc.su.se/Stockholm.html
 +   */
 +  private static Map<String, String> featureTypes = null;
 +
+   // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
+   public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+   // 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]*$");
+   StringBuffer out; // output buffer
 -  AlignmentI al;
 +  static
 +  {
 +    featureTypes = new HashMap<>();
 +    featureTypes.put("SS", "Secondary Structure");
 +    featureTypes.put("SA", "Surface Accessibility");
 +    featureTypes.put("TM", "transmembrane");
 +    featureTypes.put("PP", "Posterior Probability");
 +    featureTypes.put("LI", "ligand binding");
 +    featureTypes.put("AS", "active site");
 +    featureTypes.put("IN", "intron");
 +    featureTypes.put("IR", "interacting residue");
 +    featureTypes.put("AC", "accession");
 +    featureTypes.put("OS", "organism");
 +    featureTypes.put("CL", "class");
 +    featureTypes.put("DE", "description");
 +    featureTypes.put("DR", "reference");
 +    featureTypes.put("LO", "look");
 +    featureTypes.put("RF", "Reference Positions");
 +  }
 +
 +  private AlignmentI al;
  
    public StockholmFile()
    {
                : label;
      }
      boolean ss = false, posterior = false;
-     type = typeToDescription(type);
+     type = id2type(type);
+     boolean isrnass = false;
++
      if (type.equalsIgnoreCase("secondary structure"))
      {
        ss = true;
      String ch = (annot == null)
              ? ((sequenceI == null) ? "-"
                      : Character.toString(sequenceI.getCharAt(k)))
-             : annot.displayCharacter;
+             : (annot.displayCharacter == null
+                     ? String.valueOf(annot.secondaryStructure)
+                     : annot.displayCharacter);
+     if (ch == null)
+     {
+       ch = " ";
+     }
      if (key != null && key.equals("SS"))
      {
+       char ssannotchar = ' ';
+       boolean charset = false;
        if (annot == null)
        {
 -        // sensible gap character
 -        ssannotchar = ' ';
 -        charset = true;
++        // TODO: TVA: Check against develop for correct behaviour!
 +        // Stockholm format requires underscore, not space
 +        return UNDERSCORE;
        }
        else
        {
      {
        seq = ch.charAt(1);
      }
-     return seq;
+     return (seq == ' ' && key != null && key.equals("SS") && isrna) ? '.'
+             : seq;
    }
  
 -  public String print()
 -  {
 -    out = new StringBuffer();
 -    out.append("# STOCKHOLM 1.0");
 -    out.append(newline);
 -    print(getSeqsAsArray(), false);
 -
 -    out.append("//");
 -    out.append(newline);
 -    return out.toString();
 -  }
 -
 -  private static Hashtable typeIds = null;
 -
 -  static
 -  {
 -    if (typeIds == null)
 -    {
 -      typeIds = new Hashtable();
 -      typeIds.put("SS", "Secondary Structure");
 -      typeIds.put("SA", "Surface Accessibility");
 -      typeIds.put("TM", "transmembrane");
 -      typeIds.put("PP", "Posterior Probability");
 -      typeIds.put("LI", "ligand binding");
 -      typeIds.put("AS", "active site");
 -      typeIds.put("IN", "intron");
 -      typeIds.put("IR", "interacting residue");
 -      typeIds.put("AC", "accession");
 -      typeIds.put("OS", "organism");
 -      typeIds.put("CL", "class");
 -      typeIds.put("DE", "description");
 -      typeIds.put("DR", "reference");
 -      typeIds.put("LO", "look");
 -      typeIds.put("RF", "Reference Positions");
 -
 -    }
 -  }
 -
 -  protected static String id2type(String id)
 -  {
 -    if (typeIds.containsKey(id))
 -    {
 -      return (String) typeIds.get(id);
 -    }
 -    System.err.println(
 -            "Warning : Unknown Stockholm annotation type code " + id);
 -    return id;
 -  }
 -
 -  protected static String type2id(String type)
 -  {
 -    String key = null;
 -    Enumeration e = typeIds.keys();
 -    while (e.hasMoreElements())
 -    {
 -      Object ll = e.nextElement();
 -      if (typeIds.get(ll).toString().equalsIgnoreCase(type))
 -      {
 -        key = (String) ll;
 -        break;
 -      }
 -    }
 -    if (key != null)
 -    {
 -      return key;
 -    }
 -    System.err.println(
 -            "Warning : Unknown Stockholm annotation type: " + type);
 -    return key;
 -  }
 -
    /**
     * make a friendly ID string.
     * 
@@@ -25,9 -27,8 +27,10 @@@ 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;
  
@@@ -271,17 -262,17 +273,17 @@@ public class GAlignFrame extends JInter
          saveAs_actionPerformed(e);
        }
      };
 -
 +  
      // FIXME getDefaultToolkit throws an exception in Headless mode
      KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.SHIFT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
              false);
      addMenuActionAndAccelerator(keyStroke, saveAs, al);
 -
 +  
      closeMenuItem.setText(MessageManager.getString("action.close"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
        }
      };
      addMenuActionAndAccelerator(keyStroke, unGroup, al);
 -
 +  
      copy.setText(MessageManager.getString("action.copy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
  
      al = new ActionListener()
      {
        }
      };
      addMenuActionAndAccelerator(keyStroke, copy, al);
 -
 +  
      cut.setText(MessageManager.getString("action.cut"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
          tabbedPane_focusGained(e);
        }
      });
 -
 +  
      JMenuItem save = new JMenuItem(MessageManager.getString("action.save"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
@@@ -23,10 -24,16 +24,17 @@@ import jalview.bin.Cache
  import jalview.fts.core.FTSDataColumnPreferences;
  import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
  import jalview.fts.service.pdb.PDBFTSRestClient;
+ import jalview.gui.Desktop;
+ import jalview.gui.JalviewBooleanRadioButtons;
+ import jalview.gui.JvOptionPane;
  import jalview.gui.JvSwingUtils;
  import jalview.gui.StructureViewer.ViewerType;
+ import jalview.io.BackupFilenameParts;
+ import jalview.io.BackupFiles;
+ import jalview.io.BackupFilesPresetEntry;
+ import jalview.io.IntKeyStringValueEntry;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -41,14 -48,16 +49,17 @@@ 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;
  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;
@@@ -275,33 -283,55 +293,72 @@@ public class GPreferences extends JPane
    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;
 +
 +  /*
-    * DAS Settings tab
+    * Web Services tab
     */
-   protected JPanel dasTab = new JPanel();
+   protected JPanel wsTab = new JPanel();
  
    /*
-    * Web Services tab
+    * Backups tab components
+    * a lot of these are member variables instead of local variables only so that they
+    * can be enabled/disabled easily in one go
     */
-   protected JPanel wsTab = new JPanel();
  
+   protected JCheckBox enableBackupFiles = new JCheckBox();
+   protected JPanel presetsPanel = new JPanel();
+   protected JLabel presetsComboLabel = new JLabel();
+   protected JCheckBox customiseCheckbox = new JCheckBox();
+   protected JButton revertButton = new JButton();
+   protected JComboBox<Object> backupfilesPresetsCombo = new JComboBox<>();
+   private int backupfilesPresetsComboLastSelected = 0;
+   protected JPanel suffixPanel = new JPanel();
+   protected JPanel keepfilesPanel = new JPanel();
  
+   protected JPanel exampleFilesPanel = new JPanel();
+   protected JTextField suffixTemplate = new JTextField(null, 8);
+   protected JLabel suffixTemplateLabel = new JLabel();
+   protected JLabel suffixDigitsLabel = new JLabel();
+   protected JSpinner suffixDigitsSpinner = new JSpinner();
+   protected JalviewBooleanRadioButtons suffixReverse = new JalviewBooleanRadioButtons();
+   protected JalviewBooleanRadioButtons backupfilesKeepAll = new JalviewBooleanRadioButtons();
+   public JSpinner backupfilesRollMaxSpinner = new JSpinner();
+   protected JLabel oldBackupFilesLabel = new JLabel();
+   protected JalviewBooleanRadioButtons backupfilesConfirmDelete = new JalviewBooleanRadioButtons();
+   protected JTextArea backupfilesExampleLabel = new JTextArea();
  
    /**
     * Creates a new GPreferences object.
      tabbedPane.add(initEditingTab(),
              MessageManager.getString("label.editing"));
  
 +    tabbedPane.add(initHMMERTab(), MessageManager.getString("label.hmmer"));
 +
      /*
-      * See DasSourceBrowser for the real work of configuring this tab.
-      */
-     dasTab.setLayout(new BorderLayout());
-     tabbedPane.add(dasTab, MessageManager.getString("label.das_settings"));
-     /*
       * See WsPreferences for the real work of configuring this tab.
       */
      wsTab.setLayout(new BorderLayout());
      }
  
    }
 +
 +  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();
 +  }
  }
index 0000000,0d2ec68..0ec8b97
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,6607 +1,6683 @@@
+ /*
+  * 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.project;
+ import static jalview.math.RotatableMatrix.Axis.X;
+ import static jalview.math.RotatableMatrix.Axis.Y;
+ import static jalview.math.RotatableMatrix.Axis.Z;
+ import jalview.analysis.Conservation;
+ import jalview.analysis.PCA;
+ import jalview.analysis.scoremodels.ScoreModels;
+ import jalview.analysis.scoremodels.SimilarityParams;
+ import jalview.api.FeatureColourI;
+ import jalview.api.ViewStyleI;
+ import jalview.api.analysis.ScoreModelI;
+ import jalview.api.analysis.SimilarityParamsI;
+ import jalview.api.structures.JalviewStructureDisplayI;
+ import jalview.bin.Cache;
+ import jalview.datamodel.AlignedCodonFrame;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.GraphLine;
++import jalview.datamodel.HiddenMarkovModel;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.Point;
+ import jalview.datamodel.RnaViewerModel;
+ import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.StructureViewerModel;
+ import jalview.datamodel.StructureViewerModel.StructureData;
+ import jalview.datamodel.features.FeatureMatcher;
+ import jalview.datamodel.features.FeatureMatcherI;
+ import jalview.datamodel.features.FeatureMatcherSet;
+ import jalview.datamodel.features.FeatureMatcherSetI;
+ import jalview.ext.varna.RnaModel;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.AlignViewport;
+ 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;
+ import jalview.gui.PaintRefresher;
+ import jalview.gui.SplitFrame;
+ import jalview.gui.StructureViewer;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.gui.StructureViewerBase;
+ 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;
+ import jalview.renderer.ResidueShaderI;
+ import jalview.schemes.AnnotationColourGradient;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemeProperty;
+ 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;
+ import jalview.util.Platform;
+ import jalview.util.StringUtils;
+ import jalview.util.jarInputStreamProvider;
+ import jalview.util.matcher.Condition;
+ import jalview.viewmodel.AlignmentViewport;
+ import jalview.viewmodel.PCAModel;
+ import jalview.viewmodel.ViewportRanges;
+ import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
+ import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
+ import jalview.ws.jws2.Jws2Discoverer;
+ 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;
+ import jalview.xml.binding.jalview.AlcodonFrame;
+ import jalview.xml.binding.jalview.AlcodonFrame.AlcodMap;
+ import jalview.xml.binding.jalview.Annotation;
+ import jalview.xml.binding.jalview.Annotation.ThresholdLine;
+ import jalview.xml.binding.jalview.AnnotationColourScheme;
+ import jalview.xml.binding.jalview.AnnotationElement;
+ import jalview.xml.binding.jalview.DoubleMatrix;
+ import jalview.xml.binding.jalview.DoubleVector;
+ import jalview.xml.binding.jalview.Feature;
+ import jalview.xml.binding.jalview.Feature.OtherData;
+ import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
+ import jalview.xml.binding.jalview.FilterBy;
+ import jalview.xml.binding.jalview.JalviewModel;
+ import jalview.xml.binding.jalview.JalviewModel.FeatureSettings;
+ import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Group;
+ import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Setting;
+ import jalview.xml.binding.jalview.JalviewModel.JGroup;
+ import jalview.xml.binding.jalview.JalviewModel.JSeq;
+ import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
+ import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
+ import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
+ import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
+ import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
+ import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
+ import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
+ import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
+ import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
+ import jalview.xml.binding.jalview.JalviewModel.Tree;
+ import jalview.xml.binding.jalview.JalviewModel.UserColours;
+ import jalview.xml.binding.jalview.JalviewModel.Viewport;
+ import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
+ import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
+ import jalview.xml.binding.jalview.JalviewUserColours;
+ import jalview.xml.binding.jalview.JalviewUserColours.Colour;
+ import jalview.xml.binding.jalview.MapListType.MapListFrom;
+ import jalview.xml.binding.jalview.MapListType.MapListTo;
+ import jalview.xml.binding.jalview.Mapping;
+ import jalview.xml.binding.jalview.NoValueColour;
+ import jalview.xml.binding.jalview.ObjectFactory;
+ import jalview.xml.binding.jalview.PcaDataType;
+ import jalview.xml.binding.jalview.Pdbentry.Property;
+ import jalview.xml.binding.jalview.Sequence;
+ import jalview.xml.binding.jalview.Sequence.DBRef;
+ import jalview.xml.binding.jalview.SequenceSet;
+ import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
+ import jalview.xml.binding.jalview.ThresholdType;
+ import jalview.xml.binding.jalview.VAMSAS;
+ import java.awt.Color;
+ import java.awt.Font;
+ import java.awt.Rectangle;
+ import java.io.BufferedReader;
+ import java.io.DataInputStream;
+ import java.io.DataOutputStream;
+ import java.io.File;
+ import java.io.FileInputStream;
+ import java.io.FileOutputStream;
+ import java.io.IOException;
+ 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;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Collections;
+ import java.util.Enumeration;
+ import java.util.GregorianCalendar;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.Hashtable;
+ import java.util.IdentityHashMap;
+ import java.util.Iterator;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import java.util.Set;
+ import java.util.Vector;
+ import java.util.jar.JarEntry;
+ import java.util.jar.JarInputStream;
+ import java.util.jar.JarOutputStream;
+ import javax.swing.JInternalFrame;
+ import javax.swing.SwingUtilities;
+ import javax.xml.bind.JAXBContext;
+ import javax.xml.bind.JAXBElement;
+ import javax.xml.bind.Marshaller;
+ import javax.xml.datatype.DatatypeConfigurationException;
+ import javax.xml.datatype.DatatypeFactory;
+ import javax.xml.datatype.XMLGregorianCalendar;
+ import javax.xml.stream.XMLInputFactory;
+ import javax.xml.stream.XMLStreamReader;
+ /**
+  * Write out the current jalview desktop state as a Jalview XML stream.
+  * 
+  * Note: the vamsas objects referred to here are primitive versions of the
+  * VAMSAS project schema elements - they are not the same and most likely never
+  * will be :)
+  * 
+  * @author $author$
+  * @version $Revision: 1.134 $
+  */
+ public class Jalview2XML
+ {
+   private static final String VIEWER_PREFIX = "viewer_";
+   private static final String RNA_PREFIX = "rna_";
++  private static final String HMMER_PREFIX = "hmmer_";
++
+   private static final String UTF_8 = "UTF-8";
+   /**
+    * prefix for recovering datasets for alignments with multiple views where
+    * non-existent dataset IDs were written for some views
+    */
+   private static final String UNIQSEQSETID = "uniqueSeqSetId.";
+   // use this with nextCounter() to make unique names for entities
+   private int counter = 0;
+   /*
+    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
+    * of sequence objects are created.
+    */
+   IdentityHashMap<SequenceI, String> seqsToIds = null;
+   /**
+    * jalview XML Sequence ID to jalview sequence object reference (both dataset
+    * and alignment sequences. Populated as XML reps of sequence objects are
+    * created.)
+    */
+   Map<String, SequenceI> seqRefIds = null;
+   Map<String, SequenceI> incompleteSeqs = null;
+   List<SeqFref> frefedSequence = null;
+   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
+   /*
+    * Map of reconstructed AlignFrame objects that appear to have come from
+    * SplitFrame objects (have a dna/protein complement view).
+    */
+   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<>();
+   /*
+    * Map from displayed rna structure models to their saved session state jar
+    * entry names
+    */
+   private Map<RnaModel, String> rnaSessions = new HashMap<>();
+   /**
+    * 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 b
+    * @return
+    */
+   public static boolean safeBoolean(Boolean b)
+   {
+     return b == null ? false : b.booleanValue();
+   }
+   /**
+    * A helper method for safely using the value of an optional attribute that
+    * may be null if not present in the XML. Answers the integer value, or zero
+    * if null.
+    * 
+    * @param i
+    * @return
+    */
+   public static int safeInt(Integer i)
+   {
+     return i == null ? 0 : i.intValue();
+   }
+   /**
+    * A helper method for safely using the value of an optional attribute that
+    * may be null if not present in the XML. Answers the float value, or zero if
+    * null.
+    * 
+    * @param f
+    * @return
+    */
+   public static float safeFloat(Float f)
+   {
+     return f == null ? 0f : f.floatValue();
+   }
+   /**
+    * create/return unique hash string for sq
+    * 
+    * @param sq
+    * @return new or existing unique string for sq
+    */
+   String seqHash(SequenceI sq)
+   {
+     if (seqsToIds == null)
+     {
+       initSeqRefs();
+     }
+     if (seqsToIds.containsKey(sq))
+     {
+       return seqsToIds.get(sq);
+     }
+     else
+     {
+       // create sequential key
+       String key = "sq" + (seqsToIds.size() + 1);
+       key = makeHashCode(sq, key); // check we don't have an external reference
+       // for it already.
+       seqsToIds.put(sq, key);
+       return key;
+     }
+   }
+   void initSeqRefs()
+   {
+     if (seqsToIds == null)
+     {
+       seqsToIds = new IdentityHashMap<>();
+     }
+     if (seqRefIds == null)
+     {
+       seqRefIds = new HashMap<>();
+     }
+     if (incompleteSeqs == null)
+     {
+       incompleteSeqs = new HashMap<>();
+     }
+     if (frefedSequence == null)
+     {
+       frefedSequence = new ArrayList<>();
+     }
+   }
+   public Jalview2XML()
+   {
+   }
+   public Jalview2XML(boolean raiseGUI)
+   {
+     this.raiseGUI = raiseGUI;
+   }
+   /**
+    * base class for resolving forward references to sequences by their ID
+    * 
+    * @author jprocter
+    *
+    */
+   abstract class SeqFref
+   {
+     String sref;
+     String type;
+     public SeqFref(String _sref, String type)
+     {
+       sref = _sref;
+       this.type = type;
+     }
+     public String getSref()
+     {
+       return sref;
+     }
+     public SequenceI getSrefSeq()
+     {
+       return seqRefIds.get(sref);
+     }
+     public boolean isResolvable()
+     {
+       return seqRefIds.get(sref) != null;
+     }
+     public SequenceI getSrefDatasetSeq()
+     {
+       SequenceI sq = seqRefIds.get(sref);
+       if (sq != null)
+       {
+         while (sq.getDatasetSequence() != null)
+         {
+           sq = sq.getDatasetSequence();
+         }
+       }
+       return sq;
+     }
+     /**
+      * @return true if the forward reference was fully resolved
+      */
+     abstract boolean resolve();
+     @Override
+     public String toString()
+     {
+       return type + " reference to " + sref;
+     }
+   }
+   /**
+    * create forward reference for a mapping
+    * 
+    * @param sref
+    * @param _jmap
+    * @return
+    */
+   public SeqFref newMappingRef(final String sref,
+           final jalview.datamodel.Mapping _jmap)
+   {
+     SeqFref fref = new SeqFref(sref, "Mapping")
+     {
+       public jalview.datamodel.Mapping jmap = _jmap;
+       @Override
+       boolean resolve()
+       {
+         SequenceI seq = getSrefDatasetSeq();
+         if (seq == null)
+         {
+           return false;
+         }
+         jmap.setTo(seq);
+         return true;
+       }
+     };
+     return fref;
+   }
+   public SeqFref newAlcodMapRef(final String sref,
+           final AlignedCodonFrame _cf,
+           final jalview.datamodel.Mapping _jmap)
+   {
+     SeqFref fref = new SeqFref(sref, "Codon Frame")
+     {
+       AlignedCodonFrame cf = _cf;
+       public jalview.datamodel.Mapping mp = _jmap;
+       @Override
+       public boolean isResolvable()
+       {
+         return super.isResolvable() && mp.getTo() != null;
+       };
+       @Override
+       boolean resolve()
+       {
+         SequenceI seq = getSrefDatasetSeq();
+         if (seq == null)
+         {
+           return false;
+         }
+         cf.addMap(seq, mp.getTo(), mp.getMap());
+         return true;
+       }
+     };
+     return fref;
+   }
+   public void resolveFrefedSequences()
+   {
+     Iterator<SeqFref> nextFref = frefedSequence.iterator();
+     int toresolve = frefedSequence.size();
+     int unresolved = 0, failedtoresolve = 0;
+     while (nextFref.hasNext())
+     {
+       SeqFref ref = nextFref.next();
+       if (ref.isResolvable())
+       {
+         try
+         {
+           if (ref.resolve())
+           {
+             nextFref.remove();
+           }
+           else
+           {
+             failedtoresolve++;
+           }
+         } catch (Exception x)
+         {
+           System.err.println(
+                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
+                           + ref.getSref());
+           x.printStackTrace();
+           failedtoresolve++;
+         }
+       }
+       else
+       {
+         unresolved++;
+       }
+     }
+     if (unresolved > 0)
+     {
+       System.err.println("Jalview Project Import: There were " + unresolved
+               + " forward references left unresolved on the stack.");
+     }
+     if (failedtoresolve > 0)
+     {
+       System.err.println("SERIOUS! " + failedtoresolve
+               + " resolvable forward references failed to resolve.");
+     }
+     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
+     {
+       System.err.println(
+               "Jalview Project Import: There are " + incompleteSeqs.size()
+                       + " sequences which may have incomplete metadata.");
+       if (incompleteSeqs.size() < 10)
+       {
+         for (SequenceI s : incompleteSeqs.values())
+         {
+           System.err.println(s.toString());
+         }
+       }
+       else
+       {
+         System.err.println(
+                 "Too many to report. Skipping output of incomplete sequences.");
+       }
+     }
+   }
+   /**
+    * This maintains a map of viewports, the key being the seqSetId. Important to
+    * set historyItem and redoList for multiple views
+    */
+   Map<String, AlignViewport> viewportsAdded = new HashMap<>();
+   Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
+   String uniqueSetSuffix = "";
+   /**
+    * List of pdbfiles added to Jar
+    */
+   List<String> pdbfiles = null;
+   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
+   public void saveState(File statefile)
+   {
+     FileOutputStream fos = null;
+     try
+     {
+       fos = new FileOutputStream(statefile);
+       JarOutputStream jout = new JarOutputStream(fos);
+       saveState(jout);
+       fos.close();
+     } catch (Exception e)
+     {
+       Cache.log.error("Couln't write Jalview state to " + statefile, e);
+       // TODO: inform user of the problem - they need to know if their data was
+       // not saved !
+       if (errorMessage == null)
+       {
+         errorMessage = "Did't write Jalview Archive to output file '"
+                 + statefile + "' - See console error log for details";
+       }
+       else
+       {
+         errorMessage += "(Didn't write Jalview Archive to output file '"
+                 + statefile + ")";
+       }
+       e.printStackTrace();
+     } finally
+     {
+       if (fos != null)
+       {
+         try
+         {
+           fos.close();
+         } catch (IOException e)
+         {
+           // ignore
+         }
+       }
+     }
+     reportErrors();
+   }
+   /**
+    * Writes a jalview project archive to the given Jar output stream.
+    * 
+    * @param jout
+    */
+   public void saveState(JarOutputStream jout)
+   {
+     AlignFrame[] frames = Desktop.getAlignFrames();
+     if (frames == null)
+     {
+       return;
+     }
+     saveAllFrames(Arrays.asList(frames), jout);
+   }
+   /**
+    * core method for storing state for a set of AlignFrames.
+    * 
+    * @param frames
+    *          - frames involving all data to be exported (including containing
+    *          splitframes)
+    * @param jout
+    *          - project output stream
+    */
+   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
+   {
+     Hashtable<String, AlignFrame> dsses = new Hashtable<>();
+     /*
+      * ensure cached data is clear before starting
+      */
+     // todo tidy up seqRefIds, seqsToIds initialisation / reset
+     rnaSessions.clear();
+     splitFrameCandidates.clear();
+     try
+     {
+       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
+       // //////////////////////////////////////////////////
+       List<String> shortNames = new ArrayList<>();
+       List<String> viewIds = new ArrayList<>();
+       // REVERSE ORDER
+       for (int i = frames.size() - 1; i > -1; i--)
+       {
+         AlignFrame af = frames.get(i);
+         // skip ?
+         if (skipList != null && skipList
+                 .containsKey(af.getViewport().getSequenceSetId()))
+         {
+           continue;
+         }
+         String shortName = makeFilename(af, shortNames);
+         int apSize = af.getAlignPanels().size();
+         for (int ap = 0; ap < apSize; ap++)
+         {
+           AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
+                   .get(ap);
+           String fileName = apSize == 1 ? shortName : ap + shortName;
+           if (!fileName.endsWith(".xml"))
+           {
+             fileName = fileName + ".xml";
+           }
+           saveState(apanel, fileName, jout, viewIds);
+           String dssid = getDatasetIdRef(
+                   af.getViewport().getAlignment().getDataset());
+           if (!dsses.containsKey(dssid))
+           {
+             dsses.put(dssid, af);
+           }
+         }
+       }
+       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
+               jout);
+       try
+       {
+         jout.flush();
+       } catch (Exception foo)
+       {
+       }
+       ;
+       jout.close();
+     } catch (Exception ex)
+     {
+       // TODO: inform user of the problem - they need to know if their data was
+       // not saved !
+       if (errorMessage == null)
+       {
+         errorMessage = "Couldn't write Jalview Archive - see error output for details";
+       }
+       ex.printStackTrace();
+     }
+   }
+   /**
+    * Generates a distinct file name, based on the title of the AlignFrame, by
+    * appending _n for increasing n until an unused name is generated. The new
+    * name (without its extension) is added to the list.
+    * 
+    * @param af
+    * @param namesUsed
+    * @return the generated name, with .xml extension
+    */
+   protected String makeFilename(AlignFrame af, List<String> namesUsed)
+   {
+     String shortName = af.getTitle();
+     if (shortName.indexOf(File.separatorChar) > -1)
+     {
+       shortName = shortName
+               .substring(shortName.lastIndexOf(File.separatorChar) + 1);
+     }
+     int count = 1;
+     while (namesUsed.contains(shortName))
+     {
+       if (shortName.endsWith("_" + (count - 1)))
+       {
+         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
+       }
+       shortName = shortName.concat("_" + count);
+       count++;
+     }
+     namesUsed.add(shortName);
+     if (!shortName.endsWith(".xml"))
+     {
+       shortName = shortName + ".xml";
+     }
+     return shortName;
+   }
+   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
+   public boolean saveAlignment(AlignFrame af, String jarFile,
+           String fileName)
+   {
+     try
+     {
+       // create backupfiles object and get new temp filename destination
+       BackupFiles backupfiles = new BackupFiles(jarFile);
+       FileOutputStream fos = new FileOutputStream(
+               backupfiles.getTempFilePath());
+       JarOutputStream jout = new JarOutputStream(fos);
+       List<AlignFrame> frames = new ArrayList<>();
+       // resolve splitframes
+       if (af.getViewport().getCodingComplement() != null)
+       {
+         frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
+       }
+       else
+       {
+         frames.add(af);
+       }
+       saveAllFrames(frames, jout);
+       try
+       {
+         jout.flush();
+       } catch (Exception foo)
+       {
+       }
+       ;
+       jout.close();
+       boolean success = true;
+       backupfiles.setWriteSuccess(success);
+       success = backupfiles.rollBackupsAndRenameTempFile();
+       return success;
+     } catch (Exception ex)
+     {
+       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
+       ex.printStackTrace();
+       return false;
+     }
+   }
+   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
+           String fileName, JarOutputStream jout)
+   {
+     for (String dssids : dsses.keySet())
+     {
+       AlignFrame _af = dsses.get(dssids);
+       String jfileName = fileName + " Dataset for " + _af.getTitle();
+       if (!jfileName.endsWith(".xml"))
+       {
+         jfileName = jfileName + ".xml";
+       }
+       saveState(_af.alignPanel, jfileName, true, jout, null);
+     }
+   }
+   /**
+    * create a JalviewModel from an alignment view and marshall it to a
+    * JarOutputStream
+    * 
+    * @param ap
+    *          panel to create jalview model for
+    * @param fileName
+    *          name of alignment panel written to output stream
+    * @param jout
+    *          jar output stream
+    * @param viewIds
+    * @param out
+    *          jar entry name
+    */
+   public JalviewModel saveState(AlignmentPanel ap, String fileName,
+           JarOutputStream jout, List<String> viewIds)
+   {
+     return saveState(ap, fileName, false, jout, viewIds);
+   }
+   /**
+    * create a JalviewModel from an alignment view and marshall it to a
+    * JarOutputStream
+    * 
+    * @param ap
+    *          panel to create jalview model for
+    * @param fileName
+    *          name of alignment panel written to output stream
+    * @param storeDS
+    *          when true, only write the dataset for the alignment, not the data
+    *          associated with the view.
+    * @param jout
+    *          jar output stream
+    * @param out
+    *          jar entry name
+    */
+   public JalviewModel saveState(AlignmentPanel ap, String fileName,
+           boolean storeDS, JarOutputStream jout, List<String> viewIds)
+   {
+     if (viewIds == null)
+     {
+       viewIds = new ArrayList<>();
+     }
+     initSeqRefs();
+     List<UserColourScheme> userColours = new ArrayList<>();
+     AlignViewport av = ap.av;
+     ViewportRanges vpRanges = av.getRanges();
+     final ObjectFactory objectFactory = new ObjectFactory();
+     JalviewModel object = objectFactory.createJalviewModel();
+     object.setVamsasModel(new VAMSAS());
+     // object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
+     try
+     {
+       GregorianCalendar c = new GregorianCalendar();
+       DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
+       XMLGregorianCalendar now = datatypeFactory.newXMLGregorianCalendar(c);// gregorianCalendar);
+       object.setCreationDate(now);
+     } catch (DatatypeConfigurationException e)
+     {
+       System.err.println("error writing date: " + e.toString());
+     }
+     object.setVersion(
+             jalview.bin.Cache.getDefault("VERSION", "Development Build"));
+     /**
+      * rjal is full height alignment, jal is actual alignment with full metadata
+      * but excludes hidden sequences.
+      */
+     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
+     if (av.hasHiddenRows())
+     {
+       rjal = jal.getHiddenSequences().getFullAlignment();
+     }
+     SequenceSet vamsasSet = new SequenceSet();
+     Sequence vamsasSeq;
+     // JalviewModelSequence jms = new JalviewModelSequence();
+     vamsasSet.setGapChar(jal.getGapCharacter() + "");
+     if (jal.getDataset() != null)
+     {
+       // dataset id is the dataset's hashcode
+       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
+       if (storeDS)
+       {
+         // switch jal and the dataset
+         jal = jal.getDataset();
+         rjal = jal;
+       }
+     }
+     if (jal.getProperties() != null)
+     {
+       Enumeration en = jal.getProperties().keys();
+       while (en.hasMoreElements())
+       {
+         String key = en.nextElement().toString();
+         SequenceSetProperties ssp = new SequenceSetProperties();
+         ssp.setKey(key);
+         ssp.setValue(jal.getProperties().get(key).toString());
+         // vamsasSet.addSequenceSetProperties(ssp);
+         vamsasSet.getSequenceSetProperties().add(ssp);
+       }
+     }
+     JSeq jseq;
+     Set<String> calcIdSet = new HashSet<>();
+     // record the set of vamsas sequence XML POJO we create.
+     HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
+     // SAVE SEQUENCES
+     for (final SequenceI jds : rjal.getSequences())
+     {
+       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
+               : jds.getDatasetSequence();
+       String id = seqHash(jds);
+       if (vamsasSetIds.get(id) == null)
+       {
+         if (seqRefIds.get(id) != null && !storeDS)
+         {
+           // This happens for two reasons: 1. multiple views are being
+           // serialised.
+           // 2. the hashCode has collided with another sequence's code. This
+           // DOES
+           // HAPPEN! (PF00072.15.stk does this)
+           // JBPNote: Uncomment to debug writing out of files that do not read
+           // back in due to ArrayOutOfBoundExceptions.
+           // System.err.println("vamsasSeq backref: "+id+"");
+           // System.err.println(jds.getName()+"
+           // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
+           // System.err.println("Hashcode: "+seqHash(jds));
+           // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
+           // System.err.println(rsq.getName()+"
+           // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
+           // System.err.println("Hashcode: "+seqHash(rsq));
+         }
+         else
+         {
+           vamsasSeq = createVamsasSequence(id, jds);
+ //          vamsasSet.addSequence(vamsasSeq);
+           vamsasSet.getSequence().add(vamsasSeq);
+           vamsasSetIds.put(id, vamsasSeq);
+           seqRefIds.put(id, jds);
+         }
+       }
+       jseq = new JSeq();
+       jseq.setStart(jds.getStart());
+       jseq.setEnd(jds.getEnd());
+       jseq.setColour(av.getSequenceColour(jds).getRGB());
+       jseq.setId(id); // jseq id should be a string not a number
+       if (!storeDS)
+       {
+         // Store any sequences this sequence represents
+         if (av.hasHiddenRows())
+         {
+           // use rjal, contains the full height alignment
+           jseq.setHidden(
+                   av.getAlignment().getHiddenSequences().isHidden(jds));
+           if (av.isHiddenRepSequence(jds))
+           {
+             jalview.datamodel.SequenceI[] reps = av
+                     .getRepresentedSequences(jds).getSequencesInOrder(rjal);
+             for (int h = 0; h < reps.length; h++)
+             {
+               if (reps[h] != jds)
+               {
+                 // jseq.addHiddenSequences(rjal.findIndex(reps[h]));
+                 jseq.getHiddenSequences().add(rjal.findIndex(reps[h]));
+               }
+             }
+           }
+         }
+         // mark sequence as reference - if it is the reference for this view
+         if (jal.hasSeqrep())
+         {
+           jseq.setViewreference(jds == jal.getSeqrep());
+         }
+       }
+       // TODO: omit sequence features from each alignment view's XML dump if we
+       // are storing dataset
+       List<SequenceFeature> sfs = jds.getSequenceFeatures();
+       for (SequenceFeature sf : sfs)
+       {
+         // Features features = new Features();
+         Feature features = new Feature();
+         features.setBegin(sf.getBegin());
+         features.setEnd(sf.getEnd());
+         features.setDescription(sf.getDescription());
+         features.setType(sf.getType());
+         features.setFeatureGroup(sf.getFeatureGroup());
+         features.setScore(sf.getScore());
+         if (sf.links != null)
+         {
+           for (int l = 0; l < sf.links.size(); l++)
+           {
+             OtherData keyValue = new OtherData();
+             keyValue.setKey("LINK_" + l);
+             keyValue.setValue(sf.links.elementAt(l).toString());
+             // features.addOtherData(keyValue);
+             features.getOtherData().add(keyValue);
+           }
+         }
+         if (sf.otherDetails != null)
+         {
+           /*
+            * save feature attributes, which may be simple strings or
+            * map valued (have sub-attributes)
+            */
+           for (Entry<String, Object> entry : sf.otherDetails.entrySet())
+           {
+             String key = entry.getKey();
+             Object value = entry.getValue();
+             if (value instanceof Map<?, ?>)
+             {
+               for (Entry<String, Object> subAttribute : ((Map<String, Object>) value)
+                       .entrySet())
+               {
+                 OtherData otherData = new OtherData();
+                 otherData.setKey(key);
+                 otherData.setKey2(subAttribute.getKey());
+                 otherData.setValue(subAttribute.getValue().toString());
+                 // features.addOtherData(otherData);
+                 features.getOtherData().add(otherData);
+               }
+             }
+             else
+             {
+               OtherData otherData = new OtherData();
+               otherData.setKey(key);
+               otherData.setValue(value.toString());
+               // features.addOtherData(otherData);
+               features.getOtherData().add(otherData);
+             }
+           }
+         }
+         // jseq.addFeatures(features);
+         jseq.getFeatures().add(features);
+       }
++      /*
++       * save PDB entries for sequence
++       */
+       if (jdatasq.getAllPDBEntries() != null)
+       {
+         Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
+         while (en.hasMoreElements())
+         {
+           Pdbids pdb = new Pdbids();
+           jalview.datamodel.PDBEntry entry = en.nextElement();
+           String pdbId = entry.getId();
+           pdb.setId(pdbId);
+           pdb.setType(entry.getType());
+           /*
+            * Store any structure views associated with this sequence. This
+            * section copes with duplicate entries in the project, so a dataset
+            * only view *should* be coped with sensibly.
+            */
+           // This must have been loaded, is it still visible?
+           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+           String matchedFile = null;
+           for (int f = frames.length - 1; f > -1; f--)
+           {
+             if (frames[f] instanceof StructureViewerBase)
+             {
+               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
+               matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds,
+                       matchedFile, viewFrame);
+               /*
+                * Only store each structure viewer's state once in the project
+                * jar. First time through only (storeDS==false)
+                */
+               String viewId = viewFrame.getViewId();
+               if (!storeDS && !viewIds.contains(viewId))
+               {
+                 viewIds.add(viewId);
+                 try
+                 {
+                   String viewerState = viewFrame.getStateInfo();
+                   writeJarEntry(jout, getViewerJarEntryName(viewId),
+                           viewerState.getBytes());
+                 } catch (IOException e)
+                 {
+                   System.err.println(
+                           "Error saving viewer state: " + e.getMessage());
+                 }
+               }
+             }
+           }
+           if (matchedFile != null || entry.getFile() != null)
+           {
+             if (entry.getFile() != null)
+             {
+               // use entry's file
+               matchedFile = entry.getFile();
+             }
+             pdb.setFile(matchedFile); // entry.getFile());
+             if (pdbfiles == null)
+             {
+               pdbfiles = new ArrayList<>();
+             }
+             if (!pdbfiles.contains(pdbId))
+             {
+               pdbfiles.add(pdbId);
+               copyFileToJar(jout, matchedFile, pdbId);
+             }
+           }
+           Enumeration<String> props = entry.getProperties();
+           if (props.hasMoreElements())
+           {
+             // PdbentryItem item = new PdbentryItem();
+             while (props.hasMoreElements())
+             {
+               Property prop = new Property();
+               String key = props.nextElement();
+               prop.setName(key);
+               prop.setValue(entry.getProperty(key).toString());
+               // item.addProperty(prop);
+               pdb.getProperty().add(prop);
+             }
+             // pdb.addPdbentryItem(item);
+           }
+           // jseq.addPdbids(pdb);
+           jseq.getPdbids().add(pdb);
+         }
+       }
+       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
++      if (jds.hasHMMProfile())
++      {
++        saveHmmerProfile(jout, jseq, jds);
++      }
++
+       // jms.addJSeq(jseq);
+       object.getJSeq().add(jseq);
+     }
+     if (!storeDS && av.hasHiddenRows())
+     {
+       jal = av.getAlignment();
+     }
+     // SAVE MAPPINGS
+     // FOR DATASET
+     if (storeDS && jal.getCodonFrames() != null)
+     {
+       List<AlignedCodonFrame> jac = jal.getCodonFrames();
+       for (AlignedCodonFrame acf : jac)
+       {
+         AlcodonFrame alc = new AlcodonFrame();
+         if (acf.getProtMappings() != null
+                 && acf.getProtMappings().length > 0)
+         {
+           boolean hasMap = false;
+           SequenceI[] dnas = acf.getdnaSeqs();
+           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
+           for (int m = 0; m < pmaps.length; m++)
+           {
+             AlcodMap alcmap = new AlcodMap();
+             alcmap.setDnasq(seqHash(dnas[m]));
+             alcmap.setMapping(
+                     createVamsasMapping(pmaps[m], dnas[m], null, false));
+             // alc.addAlcodMap(alcmap);
+             alc.getAlcodMap().add(alcmap);
+             hasMap = true;
+           }
+           if (hasMap)
+           {
+             // vamsasSet.addAlcodonFrame(alc);
+             vamsasSet.getAlcodonFrame().add(alc);
+           }
+         }
+         // TODO: delete this ? dead code from 2.8.3->2.9 ?
+         // {
+         // AlcodonFrame alc = new AlcodonFrame();
+         // vamsasSet.addAlcodonFrame(alc);
+         // for (int p = 0; p < acf.aaWidth; p++)
+         // {
+         // Alcodon cmap = new Alcodon();
+         // if (acf.codons[p] != null)
+         // {
+         // // Null codons indicate a gapped column in the translated peptide
+         // // alignment.
+         // cmap.setPos1(acf.codons[p][0]);
+         // cmap.setPos2(acf.codons[p][1]);
+         // cmap.setPos3(acf.codons[p][2]);
+         // }
+         // alc.addAlcodon(cmap);
+         // }
+         // if (acf.getProtMappings() != null
+         // && acf.getProtMappings().length > 0)
+         // {
+         // SequenceI[] dnas = acf.getdnaSeqs();
+         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
+         // for (int m = 0; m < pmaps.length; m++)
+         // {
+         // AlcodMap alcmap = new AlcodMap();
+         // alcmap.setDnasq(seqHash(dnas[m]));
+         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
+         // false));
+         // alc.addAlcodMap(alcmap);
+         // }
+         // }
+       }
+     }
+     // SAVE TREES
+     // /////////////////////////////////
+     if (!storeDS && av.getCurrentTree() != null)
+     {
+       // FIND ANY ASSOCIATED TREES
+       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
+       if (Desktop.desktop != null)
+       {
+         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+         for (int t = 0; t < frames.length; t++)
+         {
+           if (frames[t] instanceof TreePanel)
+           {
+             TreePanel tp = (TreePanel) frames[t];
+             if (tp.getTreeCanvas().getViewport().getAlignment() == jal)
+             {
+               JalviewModel.Tree tree = new JalviewModel.Tree();
+               tree.setTitle(tp.getTitle());
+               tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
+               tree.setNewick(tp.getTree().print());
+               tree.setThreshold(tp.getTreeCanvas().getThreshold());
+               tree.setFitToWindow(tp.fitToWindow.getState());
+               tree.setFontName(tp.getTreeFont().getName());
+               tree.setFontSize(tp.getTreeFont().getSize());
+               tree.setFontStyle(tp.getTreeFont().getStyle());
+               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
+               tree.setShowBootstrap(tp.bootstrapMenu.getState());
+               tree.setShowDistances(tp.distanceMenu.getState());
+               tree.setHeight(tp.getHeight());
+               tree.setWidth(tp.getWidth());
+               tree.setXpos(tp.getX());
+               tree.setYpos(tp.getY());
+               tree.setId(makeHashCode(tp, null));
+               tree.setLinkToAllViews(
+                       tp.getTreeCanvas().isApplyToAllViews());
+               // jms.addTree(tree);
+               object.getTree().add(tree);
+             }
+           }
+         }
+       }
+     }
+     /*
+      * save PCA viewers
+      */
+     if (!storeDS && Desktop.desktop != null)
+     {
+       for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+       {
+         if (frame instanceof PCAPanel)
+         {
+           PCAPanel panel = (PCAPanel) frame;
+           if (panel.getAlignViewport().getAlignment() == jal)
+           {
+             savePCA(panel, object);
+           }
+         }
+       }
+     }
+     // SAVE ANNOTATIONS
+     /**
+      * store forward refs from an annotationRow to any groups
+      */
+     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<>();
+     if (storeDS)
+     {
+       for (SequenceI sq : jal.getSequences())
+       {
+         // Store annotation on dataset sequences only
+         AlignmentAnnotation[] aa = sq.getAnnotation();
+         if (aa != null && aa.length > 0)
+         {
+           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
+                   vamsasSet);
+         }
+       }
+     }
+     else
+     {
+       if (jal.getAlignmentAnnotation() != null)
+       {
+         // Store the annotation shown on the alignment.
+         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
+         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
+                 vamsasSet);
+       }
+     }
+     // SAVE GROUPS
+     if (jal.getGroups() != null)
+     {
+       JGroup[] groups = new JGroup[jal.getGroups().size()];
+       int i = -1;
+       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
+       {
+         JGroup jGroup = new JGroup();
+         groups[++i] = jGroup;
+         jGroup.setStart(sg.getStartRes());
+         jGroup.setEnd(sg.getEndRes());
+         jGroup.setName(sg.getName());
+         if (groupRefs.containsKey(sg))
+         {
+           // group has references so set its ID field
+           jGroup.setId(groupRefs.get(sg));
+         }
+         ColourSchemeI colourScheme = sg.getColourScheme();
+         if (colourScheme != null)
+         {
+           ResidueShaderI groupColourScheme = sg.getGroupColourScheme();
+           if (groupColourScheme.conservationApplied())
+           {
+             jGroup.setConsThreshold(groupColourScheme.getConservationInc());
+             if (colourScheme instanceof jalview.schemes.UserColourScheme)
+             {
+               jGroup.setColour(
+                       setUserColourScheme(colourScheme, userColours,
+                               object));
+             }
+             else
+             {
+               jGroup.setColour(colourScheme.getSchemeName());
+             }
+           }
+           else if (colourScheme instanceof jalview.schemes.AnnotationColourGradient)
+           {
+             jGroup.setColour("AnnotationColourGradient");
+             jGroup.setAnnotationColours(constructAnnotationColours(
+                     (jalview.schemes.AnnotationColourGradient) colourScheme,
+                     userColours, object));
+           }
+           else if (colourScheme instanceof jalview.schemes.UserColourScheme)
+           {
+             jGroup.setColour(
+                     setUserColourScheme(colourScheme, userColours, object));
+           }
+           else
+           {
+             jGroup.setColour(colourScheme.getSchemeName());
+           }
+           jGroup.setPidThreshold(groupColourScheme.getThreshold());
+         }
+         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
+         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
+         jGroup.setDisplayText(sg.getDisplayText());
+         jGroup.setColourText(sg.getColourText());
+         jGroup.setTextCol1(sg.textColour.getRGB());
+         jGroup.setTextCol2(sg.textColour2.getRGB());
+         jGroup.setTextColThreshold(sg.thresholdTextColour);
+         jGroup.setShowUnconserved(sg.getShowNonconserved());
 -        jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
++        jGroup.setIgnoreGapsinConsensus(sg.isIgnoreGapsConsensus());
+         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
+         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
+         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
+         for (SequenceI seq : sg.getSequences())
+         {
+           // jGroup.addSeq(seqHash(seq));
+           jGroup.getSeq().add(seqHash(seq));
+         }
+       }
+       //jms.setJGroup(groups);
+       Object group;
+       for (JGroup grp : groups)
+       {
+         object.getJGroup().add(grp);
+       }
+     }
+     if (!storeDS)
+     {
+       // /////////SAVE VIEWPORT
+       Viewport view = new Viewport();
+       view.setTitle(ap.alignFrame.getTitle());
+       view.setSequenceSetId(
+               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
+       view.setId(av.getViewId());
+       if (av.getCodingComplement() != null)
+       {
+         view.setComplementId(av.getCodingComplement().getViewId());
+       }
+       view.setViewName(av.getViewName());
+       view.setGatheredViews(av.isGatherViewsHere());
+       Rectangle size = ap.av.getExplodedGeometry();
+       Rectangle position = size;
+       if (size == null)
+       {
+         size = ap.alignFrame.getBounds();
+         if (av.getCodingComplement() != null)
+         {
+           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
+                   .getBounds();
+         }
+         else
+         {
+           position = size;
+         }
+       }
+       view.setXpos(position.x);
+       view.setYpos(position.y);
+       view.setWidth(size.width);
+       view.setHeight(size.height);
+       view.setStartRes(vpRanges.getStartRes());
+       view.setStartSeq(vpRanges.getStartSeq());
+       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
+       {
+         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
+                 userColours, object));
+       }
+       else if (av
+               .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
+       {
+         AnnotationColourScheme ac = constructAnnotationColours(
+                 (jalview.schemes.AnnotationColourGradient) av
+                         .getGlobalColourScheme(),
+                 userColours, object);
+         view.setAnnotationColours(ac);
+         view.setBgColour("AnnotationColourGradient");
+       }
+       else
+       {
+         view.setBgColour(ColourSchemeProperty
+                 .getColourName(av.getGlobalColourScheme()));
+       }
+       ResidueShaderI vcs = av.getResidueShading();
+       ColourSchemeI cs = av.getGlobalColourScheme();
+       if (cs != null)
+       {
+         if (vcs.conservationApplied())
+         {
+           view.setConsThreshold(vcs.getConservationInc());
+           if (cs instanceof jalview.schemes.UserColourScheme)
+           {
+             view.setBgColour(setUserColourScheme(cs, userColours, object));
+           }
+         }
+         view.setPidThreshold(vcs.getThreshold());
+       }
+       view.setConservationSelected(av.getConservationSelected());
+       view.setPidSelected(av.getAbovePIDThreshold());
+       final Font font = av.getFont();
+       view.setFontName(font.getName());
+       view.setFontSize(font.getSize());
+       view.setFontStyle(font.getStyle());
+       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
+       view.setRenderGaps(av.isRenderGaps());
+       view.setShowAnnotation(av.isShowAnnotation());
+       view.setShowBoxes(av.getShowBoxes());
+       view.setShowColourText(av.getColourText());
+       view.setShowFullId(av.getShowJVSuffix());
+       view.setRightAlignIds(av.isRightAlignIds());
+       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
+       view.setShowText(av.getShowText());
+       view.setShowUnconserved(av.getShowUnconserved());
+       view.setWrapAlignment(av.getWrapAlignment());
+       view.setTextCol1(av.getTextColour().getRGB());
+       view.setTextCol2(av.getTextColour2().getRGB());
+       view.setTextColThreshold(av.getThresholdTextColour());
+       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
+       view.setShowSequenceLogo(av.isShowSequenceLogo());
+       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
+       view.setShowGroupConsensus(av.isShowGroupConsensus());
+       view.setShowGroupConservation(av.isShowGroupConservation());
+       view.setShowNPfeatureTooltip(av.isShowNPFeats());
+       view.setShowDbRefTooltip(av.isShowDBRefs());
+       view.setFollowHighlight(av.isFollowHighlight());
+       view.setFollowSelection(av.followSelection);
+       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
+       if (av.getFeaturesDisplayed() != null)
+       {
+         FeatureSettings fs = new FeatureSettings();
+         FeatureRenderer fr = ap.getSeqPanel().seqCanvas
+                 .getFeatureRenderer();
+         String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
+         Vector<String> settingsAdded = new Vector<>();
+         if (renderOrder != null)
+         {
+           for (String featureType : renderOrder)
+           {
+             FeatureSettings.Setting setting = new FeatureSettings.Setting();
+             setting.setType(featureType);
+             /*
+              * save any filter for the feature type
+              */
+             FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
+             if (filter != null)  {
+               Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
+               FeatureMatcherI firstFilter = filters.next();
+               setting.setMatcherSet(Jalview2XML.marshalFilter(
+                       firstFilter, filters, filter.isAnded()));
+             }
+             /*
+              * save colour scheme for the feature type
+              */
+             FeatureColourI fcol = fr.getFeatureStyle(featureType);
+             if (!fcol.isSimpleColour())
+             {
+               setting.setColour(fcol.getMaxColour().getRGB());
+               setting.setMincolour(fcol.getMinColour().getRGB());
+               setting.setMin(fcol.getMin());
+               setting.setMax(fcol.getMax());
+               setting.setColourByLabel(fcol.isColourByLabel());
+               if (fcol.isColourByAttribute())
+               {
+                 String[] attName = fcol.getAttributeName();
+                 setting.getAttributeName().add(attName[0]);
+                 if (attName.length > 1)
+                 {
+                   setting.getAttributeName().add(attName[1]);
+                 }
+               }
+               setting.setAutoScale(fcol.isAutoScaled());
+               setting.setThreshold(fcol.getThreshold());
+               Color noColour = fcol.getNoColour();
+               if (noColour == null)
+               {
+                 setting.setNoValueColour(NoValueColour.NONE);
+               }
+               else if (noColour.equals(fcol.getMaxColour()))
+               {
+                 setting.setNoValueColour(NoValueColour.MAX);
+               }
+               else
+               {
+                 setting.setNoValueColour(NoValueColour.MIN);
+               }
+               // -1 = No threshold, 0 = Below, 1 = Above
+               setting.setThreshstate(fcol.isAboveThreshold() ? 1
+                       : (fcol.isBelowThreshold() ? 0 : -1));
+             }
+             else
+             {
+               setting.setColour(fcol.getColour().getRGB());
+             }
+             setting.setDisplay(
+                     av.getFeaturesDisplayed().isVisible(featureType));
+             float rorder = fr
+                     .getOrder(featureType);
+             if (rorder > -1)
+             {
+               setting.setOrder(rorder);
+             }
+             /// fs.addSetting(setting);
+             fs.getSetting().add(setting);
+             settingsAdded.addElement(featureType);
+           }
+         }
+         // is groups actually supposed to be a map here ?
+         Iterator<String> en = fr.getFeatureGroups().iterator();
+         Vector<String> groupsAdded = new Vector<>();
+         while (en.hasNext())
+         {
+           String grp = en.next();
+           if (groupsAdded.contains(grp))
+           {
+             continue;
+           }
+           Group g = new Group();
+           g.setName(grp);
+           g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
+                           .booleanValue());
+           // fs.addGroup(g);
+           fs.getGroup().add(g);
+           groupsAdded.addElement(grp);
+         }
+         // jms.setFeatureSettings(fs);
+         object.setFeatureSettings(fs);
+       }
+       if (av.hasHiddenColumns())
+       {
+         jalview.datamodel.HiddenColumns hidden = av.getAlignment()
+                 .getHiddenColumns();
+         if (hidden == null)
+         {
+           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
+         }
+         else
+         {
+           Iterator<int[]> hiddenRegions = hidden.iterator();
+           while (hiddenRegions.hasNext())
+           {
+             int[] region = hiddenRegions.next();
+             HiddenColumns hc = new HiddenColumns();
+             hc.setStart(region[0]);
+             hc.setEnd(region[1]);
+             // view.addHiddenColumns(hc);
+             view.getHiddenColumns().add(hc);
+           }
+         }
+       }
+       if (calcIdSet.size() > 0)
+       {
+         for (String calcId : calcIdSet)
+         {
+           if (calcId.trim().length() > 0)
+           {
+             CalcIdParam cidp = createCalcIdParam(calcId, av);
+             // Some calcIds have no parameters.
+             if (cidp != null)
+             {
+               // view.addCalcIdParam(cidp);
+               view.getCalcIdParam().add(cidp);
+             }
+           }
+         }
+       }
+       // jms.addViewport(view);
+       object.getViewport().add(view);
+     }
+     // object.setJalviewModelSequence(jms);
+     // object.getVamsasModel().addSequenceSet(vamsasSet);
+     object.getVamsasModel().getSequenceSet().add(vamsasSet);
+     if (jout != null && fileName != null)
+     {
+       // We may not want to write the object to disk,
+       // eg we can copy the alignViewport to a new view object
+       // using save and then load
+       try
+       {
+         System.out.println("Writing jar entry " + fileName);
+         JarEntry entry = new JarEntry(fileName);
+         jout.putNextEntry(entry);
+         PrintWriter pout = new PrintWriter(
+                 new OutputStreamWriter(jout, UTF_8));
+         JAXBContext jaxbContext = JAXBContext
+                 .newInstance(JalviewModel.class);
+         Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+         // output pretty printed
+         // jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+         jaxbMarshaller.marshal(
+                 new ObjectFactory().createJalviewModel(object), pout);
+         // jaxbMarshaller.marshal(object, pout);
+         // marshaller.marshal(object);
+         pout.flush();
+         jout.closeEntry();
+       } catch (Exception ex)
+       {
+         // TODO: raise error in GUI if marshalling failed.
+         System.err.println("Error writing Jalview project");
+         ex.printStackTrace();
+       }
+     }
+     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.
+    */
+   protected void savePCA(PCAPanel panel, JalviewModel object)
+   {
+     try
+     {
+       PcaViewer viewer = new PcaViewer();
+       viewer.setHeight(panel.getHeight());
+       viewer.setWidth(panel.getWidth());
+       viewer.setXpos(panel.getX());
+       viewer.setYpos(panel.getY());
+       viewer.setTitle(panel.getTitle());
+       PCAModel pcaModel = panel.getPcaModel();
+       viewer.setScoreModelName(pcaModel.getScoreModelName());
+       viewer.setXDim(panel.getSelectedDimensionIndex(X));
+       viewer.setYDim(panel.getSelectedDimensionIndex(Y));
+       viewer.setZDim(panel.getSelectedDimensionIndex(Z));
+       viewer.setBgColour(
+               panel.getRotatableCanvas().getBackgroundColour().getRGB());
+       viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
+       float[] spMin = panel.getRotatableCanvas().getSeqMin();
+       SeqPointMin spmin = new SeqPointMin();
+       spmin.setXPos(spMin[0]);
+       spmin.setYPos(spMin[1]);
+       spmin.setZPos(spMin[2]);
+       viewer.setSeqPointMin(spmin);
+       float[] spMax = panel.getRotatableCanvas().getSeqMax();
+       SeqPointMax spmax = new SeqPointMax();
+       spmax.setXPos(spMax[0]);
+       spmax.setYPos(spMax[1]);
+       spmax.setZPos(spMax[2]);
+       viewer.setSeqPointMax(spmax);
+       viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
+       viewer.setLinkToAllViews(
+               panel.getRotatableCanvas().isApplyToAllViews());
+       SimilarityParamsI sp = pcaModel.getSimilarityParameters();
+       viewer.setIncludeGaps(sp.includeGaps());
+       viewer.setMatchGaps(sp.matchGaps());
+       viewer.setIncludeGappedColumns(sp.includeGappedColumns());
+       viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
+       /*
+        * sequence points on display
+        */
+       for (jalview.datamodel.SequencePoint spt : pcaModel
+               .getSequencePoints())
+       {
+         SequencePoint point = new SequencePoint();
+         point.setSequenceRef(seqHash(spt.getSequence()));
+         point.setXPos(spt.coord.x);
+         point.setYPos(spt.coord.y);
+         point.setZPos(spt.coord.z);
+         viewer.getSequencePoint().add(point);
+       }
+       /*
+        * (end points of) axes on display
+        */
+       for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
+       {
+         Axis axis = new Axis();
+         axis.setXPos(p.x);
+         axis.setYPos(p.y);
+         axis.setZPos(p.z);
+         viewer.getAxis().add(axis);
+       }
+       /*
+        * raw PCA data (note we are not restoring PCA inputs here -
+        * alignment view, score model, similarity parameters)
+        */
+       PcaDataType data = new PcaDataType();
+       viewer.setPcaData(data);
+       PCA pca = pcaModel.getPcaData();
+       DoubleMatrix pm = new DoubleMatrix();
+       saveDoubleMatrix(pca.getPairwiseScores(), pm);
+       data.setPairwiseMatrix(pm);
+       DoubleMatrix tm = new DoubleMatrix();
+       saveDoubleMatrix(pca.getTridiagonal(), tm);
+       data.setTridiagonalMatrix(tm);
+       DoubleMatrix eigenMatrix = new DoubleMatrix();
+       data.setEigenMatrix(eigenMatrix);
+       saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
+       object.getPcaViewer().add(viewer);
+     } catch (Throwable t)
+     {
+       Cache.log.error("Error saving PCA: " + t.getMessage());
+     }
+   }
+   /**
+    * Stores values from a matrix into an XML element, including (if present) the
+    * D or E vectors
+    * 
+    * @param m
+    * @param xmlMatrix
+    * @see #loadDoubleMatrix(DoubleMatrix)
+    */
+   protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
+   {
+     xmlMatrix.setRows(m.height());
+     xmlMatrix.setColumns(m.width());
+     for (int i = 0; i < m.height(); i++)
+     {
+       DoubleVector row = new DoubleVector();
+       for (int j = 0; j < m.width(); j++)
+       {
+         row.getV().add(m.getValue(i, j));
+       }
+       xmlMatrix.getRow().add(row);
+     }
+     if (m.getD() != null)
+     {
+       DoubleVector dVector = new DoubleVector();
+       for (double d : m.getD())
+       {
+         dVector.getV().add(d);
+       }
+       xmlMatrix.setD(dVector);
+     }
+     if (m.getE() != null)
+     {
+       DoubleVector eVector = new DoubleVector();
+       for (double e : m.getE())
+       {
+         eVector.getV().add(e);
+       }
+       xmlMatrix.setE(eVector);
+     }
+   }
+   /**
+    * Loads XML matrix data into a new Matrix object, including the D and/or E
+    * vectors (if present)
+    * 
+    * @param mData
+    * @return
+    * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
+    */
+   protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
+   {
+     int rows = mData.getRows();
+     double[][] vals = new double[rows][];
+     for (int i = 0; i < rows; i++)
+     {
+       List<Double> dVector = mData.getRow().get(i).getV();
+       vals[i] = new double[dVector.size()];
+       int dvi = 0;
+       for (Double d : dVector)
+       {
+         vals[i][dvi++] = d;
+       }
+     }
+     MatrixI m = new Matrix(vals);
+     if (mData.getD() != null)
+     {
+       List<Double> dVector = mData.getD().getV();
+       double[] vec = new double[dVector.size()];
+       int dvi = 0;
+       for (Double d : dVector)
+       {
+         vec[dvi++] = d;
+       }
+       m.setD(vec);
+     }
+     if (mData.getE() != null)
+     {
+       List<Double> dVector = mData.getE().getV();
+       double[] vec = new double[dVector.size()];
+       int dvi = 0;
+       for (Double d : dVector)
+       {
+         vec[dvi++] = d;
+       }
+       m.setE(vec);
+     }
+     return m;
+   }
+   /**
+    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
+    * for each viewer, with
+    * <ul>
+    * <li>viewer geometry (position, size, split pane divider location)</li>
+    * <li>index of the selected structure in the viewer (currently shows gapped
+    * or ungapped)</li>
+    * <li>the id of the annotation holding RNA secondary structure</li>
+    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
+    * </ul>
+    * Varna viewer state is also written out (in native Varna XML) to separate
+    * project jar entries. A separate entry is written for each RNA structure
+    * displayed, with the naming convention
+    * <ul>
+    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
+    * </ul>
+    * 
+    * @param jout
+    * @param jseq
+    * @param jds
+    * @param viewIds
+    * @param ap
+    * @param storeDataset
+    */
+   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
+           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
+           boolean storeDataset)
+   {
+     if (Desktop.desktop == null)
+     {
+       return;
+     }
+     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+     for (int f = frames.length - 1; f > -1; f--)
+     {
+       if (frames[f] instanceof AppVarna)
+       {
+         AppVarna varna = (AppVarna) frames[f];
+         /*
+          * link the sequence to every viewer that is showing it and is linked to
+          * its alignment panel
+          */
+         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
+         {
+           String viewId = varna.getViewId();
+           RnaViewer rna = new RnaViewer();
+           rna.setViewId(viewId);
+           rna.setTitle(varna.getTitle());
+           rna.setXpos(varna.getX());
+           rna.setYpos(varna.getY());
+           rna.setWidth(varna.getWidth());
+           rna.setHeight(varna.getHeight());
+           rna.setDividerLocation(varna.getDividerLocation());
+           rna.setSelectedRna(varna.getSelectedIndex());
+           // jseq.addRnaViewer(rna);
+           jseq.getRnaViewer().add(rna);
+           /*
+            * Store each Varna panel's state once in the project per sequence.
+            * First time through only (storeDataset==false)
+            */
+           // boolean storeSessions = false;
+           // String sequenceViewId = viewId + seqsToIds.get(jds);
+           // if (!storeDataset && !viewIds.contains(sequenceViewId))
+           // {
+           // viewIds.add(sequenceViewId);
+           // storeSessions = true;
+           // }
+           for (RnaModel model : varna.getModels())
+           {
+             if (model.seq == jds)
+             {
+               /*
+                * VARNA saves each view (sequence or alignment secondary
+                * structure, gapped or trimmed) as a separate XML file
+                */
+               String jarEntryName = rnaSessions.get(model);
+               if (jarEntryName == null)
+               {
+                 String varnaStateFile = varna.getStateInfo(model.rna);
+                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
+                 copyFileToJar(jout, varnaStateFile, jarEntryName);
+                 rnaSessions.put(model, jarEntryName);
+               }
+               SecondaryStructure ss = new SecondaryStructure();
+               String annotationId = varna.getAnnotation(jds).annotationId;
+               ss.setAnnotationId(annotationId);
+               ss.setViewerState(jarEntryName);
+               ss.setGapped(model.gapped);
+               ss.setTitle(model.title);
+               // rna.addSecondaryStructure(ss);
+               rna.getSecondaryStructure().add(ss);
+             }
+           }
+         }
+       }
+     }
+   }
+   /**
+    * Copy the contents of a file to a new entry added to the output jar
+    * 
+    * @param jout
+    * @param infilePath
+    * @param jarEntryName
+    */
+   protected void copyFileToJar(JarOutputStream jout, String infilePath,
+           String jarEntryName)
+   {
+     DataInputStream dis = null;
+     try
+     {
+       File file = new File(infilePath);
+       if (file.exists() && jout != null)
+       {
+         dis = new DataInputStream(new FileInputStream(file));
+         byte[] data = new byte[(int) file.length()];
+         dis.readFully(data);
+         writeJarEntry(jout, jarEntryName, data);
+       }
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
+     } finally
+     {
+       if (dis != null)
+       {
+         try
+         {
+           dis.close();
+         } catch (IOException e)
+         {
+           // ignore
+         }
+       }
+     }
+   }
+   /**
+    * Write the data to a new entry of given name in the output jar file
+    * 
+    * @param jout
+    * @param jarEntryName
+    * @param data
+    * @throws IOException
+    */
+   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
+           byte[] data) throws IOException
+   {
+     if (jout != null)
+     {
+       System.out.println("Writing jar entry " + jarEntryName);
+       jout.putNextEntry(new JarEntry(jarEntryName));
+       DataOutputStream dout = new DataOutputStream(jout);
+       dout.write(data, 0, data.length);
+       dout.flush();
+       jout.closeEntry();
+     }
+   }
+   /**
+    * Save the state of a structure viewer
+    * 
+    * @param ap
+    * @param jds
+    * @param pdb
+    *          the archive XML element under which to save the state
+    * @param entry
+    * @param viewIds
+    * @param matchedFile
+    * @param viewFrame
+    * @return
+    */
+   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
+           Pdbids pdb, PDBEntry entry, List<String> viewIds,
+           String matchedFile, StructureViewerBase viewFrame)
+   {
+     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
+     /*
+      * Look for any bindings for this viewer to the PDB file of interest
+      * (including part matches excluding chain id)
+      */
+     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
+     {
+       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
+       final String pdbId = pdbentry.getId();
+       if (!pdbId.equals(entry.getId())
+               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
+                       .startsWith(pdbId.toLowerCase())))
+       {
+         /*
+          * not interested in a binding to a different PDB entry here
+          */
+         continue;
+       }
+       if (matchedFile == null)
+       {
+         matchedFile = pdbentry.getFile();
+       }
+       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());
++        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
+       // can get at it if the ID
+       // match is ambiguous (e.g.
+       // 1QIP==1qipA)
+       for (int smap = 0; smap < viewFrame.getBinding()
+               .getSequence()[peid].length; smap++)
+       {
+         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
+         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
+         {
+           StructureState state = new StructureState();
+           state.setVisible(true);
+           state.setXpos(viewFrame.getX());
+           state.setYpos(viewFrame.getY());
+           state.setWidth(viewFrame.getWidth());
+           state.setHeight(viewFrame.getHeight());
+           final String viewId = viewFrame.getViewId();
+           state.setViewId(viewId);
+           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
+           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+           state.setColourByJmol(viewFrame.isColouredByViewer());
+           state.setType(viewFrame.getViewerType().toString());
+           // pdb.addStructureState(state);
+           pdb.getStructureState().add(state);
+         }
+       }
+     }
+     return matchedFile;
+   }
+   /**
+    * Populates the AnnotationColourScheme xml for save. This captures the
+    * settings of the options in the 'Colour by Annotation' dialog.
+    * 
+    * @param acg
+    * @param userColours
+    * @param jm
+    * @return
+    */
+   private AnnotationColourScheme constructAnnotationColours(
+           AnnotationColourGradient acg, List<UserColourScheme> userColours,
+           JalviewModel jm)
+   {
+     AnnotationColourScheme ac = new AnnotationColourScheme();
+     ac.setAboveThreshold(acg.getAboveThreshold());
+     ac.setThreshold(acg.getAnnotationThreshold());
+     // 2.10.2 save annotationId (unique) not annotation label
+     ac.setAnnotation(acg.getAnnotation().annotationId);
+     if (acg.getBaseColour() instanceof UserColourScheme)
+     {
+       ac.setColourScheme(
+               setUserColourScheme(acg.getBaseColour(), userColours, jm));
+     }
+     else
+     {
+       ac.setColourScheme(
+               ColourSchemeProperty.getColourName(acg.getBaseColour()));
+     }
+     ac.setMaxColour(acg.getMaxColour().getRGB());
+     ac.setMinColour(acg.getMinColour().getRGB());
+     ac.setPerSequence(acg.isSeqAssociated());
+     ac.setPredefinedColours(acg.isPredefinedColours());
+     return ac;
+   }
+   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
+           IdentityHashMap<SequenceGroup, String> groupRefs,
+           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
+           SequenceSet vamsasSet)
+   {
+     for (int i = 0; i < aa.length; i++)
+     {
+       Annotation an = new Annotation();
+       AlignmentAnnotation annotation = aa[i];
+       if (annotation.annotationId != null)
+       {
+         annotationIds.put(annotation.annotationId, annotation);
+       }
+       an.setId(annotation.annotationId);
+       an.setVisible(annotation.visible);
+       an.setDescription(annotation.description);
+       if (annotation.sequenceRef != null)
+       {
+         // 2.9 JAL-1781 xref on sequence id rather than name
+         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
+       }
+       if (annotation.groupRef != null)
+       {
+         String groupIdr = groupRefs.get(annotation.groupRef);
+         if (groupIdr == null)
+         {
+           // make a locally unique String
+           groupRefs.put(annotation.groupRef,
+                   groupIdr = ("" + System.currentTimeMillis()
+                           + annotation.groupRef.getName()
+                           + groupRefs.size()));
+         }
+         an.setGroupRef(groupIdr.toString());
+       }
+       // store all visualization attributes for annotation
+       an.setGraphHeight(annotation.graphHeight);
+       an.setCentreColLabels(annotation.centreColLabels);
+       an.setScaleColLabels(annotation.scaleColLabel);
+       an.setShowAllColLabels(annotation.showAllColLabels);
+       an.setBelowAlignment(annotation.belowAlignment);
+       if (annotation.graph > 0)
+       {
+         an.setGraph(true);
+         an.setGraphType(annotation.graph);
+         an.setGraphGroup(annotation.graphGroup);
+         if (annotation.getThreshold() != null)
+         {
+           ThresholdLine line = new ThresholdLine();
+           line.setLabel(annotation.getThreshold().label);
+           line.setValue(annotation.getThreshold().value);
+           line.setColour(annotation.getThreshold().colour.getRGB());
+           an.setThresholdLine(line);
+         }
+       }
+       else
+       {
+         an.setGraph(false);
+       }
+       an.setLabel(annotation.label);
+       if (annotation == av.getAlignmentQualityAnnot()
+               || annotation == av.getAlignmentConservationAnnotation()
+               || annotation == av.getAlignmentConsensusAnnotation()
+               || annotation.autoCalculated)
+       {
+         // new way of indicating autocalculated annotation -
+         an.setAutoCalculated(annotation.autoCalculated);
+       }
+       if (annotation.hasScore())
+       {
+         an.setScore(annotation.getScore());
+       }
+       if (annotation.getCalcId() != null)
+       {
+         calcIdSet.add(annotation.getCalcId());
+         an.setCalcId(annotation.getCalcId());
+       }
+       if (annotation.hasProperties())
+       {
+         for (String pr : annotation.getProperties())
+         {
+           jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
+           prop.setName(pr);
+           prop.setValue(annotation.getProperty(pr));
+           // an.addProperty(prop);
+           an.getProperty().add(prop);
+         }
+       }
+       AnnotationElement ae;
+       if (annotation.annotations != null)
+       {
+         an.setScoreOnly(false);
+         for (int a = 0; a < annotation.annotations.length; a++)
+         {
+           if ((annotation == null) || (annotation.annotations[a] == null))
+           {
+             continue;
+           }
+           ae = new AnnotationElement();
+           if (annotation.annotations[a].description != null)
+           {
+             ae.setDescription(annotation.annotations[a].description);
+           }
+           if (annotation.annotations[a].displayCharacter != null)
+           {
+             ae.setDisplayCharacter(
+                     annotation.annotations[a].displayCharacter);
+           }
+           if (!Float.isNaN(annotation.annotations[a].value))
+           {
+             ae.setValue(annotation.annotations[a].value);
+           }
+           ae.setPosition(a);
+           if (annotation.annotations[a].secondaryStructure > ' ')
+           {
+             ae.setSecondaryStructure(
+                     annotation.annotations[a].secondaryStructure + "");
+           }
+           if (annotation.annotations[a].colour != null
+                   && annotation.annotations[a].colour != java.awt.Color.black)
+           {
+             ae.setColour(annotation.annotations[a].colour.getRGB());
+           }
+           // an.addAnnotationElement(ae);
+           an.getAnnotationElement().add(ae);
+           if (annotation.autoCalculated)
+           {
+             // only write one non-null entry into the annotation row -
+             // sufficient to get the visualization attributes necessary to
+             // display data
+             continue;
+           }
+         }
+       }
+       else
+       {
+         an.setScoreOnly(true);
+       }
+       if (!storeDS || (storeDS && !annotation.autoCalculated))
+       {
+         // skip autocalculated annotation - these are only provided for
+         // alignments
+         // vamsasSet.addAnnotation(an);
+         vamsasSet.getAnnotation().add(an);
+       }
+     }
+   }
+   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
+   {
+     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
+     if (settings != null)
+     {
+       CalcIdParam vCalcIdParam = new CalcIdParam();
+       vCalcIdParam.setCalcId(calcId);
+       // vCalcIdParam.addServiceURL(settings.getServiceURI());
+       vCalcIdParam.getServiceURL().add(settings.getServiceURI());
+       // generic URI allowing a third party to resolve another instance of the
+       // service used for this calculation
+       for (String url : settings.getServiceURLs())
+       {
+         // vCalcIdParam.addServiceURL(urls);
+         vCalcIdParam.getServiceURL().add(url);
+       }
+       vCalcIdParam.setVersion("1.0");
+       if (settings.getPreset() != null)
+       {
+         WsParamSetI setting = settings.getPreset();
+         vCalcIdParam.setName(setting.getName());
+         vCalcIdParam.setDescription(setting.getDescription());
+       }
+       else
+       {
+         vCalcIdParam.setName("");
+         vCalcIdParam.setDescription("Last used parameters");
+       }
+       // need to be able to recover 1) settings 2) user-defined presets or
+       // recreate settings from preset 3) predefined settings provided by
+       // service - or settings that can be transferred (or discarded)
+       vCalcIdParam.setParameters(
+               settings.getWsParamFile().replace("\n", "|\\n|"));
+       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
+       // todo - decide if updateImmediately is needed for any projects.
+       return vCalcIdParam;
+     }
+     return null;
+   }
+   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
+           AlignViewport av)
+   {
+     if (calcIdParam.getVersion().equals("1.0"))
+     {
+       final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
+       Jws2Instance service = Jws2Discoverer.getDiscoverer()
+               .getPreferredServiceFor(calcIds);
+       if (service != null)
+       {
+         WsParamSetI parmSet = null;
+         try
+         {
+           parmSet = service.getParamStore().parseServiceParameterFile(
+                   calcIdParam.getName(), calcIdParam.getDescription(),
+                   calcIds,
+                   calcIdParam.getParameters().replace("|\\n|", "\n"));
+         } catch (IOException x)
+         {
+           warn("Couldn't parse parameter data for "
+                   + calcIdParam.getCalcId(), x);
+           return false;
+         }
+         List<ArgumentI> argList = null;
+         if (calcIdParam.getName().length() > 0)
+         {
+           parmSet = service.getParamStore()
+                   .getPreset(calcIdParam.getName());
+           if (parmSet != null)
+           {
+             // TODO : check we have a good match with settings in AACon -
+             // otherwise we'll need to create a new preset
+           }
+         }
+         else
+         {
+           argList = parmSet.getArguments();
+           parmSet = null;
+         }
+         AAConSettings settings = new AAConSettings(
+                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
+         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
+                 calcIdParam.isNeedsUpdate());
+         return true;
+       }
+       else
+       {
+         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
+         return false;
+       }
+     }
+     throw new Error(MessageManager.formatMessage(
+             "error.unsupported_version_calcIdparam", new Object[]
+             { calcIdParam.toString() }));
+   }
+   /**
+    * External mapping between jalview objects and objects yielding a valid and
+    * unique object ID string. This is null for normal Jalview project IO, but
+    * non-null when a jalview project is being read or written as part of a
+    * vamsas session.
+    */
+   IdentityHashMap jv2vobj = null;
+   /**
+    * Construct a unique ID for jvobj using either existing bindings or if none
+    * exist, the result of the hashcode call for the object.
+    * 
+    * @param jvobj
+    *          jalview data object
+    * @return unique ID for referring to jvobj
+    */
+   private String makeHashCode(Object jvobj, String altCode)
+   {
+     if (jv2vobj != null)
+     {
+       Object id = jv2vobj.get(jvobj);
+       if (id != null)
+       {
+         return id.toString();
+       }
+       // check string ID mappings
+       if (jvids2vobj != null && jvobj instanceof String)
+       {
+         id = jvids2vobj.get(jvobj);
+       }
+       if (id != null)
+       {
+         return id.toString();
+       }
+       // give up and warn that something has gone wrong
+       warn("Cannot find ID for object in external mapping : " + jvobj);
+     }
+     return altCode;
+   }
+   /**
+    * return local jalview object mapped to ID, if it exists
+    * 
+    * @param idcode
+    *          (may be null)
+    * @return null or object bound to idcode
+    */
+   private Object retrieveExistingObj(String idcode)
+   {
+     if (idcode != null && vobj2jv != null)
+     {
+       return vobj2jv.get(idcode);
+     }
+     return null;
+   }
+   /**
+    * binding from ID strings from external mapping table to jalview data model
+    * objects.
+    */
+   private Hashtable vobj2jv;
+   private Sequence createVamsasSequence(String id, SequenceI jds)
+   {
+     return createVamsasSequence(true, id, jds, null);
+   }
+   private Sequence createVamsasSequence(boolean recurse, String id,
+           SequenceI jds, SequenceI parentseq)
+   {
+     Sequence vamsasSeq = new Sequence();
+     vamsasSeq.setId(id);
+     vamsasSeq.setName(jds.getName());
+     vamsasSeq.setSequence(jds.getSequenceAsString());
+     vamsasSeq.setDescription(jds.getDescription());
+     jalview.datamodel.DBRefEntry[] dbrefs = null;
+     if (jds.getDatasetSequence() != null)
+     {
+       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
+     }
+     else
+     {
+       // seqId==dsseqid so we can tell which sequences really are
+       // dataset sequences only
+       vamsasSeq.setDsseqid(id);
+       dbrefs = jds.getDBRefs();
+       if (parentseq == null)
+       {
+         parentseq = jds;
+       }
+     }
+     if (dbrefs != null)
+     {
+       for (int d = 0; d < dbrefs.length; d++)
+       {
+         DBRef dbref = new DBRef();
+         dbref.setSource(dbrefs[d].getSource());
+         dbref.setVersion(dbrefs[d].getVersion());
+         dbref.setAccessionId(dbrefs[d].getAccessionId());
+         if (dbrefs[d].hasMap())
+         {
+           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
+                   jds, recurse);
+           dbref.setMapping(mp);
+         }
+         // vamsasSeq.addDBRef(dbref);
+         vamsasSeq.getDBRef().add(dbref);
+       }
+     }
+     return vamsasSeq;
+   }
+   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
+           SequenceI parentseq, SequenceI jds, boolean recurse)
+   {
+     Mapping mp = null;
+     if (jmp.getMap() != null)
+     {
+       mp = new Mapping();
+       jalview.util.MapList mlst = jmp.getMap();
+       List<int[]> r = mlst.getFromRanges();
+       for (int[] range : r)
+       {
+         MapListFrom mfrom = new MapListFrom();
+         mfrom.setStart(range[0]);
+         mfrom.setEnd(range[1]);
+         // mp.addMapListFrom(mfrom);
+         mp.getMapListFrom().add(mfrom);
+       }
+       r = mlst.getToRanges();
+       for (int[] range : r)
+       {
+         MapListTo mto = new MapListTo();
+         mto.setStart(range[0]);
+         mto.setEnd(range[1]);
+         // mp.addMapListTo(mto);
+         mp.getMapListTo().add(mto);
+       }
+       mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
+       mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
+       if (jmp.getTo() != null)
+       {
+         // MappingChoice mpc = new MappingChoice();
+         // check/create ID for the sequence referenced by getTo()
+         String jmpid = "";
+         SequenceI ps = null;
+         if (parentseq != jmp.getTo()
+                 && parentseq.getDatasetSequence() != jmp.getTo())
+         {
+           // chaining dbref rather than a handshaking one
+           jmpid = seqHash(ps = jmp.getTo());
+         }
+         else
+         {
+           jmpid = seqHash(ps = parentseq);
+         }
+         // mpc.setDseqFor(jmpid);
+         mp.setDseqFor(jmpid);
+         if (!seqRefIds.containsKey(jmpid))
+         {
+           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
+           seqRefIds.put(jmpid, ps);
+         }
+         else
+         {
 -          jalview.bin.Cache.log.debug("reusing DseqFor ID");
++          debug("reusing DseqFor ID");
+         }
+         // mp.setMappingChoice(mpc);
+       }
+     }
+     return mp;
+   }
+   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
+           List<UserColourScheme> userColours, JalviewModel jm)
+   {
+     String id = null;
+     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
+     boolean newucs = false;
+     if (!userColours.contains(ucs))
+     {
+       userColours.add(ucs);
+       newucs = true;
+     }
+     id = "ucs" + userColours.indexOf(ucs);
+     if (newucs)
+     {
+       // actually create the scheme's entry in the XML model
+       java.awt.Color[] colours = ucs.getColours();
+       UserColours uc = new UserColours();
+       // UserColourScheme jbucs = new UserColourScheme();
+       JalviewUserColours jbucs = new JalviewUserColours();
+       for (int i = 0; i < colours.length; i++)
+       {
+         Colour col = new Colour();
+         col.setName(ResidueProperties.aa[i]);
+         col.setRGB(jalview.util.Format.getHexString(colours[i]));
+         // jbucs.addColour(col);
+         jbucs.getColour().add(col);
+       }
+       if (ucs.getLowerCaseColours() != null)
+       {
+         colours = ucs.getLowerCaseColours();
+         for (int i = 0; i < colours.length; i++)
+         {
+           Colour col = new Colour();
+           col.setName(ResidueProperties.aa[i].toLowerCase());
+           col.setRGB(jalview.util.Format.getHexString(colours[i]));
+           // jbucs.addColour(col);
+           jbucs.getColour().add(col);
+         }
+       }
+       uc.setId(id);
+       uc.setUserColourScheme(jbucs);
+       // jm.addUserColours(uc);
+       jm.getUserColours().add(uc);
+     }
+     return id;
+   }
+   jalview.schemes.UserColourScheme getUserColourScheme(
+           JalviewModel jm, String id)
+   {
+     List<UserColours> uc = jm.getUserColours();
+     UserColours colours = null;
+ /*
+     for (int i = 0; i < uc.length; i++)
+     {
+       if (uc[i].getId().equals(id))
+       {
+         colours = uc[i];
+         break;
+       }
+     }
+ */
+     for (UserColours c : uc)
+     {
+       if (c.getId().equals(id))
+       {
+         colours = c;
+         break;
+       }
+     }
+     java.awt.Color[] newColours = new java.awt.Color[24];
+     for (int i = 0; i < 24; i++)
+     {
+       newColours[i] = new java.awt.Color(Integer.parseInt(
+               // colours.getUserColourScheme().getColour(i).getRGB(), 16));
+               colours.getUserColourScheme().getColour().get(i).getRGB(),
+               16));
+     }
+     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
+             newColours);
+     if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
+     {
+       newColours = new java.awt.Color[23];
+       for (int i = 0; i < 23; i++)
+       {
+         newColours[i] = new java.awt.Color(Integer.parseInt(
+                 colours.getUserColourScheme().getColour().get(i + 24)
+                         .getRGB(),
+                 16));
+       }
+       ucs.setLowerCaseColours(newColours);
+     }
+     return ucs;
+   }
+   /**
+    * 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
+    *          - HTTP URL or filename
+    */
+   public AlignFrame loadJalviewAlign(final String file)
+   {
+     jalview.gui.AlignFrame af = null;
+     try
+     {
+       // create list to store references for any new Jmol viewers created
+       newStructureViewers = new Vector<>();
+       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
+       // Workaround is to make sure caller implements the JarInputStreamProvider
+       // interface
+       // so we can re-open the jar input stream for each entry.
+       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
+       af = loadJalviewAlign(jprovider);
+       if (af != null)
+       {
+         af.setMenusForViewport();
+       }
+     } catch (MalformedURLException e)
+     {
+       errorMessage = "Invalid URL format for '" + file + "'";
+       reportErrors();
+     } finally
+     {
+       try
+       {
+         SwingUtilities.invokeAndWait(new Runnable()
+         {
+           @Override
+           public void run()
+           {
+             setLoadingFinishedForNewStructureViewers();
+           };
+         });
+       } catch (Exception x)
+       {
+         System.err.println("Error loading alignment: " + x.getMessage());
+       }
+     }
+     return af;
+   }
+   private jarInputStreamProvider createjarInputStreamProvider(
+           final String file) throws MalformedURLException
+   {
+     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 (_url != null)
+         {
+           return new JarInputStream(_url.openStream());
+         }
+         else
+         {
+           return new JarInputStream(new FileInputStream(file));
+         }
+       }
+       @Override
+       public String getFilename()
+       {
+         return file;
+       }
+     };
+   }
+   /**
+    * Recover jalview session from a jalview project archive. Caller may
+    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
+    * themselves. Any null fields will be initialised with default values,
+    * non-null fields are left alone.
+    * 
+    * @param jprovider
+    * @return
+    */
+   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
+   {
+     errorMessage = null;
+     if (uniqueSetSuffix == null)
+     {
+       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
+     }
+     if (seqRefIds == null)
+     {
+       initSeqRefs();
+     }
+     AlignFrame af = null, _af = null;
+     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
+     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
+     final String file = jprovider.getFilename();
+     try
+     {
+       JarInputStream jin = null;
+       JarEntry jarentry = null;
+       int entryCount = 1;
+       do
+       {
+         jin = jprovider.getJarInputStream();
+         for (int i = 0; i < entryCount; i++)
+         {
+           jarentry = jin.getNextJarEntry();
+         }
+         if (jarentry != null && jarentry.getName().endsWith(".xml"))
+         {
+           InputStreamReader in = new InputStreamReader(jin, UTF_8);
+           // JalviewModel object = new JalviewModel();
+           JAXBContext jc = JAXBContext
+                   .newInstance("jalview.xml.binding.jalview");
+           XMLStreamReader streamReader = XMLInputFactory.newInstance()
+                   .createXMLStreamReader(jin);
+           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
+           JAXBElement<JalviewModel> jbe = um
+                   .unmarshal(streamReader, JalviewModel.class);
+           JalviewModel object = jbe.getValue();
+           /*
+           Unmarshaller unmar = new Unmarshaller(object);
+           unmar.setValidation(false);
+           object = (JalviewModel) unmar.unmarshal(in);
+           */
+           if (true) // !skipViewport(object))
+           {
+             _af = loadFromObject(object, file, true, jprovider);
+             if (_af != null && object.getViewport().size() > 0)
+             // getJalviewModelSequence().getViewportCount() > 0)
+             {
+               if (af == null)
+               {
+                 // store a reference to the first view
+                 af = _af;
+               }
+               if (_af.getViewport().isGatherViewsHere())
+               {
+                 // if this is a gathered view, keep its reference since
+                 // after gathering views, only this frame will remain
+                 af = _af;
+                 gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
+                         _af);
+               }
+               // Save dataset to register mappings once all resolved
+               importedDatasets.put(
+                       af.getViewport().getAlignment().getDataset(),
+                       af.getViewport().getAlignment().getDataset());
+             }
+           }
+           entryCount++;
+         }
+         else if (jarentry != null)
+         {
+           // Some other file here.
+           entryCount++;
+         }
+       } while (jarentry != null);
+       resolveFrefedSequences();
+     } catch (IOException ex)
+     {
+       ex.printStackTrace();
+       errorMessage = "Couldn't locate Jalview XML file : " + file;
+       System.err.println(
+               "Exception whilst loading jalview XML file : " + ex + "\n");
+     } catch (Exception ex)
+     {
+       System.err.println("Parsing as Jalview Version 2 file failed.");
+       ex.printStackTrace(System.err);
+       if (attemptversion1parse)
+       {
+         // used to attempt to parse as V1 castor-generated xml
+       }
+       if (Desktop.instance != null)
+       {
+         Desktop.instance.stopLoading();
+       }
+       if (af != null)
+       {
+         System.out.println("Successfully loaded archive file");
+         return af;
+       }
+       ex.printStackTrace();
+       System.err.println(
+               "Exception whilst loading jalview XML file : " + ex + "\n");
+     } catch (OutOfMemoryError e)
+     {
+       // Don't use the OOM Window here
+       errorMessage = "Out of memory loading jalview XML file";
+       System.err.println("Out of memory whilst loading jalview XML file");
+       e.printStackTrace();
+     }
+     /*
+      * Regather multiple views (with the same sequence set id) to the frame (if
+      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
+      * views instead of separate frames. Note this doesn't restore a state where
+      * some expanded views in turn have tabbed views - the last "first tab" read
+      * in will play the role of gatherer for all.
+      */
+     for (AlignFrame fr : gatherToThisFrame.values())
+     {
+       Desktop.instance.gatherViews(fr);
+     }
+     restoreSplitFrames();
+     for (AlignmentI ds : importedDatasets.keySet())
+     {
+       if (ds.getCodonFrames() != null)
+       {
+         StructureSelectionManager
+                 .getStructureSelectionManager(Desktop.instance)
+                 .registerMappings(ds.getCodonFrames());
+       }
+     }
+     if (errorMessage != null)
+     {
+       reportErrors();
+     }
+     if (Desktop.instance != null)
+     {
+       Desktop.instance.stopLoading();
+     }
+     return af;
+   }
+   /**
+    * Try to reconstruct and display SplitFrame windows, where each contains
+    * complementary dna and protein alignments. Done by pairing up AlignFrame
+    * objects (created earlier) which have complementary viewport ids associated.
+    */
+   protected void restoreSplitFrames()
+   {
+     List<SplitFrame> gatherTo = new ArrayList<>();
+     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
+     Map<String, AlignFrame> dna = new HashMap<>();
+     /*
+      * Identify the DNA alignments
+      */
+     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+             .entrySet())
+     {
+       AlignFrame af = candidate.getValue();
+       if (af.getViewport().getAlignment().isNucleotide())
+       {
+         dna.put(candidate.getKey().getId(), af);
+       }
+     }
+     /*
+      * Try to match up the protein complements
+      */
+     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+             .entrySet())
+     {
+       AlignFrame af = candidate.getValue();
+       if (!af.getViewport().getAlignment().isNucleotide())
+       {
+         String complementId = candidate.getKey().getComplementId();
+         // only non-null complements should be in the Map
+         if (complementId != null && dna.containsKey(complementId))
+         {
+           final AlignFrame dnaFrame = dna.get(complementId);
+           SplitFrame sf = createSplitFrame(dnaFrame, af);
+           addedToSplitFrames.add(dnaFrame);
+           addedToSplitFrames.add(af);
+           dnaFrame.setMenusForViewport();
+           af.setMenusForViewport();
+           if (af.getViewport().isGatherViewsHere())
+           {
+             gatherTo.add(sf);
+           }
+         }
+       }
+     }
+     /*
+      * Open any that we failed to pair up (which shouldn't happen!) as
+      * standalone AlignFrame's.
+      */
+     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+             .entrySet())
+     {
+       AlignFrame af = candidate.getValue();
+       if (!addedToSplitFrames.contains(af))
+       {
+         Viewport view = candidate.getKey();
+         Desktop.addInternalFrame(af, view.getTitle(),
+                 safeInt(view.getWidth()), safeInt(view.getHeight()));
+         af.setMenusForViewport();
+         System.err.println("Failed to restore view " + view.getTitle()
+                 + " to split frame");
+       }
+     }
+     /*
+      * Gather back into tabbed views as flagged.
+      */
+     for (SplitFrame sf : gatherTo)
+     {
+       Desktop.instance.gatherViews(sf);
+     }
+     splitFrameCandidates.clear();
+   }
+   /**
+    * Construct and display one SplitFrame holding DNA and protein alignments.
+    * 
+    * @param dnaFrame
+    * @param proteinFrame
+    * @return
+    */
+   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
+           AlignFrame proteinFrame)
+   {
+     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
+     String title = MessageManager.getString("label.linked_view_title");
+     int width = (int) dnaFrame.getBounds().getWidth();
+     int height = (int) (dnaFrame.getBounds().getHeight()
+             + proteinFrame.getBounds().getHeight() + 50);
+     /*
+      * SplitFrame location is saved to both enclosed frames
+      */
+     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
+     Desktop.addInternalFrame(splitFrame, title, width, height);
+     /*
+      * And compute cDNA consensus (couldn't do earlier with consensus as
+      * mappings were not yet present)
+      */
+     proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
+     return splitFrame;
+   }
+   /**
+    * check errorMessage for a valid error message and raise an error box in the
+    * GUI or write the current errorMessage to stderr and then clear the error
+    * state.
+    */
+   protected void reportErrors()
+   {
+     reportErrors(false);
+   }
+   protected void reportErrors(final boolean saving)
+   {
+     if (errorMessage != null)
+     {
+       final String finalErrorMessage = errorMessage;
+       if (raiseGUI)
+       {
+         javax.swing.SwingUtilities.invokeLater(new Runnable()
+         {
+           @Override
+           public void run()
+           {
+             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                     finalErrorMessage,
+                     "Error " + (saving ? "saving" : "loading")
+                             + " Jalview file",
+                     JvOptionPane.WARNING_MESSAGE);
+           }
+         });
+       }
+       else
+       {
+         System.err.println("Problem loading Jalview file: " + errorMessage);
+       }
+     }
+     errorMessage = null;
+   }
+   Map<String, String> alreadyLoadedPDB = new HashMap<>();
+   /**
+    * when set, local views will be updated from view stored in JalviewXML
+    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
+    * sync if this is set to true.
+    */
+   private final boolean updateLocalViews = false;
+   /**
+    * Returns the path to a temporary file holding the PDB file for the given PDB
+    * id. The first time of asking, searches for a file of that name in the
+    * Jalview project jar, and copies it to a new temporary file. Any repeat
+    * requests just return the path to the file previously created.
+    * 
+    * @param jprovider
+    * @param pdbId
+    * @return
+    */
+   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
+           String origFile)
+   {
+     if (alreadyLoadedPDB.containsKey(pdbId))
+     {
+       return alreadyLoadedPDB.get(pdbId).toString();
+     }
+     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
+             origFile);
+     if (tempFile != null)
+     {
+       alreadyLoadedPDB.put(pdbId, tempFile);
+     }
+     return tempFile;
+   }
+   /**
+    * Copies the jar entry of given name to a new temporary file and returns the
+    * path to the file, or null if the entry is not found.
+    * 
+    * @param jprovider
+    * @param jarEntryName
+    * @param prefix
+    *          a prefix for the temporary file name, must be at least three
+    *          characters long
+    * @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 origFile)
+   {
+     BufferedReader in = null;
+     PrintWriter out = null;
+     String suffix = ".tmp";
+     if (origFile == null)
+     {
+       origFile = jarEntryName;
+     }
+     int sfpos = origFile.lastIndexOf(".");
+     if (sfpos > -1 && sfpos < (origFile.length() - 3))
+     {
+       suffix = "." + origFile.substring(sfpos + 1);
+     }
+     try
+     {
+       JarInputStream jin = jprovider.getJarInputStream();
+       /*
+        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
+        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
+        * FileInputStream(jprovider)); }
+        */
+       JarEntry entry = null;
+       do
+       {
+         entry = jin.getNextJarEntry();
+       } while (entry != null && !entry.getName().equals(jarEntryName));
+       if (entry != null)
+       {
+         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+         File outFile = File.createTempFile(prefix, suffix);
+         outFile.deleteOnExit();
+         out = new PrintWriter(new FileOutputStream(outFile));
+         String data;
+         while ((data = in.readLine()) != null)
+         {
+           out.println(data);
+         }
+         out.flush();
+         String t = outFile.getAbsolutePath();
+         return t;
+       }
+       else
+       {
+         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
+       }
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
+     } finally
+     {
+       if (in != null)
+       {
+         try
+         {
+           in.close();
+         } catch (IOException e)
+         {
+           // ignore
+         }
+       }
+       if (out != null)
+       {
+         out.close();
+       }
+     }
+     return null;
+   }
+   private class JvAnnotRow
+   {
+     public JvAnnotRow(int i, AlignmentAnnotation jaa)
+     {
+       order = i;
+       template = jaa;
+     }
+     /**
+      * persisted version of annotation row from which to take vis properties
+      */
+     public jalview.datamodel.AlignmentAnnotation template;
+     /**
+      * original position of the annotation row in the alignment
+      */
+     public int order;
+   }
+   /**
+    * Load alignment frame from jalview XML DOM object
+    * 
+    * @param jalviewModel
+    *          DOM
+    * @param file
+    *          filename source string
+    * @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)
+   {
+     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
+     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
+     // JalviewModelSequence jms = object.getJalviewModelSequence();
+     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
+     // : null;
+     Viewport view = (jalviewModel.getViewport().size() > 0)
+             ? jalviewModel.getViewport().get(0)
+             : null;
+     // ////////////////////////////////
+     // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
+     //
+     //
+     // If we just load in the same jar file again, the sequenceSetId
+     // will be the same, and we end up with multiple references
+     // to the same sequenceSet. We must modify this id on load
+     // so that each load of the file gives a unique id
+     /**
+      * used to resolve correct alignment dataset for alignments with multiple
+      * views
+      */
+     String uniqueSeqSetId = null;
+     String viewId = null;
+     if (view != null)
+     {
+       uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
+       viewId = (view.getId() == null ? null
+               : view.getId() + uniqueSetSuffix);
+     }
+     // ////////////////////////////////
+     // LOAD SEQUENCES
+     List<SequenceI> hiddenSeqs = null;
+     List<SequenceI> tmpseqs = new ArrayList<>();
+     boolean multipleView = false;
+     SequenceI referenceseqForView = null;
+     // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
+     List<JSeq> jseqs = jalviewModel.getJSeq();
+     int vi = 0; // counter in vamsasSeq array
+     for (int i = 0; i < jseqs.size(); i++)
+     {
+       JSeq jseq = jseqs.get(i);
+       String seqId = jseq.getId();
+       SequenceI tmpSeq = seqRefIds.get(seqId);
+       if (tmpSeq != null)
+       {
+         if (!incompleteSeqs.containsKey(seqId))
+         {
+           // may not need this check, but keep it for at least 2.9,1 release
+           if (tmpSeq.getStart() != jseq.getStart()
+                   || tmpSeq.getEnd() != jseq.getEnd())
+           {
+             System.err.println(
+                     "Warning JAL-2154 regression: updating start/end for sequence "
+                             + tmpSeq.toString() + " to " + jseq);
+           }
+         }
+         else
+         {
+           incompleteSeqs.remove(seqId);
+         }
+         if (vamsasSeqs.size() > vi
+                 && vamsasSeqs.get(vi).getId().equals(seqId))
+         {
+           // most likely we are reading a dataset XML document so
+           // update from vamsasSeq section of XML for this sequence
+           tmpSeq.setName(vamsasSeqs.get(vi).getName());
+           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
+           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
+           vi++;
+         }
+         else
+         {
+           // reading multiple views, so vamsasSeq set is a subset of JSeq
+           multipleView = true;
+         }
+         tmpSeq.setStart(jseq.getStart());
+         tmpSeq.setEnd(jseq.getEnd());
+         tmpseqs.add(tmpSeq);
+       }
+       else
+       {
+         Sequence vamsasSeq = vamsasSeqs.get(vi);
+         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
+                 vamsasSeq.getSequence());
+         tmpSeq.setDescription(vamsasSeq.getDescription());
+         tmpSeq.setStart(jseq.getStart());
+         tmpSeq.setEnd(jseq.getEnd());
+         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
+         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
+         tmpseqs.add(tmpSeq);
+         vi++;
+       }
+       if (safeBoolean(jseq.isViewreference()))
+       {
+         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
+       }
+       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
+       {
+         if (hiddenSeqs == null)
+         {
+           hiddenSeqs = new ArrayList<>();
+         }
+         hiddenSeqs.add(tmpSeq);
+       }
+     }
+     // /
+     // Create the alignment object from the sequence set
+     // ///////////////////////////////
+     SequenceI[] orderedSeqs = tmpseqs
+             .toArray(new SequenceI[tmpseqs.size()]);
+     AlignmentI al = null;
+     // so we must create or recover the dataset alignment before going further
+     // ///////////////////////////////
+     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
+     {
+       // older jalview projects do not have a dataset - so creat alignment and
+       // dataset
+       al = new Alignment(orderedSeqs);
+       al.setDataset(null);
+     }
+     else
+     {
+       boolean isdsal = jalviewModel.getViewport().isEmpty();
+       if (isdsal)
+       {
+         // we are importing a dataset record, so
+         // recover reference to an alignment already materialsed as dataset
+         al = getDatasetFor(vamsasSet.getDatasetId());
+       }
+       if (al == null)
+       {
+         // materialse the alignment
+         al = new Alignment(orderedSeqs);
+       }
+       if (isdsal)
+       {
+         addDatasetRef(vamsasSet.getDatasetId(), al);
+       }
+       // finally, verify all data in vamsasSet is actually present in al
+       // passing on flag indicating if it is actually a stored dataset
+       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
+     }
+     if (referenceseqForView != null)
+     {
+       al.setSeqrep(referenceseqForView);
+     }
+     // / Add the alignment properties
+     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
+     {
+       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
+               .get(i);
+       al.setProperty(ssp.getKey(), ssp.getValue());
+     }
+     // ///////////////////////////////
+     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
+     if (!multipleView)
+     {
+       // load sequence features, database references and any associated PDB
+       // structures for the alignment
+       //
+       // prior to 2.10, this part would only be executed the first time a
+       // sequence was encountered, but not afterwards.
+       // now, for 2.10 projects, this is also done if the xml doc includes
+       // dataset sequences not actually present in any particular view.
+       //
+       for (int i = 0; i < vamsasSeqs.size(); i++)
+       {
+         JSeq jseq = jseqs.get(i);
+         if (jseq.getFeatures().size() > 0)
+         {
+           List<Feature> features = jseq.getFeatures();
+           for (int f = 0; f < features.size(); f++)
+           {
+             Feature feat = features.get(f);
+             SequenceFeature sf = new SequenceFeature(feat.getType(),
+                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
+                     safeFloat(feat.getScore()), feat.getFeatureGroup());
+             sf.setStatus(feat.getStatus());
+             /*
+              * load any feature attributes - include map-valued attributes
+              */
+             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
+             for (int od = 0; od < feat.getOtherData().size(); od++)
+             {
+               OtherData keyValue = feat.getOtherData().get(od);
+               String attributeName = keyValue.getKey();
+               String attributeValue = keyValue.getValue();
+               if (attributeName.startsWith("LINK"))
+               {
+                 sf.addLink(attributeValue);
+               }
+               else
+               {
+                 String subAttribute = keyValue.getKey2();
+                 if (subAttribute == null)
+                 {
+                   // simple string-valued attribute
+                   sf.setValue(attributeName, attributeValue);
+                 }
+                 else
+                 {
+                   // attribute 'key' has sub-attribute 'key2'
+                   if (!mapAttributes.containsKey(attributeName))
+                   {
+                     mapAttributes.put(attributeName, new HashMap<>());
+                   }
+                   mapAttributes.get(attributeName).put(subAttribute,
+                           attributeValue);
+                 }
+               }
+             }
+             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
+                     .entrySet())
+             {
+               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
+             }
+             // adds feature to datasequence's feature set (since Jalview 2.10)
 -            al.getSequenceAt(i).addSequenceFeature(sf);
++            alignmentSeq.addSequenceFeature(sf);
+           }
+         }
+         if (vamsasSeqs.get(i).getDBRef().size() > 0)
+         {
+           // adds dbrefs to datasequence's set (since Jalview 2.10)
+           addDBRefs(
+                   al.getSequenceAt(i).getDatasetSequence() == null
+                           ? al.getSequenceAt(i)
+                           : al.getSequenceAt(i).getDatasetSequence(),
+                   vamsasSeqs.get(i));
+         }
+         if (jseq.getPdbids().size() > 0)
+         {
+           List<Pdbids> ids = jseq.getPdbids();
+           for (int p = 0; p < ids.size(); p++)
+           {
+             Pdbids pdbid = ids.get(p);
+             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
+             entry.setId(pdbid.getId());
+             if (pdbid.getType() != null)
+             {
+               if (PDBEntry.Type.getType(pdbid.getType()) != null)
+               {
+                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
+               }
+               else
+               {
+                 entry.setType(PDBEntry.Type.FILE);
+               }
+             }
+             // jprovider is null when executing 'New View'
+             if (pdbid.getFile() != null && jprovider != null)
+             {
+               if (!pdbloaded.containsKey(pdbid.getFile()))
+               {
+                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
+                         pdbid.getFile()));
+               }
+               else
+               {
+                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
+               }
+             }
+             /*
+             if (pdbid.getPdbentryItem() != null)
+             {
+               for (PdbentryItem item : pdbid.getPdbentryItem())
+               {
+                 for (Property pr : item.getProperty())
+                 {
+                   entry.setProperty(pr.getName(), pr.getValue());
+                 }
+               }
+             }
+             */
+             for (Property prop : pdbid.getProperty())
+             {
+               entry.setProperty(prop.getName(), prop.getValue());
+             }
+             StructureSelectionManager
+                     .getStructureSelectionManager(Desktop.instance)
+                     .registerPDBEntry(entry);
+             // adds PDBEntry to datasequence's set (since Jalview 2.10)
 -            if (al.getSequenceAt(i).getDatasetSequence() != null)
++            if (alignmentSeq.getDatasetSequence() != null)
+             {
 -              al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
++              alignmentSeq.getDatasetSequence().addPDBId(entry);
+             }
+             else
+             {
 -              al.getSequenceAt(i).addPDBId(entry);
++              alignmentSeq.addPDBId(entry);
+             }
+           }
+         }
++
++        /*
++         * load any HMMER profile
++         */
++        String hmmJarFile = jseqs[i].getHmmerProfile();
++        if (hmmJarFile != null && jprovider != null)
++        {
++          loadHmmerProfile(jprovider, hmmJarFile, alignmentSeq);
++        }
+       }
+     } // end !multipleview
+     // ///////////////////////////////
+     // LOAD SEQUENCE MAPPINGS
+     if (vamsasSet.getAlcodonFrame().size() > 0)
+     {
+       // TODO Potentially this should only be done once for all views of an
+       // alignment
+       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
+       for (int i = 0; i < alc.size(); i++)
+       {
+         AlignedCodonFrame cf = new AlignedCodonFrame();
+         if (alc.get(i).getAlcodMap().size() > 0)
+         {
+           List<AlcodMap> maps = alc.get(i).getAlcodMap();
+           for (int m = 0; m < maps.size(); m++)
+           {
+             AlcodMap map = maps.get(m);
+             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
+             // Load Mapping
+             jalview.datamodel.Mapping mapping = null;
+             // attach to dna sequence reference.
+             if (map.getMapping() != null)
+             {
+               mapping = addMapping(map.getMapping());
+               if (dnaseq != null && mapping.getTo() != null)
+               {
+                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
+               }
+               else
+               {
+                 // defer to later
+                 frefedSequence.add(
+                         newAlcodMapRef(map.getDnasq(), cf, mapping));
+               }
+             }
+           }
+           al.addCodonFrame(cf);
+         }
+       }
+     }
+     // ////////////////////////////////
+     // LOAD ANNOTATIONS
+     List<JvAnnotRow> autoAlan = new ArrayList<>();
+     /*
+      * store any annotations which forward reference a group's ID
+      */
+     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
+     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
+     {
+       List<Annotation> an = vamsasSet.getAnnotation();
+       for (int i = 0; i < an.size(); i++)
+       {
+         Annotation annotation = an.get(i);
+         /**
+          * test if annotation is automatically calculated for this view only
+          */
+         boolean autoForView = false;
+         if (annotation.getLabel().equals("Quality")
+                 || annotation.getLabel().equals("Conservation")
+                 || annotation.getLabel().equals("Consensus"))
+         {
+           // Kludge for pre 2.5 projects which lacked the autocalculated flag
+           autoForView = true;
+           // JAXB has no has() test; schema defaults value to false
+           // if (!annotation.hasAutoCalculated())
+           // {
+           // annotation.setAutoCalculated(true);
+           // }
+         }
+         if (autoForView || annotation.isAutoCalculated())
+         {
+           // remove ID - we don't recover annotation from other views for
+           // view-specific annotation
+           annotation.setId(null);
+         }
+         // set visibility for other annotation in this view
+         String annotationId = annotation.getId();
+         if (annotationId != null && annotationIds.containsKey(annotationId))
+         {
+           AlignmentAnnotation jda = annotationIds.get(annotationId);
+           // in principle Visible should always be true for annotation displayed
+           // in multiple views
+           if (annotation.isVisible() != null)
+           {
+             jda.visible = annotation.isVisible();
+           }
+           al.addAnnotation(jda);
+           continue;
+         }
+         // Construct new annotation from model.
+         List<AnnotationElement> ae = annotation.getAnnotationElement();
+         jalview.datamodel.Annotation[] anot = null;
+         java.awt.Color firstColour = null;
+         int anpos;
+         if (!annotation.isScoreOnly())
+         {
+           anot = new jalview.datamodel.Annotation[al.getWidth()];
+           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
+           {
+             AnnotationElement annElement = ae.get(aa);
+             anpos = annElement.getPosition();
+             if (anpos >= anot.length)
+             {
+               continue;
+             }
+             float value = safeFloat(annElement.getValue());
+             anot[anpos] = new jalview.datamodel.Annotation(
+                     annElement.getDisplayCharacter(),
+                     annElement.getDescription(),
+                     (annElement.getSecondaryStructure() == null
+                             || annElement.getSecondaryStructure()
+                                     .length() == 0)
+                                             ? ' '
+                                             : annElement
+                                                     .getSecondaryStructure()
+                                                     .charAt(0),
+                     value);
+             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
+             if (firstColour == null)
+             {
+               firstColour = anot[anpos].colour;
+             }
+           }
+         }
+         jalview.datamodel.AlignmentAnnotation jaa = null;
+         if (annotation.isGraph())
+         {
+           float llim = 0, hlim = 0;
+           // if (autoForView || an[i].isAutoCalculated()) {
+           // hlim=11f;
+           // }
+           jaa = new jalview.datamodel.AlignmentAnnotation(
+                   annotation.getLabel(), annotation.getDescription(), anot,
+                   llim, hlim, safeInt(annotation.getGraphType()));
+           jaa.graphGroup = safeInt(annotation.getGraphGroup());
+           jaa._linecolour = firstColour;
+           if (annotation.getThresholdLine() != null)
+           {
+             jaa.setThreshold(new jalview.datamodel.GraphLine(
+                     safeFloat(annotation.getThresholdLine().getValue()),
+                     annotation.getThresholdLine().getLabel(),
+                     new java.awt.Color(safeInt(
+                             annotation.getThresholdLine().getColour()))));
+           }
+           if (autoForView || annotation.isAutoCalculated())
+           {
+             // Hardwire the symbol display line to ensure that labels for
+             // histograms are displayed
+             jaa.hasText = true;
+           }
+         }
+         else
+         {
+           jaa = new jalview.datamodel.AlignmentAnnotation(
+                   annotation.getLabel(), annotation.getDescription(), anot);
+           jaa._linecolour = firstColour;
+         }
+         // register new annotation
+         if (annotation.getId() != null)
+         {
+           annotationIds.put(annotation.getId(), jaa);
+           jaa.annotationId = annotation.getId();
+         }
+         // recover sequence association
+         String sequenceRef = annotation.getSequenceRef();
+         if (sequenceRef != null)
+         {
+           // from 2.9 sequenceRef is to sequence id (JAL-1781)
+           SequenceI sequence = seqRefIds.get(sequenceRef);
+           if (sequence == null)
+           {
+             // in pre-2.9 projects sequence ref is to sequence name
+             sequence = al.findName(sequenceRef);
+           }
+           if (sequence != null)
+           {
+             jaa.createSequenceMapping(sequence, 1, true);
+             sequence.addAlignmentAnnotation(jaa);
+           }
+         }
+         // and make a note of any group association
+         if (annotation.getGroupRef() != null
+                 && annotation.getGroupRef().length() > 0)
+         {
+           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
+                   .get(annotation.getGroupRef());
+           if (aal == null)
+           {
+             aal = new ArrayList<>();
+             groupAnnotRefs.put(annotation.getGroupRef(), aal);
+           }
+           aal.add(jaa);
+         }
+         if (annotation.getScore() != null)
+         {
+           jaa.setScore(annotation.getScore().doubleValue());
+         }
+         if (annotation.isVisible() != null)
+         {
+           jaa.visible = annotation.isVisible().booleanValue();
+         }
+         if (annotation.isCentreColLabels() != null)
+         {
+           jaa.centreColLabels = annotation.isCentreColLabels()
+                   .booleanValue();
+         }
+         if (annotation.isScaleColLabels() != null)
+         {
+           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
+         }
+         if (annotation.isAutoCalculated())
+         {
+           // newer files have an 'autoCalculated' flag and store calculation
+           // state in viewport properties
+           jaa.autoCalculated = true; // means annotation will be marked for
+           // update at end of load.
+         }
+         if (annotation.getGraphHeight() != null)
+         {
+           jaa.graphHeight = annotation.getGraphHeight().intValue();
+         }
+         jaa.belowAlignment = annotation.isBelowAlignment();
+         jaa.setCalcId(annotation.getCalcId());
+         if (annotation.getProperty().size() > 0)
+         {
+           for (Annotation.Property prop : annotation
+                   .getProperty())
+           {
+             jaa.setProperty(prop.getName(), prop.getValue());
+           }
+         }
+         if (jaa.autoCalculated)
+         {
+           autoAlan.add(new JvAnnotRow(i, jaa));
+         }
+         else
+         // if (!autoForView)
+         {
+           // add autocalculated group annotation and any user created annotation
+           // for the view
+           al.addAnnotation(jaa);
+         }
+       }
+     }
+     // ///////////////////////
+     // LOAD GROUPS
+     // Create alignment markup and styles for this view
+     if (jalviewModel.getJGroup().size() > 0)
+     {
+       List<JGroup> groups = jalviewModel.getJGroup();
+       boolean addAnnotSchemeGroup = false;
+       for (int i = 0; i < groups.size(); i++)
+       {
+         JGroup jGroup = groups.get(i);
+         ColourSchemeI cs = null;
+         if (jGroup.getColour() != null)
+         {
+           if (jGroup.getColour().startsWith("ucs"))
+           {
+             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
+           }
+           else if (jGroup.getColour().equals("AnnotationColourGradient")
+                   && jGroup.getAnnotationColours() != null)
+           {
+             addAnnotSchemeGroup = true;
+           }
+           else
+           {
+             cs = ColourSchemeProperty.getColourScheme(null, al,
+                     jGroup.getColour());
+           }
+         }
+         int pidThreshold = safeInt(jGroup.getPidThreshold());
+         Vector<SequenceI> seqs = new Vector<>();
+         for (int s = 0; s < jGroup.getSeq().size(); s++)
+         {
+           String seqId = jGroup.getSeq().get(s);
+           SequenceI ts = seqRefIds.get(seqId);
+           if (ts != null)
+           {
+             seqs.addElement(ts);
+           }
+         }
+         if (seqs.size() < 1)
+         {
+           continue;
+         }
+         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
+                 safeBoolean(jGroup.isDisplayBoxes()),
+                 safeBoolean(jGroup.isDisplayText()),
+                 safeBoolean(jGroup.isColourText()),
+                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
+         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
+         sg.getGroupColourScheme()
+                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
+         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
+         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
+         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
+         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
+         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
+         // attributes with a default in the schema are never null
+           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
+           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
+           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
+         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
+         if (jGroup.getConsThreshold() != null
+                 && jGroup.getConsThreshold().intValue() != 0)
+         {
+           Conservation c = new Conservation("All", sg.getSequences(null), 0,
+                   sg.getWidth() - 1);
+           c.calculate();
+           c.verdict(false, 25);
+           sg.cs.setConservation(c);
+         }
+         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
+         {
+           // re-instate unique group/annotation row reference
+           List<AlignmentAnnotation> jaal = groupAnnotRefs
+                   .get(jGroup.getId());
+           if (jaal != null)
+           {
+             for (AlignmentAnnotation jaa : jaal)
+             {
+               jaa.groupRef = sg;
+               if (jaa.autoCalculated)
+               {
+                 // match up and try to set group autocalc alignment row for this
+                 // annotation
+                 if (jaa.label.startsWith("Consensus for "))
+                 {
+                   sg.setConsensus(jaa);
+                 }
+                 // match up and try to set group autocalc alignment row for this
+                 // annotation
+                 if (jaa.label.startsWith("Conservation for "))
+                 {
+                   sg.setConservationRow(jaa);
+                 }
+               }
+             }
+           }
+         }
+         al.addGroup(sg);
+         if (addAnnotSchemeGroup)
+         {
+           // reconstruct the annotation colourscheme
+           sg.setColourScheme(constructAnnotationColour(
+                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
+         }
+       }
+     }
+     if (view == null)
+     {
+       // only dataset in this model, so just return.
+       return null;
+     }
+     // ///////////////////////////////
+     // 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)
+     {
+       // We recovered an alignment for which a viewport already exists.
+       // TODO: fix up any settings necessary for overlaying stored state onto
+       // state recovered from another document. (may not be necessary).
+       // we may need a binding from a viewport in memory to one recovered from
+       // XML.
+       // and then recover its containing af to allow the settings to be applied.
+       // TODO: fix for vamsas demo
+       System.err.println(
+               "About to recover a viewport for existing alignment: Sequence set ID is "
+                       + uniqueSeqSetId);
+       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
+       if (seqsetobj != null)
+       {
+         if (seqsetobj instanceof String)
+         {
+           uniqueSeqSetId = (String) seqsetobj;
+           System.err.println(
+                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
+                           + uniqueSeqSetId);
+         }
+         else
+         {
+           System.err.println(
+                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
+         }
+       }
+     }
+     /**
+      * indicate that annotation colours are applied across all groups (pre
+      * Jalview 2.8.1 behaviour)
+      */
+     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
+             jalviewModel.getVersion());
+     AlignmentPanel ap = null;
+     boolean isnewview = true;
+     if (viewId != null)
+     {
+       // Check to see if this alignment already has a view id == viewId
+       jalview.gui.AlignmentPanel views[] = Desktop
+               .getAlignmentPanels(uniqueSeqSetId);
+       if (views != null && views.length > 0)
+       {
+         for (int v = 0; v < views.length; v++)
+         {
+           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
+           {
+             // recover the existing alignpanel, alignframe, viewport
+             af = views[v].alignFrame;
+             av = views[v].av;
+             ap = views[v];
+             // 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)
+     {
+       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
+               uniqueSeqSetId, viewId, autoAlan);
+       av = af.getViewport();
+       ap = af.alignPanel;
+     }
+     /*
+      * Load any trees, PDB structures and viewers
+      * 
+      * Not done if flag is false (when this method is used for New View)
+      */
+     if (loadTreesAndStructures)
+     {
+       loadTrees(jalviewModel, view, af, av, ap);
+       loadPCAViewers(jalviewModel, ap);
+       loadPDBStructures(jprovider, jseqs, af, ap);
+       loadRnaViewers(jprovider, jseqs, ap);
+     }
+     // and finally return.
+     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.
+    * 
+    * Currently each viewer shows just one sequence and structure (gapped and
+    * trimmed), however this method is designed to support multiple sequences or
+    * structures in viewers if wanted in future.
+    * 
+    * @param jprovider
+    * @param jseqs
+    * @param ap
+    */
+   private void loadRnaViewers(jarInputStreamProvider jprovider,
+           List<JSeq> jseqs, AlignmentPanel ap)
+   {
+     /*
+      * scan the sequences for references to viewers; create each one the first
+      * time it is referenced, add Rna models to existing viewers
+      */
+     for (JSeq jseq : jseqs)
+     {
+       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
+       {
+         RnaViewer viewer = jseq.getRnaViewer().get(i);
+         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
+                 ap);
+         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
+         {
+           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
+           SequenceI seq = seqRefIds.get(jseq.getId());
+           AlignmentAnnotation ann = this.annotationIds
+                   .get(ss.getAnnotationId());
+           /*
+            * add the structure to the Varna display (with session state copied
+            * from the jar to a temporary file)
+            */
+           boolean gapped = safeBoolean(ss.isGapped());
+           String rnaTitle = ss.getTitle();
+           String sessionState = ss.getViewerState();
+           String tempStateFile = copyJarEntry(jprovider, sessionState,
+                   "varna", null);
+           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
+           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
+         }
+         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
+       }
+     }
+   }
+   /**
+    * Locate and return an already instantiated matching AppVarna, or create one
+    * if not found
+    * 
+    * @param viewer
+    * @param viewIdSuffix
+    * @param ap
+    * @return
+    */
+   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
+           String viewIdSuffix, AlignmentPanel ap)
+   {
+     /*
+      * on each load a suffix is appended to the saved viewId, to avoid conflicts
+      * if load is repeated
+      */
+     String postLoadId = viewer.getViewId() + viewIdSuffix;
+     for (JInternalFrame frame : getAllFrames())
+     {
+       if (frame instanceof AppVarna)
+       {
+         AppVarna varna = (AppVarna) frame;
+         if (postLoadId.equals(varna.getViewId()))
+         {
+           // this viewer is already instantiated
+           // could in future here add ap as another 'parent' of the
+           // AppVarna window; currently just 1-to-many
+           return varna;
+         }
+       }
+     }
+     /*
+      * viewer not found - make it
+      */
+     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
+             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
+             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
+             safeInt(viewer.getDividerLocation()));
+     AppVarna varna = new AppVarna(model, ap);
+     return varna;
+   }
+   /**
+    * Load any saved trees
+    * 
+    * @param jm
+    * @param view
+    * @param af
+    * @param av
+    * @param ap
+    */
+   protected void loadTrees(JalviewModel jm, Viewport view,
+           AlignFrame af, AlignViewport av, AlignmentPanel ap)
+   {
+     // TODO result of automated refactoring - are all these parameters needed?
+     try
+     {
+       for (int t = 0; t < jm.getTree().size(); t++)
+       {
+         Tree tree = jm.getTree().get(t);
+         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
+         if (tp == null)
+         {
+           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
+                   tree.getTitle(), safeInt(tree.getWidth()),
+                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
+                   safeInt(tree.getYpos()));
+           if (tree.getId() != null)
+           {
+             // perhaps bind the tree id to something ?
+           }
+         }
+         else
+         {
+           // update local tree attributes ?
+           // TODO: should check if tp has been manipulated by user - if so its
+           // settings shouldn't be modified
+           tp.setTitle(tree.getTitle());
+           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
+                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
+                   safeInt(tree.getHeight())));
+           tp.setViewport(av); // af.viewport;
+           // TODO: verify 'associate with all views' works still
+           tp.getTreeCanvas().setViewport(av); // af.viewport;
+           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);
+         if (tree.getFontName() != null)
+         {
+           tp.setTreeFont(
+                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
+                           safeInt(tree.getFontSize())));
+         }
+         else
+         {
+           tp.setTreeFont(
+                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
+                           safeInt(view.getFontSize())));
+         }
+         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
+         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
+         tp.showDistances(safeBoolean(tree.isShowDistances()));
+         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
+         if (safeBoolean(tree.isCurrentTree()))
+         {
+           af.getViewport().setCurrentTree(tp.getTree());
+         }
+       }
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
+     }
+   }
+   /**
+    * Load and link any saved structure viewers.
+    * 
+    * @param jprovider
+    * @param jseqs
+    * @param af
+    * @param ap
+    */
+   protected void loadPDBStructures(jarInputStreamProvider jprovider,
+           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
+   {
+     /*
+      * Run through all PDB ids on the alignment, and collect mappings between
+      * distinct view ids and all sequences referring to that view.
+      */
+     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
+     for (int i = 0; i < jseqs.size(); i++)
+     {
+       JSeq jseq = jseqs.get(i);
+       if (jseq.getPdbids().size() > 0)
+       {
+         List<Pdbids> ids = jseq.getPdbids();
+         for (int p = 0; p < ids.size(); p++)
+         {
+           Pdbids pdbid = ids.get(p);
+           final int structureStateCount = pdbid.getStructureState().size();
+           for (int s = 0; s < structureStateCount; s++)
+           {
+             // check to see if we haven't already created this structure view
+             final StructureState structureState = pdbid
+                     .getStructureState().get(s);
+             String sviewid = (structureState.getViewId() == null) ? null
+                     : structureState.getViewId() + uniqueSetSuffix;
+             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
+             // Originally : pdbid.getFile()
+             // : TODO: verify external PDB file recovery still works in normal
+             // jalview project load
+             jpdb.setFile(
+                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
+             jpdb.setId(pdbid.getId());
+             int x = safeInt(structureState.getXpos());
+             int y = safeInt(structureState.getYpos());
+             int width = safeInt(structureState.getWidth());
+             int height = safeInt(structureState.getHeight());
+             // Probably don't need to do this anymore...
+             // Desktop.desktop.getComponentAt(x, y);
+             // TODO: NOW: check that this recovers the PDB file correctly.
+             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
+                     pdbid.getFile());
+             jalview.datamodel.SequenceI seq = seqRefIds
+                     .get(jseq.getId() + "");
+             if (sviewid == null)
+             {
+               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
+                       + height;
+             }
+             if (!structureViewers.containsKey(sviewid))
+             {
+               structureViewers.put(sviewid,
+                       new StructureViewerModel(x, y, width, height, false,
+                               false, true, structureState.getViewId(),
+                               structureState.getType()));
+               // Legacy pre-2.7 conversion JAL-823 :
+               // do not assume any view has to be linked for colour by
+               // sequence
+             }
+             // assemble String[] { pdb files }, String[] { id for each
+             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
+             // seqs_file 2}, boolean[] {
+             // linkAlignPanel,superposeWithAlignpanel}} from hash
+             StructureViewerModel jmoldat = structureViewers.get(sviewid);
+             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
+                     || structureState.isAlignwithAlignPanel());
+             /*
+              * Default colour by linked panel to false if not specified (e.g.
+              * for pre-2.7 projects)
+              */
+             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
+             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
+             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
+             /*
+              * Default colour by viewer to true if not specified (e.g. for
+              * pre-2.7 projects)
+              */
+             boolean colourByViewer = jmoldat.isColourByViewer();
+             colourByViewer &= structureState.isColourByJmol();
+             jmoldat.setColourByViewer(colourByViewer);
+             if (jmoldat.getStateData().length() < structureState
+                     .getValue()/*Content()*/.length())
+             {
+               jmoldat.setStateData(structureState.getValue());// Content());
+             }
+             if (pdbid.getFile() != null)
+             {
+               File mapkey = new File(pdbid.getFile());
+               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
+               if (seqstrmaps == null)
+               {
+                 jmoldat.getFileData().put(mapkey,
+                         seqstrmaps = jmoldat.new StructureData(pdbFile,
+                                 pdbid.getId()));
+               }
+               if (!seqstrmaps.getSeqList().contains(seq))
+               {
+                 seqstrmaps.getSeqList().add(seq);
+                 // TODO and chains?
+               }
+             }
+             else
+             {
+               errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
+               warn(errorMessage);
+             }
+           }
+         }
+       }
+     }
+     // Instantiate the associated structure views
+     for (Entry<String, StructureViewerModel> entry : structureViewers
+             .entrySet())
+     {
+       try
+       {
+         createOrLinkStructureViewer(entry, af, ap, jprovider);
+       } catch (Exception e)
+       {
+         System.err.println(
+                 "Error loading structure viewer: " + e.getMessage());
+         // failed - try the next one
+       }
+     }
+   }
+   /**
+    * 
+    * @param viewerData
+    * @param af
+    * @param ap
+    * @param jprovider
+    */
+   protected void createOrLinkStructureViewer(
+           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+           AlignmentPanel ap, jarInputStreamProvider jprovider)
+   {
+     final StructureViewerModel stateData = viewerData.getValue();
+     /*
+      * Search for any viewer windows already open from other alignment views
+      * that exactly match the stored structure state
+      */
+     StructureViewerBase comp = findMatchingViewer(viewerData);
+     if (comp != null)
+     {
+       linkStructureViewer(ap, comp, stateData);
+       return;
+     }
+     /*
+      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
+      * "viewer_"+stateData.viewId
+      */
+     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
+     {
+       createChimeraViewer(viewerData, af, jprovider);
+     }
+     else
+     {
+       /*
+        * else Jmol (if pre-2.9, stateData contains JMOL state string)
+        */
+       createJmolViewer(viewerData, af, jprovider);
+     }
+   }
+   /**
+    * Create a new Chimera viewer.
+    * 
+    * @param data
+    * @param af
+    * @param jprovider
+    */
+   protected void createChimeraViewer(
+           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+           jarInputStreamProvider jprovider)
+   {
+     StructureViewerModel data = viewerData.getValue();
+     String chimeraSessionFile = data.getStateData();
+     /*
+      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
+      * 
+      * NB this is the 'saved' viewId as in the project file XML, _not_ the
+      * 'uniquified' sviewid used to reconstruct the viewer here
+      */
+     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
+     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
+             "chimera", null);
+     Set<Entry<File, StructureData>> fileData = data.getFileData()
+             .entrySet();
+     List<PDBEntry> pdbs = new ArrayList<>();
+     List<SequenceI[]> allseqs = new ArrayList<>();
+     for (Entry<File, StructureData> pdb : fileData)
+     {
+       String filePath = pdb.getValue().getFilePath();
+       String pdbId = pdb.getValue().getPdbId();
+       // pdbs.add(new PDBEntry(filePath, pdbId));
+       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
+       final List<SequenceI> seqList = pdb.getValue().getSeqList();
+       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
+       allseqs.add(seqs);
+     }
+     boolean colourByChimera = data.isColourByViewer();
+     boolean colourBySequence = data.isColourWithAlignPanel();
+     // TODO use StructureViewer as a factory here, see JAL-1761
+     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
+     final SequenceI[][] seqsArray = allseqs
+             .toArray(new SequenceI[allseqs.size()][]);
+     String newViewId = viewerData.getKey();
+     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
+             af.alignPanel, pdbArray, seqsArray, colourByChimera,
+             colourBySequence, newViewId);
+     cvf.setSize(data.getWidth(), data.getHeight());
+     cvf.setLocation(data.getX(), data.getY());
+   }
+   /**
+    * Create a new Jmol window. First parse the Jmol state to translate filenames
+    * loaded into the view, and record the order in which files are shown in the
+    * Jmol view, so we can add the sequence mappings in same order.
+    * 
+    * @param viewerData
+    * @param af
+    * @param jprovider
+    */
+   protected void createJmolViewer(
+           final Entry<String, StructureViewerModel> viewerData,
+           AlignFrame af, jarInputStreamProvider jprovider)
+   {
+     final StructureViewerModel svattrib = viewerData.getValue();
+     String state = svattrib.getStateData();
+     /*
+      * Pre-2.9: state element value is the Jmol state string
+      * 
+      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
+      * + viewId
+      */
+     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
+     {
+       state = readJarEntry(jprovider,
+               getViewerJarEntryName(svattrib.getViewId()));
+     }
+     List<String> pdbfilenames = new ArrayList<>();
+     List<SequenceI[]> seqmaps = new ArrayList<>();
+     List<String> pdbids = new ArrayList<>();
+     StringBuilder newFileLoc = new StringBuilder(64);
+     int cp = 0, ncp, ecp;
+     Map<File, StructureData> oldFiles = svattrib.getFileData();
+     while ((ncp = state.indexOf("load ", cp)) > -1)
+     {
+       do
+       {
+         // look for next filename in load statement
+         newFileLoc.append(state.substring(cp,
+                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
+         String oldfilenam = state.substring(ncp,
+                 ecp = state.indexOf("\"", ncp));
+         // recover the new mapping data for this old filename
+         // have to normalize filename - since Jmol and jalview do
+         // filename
+         // translation differently.
+         StructureData filedat = oldFiles.get(new File(oldfilenam));
+         if (filedat == null)
+         {
+           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
+           filedat = oldFiles.get(new File(reformatedOldFilename));
+         }
+         newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
+         pdbfilenames.add(filedat.getFilePath());
+         pdbids.add(filedat.getPdbId());
+         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+         newFileLoc.append("\"");
+         cp = ecp + 1; // advance beyond last \" and set cursor so we can
+                       // look for next file statement.
+       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
+     }
+     if (cp > 0)
+     {
+       // just append rest of state
+       newFileLoc.append(state.substring(cp));
+     }
+     else
+     {
+       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
+       newFileLoc = new StringBuilder(state);
+       newFileLoc.append("; load append ");
+       for (File id : oldFiles.keySet())
+       {
+         // add this and any other pdb files that should be present in
+         // the viewer
+         StructureData filedat = oldFiles.get(id);
+         newFileLoc.append(filedat.getFilePath());
+         pdbfilenames.add(filedat.getFilePath());
+         pdbids.add(filedat.getPdbId());
+         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+         newFileLoc.append(" \"");
+         newFileLoc.append(filedat.getFilePath());
+         newFileLoc.append("\"");
+       }
+       newFileLoc.append(";");
+     }
+     if (newFileLoc.length() == 0)
+     {
+       return;
+     }
+     int histbug = newFileLoc.indexOf("history = ");
+     if (histbug > -1)
+     {
+       /*
+        * change "history = [true|false];" to "history = [1|0];"
+        */
+       histbug += 10;
+       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
+       String val = (diff == -1) ? null
+               : newFileLoc.substring(histbug, diff);
+       if (val != null && val.length() >= 4)
+       {
+         if (val.contains("e")) // eh? what can it be?
+         {
+           if (val.trim().equals("true"))
+           {
+             val = "1";
+           }
+           else
+           {
+             val = "0";
+           }
+           newFileLoc.replace(histbug, diff, val);
+         }
+       }
+     }
+     final String[] pdbf = pdbfilenames
+             .toArray(new String[pdbfilenames.size()]);
+     final String[] id = pdbids.toArray(new String[pdbids.size()]);
+     final SequenceI[][] sq = seqmaps
+             .toArray(new SequenceI[seqmaps.size()][]);
+     final String fileloc = newFileLoc.toString();
+     final String sviewid = viewerData.getKey();
+     final AlignFrame alf = af;
+     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
+             svattrib.getWidth(), svattrib.getHeight());
+     try
+     {
+       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+       {
+         @Override
+         public void run()
+         {
+           JalviewStructureDisplayI sview = null;
+           try
+           {
+             sview = new StructureViewer(
+                     alf.alignPanel.getStructureSelectionManager())
+                             .createView(StructureViewer.ViewerType.JMOL,
+                                     pdbf, id, sq, alf.alignPanel, svattrib,
+                                     fileloc, rect, sviewid);
+             addNewStructureViewer(sview);
+           } catch (OutOfMemoryError ex)
+           {
+             new OOMWarning("restoring structure view for PDB id " + id,
+                     (OutOfMemoryError) ex.getCause());
+             if (sview != null && sview.isVisible())
+             {
+               sview.closeViewer(false);
+               sview.setVisible(false);
+               sview.dispose();
+             }
+           }
+         }
+       });
+     } catch (InvocationTargetException ex)
+     {
+       warn("Unexpected error when opening Jmol view.", ex);
+     } catch (InterruptedException e)
+     {
+       // e.printStackTrace();
+     }
+   }
+   /**
+    * Generates a name for the entry in the project jar file to hold state
+    * information for a structure viewer
+    * 
+    * @param viewId
+    * @return
+    */
+   protected String getViewerJarEntryName(String viewId)
+   {
+     return VIEWER_PREFIX + viewId;
+   }
+   /**
+    * Returns any open frame that matches given structure viewer data. The match
+    * is based on the unique viewId, or (for older project versions) the frame's
+    * geometry.
+    * 
+    * @param viewerData
+    * @return
+    */
+   protected StructureViewerBase findMatchingViewer(
+           Entry<String, StructureViewerModel> viewerData)
+   {
+     final String sviewid = viewerData.getKey();
+     final StructureViewerModel svattrib = viewerData.getValue();
+     StructureViewerBase comp = null;
+     JInternalFrame[] frames = getAllFrames();
+     for (JInternalFrame frame : frames)
+     {
+       if (frame instanceof StructureViewerBase)
+       {
+         /*
+          * Post jalview 2.4 schema includes structure view id
+          */
+         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
+                 .equals(sviewid))
+         {
+           comp = (StructureViewerBase) frame;
+           break; // break added in 2.9
+         }
+         /*
+          * Otherwise test for matching position and size of viewer frame
+          */
+         else if (frame.getX() == svattrib.getX()
+                 && frame.getY() == svattrib.getY()
+                 && frame.getHeight() == svattrib.getHeight()
+                 && frame.getWidth() == svattrib.getWidth())
+         {
+           comp = (StructureViewerBase) frame;
+           // no break in faint hope of an exact match on viewId
+         }
+       }
+     }
+     return comp;
+   }
+   /**
+    * Link an AlignmentPanel to an existing structure viewer.
+    * 
+    * @param ap
+    * @param viewer
+    * @param oldFiles
+    * @param useinViewerSuperpos
+    * @param usetoColourbyseq
+    * @param viewerColouring
+    */
+   protected void linkStructureViewer(AlignmentPanel ap,
+           StructureViewerBase viewer, StructureViewerModel stateData)
+   {
+     // NOTE: if the jalview project is part of a shared session then
+     // view synchronization should/could be done here.
+     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
+     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
+     final boolean viewerColouring = stateData.isColourByViewer();
+     Map<File, StructureData> oldFiles = stateData.getFileData();
+     /*
+      * Add mapping for sequences in this view to an already open viewer
+      */
+     final AAStructureBindingModel binding = viewer.getBinding();
+     for (File id : oldFiles.keySet())
+     {
+       // add this and any other pdb files that should be present in the
+       // viewer
+       StructureData filedat = oldFiles.get(id);
+       String pdbFile = filedat.getFilePath();
+       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
+       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
+               null);
+       binding.addSequenceForStructFile(pdbFile, seq);
+     }
+     // and add the AlignmentPanel's reference to the view panel
+     viewer.addAlignmentPanel(ap);
+     if (useinViewerSuperpos)
+     {
+       viewer.useAlignmentPanelForSuperposition(ap);
+     }
+     else
+     {
+       viewer.excludeAlignmentPanelForSuperposition(ap);
+     }
+     if (usetoColourbyseq)
+     {
+       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
+     }
+     else
+     {
+       viewer.excludeAlignmentPanelForColourbyseq(ap);
+     }
+   }
+   /**
+    * Get all frames within the Desktop.
+    * 
+    * @return
+    */
+   protected JInternalFrame[] getAllFrames()
+   {
+     JInternalFrame[] frames = null;
+     // TODO is this necessary - is it safe - risk of hanging?
+     do
+     {
+       try
+       {
+         frames = Desktop.desktop.getAllFrames();
+       } catch (ArrayIndexOutOfBoundsException e)
+       {
+         // occasional No such child exceptions are thrown here...
+         try
+         {
+           Thread.sleep(10);
+         } catch (InterruptedException f)
+         {
+         }
+       }
+     } while (frames == null);
+     return frames;
+   }
+   /**
+    * Answers true if 'version' is equal to or later than 'supported', where each
+    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
+    * changes. Development and test values for 'version' are leniently treated
+    * i.e. answer true.
+    * 
+    * @param supported
+    *          - minimum version we are comparing against
+    * @param version
+    *          - version of data being processsed
+    * @return
+    */
+   public static boolean isVersionStringLaterThan(String supported,
+           String version)
+   {
+     if (supported == null || version == null
+             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
+             || version.equalsIgnoreCase("Test")
+             || version.equalsIgnoreCase("AUTOMATED BUILD"))
+     {
+       System.err.println("Assuming project file with "
+               + (version == null ? "null" : version)
+               + " is compatible with Jalview version " + supported);
+       return true;
+     }
+     else
+     {
+       return StringUtils.compareVersions(version, supported, "b") >= 0;
+     }
+   }
+   Vector<JalviewStructureDisplayI> newStructureViewers = null;
+   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
+   {
+     if (newStructureViewers != null)
+     {
+       sview.getBinding().setFinishedLoadingFromArchive(false);
+       newStructureViewers.add(sview);
+     }
+   }
+   protected void setLoadingFinishedForNewStructureViewers()
+   {
+     if (newStructureViewers != null)
+     {
+       for (JalviewStructureDisplayI sview : newStructureViewers)
+       {
+         sview.getBinding().setFinishedLoadingFromArchive(true);
+       }
+       newStructureViewers.clear();
+       newStructureViewers = null;
+     }
+   }
+   AlignFrame loadViewport(String 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);
+     af.setFileName(file, FileFormat.Jalview);
+     final AlignViewport viewport = af.getViewport();
+     for (int i = 0; i < JSEQ.size(); i++)
+     {
+       int colour = safeInt(JSEQ.get(i).getColour());
+       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
+               new Color(colour));
+     }
+     if (al.hasSeqrep())
+     {
+       viewport.setColourByReferenceSeq(true);
+       viewport.setDisplayReferenceSeq(true);
+     }
+     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
+     if (view.getSequenceSetId() != null)
+     {
+       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
+       viewport.setSequenceSetId(uniqueSeqSetId);
+       if (av != null)
+       {
+         // propagate shared settings to this new view
+         viewport.setHistoryList(av.getHistoryList());
+         viewport.setRedoList(av.getRedoList());
+       }
+       else
+       {
+         viewportsAdded.put(uniqueSeqSetId, viewport);
+       }
+       // TODO: check if this method can be called repeatedly without
+       // side-effects if alignpanel already registered.
+       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
+     }
+     // apply Hidden regions to view.
+     if (hiddenSeqs != null)
+     {
+       for (int s = 0; s < JSEQ.size(); s++)
+       {
+         SequenceGroup hidden = new SequenceGroup();
+         boolean isRepresentative = false;
+         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
+         {
+           isRepresentative = true;
+           SequenceI sequenceToHide = al
+                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
+           hidden.addSequence(sequenceToHide, false);
+           // remove from hiddenSeqs list so we don't try to hide it twice
+           hiddenSeqs.remove(sequenceToHide);
+         }
+         if (isRepresentative)
+         {
+           SequenceI representativeSequence = al.getSequenceAt(s);
+           hidden.addSequence(representativeSequence, false);
+           viewport.hideRepSequences(representativeSequence, hidden);
+         }
+       }
+       SequenceI[] hseqs = hiddenSeqs
+               .toArray(new SequenceI[hiddenSeqs.size()]);
+       viewport.hideSequence(hseqs);
+     }
+     // recover view properties and display parameters
+     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
+     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
+     final int pidThreshold = safeInt(view.getPidThreshold());
+     viewport.setThreshold(pidThreshold);
+     viewport.setColourText(safeBoolean(view.isShowColourText()));
+     viewport
+             .setConservationSelected(
+                     safeBoolean(view.isConservationSelected()));
+     viewport.setIncrement(safeInt(view.getConsThreshold()));
+     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
+     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
+     viewport.setFont(new Font(view.getFontName(),
+             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
+             true);
+     ViewStyleI vs = viewport.getViewStyle();
+     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
+     viewport.setViewStyle(vs);
+     // TODO: allow custom charWidth/Heights to be restored by updating them
+     // after setting font - which means set above to false
+     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
+     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
+     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
+     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
+     viewport.setShowText(safeBoolean(view.isShowText()));
+     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
+     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
+     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
+     viewport.setShowUnconserved(view.isShowUnconserved());
+     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
+     if (view.getViewName() != null)
+     {
+       viewport.setViewName(view.getViewName());
+       af.setInitialTabVisible();
+     }
+     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
+             safeInt(view.getWidth()), safeInt(view.getHeight()));
+     // startSeq set in af.alignPanel.updateLayout below
+     af.alignPanel.updateLayout();
+     ColourSchemeI cs = null;
+     // apply colourschemes
+     if (view.getBgColour() != null)
+     {
+       if (view.getBgColour().startsWith("ucs"))
+       {
+         cs = getUserColourScheme(jm, view.getBgColour());
+       }
+       else if (view.getBgColour().startsWith("Annotation"))
+       {
+         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
+         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
+         // annpos
+       }
+       else
+       {
+         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
+                 view.getBgColour());
+       }
+     }
+     /*
+      * turn off 'alignment colour applies to all groups'
+      * while restoring global colour scheme
+      */
+     viewport.setColourAppliesToAllGroups(false);
+     viewport.setGlobalColourScheme(cs);
+     viewport.getResidueShading().setThreshold(pidThreshold,
+             view.isIgnoreGapsinConsensus());
+     viewport.getResidueShading()
+             .setConsensus(viewport.getSequenceConsensusHash());
+     if (safeBoolean(view.isConservationSelected()) && cs != null)
+     {
+       viewport.getResidueShading()
+               .setConservationInc(safeInt(view.getConsThreshold()));
+     }
+     af.changeColour(cs);
+     viewport.setColourAppliesToAllGroups(true);
+     viewport
+             .setShowSequenceFeatures(
+                     safeBoolean(view.isShowSequenceFeatures()));
+     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
+     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
+     viewport.setFollowHighlight(view.isFollowHighlight());
+     viewport.followSelection = view.isFollowSelection();
+     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
+     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
+     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
+     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
+     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
+     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
+     viewport.setShowGroupConservation(view.isShowGroupConservation());
+     // recover feature settings
+     if (jm.getFeatureSettings() != null)
+     {
+       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
+               .getFeatureRenderer();
+       FeaturesDisplayed fdi;
+       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
+       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++)
+       {
+         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
+         String featureType = setting.getType();
+         /*
+          * restore feature filters (if any)
+          */
+         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
+                 .getMatcherSet();
+         if (filters != null)
+         {
+           FeatureMatcherSetI filter = Jalview2XML
+                   .parseFilter(featureType, filters);
+           if (!filter.isEmpty())
+           {
+             fr.setFeatureFilter(featureType, filter);
+           }
+         }
+         /*
+          * restore feature colour scheme
+          */
+         Color maxColour = new Color(setting.getColour());
+         if (setting.getMincolour() != null)
+         {
+           /*
+            * minColour is always set unless a simple colour
+            * (including for colour by label though it doesn't use it)
+            */
+           Color minColour = new Color(setting.getMincolour().intValue());
+           Color noValueColour = minColour;
+           NoValueColour noColour = setting.getNoValueColour();
+           if (noColour == NoValueColour.NONE)
+           {
+             noValueColour = null;
+           }
+           else if (noColour == NoValueColour.MAX)
+           {
+             noValueColour = maxColour;
+           }
+           float min = safeFloat(safeFloat(setting.getMin()));
+           float max = setting.getMax() == null ? 1f
+                   : setting.getMax().floatValue();
+           FeatureColourI gc = new FeatureColour(maxColour, minColour,
+                   maxColour,
+                   noValueColour, min, max);
+           if (setting.getAttributeName().size() > 0)
+           {
+             gc.setAttributeName(setting.getAttributeName().toArray(
+                     new String[setting.getAttributeName().size()]));
+           }
+           if (setting.getThreshold() != null)
+           {
+             gc.setThreshold(setting.getThreshold().floatValue());
+             int threshstate = safeInt(setting.getThreshstate());
+             // -1 = None, 0 = Below, 1 = Above threshold
+             if (threshstate == 0)
+             {
+               gc.setBelowThreshold(true);
+             }
+             else if (threshstate == 1)
+             {
+               gc.setAboveThreshold(true);
+             }
+           }
+           gc.setAutoScaled(true); // default
+           if (setting.isAutoScale() != null)
+           {
+             gc.setAutoScaled(setting.isAutoScale());
+           }
+           if (setting.isColourByLabel() != null)
+           {
+             gc.setColourByLabel(setting.isColourByLabel());
+           }
+           // and put in the feature colour table.
+           featureColours.put(featureType, gc);
+         }
+         else
+         {
+           featureColours.put(featureType,
+                   new FeatureColour(maxColour));
+         }
+         renderOrder[fs] = featureType;
+         if (setting.getOrder() != null)
+         {
+           featureOrder.put(featureType, setting.getOrder().floatValue());
+         }
+         else
+         {
+           featureOrder.put(featureType, Float.valueOf(
+                   fs / jm.getFeatureSettings().getSetting().size()));
+         }
+         if (safeBoolean(setting.isDisplay()))
+         {
+           fdi.setVisible(featureType);
+         }
+       }
+       Map<String, Boolean> fgtable = new Hashtable<>();
+       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
+       {
+         Group grp = jm.getFeatureSettings().getGroup().get(gs);
+         fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
+       }
+       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
+       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
+       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+               fgtable, featureColours, 1.0f, featureOrder);
+       fr.transferSettings(frs);
+     }
+     if (view.getHiddenColumns().size() > 0)
+     {
+       for (int c = 0; c < view.getHiddenColumns().size(); c++)
+       {
+         final HiddenColumns hc = view.getHiddenColumns().get(c);
+         viewport.hideColumns(safeInt(hc.getStart()),
+                 safeInt(hc.getEnd()) /* +1 */);
+       }
+     }
+     if (view.getCalcIdParam() != null)
+     {
+       for (CalcIdParam calcIdParam : view.getCalcIdParam())
+       {
+         if (calcIdParam != null)
+         {
+           if (recoverCalcIdParam(calcIdParam, viewport))
+           {
+           }
+           else
+           {
+             warn("Couldn't recover parameters for "
+                     + calcIdParam.getCalcId());
+           }
+         }
+       }
+     }
+     af.setMenusFromViewport(viewport);
+     af.setTitle(view.getTitle());
+     // TODO: we don't need to do this if the viewport is aready visible.
+     /*
+      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
+      * has a 'cdna/protein complement' view, in which case save it in order to
+      * populate a SplitFrame once all views have been read in.
+      */
+     String complementaryViewId = view.getComplementId();
+     if (complementaryViewId == null)
+     {
+       Desktop.addInternalFrame(af, view.getTitle(),
+               safeInt(view.getWidth()), safeInt(view.getHeight()));
+       // recompute any autoannotation
+       af.alignPanel.updateAnnotation(false, true);
+       reorderAutoannotation(af, al, autoAlan);
+       af.alignPanel.alignmentChanged();
+     }
+     else
+     {
+       splitFrameCandidates.put(view, af);
+     }
+     return af;
+   }
+   /**
+    * Reads saved data to restore Colour by Annotation settings
+    * 
+    * @param viewAnnColour
+    * @param af
+    * @param al
+    * @param model
+    * @param checkGroupAnnColour
+    * @return
+    */
+   private ColourSchemeI constructAnnotationColour(
+           AnnotationColourScheme viewAnnColour, AlignFrame af,
+           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
+   {
+     boolean propagateAnnColour = false;
+     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
+             : al;
+     if (checkGroupAnnColour && al.getGroups() != null
+             && al.getGroups().size() > 0)
+     {
+       // pre 2.8.1 behaviour
+       // check to see if we should transfer annotation colours
+       propagateAnnColour = true;
+       for (SequenceGroup sg : al.getGroups())
+       {
+         if (sg.getColourScheme() instanceof AnnotationColourGradient)
+         {
+           propagateAnnColour = false;
+         }
+       }
+     }
+     /*
+      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
+      */
+     String annotationId = viewAnnColour.getAnnotation();
+     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
+     /*
+      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
+      */
+     if (matchedAnnotation == null
+             && annAlignment.getAlignmentAnnotation() != null)
+     {
+       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
+       {
+         if (annotationId
+                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
+         {
+           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
+           break;
+         }
+       }
+     }
+     if (matchedAnnotation == null)
+     {
+       System.err.println("Failed to match annotation colour scheme for "
+               + annotationId);
+       return null;
+     }
+     if (matchedAnnotation.getThreshold() == null)
+     {
+       matchedAnnotation.setThreshold(
+               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
+                       "Threshold", Color.black));
+     }
+     AnnotationColourGradient cs = null;
+     if (viewAnnColour.getColourScheme().equals("None"))
+     {
+       cs = new AnnotationColourGradient(matchedAnnotation,
+               new Color(safeInt(viewAnnColour.getMinColour())),
+               new Color(safeInt(viewAnnColour.getMaxColour())),
+               safeInt(viewAnnColour.getAboveThreshold()));
+     }
+     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
+     {
+       cs = new AnnotationColourGradient(matchedAnnotation,
+               getUserColourScheme(model, viewAnnColour.getColourScheme()),
+               safeInt(viewAnnColour.getAboveThreshold()));
+     }
+     else
+     {
+       cs = new AnnotationColourGradient(matchedAnnotation,
+               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
+                       viewAnnColour.getColourScheme()),
+               safeInt(viewAnnColour.getAboveThreshold()));
+     }
+     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
+     boolean useOriginalColours = safeBoolean(
+             viewAnnColour.isPredefinedColours());
+     cs.setSeqAssociated(perSequenceOnly);
+     cs.setPredefinedColours(useOriginalColours);
+     if (propagateAnnColour && al.getGroups() != null)
+     {
+       // Also use these settings for all the groups
+       for (int g = 0; g < al.getGroups().size(); g++)
+       {
+         SequenceGroup sg = al.getGroups().get(g);
+         if (sg.getGroupColourScheme() == null)
+         {
+           continue;
+         }
+         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
+                 matchedAnnotation, sg.getColourScheme(),
+                 safeInt(viewAnnColour.getAboveThreshold()));
+         sg.setColourScheme(groupScheme);
+         groupScheme.setSeqAssociated(perSequenceOnly);
+         groupScheme.setPredefinedColours(useOriginalColours);
+       }
+     }
+     return cs;
+   }
+   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
+           List<JvAnnotRow> autoAlan)
+   {
+     // copy over visualization settings for autocalculated annotation in the
+     // view
+     if (al.getAlignmentAnnotation() != null)
+     {
+       /**
+        * Kludge for magic autoannotation names (see JAL-811)
+        */
+       String[] magicNames = new String[] { "Consensus", "Quality",
+           "Conservation" };
+       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
+       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
+       for (String nm : magicNames)
+       {
+         visan.put(nm, nullAnnot);
+       }
+       for (JvAnnotRow auan : autoAlan)
+       {
+         visan.put(auan.template.label
+                 + (auan.template.getCalcId() == null ? ""
+                         : "\t" + auan.template.getCalcId()),
+                 auan);
+       }
+       int hSize = al.getAlignmentAnnotation().length;
+       List<JvAnnotRow> reorder = new ArrayList<>();
+       // work through any autoCalculated annotation already on the view
+       // removing it if it should be placed in a different location on the
+       // annotation panel.
+       List<String> remains = new ArrayList<>(visan.keySet());
+       for (int h = 0; h < hSize; h++)
+       {
+         jalview.datamodel.AlignmentAnnotation jalan = al
+                 .getAlignmentAnnotation()[h];
+         if (jalan.autoCalculated)
+         {
+           String k;
+           JvAnnotRow valan = visan.get(k = jalan.label);
+           if (jalan.getCalcId() != null)
+           {
+             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
+           }
+           if (valan != null)
+           {
+             // delete the auto calculated row from the alignment
+             al.deleteAnnotation(jalan, false);
+             remains.remove(k);
+             hSize--;
+             h--;
+             if (valan != nullAnnot)
+             {
+               if (jalan != valan.template)
+               {
+                 // newly created autoannotation row instance
+                 // so keep a reference to the visible annotation row
+                 // and copy over all relevant attributes
+                 if (valan.template.graphHeight >= 0)
+                 {
+                   jalan.graphHeight = valan.template.graphHeight;
+                 }
+                 jalan.visible = valan.template.visible;
+               }
+               reorder.add(new JvAnnotRow(valan.order, jalan));
+             }
+           }
+         }
+       }
+       // Add any (possibly stale) autocalculated rows that were not appended to
+       // the view during construction
+       for (String other : remains)
+       {
+         JvAnnotRow othera = visan.get(other);
+         if (othera != nullAnnot && othera.template.getCalcId() != null
+                 && othera.template.getCalcId().length() > 0)
+         {
+           reorder.add(othera);
+         }
+       }
+       // now put the automatic annotation in its correct place
+       int s = 0, srt[] = new int[reorder.size()];
+       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
+       for (JvAnnotRow jvar : reorder)
+       {
+         rws[s] = jvar;
+         srt[s++] = jvar.order;
+       }
+       reorder.clear();
+       jalview.util.QuickSort.sort(srt, rws);
+       // and re-insert the annotation at its correct position
+       for (JvAnnotRow jvar : rws)
+       {
+         al.addAnnotation(jvar.template, jvar.order);
+       }
+       af.alignPanel.adjustAnnotationHeight();
+     }
+   }
+   Hashtable skipList = null;
+   /**
+    * TODO remove this method
+    * 
+    * @param view
+    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
+    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
+    *         throw new Error("Implementation Error. No skipList defined for this
+    *         Jalview2XML instance."); } return (AlignFrame)
+    *         skipList.get(view.getSequenceSetId()); }
+    */
+   /**
+    * Check if the Jalview view contained in object should be skipped or not.
+    * 
+    * @param object
+    * @return true if view's sequenceSetId is a key in skipList
+    */
+   private boolean skipViewport(JalviewModel object)
+   {
+     if (skipList == null)
+     {
+       return false;
+     }
+     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);
 -      }
++      debug("Skipping sequence set id " + id);
+       return true;
+     }
+     return false;
+   }
+   public void addToSkipList(AlignFrame af)
+   {
+     if (skipList == null)
+     {
+       skipList = new Hashtable();
+     }
+     skipList.put(af.getViewport().getSequenceSetId(), af);
+   }
+   public void clearSkipList()
+   {
+     if (skipList != null)
+     {
+       skipList.clear();
+       skipList = null;
+     }
+   }
+   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
+           boolean ignoreUnrefed, String uniqueSeqSetId)
+   {
+     jalview.datamodel.AlignmentI ds = getDatasetFor(
+             vamsasSet.getDatasetId());
+     AlignmentI xtant_ds = ds;
+     if (xtant_ds == null)
+     {
+       // good chance we are about to create a new dataset, but check if we've
+       // seen some of the dataset sequence IDs before.
+       // TODO: skip this check if we are working with project generated by
+       // version 2.11 or later
+       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
+       if (xtant_ds != null)
+       {
+         ds = xtant_ds;
+         addDatasetRef(vamsasSet.getDatasetId(), ds);
+       }
+     }
+     Vector dseqs = null;
+     if (!ignoreUnrefed)
+     {
+       // recovering an alignment View
+       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
+       if (seqSetDS != null)
+       {
+         if (ds != null && ds != seqSetDS)
+         {
+           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
+                   + " - CDS/Protein crossreference data may be lost");
+           if (xtant_ds != null)
+           {
+             // This can only happen if the unique sequence set ID was bound to a
+             // dataset that did not contain any of the sequences in the view
+             // currently being restored.
+             warn("JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
+           }
+         }
+         ds = seqSetDS;
+         addDatasetRef(vamsasSet.getDatasetId(), ds);
+       }
+     }
+     if (ds == null)
+     {
+       // try even harder to restore dataset
+       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
+       // create a list of new dataset sequences
+       dseqs = new Vector();
+     }
+     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
+     {
+       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
+       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
+     }
+     // create a new dataset
+     if (ds == 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));
+       addDatasetRef(vamsasSet.getDatasetId(), ds);
+     }
+     // set the dataset for the newly imported alignment.
+     if (al.getDataset() == null && !ignoreUnrefed)
+     {
+       al.setDataset(ds);
+       // register dataset for the alignment's uniqueSeqSetId for legacy projects
+       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
+     }
+     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
+   }
+   /**
+    * XML dataset sequence ID to materialised dataset reference
+    */
+   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
+   /**
+    * @return the first materialised dataset reference containing a dataset
+    *         sequence referenced in the given view
+    * @param list
+    *          - sequences from the view
+    */
+   AlignmentI checkIfHasDataset(List<Sequence> list)
+   {
+     for (Sequence restoredSeq : list)
+     {
+       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
+       if (datasetFor != null)
+       {
+         return datasetFor;
+       }
+     }
+     return null;
+   }
+   /**
+    * Register ds as the containing dataset for the dataset sequences referenced
+    * by sequences in list
+    * 
+    * @param list
+    *          - sequences in a view
+    * @param ds
+    */
+   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
+   {
+     for (Sequence restoredSeq : list)
+     {
+       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
+       if (prevDS != null && prevDS != ds)
+       {
+         warn("Dataset sequence appears in many datasets: "
+                 + restoredSeq.getDsseqid());
+         // TODO: try to merge!
+       }
+     }
+   }
+   /**
+    * 
+    * @param vamsasSeq
+    *          sequence definition to create/merge dataset sequence for
+    * @param ds
+    *          dataset alignment
+    * @param dseqs
+    *          vector to add new dataset sequence to
+    * @param ignoreUnrefed
+    *          - when true, don't create new sequences from vamsasSeq if it's id
+    *          doesn't already have an asssociated Jalview sequence.
+    * @param vseqpos
+    *          - used to reorder the sequence in the alignment according to the
+    *          vamsasSeq array ordering, to preserve ordering of dataset
+    */
+   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
+           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
+   {
+     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
+     // xRef Codon Maps
+     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
+     boolean reorder = false;
+     SequenceI dsq = null;
+     if (sq != null && sq.getDatasetSequence() != null)
+     {
+       dsq = sq.getDatasetSequence();
+     }
+     else
+     {
+       reorder = true;
+     }
+     if (sq == null && ignoreUnrefed)
+     {
+       return;
+     }
+     String sqid = vamsasSeq.getDsseqid();
+     if (dsq == null)
+     {
+       // need to create or add a new dataset sequence reference to this sequence
+       if (sqid != null)
+       {
+         dsq = seqRefIds.get(sqid);
+       }
+       // check again
+       if (dsq == null)
+       {
+         // make a new dataset sequence
+         dsq = sq.createDatasetSequence();
+         if (sqid == null)
+         {
+           // make up a new dataset reference for this sequence
+           sqid = seqHash(dsq);
+         }
+         dsq.setVamsasId(uniqueSetSuffix + sqid);
+         seqRefIds.put(sqid, dsq);
+         if (ds == null)
+         {
+           if (dseqs != null)
+           {
+             dseqs.addElement(dsq);
+           }
+         }
+         else
+         {
+           ds.addSequence(dsq);
+         }
+       }
+       else
+       {
+         if (sq != dsq)
+         { // make this dataset sequence sq's dataset sequence
+           sq.setDatasetSequence(dsq);
+           // and update the current dataset alignment
+           if (ds == null)
+           {
+             if (dseqs != null)
+             {
+               if (!dseqs.contains(dsq))
+               {
+                 dseqs.add(dsq);
+               }
+             }
+             else
+             {
+               if (ds.findIndex(dsq) < 0)
+               {
+                 ds.addSequence(dsq);
+               }
+             }
+           }
+         }
+       }
+     }
+     // TODO: refactor this as a merge dataset sequence function
+     // now check that sq (the dataset sequence) sequence really is the union of
+     // all references to it
+     // boolean pre = sq.getStart() < dsq.getStart();
+     // boolean post = sq.getEnd() > dsq.getEnd();
+     // if (pre || post)
+     if (sq != dsq)
+     {
+       // StringBuffer sb = new StringBuffer();
+       String newres = jalview.analysis.AlignSeq.extractGaps(
+               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
+       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
+               && newres.length() > dsq.getLength())
+       {
+         // Update with the longer sequence.
+         synchronized (dsq)
+         {
+           /*
+            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
+            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
+            * sb.append(newres.substring(newres.length() - sq.getEnd() -
+            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
+            */
+           dsq.setSequence(newres);
+         }
+         // TODO: merges will never happen if we 'know' we have the real dataset
+         // sequence - this should be detected when id==dssid
+         System.err.println(
+                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
+         // + (pre ? "prepended" : "") + " "
+         // + (post ? "appended" : ""));
+       }
+     }
+     else
+     {
+       // sequence refs are identical. We may need to update the existing dataset
+       // alignment with this one, though.
+       if (ds != null && dseqs == null)
+       {
+         int opos = ds.findIndex(dsq);
+         SequenceI tseq = null;
+         if (opos != -1 && vseqpos != opos)
+         {
+           // remove from old position
+           ds.deleteSequence(dsq);
+         }
+         if (vseqpos < ds.getHeight())
+         {
+           if (vseqpos != opos)
+           {
+             // save sequence at destination position
+             tseq = ds.getSequenceAt(vseqpos);
+             ds.replaceSequenceAt(vseqpos, dsq);
+             ds.addSequence(tseq);
+           }
+         }
+         else
+         {
+           ds.addSequence(dsq);
+         }
+       }
+     }
+   }
+   /*
+    * TODO use AlignmentI here and in related methods - needs
+    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
+    */
+   Hashtable<String, AlignmentI> datasetIds = null;
+   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
+   private AlignmentI getDatasetFor(String datasetId)
+   {
+     if (datasetIds == null)
+     {
+       datasetIds = new Hashtable<>();
+       return null;
+     }
+     if (datasetIds.containsKey(datasetId))
+     {
+       return datasetIds.get(datasetId);
+     }
+     return null;
+   }
+   private void addDatasetRef(String datasetId, AlignmentI dataset)
+   {
+     if (datasetIds == null)
+     {
+       datasetIds = new Hashtable<>();
+     }
+     datasetIds.put(datasetId, dataset);
+   }
+   /**
+    * make a new dataset ID for this jalview dataset alignment
+    * 
+    * @param dataset
+    * @return
+    */
+   private String getDatasetIdRef(AlignmentI dataset)
+   {
+     if (dataset.getDataset() != null)
+     {
+       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
+     }
+     String datasetId = makeHashCode(dataset, null);
+     if (datasetId == null)
+     {
+       // make a new datasetId and record it
+       if (dataset2Ids == null)
+       {
+         dataset2Ids = new IdentityHashMap<>();
+       }
+       else
+       {
+         datasetId = dataset2Ids.get(dataset);
+       }
+       if (datasetId == null)
+       {
+         datasetId = "ds" + dataset2Ids.size() + 1;
+         dataset2Ids.put(dataset, datasetId);
+       }
+     }
+     return datasetId;
+   }
+   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
+   {
+     for (int d = 0; d < sequence.getDBRef().size(); d++)
+     {
+       DBRef dr = sequence.getDBRef().get(d);
+       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
+               dr.getSource(), dr.getVersion(), dr.getAccessionId());
+       if (dr.getMapping() != null)
+       {
+         entry.setMap(addMapping(dr.getMapping()));
+       }
+       datasetSequence.addDBRef(entry);
+     }
+   }
+   private jalview.datamodel.Mapping addMapping(Mapping m)
+   {
+     SequenceI dsto = null;
+     // Mapping m = dr.getMapping();
+     int fr[] = new int[m.getMapListFrom().size() * 2];
+     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
+     for (int _i = 0; from.hasNext(); _i += 2)
+     {
+       MapListFrom mf = from.next();
+       fr[_i] = mf.getStart();
+       fr[_i + 1] = mf.getEnd();
+     }
+     int fto[] = new int[m.getMapListTo().size() * 2];
+     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
+     for (int _i = 0; to.hasNext(); _i += 2)
+     {
+       MapListTo mf = to.next();
+       fto[_i] = mf.getStart();
+       fto[_i + 1] = mf.getEnd();
+     }
+     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
+             fto, m.getMapFromUnit().intValue(),
+             m.getMapToUnit().intValue());
+     /*
+      * (optional) choice of dseqFor or Sequence
+      */
+     if (m.getDseqFor() != null)
+     {
+       String dsfor = m.getDseqFor();
+       if (seqRefIds.containsKey(dsfor))
+       {
+         /*
+          * recover from hash
+          */
+         jmap.setTo(seqRefIds.get(dsfor));
+       }
+       else
+       {
+         frefedSequence.add(newMappingRef(dsfor, jmap));
+       }
+     }
+     else if (m.getSequence() != null)
+     {
+       /*
+        * local sequence definition
+        */
+       Sequence ms = m.getSequence();
+       SequenceI djs = null;
+       String sqid = ms.getDsseqid();
+       if (sqid != null && sqid.length() > 0)
+       {
+         /*
+          * recover dataset sequence
+          */
+         djs = seqRefIds.get(sqid);
+       }
+       else
+       {
+         System.err.println(
+                 "Warning - making up dataset sequence id for DbRef sequence map reference");
+         sqid = ((Object) ms).toString(); // make up a new hascode for
+         // undefined dataset sequence hash
+         // (unlikely to happen)
+       }
+       if (djs == null)
+       {
+         /**
+          * make a new dataset sequence and add it to refIds hash
+          */
+         djs = new jalview.datamodel.Sequence(ms.getName(),
+                 ms.getSequence());
+         djs.setStart(jmap.getMap().getToLowest());
+         djs.setEnd(jmap.getMap().getToHighest());
+         djs.setVamsasId(uniqueSetSuffix + sqid);
+         jmap.setTo(djs);
+         incompleteSeqs.put(sqid, djs);
+         seqRefIds.put(sqid, djs);
 -
+       }
+       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
+       addDBRefs(djs, ms);
+     }
+     return jmap;
+   }
+   /**
+    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
+    * view as XML (but not to file), and then reloading it
+    * 
+    * @param ap
+    * @return
+    */
+   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
+   {
+     initSeqRefs();
+     JalviewModel jm = saveState(ap, null, null, null);
+     addDatasetRef(
+             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
+             ap.getAlignment().getDataset());
+     uniqueSetSuffix = "";
+     // jm.getJalviewModelSequence().getViewport(0).setId(null);
+     jm.getViewport().get(0).setId(null);
+     // we don't overwrite the view we just copied
+     if (this.frefedSequence == null)
+     {
+       frefedSequence = new Vector<>();
+     }
+     viewportsAdded.clear();
+     AlignFrame af = loadFromObject(jm, null, false, null);
+     af.getAlignPanels().clear();
+     af.closeMenuItem_actionPerformed(true);
+     /*
+      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
+      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
+      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
+      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
+      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
+      */
+     return af.alignPanel;
+   }
+   private Hashtable jvids2vobj;
+   private void warn(String msg)
+   {
+     warn(msg, null);
+   }
+   private void warn(String msg, Exception e)
+   {
+     if (Cache.log != null)
+     {
+       if (e != null)
+       {
+         Cache.log.warn(msg, e);
+       }
+       else
+       {
+         Cache.log.warn(msg);
+       }
+     }
+     else
+     {
+       System.err.println("Warning: " + msg);
+       if (e != null)
+       {
+         e.printStackTrace();
+       }
+     }
+   }
+   private void debug(String string)
+   {
+     debug(string, null);
+   }
+   private void debug(String msg, Exception e)
+   {
+     if (Cache.log != null)
+     {
+       if (e != null)
+       {
+         Cache.log.debug(msg, e);
+       }
+       else
+       {
+         Cache.log.debug(msg);
+       }
+     }
+     else
+     {
+       System.err.println("Warning: " + msg);
+       if (e != null)
+       {
+         e.printStackTrace();
+       }
+     }
+   }
+   /**
+    * set the object to ID mapping tables used to write/recover objects and XML
+    * ID strings for the jalview project. If external tables are provided then
+    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
+    * object goes out of scope. - also populates the datasetIds hashtable with
+    * alignment objects containing dataset sequences
+    * 
+    * @param vobj2jv
+    *          Map from ID strings to jalview datamodel
+    * @param jv2vobj
+    *          Map from jalview datamodel to ID strings
+    * 
+    * 
+    */
+   public void setObjectMappingTables(Hashtable vobj2jv,
+           IdentityHashMap jv2vobj)
+   {
+     this.jv2vobj = jv2vobj;
+     this.vobj2jv = vobj2jv;
+     Iterator ds = jv2vobj.keySet().iterator();
+     String id;
+     while (ds.hasNext())
+     {
+       Object jvobj = ds.next();
+       id = jv2vobj.get(jvobj).toString();
+       if (jvobj instanceof jalview.datamodel.Alignment)
+       {
+         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
+         {
+           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
+         }
+       }
+       else if (jvobj instanceof jalview.datamodel.Sequence)
+       {
+         // register sequence object so the XML parser can recover it.
+         if (seqRefIds == null)
+         {
+           seqRefIds = new HashMap<>();
+         }
+         if (seqsToIds == null)
+         {
+           seqsToIds = new IdentityHashMap<>();
+         }
+         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
+         seqsToIds.put((SequenceI) jvobj, id);
+       }
+       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
+       {
+         String anid;
+         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
+         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
+         if (jvann.annotationId == null)
+         {
+           jvann.annotationId = anid;
+         }
+         if (!jvann.annotationId.equals(anid))
+         {
+           // TODO verify that this is the correct behaviour
+           this.warn("Overriding Annotation ID for " + anid
+                   + " from different id : " + jvann.annotationId);
+           jvann.annotationId = anid;
+         }
+       }
+       else if (jvobj instanceof String)
+       {
+         if (jvids2vobj == null)
+         {
+           jvids2vobj = new Hashtable();
+           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
+         }
+       }
+       else
+       {
 -        Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
++        debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
+       }
+     }
+   }
+   /**
+    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
+    * objects created from the project archive. If string is null (default for
+    * construction) then suffix will be set automatically.
+    * 
+    * @param string
+    */
+   public void setUniqueSetSuffix(String string)
+   {
+     uniqueSetSuffix = string;
+   }
+   /**
+    * uses skipList2 as the skipList for skipping views on sequence sets
+    * associated with keys in the skipList
+    * 
+    * @param skipList2
+    */
+   public void setSkipList(Hashtable skipList2)
+   {
+     skipList = skipList2;
+   }
+   /**
+    * Reads the jar entry of given name and returns its contents, or null if the
+    * entry is not found.
+    * 
+    * @param jprovider
+    * @param jarEntryName
+    * @return
+    */
+   protected String readJarEntry(jarInputStreamProvider jprovider,
+           String jarEntryName)
+   {
+     String result = null;
+     BufferedReader in = null;
+     try
+     {
+       /*
+        * Reopen the jar input stream and traverse its entries to find a matching
+        * name
+        */
+       JarInputStream jin = jprovider.getJarInputStream();
+       JarEntry entry = null;
+       do
+       {
+         entry = jin.getNextJarEntry();
+       } while (entry != null && !entry.getName().equals(jarEntryName));
+       if (entry != null)
+       {
+         StringBuilder out = new StringBuilder(256);
+         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+         String data;
+         while ((data = in.readLine()) != null)
+         {
+           out.append(data);
+         }
+         result = out.toString();
+       }
+       else
+       {
+         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
+       }
+     } catch (Exception ex)
+     {
+       ex.printStackTrace();
+     } finally
+     {
+       if (in != null)
+       {
+         try
+         {
+           in.close();
+         } catch (IOException e)
+         {
+           // ignore
+         }
+       }
+     }
+     return result;
+   }
+   /**
+    * Returns an incrementing counter (0, 1, 2...)
+    * 
+    * @return
+    */
+   private synchronized int nextCounter()
+   {
+     return counter++;
+   }
+   /**
+    * Loads any saved PCA viewers
+    * 
+    * @param jms
+    * @param ap
+    */
+   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
+   {
+     try
+     {
+       List<PcaViewer> pcaviewers = model.getPcaViewer();
+       for (PcaViewer viewer : pcaviewers)
+       {
+         String modelName = viewer.getScoreModelName();
+         SimilarityParamsI params = new SimilarityParams(
+                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
+                 viewer.isIncludeGaps(),
+                 viewer.isDenominateByShortestLength());
+         /*
+          * create the panel (without computing the PCA)
+          */
+         PCAPanel panel = new PCAPanel(ap, modelName, params);
+         panel.setTitle(viewer.getTitle());
+         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
+                 viewer.getWidth(), viewer.getHeight()));
+         boolean showLabels = viewer.isShowLabels();
+         panel.setShowLabels(showLabels);
+         panel.getRotatableCanvas().setShowLabels(showLabels);
+         panel.getRotatableCanvas()
+                 .setBgColour(new Color(viewer.getBgColour()));
+         panel.getRotatableCanvas()
+                 .setApplyToAllViews(viewer.isLinkToAllViews());
+         /*
+          * load PCA output data
+          */
+         ScoreModelI scoreModel = ScoreModels.getInstance()
+                 .getScoreModel(modelName, ap);
+         PCA pca = new PCA(null, scoreModel, params);
+         PcaDataType pcaData = viewer.getPcaData();
+         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
+         pca.setPairwiseScores(pairwise);
+         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
+         pca.setTridiagonal(triDiag);
+         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
+         pca.setEigenmatrix(result);
+         panel.getPcaModel().setPCA(pca);
+         /*
+          * we haven't saved the input data! (JAL-2647 to do)
+          */
+         panel.setInputData(null);
+         /*
+          * add the sequence points for the PCA display
+          */
+         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
+         for (SequencePoint sp : viewer.getSequencePoint())
+         {
+           String seqId = sp.getSequenceRef();
+           SequenceI seq = seqRefIds.get(seqId);
+           if (seq == null)
+           {
+             throw new IllegalStateException(
+                     "Unmatched seqref for PCA: " + seqId);
+           }
+           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
+           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
+                   seq, pt);
+           seqPoints.add(seqPoint);
+         }
+         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
+         /*
+          * set min-max ranges and scale after setPoints (which recomputes them)
+          */
+         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
+         SeqPointMin spMin = viewer.getSeqPointMin();
+         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
+             spMin.getZPos() };
+         SeqPointMax spMax = viewer.getSeqPointMax();
+         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
+             spMax.getZPos() };
+         panel.getRotatableCanvas().setSeqMinMax(min, max);
+         // todo: hold points list in PCAModel only
+         panel.getPcaModel().setSequencePoints(seqPoints);
+         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
+         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
+         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
+         // is this duplication needed?
+         panel.setTop(seqPoints.size() - 1);
+         panel.getPcaModel().setTop(seqPoints.size() - 1);
+         /*
+          * add the axes' end points for the display
+          */
+         for (int i = 0; i < 3; i++)
+         {
+           Axis axis = viewer.getAxis().get(i);
+           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
+                   axis.getXPos(), axis.getYPos(), axis.getZPos());
+         }
+         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+                 "label.calc_title", "PCA", modelName), 475, 450);
+       }
+     } catch (Exception ex)
+     {
+       Cache.log.error("Error loading PCA: " + ex.toString());
+     }
+   }
+   /**
+    * Populates an XML model of the feature colour scheme for one feature type
+    * 
+    * @param featureType
+    * @param fcol
+    * @return
+    */
+   public static Colour marshalColour(
+           String featureType, FeatureColourI fcol)
+   {
+     Colour col = new Colour();
+     if (fcol.isSimpleColour())
+     {
+       col.setRGB(Format.getHexString(fcol.getColour()));
+     }
+     else
+     {
+       col.setRGB(Format.getHexString(fcol.getMaxColour()));
+       col.setMin(fcol.getMin());
+       col.setMax(fcol.getMax());
+       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
+       col.setAutoScale(fcol.isAutoScaled());
+       col.setThreshold(fcol.getThreshold());
+       col.setColourByLabel(fcol.isColourByLabel());
+       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
+               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
+                       : ThresholdType.NONE));
+       if (fcol.isColourByAttribute())
+       {
+         final String[] attName = fcol.getAttributeName();
+         col.getAttributeName().add(attName[0]);
+         if (attName.length > 1)
+         {
+           col.getAttributeName().add(attName[1]);
+         }
+       }
+       Color noColour = fcol.getNoColour();
+       if (noColour == null)
+       {
+         col.setNoValueColour(NoValueColour.NONE);
+       }
+       else if (noColour == fcol.getMaxColour())
+       {
+         col.setNoValueColour(NoValueColour.MAX);
+       }
+       else
+       {
+         col.setNoValueColour(NoValueColour.MIN);
+       }
+     }
+     col.setName(featureType);
+     return col;
+   }
+   /**
+    * Populates an XML model of the feature filter(s) for one feature type
+    * 
+    * @param firstMatcher
+    *          the first (or only) match condition)
+    * @param filter
+    *          remaining match conditions (if any)
+    * @param and
+    *          if true, conditions are and-ed, else or-ed
+    */
+   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
+           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
+           boolean and)
+   {
+     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
+   
+     if (filters.hasNext())
+     {
+       /*
+        * compound matcher
+        */
+       CompoundMatcher compound = new CompoundMatcher();
+       compound.setAnd(and);
+       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
+               firstMatcher, Collections.emptyIterator(), and);
+       // compound.addMatcherSet(matcher1);
+       compound.getMatcherSet().add(matcher1);
+       FeatureMatcherI nextMatcher = filters.next();
+       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
+               nextMatcher, filters, and);
+       // compound.addMatcherSet(matcher2);
+       compound.getMatcherSet().add(matcher2);
+       result.setCompoundMatcher(compound);
+     }
+     else
+     {
+       /*
+        * single condition matcher
+        */
+       // MatchCondition matcherModel = new MatchCondition();
+       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
+       matcherModel.setCondition(
+               firstMatcher.getMatcher().getCondition().getStableName());
+       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
+       if (firstMatcher.isByAttribute())
+       {
+         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
+         // matcherModel.setAttributeName(firstMatcher.getAttribute());
+         String[] attName = firstMatcher.getAttribute();
+         matcherModel.getAttributeName().add(attName[0]); // attribute
+         if (attName.length > 1)
+         {
+           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
+         }
+       }
+       else if (firstMatcher.isByLabel())
+       {
+         matcherModel.setBy(FilterBy.BY_LABEL);
+       }
+       else if (firstMatcher.isByScore())
+       {
+         matcherModel.setBy(FilterBy.BY_SCORE);
+       }
+       result.setMatchCondition(matcherModel);
+     }
+   
+     return result;
+   }
+   /**
+    * Loads one XML model of a feature filter to a Jalview object
+    * 
+    * @param featureType
+    * @param matcherSetModel
+    * @return
+    */
+   public static FeatureMatcherSetI parseFilter(
+           String featureType,
+           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
+   {
+     FeatureMatcherSetI result = new FeatureMatcherSet();
+     try
+     {
+       parseFilterConditions(result, matcherSetModel, true);
+     } catch (IllegalStateException e)
+     {
+       // mixing AND and OR conditions perhaps
+       System.err.println(
+               String.format("Error reading filter conditions for '%s': %s",
+                       featureType, e.getMessage()));
+       // return as much as was parsed up to the error
+     }
+   
+     return result;
+   }
+   /**
+    * Adds feature match conditions to matcherSet as unmarshalled from XML
+    * (possibly recursively for compound conditions)
+    * 
+    * @param matcherSet
+    * @param matcherSetModel
+    * @param and
+    *          if true, multiple conditions are AND-ed, else they are OR-ed
+    * @throws IllegalStateException
+    *           if AND and OR conditions are mixed
+    */
+   protected static void parseFilterConditions(
+           FeatureMatcherSetI matcherSet,
+           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
+           boolean and)
+   {
+     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
+             .getMatchCondition();
+     if (mc != null)
+     {
+       /*
+        * single condition
+        */
+       FilterBy filterBy = mc.getBy();
+       Condition cond = Condition.fromString(mc.getCondition());
+       String pattern = mc.getValue();
+       FeatureMatcherI matchCondition = null;
+       if (filterBy == FilterBy.BY_LABEL)
+       {
+         matchCondition = FeatureMatcher.byLabel(cond, pattern);
+       }
+       else if (filterBy == FilterBy.BY_SCORE)
+       {
+         matchCondition = FeatureMatcher.byScore(cond, pattern);
+   
+       }
+       else if (filterBy == FilterBy.BY_ATTRIBUTE)
+       {
+         final List<String> attributeName = mc.getAttributeName();
+         String[] attNames = attributeName
+                 .toArray(new String[attributeName.size()]);
+         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
+                 attNames);
+       }
+   
+       /*
+        * note this throws IllegalStateException if AND-ing to a 
+        * previously OR-ed compound condition, or vice versa
+        */
+       if (and)
+       {
+         matcherSet.and(matchCondition);
+       }
+       else
+       {
+         matcherSet.or(matchCondition);
+       }
+     }
+     else
+     {
+       /*
+        * compound condition
+        */
+       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
+               .getCompoundMatcher().getMatcherSet();
+       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
+       if (matchers.size() == 2)
+       {
+         parseFilterConditions(matcherSet, matchers.get(0), anded);
+         parseFilterConditions(matcherSet, matchers.get(1), anded);
+       }
+       else
+       {
+         System.err.println("Malformed compound filter condition");
+       }
+     }
+   }
+   /**
+    * Loads one XML model of a feature colour to a Jalview object
+    * 
+    * @param colourModel
+    * @return
+    */
+   public static FeatureColourI parseColour(Colour colourModel)
+   {
+     FeatureColourI colour = null;
+   
+     if (colourModel.getMax() != null)
+     {
+       Color mincol = null;
+       Color maxcol = null;
+       Color noValueColour = null;
+   
+       try
+       {
+         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
+         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
+       } catch (Exception e)
+       {
 -        Cache.log.warn("Couldn't parse out graduated feature color.", e);
++        if (Cache.log != null)
++        {
++          Cache.log.warn("Couldn't parse out graduated feature color.", e);
++        }
+       }
+   
+       NoValueColour noCol = colourModel.getNoValueColour();
+       if (noCol == NoValueColour.MIN)
+       {
+         noValueColour = mincol;
+       }
+       else if (noCol == NoValueColour.MAX)
+       {
+         noValueColour = maxcol;
+       }
+   
+       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
+               safeFloat(colourModel.getMin()),
+               safeFloat(colourModel.getMax()));
+       final List<String> attributeName = colourModel.getAttributeName();
+       String[] attributes = attributeName
+               .toArray(new String[attributeName.size()]);
+       if (attributes != null && attributes.length > 0)
+       {
+         colour.setAttributeName(attributes);
+       }
+       if (colourModel.isAutoScale() != null)
+       {
+         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
+       }
+       if (colourModel.isColourByLabel() != null)
+       {
+         colour.setColourByLabel(
+                 colourModel.isColourByLabel().booleanValue());
+       }
+       if (colourModel.getThreshold() != null)
+       {
+         colour.setThreshold(colourModel.getThreshold().floatValue());
+       }
+       ThresholdType ttyp = colourModel.getThreshType();
+       if (ttyp == ThresholdType.ABOVE)
+       {
+         colour.setAboveThreshold(true);
+       }
+       else if (ttyp == ThresholdType.BELOW)
+       {
+         colour.setBelowThreshold(true);
+       }
+     }
+     else
+     {
+       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
+       colour = new FeatureColour(color);
+     }
+   
+     return colour;
+   }
+ }
@@@ -43,8 -43,7 +43,9 @@@ public enum JalviewColourSchem
    PurinePyrimidine("Purine/Pyrimidine", PurinePyrimidineColourScheme.class),
    RNAHelices("RNA Helices", RNAHelicesColour.class),
    TCoffee("T-Coffee Scores", TCoffeeColourScheme.class),
 -  IdColour("Sequence ID", IdColourScheme.class);
++  IdColour("Sequence ID", IdColourScheme.class),
 +  HMMERU("HMMER-Uniprot", HmmerGlobalBackground.class),
 +  HMMERA("HMMER-Alignment", HmmerLocalBackground.class);
    // RNAInteraction("RNA Interaction type", RNAInteractionColourScheme.class)
  
    private String name;
Simple merge
@@@ -3063,25 -2957,67 +3065,89 @@@ public abstract class AlignmentViewpor
      return currentTree;
    }
  
 +  @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
+    */
+   private boolean needToUpdateStructureViews = false;
+   @Override
+   public boolean isUpdateStructures()
+   {
+     return needToUpdateStructureViews;
+   }
+   @Override
+   public void setUpdateStructures(boolean update)
+   {
+     needToUpdateStructureViews = update;
+   }
+   @Override
+   public boolean needToUpdateStructureViews()
+   {
+     boolean update = needToUpdateStructureViews;
+     needToUpdateStructureViews = false;
+     return update;
+   }
+   @Override
+   public void addSequenceGroup(SequenceGroup sequenceGroup)
+   {
+     alignment.addGroup(sequenceGroup);
+     Color col = sequenceGroup.idColour;
+     if (col != null)
+     {
+       col = col.brighter();
+       for (SequenceI sq : sequenceGroup.getSequences())
+       {
+         setSequenceColour(sq, col);
+       }
+     }
+     if (codingComplement != null)
+     {
+       SequenceGroup mappedGroup = MappingUtils
+               .mapSequenceGroup(sequenceGroup, this, codingComplement);
+       if (mappedGroup.getSequences().size() > 0)
+       {
+         codingComplement.getAlignment().addGroup(mappedGroup);
+         if (col != null)
+         {
+           for (SequenceI seq : mappedGroup.getSequences())
+           {
+             codingComplement.setSequenceColour(seq, col);
+           }
+         }
+       }
+       // propagate the structure view update flag according to our own setting
+       codingComplement.setUpdateStructures(needToUpdateStructureViews);
+     }
+   }
  }
@@@ -14,8 -14,10 +14,11 @@@ import jalview.schemes.NucleotideColour
  import jalview.schemes.PIDColourScheme;
  
  import java.awt.Color;
+ import java.util.ArrayList;
  import java.util.Collections;
 +
+ import java.util.List;
  import org.testng.annotations.Test;
  
  import junit.extensions.PA;
Simple merge
index 0000000,d902fa2..6b14282
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1182 +1,1239 @@@
+ /*
+  * 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.project;
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertFalse;
+ import static org.testng.Assert.assertNotNull;
+ import static org.testng.Assert.assertNull;
+ import static org.testng.Assert.assertSame;
+ import static org.testng.Assert.assertTrue;
+ import jalview.analysis.scoremodels.SimilarityParams;
+ import jalview.api.AlignViewportI;
+ import jalview.api.AlignmentViewPanel;
+ import jalview.api.FeatureColourI;
+ import jalview.api.ViewStyleI;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
++import jalview.datamodel.HiddenMarkovModel;
+ import jalview.datamodel.HiddenSequences;
++import jalview.datamodel.Mapping;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.PDBEntry.Type;
+ import jalview.datamodel.SequenceCollectionI;
+ import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.features.FeatureMatcher;
+ 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.FeatureRenderer;
+ import jalview.gui.JvOptionPane;
+ import jalview.gui.PCAPanel;
+ import jalview.gui.PopupMenu;
+ import jalview.gui.SliderPanel;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileFormat;
+ import jalview.io.FileLoader;
+ import jalview.io.Jalview2xmlBase;
+ import jalview.renderer.ResidueShaderI;
+ import jalview.schemes.AnnotationColourGradient;
+ import jalview.schemes.BuriedColourScheme;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemeProperty;
+ import jalview.schemes.FeatureColour;
+ import jalview.schemes.JalviewColourScheme;
+ import jalview.schemes.RNAHelicesColour;
+ import jalview.schemes.StrandColourScheme;
+ import jalview.schemes.TCoffeeColourScheme;
+ import jalview.structure.StructureImportSettings;
+ import jalview.util.matcher.Condition;
+ import jalview.viewmodel.AlignmentViewport;
+ import java.awt.Color;
+ import java.io.File;
+ import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import javax.swing.JInternalFrame;
+ import org.testng.Assert;
+ import org.testng.AssertJUnit;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
++import junit.extensions.PA;
++
+ @Test(singleThreaded = true)
+ public class Jalview2xmlTests extends Jalview2xmlBase
+ {
+   @Override
+   @BeforeClass(alwaysRun = true)
+   public void setUpJvOptionPane()
+   {
+     JvOptionPane.setInteractiveMode(false);
+     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+   }
+   @Test(groups = { "Functional" })
+   public void testRNAStructureRecovery() throws Exception
+   {
+     String inFile = "examples/RF00031_folded.stk";
+     String tfile = File.createTempFile("JalviewTest", ".jvp")
+             .getAbsolutePath();
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Didn't read input file " + inFile);
+     int olddsann = countDsAnn(af.getViewport());
+     assertTrue(olddsann > 0, "Didn't find any dataset annotations");
+     af.changeColour_actionPerformed(
+             JalviewColourScheme.RNAHelices.toString());
+     assertTrue(
+             af.getViewport()
+                     .getGlobalColourScheme() instanceof RNAHelicesColour,
+             "Couldn't apply RNA helices colourscheme");
+     assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
+             "Failed to store as a project.");
+     af.closeMenuItem_actionPerformed(true);
+     af = null;
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Failed to import new project");
+     int newdsann = countDsAnn(af.getViewport());
+     assertEquals(olddsann, newdsann,
+             "Differing numbers of dataset sequence annotation\nOriginally "
+                     + olddsann + " and now " + newdsann);
+     System.out.println(
+             "Read in same number of annotations as originally present ("
+                     + olddsann + ")");
+     assertTrue(
+             af.getViewport()
+                     .getGlobalColourScheme() instanceof RNAHelicesColour,
+             "RNA helices colourscheme was not applied on import.");
+   }
+   @Test(groups = { "Functional" })
+   public void testTCoffeeScores() throws Exception
+   {
+     String inFile = "examples/uniref50.fa",
+             inAnnot = "examples/uniref50.score_ascii";
+     String tfile = File.createTempFile("JalviewTest", ".jvp")
+             .getAbsolutePath();
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Didn't read input file " + inFile);
+     af.loadJalviewDataFile(inAnnot, DataSourceType.FILE, null, null);
+     AlignViewport viewport = af.getViewport();
+     assertSame(viewport.getGlobalColourScheme().getClass(),
+             TCoffeeColourScheme.class, "Didn't set T-coffee colourscheme");
+     assertNotNull(
+             ColourSchemeProperty.getColourScheme(viewport,
+                     viewport.getAlignment(),
+                     viewport.getGlobalColourScheme()
+                             .getSchemeName()),
+             "Recognise T-Coffee score from string");
+     assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
+             "Failed to store as a project.");
+     af.closeMenuItem_actionPerformed(true);
+     af = null;
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Failed to import new project");
+     assertSame(af.getViewport().getGlobalColourScheme().getClass(),
+             TCoffeeColourScheme.class,
+             "Didn't set T-coffee colourscheme for imported project.");
+     System.out.println(
+             "T-Coffee score shading successfully recovered from project.");
+   }
+   @Test(groups = { "Functional" })
+   public void testColourByAnnotScores() throws Exception
+   {
+     String inFile = "examples/uniref50.fa",
+             inAnnot = "examples/testdata/uniref50_iupred.jva";
+     String tfile = File.createTempFile("JalviewTest", ".jvp")
+             .getAbsolutePath();
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Didn't read input file " + inFile);
+     af.loadJalviewDataFile(inAnnot, DataSourceType.FILE, null, null);
+     AlignmentAnnotation[] aa = af.getViewport().getAlignment()
+             .getSequenceAt(0).getAnnotation("IUPredWS (Short)");
+     assertTrue(
+             aa != null && aa.length > 0,
+             "Didn't find any IUPred annotation to use to shade alignment.");
+     AnnotationColourGradient cs = new AnnotationColourGradient(aa[0], null,
+             AnnotationColourGradient.ABOVE_THRESHOLD);
+     AnnotationColourGradient gcs = new AnnotationColourGradient(aa[0], null,
+             AnnotationColourGradient.BELOW_THRESHOLD);
+     cs.setSeqAssociated(true);
+     gcs.setSeqAssociated(true);
+     af.changeColour(cs);
+     SequenceGroup sg = new SequenceGroup();
+     sg.setStartRes(57);
+     sg.setEndRes(92);
+     sg.cs.setColourScheme(gcs);
+     af.getViewport().getAlignment().addGroup(sg);
+     sg.addSequence(af.getViewport().getAlignment().getSequenceAt(1), false);
+     sg.addSequence(af.getViewport().getAlignment().getSequenceAt(2), true);
+     af.alignPanel.alignmentChanged();
+     assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
+             "Failed to store as a project.");
+     af.closeMenuItem_actionPerformed(true);
+     af = null;
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Failed to import new project");
+     // check for group and alignment colourschemes
+     ColourSchemeI _rcs = af.getViewport().getGlobalColourScheme();
+     ColourSchemeI _rgcs = af.getViewport().getAlignment().getGroups().get(0)
+             .getColourScheme();
+     assertNotNull(_rcs, "Didn't recover global colourscheme");
+     assertTrue(_rcs instanceof AnnotationColourGradient,
+             "Didn't recover annotation colour global scheme");
+     AnnotationColourGradient __rcs = (AnnotationColourGradient) _rcs;
+     assertTrue(__rcs.isSeqAssociated(),
+             "Annotation colourscheme wasn't sequence associated");
+     boolean diffseqcols = false, diffgseqcols = false;
+     SequenceI[] sqs = af.getViewport().getAlignment().getSequencesArray();
+     for (int p = 0, pSize = af.getViewport().getAlignment()
+             .getWidth(); p < pSize && (!diffseqcols || !diffgseqcols); p++)
+     {
+       if (_rcs.findColour(sqs[0].getCharAt(p), p, sqs[0], null, 0f) != _rcs
+               .findColour(sqs[5].getCharAt(p), p, sqs[5], null, 0f))
+       {
+         diffseqcols = true;
+       }
+     }
+     assertTrue(diffseqcols, "Got Different sequence colours");
+     System.out.println(
+             "Per sequence colourscheme (Background) successfully applied and recovered.");
+     assertNotNull(_rgcs, "Didn't recover group colourscheme");
+     assertTrue(_rgcs instanceof AnnotationColourGradient,
+             "Didn't recover annotation colour group colourscheme");
+     __rcs = (AnnotationColourGradient) _rgcs;
+     assertTrue(__rcs.isSeqAssociated(),
+             "Group Annotation colourscheme wasn't sequence associated");
+     for (int p = 0, pSize = af.getViewport().getAlignment()
+             .getWidth(); p < pSize && (!diffseqcols || !diffgseqcols); p++)
+     {
+       if (_rgcs.findColour(sqs[1].getCharAt(p), p, sqs[1], null,
+               0f) != _rgcs.findColour(sqs[2].getCharAt(p), p, sqs[2], null,
+                       0f))
+       {
+         diffgseqcols = true;
+       }
+     }
+     assertTrue(diffgseqcols, "Got Different group sequence colours");
+     System.out.println(
+             "Per sequence (Group) colourscheme successfully applied and recovered.");
+   }
+   @Test(groups = { "Functional" })
+   public void gatherViewsHere() throws Exception
+   {
+     int origCount = Desktop.getAlignFrames() == null ? 0
+             : Desktop.getAlignFrames().length;
+     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,
+             "Didn't gather the views in the example file.");
+   }
+   /**
+    * Test for JAL-2223 - multiple mappings in View Mapping report
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" })
+   public void noDuplicatePdbMappingsMade() throws Exception
+   {
+     StructureImportSettings.setProcessSecondaryStructure(true);
+     StructureImportSettings.setVisibleChainAnnotation(true);
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/exampleFile_2_7.jar", DataSourceType.FILE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+     // locate Jmol viewer
+     // count number of PDB mappings the structure selection manager holds -
+     String pdbFile = af.getCurrentView().getStructureSelectionManager()
+             .findFileForPDBId("1A70");
+     assertEquals(
+             af.getCurrentView().getStructureSelectionManager()
+                     .getMapping(pdbFile).length,
+             2, "Expected only two mappings for 1A70");
+   }
+   @Test(groups = { "Functional" })
+   public void viewRefPdbAnnotation() throws Exception
+   {
+     StructureImportSettings.setProcessSecondaryStructure(true);
+     StructureImportSettings.setVisibleChainAnnotation(true);
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/exampleFile_2_7.jar", DataSourceType.FILE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+     AlignmentViewPanel sps = null;
+     for (AlignmentViewPanel ap : af.alignPanel.alignFrame.getAlignPanels())
+     {
+       if ("Spinach Feredoxin Structure".equals(ap.getViewName()))
+       {
+         sps = ap;
+         break;
+       }
+     }
+     assertNotNull(sps, "Couldn't find the structure view");
+     AlignmentAnnotation refan = null;
+     for (AlignmentAnnotation ra : sps.getAlignment()
+             .getAlignmentAnnotation())
+     {
+       if (ra.graph != 0)
+       {
+         refan = ra;
+         break;
+       }
+     }
+     assertNotNull(refan, "Annotation secondary structure not found.");
+     SequenceI sq = sps.getAlignment().findName("1A70|");
+     assertNotNull(sq, "Couldn't find 1a70 null chain");
+     // compare the manually added temperature factor annotation
+     // to the track automatically transferred from the pdb structure on load
+     assertNotNull(sq.getDatasetSequence().getAnnotation(),
+             "1a70 has no annotation");
+     for (AlignmentAnnotation ala : sq.getDatasetSequence().getAnnotation())
+     {
+       AlignmentAnnotation alaa;
+       sq.addAlignmentAnnotation(alaa = new AlignmentAnnotation(ala));
+       alaa.adjustForAlignment();
+       if (ala.graph == refan.graph)
+       {
+         for (int p = 0; p < ala.annotations.length; p++)
+         {
+           sq.findPosition(p);
+           try
+           {
+             assertTrue((alaa.annotations[p] == null
+                     && refan.annotations[p] == null)
+                     || alaa.annotations[p].value == refan.annotations[p].value,
+                     "Mismatch at alignment position " + p);
+           } catch (NullPointerException q)
+           {
+             Assert.fail("Mismatch of alignment annotations at position " + p
+                     + " Ref seq ann: " + refan.annotations[p]
+                     + " alignment " + alaa.annotations[p]);
+           }
+         }
+       }
+     }
+   }
+   @Test(groups = { "Functional" })
+   public void testCopyViewSettings() throws Exception
+   {
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/exampleFile_2_7.jar", DataSourceType.FILE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+     AlignmentViewPanel sps = null, groups = null;
+     for (AlignmentViewPanel ap : af.alignPanel.alignFrame.getAlignPanels())
+     {
+       if ("Spinach Feredoxin Structure".equals(ap.getViewName()))
+       {
+         sps = ap;
+       }
+       if (ap.getViewName().contains("MAFFT"))
+       {
+         groups = ap;
+       }
+     }
+     assertNotNull(sps, "Couldn't find the structure view");
+     assertNotNull(groups, "Couldn't find the MAFFT view");
+     ViewStyleI structureStyle = sps.getAlignViewport().getViewStyle();
+     ViewStyleI groupStyle = groups.getAlignViewport().getViewStyle();
+     AssertJUnit.assertFalse(structureStyle.sameStyle(groupStyle));
+     groups.getAlignViewport().setViewStyle(structureStyle);
+     AssertJUnit.assertFalse(
+             groupStyle.sameStyle(groups.getAlignViewport().getViewStyle()));
+     Assert.assertTrue(structureStyle
+             .sameStyle(groups.getAlignViewport().getViewStyle()));
+   }
+   /**
+    * test store and recovery of expanded views
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" }, enabled = true)
+   public void testStoreAndRecoverExpandedviews() throws Exception
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/exampleFile_2_7.jar", DataSourceType.FILE);
+     Assert.assertEquals(Desktop.getAlignFrames().length, 1);
+     String afid = af.getViewport().getSequenceSetId();
+     // check FileLoader returned a reference to the one alignFrame that is
+     // actually on the Desktop
+     assertSame(af, Desktop.getAlignFrameFor(af.getViewport()),
+             "Jalview2XML.loadAlignFrame() didn't return correct AlignFrame reference for multiple view window");
+     Desktop.explodeViews(af);
+     int oldviews = Desktop.getAlignFrames().length;
+     Assert.assertEquals(Desktop.getAlignFrames().length,
+             Desktop.getAlignmentPanels(afid).length);
+     File tfile = File.createTempFile("testStoreAndRecoverExpanded", ".jvp");
+     try
+     {
+       new Jalview2XML(false).saveState(tfile);
+     } catch (Error e)
+     {
+       Assert.fail("Didn't save the expanded view state", e);
+     } catch (Exception e)
+     {
+       Assert.fail("Didn't save the expanded view state", e);
+     }
+     Desktop.instance.closeAll_actionPerformed(null);
+     if (Desktop.getAlignFrames() != null)
+     {
+       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+     }
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
+             DataSourceType.FILE);
+     Assert.assertNotNull(af);
+     Assert.assertEquals(Desktop.getAlignFrames().length,
+             Desktop.getAlignmentPanels(
+                     af.getViewport().getSequenceSetId()).length);
+     Assert.assertEquals(
+             Desktop.getAlignmentPanels(
+                     af.getViewport().getSequenceSetId()).length,
+             oldviews);
+   }
+   /**
+    * Test save and reload of a project with a different representative sequence
+    * in each view.
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" })
+   public void testStoreAndRecoverReferenceSeqSettings() throws Exception
+   {
+     Desktop.instance.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.");
+     String afid = af.getViewport().getSequenceSetId();
+     // remember reference sequence for each panel
+     Map<String, SequenceI> refseqs = new HashMap<>();
+     /*
+      * mark sequence 2, 3, 4.. in panels 1, 2, 3...
+      * as reference sequence for itself and the preceding sequence
+      */
+     int n = 1;
+     for (AlignmentViewPanel ap : Desktop.getAlignmentPanels(afid))
+     {
+       AlignViewportI av = ap.getAlignViewport();
+       AlignmentI alignment = ap.getAlignment();
+       int repIndex = n % alignment.getHeight();
+       SequenceI rep = alignment.getSequenceAt(repIndex);
+       refseqs.put(ap.getViewName(), rep);
+       // code from mark/unmark sequence as reference in jalview.gui.PopupMenu
+       // todo refactor this to an alignment view controller
+       av.setDisplayReferenceSeq(true);
+       av.setColourByReferenceSeq(true);
+       av.getAlignment().setSeqrep(rep);
+       n++;
+     }
+     File tfile = File.createTempFile("testStoreAndRecoverReferenceSeq",
+             ".jvp");
+     try
+     {
+       new Jalview2XML(false).saveState(tfile);
+     } catch (Throwable e)
+     {
+       Assert.fail("Didn't save the expanded view state", e);
+     }
+     Desktop.instance.closeAll_actionPerformed(null);
+     if (Desktop.getAlignFrames() != null)
+     {
+       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+     }
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
+             DataSourceType.FILE);
+     afid = af.getViewport().getSequenceSetId();
+     for (AlignmentViewPanel ap : Desktop.getAlignmentPanels(afid))
+     {
+       // check representative
+       AlignmentI alignment = ap.getAlignment();
+       SequenceI rep = alignment.getSeqrep();
+       Assert.assertNotNull(rep,
+               "Couldn't restore sequence representative from project");
+       // can't use a strong equals here, because by definition, the sequence IDs
+       // will be different.
+       // could set vamsas session save/restore flag to preserve IDs across
+       // load/saves.
+       Assert.assertEquals(refseqs.get(ap.getViewName()).toString(),
+               rep.toString(),
+               "Representative wasn't the same when recovered.");
+       Assert.assertTrue(ap.getAlignViewport().isDisplayReferenceSeq(),
+               "Display reference sequence view setting not set.");
+       Assert.assertTrue(ap.getAlignViewport().isColourByReferenceSeq(),
+               "Colour By Reference Seq view setting not set.");
+     }
+   }
+   @Test(groups = { "Functional" })
+   public void testIsVersionStringLaterThan()
+   {
+     /*
+      * No version / development / test / autobuild is leniently assumed to be
+      * compatible
+      */
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null, null));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", null));
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null, "2.8.3"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null,
+             "Development Build"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null,
+             "DEVELOPMENT BUILD"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3",
+             "Development Build"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null, "Test"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan(null, "TEST"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "Test"));
+     assertTrue(
+             Jalview2XML.isVersionStringLaterThan(null, "Automated Build"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3",
+             "Automated Build"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3",
+             "AUTOMATED BUILD"));
+     /*
+      * same version returns true i.e. compatible
+      */
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8", "2.8"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.8.3"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3b1", "2.8.3b1"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3B1", "2.8.3b1"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3b1", "2.8.3B1"));
+     /*
+      * later version returns true
+      */
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.8.4"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.9"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.9.2"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8", "2.8.3"));
+     assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.8.3b1"));
+     /*
+      * earlier version returns false
+      */
+     assertFalse(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.8"));
+     assertFalse(Jalview2XML.isVersionStringLaterThan("2.8.4", "2.8.3"));
+     assertFalse(Jalview2XML.isVersionStringLaterThan("2.8.3b1", "2.8.3"));
+     assertFalse(Jalview2XML.isVersionStringLaterThan("2.8.3", "2.8.2b1"));
+     assertFalse(Jalview2XML.isVersionStringLaterThan("2.8.0b2", "2.8.0b1"));
+   }
+   /**
+    * Test save and reload of a project with a different sequence group (and
+    * representative sequence) in each view.
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" })
+   public void testStoreAndRecoverGroupRepSeqs() throws Exception
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/uniref50.fa", DataSourceType.FILE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+     String afid = af.getViewport().getSequenceSetId();
+     // make a second view of the alignment
+     af.newView_actionPerformed(null);
+     /*
+      * remember representative and hidden sequences marked 
+      * on each panel
+      */
+     Map<String, SequenceI> repSeqs = new HashMap<>();
+     Map<String, List<String>> hiddenSeqNames = new HashMap<>();
+     /*
+      * mark sequence 2, 3, 4.. in panels 1, 2, 3...
+      * as reference sequence for itself and the preceding sequence
+      */
+     int n = 1;
+     for (AlignmentViewPanel ap : Desktop.getAlignmentPanels(afid))
+     {
+       AlignViewportI av = ap.getAlignViewport();
+       AlignmentI alignment = ap.getAlignment();
+       int repIndex = n % alignment.getHeight();
+       // ensure at least one preceding sequence i.e. index >= 1
+       repIndex = Math.max(repIndex, 1);
+       SequenceI repSeq = alignment.getSequenceAt(repIndex);
+       repSeqs.put(ap.getViewName(), repSeq);
+       List<String> hiddenNames = new ArrayList<>();
+       hiddenSeqNames.put(ap.getViewName(), hiddenNames);
+       /*
+        * have rep sequence represent itself and the one before it
+        * this hides the group (except for the rep seq)
+        */
+       SequenceGroup sg = new SequenceGroup();
+       sg.addSequence(repSeq, false);
+       SequenceI precedingSeq = alignment.getSequenceAt(repIndex - 1);
+       sg.addSequence(precedingSeq, false);
+       sg.setSeqrep(repSeq);
+       assertTrue(sg.getSequences().contains(repSeq));
+       assertTrue(sg.getSequences().contains(precedingSeq));
+       av.setSelectionGroup(sg);
+       assertSame(repSeq, sg.getSeqrep());
+       /*
+        * represent group with sequence adds to a map of hidden rep sequences
+        * (it does not create a group on the alignment) 
+        */
+       ((AlignmentViewport) av).hideSequences(repSeq, true);
+       assertSame(repSeq, sg.getSeqrep());
+       assertTrue(sg.getSequences().contains(repSeq));
+       assertTrue(sg.getSequences().contains(precedingSeq));
+       assertTrue(alignment.getGroups().isEmpty(), "alignment has groups");
+       Map<SequenceI, SequenceCollectionI> hiddenRepSeqsMap = av
+               .getHiddenRepSequences();
+       assertNotNull(hiddenRepSeqsMap);
+       assertEquals(1, hiddenRepSeqsMap.size());
+       assertSame(sg, hiddenRepSeqsMap.get(repSeq));
+       assertTrue(alignment.getHiddenSequences().isHidden(precedingSeq));
+       assertFalse(alignment.getHiddenSequences().isHidden(repSeq));
+       hiddenNames.add(precedingSeq.getName());
+       n++;
+     }
+     File tfile = File.createTempFile("testStoreAndRecoverGroupReps",
+             ".jvp");
+     try
+     {
+       new Jalview2XML(false).saveState(tfile);
+     } catch (Throwable e)
+     {
+       Assert.fail("Didn't save the expanded view state", e);
+     }
+     Desktop.instance.closeAll_actionPerformed(null);
+     if (Desktop.getAlignFrames() != null)
+     {
+       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+     }
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
+             DataSourceType.FILE);
+     afid = af.getViewport().getSequenceSetId();
+     for (AlignmentViewPanel ap : Desktop.getAlignmentPanels(afid))
+     {
+       String viewName = ap.getViewName();
+       AlignViewportI av = ap.getAlignViewport();
+       AlignmentI alignment = ap.getAlignment();
+       List<SequenceGroup> groups = alignment.getGroups();
+       assertNotNull(groups);
+       assertTrue(groups.isEmpty(), "Alignment has groups");
+       Map<SequenceI, SequenceCollectionI> hiddenRepSeqsMap = av
+               .getHiddenRepSequences();
+       assertNotNull(hiddenRepSeqsMap, "No hidden represented sequences");
+       assertEquals(1, hiddenRepSeqsMap.size());
+       assertEquals(repSeqs.get(viewName).getDisplayId(true),
+               hiddenRepSeqsMap.keySet().iterator().next()
+                       .getDisplayId(true));
+       /*
+        * verify hidden sequences in restored panel
+        */
+       List<String> hidden = hiddenSeqNames.get(ap.getViewName());
+       HiddenSequences hs = alignment.getHiddenSequences();
+       assertEquals(hidden.size(), hs.getSize(),
+               "wrong number of restored hidden sequences in "
+                       + ap.getViewName());
+     }
+   }
+   /**
+    * Test save and reload of PDBEntry in Jalview project
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" })
+   public void testStoreAndRecoverPDBEntry() throws Exception
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     String exampleFile = "examples/3W5V.pdb";
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(exampleFile,
+             DataSourceType.FILE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+     String afid = af.getViewport().getSequenceSetId();
+     AlignmentPanel[] alignPanels = Desktop.getAlignmentPanels(afid);
+     System.out.println();
+     AlignmentViewPanel ap = alignPanels[0];
+     String tfileBase = new File(".").getAbsolutePath().replace(".", "");
+     String testFile = tfileBase + exampleFile;
+     AlignmentI alignment = ap.getAlignment();
+     System.out.println("blah");
+     SequenceI[] seqs = alignment.getSequencesArray();
+     Assert.assertNotNull(seqs[0]);
+     Assert.assertNotNull(seqs[1]);
+     Assert.assertNotNull(seqs[2]);
+     Assert.assertNotNull(seqs[3]);
+     Assert.assertNotNull(seqs[0].getDatasetSequence());
+     Assert.assertNotNull(seqs[1].getDatasetSequence());
+     Assert.assertNotNull(seqs[2].getDatasetSequence());
+     Assert.assertNotNull(seqs[3].getDatasetSequence());
+     PDBEntry[] pdbEntries = new PDBEntry[4];
+     pdbEntries[0] = new PDBEntry("3W5V", "A", Type.PDB, testFile);
+     pdbEntries[1] = new PDBEntry("3W5V", "B", Type.PDB, testFile);
+     pdbEntries[2] = new PDBEntry("3W5V", "C", Type.PDB, testFile);
+     pdbEntries[3] = new PDBEntry("3W5V", "D", Type.PDB, testFile);
+     Assert.assertEquals(
+             seqs[0].getDatasetSequence().getAllPDBEntries().get(0),
+             pdbEntries[0]);
+     Assert.assertEquals(
+             seqs[1].getDatasetSequence().getAllPDBEntries().get(0),
+             pdbEntries[1]);
+     Assert.assertEquals(
+             seqs[2].getDatasetSequence().getAllPDBEntries().get(0),
+             pdbEntries[2]);
+     Assert.assertEquals(
+             seqs[3].getDatasetSequence().getAllPDBEntries().get(0),
+             pdbEntries[3]);
+     File tfile = File.createTempFile("testStoreAndRecoverPDBEntry", ".jvp");
+     try
+     {
+       new Jalview2XML(false).saveState(tfile);
+     } catch (Throwable e)
+     {
+       Assert.fail("Didn't save the state", e);
+     }
+     Desktop.instance.closeAll_actionPerformed(null);
+     if (Desktop.getAlignFrames() != null)
+     {
+       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+     }
+     AlignFrame restoredFrame = new FileLoader().LoadFileWaitTillLoaded(
+             tfile.getAbsolutePath(), DataSourceType.FILE);
+     String rfid = restoredFrame.getViewport().getSequenceSetId();
+     AlignmentPanel[] rAlignPanels = Desktop.getAlignmentPanels(rfid);
+     AlignmentViewPanel rap = rAlignPanels[0];
+     AlignmentI rAlignment = rap.getAlignment();
+     System.out.println("blah");
+     SequenceI[] rseqs = rAlignment.getSequencesArray();
+     Assert.assertNotNull(rseqs[0]);
+     Assert.assertNotNull(rseqs[1]);
+     Assert.assertNotNull(rseqs[2]);
+     Assert.assertNotNull(rseqs[3]);
+     Assert.assertNotNull(rseqs[0].getDatasetSequence());
+     Assert.assertNotNull(rseqs[1].getDatasetSequence());
+     Assert.assertNotNull(rseqs[2].getDatasetSequence());
+     Assert.assertNotNull(rseqs[3].getDatasetSequence());
+     // The Asserts below are expected to fail until the PDB chainCode is
+     // recoverable from a Jalview projects
+     for (int chain = 0; chain < 4; chain++)
+     {
+       PDBEntry recov = rseqs[chain].getDatasetSequence().getAllPDBEntries()
+               .get(0);
+       PDBEntry expected = pdbEntries[chain];
+       Assert.assertEquals(recov.getId(), expected.getId(),
+               "Mismatch PDB ID");
+       Assert.assertEquals(recov.getChainCode(), expected.getChainCode(),
+               "Mismatch PDB ID");
+       Assert.assertEquals(recov.getType(), expected.getType(),
+               "Mismatch PDBEntry 'Type'");
+       Assert.assertNotNull(recov.getFile(),
+               "Recovered PDBEntry should have a non-null file entry");
+     }
+   }
+   /**
+    * Configure an alignment and a sub-group each with distinct colour schemes,
+    * Conservation and PID thresholds, and confirm these are restored from the
+    * saved project.
+    * 
+    * @throws IOException
+    */
+   @Test(groups = { "Functional" })
+   public void testStoreAndRecoverColourThresholds() throws IOException
+   {
+     Desktop.instance.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();
+     /*
+      * Colour alignment by Buried Index, Above 10% PID, By Conservation 20%
+      */
+     av.setColourAppliesToAllGroups(false);
+     af.changeColour_actionPerformed(JalviewColourScheme.Buried.toString());
+     assertTrue(av.getGlobalColourScheme() instanceof BuriedColourScheme);
+     af.abovePIDThreshold_actionPerformed(true);
+     SliderPanel sp = SliderPanel.getSliderPanel();
+     assertFalse(sp.isForConservation());
+     sp.valueChanged(10);
+     af.conservationMenuItem_actionPerformed(true);
+     sp = SliderPanel.getSliderPanel();
+     assertTrue(sp.isForConservation());
+     sp.valueChanged(20);
+     ResidueShaderI rs = av.getResidueShading();
+     assertEquals(rs.getThreshold(), 10);
+     assertTrue(rs.conservationApplied());
+     assertEquals(rs.getConservationInc(), 20);
+     /*
+      * create a group with Strand colouring, 30% Conservation
+      * and 40% PID threshold
+      */
+     SequenceGroup sg = new SequenceGroup();
+     sg.addSequence(al.getSequenceAt(0), false);
+     sg.setStartRes(15);
+     sg.setEndRes(25);
+     av.setSelectionGroup(sg);
+     PopupMenu popupMenu = new PopupMenu(af.alignPanel, null, null);
+     popupMenu.changeColour_actionPerformed(
+             JalviewColourScheme.Strand.toString());
+     assertTrue(sg.getColourScheme() instanceof StrandColourScheme);
+     assertEquals(al.getGroups().size(), 1);
+     assertSame(al.getGroups().get(0), sg);
+     popupMenu.conservationMenuItem_actionPerformed(true);
+     sp = SliderPanel.getSliderPanel();
+     assertTrue(sp.isForConservation());
+     sp.valueChanged(30);
+     popupMenu.abovePIDColour_actionPerformed(true);
+     sp = SliderPanel.getSliderPanel();
+     assertFalse(sp.isForConservation());
+     sp.valueChanged(40);
+     assertTrue(sg.getGroupColourScheme().conservationApplied());
+     assertEquals(sg.getGroupColourScheme().getConservationInc(), 30);
+     assertEquals(sg.getGroupColourScheme().getThreshold(), 40);
+     /*
+      * save project, close windows, reload project, verify
+      */
+     File tfile = File.createTempFile("testStoreAndRecoverColourThresholds",
+             ".jvp");
+     tfile.deleteOnExit();
+     new Jalview2XML(false).saveState(tfile);
+     Desktop.instance.closeAll_actionPerformed(null);
+     af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
+             DataSourceType.FILE);
+     Assert.assertNotNull(af, "Failed to reload project");
+     /*
+      * verify alignment (background) colouring
+      */
+     rs = af.getViewport().getResidueShading();
+     assertTrue(rs.getColourScheme() instanceof BuriedColourScheme);
+     assertEquals(rs.getThreshold(), 10);
+     assertTrue(rs.conservationApplied());
+     assertEquals(rs.getConservationInc(), 20);
+     /*
+      * verify group colouring
+      */
+     assertEquals(1, af.getViewport().getAlignment().getGroups().size(), 1);
+     rs = af.getViewport().getAlignment().getGroups().get(0)
+             .getGroupColourScheme();
+     assertTrue(rs.getColourScheme() instanceof StrandColourScheme);
+     assertEquals(rs.getThreshold(), 40);
+     assertTrue(rs.conservationApplied());
+     assertEquals(rs.getConservationInc(), 30);
+   }
+   /**
+    * Test save and reload of feature colour schemes and filter settings
+    * 
+    * @throws IOException
+    */
+   @Test(groups = { "Functional" })
 -  public void testSaveLoadFeatureColoursAndFilters() throws IOException
++  public void testStoreAndRecoverFeatureColoursAndFilters()
++          throws IOException
+   {
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             ">Seq1\nACDEFGHIKLM", DataSourceType.PASTE);
+     SequenceI seq1 = af.getViewport().getAlignment().getSequenceAt(0);
+     /*
+      * add some features to the sequence
+      */
+     int score = 1;
+     addFeatures(seq1, "type1", score++);
+     addFeatures(seq1, "type2", score++);
+     addFeatures(seq1, "type3", score++);
+     addFeatures(seq1, "type4", score++);
+     addFeatures(seq1, "type5", score++);
+     /*
+      * set colour schemes for features
+      */
+     FeatureRenderer fr = af.getFeatureRenderer();
+     fr.findAllFeatures(true);
+     // type1: red
+     fr.setColour("type1", new FeatureColour(Color.red));
+     // type2: by label
+     FeatureColourI byLabel = new FeatureColour();
+     byLabel.setColourByLabel(true);
+     fr.setColour("type2", byLabel);
+     // type3: by score above threshold
+     FeatureColourI byScore = new FeatureColour(null, Color.BLACK,
+             Color.BLUE, null, 1, 10);
+     byScore.setAboveThreshold(true);
+     byScore.setThreshold(2f);
+     fr.setColour("type3", byScore);
+     // type4: by attribute AF
+     FeatureColourI byAF = new FeatureColour();
+     byAF.setColourByLabel(true);
+     byAF.setAttributeName("AF");
+     fr.setColour("type4", byAF);
+     // type5: by attribute CSQ:PolyPhen below threshold
+     FeatureColourI byPolyPhen = new FeatureColour(null, Color.BLACK,
+             Color.BLUE, null, 1, 10);
+     byPolyPhen.setBelowThreshold(true);
+     byPolyPhen.setThreshold(3f);
+     byPolyPhen.setAttributeName("CSQ", "PolyPhen");
+     fr.setColour("type5", byPolyPhen);
+     /*
+      * set filters for feature types
+      */
+     // filter type1 features by (label contains "x")
+     FeatureMatcherSetI filterByX = new FeatureMatcherSet();
+     filterByX.and(FeatureMatcher.byLabel(Condition.Contains, "x"));
+     fr.setFeatureFilter("type1", filterByX);
+     // filter type2 features by (score <= 2.4 and score > 1.1)
+     FeatureMatcherSetI filterByScore = new FeatureMatcherSet();
+     filterByScore.and(FeatureMatcher.byScore(Condition.LE, "2.4"));
+     filterByScore.and(FeatureMatcher.byScore(Condition.GT, "1.1"));
+     fr.setFeatureFilter("type2", filterByScore);
+     // filter type3 features by (AF contains X OR CSQ:PolyPhen != 0)
+     FeatureMatcherSetI filterByXY = new FeatureMatcherSet();
+     filterByXY
+             .and(FeatureMatcher.byAttribute(Condition.Contains, "X", "AF"));
+     filterByXY.or(FeatureMatcher.byAttribute(Condition.NE, "0", "CSQ",
+             "PolyPhen"));
+     fr.setFeatureFilter("type3", filterByXY);
+     /*
+      * save as Jalview project
+      */
+     File tfile = File.createTempFile("JalviewTest", ".jvp");
+     tfile.deleteOnExit();
+     String filePath = tfile.getAbsolutePath();
+     assertTrue(af.saveAlignment(filePath, FileFormat.Jalview),
+             "Failed to store as a project.");
+     /*
+      * close current alignment and load the saved project
+      */
+     af.closeMenuItem_actionPerformed(true);
+     af = null;
+     af = new FileLoader().LoadFileWaitTillLoaded(filePath,
+             DataSourceType.FILE);
+     assertNotNull(af, "Failed to import new project");
+     /*
+      * verify restored feature colour schemes and filters
+      */
+     fr = af.getFeatureRenderer();
+     FeatureColourI fc = fr.getFeatureStyle("type1");
+     assertTrue(fc.isSimpleColour());
+     assertEquals(fc.getColour(), Color.red);
+     fc = fr.getFeatureStyle("type2");
+     assertTrue(fc.isColourByLabel());
+     fc = fr.getFeatureStyle("type3");
+     assertTrue(fc.isGraduatedColour());
+     assertNull(fc.getAttributeName());
+     assertTrue(fc.isAboveThreshold());
+     assertEquals(fc.getThreshold(), 2f);
+     fc = fr.getFeatureStyle("type4");
+     assertTrue(fc.isColourByLabel());
+     assertTrue(fc.isColourByAttribute());
+     assertEquals(fc.getAttributeName(), new String[] { "AF" });
+     fc = fr.getFeatureStyle("type5");
+     assertTrue(fc.isGraduatedColour());
+     assertTrue(fc.isColourByAttribute());
+     assertEquals(fc.getAttributeName(), new String[] { "CSQ", "PolyPhen" });
+     assertTrue(fc.isBelowThreshold());
+     assertEquals(fc.getThreshold(), 3f);
+     assertEquals(fr.getFeatureFilter("type1").toStableString(),
+             "Label Contains x");
+     assertEquals(fr.getFeatureFilter("type2").toStableString(),
+             "(Score LE 2.4) AND (Score GT 1.1)");
+     assertEquals(fr.getFeatureFilter("type3").toStableString(),
+             "(AF Contains X) OR (CSQ:PolyPhen NE 0.0)");
+   }
+   private void addFeature(SequenceI seq, String featureType, int score)
+   {
+     SequenceFeature sf = new SequenceFeature(featureType, "desc", 1, 2,
+             score, "grp");
+     sf.setValue("AF", score);
+     sf.setValue("CSQ", new HashMap<String, String>()
+     {
+       {
+         put("PolyPhen", Integer.toString(score));
+       }
+     });
+     seq.addSequenceFeature(sf);
+   }
+   /**
+    * Adds two features of the given type to the given sequence, also setting the
+    * score as the value of attribute "AF" and sub-attribute "CSQ:PolyPhen"
+    * 
+    * @param seq
+    * @param featureType
+    * @param score
+    */
+   private void addFeatures(SequenceI seq, String featureType, int score)
+   {
+     addFeature(seq, featureType, score++);
+     addFeature(seq, featureType, score);
+   }
+   /**
++   * 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);
++    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);
++    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 testMergeDatasetsforViews() throws IOException
+   {
+     // simple project - two views on one alignment
+     AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
+             "examples/testdata/projects/twoViews.jvp", DataSourceType.FILE);
+     assertNotNull(af);
+     assertTrue(af.getAlignPanels().size() > 1);
+     verifyDs(af);
+   }
+   /**
+    * 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 This
+    * is a more complex project
+    */
+   @Test(groups = { "Functional" })
+   public void testMergeDatasetsforManyViews() throws IOException
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     // complex project - one dataset, several views on several alignments
+     AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
+             "examples/testdata/projects/manyViews.jvp",
+             DataSourceType.FILE);
+     assertNotNull(af);
+     AlignmentI ds = null;
+     for (AlignFrame alignFrame : Desktop.getAlignFrames())
+     {
+       if (ds == null)
+       {
+         ds = verifyDs(alignFrame);
+       }
+       else
+       {
+         // check that this frame's dataset matches the last
+         assertTrue(ds == verifyDs(alignFrame));
+       }
+     }
+   }
+   private AlignmentI verifyDs(AlignFrame af)
+   {
+     AlignmentI ds = null;
+     for (AlignmentViewPanel ap : af.getAlignPanels())
+     {
+       if (ds == null)
+       {
+         ds = ap.getAlignment().getDataset();
+       }
+       else
+       {
+         assertTrue(ap.getAlignment().getDataset() == ds,
+                 "Dataset was not the same for imported 2.10.5 project with several alignment views");
+       }
+     }
+     return ds;
+   }
+   @Test(groups = "Functional")
+   public void testPcaViewAssociation() throws IOException
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     final String PCAVIEWNAME = "With PCA";
+     // create a new tempfile
+     File tempfile = File.createTempFile("jvPCAviewAssoc", "jvp");
+     {
+       String exampleFile = "examples/uniref50.fa";
+       AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(exampleFile,
+               DataSourceType.FILE);
+       assertNotNull(af, "Didn't read in the example file correctly.");
+       AlignmentPanel origView = (AlignmentPanel) af.getAlignPanels().get(0);
+       AlignmentPanel newview = af.newView(PCAVIEWNAME, true);
+       // create another for good measure
+       af.newView("Not the PCA View", true);
+       PCAPanel pcaPanel = new PCAPanel(origView, "BLOSUM62",
+               new SimilarityParams(true, true, true, false));
+       // we're in the test exec thread, so we can just run synchronously here
+       pcaPanel.run();
+       // now switch the linked view
+       pcaPanel.selectAssociatedView(newview);
+       assertTrue(pcaPanel.getAlignViewport() == newview.getAlignViewport(),
+               "PCA should be associated with 'With PCA' view: test is broken");
+       // now save and reload project
+       Jalview2XML jv2xml = new jalview.project.Jalview2XML(false);
+       tempfile.delete();
+       jv2xml.saveState(tempfile);
+       assertTrue(jv2xml.errorMessage == null,
+               "Failed to save dummy project with PCA: test broken");
+     }
+     // load again.
+     Desktop.instance.closeAll_actionPerformed(null);
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             tempfile.getCanonicalPath(), DataSourceType.FILE);
+     JInternalFrame[] frames = Desktop.instance.getAllFrames();
+     // PCA and the tabbed alignment view should be the only two windows on the
+     // desktop
+     assertEquals(frames.length, 2,
+             "PCA and the tabbed alignment view should be the only two windows on the desktop");
+     PCAPanel pcaPanel = (PCAPanel) frames[frames[0] == af ? 1 : 0];
+     AlignmentViewPanel restoredNewView = null;
+     for (AlignmentViewPanel alignpanel : Desktop.getAlignmentPanels(null))
+     {
+       if (alignpanel.getAlignViewport() == pcaPanel.getAlignViewport())
+       {
+         restoredNewView = alignpanel;
+       }
+     }
+     assertEquals(restoredNewView.getViewName(), PCAVIEWNAME);
+     assertTrue(
+             restoredNewView.getAlignViewport() == pcaPanel
+                     .getAlignViewport(),
+             "Didn't restore correct view association for the PCA view");
+   }
+ }
Simple merge