Merge branch 'develop' into features/JAL-2349_matrixvis
authorJim Procter <jprocter@issues.jalview.org>
Fri, 9 Jun 2017 14:33:17 +0000 (18:33 +0400)
committerJim Procter <jprocter@issues.jalview.org>
Fri, 9 Jun 2017 14:33:17 +0000 (18:33 +0400)
JAL-2349 update for refactor of hidden columns out of column selection

372 files changed:
.classpath
examples/appletParameters.html
examples/groovy/colourConserved.groovy [new file with mode: 0644]
examples/groovy/colourUnconserved.groovy [new file with mode: 0644]
examples/groovy/featureCounter.groovy [deleted file]
examples/groovy/featuresCounter.groovy [new file with mode: 0644]
examples/groovy/multipleFeatureAnnotations.groovy [deleted file]
examples/groovy/visibleFeaturesCounter.groovy [new file with mode: 0644]
examples/testdata/example_annot_file.jva
help/help.jhm
help/helpTOC.xml
help/html/colourSchemes/user.html
help/html/colourSchemes/userDefined_java6.gif [deleted file]
help/html/colourSchemes/userDefined_java7.gif
help/html/features/chimera.html
help/html/features/groovy.html
help/html/features/overview.html
help/html/features/preferences.html
help/html/features/splitView.html
help/html/groovy/featureCounter.html [deleted file]
help/html/groovy/featuresCounter.html [new file with mode: 0644]
help/html/menus/desktopMenu.html
help/html/menus/popupMenu.html
help/html/releases.html
help/html/webServices/JABAWS.html
help/html/webServices/jnet.html
help/html/webServices/jnetprediction.gif [changed mode: 0755->0644]
help/html/webServices/msaclient.html
help/html/webServices/proteinDisorder.html
help/html/webServices/urllinks.html
help/html/whatsNew.html
lib/jabaws-min-client-2.2.0.jar [moved from lib/min-jabaws-client-2.1.0.jar with 73% similarity]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
resources/scoreModel/blosum62.scm [new file with mode: 0644]
resources/scoreModel/blosum80.scm [new file with mode: 0644]
resources/scoreModel/dna.scm [new file with mode: 0644]
resources/scoreModel/pam250.scm [new file with mode: 0644]
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/MCview/PDBChain.java
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/AlignSeq.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/AverageDistanceTree.java [new file with mode: 0644]
src/jalview/analysis/Conservation.java
src/jalview/analysis/NJTree.java
src/jalview/analysis/PCA.java
src/jalview/analysis/TreeBuilder.java [new file with mode: 0644]
src/jalview/analysis/TreeModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/DistanceScoreModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/FeatureDistanceModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/FeatureScoreModel.java [deleted file]
src/jalview/analysis/scoremodels/PIDModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/PIDScoreModel.java [deleted file]
src/jalview/analysis/scoremodels/PairwiseSeqScoreModel.java [deleted file]
src/jalview/analysis/scoremodels/ScoreMatrix.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/ScoreModels.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/SimilarityParams.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/SimilarityScoreModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/SmithWatermanModel.java [moved from src/jalview/analysis/scoremodels/SWScoreModel.java with 63% similarity]
src/jalview/api/AlignViewportI.java
src/jalview/api/AlignmentColsCollectionI.java [new file with mode: 0644]
src/jalview/api/AlignmentRowsCollectionI.java [new file with mode: 0644]
src/jalview/api/ComplexAlignFile.java
src/jalview/api/FeatureRenderer.java
src/jalview/api/SequenceRenderer.java
src/jalview/api/SiftsClientI.java
src/jalview/api/ViewStyleI.java
src/jalview/api/analysis/PairwiseScoreModelI.java [new file with mode: 0644]
src/jalview/api/analysis/ScoreModelI.java
src/jalview/api/analysis/SimilarityParamsI.java [new file with mode: 0644]
src/jalview/appletgui/#OverviewPanel.java# [new file with mode: 0755]
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationColourChooser.java
src/jalview/appletgui/AnnotationColumnChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/AnnotationRowFilter.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/CutAndPasteTransfer.java
src/jalview/appletgui/ExtJmol.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/Finder.java
src/jalview/appletgui/FontChooser.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/OverviewCanvas.java [new file with mode: 0644]
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/PCAPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/SequenceRenderer.java
src/jalview/appletgui/TreeCanvas.java
src/jalview/appletgui/TreePanel.java
src/jalview/appletgui/UserDefinedColours.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/AlignmentView.java
src/jalview/datamodel/AllColsCollection.java [new file with mode: 0644]
src/jalview/datamodel/AllColsIterator.java [new file with mode: 0644]
src/jalview/datamodel/AllRowsCollection.java [new file with mode: 0644]
src/jalview/datamodel/AllRowsIterator.java [new file with mode: 0644]
src/jalview/datamodel/AnnotatedCollectionI.java
src/jalview/datamodel/Annotation.java
src/jalview/datamodel/BinarySequence.java
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/HiddenColumns.java [new file with mode: 0644]
src/jalview/datamodel/HiddenSequences.java
src/jalview/datamodel/Profile.java~ [new file with mode: 0644]
src/jalview/datamodel/ProfileI.java~ [new file with mode: 0644]
src/jalview/datamodel/Profiles.java~ [new file with mode: 0644]
src/jalview/datamodel/ProfilesI.java~ [new file with mode: 0644]
src/jalview/datamodel/ResidueCount.java~ [new file with mode: 0644]
src/jalview/datamodel/SearchResultMatchI.java~ [new file with mode: 0644]
src/jalview/datamodel/SearchResultsI.java~ [new file with mode: 0644]
src/jalview/datamodel/SeqCigar.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/SequenceNode.java
src/jalview/datamodel/VisibleColsCollection.java [new file with mode: 0644]
src/jalview/datamodel/VisibleColsIterator.java [new file with mode: 0644]
src/jalview/datamodel/VisibleRowsCollection.java [new file with mode: 0644]
src/jalview/datamodel/VisibleRowsIterator.java [new file with mode: 0644]
src/jalview/ext/android/ContainerHelpers.java
src/jalview/ext/android/SparseDoubleArray.java [new file with mode: 0644]
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/jmol/JmolParser.java~ [new file with mode: 0644]
src/jalview/ext/rbvi/chimera/AtomSpecModel.java [new file with mode: 0644]
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/ChimeraListener.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/varna/VarnaCommands.java
src/jalview/fts/api/GFTSPanelI.java
src/jalview/fts/core/GFTSPanel.java
src/jalview/fts/service/pdb/PDBFTSPanel.java
src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
src/jalview/fts/service/uniprot/UniprotFTSPanel.java
src/jalview/gui/#OverviewPanel.java# [new file with mode: 0755]
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AnnotationRowFilter.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/AppVarna.java
src/jalview/gui/AppVarnaBinding.java
src/jalview/gui/CalculationChooser.java [new file with mode: 0644]
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ColourMenuHelper.java
src/jalview/gui/ComboBoxTooltipRenderer.java [new file with mode: 0644]
src/jalview/gui/Console.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/FontChooser.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/JDatabaseTree.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/JalviewDialog.java
src/jalview/gui/OverviewCanvas.java [new file with mode: 0644]
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqCanvas.java.broken [new file with mode: 0755]
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/SequenceRenderer.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureChooser.java~ [new file with mode: 0644]
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/TreeCanvas.java
src/jalview/gui/TreeCanvas.java~ [new file with mode: 0755]
src/jalview/gui/TreePanel.java
src/jalview/gui/UserDefinedColours.java
src/jalview/gui/VamsasApplication.java
src/jalview/io/AnnotationFile.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/FileFormat.java
src/jalview/io/FileLoader.java
src/jalview/io/FormatAdapter.java
src/jalview/io/HTMLOutput.java
src/jalview/io/HtmlFile.java
src/jalview/io/IdentifyFile.java
src/jalview/io/JSONFile.java
src/jalview/io/PDBFeatureSettings.java
src/jalview/io/ScoreMatrixFile.java [new file with mode: 0644]
src/jalview/io/StockholmFile.java
src/jalview/io/StructureFile.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/cache/AppCache.java [new file with mode: 0644]
src/jalview/io/cache/JvCacheableInputBox.java [new file with mode: 0644]
src/jalview/io/packed/JalviewDataset.java
src/jalview/io/vamsas/Tree.java
src/jalview/javascript/JsSelectionSender.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GDesktop.java
src/jalview/jbgui/GFinder.java
src/jalview/jbgui/GFontChooser.java
src/jalview/jbgui/GPCAPanel.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GStructureChooser.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/jbgui/GUserDefinedColours.java
src/jalview/math/Matrix.java
src/jalview/math/MatrixI.java [new file with mode: 0644]
src/jalview/math/SparseMatrix.java [new file with mode: 0644]
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ContactMapRenderer.java
src/jalview/renderer/OverviewRenderer.java [new file with mode: 0644]
src/jalview/renderer/ScaleRenderer.java
src/jalview/renderer/api/AnnotationRowRendererI.java
src/jalview/renderer/seqfeatures/FeatureColourFinder.java [new file with mode: 0644]
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/schemes/Blosum62ColourScheme.java
src/jalview/schemes/ColourSchemeLoader.java [new file with mode: 0644]
src/jalview/schemes/ColourSchemes.java
src/jalview/schemes/ResidueProperties.java
src/jalview/schemes/ScoreMatrix.java [deleted file]
src/jalview/structure/AtomSpec.java
src/jalview/structure/SelectionListener.java
src/jalview/structure/StructureListener.java
src/jalview/structure/StructureMapping.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/structure/StructureSelectionManager.java~ [new file with mode: 0644]
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/urls/UrlLinkDisplay.java
src/jalview/util/Comparison.java
src/jalview/util/LinkedIdentityHashSet.java
src/jalview/util/MappingUtils.java
src/jalview/util/RangeComparator.java [new file with mode: 0644]
src/jalview/util/SetUtils.java [new file with mode: 0644]
src/jalview/util/SparseCount.java~ [new file with mode: 0644]
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/OverviewDimensions.java [new file with mode: 0644]
src/jalview/viewmodel/OverviewDimensionsHideHidden.java [new file with mode: 0644]
src/jalview/viewmodel/OverviewDimensionsShowHidden.java [new file with mode: 0644]
src/jalview/viewmodel/PCAModel.java
src/jalview/viewmodel/ViewportListenerI.java [new file with mode: 0644]
src/jalview/viewmodel/ViewportProperties.java [moved from src/jalview/api/analysis/ViewBasedAnalysisI.java with 64% similarity]
src/jalview/viewmodel/ViewportRanges.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/viewmodel/styles/ViewStyle.java
src/jalview/workers/AlignmentAnnotationFactory.java
src/jalview/workers/ColumnCounterSetWorker.java [moved from src/jalview/workers/ColumnCounterWorker.java with 61% similarity]
src/jalview/workers/ConsensusThread.java
src/jalview/workers/ConservationThread.java
src/jalview/workers/FeatureSetCounterI.java [moved from src/jalview/workers/FeatureCounterI.java with 76% similarity]
src/jalview/ws/DasSequenceFeatureFetcher.java
src/jalview/ws/dbsources/Pdb.java~ [new file with mode: 0644]
src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java
src/jalview/ws/jws1/JPredThread.java
src/jalview/ws/jws1/MsaWSThread.java
src/jalview/ws/jws1/SeqSearchWSThread.java
src/jalview/ws/jws2/JPred301Client.java [deleted file]
src/jalview/ws/jws2/Jws2Discoverer.java
src/jalview/ws/jws2/MsaWSThread.java
src/jalview/ws/jws2/jabaws2/Jws2InstanceFactory.java
src/jalview/ws/rest/RestClient.java
src/jalview/ws/rest/RestJobThread.java
src/jalview/ws/sifts/SiftsClient.java
src/jalview/ws/utils/UrlDownloadClient.java
test/jalview/analysis/AlignSeqTest.java
test/jalview/analysis/AlignmentSorterTest.java [new file with mode: 0644]
test/jalview/analysis/DnaTest.java
test/jalview/analysis/TestAlignSeq.java
test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java [moved from test/jalview/analysis/scoremodels/FeatureScoreModelTest.java with 50% similarity]
test/jalview/analysis/scoremodels/PIDModelTest.java [new file with mode: 0644]
test/jalview/analysis/scoremodels/ScoreMatrixTest.java [new file with mode: 0644]
test/jalview/analysis/scoremodels/ScoreModelsTest.java [new file with mode: 0644]
test/jalview/datamodel/AlignmentAnnotationTests.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/AlignmentViewTest.java
test/jalview/datamodel/AllColsIteratorTest.java [new file with mode: 0644]
test/jalview/datamodel/AllRowsIteratorTest.java [new file with mode: 0644]
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/HiddenColumnsTest.java [new file with mode: 0644]
test/jalview/datamodel/HiddenSequencesTest.java
test/jalview/datamodel/SequenceGroupTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/datamodel/VisibleColsIteratorTest.java [new file with mode: 0644]
test/jalview/datamodel/VisibleRowsIteratorTest.java [new file with mode: 0644]
test/jalview/ext/android/SparseDoubleArrayTest.java [new file with mode: 0644]
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/jmol/JmolParserTest.java
test/jalview/ext/jmol/JmolViewerTest.java
test/jalview/ext/rbvi/chimera/4zho.pdb [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/4zho.xml.gz [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/ext/rbvi/chimera/testProps.jvprops
test/jalview/fts/service/pdb/PDBFTSPanelTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
test/jalview/gui/AlignmentPanelTest.java [new file with mode: 0644]
test/jalview/gui/AnnotationChooserTest.java
test/jalview/gui/AnnotationRowFilterTest.java [new file with mode: 0644]
test/jalview/gui/SeqPanelTest.java [new file with mode: 0644]
test/jalview/gui/SequenceRendererTest.java
test/jalview/gui/StructureChooserTest.java
test/jalview/io/AnnotationFileIOTest.java
test/jalview/io/FileLoaderTest.java [new file with mode: 0644]
test/jalview/io/IdentifyFileTest.java
test/jalview/io/JSONFileTest.java
test/jalview/io/NewickFileTests.java
test/jalview/io/PhylipFileTests.java
test/jalview/io/RNAMLfileTest.java
test/jalview/io/ScoreMatrixFileTest.java [new file with mode: 0644]
test/jalview/io/StockholmFileTest.java
test/jalview/io/cache/AppCacheTest.java [new file with mode: 0644]
test/jalview/io/cache/JvCacheableInputBoxTest.java [new file with mode: 0644]
test/jalview/io/testProps.jvprops
test/jalview/math/MatrixTest.java
test/jalview/math/SparseMatrixTest.java [new file with mode: 0644]
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java [new file with mode: 0644]
test/jalview/schemes/AnnotationColourGradientTest.java [new file with mode: 0644]
test/jalview/schemes/ColourSchemesTest.java
test/jalview/schemes/ScoreMatrixPrinter.java [deleted file]
test/jalview/structure/AtomSpecTest.java [new file with mode: 0644]
test/jalview/structure/StructureMappingTest.java [new file with mode: 0644]
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/urls/UrlLinkDisplayTest.java
test/jalview/util/ComparisonTest.java
test/jalview/util/MappingUtilsTest.java
test/jalview/util/SetUtilsTest.java [new file with mode: 0644]
test/jalview/viewmodel/OverviewDimensionsHideHiddenTest.java [new file with mode: 0644]
test/jalview/viewmodel/OverviewDimensionsShowHiddenTest.java [new file with mode: 0644]
test/jalview/viewmodel/ViewportRangesTest.java [new file with mode: 0644]
test/jalview/ws/dbsources/RemoteFormatTest.java [new file with mode: 0644]
test/jalview/ws/jabaws/DisorderAnnotExportImport.java
test/jalview/ws/jabaws/JpredJabaStructExportImport.java [deleted file]
test/jalview/ws/jabaws/MinJabawsClientTests.java
test/jalview/ws/jabaws/RNAStructExportImport.java
test/jalview/ws/jws2/ParameterUtilsTest.java
test/jalview/ws/seqfetcher/DasSequenceFetcher.java
test/jalview/ws/sifts/SiftsClientTest.java
test/junit/extensions/PA.java [new file with mode: 0644]
test/junit/extensions/PrivilegedAccessor.java [new file with mode: 0644]

index 8aef745..c4a2832 100644 (file)
@@ -39,7 +39,7 @@
        <classpathentry kind="lib" path="lib/jdas-1.0.4.jar"/>
        <classpathentry kind="lib" path="lib/spring-core-3.0.5.RELEASE.jar"/>
        <classpathentry kind="lib" path="lib/spring-web-3.0.5.RELEASE.jar"/>
-       <classpathentry kind="lib" path="lib/min-jabaws-client-2.1.0.jar" sourcepath="/clustengine"/>
+       <classpathentry kind="lib" path="lib/jabaws-min-client-2.2.0.jar" sourcepath="/clustengine"/>
        <classpathentry kind="lib" path="lib/json_simple-1.1.jar" sourcepath="/Users/jimp/Downloads/json_simple-1.1-all.zip"/>
        <classpathentry kind="lib" path="lib/slf4j-api-1.7.7.jar"/>
        <classpathentry kind="lib" path="lib/jsoup-1.8.1.jar"/>
index 7959c3a..04cc235 100644 (file)
@@ -159,6 +159,11 @@ the applet can be interacted with <em>via</em> its
             <td>Default is true.</td>
           </tr>
           <tr> 
+            <td>showOccupancy</td>
+            <td>true <em>or</em> false</td>
+            <td>Default is true.</td>
+          </tr>
+          <tr> 
             <td>sortBy</td>
             <td> Id <em>, </em> Pairwise Identity<em>, or</em> Length</td>
             <td> Sorts the alignment on startup</td>
diff --git a/examples/groovy/colourConserved.groovy b/examples/groovy/colourConserved.groovy
new file mode 100644 (file)
index 0000000..4a15922
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+import java.awt.Color
+import jalview.schemes.ColourSchemeI
+import jalview.schemes.ColourSchemes
+import jalview.datamodel.AnnotatedCollectionI
+import jalview.datamodel.SequenceI
+import jalview.datamodel.SequenceCollectionI
+import jalview.util.Comparison
+
+/*
+ * Closure that defines a colour scheme where fully conserved residues are red,
+ * partly conserved (match consensus but < 100% consensus) are yellow,
+ * unconserved and gaps are white
+ */
+def conserved
+conserved = { ->
+  [
+    /*
+     * name shown in the colour menu
+     */
+    getSchemeName: { -> 'Conserved' },
+    
+    /*
+     * to make a new instance for each alignment view
+     */
+    getInstance: { AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> conserved() },
+    
+    /*
+     * method only needed if colour scheme has to recalculate
+     * values when an alignment is modified
+     */
+    alignmentChanged: { AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> },
+    
+    /*
+     * determine colour for a residue at an aligned position of a
+     * sequence, given consensus residue(s) for the column and the
+     * consensus percentage identity score for the column
+     */
+    findColour: { char res, int col, SequenceI seq, String consensus, float pid -> 
+        if ('a' <= res && res <= 'z')
+        {
+            res -= ('a' - 'A');
+        }
+        if (Comparison.isGap(res) || !consensus.contains(String.valueOf(res)))
+        {
+            Color.white
+        } else if (pid < 100)
+        {
+            Color.yellow
+        } else
+        {
+            Color.red
+        }
+    },
+    
+    /*
+     * true means applicable to nucleotide or peptide data
+     */
+    isApplicableTo: {AnnotatedCollectionI coll -> true},
+    
+    /*
+     * simple colour schemes are those that depend on the residue
+     * only (these are also available to colour structure viewers)
+     */
+    isSimple: { false }
+ ] as ColourSchemeI
+}
+
+ColourSchemes.instance.registerColourScheme(conserved())
diff --git a/examples/groovy/colourUnconserved.groovy b/examples/groovy/colourUnconserved.groovy
new file mode 100644 (file)
index 0000000..68730f3
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+import java.awt.Color
+import jalview.schemes.ColourSchemeI
+import jalview.schemes.ColourSchemes
+import jalview.datamodel.AnnotatedCollectionI
+import jalview.datamodel.SequenceI
+import jalview.datamodel.SequenceCollectionI
+import jalview.util.Comparison
+
+/*
+ * Closure that defines a colour scheme where non-consensus residues are pink,
+ * other residues (and gaps) are white
+ */
+def unconserved
+unconserved = { ->
+  [
+    /*
+     * name shown in the colour menu
+     */
+    getSchemeName: { -> 'Unconserved' },
+    
+    /*
+     * to make a new instance for each alignment view
+     */
+    getInstance: { AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> unconserved() },
+    
+    /*
+     * method only needed if colour scheme has to recalculate
+     * values when an alignment is modified
+     */
+    alignmentChanged: { AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> },
+    
+    /*
+     * determine colour for a residue at an aligned position of a
+     * sequence, given consensus residue(s) for the column and the
+     * consensus percentage identity score for the column
+     */
+    findColour: { char res, int col, SequenceI seq, String consensus, float pid -> 
+        if ('a' <= res && res <= 'z')
+        {
+            res -= ('a' - 'A');
+        }
+        if (Comparison.isGap(res) || consensus.contains(String.valueOf(res)))
+        {
+            Color.white
+        } else 
+        {
+            Color.pink
+        }
+    },
+    
+    /*
+     * true means applicable to nucleotide or peptide data
+     */
+    isApplicableTo: {AnnotatedCollectionI coll -> true},
+    
+    /*
+     * simple colour schemes are those that depend on the residue
+     * only (these are also available to colour structure viewers)
+     */
+    isSimple: { false }
+ ] as ColourSchemeI
+}
+
+ColourSchemes.instance.registerColourScheme(unconserved())
diff --git a/examples/groovy/featureCounter.groovy b/examples/groovy/featureCounter.groovy
deleted file mode 100644 (file)
index 9059dd0..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.
- */
-import jalview.workers.FeatureCounterI;
-import jalview.workers.AlignmentAnnotationFactory;
-
-/*
- * Example script that registers two alignment annotation calculators
- * - one that counts residues in a column with Pfam annotation
- * - one that counts only charged residues with Pfam annotation
- *
- * To try:
- * 1. load uniref50.fa from the examples folder
- * 2. load features onto it from from examples/exampleFeatures.txt
- * 3. Open this script in the Groovy console.
- * 4. Either execute this script from the console, or via Calculate->Run Groovy Script
- * To explore further, try changing this script to count other kinds of occurrences of 
- * residue and sequence features at columns in an alignment.
- */
-
-/*
- * A closure that returns true for any Charged residue
- */
-def isCharged = { residue ->
-    switch(residue) {
-        case ['D', 'd', 'E', 'e', 'H', 'h', 'K', 'k', 'R', 'r']:
-            return true
-    }
-    false
-} 
-
-/*
- * A closure that returns 1 if sequence features include type 'Pfam', else 0
- * Argument should be a list of SequenceFeature 
- */
-def hasPfam = { features -> 
-    for (sf in features)
-    {
-        /*
-         * Here we inspect the type of the sequence feature.
-         * You can also test sf.description, sf.score, sf.featureGroup,
-         * sf.strand, sf.phase, sf.begin, sf.end
-         * or sf.getValue(attributeName) for GFF 'column 9' properties
-         */
-        if ("Pfam".equals(sf.type))
-        {
-            return true
-        }
-    }
-    false
-}
-
-/*
- * Closure that computes an annotation based on 
- * presence of particular residues and features
- * Parameters are
- * - the name (label) for the alignment annotation
- * - the description (tooltip) for the annotation
- * - a closure (groovy function) that tests whether to include a residue
- * - a closure that tests whether to increment count based on sequence features  
- */
-def getColumnCounter = { name, desc, acceptResidue, acceptFeatures ->
-    [
-     getName: { name }, 
-     getDescription: { desc },
-     getMinColour: { [0, 255, 255] }, // cyan
-     getMaxColour: { [0, 0, 255] }, // blue
-     count: 
-         { res, feats -> 
-            def c = 0
-            if (acceptResidue.call(res))
-            {
-                if (acceptFeatures.call(feats))
-                {
-                    c++
-                }
-            }
-            c
-         }
-     ] as FeatureCounterI
-}
-
-/*
- * Define an annotation row that counts any residue with Pfam domain annotation
- */
-def pfamAnnotation = getColumnCounter("Pfam", "Count of residues with Pfam domain annotation", {true}, hasPfam)
-
-/*
- * Define an annotation row that counts charged residues with Pfam domain annotation
- */
-def chargedPfamAnnotation = getColumnCounter("Pfam charged", "Count of charged residues with Pfam domain annotation", isCharged, hasPfam)
-
-/*
- * Register the annotations
- */
-AlignmentAnnotationFactory.newCalculator(pfamAnnotation) 
-AlignmentAnnotationFactory.newCalculator(chargedPfamAnnotation)
diff --git a/examples/groovy/featuresCounter.groovy b/examples/groovy/featuresCounter.groovy
new file mode 100644 (file)
index 0000000..dc4c97c
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+import jalview.workers.AlignmentAnnotationFactory;
+import jalview.workers.FeatureSetCounterI;
+
+/*
+ * Example script to compute two alignment annotations
+ * - count of Phosphorylation features
+ * - count of Turn features
+ * To try this, first load example file uniref50.fa and load on features file
+ * exampleFeatures.txt, before running this script
+ *
+ * The script only needs to be run once - it will be registered by Jalview
+ * and recalculated automatically when the alignment changes.
+ * 
+ * Note: The feature api provided by 2.10.2 is not compatible with scripts
+ * that worked with earlier Jalview versions. Apologies for the inconvenience.
+ */
+def annotator = 
+    [
+     getNames: { ['Phosphorylation', 'Turn'] as String[] }, 
+     getDescriptions:  { ['Count of Phosphorylation features', 'Count of Turn features'] as String[] },
+     getMinColour: { [0, 255, 255] as int[] }, // cyan
+     getMaxColour: { [0, 0, 255] as int[] }, // blue
+     count: 
+         { res, feats -> 
+                int phos
+                int turn
+                for (sf in feats)
+                {
+                         /*
+                          * Here we inspect the type of the sequence feature.
+                          * You can also test sf.description, sf.score, sf.featureGroup,
+                          * sf.strand, sf.phase, sf.begin, sf.end
+                          * or sf.getValue(attributeName) for GFF 'column 9' properties
+                          */
+                          if (sf.type.contains('TURN'))
+                   {
+                      turn++
+                   }
+                   if (sf.type.contains('PHOSPHORYLATION'))
+                   {
+                      phos++
+                   }
+                }
+                [phos, turn] as int[]
+         }
+     ] as FeatureSetCounterI
+    
+/*
+ * Register the annotation calculator with Jalview
+ */
+AlignmentAnnotationFactory.newCalculator(annotator) 
diff --git a/examples/groovy/multipleFeatureAnnotations.groovy b/examples/groovy/multipleFeatureAnnotations.groovy
deleted file mode 100644 (file)
index 592c7f5..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-import jalview.workers.AlignmentAnnotationFactory;
-import jalview.workers.AnnotationProviderI;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.util.ColorUtils;
-import jalview.util.Comparison;
-import java.awt.Color;
-
-/*
- * Example script to compute two alignment annotations
- * - count of Phosphorylation features
- * - count of Turn features
- * To try this, first load example file uniref50.fa and load on features file
- * exampleFeatures.txt, before running this script
- *
- * The script only needs to be run once - it will be registered by Jalview
- * and recalculated automatically when the alignment changes.
- */
-
-/*
- * A closure that returns true if value includes "PHOSPHORYLATION"
- */
-def phosCounter = { type ->    type.contains("PHOSPHORYLATION") }
-
-/*
- * A closure that returns true if value includes "TURN"
- */
-def turnCounter = { type ->    type.contains("TURN") }
-
-/*
- * A closure that computes and returns an array of Annotation values,
- * one for each column of the alignment
- */
-def getAnnotations(al, fr, counter) 
-{
-    def width = al.width
-    def counts = new int[width] 
-    def max = 0
-    
-    /*
-     * count features in each column, record the maximum value
-     */
-    for (col = 0 ; col < width ; col++)
-    {
-        def count = 0
-        for (row = 0 ; row < al.height ; row++)
-        {
-            seq = al.getSequenceAt(row)
-            if (seq != null && col < seq.getLength())
-            {
-                def res = seq.getCharAt(col)
-                if (!Comparison.isGap(res))
-                {
-                    pos = seq.findPosition(col)
-                    features = fr.findFeaturesAtRes(seq, pos)
-                    for (feature in features)
-                    {
-                        if (counter.call(feature.type))
-                        {
-                            count++
-                        }
-                    }
-                }
-            }
-        }
-        counts[col] = count
-        if (count > max)
-        {
-            max = count
-        }
-    }
-    
-    /*
-     * make the Annotation objects, with a graduated colour scale 
-     * (from min value to max value) for the histogram bars 
-     */
-    def zero = '0' as char
-    def anns = new Annotation[width] 
-    for (col = 0 ; col < width ; col++)
-    {
-        def c = counts[col]
-        if (c > 0)
-        {
-            Color color = ColorUtils.getGraduatedColour(c, 0, Color.cyan,
-                max, Color.blue)
-            anns[col] = AlignmentAnnotationFactory.newAnnotation(String.valueOf(c),
-                String.valueOf(c), zero, c, color)
-        }
-    }
-    anns
-}
-
-/*
- * Define the method that performs the calculations, and builds two
- * AlignmentAnnotation objects
- */
-def annotator = 
-    [ calculateAnnotation: { al, fr ->
-        def phosAnns = getAnnotations(al, fr, phosCounter)
-        def ann1 = AlignmentAnnotationFactory.newAlignmentAnnotation("Phosphorylation", "Count of Phosphorylation features", phosAnns)
-        def turnAnns = getAnnotations(al, fr, turnCounter)
-        def ann2 = AlignmentAnnotationFactory.newAlignmentAnnotation("Turn", "Count of Turn features", turnAnns)
-        return [ann1, ann2]
-      } 
-    ] as AnnotationProviderI
-    
-/*
- * Register the annotation calculator with Jalview
- */
-AlignmentAnnotationFactory.newCalculator(annotator) 
diff --git a/examples/groovy/visibleFeaturesCounter.groovy b/examples/groovy/visibleFeaturesCounter.groovy
new file mode 100644 (file)
index 0000000..b3180f8
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+import jalview.bin.Jalview
+import jalview.workers.FeatureSetCounterI
+import jalview.workers.AlignmentAnnotationFactory
+
+/*
+ * Demonstration of FeatureSetCounterI
+ * compute annotation tracks counting number of displayed 
+ * features of each type in each column
+ */
+
+/*
+ * discover features on the current view
+ */
+def featuresDisp=Jalview.currentAlignFrame.currentView.featuresDisplayed
+if (featuresDisp == null) {
+    print 'Need at least one feature visible on alignment'
+}
+def visibleFeatures=featuresDisp.visibleFeatures.toList()
+assert 'java.util.ArrayList' == visibleFeatures.class.name
+
+/*
+ * A closure that returns an array of features present 
+ * for each feature type in visibleFeatures
+ * Argument 'features' will be a list of SequenceFeature 
+ */
+def getCounts = 
+    { features -> 
+        int[] obs = new int[visibleFeatures.size]
+        for (sf in features)
+        {
+            /*
+             * Here we inspect the type of the sequence feature.
+             * You can also test sf.description, sf.score, sf.featureGroup,
+             * sf.strand, sf.phase, sf.begin, sf.end
+             * or sf.getValue(attributeName) for GFF 'column 9' properties
+             */
+            int pos = 0
+            for (type in visibleFeatures) 
+            {
+              if (type.equals(sf.type)) 
+              {
+                  obs[pos]++
+              }
+              pos++
+            }
+        }
+        obs
+}
+  
+/*
+ * Define something that counts each visible feature type
+ */
+def columnSetCounter =
+    [
+     getNames: { visibleFeatures as String[] }, 
+     getDescriptions:  { visibleFeatures as String[] },
+     getMinColour: { [0, 255, 255] as int[] }, // cyan
+     getMaxColour: { [0, 0, 255] as int[] }, // blue
+     count: 
+         { res, feats -> 
+             getCounts.call(feats) 
+         }
+     ] as FeatureSetCounterI
+
+/*
+ * and register the counter
+ */
+AlignmentAnnotationFactory.newCalculator(columnSetCounter)
index 794f42b..1779247 100644 (file)
@@ -18,5 +18,5 @@ SEQUENCE_GROUP        Group_A 30      50      *
 SEQUENCE_GROUP Group_B 1       351     2-5
 SEQUENCE_GROUP Group_C 12      14      -1      seq1    seq2    seq3
 PROPERTIES     Group_A description=This is the description     colour=Helix Propensity pidThreshold=0  outlineColour=red       displayBoxes=true       displayText=false       colourText=false        textCol1=black  textCol2=black  textColThreshold=0
-PROPERTIES     Group_B outlineColour=red
+PROPERTIES     Group_B outlineColour=red       colour=None
 PROPERTIES     Group_C colour=Clustal
index 984c2d1..54cce2a 100755 (executable)
@@ -22,7 +22,7 @@
    <mapID target="home" url="html/index.html" />
    
    <mapID target="new" url="html/whatsNew.html"/>
-   <mapID target="release" url="html/releases.html#Jalview.2.10.1"/>
+   <mapID target="release" url="html/releases.html#Jalview.2.10.2"/>
    <mapID target="alannotation" url="html/features/annotation.html"/>
    <mapID target="keys" url="html/keys.html"/>
    <mapID target="newkeys" url="html/features/newkeystrokes.html"/>
@@ -75,6 +75,7 @@
    <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="memory" url="html/memory.html" />
    <mapID target="groovy" url="html/features/groovy.html" />
-   <mapID target="groovy.featurecounter" url="html/groovy/featureCounter.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="homeIcon" url="icons/Home.png" />
    <mapID target="printIcon" url="icons/print.png" />
    <mapID target="printSetupIcon" url="icons/setup.png" />
+   
+   <mapID target="overview" url="features/overview.html" />
 </map>
index 482ccdf..a8aadf5 100755 (executable)
        <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="Groovy Features Counter example" target="groovy.featurescounter"/>
+                               <tocitem text="Custom Colourschemes in Groovy" target="groovy.colours"/>
+                               <tocitem text="Omit hidden regions in Overview" target="overview"/>
+                               <tocitem text="identifers.org for URL Links" target="linksprefs" />
+                               <tocitem text="New features in Split Frame View" target="splitframe.mirrorfonts" />
                </tocitem>
                
                <tocitem text="Editing Alignments" target="edit" />
                </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="urllinkspref" />
+                   <tocitem text="Configuring URL Links" target="linksprefs" />
                </tocitem>
                <tocitem text="VAMSAS Data Exchange" target="vamsas">
                        <!-- what can Jalview share with other apps -->
                <tocitem text="Preferences" target="preferences" />
                <tocitem text="Memory Settings" target="memory" expand="false"/>
                <tocitem text="Scripting with Groovy" target="groovy">
-                       <tocitem text="Groovy Feature Counter example" target="groovy.featurecounter"/>
+                       <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" />
index fb6c356..c2fde1c 100755 (executable)
@@ -27,7 +27,7 @@
     <strong>User Defined Colours</strong>
   </p>
   <p>
-    <img src="userDefined_java6.gif" width="815" height="402">
+    <img src="userDefined_java7.gif" width="815" height="402">
   </p>
   <p>
     You may define any number of new colour schemes, each with a unique
@@ -41,7 +41,7 @@
     The <strong>Case Sensitive</strong> option allows you to choose
     distinct colours for upper and lower case residue codes.
   <p>
-    The <strong>Lower Case Colour</strong> option allows you to apply a selected colour
+    The <strong>Colour All Lower Case  </strong> option allows you to apply a selected colour
     to all lower case residues.
   <p>
     Click <strong>Apply</strong> or <strong>OK</strong> to set your new
   <br> Any saved colour schemes will be automatically loaded the
   next time you use Jalview.
   <br>
-  <br>
-  <em>Note: the screenshot shows the appearance when running Java
-    version 6. For Java 7 (from Jalview 2.8.2) only the Swatches colour
-    chooser is currently supported (for reasons of available screen
-    space).</em>
-  <p />
 </body>
 </html>
diff --git a/help/html/colourSchemes/userDefined_java6.gif b/help/html/colourSchemes/userDefined_java6.gif
deleted file mode 100644 (file)
index d737e80..0000000
Binary files a/help/html/colourSchemes/userDefined_java6.gif and /dev/null differ
index f0ced11..de80b84 100644 (file)
Binary files a/help/html/colourSchemes/userDefined_java7.gif and b/help/html/colourSchemes/userDefined_java7.gif differ
index 5ae00af..68ac465 100644 (file)
             structure in the alignment. The regions used to calculate
             the superposition will be highlighted using the 'Cartoon'
             rendering style, and the remaining data shown as a chain
-            trace.<br>
+            trace.<br/><br/>
         </em></li>
-      </ul></li>
-    <li><strong>Help<br>
+        <li><strong><a name="experimental">EXPERIMENTAL FEATURES</a></strong><br/>
+          <em>
+            These are only available if the <strong>Tools&#8594;Enable
+            Experimental Features</strong> option is enabled. (Since Jalview 2.10.2)</em>
+          <ul>
+            <li><strong>Write Jalview features</strong><br /> <em>Selecting
+                this option will create new residue attributes for any
+                features currently visible in the associated alignment
+                views, allowing those positions to be selected and
+                analysed with via Chimera's 'Render by Attribute' tool
+                (found in the Tools submenu called Structure Analysis).<br />
+                <br />If you use this option, please remember to select
+                the <em>Refresh Menus</em> option in Chimera's Render by
+                Attribute dialog box in order to see the attributes
+                derived from Jalview sequence features.
+            </em><br />
+            <a href="https://issues.jalview.org/browse/JAL-2295">View
+                this function's issue in Jalview's bug tracker</a></li>
+            <li><strong>Fetch Chimera Attributes</strong><br /> <em>This
+                submenu lists available Chimera residue attributes that
+                can be imported as Jalview features on associated
+                sequences.<br />This is particularly useful for
+                transferring quantitative positional annotation. For
+                example, structure similarity for an alignment can be
+                visualised by transferring the local RMSD attributes
+                generated by Chimera's Match->Align tool onto aligned
+                sequences and displayed with a <a
+                href="featureschemes.html">Graduated feature colour
+                  scheme</a>.
+            </em><a href="https://issues.jalview.org/browse/JAL-2296">View
+                this function's issue in Jalview's bug tracker</a></li>
+          </ul></li>
+        <li><strong>Help<br>
     </strong>
       <ul>
         <li><strong>Chimera Help<br>
index 254f92e..ead4436 100644 (file)
@@ -108,9 +108,18 @@ print currentAlFrame.getTitle();</pre>
     simplified the alignment analysis programming interface in Jalview
     2.10 to make it easy for you to add your own dynamic annotation
     tracks with Groovy. Have a look at the <a
-      href="../groovy/featureCounter.html">featureCounter.groovy</a>
+      href="../groovy/featuresCounter.html">featuresCounter.groovy</a>
     example for more information.
   </p>
+  <p><a name="groovyColours"/>
+    <em>Creating custom colourschemes</em><br/>
+    You can create your own alignment colourschemes with a groovy script. We've provided two examples:<br/>
+    <ul>
+    <li><a href="http://www.jalview.org/examples/groovy/colourConserved.groovy">colourConserved.groovy</a> creates an 'Conserved' colourscheme - similar to the classic <a href="http://www.nrbsc.org/old/gfx/genedoc/">GeneDOC</a> shading model.</li>
+    <li><a href="http://www.jalview.org/examples/groovy/colourUnconserved.groovy">colourUnconserved.groovy</a> creates an 'Unconserved' colourscheme, where any unconserved residues are coloured pink.</li>
+    
+    </ul>
+  </p>
 
 </body>
 </html>
index 9d36a1c..4f26592 100755 (executable)
   <p>The red box indicates the currently viewed region of the
     alignment, this may be moved by clicking and dragging with the
     mouse.</p>
+    <p><strong>Right-click</strong> (or CMD-Click) to open the
+    overview's popup menu. This provides an option to include hidden
+    regions in the overview (shown as dark-grey rows and columns). <br/><br/>
+    <em>The option to include/exclude hidden regions in the
+    overview was introduced in Jalview 2.10.2</em>. 
   <p>
     <img src="overview.gif" width="407" height="137">
   </p>
index da045ba..acd7ba6 100755 (executable)
   <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 3
-    standard annotations <em>Conservation</em>, <em>Quality</em> and <em>Consensus</em>
-    for the alignment may be shown or hidden by default using the
-    checkboxes below.
+    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
     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>
+  <p>The links table is prepoulated 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>
index 03b993c..3862c39 100644 (file)
       alignments, the <strong><a href="../menus/alwformat.html">"Format&#8594;Font"</a></strong>
       menu option has an option 'Scale protein residues to codons'. This
       option will make each protein residue the same width as a DNA
-      codon (so the alignments 'line up' vertically)
+      codon (so the alignments 'line up' vertically).<br/><br/>
+      <a name="mirror"/>The 'Use same 
+      font for cDNA and peptide' checkbox, when enabled, ensures that font or
+       font-size changes in either the cDNA or Protein alignment will also 
+       be mirrored. (<em>Added in 2.10.2</em>)
     </li>
     <li><strong>"View&#8594;Protein"</strong> (in the cDNA panel)
       or <strong>"View&#8594;Nucleotide"</strong> (in the protein panel)
diff --git a/help/html/groovy/featureCounter.html b/help/html/groovy/featureCounter.html
deleted file mode 100644 (file)
index 2ebaf45..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-<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>Extending Jalview with Groovy - Feature Counter Example</title>
-</head>
-<body>
-  <p>
-    <strong>Extending Jalview with Groovy - A customisable
-      feature counter</strong><br /> <br />The groovy script below shows how to
-    add a new calculation track to a Jalview alignment window.
-  </p>
-  <p>As currently written, it will add two tracks to a protein
-    alignment view which count Pfam features in each column, and ones
-    where a charge residue also occur.</p>
-  <p>To try it for yourself:</p>
-  <ol>
-    <li>Copy and paste it into the groovy script console</li>
-    <li>Load the example Feredoxin project (the one that opens by
-      default when you first launched Jalview)</li>
-    <li>Select <strong>Calculations&#8594;Execute Groovy
-        Script</strong> from the alignment window's menu bar to run the script on
-      the current view.
-    </li>
-  </ol>
-  <em><a
-    href="http://www.jalview.org/examples/groovy/featureCounter.groovy">http://www.jalview.org/examples/groovy/featureCounter.groovy</a>
-    - rendered with <a href="http://hilite.me">hilite.me</a></em>
-  <!-- HTML generated using hilite.me -->
-  <div
-    style="background: #ffffff; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;">
-    <pre style="margin: 0; line-height: 125%">
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Jalview - A Sequence Alignment Editor and Viewer (Version 2.10)</span>
-<span style="color: #888888"> * Copyright (C) 2016 The Jalview Authors</span>
-<span style="color: #888888"> * </span>
-<span style="color: #888888"> * This file is part of Jalview.</span>
-<span style="color: #888888"> * </span>
-<span style="color: #888888"> * Jalview is free software: you can redistribute it and/or</span>
-<span style="color: #888888"> * modify it under the terms of the GNU General Public License </span>
-<span style="color: #888888"> * as published by the Free Software Foundation, either version 3</span>
-<span style="color: #888888"> * of the License, or (at your option) any later version.</span>
-<span style="color: #888888"> *  </span>
-<span style="color: #888888"> * Jalview is distributed in the hope that it will be useful, but </span>
-<span style="color: #888888"> * WITHOUT ANY WARRANTY; without even the implied warranty </span>
-<span style="color: #888888"> * of MERCHANTABILITY or FITNESS FOR A PARTICULAR </span>
-<span style="color: #888888"> * PURPOSE.  See the GNU General Public License for more details.</span>
-<span style="color: #888888"> * </span>
-<span style="color: #888888"> * You should have received a copy of the GNU General Public License</span>
-<span style="color: #888888"> * along with Jalview.  If not, see &lt;http://www.gnu.org/licenses/&gt;.</span>
-<span style="color: #888888"> * The Jalview Authors are detailed in the &#39;AUTHORS&#39; file.</span>
-<span style="color: #888888"> */</span>
-<span style="color: #008800; font-weight: bold">import</span> <span
-        style="color: #0e84b5; font-weight: bold">jalview.workers.FeatureCounterI</span><span
-        style="color: #333333">;</span>
-<span style="color: #008800; font-weight: bold">import</span> <span
-        style="color: #0e84b5; font-weight: bold">jalview.workers.AlignmentAnnotationFactory</span><span
-        style="color: #333333">;</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Example script that registers two alignment annotation calculators</span>
-<span style="color: #888888"> * - one that counts residues in a column with Pfam annotation</span>
-<span style="color: #888888"> * - one that counts only charged residues with Pfam annotation</span>
-<span style="color: #888888"> *</span>
-<span style="color: #888888"> * To try:</span>
-<span style="color: #888888"> * 1. load uniref50.fa from the examples folder</span>
-<span style="color: #888888"> * 2. load features onto it from from examples/exampleFeatures.txt</span>
-<span style="color: #888888"> * 3. Open this script in the Groovy console.</span>
-<span style="color: #888888"> * 4. Either execute this script from the console, or via Calculate-&gt;Run Groovy Script</span>
-<span style="color: #888888"> </span>
-<span style="color: #888888"> * To explore further, try changing this script to count other kinds of occurrences of </span>
-<span style="color: #888888"> * residue and sequence features at columns in an alignment.</span>
-<span style="color: #888888"> */</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * A closure that returns true for any Charged residue</span>
-<span style="color: #888888"> */</span>
-<span style="color: #333399; font-weight: bold">def</span> isCharged <span
-        style="color: #333333">=</span> <span style="color: #333333">{</span> residue <span
-        style="color: #333333">-&gt;</span>
-    <span style="color: #008800; font-weight: bold">switch</span><span
-        style="color: #333333">(</span>residue<span
-        style="color: #333333">)</span> <span style="color: #333333">{</span>
-        <span style="color: #008800; font-weight: bold">case</span> <span
-        style="color: #333333">[</span><span
-        style="background-color: #fff0f0">&#39;D&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;d&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;E&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;e&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;H&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;h&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;K&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;k&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;R&#39;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&#39;r&#39;</span><span
-        style="color: #333333">]:</span>
-            <span style="color: #008800; font-weight: bold">return</span> <span
-        style="color: #008800; font-weight: bold">true</span>
-    <span style="color: #333333">}</span>
-    <span style="color: #008800; font-weight: bold">false</span>
-<span style="color: #333333">}</span> 
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * A closure that returns 1 if sequence features include type &#39;Pfam&#39;, else 0</span>
-<span style="color: #888888"> * Argument should be a list of SequenceFeature </span>
-<span style="color: #888888"> */</span>
-<span style="color: #333399; font-weight: bold">def</span> hasPfam <span
-        style="color: #333333">=</span> <span style="color: #333333">{</span> features <span
-        style="color: #333333">-&gt;</span> 
-    <span style="color: #008800; font-weight: bold">for</span> <span
-        style="color: #333333">(</span>sf <span
-        style="color: #008800; font-weight: bold">in</span> features<span
-        style="color: #333333">)</span>
-    <span style="color: #333333">{</span>
-        <span style="color: #888888">/*</span>
-<span style="color: #888888">         * Here we inspect the type of the sequence feature.</span>
-<span style="color: #888888">         * You can also test sf.description, sf.score, sf.featureGroup,</span>
-<span style="color: #888888">         * sf.strand, sf.phase, sf.begin, sf.end</span>
-<span style="color: #888888">         * or sf.getValue(attributeName) for GFF &#39;column 9&#39; properties</span>
-<span style="color: #888888">         */</span>
-        <span style="color: #008800; font-weight: bold">if</span> <span
-        style="color: #333333">(</span><span
-        style="background-color: #fff0f0">&quot;Pfam&quot;</span><span
-        style="color: #333333">.</span><span style="color: #0000CC">equals</span><span
-        style="color: #333333">(</span>sf<span style="color: #333333">.</span><span
-        style="color: #0000CC">type</span><span style="color: #333333">))</span>
-        <span style="color: #333333">{</span>
-            <span style="color: #008800; font-weight: bold">return</span> <span
-        style="color: #008800; font-weight: bold">true</span>
-        <span style="color: #333333">}</span>
-    <span style="color: #333333">}</span>
-    <span style="color: #008800; font-weight: bold">false</span>
-<span style="color: #333333">}</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Closure that computes an annotation based on </span>
-<span style="color: #888888"> * presence of particular residues and features</span>
-<span style="color: #888888"> * Parameters are</span>
-<span style="color: #888888"> * - the name (label) for the alignment annotation</span>
-<span style="color: #888888"> * - the description (tooltip) for the annotation</span>
-<span style="color: #888888"> * - a closure (groovy function) that tests whether to include a residue</span>
-<span style="color: #888888"> * - a closure that tests whether to increment count based on sequence features  </span>
-<span style="color: #888888"> */</span>
-<span style="color: #333399; font-weight: bold">def</span> getColumnCounter <span
-        style="color: #333333">=</span> <span style="color: #333333">{</span> name<span
-        style="color: #333333">,</span> desc<span style="color: #333333">,</span> acceptResidue<span
-        style="color: #333333">,</span> acceptFeatures <span
-        style="color: #333333">-&gt;</span>
-    <span style="color: #333333">[</span>
-     <span style="color: #997700; font-weight: bold">getName:</span> <span
-        style="color: #333333">{</span> name <span
-        style="color: #333333">},</span> 
-     <span style="color: #997700; font-weight: bold">getDescription:</span> <span
-        style="color: #333333">{</span> desc <span
-        style="color: #333333">},</span>
-     <span style="color: #997700; font-weight: bold">getMinColour:</span> <span
-        style="color: #333333">{</span> <span style="color: #333333">[</span><span
-        style="color: #0000DD; font-weight: bold">0</span><span
-        style="color: #333333">,</span> <span
-        style="color: #0000DD; font-weight: bold">255</span><span
-        style="color: #333333">,</span> <span
-        style="color: #0000DD; font-weight: bold">255</span><span
-        style="color: #333333">]</span> <span style="color: #333333">},</span> <span
-        style="color: #888888">// cyan</span>
-     <span style="color: #997700; font-weight: bold">getMaxColour:</span> <span
-        style="color: #333333">{</span> <span style="color: #333333">[</span><span
-        style="color: #0000DD; font-weight: bold">0</span><span
-        style="color: #333333">,</span> <span
-        style="color: #0000DD; font-weight: bold">0</span><span
-        style="color: #333333">,</span> <span
-        style="color: #0000DD; font-weight: bold">255</span><span
-        style="color: #333333">]</span> <span style="color: #333333">},</span> <span
-        style="color: #888888">// blue</span>
-     <span style="color: #997700; font-weight: bold">count:</span> 
-         <span style="color: #333333">{</span> res<span
-        style="color: #333333">,</span> feats <span
-        style="color: #333333">-&gt;</span> 
-            <span style="color: #333399; font-weight: bold">def</span> c <span
-        style="color: #333333">=</span> <span
-        style="color: #0000DD; font-weight: bold">0</span>
-            <span style="color: #008800; font-weight: bold">if</span> <span
-        style="color: #333333">(</span>acceptResidue<span
-        style="color: #333333">.</span><span style="color: #0000CC">call</span><span
-        style="color: #333333">(</span>res<span style="color: #333333">))</span>
-            <span style="color: #333333">{</span>
-                <span style="color: #008800; font-weight: bold">if</span> <span
-        style="color: #333333">(</span>acceptFeatures<span
-        style="color: #333333">.</span><span style="color: #0000CC">call</span><span
-        style="color: #333333">(</span>feats<span style="color: #333333">))</span>
-                <span style="color: #333333">{</span>
-                    c<span style="color: #333333">++</span>
-                <span style="color: #333333">}</span>
-            <span style="color: #333333">}</span>
-            c
-         <span style="color: #333333">}</span>
-     <span style="color: #333333">]</span> <span
-        style="color: #008800; font-weight: bold">as</span> FeatureCounterI
-<span style="color: #333333">}</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Define an annotation row that counts any residue with Pfam domain annotation</span>
-<span style="color: #888888"> */</span>
-<span style="color: #333399; font-weight: bold">def</span> pfamAnnotation <span
-        style="color: #333333">=</span> getColumnCounter<span
-        style="color: #333333">(</span><span
-        style="background-color: #fff0f0">&quot;Pfam&quot;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&quot;Count of residues with Pfam domain annotation&quot;</span><span
-        style="color: #333333">,</span> <span style="color: #333333">{</span><span
-        style="color: #008800; font-weight: bold">true</span><span
-        style="color: #333333">},</span> hasPfam<span
-        style="color: #333333">)</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Define an annotation row that counts charged residues with Pfam domain annotation</span>
-<span style="color: #888888"> */</span>
-<span style="color: #333399; font-weight: bold">def</span> chargedPfamAnnotation <span
-        style="color: #333333">=</span> getColumnCounter<span
-        style="color: #333333">(</span><span
-        style="background-color: #fff0f0">&quot;Pfam charged&quot;</span><span
-        style="color: #333333">,</span> <span
-        style="background-color: #fff0f0">&quot;Count of charged residues with Pfam domain annotation&quot;</span><span
-        style="color: #333333">,</span> isCharged<span
-        style="color: #333333">,</span> hasPfam<span
-        style="color: #333333">)</span>
-
-<span style="color: #888888">/*</span>
-<span style="color: #888888"> * Register the annotations</span>
-<span style="color: #888888"> */</span>
-AlignmentAnnotationFactory<span style="color: #333333">.</span><span
-        style="color: #0000CC">newCalculator</span><span
-        style="color: #333333">(</span>pfamAnnotation<span
-        style="color: #333333">)</span> 
-AlignmentAnnotationFactory<span style="color: #333333">.</span><span
-        style="color: #0000CC">newCalculator</span><span
-        style="color: #333333">(</span>chargedPfamAnnotation<span
-        style="color: #333333">)</span>
-</pre>
-  </div>
-</body>
-</html>
diff --git a/help/html/groovy/featuresCounter.html b/help/html/groovy/featuresCounter.html
new file mode 100644 (file)
index 0000000..3b6705b
--- /dev/null
@@ -0,0 +1,123 @@
+<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>Extending Jalview with Groovy - Feature Counter Example</title>
+</head>
+<body>
+  <p>
+    <strong>Extending Jalview with Groovy - A customisable
+      feature counter</strong><br /> <br />The groovy script below shows how to
+    add a new calculation track to a Jalview alignment window.
+  </p>
+  <p>As currently written, it will add two tracks to a protein
+    alignment view which count Pfam features in each column, and ones
+    where a charge residue also occur.</p>
+  <p>To try it for yourself:</p>
+  <ol>
+    <li>Copy and paste it into the groovy script console</li>
+    <li>Load the example Feredoxin project (the one that opens by
+      default when you first launched Jalview)</li>
+    <li>Select <strong>Calculations&#8594;Execute Groovy
+        Script</strong> from the alignment window's menu bar to run the script on
+      the current view.
+    </li>
+  </ol>
+  <strong>Please note: The 2.10.2 feature counting interface is not compatible with earlier versions.</strong><br/><br/>
+  <em><a
+    href="http://www.jalview.org/examples/groovy/featuresCounter.groovy">http://www.jalview.org/examples/groovy/featuresCounter.groovy</a>
+    - rendered with <a href="http://hilite.me">hilite.me</a></em>
+  <!-- HTML generated using hilite.me --><div style="background: #f8f8f8; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #408080; font-style: italic">/*</span>
+<span style="color: #408080; font-style: italic"> * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)</span>
+<span style="color: #408080; font-style: italic"> * Copyright (C) $$Year-Rel$$ The Jalview Authors</span>
+<span style="color: #408080; font-style: italic"> * </span>
+<span style="color: #408080; font-style: italic"> * This file is part of Jalview.</span>
+<span style="color: #408080; font-style: italic"> * </span>
+<span style="color: #408080; font-style: italic"> * Jalview is free software: you can redistribute it and/or</span>
+<span style="color: #408080; font-style: italic"> * modify it under the terms of the GNU General Public License </span>
+<span style="color: #408080; font-style: italic"> * as published by the Free Software Foundation, either version 3</span>
+<span style="color: #408080; font-style: italic"> * of the License, or (at your option) any later version.</span>
+<span style="color: #408080; font-style: italic"> *  </span>
+<span style="color: #408080; font-style: italic"> * Jalview is distributed in the hope that it will be useful, but </span>
+<span style="color: #408080; font-style: italic"> * WITHOUT ANY WARRANTY; without even the implied warranty </span>
+<span style="color: #408080; font-style: italic"> * of MERCHANTABILITY or FITNESS FOR A PARTICULAR </span>
+<span style="color: #408080; font-style: italic"> * PURPOSE.  See the GNU General Public License for more details.</span>
+<span style="color: #408080; font-style: italic"> * </span>
+<span style="color: #408080; font-style: italic"> * You should have received a copy of the GNU General Public License</span>
+<span style="color: #408080; font-style: italic"> * along with Jalview.  If not, see &lt;http://www.gnu.org/licenses/&gt;.</span>
+<span style="color: #408080; font-style: italic"> * The Jalview Authors are detailed in the &#39;AUTHORS&#39; file.</span>
+<span style="color: #408080; font-style: italic"> */</span>
+
+<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">jalview.workers.AlignmentAnnotationFactory</span><span style="color: #666666">;</span>
+<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">jalview.workers.FeatureSetCounterI</span><span style="color: #666666">;</span>
+
+<span style="color: #408080; font-style: italic">/*</span>
+<span style="color: #408080; font-style: italic"> * Example script to compute two alignment annotations</span>
+<span style="color: #408080; font-style: italic"> * - count of Phosphorylation features</span>
+<span style="color: #408080; font-style: italic"> * - count of Turn features</span>
+<span style="color: #408080; font-style: italic"> * To try this, first load example file uniref50.fa and load on features file</span>
+<span style="color: #408080; font-style: italic"> * exampleFeatures.txt, before running this script</span>
+<span style="color: #408080; font-style: italic"> *</span>
+<span style="color: #408080; font-style: italic"> * The script only needs to be run once - it will be registered by Jalview</span>
+<span style="color: #408080; font-style: italic"> * and recalculated automatically when the alignment changes.</span>
+<span style="color: #408080; font-style: italic"> * </span>
+<span style="color: #408080; font-style: italic"> * Note: The feature api provided by 2.10.2 is not compatible with scripts</span>
+<span style="color: #408080; font-style: italic"> * that worked with earlier Jalview versions. Apologies for the inconvenience.</span>
+<span style="color: #408080; font-style: italic"> */</span>
+<span style="color: #B00040">def</span> annotator <span style="color: #666666">=</span> 
+    <span style="color: #666666">[</span>
+     <span style="color: #A0A000">getNames:</span> <span style="color: #666666">{</span> <span style="color: #666666">[</span><span style="color: #BA2121">&#39;Phosphorylation&#39;</span><span style="color: #666666">,</span> <span style="color: #BA2121">&#39;Turn&#39;</span><span style="color: #666666">]</span> <span style="color: #008000; font-weight: bold">as</span> String<span style="color: #666666">[]</span> <span style="color: #666666">},</span> 
+     <span style="color: #A0A000">getDescriptions:</span>  <span style="color: #666666">{</span> <span style="color: #666666">[</span><span style="color: #BA2121">&#39;Count of Phosphorylation features&#39;</span><span style="color: #666666">,</span> <span style="color: #BA2121">&#39;Count of Turn features&#39;</span><span style="color: #666666">]</span> <span style="color: #008000; font-weight: bold">as</span> String<span style="color: #666666">[]</span> <span style="color: #666666">},</span>
+     <span style="color: #A0A000">getMinColour:</span> <span style="color: #666666">{</span> <span style="color: #666666">[0,</span> <span style="color: #666666">255,</span> <span style="color: #666666">255]</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #B00040">int</span><span style="color: #666666">[]</span> <span style="color: #666666">},</span> <span style="color: #408080; font-style: italic">// cyan</span>
+     <span style="color: #A0A000">getMaxColour:</span> <span style="color: #666666">{</span> <span style="color: #666666">[0,</span> <span style="color: #666666">0,</span> <span style="color: #666666">255]</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #B00040">int</span><span style="color: #666666">[]</span> <span style="color: #666666">},</span> <span style="color: #408080; font-style: italic">// blue</span>
+     <span style="color: #A0A000">count:</span> 
+         <span style="color: #666666">{</span> res<span style="color: #666666">,</span> feats <span style="color: #666666">-&gt;</span> 
+                <span style="color: #B00040">int</span> phos
+                <span style="color: #B00040">int</span> turn
+                <span style="color: #0000FF">for</span> <span style="color: #666666">(</span>sf <span style="color: #008000; font-weight: bold">in</span> feats<span style="color: #666666">)</span>
+                <span style="color: #666666">{</span>
+                         <span style="color: #408080; font-style: italic">/*</span>
+<span style="color: #408080; font-style: italic">                         * Here we inspect the type of the sequence feature.</span>
+<span style="color: #408080; font-style: italic">                         * You can also test sf.description, sf.score, sf.featureGroup,</span>
+<span style="color: #408080; font-style: italic">                         * sf.strand, sf.phase, sf.begin, sf.end</span>
+<span style="color: #408080; font-style: italic">                         * or sf.getValue(attributeName) for GFF &#39;column 9&#39; properties</span>
+<span style="color: #408080; font-style: italic">                         */</span>
+                          <span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">(</span>sf<span style="color: #666666">.</span><span style="color: #7D9029">type</span><span style="color: #666666">.</span><span style="color: #7D9029">contains</span><span style="color: #666666">(</span><span style="color: #BA2121">&#39;TURN&#39;</span><span style="color: #666666">))</span>
+                   <span style="color: #666666">{</span>
+                      turn<span style="color: #666666">++</span>
+                   <span style="color: #666666">}</span>
+                   <span style="color: #008000; font-weight: bold">if</span> <span style="color: #666666">(</span>sf<span style="color: #666666">.</span><span style="color: #7D9029">type</span><span style="color: #666666">.</span><span style="color: #7D9029">contains</span><span style="color: #666666">(</span><span style="color: #BA2121">&#39;PHOSPHORYLATION&#39;</span><span style="color: #666666">))</span>
+                   <span style="color: #666666">{</span>
+                      phos<span style="color: #666666">++</span>
+                   <span style="color: #666666">}</span>
+                <span style="color: #666666">}</span>
+                <span style="color: #666666">[</span>phos<span style="color: #666666">,</span> turn<span style="color: #666666">]</span> <span style="color: #008000; font-weight: bold">as</span> <span style="color: #B00040">int</span><span style="color: #666666">[]</span>
+         <span style="color: #666666">}</span>
+     <span style="color: #666666">]</span> <span style="color: #008000; font-weight: bold">as</span> FeatureSetCounterI
+    
+<span style="color: #408080; font-style: italic">/*</span>
+<span style="color: #408080; font-style: italic"> * Register the annotation calculator with Jalview</span>
+<span style="color: #408080; font-style: italic"> */</span>
+AlignmentAnnotationFactory<span style="color: #666666">.</span><span style="color: #7D9029">newCalculator</span><span style="color: #666666">(</span>annotator<span style="color: #666666">)</span> 
+</pre></div>
+</body>
+</html>
index 20e784b..a93ce4b 100755 (executable)
@@ -86,6 +86,7 @@
             the <a href="../features/groovy.html">Groovy Console</a> for
             interactive scripting.
         </em><strong><br></strong></li>
+        <li><strong>Enable Experimental Features</strong> <em>Enable or disable <a href="../whatsNew.html#experimental">features still under development</a> in Jalview's user interface. This setting is remembered in your preferences.</em>
 
       </ul></li>
     <li><strong>Vamsas</strong> <em>For more details, read the
index d42f854..7625606 100755 (executable)
         href="../features/varna.html">VARNA</a>.
     </em></li>
     <li><a name="hideinserts"><strong>Hide Insertions</strong></a><br />
-      <em>Hides columns containing gaps in the current sequence or
-        selected region, and reveals columns not including gaps.</em>
+      <em>Hides columns containing gaps in both the current
+        sequence and selected region, and reveals columns not including
+        gaps. (before 2.10.2, this option hid or revealed columns
+        according to gaps in just the current sequence)</em></li>
     <li><strong>Hide Sequences</strong><br> <em>Hides the
         currently selected sequences in this alignment view.</em><strong><br>
     </strong></li>
index 6f44b3d..5c8da91 100755 (executable)
  -->
 <head>
 <title>Release History</title>
+<style>
+ul {
+  /* remove bullets, narrower indent */
+  list-style-type: none;
+  margin:0;
+  padding-left: 10px;
+  padding-bottom: 4px;
+}
+
+li {
+  /* separate the items from eachother */
+   margin-left: -3px;
+   padding-bottom: 3px;
+   padding-left: 6px;   
+}
+li:before {
+  /*   doesnt get processed in javahelp */
+  content: '\00b7 ';
+  padding: 3px;
+  margin-left: -14px;
+}
+
+</style>
 </head>
 <body>
   <p>
     <tr>
       <td width="60" nowrap>
         <div align="center">
+          <strong><a name="Jalview.2.10.2">2.10.2</a><br />
+            <em>30/5/2017</em></strong>
+        </div>
+      </td>
+      <td><div align="left">
+          <em>General</em>
+          <ul>
+          <li><!-- JAL-2360,JAL-2371, -->More robust colours and shader model for alignments and groups</li>
+          <li><!--  JAL-384 -->Custom shading schemes created via groovy scripts</li>
+          <li><!--  JAL-2491 -->linked scrolling of CDS/Protein views via Overview or sequence motif search operations</li>
+          <li><!--  JAL-2526 -->Efficiency improvements for interacting with alignment and overview windows</li>
+          <li><!-- JAL-2388 -->Hidden columns and sequences can be omitted in Overview</li>
+            <li>
+              <!-- JAL-2535 -->Posterior probability annotation from
+              Stockholm files imported as sequence associated annotation
+            </li>
+            <li>
+              <!-- JAL-2533 -->Sequence names don't include file
+              extension when importing structure files without embedded
+              names or PDB accessions
+            </li>
+            <li><!-- JAL-2547 -->Amend sequence features dialog box can be opened by double clicking gaps within sequence feature extent</li>
+          </ul>
+          <em>Application</em>
+          <ul>
+            <li>
+              <!-- JAL-2447 -->
+              Experimental Features Checkbox in Desktop's Tools
+              menu to hide or show untested features in the application.
+            </li>
+            <li><!-- JAL-1476 -->Warning in alignment status bar when there are not enough columns to superimpose structures in Chimera</li>
+          <li><!-- JAL-1596 -->Faster Chimera/Jalview communication by file-based command exchange</li>  
+          <li><!-- JAL-2316, -->URLs for viewing database cross-references provided by identifiers.org and the EMBL-EBI's MIRIAM DB</li>
+          <li><!-- JAL-2549 -->Updated JABAWS client to v2.2</li>
+          </ul>
+          <em>Experimental features</em>
+          <ul>
+            <li>
+              <!-- JAL-2295, JAL-2296 -->New entries in the Chimera menu
+              to transfer Chimera's structure attributes as Jalview
+              features, and vice-versa.
+            </li>
+          </ul>
+          <em>Applet</em>
+          <ul>
+          <li><!--  --></li>
+          </ul>
+          <em>Test Suite</em>
+          <li><!--  JAL-2474 -->Added PrivilegedAccessor to test suite</li>
+          <li><!-- JAL-2326 -->Prevent or clear modal dialogs raised during tests</li>
+          <li><!--  -->  
+          </ul>
+          </div></td><td><div align="left">
+          <em>General</em>
+          <ul>
+            <li>
+              <!-- JAL-2398, -->Fixed incorrect value in BLOSUM 62 score
+              matrix - C->R should be '3'<br />Old matrix restored with
+              this one-line groovy script:<br />jalview.analysis.scoremodels.ScoreModels.instance.BLOSUM62.@matrix[4][1]=3
+            </li>
+            <li>
+              <!-- JAL-2397 -->Fixed Jalview's treatment of gaps in PCA
+              and substitution matrix based Tree calculations.<br />In
+              earlier versions of Jalview, gaps matching gaps were
+              penalised, and gaps matching non-gaps penalised even more.
+              In the PCA calculation, gaps were actually treated as
+              non-gaps - so different costs were applied, which mean't
+              Jalview's PCAs were different to those produced by
+              SeqSpace.<br />Jalview now treats gaps in the same way as
+              SeqSpace (ie it scores them as 0). To restore pre-2.10.2
+              behaviour<br />
+              jalview.viewmodel.PCAModel.scoreGapAsAny=true // for
+              2.10.1 mode<br />
+              jalview.viewmodel.PCAModel.scoreGapAsAny=false // to
+              restore 2.10.2 mode
+            </li>
+            <li><!-- JAL-2346 -->Reopening Colour by annotation dialog doesn't reselect a specific sequence's associated annotation after it was used for colouring a view</li>
+          <li><!-- JAL-2430 -->Hidden regions in alignment views are not coloured in linked structure views</li>
+          <li><!-- JAL-2419 -->Current selection lost if popup menu opened on a region of alignment without groups</li>
+          <li><!-- JAL-2374 -->Popup menu not always shown for regions of an alignment with overlapping groups</li> 
+          <li><!-- JAL-2310 -->Finder double counts if both a sequence's name and description match</li>
+          <li><!-- JAL-2370 -->Hiding column selection containing two hidden regions results in incorrect hidden regions</li>
+          <li><!-- JAL-2377 -->PCA calculation could hang when generating output report when working with highly redundant alignments</li>
+          <li><!-- JAL-2365 -->Cannot configure feature colours with lightGray or darkGray via features file</li>
+          <li><!-- JAL-2421 -->Overview window visible region moves erratically when hidden rows or columns are present</li>
+          <li><!-- JAL-2362 -->Per-residue colourschemes applied via the Structure Viewer's colour menu don't correspond to sequence colouring</li>  
+          <li><!-- JAL-2405 -->Protein specific colours only offered in colour and group colour menu for protein alignments</li>
+          <li><!-- JAL-2386 -->'Apply to all groups' setting when changing colour does not apply Conservation slider value to all groups</li>
+          <li><!-- JAL-2385 -->Colour threshold slider doesn't update to reflect currently selected view or group's shading thresholds</li>
+          <li><!-- JAL-2373 -->Percentage identity and conservation menu items do not show a tick or allow shading to be disabled</li>
+          <li><!-- JAL-2385 -->Conservation shading or PID threshold lost when base colourscheme changed if slider not visible</li>
+          <li><!-- JAL-2547 -->Sequence features shown in tooltip for gaps before start of features</li>
+          <li><!-- JAL-2576 -->Very large alignments take a long time to load</li>
+          <li><!-- JAL-2590 -->Cannot load Newick trees from eggnog ortholog database</li>
+          </ul>
+          <em>Application</em>
+          <ul>
+          <li><!-- JAL-2401 -->Easier creation of colours for all 'Lower case' residues (button in colourscheme editor debugged and new documentation and tooltips added)</li> 
+          <li><!-- JAL-2399-->Text colour threshold's 'Cancel' button doesn't restore group-specific text colour thresholds</li>
+          <li><!-- JAL-2243 -->Feature settings panel does not update as new features are added to alignment</li> 
+          <li><!-- JAL-2436 -->Structure viewer's View -> Colour By view selection menu changes colours of alignment views</li>
+          <li><!--  JAL-2366 -->Proxy server address and port always appear enabled in Preferences->Connections</li>
+          <li><!-- JAL-2426 -->Spurious exceptions in console raised from alignment calculation workers after alignment has been closed</li>
+          <li><!-- JAL-1608 -->Typo in selection popup menu - Create groups now 'Create Group'</li>
+          <li><!-- JAL-1608 -->CMD/CTRL and G or Shift G for Create/Undefine group doesn't always work</li>
+          <li><!-- JAL-2464 -->Tree Viewer's Print Dialog doesn't get shown again after pressing 'Cancel'</li>
+          <li><!--  JAL-2461 -->DAS registry not found exceptions removed from console output</li>
+          <li><!--  JAL-2383 -->Above PID colour threshold not recovered when alignment view imported from project</li> 
+          <li><!-- JAL-2465 -->No mappings generated between structure and sequences extracted from structure files imported via URL</li>
+            <li>
+              <!-- JAL-2520 -->Structures loaded via URL are saved in
+              Jalview Projects rather than fetched via URL again when
+              the project is loaded and the structure viewed
+            </li>
+            <li><!-- JAL-1256 -->Trackpad horizontal scroll gesture adjusts start position in wrap mode</li>
+            <li><!-- JAL-2563 -->Status bar doesn't show positions for ambiguous amino acids</li>
+            <li><!-- JAL-2291 -->Hide insertions in PopUp menu excludes gaps in selection, current sequence and only within selected columns</li> 
+          </ul>
+          <em>Applet</em>
+          <ul>
+          <li><!-- JAL-2442 -->Features not rendered as transparent on overview or linked structure view</li> 
+          <li><!--  JAL-2372 -->Colour group by conservation doesn't work (since 2.8)</li>
+          <li><!-- JAL-2517 -->Hitting Cancel after applying user-defined colourscheme doesn't restore original colourscheme</li>
+          </ul>
+          <em>New Known Issues</em>
+          <ul>
+          <li><!--  JAL-2566 -->Protein/CDS view scrolling not always in phase after a sequence motif find operation</li>
+          <li><!-- JAL-2550 -->Importing annotation file with rows containing just upper and lower case letters are interpreted as WUSS rna secondary structure symbols</li>  
+          </ul>
+          
+          </div>
+    <tr>
+      <td width="60" nowrap>
+        <div align="center">
           <strong><a name="Jalview.2.10.1">2.10.1</a><br />
             <em>29/11/2016</em></strong>
         </div>
               <!--JAL-2332 -->Attempting to view structure for Hen
               lysozyme results in a PDB Client error dialog box
             </li>
+            <li>
+            <!-- JAL-2319 -->Structure View's mapping report switched ranges for PDB and sequence for SIFTS</li> 
+            <!-- JAL-2319 -->SIFTS 'Not_Observed' residues mapped to non-existant coordindate data</li> 
           </ul>
 <!--           <em>New Known Issues</em>
           <ul>
index f74e4c4..84b2b86 100644 (file)
     <strong><em>A</em></strong>nalysis <strong><em>W</em></strong>eb <strong><em>S</em></strong>ervices
     <strong>system</strong> (<strong>JABAWS</strong>)<br> Jalview
     includes a client for interacting with programmatic (SOAP) web
-    services for the <a href="http://www.compbio.dundee.ac.uk/jabaws">JABAWS</a>
-    service model, developed at the University of Dundee by Peter
-    Troshin and Geoff Barton. This is an open source system that
-    provides a framework for wrapping command line bioinformatics
+    services provided by the <a href="http://www.compbio.dundee.ac.uk/jabaws">JABAWS</a>
+    system, developed at the University of Dundee by Peter
+    Troshin, Sasha Sherstnev, Dan Barton, Fabio Madeira-Marquez, Jim Procter and Geoff Barton.
+    This is an open source system that provides a framework for wrapping command line bioinformatics
     analysis programs that enables them to be executed locally or on a
     cluster using data and analysis parameters provided by a program
     linked with the JABA engine directly or accessing it remotely <em>via</em>
     each new server.
   </p>
   <p>
-    <em>Support for accessing JABAWS servers was introduced in
-      Jalview 2.6.</em>
+    <em>JABAWS Client updated to version 2.2 in Jalview 2.10.2</em>
   </p>
   <p>
     <em>Option for adding JABAWS servers which fails validation was
       introduced from version 2.8.2 </em>
   </p>
+  <p>
+    <em>Support for accessing JABAWS servers was introduced in
+      Jalview 2.6.</em>
+  </p>
 </body>
 </html>
index 077ace6..e42a4e6 100755 (executable)
     <li>Lupas_21, Lupas_14, Lupas_28<br> <em>Coiled-coil
         predictions for the sequence. These are binary predictions for
         each location.</em></li>
-    <li>JNETSOL25,JNETSOL5,JNETSOL0<br> <em>Solvent
-        accessibility predictions - binary predictions of 25%, 5% or 0%
-        solvent accessibility.</em></li>
+    <li>Jnet Burial<br> <em>Prediction of Solvent
+        Accessibility. levels are
+        <ul>
+          <li>0 - Exposed</li>
+          <li>3 - 25% or more S.A. accessible</li>
+          <li>6 - 5% or more S.A. accessible</li>
+          <li>9 - Buried (<5% exposed)</li>
+        </ul></li>
     <li>JNetPRED<br> <em>The consensus prediction -
         helices are marked as red tubes, and sheets as dark green
         arrows.</em></li>
     <li>JNetHMM<br> <em>HMM profile based prediction -
         helices are marked as red tubes, and sheets as dark green
         arrows.</em></li>
-    <li>jpred<br> <em>Jpred prediction - helices are
-        marked as red tubes, and sheets as dark green arrows.</em></li>
     <li>JNETPSSM<br> <em>PSSM based prediction - helices
         are marked as red tubes, and sheets as dark green arrows.</em></li>
-    <li>JNETFREQ<br> <em>Amino Acid frequency based
-        prediction - helices are marked as red tubes, and sheets as dark
-        green arrows.</em></li>
     <li>JNETJURY<br> <em>A '*' in this annotation
         indicates that the JNETJURY was invoked to rationalise
         significantly different primary predictions.</em></li>
     href="../features/annotation.html#seqannots">Add reference
       annotation</a> Sequence ID popup menu option.
   </em>
-  <em>As of Jalview 2.6, the JPred service accessed accessed via the
-    'Secondary structure prediction' submenu should be considered a
+  <em>As of Jalview 2.6, the JPred service accessed accessed via
+    the 'Secondary structure prediction' submenu should be considered a
     legacy Jalview SOAP service, and will be replaced in the near future
     by a JPred4 Rest service.</em>
 
old mode 100755 (executable)
new mode 100644 (file)
index 3f827d9..006c50d
Binary files a/help/html/webServices/jnetprediction.gif and b/help/html/webServices/jnetprediction.gif differ
index 2fbbdbc..d627c66 100644 (file)
   </p>
   <p>
     <strong>Alignment programs supported by JABAWS</strong>. <br />Versions
-    shown are those bundled with JABAWS 2.01 - if you are using a
+    shown are those bundled with JABAWS 2.2 - if you are using a
     different server, check its home page to find out which versions are
     provided.
-  <ul>
-    <li><a href="http://www.clustal.org/">Clustal Omega and
-        Clustal W</a> (version 2.0.12)</li>
-    <li><a href="http://mafft.cbrc.jp/alignment/software/">Mafft</a>
-      (version 6.8.57b)</li>
-    <li><a href="http://www.drive5.com/muscle">Muscle</a> (version
-      3.8.31)</li>
-    <li><a
-      href="http://www.tcoffee.org/Projects_home_page/t_coffee_home_page.html">Tcoffee</a>
-      (version 8.99)</li>
-    <li><a href="http://probcons.stanford.edu/">Probcons</a>
-      (version 1.12)</li>
+    <ul>
+      <li><a href="http://www.clustal.org/omega">Clustal Omega</a> (version 1.2.4)</li>
+      <li><a href="http://www.clustal.org/clustal2">ClustalW</a> (version 2.1)</li>
+      <li><a href="http://align.bmr.kyushu-u.ac.jp/mafft/software/">Mafft</a> (version 7.310)</li>
+      <li><a href="http://www.drive5.com/muscle">Muscle</a> (version 3.8.31)</li>
+      <li><a href="http://www.tcoffee.org/Projects_home_page/t_coffee_home_page.html">T-coffee</a> (version 11.00.8cbe486)</li>
+      <li><a href="http://probcons.stanford.edu/">Probcons</a> (version 1.12)</li>
+      <li><a href="http://msaprobs.sourceforge.net/">MSAProbs</a> (version 0.9.7)</li>
+      <li><a href="http://sourceforge.net/projects/glprobs/">GLProbs</a> (version 0.9.7)</li>
   </ul>
   </p>
 
index 1c35bf3..a06b3a9 100644 (file)
@@ -48,7 +48,7 @@
       By Annotation</a> dialog box to colour sequences according to the
     results of predictors shown as annotation rows.
   </p>
-  <p>JABAWS 2.0 provides four disorder predictors which are
+  <p>JABAWS 2.2 provides four disorder predictors which are
     described below:</p>
   <ul>
     <li><a href="#disembl">DisEMBL</a></li>
   </p>
   <p>
     <strong><a name="iupred"></a><a
-      href="http://iupred.enzim.hu/Help.php">IUPred</a></strong><br />
+      href="http://iupred.enzim.hu/">IUPred</a></strong><br />
     IUPred employs an empirical model to estimate likely regions of
     disorder. There are three different prediction types offered, each
     using different parameters optimized for slightly different
index da5d7dd..56469e5 100644 (file)
     <em>Adding additional links</em><br /> You can configure your own
     links via the Jalview <a href="../features/preferences.html#links"><strong>Preferences</strong></a>
     dialog. Jalview also provides persistent URLs for many common
-    bioinformatics databases. These links are downloaded by Jalview from
+    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.
   </p>
   <p>
-    <em>Creating your own URL link</em> URL links are specified as a
+    <em>Creating your own URL link</em> <br/>URL links are specified as a
     template containing special tokens that Jalview will replace with
     the Sequence ID or Database Accession of the sequence when you
     double click on its ID or open it's <strong>Link</strong> submenu.
     Swissprot = http://www.expasy.org/uniprot/$SEQUENCE_ID$ <br> </pre>
   <p>
     Links will also be made for any database cross references associated
-    with the sequence where the database name exactly matches a URL link
-    name. In this case, the $DB_ACCESSION$ string will be replaced with
+    with the sequence for any link templates whose name begins with the database name.
+    In this case, the $DB_ACCESSION$ string will be replaced with
     the accession string for the database cross-reference, rather than
     the sequence ID for the sequence (<em>since Jalview 2.10.1</em>).
+    <br /> <em>For example: to create a link for viewing MACiE records
+      from PDB Entries, create a new custom link entry with the name
+      "PDB in MACiE", and link URL template:
+      <pre>https://www.ebi.ac.uk/thornton-srv/databases/cgi-bin/MACiE/index.pl?query_pdb=1&amp;pdb=$DBACCESSION$</pre>
+      <br />The sequence ID popup menu for seuqences with a PDB entry
+      will now show 'PDB in MACiE|1xyz..' links in the <strong>links</strong>
+      submenu.
+    </em>
   </p>
   <p>
     <strong><a name="warning">Warning dialog about updating
index d1d141d..4b82179 100755 (executable)
 </head>
 <body>
   <p>
-    <strong>What's new in Jalview 2.10.1 ?</strong>
+    <strong>What's new in Jalview 2.10.2 ?</strong>
   </p>
   <p>
-    Jalview 2.10.1 was released on 29th November 2016. Full details are
-    in the <a href="releases.html#Jalview.2.10.1">Jalview 2.10.1
-      Release Notes</a>, but the highlights are below. This is also the
-    first release to include contributions from Kira Mour&atilde;o, who
-    joined Jalview's core development team in October 2016.
+    Full details about Jalview 2.10.2 are in the <a
+      href="releases.html#Jalview.2.10.2"> Release Notes</a>, but the
+    highlights are below.
   </p>
   <ul>
-    <li><strong>More memory efficient</strong><br />We've slimmed
-      down the consensus analysis data structures used by Jalview so
-      even wider alignments can be worked with.</li>
-    <li><strong>Select highlighted region</strong><br />Press 'B'
-      or use the new menu option in the alignment window's Select menu
-      to mark columns containing highlighted regions generated from
-      structure selections, mouse-overs, or resulting from a Find
-      operation.</li>
-    <li><strong>New custom link mechanism for opening URLs
-        for database cross references.</strong><br /> If you have customised URL
-      links in your Jalview preferences, then you may already have seen
-      the <a href="#warning"> warning dialog (see below).</a></li>
-    <li><strong>New command line export option for BioJS
-        MSAviewer</strong><br />A number of small bugs with the HTML export
-      functions from the Jalview desktop were also fixed.</li>
-    <li><strong>Small but significant changes to the
-        physicochemical properties and consensus calculations</strong><br />Threonine
-      is no longer considered a non-hydrophobic residue in the protein
-      conservation calculation, and minor bugs addressed in PID and
-      consensus colouring.</li>
-    <li><strong>Correct display of disulphide bond
-        features</strong><br /> In linked structure views, Jalview would
-      highlight all residues between in addition to the two linked
-      cysteines. The 'select columns by feature' function in the feature
-      settings would also select all intermediate columns.
+    <li><strong>Update to JABAWS 2.2</strong><br />Jalview's
+      alignment, protein conservation analysis, and protein disorder and
+      RNA secondary structure prediction services are now provided by <a
+      href="http://www.compbio.dundee.ac.uk/jabaws">JABAWS 2.2</a>.
+      Several of the programs provided as services have been updated, so
+      their options and parameters have changed.</li>
+    <li>New preferences for <a href="webServices/urllinks.html">opening
+        web pages for database cross-references</a> via the UK Elixir's
+      EMBL-EBI's MIRIAM database and identifiers.org services.
+    </li>
+    <li><em>Showing and hiding regions</em>
+      <ul>
+        <li><a href="menus/popupMenu.html#hideinserts">Hide
+            insertions</a> in the PopUp menu has changed its behaviour.
+          Prior to 2.10.2, columns were only shown or hidden according
+          to gaps in the sequence under the popup menu. Now, only
+          columns that are gapped in all selected sequences as well as
+          the sequence under the popup menu are hidden, and column
+          visibility outside the selected region is left as is. This
+          makes it easy to filter insertions from the alignment view
+          (just select the region containing insertions to remove)
+          without affecting the rest of the hidden columns.</li>
+      </ul></li>
   </ul>
-
   <p>
-    <strong><a name="warning">Warning dialog about updating
-        your configured URL links</a></strong><br /> In the desktop prior to Jalview
-    2.10.1, the only way to configure custom links for a particular
-    database cross-reference for a sequence was to give it a name that <em>exactly</em>
-    matched the database source, and a regular expression for filtering
-    out any spurious matches generated when the custom linked was tested
-    against the Sequence's ID string. Since the introduction of the
-    $DB_ACCESSION$ token, however, $SEQUENCE_ID$ will not be used for
-    database cross-reference accession strings, and if you have custom
-    links configured, Jalview will raise a warning message so let you
-    know that you may need to update your links to use $DB_ACCESSION$.
+    <strong><a name="experimental">Experimental Features</a></strong>
   </p>
+  <p>
+    This release of Jalview includes a new option in the Jalview Desktop
+    that allows you to try out features that are still in development.
+    To access the features described below, please first enable the <strong>Tools&#8594;Enable
+      Experimental Features</strong> option, and then restart Jalview.
+  </p>
+  <ul>
+    <li><em>Annotation transfer between Chimera and Jalview</em><br />Two
+      <a href="features/chimera.html#experimental">new entries in
+        the Chimera viewer's Chimera menu</a> allow positional annotation to
+      be exchanged between Chimera and Jalview.</li>
+  </ul>
 </body>
 </html>
similarity index 73%
rename from lib/min-jabaws-client-2.1.0.jar
rename to lib/jabaws-min-client-2.2.0.jar
index ea5a1f4..bf1e8b1 100644 (file)
Binary files a/lib/min-jabaws-client-2.1.0.jar and b/lib/jabaws-min-client-2.2.0.jar differ
index f720f39..ee08b56 100644 (file)
@@ -3,6 +3,9 @@ action.reset_services = Reset Services
 action.merge_results = Merge Results
 action.load_scheme = Load scheme
 action.save_scheme = Save scheme
+label.scheme_changed = Changes to scheme ''{0}'' have not been saved.<br><br>Save changes, or continue without saving to make a new colour scheme.
+label.save_changes = Save Changes
+label.dont_save_changes = Don't Save
 action.save_image = Save Image
 action.paste = Paste
 action.show_html_source = Show HTML Source
@@ -68,7 +71,6 @@ action.show_gaps = Show Gaps
 action.show_hidden_markers = Show Hidden Markers
 action.find = Find
 action.undefine_groups = Undefine Groups
-action.create_groups = Create Groups
 action.make_groups_selection = Make Groups For Selection
 action.copy = Copy
 action.cut = Cut
@@ -78,7 +80,8 @@ action.scale_left = Scale Left
 action.scale_right = Scale Right
 action.by_tree_order = By Tree Order
 action.sort = Sort
-action.calculate_tree = Calculate Tree
+action.calculate_tree = Calculate Tree...
+action.calculate_tree_pca = Calculate Tree or PCA...
 action.help = Help
 action.by_annotation = By Annotation...
 action.invert_sequence_selection = Invert Sequence Selection
@@ -168,6 +171,7 @@ label.redo_command = Redo {0}
 label.principal_component_analysis = Principal Component Analysis
 label.average_distance_identity = Average Distance Using % Identity
 label.neighbour_joining_identity = Neighbour Joining Using % Identity
+label.choose_calculation = Choose Calculation
 label.treecalc_title = {0} Using {1}
 label.tree_calc_av = Average Distance
 label.tree_calc_nj = Neighbour Joining
@@ -175,10 +179,13 @@ label.select_score_model = Select score model
 label.score_model_pid = % Identity
 label.score_model_blosum62 = BLOSUM62
 label.score_model_pam250 = PAM 250
+label.score_model_smithwatermanscore = Score between two sequences aligned with Smith-Waterman with default Peptide/Nucleotide matrix
+label.score_model_sequencefeaturesimilarity = Distance measure of average number of features not shared at sequence positions 
 label.score_model_conservation = Physicochemical property conservation
 label.score_model_enhconservation = Physicochemical property conservation
 label.status_bar = Status bar
 label.out_to_textbox = Output to Textbox
+label.occupancy = Occupancy
 # delete Clustal - use FileFormat name instead
 label.clustal = Clustal
 # label.colourScheme_<schemeName> as in JalviewColourScheme
@@ -330,6 +337,7 @@ label.colour_residues_above_occurrence = Colour residues above % occurrence
 label.set_this_label_text = set this label text
 label.sequences_from = Sequences from {0}
 label.successfully_loaded_file  = Successfully loaded file {0}
+label.successfully_loaded_matrix  = Successfully loaded score matrix {0}
 label.successfully_saved_to_file_in_format = Successfully saved to file: {0} in {1} format.
 label.copied_sequences_to_clipboard = Copied {0} sequences to clipboard.
 label.check_file_matches_sequence_ids_alignment = Check that the file matches sequence IDs in the alignment.
@@ -373,14 +381,11 @@ label.remove_from_default_list = Remove from default list?
 label.remove_user_defined_colour = Remove user defined colour
 label.you_must_select_least_two_sequences = You must select at least 2 sequences.
 label.invalid_selection = Invalid Selection
-label.principal_component_analysis_must_take_least_four_input_sequences = Principal component analysis must take\nat least 4 input sequences.
 label.sequence_selection_insufficient = Sequence selection insufficient
-label.you_need_more_two_sequences_selected_build_tree = You need to have more than two sequences selected to build a tree!
+label.you_need_at_least_n_sequences = You need to select at least {0} sequences
 label.not_enough_sequences = Not enough sequences
 label.selected_region_to_tree_may_only_contain_residues_or_gaps =  The selected region to create a tree may\nonly contain residues or gaps.\nTry using the Pad function in the edit menu,\nor one of the multiple sequence alignment web services.
 label.sequences_selection_not_aligned = Sequences in selection are not aligned
-label.sequences_must_be_aligned_before_creating_tree = The sequences must be aligned before creating a tree.\nTry using the Pad function in the edit menu,\n or one of the multiple sequence alignment web services.
-label.sequences_not_aligned = Sequences not aligned
 label.problem_reading_tree_file =  Problem reading tree file
 label.possible_problem_with_tree_file = Possible problem with tree file
 label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation = Please select at least three bases in at least one sequence in order to perform a cDNA translation.
@@ -515,7 +520,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences =
 label.standard_databases = Standard Databases
 label.fetch_embl_uniprot = Fetch from EMBL/EMBLCDS or Uniprot/PDB and any selected DAS sources
 label.reset_min_max_colours_to_defaults = Reset min and max colours to defaults from user preferences.
-label.align_structures_using_linked_alignment_views = Align structures using {0} linked alignment views
+label.align_structures_using_linked_alignment_views = Superpose structures using {0} selected alignment view(s)
 label.connect_to_session = Connect to session {0}
 label.threshold_feature_display_by_score = Threshold the feature display by score.
 label.threshold_feature_no_threshold = No Threshold
@@ -620,6 +625,8 @@ label.web_services = Web Services
 label.right_click_to_edit_currently_selected_parameter = Right click to edit currently selected parameter.
 label.let_jmol_manage_structure_colours = Let Jmol manage structure colours
 label.let_chimera_manage_structure_colours = Let Chimera manage structure colours
+label.fetch_chimera_attributes = Fetch Chimera attributes
+label.fetch_chimera_attributes_tip = Copy Chimera attribute to Jalview feature
 label.marks_leaves_tree_not_associated_with_sequence = Marks leaves of tree not associated with a sequence
 label.index_web_services_menu_by_host_site = Index web services in menu by the host site
 label.option_want_informed_web_service_URL_cannot_be_accessed_jalview_when_starts_up = Check this option if you want to be informed<br>when a web service URL cannot be accessed by Jalview<br>when it starts up
@@ -667,8 +674,7 @@ label.2d_rna_sequence_name = 2D RNA - {0}
 label.edit_name_and_description_current_group = Edit name and description of current group
 label.from_file = From File
 label.enter_pdb_id = Enter PDB Id (or pdbid:chaincode)
-label.text_colour = Text Colour
-action.set_text_colour = Text Colour...
+label.text_colour = Text Colour...
 label.structure = Structure
 label.show_pdbstruct_dialog = 3D Structure Data...
 label.view_rna_structure = VARNA 2D Structure
@@ -708,14 +714,18 @@ label.set_as_default = Set as Default
 label.show_labels = Show labels
 action.background_colour = Background Colour...
 label.associate_nodes_with = Associate Nodes With
-label.jalview_pca_calculation = Jalview PCA Calculation
 label.link_name = Link Name
 label.pdb_file = PDB file
 label.colour_with_jmol = Colour with Jmol
 label.colour_with_chimera = Colour with Chimera
-label.align_structures = Align Structures
+label.superpose_structures = Superpose Structures
+error.superposition_failed = Superposition failed: {0}
+label.insufficient_residues = Not enough aligned residues ({0}) to perform superposition
 label.jmol = Jmol
 label.chimera = Chimera
+label.create_chimera_attributes = Write Jalview features
+label.create_chimera_attributes_tip = Set Chimera residue attributes for visible features
+label.attributes_set = {0} attribute values set on Chimera
 label.sort_alignment_by_tree = Sort Alignment By Tree
 label.mark_unlinked_leaves = Mark Unlinked Leaves
 label.associate_leaves_with = Associate Leaves With
@@ -846,7 +856,6 @@ label.couldnt_save_project = Couldn't save project
 label.error_whilst_saving_current_state_to = Error whilst saving current state to {0}
 label.error_whilst_loading_project_from = Error whilst loading project from {0}
 label.couldnt_load_project = Couldn't load project
-label.pca_sequences_not_aligned = The sequences must be aligned before calculating PCA.\nTry using the Pad function in the edit menu,\nor one of the multiple sequence alignment web services.
 label.invalid_name_preset_exists = Invalid name - preset already exists.
 label.invalid_name = Invalid name
 label.set_proxy_settings = Please set up your proxy settings in the 'Connections' tab of the Preferences window
@@ -890,6 +899,7 @@ label.choose_filename_for_param_file = Choose a filename for this parameter file
 label.save_as_html = Save as HTML
 label.recently_opened = Recently Opened
 label.blasting_for_unidentified_sequence_jobs_running = BLASTing for unidentified sequences - {0}  jobs running.
+label.tree = Tree
 label.tree_from = Tree from {0}
 label.webservice_job_title = {0} using {1}
 label.select_visible_region_of = selected {0} region of {1}
@@ -900,6 +910,7 @@ label.webservice_job_title_on = {0} using {1} on {2}
 label.updating_vamsas_session = Updating vamsas session
 label.loading_file = Loading File: {0}
 label.edit_params = Edit {0}
+label.as_percentage = As Percentage
 error.not_implemented = Not implemented
 error.no_such_method_as_clone1_for = No such method as clone1 for {0}
 error.null_from_clone1 = Null from clone1!
@@ -1214,6 +1225,7 @@ label.configure_displayed_columns = Customise Displayed Options
 label.start_jalview = Start Jalview
 label.biojs_html_export = BioJS
 label.scale_as_cdna = Scale protein residues to codons
+label.font_as_cdna = Use same font for cDNA and peptide
 label.scale_protein_to_cdna = Scale Protein to cDNA
 label.scale_protein_to_cdna_tip = Make protein residues same width as codons in split frame views
 info.select_annotation_row = Select Annotation Row
@@ -1288,4 +1300,16 @@ label.edit_sequence_url_link = Edit sequence URL link
 warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids
 label.invalid_name = Invalid Name !
 label.output_seq_details = Output Sequence Details to list all database references
-label.urllinks = Links
\ No newline at end of file
+label.urllinks = Links
+label.default_cache_size = Default Cache Size
+action.clear_cached_items = Clear Cached Items
+label.togglehidden = Show hidden regions
+label.quality_descr = Alignment Quality based on Blosum62 scores
+label.conservation_descr = Conservation of total alignment less than {0}% gaps
+label.consensus_descr = PID
+label.complement_consensus_descr = PID for cDNA
+label.strucconsensus_descr = PID for base pairs
+label.occupancy_descr = Number of aligned positions 
+label.show_experimental = Enable experimental features
+label.show_experimental_tip = Enable any new and currently 'experimental' features (see Latest Release Notes for details)
+label.warning_hidden = Warning: {0} {1} is currently hidden
index d408fee..cee099d 100644 (file)
@@ -3,6 +3,9 @@ action.reset_services = Reiniciar servicios
 action.merge_results = Unificar resultados
 action.load_scheme = Cargar esquema
 action.save_scheme = Guardar esquema
+label.scheme_changed = Cambios en el esquema ''{0}'' no se han guardado.<br><br>Guardar cambios, o continuar sin guardar para hacer un nuevo esquema.
+label.save_changes = Guardar cambios
+label.dont_save_changes = No guardar
 action.save_image = Guardar imagen
 action.paste = Pegar
 action.show_html_source = Mostrar código HTML
@@ -66,7 +69,6 @@ action.show_gaps = Mostrar huecos
 action.show_hidden_markers = Mostrar marcadores ocultos
 action.find = Buscar
 action.undefine_groups = Grupos sin definir
-action.create_groups = Crear grupos
 action.make_groups_selection = Hacer grupos para seleccionar
 action.copy = Copiar
 action.cut = Cortar
@@ -76,7 +78,8 @@ action.scale_left = Escala izquierda
 action.scale_right = Escala derecha
 action.by_tree_order = Por orden del árbol
 action.sort = Ordenar
-action.calculate_tree = Calcular árbol
+action.calculate_tree = Calcular árbol...
+action.calculate_tree_pca = Calcular árbol o ACP...
 action.help = Ayuda
 action.by_annotation = Por anotación...
 action.invert_sequence_selection = Invertir selección de secuencias
@@ -165,6 +168,7 @@ label.redo_command = Rehacer {0}
 label.principal_component_analysis = Análisis del Componente Principal
 label.average_distance_identity = Distancia Media Usando % de Identidad
 label.neighbour_joining_identity = Unir vecinos utilizando % de Identidad
+label.choose_calculation = Elegir el cálculo
 label.treecalc_title = {0} utilizando {1}
 label.tree_calc_av = Distancia media
 label.tree_calc_nj = Unir vecinos
@@ -172,10 +176,13 @@ label.select_score_model = Selecciones modelo de puntuaci
 label.score_model_pid = % Identidad
 label.score_model_blosum62 = BLOSUM62
 label.score_model_pam250 = PAM 250
+label.score_model_smithwatermanscore = Puntuación entre secuencias alineadas por Smith-Waterman con matriz por defecto proteica / nucleotídica
+label.score_model_sequencefeaturesimilarity = Medida de distancia por cuenta promedia de características no compartidas en posiciones de secuencia
 label.score_model_conservation = Conservación de las propiedades físico-químicas
 label.score_model_enhconservation = Conservación de las propiedades físico-químicas
 label.status_bar = Barra de estado
 label.out_to_textbox = Generar cuadro de texto
+label.occupancy = Ocupación
 label.clustal = Clustal
 # label.colourScheme_<schemeName> as in JalviewColourScheme
 label.colourScheme_clustal = Clustalx
@@ -299,6 +306,7 @@ label.colour_residues_above_occurrence = Residuos de color por encima del % de a
 label.set_this_label_text = fijar como etiqueta 
 label.sequences_from = Secuencias de {0}
 label.successfully_loaded_file  = Fichero cargado exitosamente {0}
+label.successfully_loaded_matrix  = Matriz cargada exitosamente {0}
 label.successfully_saved_to_file_in_format = Guardado exitosamente en el fichero: {0} en formato {1}.
 label.copied_sequences_to_clipboard = Copiadas {0} secuencias en el portapapeles.
 label.check_file_matches_sequence_ids_alignment = Comprobar que el fichero coincide con el ID de la secuencia en el alineamiento.
@@ -341,14 +349,11 @@ label.remove_from_default_list = eliminar de la lista de defectuosos?
 label.remove_user_defined_colour = Eliminar el color definido por el usuario
 label.you_must_select_least_two_sequences = Debes seleccionar al menos 2 secuencias.
 label.invalid_selection = Selección inválida
-label.principal_component_analysis_must_take_least_four_input_sequences = El an\u00E1lisis de la componente principal debe tomar\nal menos 4 secuencias de entrada.
 label.sequence_selection_insufficient = Selección de secuencias insuficiente
-label.you_need_more_two_sequences_selected_build_tree = necesitas seleccionar más de dos secuencias para construir un árbol!
+label.you_need_at_least_n_sequences = Necesitas seleccionar al menos {0} secuencias
 label.not_enough_sequences = No suficientes secuencias
 label.selected_region_to_tree_may_only_contain_residues_or_gaps = La regi\u00F3n seleccionada para construir un \u00E1rbol puede\ncontener s\u00F3lo residuos o espacios.\nPrueba usando la funci\u00F3n Pad en el men\u00FA de edici\u00F3n,\n o uno de los m\u00FAltiples servicios web de alineamiento de secuencias.
 label.sequences_selection_not_aligned = Las secuencias seleccionadas no están alineadas
-label.sequences_must_be_aligned_before_creating_tree = Las secuencias deben estar alineadas antes de crear el \u00E1rbol.\nPrueba usando la funci\u00F3n Pad en el men\u00FA de editar,\n o uno de los m\u00FAltiples servicios web de alineamiento de secuencias.
-label.sequences_not_aligned = Secuencias no alineadas
 label.problem_reading_tree_file =  Problema al leer el fichero del árbol
 label.possible_problem_with_tree_file = Posible problema con el fichero del árbol
 label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation = Por favor seleccionar al menos tres bases de al menos una secuencia para poder realizar la traducción de cDNA.
@@ -411,7 +416,7 @@ label.colour_by_annotation = Color por anotaci
 label.selection_output_command = Seleccionar salida - {0}
 label.annotation_for_displayid = <p><h2>Anotación para {0} </h2></p><p>
 label.pdb_sequence_mapping = PDB - Mapeado de secuencia
-label.pca_details = detalles de la PCA
+label.pca_details = detalles de la ACP
 label.redundancy_threshold_selection = Selección del umbral de redundancia
 label.user_defined_colours = Colores definidos del usuario
 label.jalviewLite_release = JalviewLite - versión {0}
@@ -432,7 +437,7 @@ label.label = Etiqueta
 label.no_features_added_to_this_alignment = No hay funciones asociadas a este alineamiento!!
 label.features_can_be_added_from_searches_1 = (Las funciones pueden ser añadidas de búsquedas o
 label.features_can_be_added_from_searches_2 = de ficheros de funciones Jalview / GFF)
-label.calculating_pca= Calculando PCA
+label.calculating_pca= Calculando ACP
 label.jalview_cannot_open_file = Jalview no puede abrir el fichero
 label.jalview_applet = Aplicación Jalview  
 label.loading_data = Cargando datos
@@ -476,7 +481,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences =
 label.standard_databases = Bases de datos estándar
 label.fetch_embl_uniprot = Recuperar de EMBL/EMBLCDS o Uniprot/PDB y de cualquier fuente DAS seleccionada
 label.reset_min_max_colours_to_defaults = Reiniciar los colores min y max colours a los valores por defecto establecidos en las preferencias de usuario
-label.align_structures_using_linked_alignment_views = Alinear las estructuras utlizando las {0} vistas de alineamiento enlazadas
+label.align_structures_using_linked_alignment_views = Alinear las estructuras utilizando las {0} vista(s) de alineamiento enlazada(s)
 label.connect_to_session = Conectar a la sesión {0}
 label.threshold_feature_display_by_score = Filtrar la característica mostrada por puntuación.
 label.threshold_feature_no_threshold = Sin umbral
@@ -621,7 +626,7 @@ label.2d_rna_sequence_name = 2D RNA - {0}
 label.edit_name_and_description_current_group = Editar el nombre y la descripción del grupo actual
 label.from_file = desde fichero
 label.enter_pdb_id = Introducir PDB Id
-label.text_colour = Color del texto
+label.text_colour = Color de texto...
 label.structure = Estructura
 label.create_sequence_details_report_annotation_for = Anotación para {0}
 label.sequence_details_for = Detalles de la secuencia para {0}
@@ -654,11 +659,9 @@ label.add_local_source = A
 label.set_as_default = Establecer por defecto
 label.show_labels = Mostrar etiquetas
 label.associate_nodes_with = Asociar nodos con
-label.jalview_pca_calculation = Cálculo del PCA por Jalview
 label.link_name = Nombre del enalce
 label.pdb_file = Fichero PDB
 label.colour_with_jmol = Colorear con Jmol
-label.align_structures = Alinear estructuras
 label.jmol = Jmol
 label.sort_alignment_by_tree = Ordenar alineamiento por árbol
 label.mark_unlinked_leaves = Marcar las hojas como no enlazadas
@@ -778,7 +781,6 @@ label.couldnt_save_project = No es posible guardar el proyecto
 label.error_whilst_saving_current_state_to = Error mientras se guardaba el estado a {0}
 label.error_whilst_loading_project_from = Error cargando el proyecto desde  {0}
 label.couldnt_load_project = No es posible cargar el proyecto
-label.pca_sequences_not_aligned = Las secuencias deben estar alineadas antes de calcular el PCA.\nPruebe a utilizar la funci\u00F3n de rellenar huecos en el men\u00FA Editar,\no cualquiera de los servicios web de alineamiento m\u00FAltiple.
 label.invalid_name_preset_exists = Nombre no válido - esta preconfiguración ya existe.
 label.invalid_name = Nombre no válido
 label.set_proxy_settings = Por favor, configure su proxy en la pestaña 'Conexiones' de la ventana de Preferencia
@@ -822,6 +824,7 @@ label.choose_filename_for_param_file = Escoja un nombre de fichero para este fic
 label.save_as_html = Guardar como HTML
 label.recently_opened = Abiertos recientemente
 label.blasting_for_unidentified_sequence_jobs_running = Ejecutando BLAST de las secuencias no indentificadas - {0}  trabajos en marcha.
+label.tree = Árbol
 label.tree_from = Árbol de {0}
 label.webservice_job_title = {0} usando {1}
 label.select_visible_region_of = seleccionada {0} región de {1}
@@ -832,6 +835,7 @@ label.webservice_job_title_on = {0} usando {1} de {2}
 label.updating_vamsas_session = Actualizando sesión VAMSAS
 label.loading_file = Cargando fichero: {0}
 label.edit_params = Editar {0}
+label.as_percentage = Como Porcentaje
 error.not_implemented = No implementado
 error.no_such_method_as_clone1_for = No existe ese método como un clone1 de {0}
 error.null_from_clone1 = Nulo de clone1!
@@ -938,8 +942,8 @@ label.submission_params = Env
 label.empty_alignment_job = Trabajo de alineamiento vacío
 label.add_new_sbrs_service = Añadir un nuevo SBRS
 label.edit_sbrs_entry = Editar entrada SBRS
-label.pca_recalculating = Recalculando PCA
-label.pca_calculating = Calculando PCA
+label.pca_recalculating = Recalculando ACP
+label.pca_calculating = Calculando ACP
 label.select_foreground_colour = Escoger color del primer plano
 label.select_colour_for_text = Seleccione el color del texto
 label.adjunst_foreground_text_colour_threshold = Ajustar el umbral del color del texto en primer plano
@@ -1141,10 +1145,14 @@ action.annotations=Anotaciones
 label.nuc_alignment_colour=Color del Alineamiento Nucleotídico
 label.copy_format_from=Copiar formato de
 label.chimera=Chimera
+label.create_chimera_attributes = Escribir características de Jalview
+label.create_chimera_attributes_tip = Establecer atributos en Chimera para características visibles 
+label.attributes_set = {0} valores de atributos establecidos en Chimera
 label.open_split_window=Abrir ventana dividida
 label.open_split_window?=¿Quieres abrir ventana dividida, con cDNA y proteína vinculadas?
 status.searching_for_pdb_structures=Buscando Estructuras PDB
 label.scale_as_cdna=Adaptar residuos proteicos a los codones
+label.font_as_cdna=Utilizar la misma fuente para nucleotídos y proteicos
 action.export_hidden_sequences=Exportar Secuencias Ocultas
 action.export_hidden_columns=Exportar Columnas Ocultas
 label.found_structures_summary=Resumen de Estructuras Encontradas
@@ -1158,12 +1166,12 @@ label.invalid_search=Texto de b
 action.export_annotations=Exportar Anotaciones
 action.set_as_reference=Marcar como Referencia
 action.unmark_as_reference=Desmarcar como Referencia
-action.set_text_colour=Color de Texto...
 label.chimera_failed=Error al abrir Chimera - está instalado?\nCompruebe ruta en Preferencias, Estructura
 label.find=Buscar
 label.select_pdb_file=Seleccionar Fichero PDB
 label.structures_filter=Filtro de Estructuras
 label.scale_protein_to_cdna=Adaptar proteína a cDNA
+label.scale_protein_to_cdna_tip=Hacer a los residuos de proteínas de la misma anchura que los codones en ventanas divididas
 status.loading_cached_pdb_entries=Cargando Entradas PDB en Caché
 label.select=Seleccionar :
 label.select_by_annotation=Seleccionar/Ocultar Columnas por Anotación
@@ -1180,9 +1188,13 @@ label.hide_insertions=Ocultar Inserciones
 info.change_threshold_mode_to_enable=Cambiar Modo de Umbral para Habilitar
 label.separate_multiple_query_values=Introducir uno o mas {0}s separados por punto y coma ";"
 label.let_chimera_manage_structure_colours=Deja que Chimera maneje colores de estructuras
+label.fetch_chimera_attributes = Buscar atributos desde Chimera
+label.fetch_chimera_attributes_tip = Copiar atributo de Chimera a característica de Jalview
 label.view_rna_structure=Estructura 2D VARNA
-label.scale_protein_to_cdna_tip=Hacer a los residuos de proteínas de la misma anchura que los codones en ventanas divididas
 label.colour_with_chimera=Colorear con Chimera
+label.superpose_structures = Superponer estructuras
+error.superposition_failed = Superposición fallido: {0}
+label.insufficient_residues = Residuos alineados ({0}) insuficentes para superponer
 label.show_pdbstruct_dialog=Datos de Estructura 3D...
 label.hide_all=Ocultar todos
 label.invert=Invertir
@@ -1219,7 +1231,7 @@ label.select_all=Seleccionar Todos
 label.alpha_helix=Hélice Alfa
 label.chimera_help=Ayuda para Chimera
 label.find_tip=Buscar alineamiento, selección o IDs de secuencia para una subsecuencia (sin huecos)
-label.structure_viewer=Visualizador de estructura for defecto
+label.structure_viewer=Visualizador de estructura por defecto
 label.embbed_biojson=Incrustar BioJSON al exportar HTML
 label.transparency_tip=Ajustar la transparencia a "ver a través" los colores de las características.
 label.choose_annotations=Escoja anotaciones
@@ -1288,4 +1300,12 @@ label.edit_sequence_url_link = Editar link de secuencia URL
 warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben ser únicos y no pueden ser ids de MIRIAM
 label.invalid_name = Nombre inválido !
 label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
-label.urllinks = Enlaces
\ No newline at end of file
+label.urllinks = Enlaces
+label.quality_descr = Calidad de alineamiento basándose en puntuación Blosum62
+label.conservation_descr = Conservación del alineamiento total menos de {0}% huecos
+label.consensus_descr = % Identidad
+label.complement_consensus_descr = % Identidad para cDNA
+label.strucconsensus_descr = % Identidad para pares de bases
+label.occupancy_descr = Número de posiciones alineadas
+label.togglehidden = Show hidden regions
+label.warning_hidden = Advertencia: {0} {1} está actualmente oculto
diff --git a/resources/scoreModel/blosum62.scm b/resources/scoreModel/blosum62.scm
new file mode 100644 (file)
index 0000000..b0e927d
--- /dev/null
@@ -0,0 +1,34 @@
+ScoreMatrix BLOSUM62
+#
+# The BLOSUM62 substitution matrix, as at https://www.ncbi.nlm.nih.gov/Class/FieldGuide/BLOSUM62.txt
+# The first line declares a ScoreMatrix with the name BLOSUM62 (shown in menus)
+#
+# Scores are not symbol case sensitive, unless column(s) are provided for lower case characters
+# The 'guide symbol' at the start of each row of score values is optional
+# Values may be integer or floating point, delimited by tab, space, comma or combinations
+#
+        A       R       N       D       C       Q       E       G       H       I       L       K       M       F       P       S       T       W       Y       V       B       Z       X       * 
+A       4      -1      -2      -2       0      -1      -1       0      -2      -1      -1      -1      -1      -2      -1       1       0      -3      -2       0      -2      -1       0      -4
+R      -1       5       0      -2      -3       1       0      -2       0      -3      -2       2      -1      -3      -2      -1      -1      -3      -2      -3      -1       0      -1      -4
+N      -2       0       6       1      -3       0       0       0       1      -3      -3       0      -2      -3      -2       1       0      -4      -2      -3       3       0      -1      -4
+D      -2      -2       1       6      -3       0       2      -1      -1      -3      -4      -1      -3      -3      -1       0      -1      -4      -3      -3       4       1      -1      -4
+C       0      -3      -3      -3       9      -3      -4      -3      -3      -1      -1      -3      -1      -2      -3      -1      -1      -2      -2      -1      -3      -3      -2      -4
+Q      -1       1       0       0      -3       5       2      -2       0      -3      -2       1       0      -3      -1       0      -1      -2      -1      -2       0       3      -1      -4
+E      -1       0       0       2      -4       2       5      -2       0      -3      -3       1      -2      -3      -1       0      -1      -3      -2      -2       1       4      -1      -4
+G       0      -2       0      -1      -3      -2      -2       6      -2      -4      -4      -2      -3      -3      -2       0      -2      -2      -3      -3      -1      -2      -1      -4
+H      -2       0       1      -1      -3       0       0      -2       8      -3      -3      -1      -2      -1      -2      -1      -2      -2       2      -3       0       0      -1      -4
+I      -1      -3      -3      -3      -1      -3      -3      -4      -3       4       2      -3       1       0      -3      -2      -1      -3      -1       3      -3      -3      -1      -4
+L      -1      -2      -3      -4      -1      -2      -3      -4      -3       2       4      -2       2       0      -3      -2      -1      -2      -1       1      -4      -3      -1      -4
+K      -1       2       0      -1      -3       1       1      -2      -1      -3      -2       5      -1      -3      -1       0      -1      -3      -2      -2       0       1      -1      -4
+M      -1      -1      -2      -3      -1       0      -2      -3      -2       1       2      -1       5       0      -2      -1      -1      -1      -1       1      -3      -1      -1      -4
+F      -2      -3      -3      -3      -2      -3      -3      -3      -1       0       0      -3       0       6      -4      -2      -2       1       3      -1      -3      -3      -1      -4
+P      -1      -2      -2      -1      -3      -1      -1      -2      -2      -3      -3      -1      -2      -4       7      -1      -1      -4      -3      -2      -2      -1      -2      -4
+S       1      -1       1       0      -1       0       0       0      -1      -2      -2       0      -1      -2      -1       4       1      -3      -2      -2       0       0       0      -4
+T       0      -1       0      -1      -1      -1      -1      -2      -2      -1      -1      -1      -1      -2      -1       1       5      -2      -2       0      -1      -1       0      -4
+W      -3      -3      -4      -4      -2      -2      -3      -2      -2      -3      -2      -3      -1       1      -4      -3      -2      11       2      -3      -4      -3      -2      -4
+Y      -2      -2      -2      -3      -2      -1      -2      -3       2      -1      -1      -2      -1       3      -3      -2      -2       2       7      -1      -3      -2      -1      -4
+V       0      -3      -3      -3      -1      -2      -2      -3      -3       3       1      -2       1      -1      -2      -2       0      -3      -1       4      -3      -2      -1      -4
+B      -2      -1       3       4      -3       0       1      -1       0      -3      -4       0      -3      -3      -2       0      -1      -4      -3      -3       4       1      -1      -4
+Z      -1       0       0       1      -3       3       4      -2       0      -3      -3       1      -1      -3      -1       0      -1      -3      -2      -2       1       4      -1      -4
+X       0      -1      -1      -1      -2      -1      -1      -1      -1      -1      -1      -1      -1      -1      -2       0       0      -2      -1      -1      -1      -1      -1      -4
+*      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4      -4       1 
diff --git a/resources/scoreModel/blosum80.scm b/resources/scoreModel/blosum80.scm
new file mode 100644 (file)
index 0000000..8153d3b
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# Source: http://www.genome.jp/dbget-bin/www_bget?aaindex:HENS920103
+#
+H HENS920103
+D BLOSUM80 substitution matrix (Henikoff-Henikoff, 1992)
+R PMID:1438297
+A Henikoff, S. and Henikoff, J.G.
+T Amino acid substitution matrices from protein blocks
+J Proc. Natl. Acad. Sci. USA 89, 10915-10919 (1992)
+* matrix in 1/3 Bit Units
+M rows = ARNDCQEGHILKMFPSTWYV, cols = ARNDCQEGHILKMFPSTWYV
+      7.
+     -3.      9.
+     -3.     -1.      9.
+     -3.     -3.      2.     10.
+     -1.     -6.     -5.     -7.     13.
+     -2.      1.      0.     -1.     -5.      9.
+     -2.     -1.     -1.      2.     -7.      3.      8.
+      0.     -4.     -1.     -3.     -6.     -4.     -4.      9.
+     -3.      0.      1.     -2.     -7.      1.      0.     -4.     12.
+     -3.     -5.     -6.     -7.     -2.     -5.     -6.     -7.     -6.      7.
+     -3.     -4.     -6.     -7.     -3.     -4.     -6.     -7.     -5.      2.      6.
+     -1.      3.      0.     -2.     -6.      2.      1.     -3.     -1.     -5.     -4.      8.
+     -2.     -3.     -4.     -6.     -3.     -1.     -4.     -5.     -4.      2.      3.     -3.      9.
+     -4.     -5.     -6.     -6.     -4.     -5.     -6.     -6.     -2.     -1.      0.     -5.      0.     10.
+     -1.     -3.     -4.     -3.     -6.     -3.     -2.     -5.     -4.     -5.     -5.     -2.     -4.     -6.     12.
+      2.     -2.      1.     -1.     -2.     -1.     -1.     -1.     -2.     -4.     -4.     -1.     -3.     -4.     -2.      7.
+      0.     -2.      0.     -2.     -2.     -1.     -2.     -3.     -3.     -2.     -3.     -1.     -1.     -4.     -3.      2.      8.
+     -5.     -5.     -7.     -8.     -5.     -4.     -6.     -6.     -4.     -5.     -4.     -6.     -3.      0.     -7.     -6.     -5.     16.
+     -4.     -4.     -4.     -6.     -5.     -3.     -5.     -6.      3.     -3.     -2.     -4.     -3.      4.     -6.     -3.     -3.      3.     11.
+     -1.     -4.     -5.     -6.     -2.     -4.     -4.     -6.     -5.      4.      1.     -4.      1.     -2.     -4.     -3.      0.     -5.     -3.      7.
+//
diff --git a/resources/scoreModel/dna.scm b/resources/scoreModel/dna.scm
new file mode 100644 (file)
index 0000000..0d7cbc1
--- /dev/null
@@ -0,0 +1,27 @@
+ScoreMatrix DNA
+#
+# A DNA substitution matrix.
+# This is an ad-hoc matrix which, in addition to penalising mutations between the common 
+# nucleotides (ACGT), includes T/U equivalence in order to allow both DNA and/or RNA. 
+# In addition, it encodes weak equivalence between R and Y with AG and CTU, respectively, 
+# and N is allowed to match any other base weakly. 
+# This matrix also includes I (Inosine) and X (Xanthine), but encodes them to weakly match
+# any of (ACGTU), and unfavourably match each other.
+#
+# The first line declares a ScoreMatrix with the name DNA (shown in menus)
+# Scores are not case sensitive, unless column(s) are provided for lower case characters
+#
+# Values may be integer or floating point, delimited by tab, space, comma or combinations
+#
+        A       C       G       T       U       I       X       R       Y       N      -
+A      10      -8      -8      -8      -8       1       1       1      -8       1      1 
+C      -8      10      -8      -8      -8       1       1      -8       1       1      1
+G      -8      -8      10      -8      -8       1       1       1      -8       1      1
+T      -8      -8      -8      10      10       1       1      -8       1       1      1
+U      -8      -8      -8      10      10       1       1      -8       1       1      1
+I       1       1       1       1       1      10       0       0       0       1      1
+X       1       1       1       1       1       0      10       0       0       1      1
+R       1      -8       1      -8      -8       0       0      10      -8       1      1
+Y      -8       1      -8       1       1       0       0      -8      10       1      1
+N       1       1       1       1       1       1       1       1       1      10      1
+-       1       1       1       1       1       1       1       1       1       1      1
diff --git a/resources/scoreModel/pam250.scm b/resources/scoreModel/pam250.scm
new file mode 100644 (file)
index 0000000..898c723
--- /dev/null
@@ -0,0 +1,32 @@
+ScoreMatrix PAM250
+#
+# The PAM250 substitution matrix
+# The first line declares a ScoreMatrix with the name PAM250 (shown in menus)
+# Scores are not case sensitive, unless column(s) are provided for lower case characters
+# Values may be integer or floating point, delimited by tab, space, comma or combinations
+#
+        A       R       N       D       C       Q       E       G       H       I       L       K       M       F       P       S       T       W       Y       V       B       Z       X       *
+A       2      -2       0       0      -2       0       0       1      -1      -1      -2      -1      -1      -3       1       1       1      -6      -3       0       0       0       0      -8
+R      -2       6       0      -1      -4       1      -1      -3       2      -2      -3       3       0      -4       0       0      -1       2      -4      -2      -1       0      -1      -8
+N       0       0       2       2      -4       1       1       0       2      -2      -3       1      -2      -3       0       1       0      -4      -2      -2       2       1       0      -8
+D       0      -1       2       4      -5       2       3       1       1      -2      -4       0      -3      -6      -1       0       0      -7      -4      -2       3       3      -1      -8
+C      -2      -4      -4      -5      12      -5      -5      -3      -3      -2      -6      -5      -5      -4      -3       0      -2      -8       0      -2      -4      -5      -3      -8
+Q       0       1       1       2      -5       4       2      -1       3      -2      -2       1      -1      -5       0      -1      -1      -5      -4      -2       1       3      -1      -8
+E       0      -1       1       3      -5       2       4       0       1      -2      -3       0      -2      -5      -1       0       0      -7      -4      -2       3       3      -1      -8
+G       1      -3       0       1      -3      -1       0       5      -2      -3      -4      -2      -3      -5       0       1       0      -7      -5      -1       0       0      -1      -8
+H      -1       2       2       1      -3       3       1      -2       6      -2      -2       0      -2      -2       0      -1      -1      -3       0      -2       1       2      -1      -8
+I      -1      -2      -2      -2      -2      -2      -2      -3      -2       5       2      -2       2       1      -2      -1       0      -5      -1       4      -2      -2      -1      -8
+L      -2      -3      -3      -4      -6      -2      -3      -4      -2       2       6      -3       4       2      -3      -3      -2      -2      -1       2      -3      -3      -1      -8
+K      -1       3       1       0      -5       1       0      -2       0      -2      -3       5       0      -5      -1       0       0      -3      -4      -2       1       0      -1      -8
+M      -1       0      -2      -3      -5      -1      -2      -3      -2       2       4       0       6       0      -2      -2      -1      -4      -2       2      -2      -2      -1      -8
+F      -3      -4      -3      -6      -4      -5      -5      -5      -2       1       2      -5       0      9       -5      -3      -3       0       7      -1      -4      -5      -2      -8
+P       1       0       0      -1      -3       0      -1       0       0      -2      -3      -1      -2      -5       6       1       0      -6      -5      -1      -1       0      -1      -8
+S       1       0       1       0       0      -1       0       1      -1      -1      -3       0      -2      -3       1       2       1      -2      -3      -1       0       0       0      -8
+T       1      -1       0       0      -2      -1       0       0      -1       0      -2       0      -1      -3       0       1       3      -5      -3       0       0      -1       0      -8
+W      -6       2      -4      -7      -8      -5      -7      -7      -3      -5      -2      -3      -4       0      -6      -2      -5      17       0      -6      -5      -6      -4      -8
+Y      -3      -4      -2      -4       0      -4      -4      -5       0      -1      -1      -4      -2       7      -5      -3      -3       0      10      -2      -3      -4      -2      -8
+V       0      -2      -2      -2      -2      -2      -2      -1      -2       4       2      -2       2      -1      -1      -1       0      -6      -2       4      -2      -2      -1      -8
+B       0      -1       2       3      -4       1       3       0       1      -2      -3       1      -2      -4      -1       0       0      -5      -3      -2       3       2      -1      -8
+Z       0       0       1       3      -5       3       3       0       2      -2      -3       0      -2      -5       0       0      -1      -6      -4      -2       2       3      -1      -8
+X       0      -1       0      -1      -3      -1      -1      -1      -1      -1      -1      -1      -1      -2      -1       0       0      -4      -2      -1      -1      -1      -1      -8
+*      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8      -8       1
index aac796c..39111c3 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
@@ -177,7 +178,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
 
     colourBySequence();
 
-    int max = -10;
+    float max = -10;
     int maxchain = -1;
     int pdbstart = 0;
     int pdbend = 0;
@@ -577,6 +578,8 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       showFeatures = true;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+
     PDBChain chain;
     if (bysequence && pdb != null)
     {
@@ -604,25 +607,16 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.startCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.startCol = fr.findFeatureColour(tmp.startCol,
-                            sequence[s], pos);
-                  }
+                  tmp.startCol = sr.getResidueColour(sequence[s], pos,
+                          finder);
                 }
                 pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1;
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.endCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.endCol = fr.findFeatureColour(tmp.endCol,
-                            sequence[s], pos);
-                  }
+                  tmp.endCol = sr
+                          .getResidueColour(sequence[s], pos, finder);
                 }
-
               }
             }
           }
@@ -1125,7 +1119,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
   // ////////////////////////////////
   // /StructureListener
   @Override
-  public String[] getPdbFile()
+  public String[] getStructureFiles()
   {
     return new String[] { pdbentry.getFile() };
   }
index 292de91..83642cc 100644 (file)
@@ -28,6 +28,7 @@ import jalview.gui.FeatureRenderer;
 import jalview.gui.SequenceRenderer;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
@@ -176,7 +177,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
     colourBySequence();
 
-    int max = -10;
+    float max = -10;
     int maxchain = -1;
     int pdbstart = 0;
     int pdbend = 0;
@@ -546,6 +547,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
       showFeatures = true;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
     PDBChain chain;
     if (bysequence && pdb != null)
     {
@@ -573,23 +575,15 @@ public class PDBCanvas extends JPanel implements MouseListener,
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.startCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.startCol = fr.findFeatureColour(tmp.startCol,
-                            sequence[s], pos);
-                  }
+                  tmp.startCol = sr.getResidueColour(sequence[s], pos,
+                          finder);
                 }
                 pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1;
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.endCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.endCol = fr.findFeatureColour(tmp.endCol,
-                            sequence[s], pos);
-                  }
+                  tmp.endCol = sr
+                          .getResidueColour(sequence[s], pos, finder);
                 }
 
               }
@@ -1081,7 +1075,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
   // ////////////////////////////////
   // /StructureListener
   @Override
-  public String[] getPdbFile()
+  public String[] getStructureFiles()
   {
     return new String[] { pdbentry.getFile() };
   }
index c40cdda..ba93046 100755 (executable)
@@ -39,6 +39,8 @@ import java.util.Vector;
 
 public class PDBChain
 {
+  public static final String RESNUM_FEATURE = "RESNUM";
+
   /**
    * SequenceFeature group for PDB File features added to sequences
    */
@@ -367,7 +369,7 @@ public class PDBChain
         Residue tmpres = residues.lastElement();
         Atom tmpat = tmpres.atoms.get(0);
         // Make A new SequenceFeature for the current residue numbering
-        SequenceFeature sf = new SequenceFeature("RESNUM", tmpat.resName
+        SequenceFeature sf = new SequenceFeature(RESNUM_FEATURE, tmpat.resName
                 + ":" + tmpat.resNumIns + " " + pdbid + id, "", offset
                 + count, offset + count, pdbid);
         resFeatures.addElement(sf);
index 439d479..85ae718 100644 (file)
@@ -423,6 +423,11 @@ public class ChimeraManager
             "list selection level residue", true);
     if (chimeraReply != null)
     {
+      /*
+       * expect 0, 1 or more lines of the format
+       * residue id #0:43.A type GLY
+       * where we are only interested in the atomspec #0.43.A
+       */
       for (String inputLine : chimeraReply)
       {
         String[] inputLineParts = inputLine.split("\\s+");
index 17874e6..b806355 100755 (executable)
@@ -37,6 +37,7 @@ import jalview.util.Format;
 import jalview.util.MappingUtils;
 import jalview.util.QuickSort;
 
+import java.awt.Color;
 import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.List;
@@ -67,8 +68,8 @@ public class AAFrequency
     }
   }
 
-  public static final ProfilesI calculate(List<SequenceI> list,
-          int start, int end)
+  public static final ProfilesI calculate(List<SequenceI> list, int start,
+          int end)
   {
     return calculate(list, start, end, false);
   }
@@ -289,6 +290,57 @@ public class AAFrequency
   }
 
   /**
+   * Derive the gap count annotation row.
+   * 
+   * @param gaprow
+   *          the annotation row to add annotations to
+   * @param profiles
+   *          the source consensus data
+   * @param startCol
+   *          start column (inclusive)
+   * @param endCol
+   *          end column (exclusive)
+   */
+  public static void completeGapAnnot(AlignmentAnnotation gaprow,
+          ProfilesI profiles, int startCol, int endCol, long nseq)
+  {
+    if (gaprow == null || gaprow.annotations == null
+            || gaprow.annotations.length < endCol)
+    {
+      /*
+       * called with a bad alignment annotation row 
+       * wait for it to be initialised properly
+       */
+      return;
+    }
+    // always set ranges again
+    gaprow.graphMax = nseq;
+    gaprow.graphMin = 0;
+    double scale = 0.8/nseq;
+    for (int i = startCol; i < endCol; i++)
+    {
+      ProfileI profile = profiles.get(i);
+      if (profile == null)
+      {
+        /*
+         * happens if sequences calculated over were 
+         * shorter than alignment width
+         */
+        gaprow.annotations[i] = null;
+        return;
+      }
+
+      final int gapped = profile.getNonGapped();
+
+      String description = "" + gapped;
+
+      gaprow.annotations[i] = new Annotation("", description,
+              '\0', gapped, jalview.util.ColorUtils.bleachColour(
+                      Color.DARK_GRAY, (float) scale * gapped));
+    }
+  }
+
+  /**
    * Returns a tooltip showing either
    * <ul>
    * <li>the full profile (percentages of all residues present), if
@@ -357,8 +409,7 @@ public class AAFrequency
    *          calculations
    * @return
    */
-  public static int[] extractProfile(ProfileI profile,
-          boolean ignoreGaps)
+  public static int[] extractProfile(ProfileI profile, boolean ignoreGaps)
   {
     int[] rtnval = new int[64];
     ResidueCount counts = profile.getCounts();
index 86bf721..07f43da 100755 (executable)
  */
 package jalview.analysis;
 
+import jalview.analysis.scoremodels.PIDModel;
+import jalview.analysis.scoremodels.ScoreMatrix;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
-import jalview.schemes.ResidueProperties;
-import jalview.schemes.ScoreMatrix;
 import jalview.util.Comparison;
 import jalview.util.Format;
 import jalview.util.MapList;
@@ -53,17 +55,11 @@ public class AlignSeq
 
   private static final String NEWLINE = System.lineSeparator();
 
-  static String[] dna = { "A", "C", "G", "T", "-" };
+  float[][] score;
 
-  // "C", "T", "A", "G", "-"};
-  static String[] pep = { "A", "R", "N", "D", "C", "Q", "E", "G", "H", "I",
-      "L", "K", "M", "F", "P", "S", "T", "W", "Y", "V", "B", "Z", "X", "-" };
+  float[][] E;
 
-  int[][] score;
-
-  int[][] E;
-
-  int[][] F;
+  float[][] F;
 
   int[][] traceback;
 
@@ -106,7 +102,7 @@ public class AlignSeq
   int count;
 
   /** DOCUMENT ME!! */
-  public int maxscore;
+  public float maxscore;
 
   float pid;
 
@@ -116,31 +112,24 @@ public class AlignSeq
 
   int gapExtend = 20;
 
-  int[][] lookup = ResidueProperties.getBLOSUM62();
-
-  String[] intToStr = pep;
-
-  int defInt = 23;
-
   StringBuffer output = new StringBuffer();
 
-  String type;
+  String type; // AlignSeq.PEP or AlignSeq.DNA
+
+  private ScoreMatrix scoreMatrix;
 
-  private int[] charToInt;
+  private static final int GAP_INDEX = -1;
 
   /**
    * Creates a new AlignSeq object.
    * 
-   * @param s1
-   *          DOCUMENT ME!
-   * @param s2
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
+   * @param s1 first sequence for alignment
+   * @param s2 second sequence for alignment
+   * @param type molecule type, either AlignSeq.PEP or AlignSeq.DNA
    */
   public AlignSeq(SequenceI s1, SequenceI s2, String type)
   {
-    SeqInit(s1, s1.getSequenceAsString(), s2, s2.getSequenceAsString(),
+    seqInit(s1, s1.getSequenceAsString(), s2, s2.getSequenceAsString(),
             type);
   }
 
@@ -157,7 +146,7 @@ public class AlignSeq
   public AlignSeq(SequenceI s1, String string1, SequenceI s2,
           String string2, String type)
   {
-    SeqInit(s1, string1.toUpperCase(), s2, string2.toUpperCase(), type);
+    seqInit(s1, string1.toUpperCase(), s2, string2.toUpperCase(), type);
   }
 
   /**
@@ -165,7 +154,7 @@ public class AlignSeq
    * 
    * @return DOCUMENT ME!
    */
-  public int getMaxScore()
+  public float getMaxScore()
   {
     return maxscore;
   }
@@ -261,26 +250,6 @@ public class AlignSeq
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceI getS1()
-  {
-    return s1;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceI getS2()
-  {
-    return s2;
-  }
-
-  /**
    * 
    * @return aligned instance of Seq 1
    */
@@ -322,36 +291,13 @@ public class AlignSeq
    * @param type
    *          DNA or PEPTIDE
    */
-  public void SeqInit(SequenceI s1, String string1, SequenceI s2,
+  public void seqInit(SequenceI s1, String string1, SequenceI s2,
           String string2, String type)
   {
     this.s1 = s1;
     this.s2 = s2;
     setDefaultParams(type);
-    SeqInit(string1, string2);
-  }
-
-  /**
-   * Construct score matrix for sequences with custom substitution matrix
-   * 
-   * @param s1
-   *          - sequence 1
-   * @param string1
-   *          - string to use for s1
-   * @param s2
-   *          - sequence 2
-   * @param string2
-   *          - string to use for s2
-   * @param scoreMatrix
-   *          - substitution matrix to use for alignment
-   */
-  public void SeqInit(SequenceI s1, String string1, SequenceI s2,
-          String string2, ScoreMatrix scoreMatrix)
-  {
-    this.s1 = s1;
-    this.s2 = s2;
-    setType(scoreMatrix.isDNA() ? AlignSeq.DNA : AlignSeq.PEP);
-    lookup = scoreMatrix.getMatrix();
+    seqInit(string1, string2);
   }
 
   /**
@@ -361,7 +307,7 @@ public class AlignSeq
    * @param string1
    * @param string2
    */
-  private void SeqInit(String string1, String string2)
+  private void seqInit(String string1, String string2)
   {
     s1str = extractGaps(jalview.util.Comparison.GapChars, string1);
     s2str = extractGaps(jalview.util.Comparison.GapChars, string2);
@@ -374,84 +320,31 @@ public class AlignSeq
       return;
     }
 
-    // System.out.println("lookuip " + rt.freeMemory() + " "+ rt.totalMemory());
-    seq1 = new int[s1str.length()];
-
-    // System.out.println("seq1 " + rt.freeMemory() +" " + rt.totalMemory());
-    seq2 = new int[s2str.length()];
-
-    // System.out.println("seq2 " + rt.freeMemory() + " " + rt.totalMemory());
-    score = new int[s1str.length()][s2str.length()];
+    score = new float[s1str.length()][s2str.length()];
 
-    // System.out.println("score " + rt.freeMemory() + " " + rt.totalMemory());
-    E = new int[s1str.length()][s2str.length()];
+    E = new float[s1str.length()][s2str.length()];
 
-    // System.out.println("E " + rt.freeMemory() + " " + rt.totalMemory());
-    F = new int[s1str.length()][s2str.length()];
+    F = new float[s1str.length()][s2str.length()];
     traceback = new int[s1str.length()][s2str.length()];
 
-    // System.out.println("F " + rt.freeMemory() + " " + rt.totalMemory());
-    seq1 = stringToInt(s1str, type);
-
-    // System.out.println("seq1 " + rt.freeMemory() + " " + rt.totalMemory());
-    seq2 = stringToInt(s2str, type);
-
-    // System.out.println("Seq2 " + rt.freeMemory() + " " + rt.totalMemory());
-    // long tstart = System.currentTimeMillis();
-    // calcScoreMatrix();
-    // long tend = System.currentTimeMillis();
-    // System.out.println("Time take to calculate score matrix = " +
-    // (tend-tstart) + " ms");
-    // printScoreMatrix(score);
-    // System.out.println();
-    // printScoreMatrix(traceback);
-    // System.out.println();
-    // printScoreMatrix(E);
-    // System.out.println();
-    // /printScoreMatrix(F);
-    // System.out.println();
-    // tstart = System.currentTimeMillis();
-    // traceAlignment();
-    // tend = System.currentTimeMillis();
-    // System.out.println("Time take to traceback alignment = " + (tend-tstart)
-    // + " ms");
-  }
-
-  private void setDefaultParams(String type)
-  {
-    setType(type);
+    seq1 = indexEncode(s1str);
 
-    if (type.equals(AlignSeq.PEP))
-    {
-      lookup = ResidueProperties.getDefaultPeptideMatrix();
-    }
-    else if (type.equals(AlignSeq.DNA))
-    {
-      lookup = ResidueProperties.getDefaultDnaMatrix();
-    }
+    seq2 = indexEncode(s2str);
   }
 
-  private void setType(String type2)
+  private void setDefaultParams(String moleculeType)
   {
-    this.type = type2;
-    if (type.equals(AlignSeq.PEP))
-    {
-      intToStr = pep;
-      charToInt = ResidueProperties.aaIndex;
-      defInt = ResidueProperties.maxProteinIndex;
-    }
-    else if (type.equals(AlignSeq.DNA))
-    {
-      intToStr = dna;
-      charToInt = ResidueProperties.nucleotideIndex;
-      defInt = ResidueProperties.maxNucleotideIndex;
-    }
-    else
+    if (!PEP.equals(moleculeType) && !DNA.equals(moleculeType))
     {
       output.append("Wrong type = dna or pep only");
       throw new Error(MessageManager.formatMessage(
-              "error.unknown_type_dna_or_pep", new String[] { type2 }));
+              "error.unknown_type_dna_or_pep",
+              new String[] { moleculeType }));
     }
+
+    type = moleculeType;
+    scoreMatrix = ScoreModels.getInstance().getDefaultModel(
+            PEP.equals(type));
   }
 
   /**
@@ -460,7 +353,7 @@ public class AlignSeq
   public void traceAlignment()
   {
     // Find the maximum score along the rhs or bottom row
-    int max = -9999;
+    float max = -Float.MAX_VALUE;
 
     for (int i = 0; i < seq1.length; i++)
     {
@@ -494,21 +387,17 @@ public class AlignSeq
     aseq1 = new int[seq1.length + seq2.length];
     aseq2 = new int[seq1.length + seq2.length];
 
+    StringBuilder sb1 = new StringBuilder(aseq1.length);
+    StringBuilder sb2 = new StringBuilder(aseq2.length);
+
     count = (seq1.length + seq2.length) - 1;
 
-    while ((i > 0) && (j > 0))
+    while (i > 0 && j > 0)
     {
-      if ((aseq1[count] != defInt) && (i >= 0))
-      {
-        aseq1[count] = seq1[i];
-        astr1 = s1str.charAt(i) + astr1;
-      }
-
-      if ((aseq2[count] != defInt) && (j > 0))
-      {
-        aseq2[count] = seq2[j];
-        astr2 = s2str.charAt(j) + astr2;
-      }
+      aseq1[count] = seq1[i];
+      sb1.append(s1str.charAt(i));
+      aseq2[count] = seq2[j];
+      sb2.append(s2str.charAt(j));
 
       trace = findTrace(i, j);
 
@@ -520,14 +409,14 @@ public class AlignSeq
       else if (trace == 1)
       {
         j--;
-        aseq1[count] = defInt;
-        astr1 = "-" + astr1.substring(1);
+        aseq1[count] = GAP_INDEX;
+        sb1.replace(sb1.length() - 1, sb1.length(), "-");
       }
       else if (trace == -1)
       {
         i--;
-        aseq2[count] = defInt;
-        astr2 = "-" + astr2.substring(1);
+        aseq2[count] = GAP_INDEX;
+        sb2.replace(sb2.length() - 1, sb2.length(), "-");
       }
 
       count--;
@@ -536,17 +425,24 @@ public class AlignSeq
     seq1start = i + 1;
     seq2start = j + 1;
 
-    if (aseq1[count] != defInt)
+    if (aseq1[count] != GAP_INDEX)
     {
       aseq1[count] = seq1[i];
-      astr1 = s1str.charAt(i) + astr1;
+      sb1.append(s1str.charAt(i));
     }
 
-    if (aseq2[count] != defInt)
+    if (aseq2[count] != GAP_INDEX)
     {
       aseq2[count] = seq2[j];
-      astr2 = s2str.charAt(j) + astr2;
+      sb2.append(s2str.charAt(j));
     }
+
+    /*
+     * we built the character strings backwards, so now
+     * reverse them to convert to sequence strings
+     */
+    astr1 = sb1.reverse().toString();
+    astr2 = sb2.reverse().toString();
   }
 
   /**
@@ -599,6 +495,8 @@ public class AlignSeq
             .append(String.valueOf(s2str.length())).append(")")
             .append(NEWLINE).append(NEWLINE);
 
+    ScoreMatrix pam250 = ScoreModels.getInstance().getPam250();
+
     for (int j = 0; j < nochunks; j++)
     {
       // Print the first aligned sequence
@@ -615,25 +513,27 @@ public class AlignSeq
       output.append(NEWLINE);
       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
 
-      // Print out the matching chars
+      /*
+       * Print out the match symbols:
+       * | for exact match (ignoring case)
+       * . if PAM250 score is positive
+       * else a space
+       */
       for (int i = 0; i < len; i++)
       {
         if ((i + (j * len)) < astr1.length())
         {
-          boolean sameChar = Comparison.isSameResidue(
-                  astr1.charAt(i + (j * len)), astr2.charAt(i + (j * len)),
-                  false);
-          if (sameChar
-                  && !jalview.util.Comparison.isGap(astr1.charAt(i
-                          + (j * len))))
+          char c1 = astr1.charAt(i + (j * len));
+          char c2 = astr2.charAt(i + (j * len));
+          boolean sameChar = Comparison.isSameResidue(c1, c2, false);
+          if (sameChar && !Comparison.isGap(c1))
           {
             pid++;
             output.append("|");
           }
           else if (type.equals("pep"))
           {
-            if (ResidueProperties.getPAM250(astr1.charAt(i + (j * len)),
-                    astr2.charAt(i + (j * len))) > 0)
+            if (pam250.getPairwiseScore(c1, c2) > 0)
             {
               output.append(".");
             }
@@ -678,46 +578,6 @@ public class AlignSeq
   /**
    * DOCUMENT ME!
    * 
-   * @param mat
-   *          DOCUMENT ME!
-   */
-  public void printScoreMatrix(int[][] mat)
-  {
-    int n = seq1.length;
-    int m = seq2.length;
-
-    for (int i = 0; i < n; i++)
-    {
-      // Print the top sequence
-      if (i == 0)
-      {
-        Format.print(System.out, "%8s", s2str.substring(0, 1));
-
-        for (int jj = 1; jj < m; jj++)
-        {
-          Format.print(System.out, "%5s", s2str.substring(jj, jj + 1));
-        }
-
-        System.out.println();
-      }
-
-      for (int j = 0; j < m; j++)
-      {
-        if (j == 0)
-        {
-          Format.print(System.out, "%3s", s1str.substring(i, i + 1));
-        }
-
-        Format.print(System.out, "%3d ", mat[i][j] / 10);
-      }
-
-      System.out.println();
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
    * @param i
    *          DOCUMENT ME!
    * @param j
@@ -728,7 +588,10 @@ public class AlignSeq
   public int findTrace(int i, int j)
   {
     int t = 0;
-    int max = score[i - 1][j - 1] + (lookup[seq1[i]][seq2[j]] * 10);
+    // float pairwiseScore = lookup[seq1[i]][seq2[j]];
+    float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
+            s2str.charAt(j));
+    float max = score[i - 1][j - 1] + (pairwiseScore * 10);
 
     if (F[i][j] > max)
     {
@@ -772,7 +635,8 @@ public class AlignSeq
     int m = seq2.length;
 
     // top left hand element
-    score[0][0] = lookup[seq1[0]][seq2[0]] * 10;
+    score[0][0] = scoreMatrix.getPairwiseScore(s1str.charAt(0),
+            s2str.charAt(0)) * 10;
     E[0][0] = -gapExtend;
     F[0][0] = 0;
 
@@ -783,7 +647,9 @@ public class AlignSeq
       E[0][j] = max(score[0][j - 1] - gapOpen, E[0][j - 1] - gapExtend);
       F[0][j] = -gapExtend;
 
-      score[0][j] = max(lookup[seq1[0]][seq2[j]] * 10, -gapOpen, -gapExtend);
+      float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(0),
+              s2str.charAt(j));
+      score[0][j] = max(pairwiseScore * 10, -gapOpen, -gapExtend);
 
       traceback[0][j] = 1;
     }
@@ -794,7 +660,9 @@ public class AlignSeq
       E[i][0] = -gapOpen;
       F[i][0] = max(score[i - 1][0] - gapOpen, F[i - 1][0] - gapExtend);
 
-      score[i][0] = max(lookup[seq1[i]][seq2[0]] * 10, E[i][0], F[i][0]);
+      float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
+              s2str.charAt(0));
+      score[i][0] = max(pairwiseScore * 10, E[i][0], F[i][0]);
       traceback[i][0] = -1;
     }
 
@@ -806,8 +674,10 @@ public class AlignSeq
         E[i][j] = max(score[i][j - 1] - gapOpen, E[i][j - 1] - gapExtend);
         F[i][j] = max(score[i - 1][j] - gapOpen, F[i - 1][j] - gapExtend);
 
+        float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
+                s2str.charAt(j));
         score[i][j] = max(score[i - 1][j - 1]
-                + (lookup[seq1[i]][seq2[j]] * 10), E[i][j], F[i][j]);
+                + (pairwiseScore * 10), E[i][j], F[i][j]);
         traceback[i][j] = findTrace(i, j);
       }
     }
@@ -843,27 +713,27 @@ public class AlignSeq
   /**
    * DOCUMENT ME!
    * 
-   * @param i1
+   * @param f1
    *          DOCUMENT ME!
-   * @param i2
+   * @param f2
    *          DOCUMENT ME!
-   * @param i3
+   * @param f3
    *          DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    */
-  public int max(int i1, int i2, int i3)
+  private static float max(float f1, float f2, float f3)
   {
-    int max = i1;
+    float max = f1;
 
-    if (i2 > i1)
+    if (f2 > f1)
     {
-      max = i2;
+      max = f2;
     }
 
-    if (i3 > max)
+    if (f3 > max)
     {
-      max = i3;
+      max = f3;
     }
 
     return max;
@@ -872,65 +742,44 @@ public class AlignSeq
   /**
    * DOCUMENT ME!
    * 
-   * @param i1
+   * @param f1
    *          DOCUMENT ME!
-   * @param i2
+   * @param f2
    *          DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    */
-  public int max(int i1, int i2)
+  private static float max(float f1, float f2)
   {
-    int max = i1;
+    float max = f1;
 
-    if (i2 > i1)
+    if (f2 > f1)
     {
-      max = i2;
+      max = f2;
     }
 
     return max;
   }
 
   /**
-   * DOCUMENT ME!
+   * Converts the character string to an array of integers which are the
+   * corresponding indices to the characters in the score matrix
    * 
    * @param s
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  public int[] stringToInt(String s, String type)
+  int[] indexEncode(String s)
   {
-    int[] seq1 = new int[s.length()];
+    int[] encoded = new int[s.length()];
 
     for (int i = 0; i < s.length(); i++)
     {
-      // String ss = s.substring(i, i + 1).toUpperCase();
       char c = s.charAt(i);
-      if ('a' <= c && c <= 'z')
-      {
-        // TO UPPERCASE !!!
-        c -= ('a' - 'A');
-      }
-
-      try
-      {
-        seq1[i] = charToInt[c]; // set accordingly from setType
-        if (seq1[i] < 0 || seq1[i] > defInt) // set from setType: 23 for
-                                             // peptides, or 4 for NA.
-        {
-          seq1[i] = defInt;
-        }
-
-      } catch (Exception e)
-      {
-        seq1[i] = defInt;
-      }
+      encoded[i] = scoreMatrix.getMatrixIndex(c);
     }
 
-    return seq1;
+    return encoded;
   }
 
   /**
@@ -950,7 +799,7 @@ public class AlignSeq
   public static void displayMatrix(Graphics g, int[][] mat, int n, int m,
           int psize)
   {
-    // TODO method dosen't seem to be referenced anywhere delete??
+    // TODO method doesn't seem to be referenced anywhere delete??
     int max = -1000;
     int min = 1000;
 
@@ -1113,7 +962,7 @@ public class AlignSeq
       {
         SequenceI bestm = null;
         AlignSeq bestaseq = null;
-        int bestscore = 0;
+        float bestscore = 0;
         for (SequenceI msq : al.getSequences())
         {
           AlignSeq aseq = doGlobalNWAlignment(msq, sq, dnaOrProtein);
@@ -1124,8 +973,8 @@ public class AlignSeq
             bestm = msq;
           }
         }
-        System.out.println("Best Score for " + (matches.size() + 1) + " :"
-                + bestscore);
+        // System.out.println("Best Score for " + (matches.size() + 1) + " :"
+        // + bestscore);
         matches.add(bestm);
         aligns.add(bestaseq);
         al.deleteSequence(bestm);
@@ -1214,6 +1063,8 @@ public class AlignSeq
 
     // long start = System.currentTimeMillis();
 
+    SimilarityParams pidParams = new SimilarityParams(true, true, true,
+            true);
     float pid;
     String seqi, seqj;
     for (int i = 0; i < height; i++)
@@ -1254,7 +1105,7 @@ public class AlignSeq
             seqj = ug;
           }
         }
-        pid = Comparison.PID(seqi, seqj);
+        pid = (float) PIDModel.computePID(seqi, seqj, pidParams);
 
         // use real sequence length rather than string length
         if (lngth[j] < lngth[i])
index 59cdccf..693e794 100755 (executable)
@@ -20,6 +20,8 @@
  */
 package jalview.analysis;
 
+import jalview.analysis.scoremodels.PIDModel;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
@@ -27,7 +29,6 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequenceNode;
-import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.util.QuickSort;
 
@@ -66,7 +67,7 @@ public class AlignmentSorter
 
   static boolean sortOrderAscending = true;
 
-  static NJTree lastTree = null;
+  static TreeModel lastTree = null;
 
   static boolean sortTreeAscending = true;
 
@@ -87,46 +88,29 @@ public class AlignmentSorter
   private static boolean sortLengthAscending;
 
   /**
-   * Sort by Percentage Identity w.r.t. s
+   * Sorts sequences in the alignment by Percentage Identity with the given
+   * reference sequence, sorting the highest identity to the top
    * 
    * @param align
    *          AlignmentI
    * @param s
    *          SequenceI
-   * @param tosort
-   *          sequences from align that are to be sorted.
-   */
-  public static void sortByPID(AlignmentI align, SequenceI s,
-          SequenceI[] tosort)
-  {
-    sortByPID(align, s, tosort, 0, -1);
-  }
-
-  /**
-   * Sort by Percentage Identity w.r.t. s
-   * 
-   * @param align
-   *          AlignmentI
-   * @param s
-   *          SequenceI
-   * @param tosort
-   *          sequences from align that are to be sorted.
-   * @param start
-   *          start column (0 for beginning
    * @param end
    */
-  public static void sortByPID(AlignmentI align, SequenceI s,
-          SequenceI[] tosort, int start, int end)
+  public static void sortByPID(AlignmentI align, SequenceI s)
   {
     int nSeq = align.getHeight();
 
     float[] scores = new float[nSeq];
     SequenceI[] seqs = new SequenceI[nSeq];
+    String refSeq = s.getSequenceAsString();
 
+    SimilarityParams pidParams = new SimilarityParams(true, true, true,
+            true);
     for (int i = 0; i < nSeq; i++)
     {
-      scores[i] = Comparison.PID(align.getSequenceAt(i)
-              .getSequenceAsString(), s.getSequenceAsString());
+      scores[i] = (float) PIDModel.computePID(align.getSequenceAt(i)
+              .getSequenceAsString(), refSeq, pidParams);
       seqs[i] = align.getSequenceAt(i);
     }
 
@@ -447,7 +431,7 @@ public class AlignmentSorter
    * @return DOCUMENT ME!
    */
   private static List<SequenceI> getOrderByTree(AlignmentI align,
-          NJTree tree)
+          TreeModel tree)
   {
     int nSeq = align.getHeight();
 
@@ -487,7 +471,7 @@ public class AlignmentSorter
    * @param tree
    *          tree which has
    */
-  public static void sortByTree(AlignmentI align, NJTree tree)
+  public static void sortByTree(AlignmentI align, TreeModel tree)
   {
     List<SequenceI> tmp = getOrderByTree(align, tree);
 
@@ -827,17 +811,23 @@ public class AlignmentSorter
       for (int f = 0; f < sf.length; f++)
       {
         // filter for selection criteria
-        if (
-        // ignore features outwith alignment start-stop positions.
-        (sf[f].end < sstart || sf[f].begin > sstop) ||
-        // or ignore based on selection criteria
-                (featureLabels != null && !AlignmentSorter
-                        .containsIgnoreCase(sf[f].type, featureLabels))
-                || (groupLabels != null
-                // problem here: we cannot eliminate null feature group features
-                && (sf[f].getFeatureGroup() != null && !AlignmentSorter
-                        .containsIgnoreCase(sf[f].getFeatureGroup(),
-                                groupLabels))))
+        SequenceFeature feature = sf[f];
+
+        /*
+         * double-check feature overlaps columns (JAL-2544)
+         * (could avoid this with a findPositions(fromCol, toCol) method)
+         * findIndex returns base 1 column values, startCol/endCol are base 0
+         */
+        boolean noOverlap = seqs[i].findIndex(feature.getBegin()) > stop + 1
+                || seqs[i].findIndex(feature.getEnd()) < start + 1;
+        boolean skipFeatureType = featureLabels != null
+                && !AlignmentSorter.containsIgnoreCase(feature.type,
+                        featureLabels);
+        boolean skipFeatureGroup = groupLabels != null
+                && (feature.getFeatureGroup() != null && !AlignmentSorter
+                        .containsIgnoreCase(feature.getFeatureGroup(),
+                                groupLabels));
+        if (noOverlap || skipFeatureType || skipFeatureGroup)
         {
           // forget about this feature
           sf[f] = null;
@@ -846,7 +836,7 @@ public class AlignmentSorter
         else
         {
           // or, also take a look at the scores if necessary.
-          if (!ignoreScore && !Float.isNaN(sf[f].getScore()))
+          if (!ignoreScore && !Float.isNaN(feature.getScore()))
           {
             if (seqScores[i] == 0)
             {
@@ -854,7 +844,7 @@ public class AlignmentSorter
             }
             seqScores[i]++;
             hasScore[i] = true;
-            scores[i] += sf[f].getScore(); // take the first instance of this
+            scores[i] += feature.getScore(); // take the first instance of this
             // score.
           }
         }
index ea96b3b..232cb5d 100644 (file)
@@ -42,6 +42,7 @@ import jalview.util.Comparison;
 import jalview.util.DBRefUtils;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
+import jalview.util.RangeComparator;
 import jalview.util.StringUtils;
 
 import java.io.UnsupportedEncodingException;
@@ -2265,14 +2266,7 @@ public class AlignmentUtils
      * ranges are assembled in order. Other cases should not use this method,
      * but instead construct an explicit mapping for CDS (e.g. EMBL parsing).
      */
-    Collections.sort(result, new Comparator<int[]>()
-    {
-      @Override
-      public int compare(int[] o1, int[] o2)
-      {
-        return Integer.compare(o1[0], o2[0]);
-      }
-    });
+    Collections.sort(result, new RangeComparator(true));
     return result;
   }
 
diff --git a/src/jalview/analysis/AverageDistanceTree.java b/src/jalview/analysis/AverageDistanceTree.java
new file mode 100644 (file)
index 0000000..907109e
--- /dev/null
@@ -0,0 +1,121 @@
+package jalview.analysis;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.SequenceNode;
+import jalview.viewmodel.AlignmentViewport;
+
+/**
+ * This class implements distance calculations used in constructing a Average
+ * Distance tree (also known as UPGMA)
+ */
+public class AverageDistanceTree extends TreeBuilder
+{
+  /**
+   * Constructor
+   * 
+   * @param av
+   * @param sm
+   * @param scoreParameters
+   */
+  public AverageDistanceTree(AlignmentViewport av, ScoreModelI sm,
+          SimilarityParamsI scoreParameters)
+  {
+    super(av, sm, scoreParameters);
+  }
+
+  /**
+   * Calculates and saves the distance between the combination of cluster(i) and
+   * cluster(j) and all other clusters. An average of the distances from
+   * cluster(i) and cluster(j) is calculated, weighted by the sizes of each
+   * cluster.
+   * 
+   * @param i
+   * @param j
+   */
+  @Override
+  protected void findClusterDistance(int i, int j)
+  {
+    int noi = clusters.elementAt(i).cardinality();
+    int noj = clusters.elementAt(j).cardinality();
+
+    // New distances from cluster i to others
+    double[] newdist = new double[noseqs];
+
+    for (int l = 0; l < noseqs; l++)
+    {
+      if ((l != i) && (l != j))
+      {
+        newdist[l] = ((distances.getValue(i, l) * noi) + (distances
+                .getValue(j, l) * noj)) / (noi + noj);
+      }
+      else
+      {
+        newdist[l] = 0;
+      }
+    }
+
+    for (int ii = 0; ii < noseqs; ii++)
+    {
+      distances.setValue(i, ii, newdist[ii]);
+      distances.setValue(ii, i, newdist[ii]);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected double findMinDistance()
+  {
+    double min = Double.MAX_VALUE;
+
+    for (int i = 0; i < (noseqs - 1); i++)
+    {
+      for (int j = i + 1; j < noseqs; j++)
+      {
+        if (!done.get(i) && !done.get(j))
+        {
+          if (distances.getValue(i, j) < min)
+          {
+            mini = i;
+            minj = j;
+
+            min = distances.getValue(i, j);
+          }
+        }
+      }
+    }
+    return min;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected void findNewDistances(SequenceNode nodei, SequenceNode nodej,
+          double dist)
+  {
+    double ih = 0;
+    double jh = 0;
+
+    SequenceNode sni = nodei;
+    SequenceNode snj = nodej;
+
+    while (sni != null)
+    {
+      ih = ih + sni.dist;
+      sni = (SequenceNode) sni.left();
+    }
+
+    while (snj != null)
+    {
+      jh = jh + snj.dist;
+      snj = (SequenceNode) snj.left();
+    }
+
+    nodei.dist = ((dist / 2) - ih);
+    nodej.dist = ((dist / 2) - jh);
+  }
+
+}
index 565924b..2b5a8f6 100755 (executable)
@@ -20,6 +20,8 @@
  */
 package jalview.analysis;
 
+import jalview.analysis.scoremodels.ScoreMatrix;
+import jalview.analysis.scoremodels.ScoreModels;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ResidueCount;
@@ -50,14 +52,19 @@ public class Conservation
 
   private static final int TOUPPERCASE = 'a' - 'A';
 
+  private static final int GAP_INDEX = -1;
+
   SequenceI[] sequences;
 
   int start;
 
   int end;
 
-  Vector<int[]> seqNums; // vector of int vectors where first is sequence
-                         // checksum
+  /*
+   * a list whose i'th element is an array whose first entry is the checksum
+   * of the i'th sequence, followed by residues encoded to score matrix index
+   */
+  Vector<int[]> seqNums;
 
   int maxLength = 0; // used by quality calcs
 
@@ -70,17 +77,17 @@ public class Conservation
    */
   Map<String, Integer>[] total;
 
-  boolean canonicaliseAa = true; // if true then conservation calculation will
-
-  // map all symbols to canonical aa numbering
-  // rather than consider conservation of that
-  // symbol
+  /*
+   * if true then conservation calculation will map all symbols to canonical aa
+   * numbering rather than consider conservation of that symbol
+   */
+  boolean canonicaliseAa = true;
 
-  /** Stores calculated quality values */
   private Vector<Double> quality;
 
-  /** Stores maximum and minimum values of quality values */
-  private double[] qualityRange = new double[2];
+  private double qualityMinimum;
+
+  private double qualityMaximum;
 
   private Sequence consSequence;
 
@@ -91,8 +98,16 @@ public class Conservation
 
   private String name = "";
 
+  /*
+   * an array, for each column, of counts of symbols (by score matrix index)
+   */
   private int[][] cons2;
 
+  /*
+   * gap counts for each column
+   */
+  private int[] cons2GapCounts;
+
   private String[] consSymbs;
 
   /**
@@ -162,27 +177,29 @@ public class Conservation
   }
 
   /**
-   * Translate sequence i into a numerical representation and store it in the
-   * i'th position of the seqNums array.
+   * Translate sequence i into score matrix indices and store it in the i'th
+   * position of the seqNums array.
    * 
    * @param i
+   * @param sm
    */
-  private void calcSeqNum(int i)
+  private void calcSeqNum(int i, ScoreMatrix sm)
   {
-    String sq = null; // for dumb jbuilder not-inited exception warning
-    int[] sqnum = null;
-
     int sSize = sequences.length;
 
     if ((i > -1) && (i < sSize))
     {
-      sq = sequences[i].getSequenceAsString();
+      String sq = sequences[i].getSequenceAsString();
 
       if (seqNums.size() <= i)
       {
         seqNums.addElement(new int[sq.length() + 1]);
       }
 
+      /*
+       * the first entry in the array is the sequence's hashcode,
+       * following entries are matrix indices of sequence characters
+       */
       if (sq.hashCode() != seqNums.elementAt(i)[0])
       {
         int j;
@@ -195,14 +212,26 @@ public class Conservation
           maxLength = len;
         }
 
-        sqnum = new int[len + 1]; // better to always make a new array -
+        int[] sqnum = new int[len + 1]; // better to always make a new array -
         // sequence can change its length
         sqnum[0] = sq.hashCode();
 
         for (j = 1; j <= len; j++)
         {
-          sqnum[j] = jalview.schemes.ResidueProperties.aaIndex[sq
-                  .charAt(j - 1)];
+          // sqnum[j] = ResidueProperties.aaIndex[sq.charAt(j - 1)];
+          char residue = sq.charAt(j - 1);
+          if (Comparison.isGap(residue))
+          {
+            sqnum[j] = GAP_INDEX;
+          }
+          else
+          {
+            sqnum[j] = sm.getMatrixIndex(residue);
+            if (sqnum[j] == -1)
+            {
+              sqnum[j] = GAP_INDEX;
+            }
+          }
         }
 
         seqNums.setElementAt(sqnum, i);
@@ -527,137 +556,133 @@ public class Conservation
   // From Alignment.java in jalview118
   public void findQuality()
   {
-    findQuality(0, maxLength - 1);
+    findQuality(0, maxLength - 1, ScoreModels.getInstance().getBlosum62());
   }
 
   /**
    * DOCUMENT ME!
+   * 
+   * @param sm
    */
-  private void percentIdentity2()
+  private void percentIdentity(ScoreMatrix sm)
   {
     seqNums = new Vector<int[]>();
-    // calcSeqNum(s);
     int i = 0, iSize = sequences.length;
     // Do we need to calculate this again?
     for (i = 0; i < iSize; i++)
     {
-      calcSeqNum(i);
+      calcSeqNum(i, sm);
     }
 
     if ((cons2 == null) || seqNumsChanged)
     {
+      // FIXME remove magic number 24 without changing calc
+      // sm.getSize() returns 25 so doesn't quite do it...
       cons2 = new int[maxLength][24];
+      cons2GapCounts = new int[maxLength];
 
-      // Initialize the array
-      for (int j = 0; j < 24; j++)
-      {
-        for (i = 0; i < maxLength; i++)
-        {
-          cons2[i][j] = 0;
-        }
-      }
-
-      int[] sqnum;
       int j = 0;
 
       while (j < sequences.length)
       {
-        sqnum = seqNums.elementAt(j);
+        int[] sqnum = seqNums.elementAt(j);
 
         for (i = 1; i < sqnum.length; i++)
         {
-          cons2[i - 1][sqnum[i]]++;
+          int index = sqnum[i];
+          if (index == GAP_INDEX)
+          {
+            cons2GapCounts[i - 1]++;
+          }
+          else
+          {
+            cons2[i - 1][index]++;
+          }
         }
 
+        // TODO should this start from sqnum.length?
         for (i = sqnum.length - 1; i < maxLength; i++)
         {
-          cons2[i][23]++; // gap count
+          cons2GapCounts[i]++;
         }
-
         j++;
       }
-
-      // unnecessary ?
-
-      /*
-       * for (int i=start; i <= end; i++) { int max = -1000; int maxi = -1; int
-       * maxj = -1;
-       * 
-       * for (int j=0;j<24;j++) { if (cons2[i][j] > max) { max = cons2[i][j];
-       * maxi = i; maxj = j; } } }
-       */
     }
   }
 
   /**
-   * Calculates the quality of the set of sequences
+   * Calculates the quality of the set of sequences over the given inclusive
+   * column range, using the specified substitution score matrix
    * 
-   * @param startRes
-   *          Start residue
-   * @param endRes
-   *          End residue
+   * @param startCol
+   * @param endCol
+   * @param scoreMatrix
    */
-  public void findQuality(int startRes, int endRes)
+  protected void findQuality(int startCol, int endCol, ScoreMatrix scoreMatrix)
   {
     quality = new Vector<Double>();
 
-    double max = -10000;
-    int[][] BLOSUM62 = ResidueProperties.getBLOSUM62();
+    double max = -Double.MAX_VALUE;
+    float[][] scores = scoreMatrix.getMatrix();
 
-    // Loop over columns // JBPNote Profiling info
-    // long ts = System.currentTimeMillis();
-    // long te = System.currentTimeMillis();
-    percentIdentity2();
+    percentIdentity(scoreMatrix);
 
     int size = seqNums.size();
     int[] lengths = new int[size];
-    double tot, bigtot, sr, tmp;
-    double[] x, xx;
-    int l, j, i, ii, i2, k, seqNum;
 
-    for (l = 0; l < size; l++)
+    for (int l = 0; l < size; l++)
     {
       lengths[l] = seqNums.elementAt(l).length - 1;
     }
 
-    for (j = startRes; j <= endRes; j++)
+    final int symbolCount = scoreMatrix.getSize();
+
+    for (int j = startCol; j <= endCol; j++)
     {
-      bigtot = 0;
+      double bigtot = 0;
 
       // First Xr = depends on column only
-      x = new double[24];
+      double[] x = new double[symbolCount];
 
-      for (ii = 0; ii < 24; ii++)
+      for (int ii = 0; ii < symbolCount; ii++)
       {
         x[ii] = 0;
 
-        for (i2 = 0; i2 < 24; i2++)
+        /*
+         * todo JAL-728 currently assuming last symbol in matrix is * for gap
+         * (which we ignore as counted separately); true for BLOSUM62 but may
+         * not be once alternative matrices are supported
+         */
+        for (int i2 = 0; i2 < symbolCount - 1; i2++)
         {
-          x[ii] += (((double) cons2[j][i2] * BLOSUM62[ii][i2]) + 4);
+          x[ii] += (((double) cons2[j][i2] * scores[ii][i2]) + 4D);
         }
+        x[ii] += 4D + cons2GapCounts[j] * scoreMatrix.getMinimumScore();
 
         x[ii] /= size;
       }
 
       // Now calculate D for each position and sum
-      for (k = 0; k < size; k++)
+      for (int k = 0; k < size; k++)
       {
-        tot = 0;
-        xx = new double[24];
-        seqNum = (j < lengths[k]) ? seqNums.elementAt(k)[j + 1] : 23; // Sequence,
-                                                                      // or gap
-                                                                      // at the
-                                                                      // end
-
-        // This is a loop over r
-        for (i = 0; i < 23; i++)
-        {
-          sr = 0;
+        double tot = 0;
+        double[] xx = new double[symbolCount];
+        // sequence character index, or implied gap if sequence too short
+        int seqNum = (j < lengths[k]) ? seqNums.elementAt(k)[j + 1]
+                : GAP_INDEX;
 
-          sr = (double) BLOSUM62[i][seqNum] + 4;
+        for (int i = 0; i < symbolCount - 1; i++)
+        {
+          double sr = 4D;
+          if (seqNum == GAP_INDEX)
+          {
+            sr += scoreMatrix.getMinimumScore();
+          }
+          else
+          {
+            sr += scores[i][seqNum];
+          }
 
-          // Calculate X with another loop over residues
-          // System.out.println("Xi " + i + " " + x[i] + " " + sr);
           xx[i] = x[i] - sr;
 
           tot += (xx[i] * xx[i]);
@@ -666,24 +691,18 @@ public class Conservation
         bigtot += Math.sqrt(tot);
       }
 
-      // This is the quality for one column
-      if (max < bigtot)
-      {
-        max = bigtot;
-      }
+      max = Math.max(max, bigtot);
 
-      // bigtot = bigtot * (size-cons2[j][23])/size;
       quality.addElement(new Double(bigtot));
-
-      // Need to normalize by gaps
     }
 
-    double newmax = -10000;
+    double newmax = -Double.MAX_VALUE;
 
-    for (j = startRes; j <= endRes; j++)
+    for (int j = startCol; j <= endCol; j++)
     {
-      tmp = quality.elementAt(j).doubleValue();
-      tmp = ((max - tmp) * (size - cons2[j][23])) / size;
+      double tmp = quality.elementAt(j).doubleValue();
+      // tmp = ((max - tmp) * (size - cons2[j][23])) / size;
+      tmp = ((max - tmp) * (size - cons2GapCounts[j])) / size;
 
       // System.out.println(tmp+ " " + j);
       quality.setElementAt(new Double(tmp), j);
@@ -694,9 +713,8 @@ public class Conservation
       }
     }
 
-    // System.out.println("Quality " + s);
-    qualityRange[0] = 0D;
-    qualityRange[1] = newmax;
+    qualityMinimum = 0D;
+    qualityMaximum = newmax;
   }
 
   /**
@@ -746,14 +764,14 @@ public class Conservation
 
     if (quality2 != null)
     {
-      quality2.graphMax = (float) qualityRange[1];
+      quality2.graphMax = (float) qualityMaximum;
       if (quality2.annotations != null
               && quality2.annotations.length < alWidth)
       {
         quality2.annotations = new Annotation[alWidth];
       }
-      qmin = (float) qualityRange[0];
-      qmax = (float) qualityRange[1];
+      qmin = (float) qualityMinimum;
+      qmax = (float) qualityMaximum;
     }
 
     for (int i = istart; i < alWidth; i++)
index e0e50fb..487e85e 100644 (file)
 package jalview.analysis;
 
 import jalview.api.analysis.ScoreModelI;
-import jalview.datamodel.AlignmentView;
-import jalview.datamodel.BinaryNode;
-import jalview.datamodel.CigarArray;
-import jalview.datamodel.NodeTransformI;
-import jalview.datamodel.SeqCigar;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.SequenceNode;
-import jalview.io.NewickFile;
-import jalview.schemes.ResidueProperties;
-
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Vector;
+import jalview.viewmodel.AlignmentViewport;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision$
+ * This class implements distance calculations used in constructing a Neighbour
+ * Joining tree
  */
-public class NJTree
+public class NJTree extends TreeBuilder
 {
-  Vector<Cluster> cluster;
-
-  SequenceI[] sequence;
-
-  // SequenceData is a string representation of what the user
-  // sees. The display may contain hidden columns.
-  public AlignmentView seqData = null;
-
-  int[] done;
-
-  int noseqs;
-
-  int noClus;
-
-  float[][] distance;
-
-  int mini;
-
-  int minj;
-
-  float ri;
-
-  float rj;
-
-  Vector<SequenceNode> groups = new Vector<SequenceNode>();
-
-  SequenceNode maxdist;
-
-  SequenceNode top;
-
-  float maxDistValue;
-
-  float maxheight;
-
-  int ycount;
-
-  Vector<SequenceNode> node;
-
-  String type;
-
-  String pwtype;
-
-  Object found = null;
-
-  boolean hasDistances = true; // normal case for jalview trees
-
-  boolean hasBootstrap = false; // normal case for jalview trees
-
-  private boolean hasRootDistance = true;
-
-  /**
-   * Create a new NJTree object with leaves associated with sequences in seqs,
-   * and original alignment data represented by Cigar strings.
-   * 
-   * @param seqs
-   *          SequenceI[]
-   * @param odata
-   *          Cigar[]
-   * @param treefile
-   *          NewickFile
-   */
-  public NJTree(SequenceI[] seqs, AlignmentView odata, NewickFile treefile)
-  {
-    this(seqs, treefile);
-    if (odata != null)
-    {
-      seqData = odata;
-    }
-    /*
-     * sequenceString = new String[odata.length]; char gapChar =
-     * jalview.util.Comparison.GapChars.charAt(0); for (int i = 0; i <
-     * odata.length; i++) { SequenceI oseq_aligned = odata[i].getSeq(gapChar);
-     * sequenceString[i] = oseq_aligned.getSequence(); }
-     */
-  }
-
-  /**
-   * Creates a new NJTree object from a tree from an external source
-   * 
-   * @param seqs
-   *          SequenceI which should be associated with leafs of treefile
-   * @param treefile
-   *          A parsed tree
-   */
-  public NJTree(SequenceI[] seqs, NewickFile treefile)
-  {
-    this.sequence = seqs;
-    top = treefile.getTree();
-
-    /**
-     * There is no dependent alignment to be recovered from an imported tree.
-     * 
-     * if (sequenceString == null) { sequenceString = new String[seqs.length];
-     * for (int i = 0; i < seqs.length; i++) { sequenceString[i] =
-     * seqs[i].getSequence(); } }
-     */
-
-    hasDistances = treefile.HasDistances();
-    hasBootstrap = treefile.HasBootstrap();
-    hasRootDistance = treefile.HasRootDistance();
-
-    maxheight = findHeight(top);
-
-    SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs);
-
-    Vector<SequenceNode> leaves = findLeaves(top);
-
-    int i = 0;
-    int namesleft = seqs.length;
-
-    SequenceNode j;
-    SequenceI nam;
-    String realnam;
-    Vector<SequenceI> one2many = new Vector<SequenceI>();
-    int countOne2Many = 0;
-    while (i < leaves.size())
-    {
-      j = leaves.elementAt(i++);
-      realnam = j.getName();
-      nam = null;
-
-      if (namesleft > -1)
-      {
-        nam = algnIds.findIdMatch(realnam);
-      }
-
-      if (nam != null)
-      {
-        j.setElement(nam);
-        if (one2many.contains(nam))
-        {
-          countOne2Many++;
-          // if (jalview.bin.Cache.log.isDebugEnabled())
-          // jalview.bin.Cache.log.debug("One 2 many relationship for
-          // "+nam.getName());
-        }
-        else
-        {
-          one2many.addElement(nam);
-          namesleft--;
-        }
-      }
-      else
-      {
-        j.setElement(new Sequence(realnam, "THISISAPLACEHLDER"));
-        j.setPlaceholder(true);
-      }
-    }
-    // if (jalview.bin.Cache.log.isDebugEnabled() && countOne2Many>0) {
-    // jalview.bin.Cache.log.debug("There were "+countOne2Many+" alignment
-    // sequence ids (out of "+one2many.size()+" unique ids) linked to two or
-    // more leaves.");
-    // }
-    // one2many.clear();
-  }
-
   /**
-   * Creates a new NJTree object.
+   * Constructor given a viewport, tree type and score model
    * 
-   * @param sequence
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
-   * @param pwtype
-   *          DOCUMENT ME!
-   * @param start
-   *          DOCUMENT ME!
-   * @param end
-   *          DOCUMENT ME!
+   * @param av
+   *          the current alignment viewport
+   * @param sm
+   *          a distance or similarity score model to use to compute the tree
+   * @param scoreParameters
    */
-  public NJTree(SequenceI[] sequence, AlignmentView seqData, String type,
-          String pwtype, ScoreModelI sm, int start, int end)
+  public NJTree(AlignmentViewport av, ScoreModelI sm,
+          SimilarityParamsI scoreParameters)
   {
-    this.sequence = sequence;
-    this.node = new Vector<SequenceNode>();
-    this.type = type;
-    this.pwtype = pwtype;
-    if (seqData != null)
-    {
-      this.seqData = seqData;
-    }
-    else
-    {
-      SeqCigar[] seqs = new SeqCigar[sequence.length];
-      for (int i = 0; i < sequence.length; i++)
-      {
-        seqs[i] = new SeqCigar(sequence[i], start, end);
-      }
-      CigarArray sdata = new CigarArray(seqs);
-      sdata.addOperation(CigarArray.M, end - start + 1);
-      this.seqData = new AlignmentView(sdata, start);
-    }
-    // System.err.println("Made seqData");// dbg
-    if (!(type.equals("NJ")))
-    {
-      type = "AV";
-    }
-
-    if (sm == null && !(pwtype.equals("PID")))
-    {
-      if (ResidueProperties.getScoreMatrix(pwtype) == null)
-      {
-        pwtype = "BLOSUM62";
-      }
-    }
-
-    int i = 0;
-
-    done = new int[sequence.length];
-
-    while ((i < sequence.length) && (sequence[i] != null))
-    {
-      done[i] = 0;
-      i++;
-    }
-
-    noseqs = i++;
-
-    distance = findDistances(sm);
-    // System.err.println("Made distances");// dbg
-    makeLeaves();
-    // System.err.println("Made leaves");// dbg
-
-    noClus = cluster.size();
-
-    cluster();
-    // System.err.println("Made clusters");// dbg
-
+    super(av, sm, scoreParameters);
   }
 
   /**
-   * Generate a string representation of the Tree
-   * 
-   * @return Newick File with all tree data available
+   * {@inheritDoc}
    */
   @Override
-  public String toString()
-  {
-    jalview.io.NewickFile fout = new jalview.io.NewickFile(getTopNode());
-
-    return fout.print(isHasBootstrap(), isHasDistances(),
-            isHasRootDistance()); // output all data available for tree
-  }
-
-  /**
-   * 
-   * used when the alignment associated to a tree has changed.
-   * 
-   * @param list
-   *          Sequence set to be associated with tree nodes
-   */
-  public void UpdatePlaceHolders(List<SequenceI> list)
+  protected double findMinDistance()
   {
-    Vector<SequenceNode> leaves = findLeaves(top);
+    double min = Double.MAX_VALUE;
 
-    int sz = leaves.size();
-    SequenceIdMatcher seqmatcher = null;
-    int i = 0;
-
-    while (i < sz)
+    for (int i = 0; i < (noseqs - 1); i++)
     {
-      SequenceNode leaf = leaves.elementAt(i++);
-
-      if (list.contains(leaf.element()))
-      {
-        leaf.setPlaceholder(false);
-      }
-      else
+      for (int j = i + 1; j < noseqs; j++)
       {
-        if (seqmatcher == null)
+        if (!done.get(i) && !done.get(j))
         {
-          // Only create this the first time we need it
-          SequenceI[] seqs = new SequenceI[list.size()];
+          double tmp = distances.getValue(i, j)
+                  - (findr(i, j) + findr(j, i));
 
-          for (int j = 0; j < seqs.length; j++)
+          if (tmp < min)
           {
-            seqs[j] = list.get(j);
-          }
-
-          seqmatcher = new SequenceIdMatcher(seqs);
-        }
-
-        SequenceI nam = seqmatcher.findIdMatch(leaf.getName());
+            mini = i;
+            minj = j;
 
-        if (nam != null)
-        {
-          if (!leaf.isPlaceholder())
-          {
-            // remapping the node to a new sequenceI - should remove any refs to
-            // old one.
-            // TODO - make many sequenceI to one leaf mappings possible!
-            // (JBPNote)
-          }
-          leaf.setPlaceholder(false);
-          leaf.setElement(nam);
-        }
-        else
-        {
-          if (!leaf.isPlaceholder())
-          {
-            // Construct a new placeholder sequence object for this leaf
-            leaf.setElement(new Sequence(leaf.getName(),
-                    "THISISAPLACEHLDER"));
+            min = tmp;
           }
-          leaf.setPlaceholder(true);
-
-        }
-      }
-    }
-  }
-
-  /**
-   * rename any nodes according to their associated sequence. This will modify
-   * the tree's metadata! (ie the original NewickFile or newly generated
-   * BinaryTree's label data)
-   */
-  public void renameAssociatedNodes()
-  {
-    applyToNodes(new NodeTransformI()
-    {
-
-      @Override
-      public void transform(BinaryNode nd)
-      {
-        Object el = nd.element();
-        if (el != null && el instanceof SequenceI)
-        {
-          nd.setName(((SequenceI) el).getName());
         }
       }
-    });
-  }
-
-  /**
-   * DOCUMENT ME!
-   */
-  public void cluster()
-  {
-    while (noClus > 2)
-    {
-      if (type.equals("NJ"))
-      {
-        findMinNJDistance();
-      }
-      else
-      {
-        findMinDistance();
-      }
-
-      Cluster c = joinClusters(mini, minj);
-
-      done[minj] = 1;
-
-      cluster.setElementAt(null, minj);
-      cluster.setElementAt(c, mini);
-
-      noClus--;
-    }
-
-    boolean onefound = false;
-
-    int one = -1;
-    int two = -1;
-
-    for (int i = 0; i < noseqs; i++)
-    {
-      if (done[i] != 1)
-      {
-        if (onefound == false)
-        {
-          two = i;
-          onefound = true;
-        }
-        else
-        {
-          one = i;
-        }
-      }
-    }
-
-    joinClusters(one, two);
-    top = (node.elementAt(one));
-
-    reCount(top);
-    findHeight(top);
-    findMaxDist(top);
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * @param j
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public Cluster joinClusters(int i, int j)
-  {
-    float dist = distance[i][j];
-
-    int noi = cluster.elementAt(i).value.length;
-    int noj = cluster.elementAt(j).value.length;
-
-    int[] value = new int[noi + noj];
-
-    for (int ii = 0; ii < noi; ii++)
-    {
-      value[ii] = cluster.elementAt(i).value[ii];
-    }
-
-    for (int ii = noi; ii < (noi + noj); ii++)
-    {
-      value[ii] = cluster.elementAt(j).value[ii - noi];
-    }
-
-    Cluster c = new Cluster(value);
-
-    ri = findr(i, j);
-    rj = findr(j, i);
-
-    if (type.equals("NJ"))
-    {
-      findClusterNJDistance(i, j);
-    }
-    else
-    {
-      findClusterDistance(i, j);
-    }
-
-    SequenceNode sn = new SequenceNode();
-
-    sn.setLeft((node.elementAt(i)));
-    sn.setRight((node.elementAt(j)));
-
-    SequenceNode tmpi = (node.elementAt(i));
-    SequenceNode tmpj = (node.elementAt(j));
-
-    if (type.equals("NJ"))
-    {
-      findNewNJDistances(tmpi, tmpj, dist);
-    }
-    else
-    {
-      findNewDistances(tmpi, tmpj, dist);
-    }
-
-    tmpi.setParent(sn);
-    tmpj.setParent(sn);
-
-    node.setElementAt(sn, i);
-
-    return c;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param tmpi
-   *          DOCUMENT ME!
-   * @param tmpj
-   *          DOCUMENT ME!
-   * @param dist
-   *          DOCUMENT ME!
-   */
-  public void findNewNJDistances(SequenceNode tmpi, SequenceNode tmpj,
-          float dist)
-  {
-
-    tmpi.dist = ((dist + ri) - rj) / 2;
-    tmpj.dist = (dist - tmpi.dist);
-
-    if (tmpi.dist < 0)
-    {
-      tmpi.dist = 0;
-    }
-
-    if (tmpj.dist < 0)
-    {
-      tmpj.dist = 0;
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param tmpi
-   *          DOCUMENT ME!
-   * @param tmpj
-   *          DOCUMENT ME!
-   * @param dist
-   *          DOCUMENT ME!
-   */
-  public void findNewDistances(SequenceNode tmpi, SequenceNode tmpj,
-          float dist)
-  {
-    float ih = 0;
-    float jh = 0;
-
-    SequenceNode sni = tmpi;
-    SequenceNode snj = tmpj;
-
-    while (sni != null)
-    {
-      ih = ih + sni.dist;
-      sni = (SequenceNode) sni.left();
-    }
-
-    while (snj != null)
-    {
-      jh = jh + snj.dist;
-      snj = (SequenceNode) snj.left();
     }
 
-    tmpi.dist = ((dist / 2) - ih);
-    tmpj.dist = ((dist / 2) - jh);
+    return min;
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * @param j
-   *          DOCUMENT ME!
+   * {@inheritDoc}
    */
-  public void findClusterDistance(int i, int j)
+  @Override
+  protected void findNewDistances(SequenceNode nodei, SequenceNode nodej,
+          double dist)
   {
-    int noi = cluster.elementAt(i).value.length;
-    int noj = cluster.elementAt(j).value.length;
+    nodei.dist = ((dist + ri) - rj) / 2;
+    nodej.dist = (dist - nodei.dist);
 
-    // New distances from cluster to others
-    float[] newdist = new float[noseqs];
-
-    for (int l = 0; l < noseqs; l++)
+    if (nodei.dist < 0)
     {
-      if ((l != i) && (l != j))
-      {
-        newdist[l] = ((distance[i][l] * noi) + (distance[j][l] * noj))
-                / (noi + noj);
-      }
-      else
-      {
-        newdist[l] = 0;
-      }
+      nodei.dist = 0;
     }
 
-    for (int ii = 0; ii < noseqs; ii++)
+    if (nodej.dist < 0)
     {
-      distance[i][ii] = newdist[ii];
-      distance[ii][i] = newdist[ii];
+      nodej.dist = 0;
     }
   }
 
   /**
-   * DOCUMENT ME!
+   * Calculates and saves the distance between the combination of cluster(i) and
+   * cluster(j) and all other clusters. The new distance to cluster k is
+   * calculated as the average of the distances from i to k and from j to k,
+   * less half the distance from i to j.
    * 
    * @param i
-   *          DOCUMENT ME!
    * @param j
-   *          DOCUMENT ME!
    */
-  public void findClusterNJDistance(int i, int j)
+  @Override
+  protected
+  void findClusterDistance(int i, int j)
   {
-
-    // New distances from cluster to others
-    float[] newdist = new float[noseqs];
-
+    // New distances from cluster i to others
+    double[] newdist = new double[noseqs];
+  
+    double ijDistance = distances.getValue(i, j);
     for (int l = 0; l < noseqs; l++)
     {
       if ((l != i) && (l != j))
       {
-        newdist[l] = ((distance[i][l] + distance[j][l]) - distance[i][j]) / 2;
+        newdist[l] = (distances.getValue(i, l) + distances.getValue(j, l) - ijDistance) / 2;
       }
       else
       {
         newdist[l] = 0;
       }
     }
-
+  
     for (int ii = 0; ii < noseqs; ii++)
     {
-      distance[i][ii] = newdist[ii];
-      distance[ii][i] = newdist[ii];
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * @param j
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public float findr(int i, int j)
-  {
-    float tmp = 1;
-
-    for (int k = 0; k < noseqs; k++)
-    {
-      if ((k != i) && (k != j) && (done[k] != 1))
-      {
-        tmp = tmp + distance[i][k];
-      }
-    }
-
-    if (noClus > 2)
-    {
-      tmp = tmp / (noClus - 2);
+      distances.setValue(i, ii, newdist[ii]);
+      distances.setValue(ii, i, newdist[ii]);
     }
-
-    return tmp;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public float findMinNJDistance()
-  {
-    float min = 100000;
-
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i + 1; j < noseqs; j++)
-      {
-        if ((done[i] != 1) && (done[j] != 1))
-        {
-          float tmp = distance[i][j] - (findr(i, j) + findr(j, i));
-
-          if (tmp < min)
-          {
-            mini = i;
-            minj = j;
-
-            min = tmp;
-          }
-        }
-      }
-    }
-
-    return min;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public float findMinDistance()
-  {
-    float min = 100000;
-
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i + 1; j < noseqs; j++)
-      {
-        if ((done[i] != 1) && (done[j] != 1))
-        {
-          if (distance[i][j] < min)
-          {
-            mini = i;
-            minj = j;
-
-            min = distance[i][j];
-          }
-        }
-      }
-    }
-
-    return min;
-  }
-
-  /**
-   * Calculate a distance matrix given the sequence input data and score model
-   * 
-   * @return similarity matrix used to compute tree
-   */
-  public float[][] findDistances(ScoreModelI _pwmatrix)
-  {
-
-    float[][] dist = new float[noseqs][noseqs];
-    if (_pwmatrix == null)
-    {
-      // Resolve substitution model
-      _pwmatrix = ResidueProperties.getScoreModel(pwtype);
-      if (_pwmatrix == null)
-      {
-        _pwmatrix = ResidueProperties.getScoreMatrix("BLOSUM62");
-      }
-    }
-    dist = _pwmatrix.findDistances(seqData);
-    return dist;
-
-  }
-
-  /**
-   * DOCUMENT ME!
-   */
-  public void makeLeaves()
-  {
-    cluster = new Vector<Cluster>();
-
-    for (int i = 0; i < noseqs; i++)
-    {
-      SequenceNode sn = new SequenceNode();
-
-      sn.setElement(sequence[i]);
-      sn.setName(sequence[i].getName());
-      node.addElement(sn);
-
-      int[] value = new int[1];
-      value[0] = i;
-
-      Cluster c = new Cluster(value);
-      cluster.addElement(c);
-    }
-  }
-
-  /**
-   * Search for leaf nodes below (or at) the given node
-   * 
-   * @param nd
-   *          root node to search from
-   * 
-   * @return
-   */
-  public Vector<SequenceNode> findLeaves(SequenceNode nd)
-  {
-    Vector<SequenceNode> leaves = new Vector<SequenceNode>();
-    findLeaves(nd, leaves);
-    return leaves;
-  }
-
-  /**
-   * Search for leaf nodes.
-   * 
-   * @param nd
-   *          root node to search from
-   * @param leaves
-   *          Vector of leaves to add leaf node objects too.
-   * 
-   * @return Vector of leaf nodes on binary tree
-   */
-  Vector<SequenceNode> findLeaves(SequenceNode nd,
-          Vector<SequenceNode> leaves)
-  {
-    if (nd == null)
-    {
-      return leaves;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null)) // Interior node
-    // detection
-    {
-      leaves.addElement(nd);
-
-      return leaves;
-    }
-    else
-    {
-      /*
-       * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
-       * leaves.addElement(node); }
-       */
-      findLeaves((SequenceNode) nd.left(), leaves);
-      findLeaves((SequenceNode) nd.right(), leaves);
-    }
-
-    return leaves;
-  }
-
-  /**
-   * Find the leaf node with a particular ycount
-   * 
-   * @param nd
-   *          initial point on tree to search from
-   * @param count
-   *          value to search for
-   * 
-   * @return null or the node with ycound=count
-   */
-  public Object findLeaf(SequenceNode nd, int count)
-  {
-    found = _findLeaf(nd, count);
-
-    return found;
-  }
-
-  /*
-   * #see findLeaf(SequenceNode node, count)
-   */
-  public Object _findLeaf(SequenceNode nd, int count)
-  {
-    if (nd == null)
-    {
-      return null;
-    }
-
-    if (nd.ycount == count)
-    {
-      found = nd.element();
-
-      return found;
-    }
-    else
-    {
-      _findLeaf((SequenceNode) nd.left(), count);
-      _findLeaf((SequenceNode) nd.right(), count);
-    }
-
-    return found;
-  }
-
-  /**
-   * printNode is mainly for debugging purposes.
-   * 
-   * @param nd
-   *          SequenceNode
-   */
-  public void printNode(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null))
-    {
-      System.out.println("Leaf = " + ((SequenceI) nd.element()).getName());
-      System.out.println("Dist " + nd.dist);
-      System.out.println("Boot " + nd.getBootstrap());
-    }
-    else
-    {
-      System.out.println("Dist " + nd.dist);
-      printNode((SequenceNode) nd.left());
-      printNode((SequenceNode) nd.right());
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  public void findMaxDist(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null))
-    {
-      float dist = nd.dist;
-
-      if (dist > maxDistValue)
-      {
-        maxdist = nd;
-        maxDistValue = dist;
-      }
-    }
-    else
-    {
-      findMaxDist((SequenceNode) nd.left());
-      findMaxDist((SequenceNode) nd.right());
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public Vector<SequenceNode> getGroups()
-  {
-    return groups;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public float getMaxHeight()
-  {
-    return maxheight;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   * @param threshold
-   *          DOCUMENT ME!
-   */
-  public void groupNodes(SequenceNode nd, float threshold)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if ((nd.height / maxheight) > threshold)
-    {
-      groups.addElement(nd);
-    }
-    else
-    {
-      groupNodes((SequenceNode) nd.left(), threshold);
-      groupNodes((SequenceNode) nd.right(), threshold);
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public float findHeight(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return maxheight;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null))
-    {
-      nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
-
-      if (nd.height > maxheight)
-      {
-        return nd.height;
-      }
-      else
-      {
-        return maxheight;
-      }
-    }
-    else
-    {
-      if (nd.parent() != null)
-      {
-        nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
-      }
-      else
-      {
-        maxheight = 0;
-        nd.height = (float) 0.0;
-      }
-
-      maxheight = findHeight((SequenceNode) (nd.left()));
-      maxheight = findHeight((SequenceNode) (nd.right()));
-    }
-
-    return maxheight;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceNode reRoot()
-  {
-    if (maxdist != null)
-    {
-      ycount = 0;
-
-      float tmpdist = maxdist.dist;
-
-      // New top
-      SequenceNode sn = new SequenceNode();
-      sn.setParent(null);
-
-      // New right hand of top
-      SequenceNode snr = (SequenceNode) maxdist.parent();
-      changeDirection(snr, maxdist);
-      System.out.println("Printing reversed tree");
-      printN(snr);
-      snr.dist = tmpdist / 2;
-      maxdist.dist = tmpdist / 2;
-
-      snr.setParent(sn);
-      maxdist.setParent(sn);
-
-      sn.setRight(snr);
-      sn.setLeft(maxdist);
-
-      top = sn;
-
-      ycount = 0;
-      reCount(top);
-      findHeight(top);
-    }
-
-    return top;
-  }
-
-  /**
-   * 
-   * @return true if original sequence data can be recovered
-   */
-  public boolean hasOriginalSequenceData()
-  {
-    return seqData != null;
-  }
-
-  /**
-   * Returns original alignment data used for calculation - or null where not
-   * available.
-   * 
-   * @return null or cut'n'pasteable alignment
-   */
-  public String printOriginalSequenceData(char gapChar)
-  {
-    if (seqData == null)
-    {
-      return null;
-    }
-
-    StringBuffer sb = new StringBuffer();
-    String[] seqdatas = seqData.getSequenceStrings(gapChar);
-    for (int i = 0; i < seqdatas.length; i++)
-    {
-      sb.append(new jalview.util.Format("%-" + 15 + "s").form(sequence[i]
-              .getName()));
-      sb.append(" " + seqdatas[i] + "\n");
-    }
-    return sb.toString();
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  public void printN(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if ((nd.left() != null) && (nd.right() != null))
-    {
-      printN((SequenceNode) nd.left());
-      printN((SequenceNode) nd.right());
-    }
-    else
-    {
-      System.out.println(" name = " + ((SequenceI) nd.element()).getName());
-    }
-
-    System.out.println(" dist = " + nd.dist + " " + nd.count + " "
-            + nd.height);
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  public void reCount(SequenceNode nd)
-  {
-    ycount = 0;
-    _lycount = 0;
-    // _lylimit = this.node.size();
-    _reCount(nd);
-  }
-
-  private long _lycount = 0, _lylimit = 0;
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  public void _reCount(SequenceNode nd)
-  {
-    // if (_lycount<_lylimit)
-    // {
-    // System.err.println("Warning: depth of _recount greater than number of nodes.");
-    // }
-    if (nd == null)
-    {
-      return;
-    }
-    _lycount++;
-
-    if ((nd.left() != null) && (nd.right() != null))
-    {
-
-      _reCount((SequenceNode) nd.left());
-      _reCount((SequenceNode) nd.right());
-
-      SequenceNode l = (SequenceNode) nd.left();
-      SequenceNode r = (SequenceNode) nd.right();
-
-      nd.count = l.count + r.count;
-      nd.ycount = (l.ycount + r.ycount) / 2;
-    }
-    else
-    {
-      nd.count = 1;
-      nd.ycount = ycount++;
-    }
-    _lycount--;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  public void swapNodes(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    SequenceNode tmp = (SequenceNode) nd.left();
-
-    nd.setLeft(nd.right());
-    nd.setRight(tmp);
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   * @param dir
-   *          DOCUMENT ME!
-   */
-  public void changeDirection(SequenceNode nd, SequenceNode dir)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if (nd.parent() != top)
-    {
-      changeDirection((SequenceNode) nd.parent(), nd);
-
-      SequenceNode tmp = (SequenceNode) nd.parent();
-
-      if (dir == nd.left())
-      {
-        nd.setParent(dir);
-        nd.setLeft(tmp);
-      }
-      else if (dir == nd.right())
-      {
-        nd.setParent(dir);
-        nd.setRight(tmp);
-      }
-    }
-    else
-    {
-      if (dir == nd.left())
-      {
-        nd.setParent(nd.left());
-
-        if (top.left() == nd)
-        {
-          nd.setRight(top.right());
-        }
-        else
-        {
-          nd.setRight(top.left());
-        }
-      }
-      else
-      {
-        nd.setParent(nd.right());
-
-        if (top.left() == nd)
-        {
-          nd.setLeft(top.right());
-        }
-        else
-        {
-          nd.setLeft(top.left());
-        }
-      }
-    }
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceNode getMaxDist()
-  {
-    return maxdist;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceNode getTopNode()
-  {
-    return top;
-  }
-
-  /**
-   * 
-   * @return true if tree has real distances
-   */
-  public boolean isHasDistances()
-  {
-    return hasDistances;
-  }
-
-  /**
-   * 
-   * @return true if tree has real bootstrap values
-   */
-  public boolean isHasBootstrap()
-  {
-    return hasBootstrap;
-  }
-
-  public boolean isHasRootDistance()
-  {
-    return hasRootDistance;
-  }
-
-  /**
-   * apply the given transform to all the nodes in the tree.
-   * 
-   * @param nodeTransformI
-   */
-  public void applyToNodes(NodeTransformI nodeTransformI)
-  {
-    for (Enumeration<SequenceNode> nodes = node.elements(); nodes
-            .hasMoreElements(); nodeTransformI.transform(nodes
-            .nextElement()))
-    {
-      ;
-    }
-  }
-}
-
-/**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision$
- */
-class Cluster
-{
-  int[] value;
-
-  /**
-   * Creates a new Cluster object.
-   * 
-   * @param value
-   *          DOCUMENT ME!
-   */
-  public Cluster(int[] value)
-  {
-    this.value = value;
   }
 }
index eaea7bf..3ec7995 100755 (executable)
  */
 package jalview.analysis;
 
-import jalview.datamodel.BinarySequence;
-import jalview.datamodel.BinarySequence.InvalidSequenceTypeException;
-import jalview.math.Matrix;
-import jalview.schemes.ResidueProperties;
-import jalview.schemes.ScoreMatrix;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.math.MatrixI;
 
 import java.io.PrintStream;
 
 /**
  * Performs Principal Component Analysis on given sequences
- * 
- * @author $author$
- * @version $Revision$
  */
 public class PCA implements Runnable
 {
-  Matrix m;
-
-  Matrix symm;
-
-  Matrix m2;
+  MatrixI symm;
 
   double[] eigenvalue;
 
-  Matrix eigenvector;
+  MatrixI eigenvector;
 
-  StringBuffer details = new StringBuffer();
+  StringBuilder details = new StringBuilder(1024);
 
-  /**
-   * Creates a new PCA object. By default, uses blosum62 matrix to generate
-   * sequence similarity matrices
-   * 
-   * @param s
-   *          Set of amino acid sequences to perform PCA on
-   */
-  public PCA(String[] s)
-  {
-    this(s, false);
-  }
+  final private AlignmentView seqs;
 
-  /**
-   * Creates a new PCA object. By default, uses blosum62 matrix to generate
-   * sequence similarity matrices
-   * 
-   * @param s
-   *          Set of sequences to perform PCA on
-   * @param nucleotides
-   *          if true, uses standard DNA/RNA matrix for sequence similarity
-   *          calculation.
-   */
-  public PCA(String[] s, boolean nucleotides)
-  {
-    this(s, nucleotides, null);
-  }
+  private ScoreModelI scoreModel;
+  
+  private SimilarityParamsI similarityParams;
 
-  public PCA(String[] s, boolean nucleotides, String s_m)
+  public PCA(AlignmentView s, ScoreModelI sm, SimilarityParamsI options)
   {
-
-    BinarySequence[] bs = new BinarySequence[s.length];
-    int ii = 0;
-
-    while ((ii < s.length) && (s[ii] != null))
-    {
-      bs[ii] = new BinarySequence(s[ii], nucleotides);
-      bs[ii].encode();
-      ii++;
-    }
-
-    BinarySequence[] bs2 = new BinarySequence[s.length];
-    ii = 0;
-    ScoreMatrix smtrx = null;
-    String sm = s_m;
-    if (sm != null)
-    {
-      smtrx = ResidueProperties.getScoreMatrix(sm);
-    }
-    if (smtrx == null)
-    {
-      // either we were given a non-existent score matrix or a scoremodel that
-      // isn't based on a pairwise symbol score matrix
-      smtrx = ResidueProperties.getScoreMatrix(sm = (nucleotides ? "DNA"
-              : "BLOSUM62"));
-    }
-    details.append("PCA calculation using " + sm
+    this.seqs = s;
+    this.similarityParams = options;
+    this.scoreModel = sm;
+    
+    details.append("PCA calculation using " + sm.getName()
             + " sequence similarity matrix\n========\n\n");
-    while ((ii < s.length) && (s[ii] != null))
-    {
-      bs2[ii] = new BinarySequence(s[ii], nucleotides);
-      if (smtrx != null)
-      {
-        try
-        {
-          bs2[ii].matrixEncode(smtrx);
-        } catch (InvalidSequenceTypeException x)
-        {
-          details.append("Unexpected mismatch of sequence type and score matrix. Calculation will not be valid!\n\n");
-        }
-      }
-      ii++;
-    }
-
-    // System.out.println("Created binary encoding");
-    // printMemory(rt);
-    int count = 0;
-
-    while ((count < bs.length) && (bs[count] != null))
-    {
-      count++;
-    }
-
-    double[][] seqmat = new double[count][bs[0].getDBinary().length];
-    double[][] seqmat2 = new double[count][bs2[0].getDBinary().length];
-    int i = 0;
-
-    while (i < count)
-    {
-      seqmat[i] = bs[i].getDBinary();
-      seqmat2[i] = bs2[i].getDBinary();
-      i++;
-    }
-
-    // System.out.println("Created array");
-    // printMemory(rt);
-    // System.out.println(" --- Original matrix ---- ");
-    m = new Matrix(seqmat);
-    m2 = new Matrix(seqmat2);
-
-  }
-
-  /**
-   * Returns the matrix used in PCA calculation
-   * 
-   * @return java.math.Matrix object
-   */
-
-  public Matrix getM()
-  {
-    return m;
   }
 
   /**
@@ -170,7 +66,7 @@ public class PCA implements Runnable
    */
   public double getEigenvalue(int i)
   {
-    return eigenvector.d[i];
+    return eigenvector.getD()[i];
   }
 
   /**
@@ -189,9 +85,9 @@ public class PCA implements Runnable
    */
   public float[][] getComponents(int l, int n, int mm, float factor)
   {
-    float[][] out = new float[m.rows][3];
+    float[][] out = new float[getHeight()][3];
 
-    for (int i = 0; i < m.rows; i++)
+    for (int i = 0; i < getHeight(); i++)
     {
       out[i][0] = (float) component(i, l) * factor;
       out[i][1] = (float) component(i, n) * factor;
@@ -212,9 +108,9 @@ public class PCA implements Runnable
   public double[] component(int n)
   {
     // n = index of eigenvector
-    double[] out = new double[m.rows];
+    double[] out = new double[getHeight()];
 
-    for (int i = 0; i < m.rows; i++)
+    for (int i = 0; i < out.length; i++)
     {
       out[i] = component(i, n);
     }
@@ -236,12 +132,12 @@ public class PCA implements Runnable
   {
     double out = 0.0;
 
-    for (int i = 0; i < symm.cols; i++)
+    for (int i = 0; i < symm.width(); i++)
     {
-      out += (symm.value[row][i] * eigenvector.value[i][n]);
+      out += (symm.getValue(row, i) * eigenvector.getValue(i, n));
     }
 
-    return out / eigenvector.d[n];
+    return out / eigenvector.getD()[n];
   }
 
   public String getDetails()
@@ -270,25 +166,13 @@ public class PCA implements Runnable
       }
     };
 
+    // long now = System.currentTimeMillis();
     try
     {
-      details.append("PCA Calculation Mode is "
-              + (jvCalcMode ? "Jalview variant" : "Original SeqSpace")
-              + "\n");
-      Matrix mt = m.transpose();
+      eigenvector = scoreModel.findSimilarities(seqs, similarityParams);
 
       details.append(" --- OrigT * Orig ---- \n");
-      if (!jvCalcMode)
-      {
-        eigenvector = mt.preMultiply(m); // standard seqspace comparison matrix
-      }
-      else
-      {
-        eigenvector = mt.preMultiply(m2); // jalview variation on seqsmace
-                                          // method
-      }
-
-      eigenvector.print(ps);
+      eigenvector.print(ps, "%8.2f");
 
       symm = eigenvector.copy();
 
@@ -296,10 +180,10 @@ public class PCA implements Runnable
 
       details.append(" ---Tridiag transform matrix ---\n");
       details.append(" --- D vector ---\n");
-      eigenvector.printD(ps);
+      eigenvector.printD(ps, "%15.4e");
       ps.println();
       details.append("--- E vector ---\n");
-      eigenvector.printE(ps);
+      eigenvector.printE(ps, "%15.4e");
       ps.println();
 
       // Now produce the diagonalization matrix
@@ -313,9 +197,9 @@ public class PCA implements Runnable
     }
 
     details.append(" --- New diagonalization matrix ---\n");
-    eigenvector.print(ps);
+    eigenvector.print(ps, "%8.2f");
     details.append(" --- Eigenvalues ---\n");
-    eigenvector.printD(ps);
+    eigenvector.printD(ps, "%15.4e");
     ps.println();
     /*
      * for (int seq=0;seq<symm.rows;seq++) { ps.print("\"Seq"+seq+"\""); for
@@ -323,12 +207,19 @@ public class PCA implements Runnable
      * 
      * ps.print(","+component(seq, ev)); } ps.println(); }
      */
+    // System.out.println(("PCA.run() took "
+    // + (System.currentTimeMillis() - now) + "ms"));
   }
 
-  boolean jvCalcMode = true;
-
-  public void setJvCalcMode(boolean calcMode)
+  /**
+   * Answers the N dimensions of the NxN PCA matrix. This is the number of
+   * sequences involved in the pairwise score calculation.
+   * 
+   * @return
+   */
+  public int getHeight()
   {
-    this.jvCalcMode = calcMode;
+    // TODO can any of seqs[] be null?
+    return seqs.getSequences().length;
   }
 }
diff --git a/src/jalview/analysis/TreeBuilder.java b/src/jalview/analysis/TreeBuilder.java
new file mode 100644 (file)
index 0000000..effef9a
--- /dev/null
@@ -0,0 +1,460 @@
+package jalview.analysis;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.CigarArray;
+import jalview.datamodel.SeqCigar;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.math.MatrixI;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.util.BitSet;
+import java.util.Vector;
+
+public abstract class TreeBuilder
+{
+  public static final String AVERAGE_DISTANCE = "AV";
+
+  public static final String NEIGHBOUR_JOINING = "NJ";
+
+  protected Vector<BitSet> clusters;
+
+  protected SequenceI[] sequences;
+
+  public AlignmentView seqData;
+
+  protected BitSet done;
+
+  protected int noseqs;
+
+  int noClus;
+
+  protected MatrixI distances;
+
+  protected int mini;
+
+  protected int minj;
+
+  protected double ri;
+
+  protected double rj;
+
+  SequenceNode maxdist;
+
+  SequenceNode top;
+
+  double maxDistValue;
+
+  double maxheight;
+
+  int ycount;
+
+  Vector<SequenceNode> node;
+
+  private AlignmentView seqStrings;
+
+  /**
+   * Constructor
+   * 
+   * @param av
+   * @param sm
+   * @param scoreParameters
+   */
+  public TreeBuilder(AlignmentViewport av, ScoreModelI sm,
+          SimilarityParamsI scoreParameters)
+  {
+    int start, end;
+    boolean selview = av.getSelectionGroup() != null
+            && av.getSelectionGroup().getSize() > 1;
+    seqStrings = av.getAlignmentView(selview);
+    if (!selview)
+    {
+      start = 0;
+      end = av.getAlignment().getWidth();
+      this.sequences = av.getAlignment().getSequencesArray();
+    }
+    else
+    {
+      start = av.getSelectionGroup().getStartRes();
+      end = av.getSelectionGroup().getEndRes() + 1;
+      this.sequences = av.getSelectionGroup().getSequencesInOrder(
+              av.getAlignment());
+    }
+
+    init(seqStrings, start, end);
+
+    computeTree(sm, scoreParameters);
+  }
+
+  public SequenceI[] getSequences()
+  {
+    return sequences;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  double findHeight(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return maxheight;
+    }
+  
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+  
+      if (nd.height > maxheight)
+      {
+        return nd.height;
+      }
+      else
+      {
+        return maxheight;
+      }
+    }
+    else
+    {
+      if (nd.parent() != null)
+      {
+        nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+      }
+      else
+      {
+        maxheight = 0;
+        nd.height = (float) 0.0;
+      }
+  
+      maxheight = findHeight((SequenceNode) (nd.left()));
+      maxheight = findHeight((SequenceNode) (nd.right()));
+    }
+  
+    return maxheight;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  void reCount(SequenceNode nd)
+  {
+    ycount = 0;
+    // _lycount = 0;
+    // _lylimit = this.node.size();
+    _reCount(nd);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  void _reCount(SequenceNode nd)
+  {
+    // if (_lycount<_lylimit)
+    // {
+    // System.err.println("Warning: depth of _recount greater than number of nodes.");
+    // }
+    if (nd == null)
+    {
+      return;
+    }
+    // _lycount++;
+  
+    if ((nd.left() != null) && (nd.right() != null))
+    {
+  
+      _reCount((SequenceNode) nd.left());
+      _reCount((SequenceNode) nd.right());
+  
+      SequenceNode l = (SequenceNode) nd.left();
+      SequenceNode r = (SequenceNode) nd.right();
+  
+      nd.count = l.count + r.count;
+      nd.ycount = (l.ycount + r.ycount) / 2;
+    }
+    else
+    {
+      nd.count = 1;
+      nd.ycount = ycount++;
+    }
+    // _lycount--;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public SequenceNode getTopNode()
+  {
+    return top;
+  }
+
+  /**
+   * 
+   * @return true if tree has real distances
+   */
+  public boolean hasDistances()
+  {
+    return true;
+  }
+
+  /**
+   * 
+   * @return true if tree has real bootstrap values
+   */
+  public boolean hasBootstrap()
+  {
+    return false;
+  }
+
+  public boolean hasRootDistance()
+  {
+    return true;
+  }
+
+  /**
+   * Form clusters by grouping sub-clusters, starting from one sequence per
+   * cluster, and finishing when only two clusters remain
+   */
+  void cluster()
+  {
+    while (noClus > 2)
+    {
+      findMinDistance();
+  
+      joinClusters(mini, minj);
+  
+      noClus--;
+    }
+  
+    int rightChild = done.nextClearBit(0);
+    int leftChild = done.nextClearBit(rightChild + 1);
+  
+    joinClusters(leftChild, rightChild);
+    top = (node.elementAt(leftChild));
+  
+    reCount(top);
+    findHeight(top);
+    findMaxDist(top);
+  }
+
+  /**
+   * Returns the minimum distance between two clusters, and also sets the
+   * indices of the clusters in fields mini and minj
+   * 
+   * @return
+   */
+  protected abstract double findMinDistance();
+
+  /**
+   * Calculates the tree using the given score model and parameters, and the
+   * configured tree type
+   * <p>
+   * If the score model computes pairwise distance scores, then these are used
+   * directly to derive the tree
+   * <p>
+   * If the score model computes similarity scores, then the range of the scores
+   * is reversed to give a distance measure, and this is used to derive the tree
+   * 
+   * @param sm
+   * @param scoreOptions
+   */
+  protected void computeTree(ScoreModelI sm, SimilarityParamsI scoreOptions)
+  {
+    distances = sm.findDistances(seqData, scoreOptions);
+  
+    makeLeaves();
+  
+    noClus = clusters.size();
+  
+    cluster();
+  }
+
+  /**
+   * Finds the node, at or below the given node, with the maximum distance, and
+   * saves the node and the distance value
+   * 
+   * @param nd
+   */
+  void findMaxDist(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+  
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      double dist = nd.dist;
+  
+      if (dist > maxDistValue)
+      {
+        maxdist = nd;
+        maxDistValue = dist;
+      }
+    }
+    else
+    {
+      findMaxDist((SequenceNode) nd.left());
+      findMaxDist((SequenceNode) nd.right());
+    }
+  }
+
+  /**
+   * Calculates and returns r, whatever that is
+   * 
+   * @param i
+   * @param j
+   * 
+   * @return
+   */
+  protected double findr(int i, int j)
+  {
+    double tmp = 1;
+  
+    for (int k = 0; k < noseqs; k++)
+    {
+      if ((k != i) && (k != j) && (!done.get(k)))
+      {
+        tmp = tmp + distances.getValue(i, k);
+      }
+    }
+  
+    if (noClus > 2)
+    {
+      tmp = tmp / (noClus - 2);
+    }
+  
+    return tmp;
+  }
+
+  protected void init(AlignmentView seqView, int start, int end)
+  {
+    this.node = new Vector<SequenceNode>();
+    if (seqView != null)
+    {
+      this.seqData = seqView;
+    }
+    else
+    {
+      SeqCigar[] seqs = new SeqCigar[sequences.length];
+      for (int i = 0; i < sequences.length; i++)
+      {
+        seqs[i] = new SeqCigar(sequences[i], start, end);
+      }
+      CigarArray sdata = new CigarArray(seqs);
+      sdata.addOperation(CigarArray.M, end - start + 1);
+      this.seqData = new AlignmentView(sdata, start);
+    }
+  
+    /*
+     * count the non-null sequences
+     */
+    noseqs = 0;
+  
+    done = new BitSet();
+  
+    for (SequenceI seq : sequences)
+    {
+      if (seq != null)
+      {
+        noseqs++;
+      }
+    }
+  }
+
+  /**
+   * Merges cluster(j) to cluster(i) and recalculates cluster and node distances
+   * 
+   * @param i
+   * @param j
+   */
+  void joinClusters(final int i, final int j)
+  {
+    double dist = distances.getValue(i, j);
+  
+    ri = findr(i, j);
+    rj = findr(j, i);
+  
+    findClusterDistance(i, j);
+  
+    SequenceNode sn = new SequenceNode();
+  
+    sn.setLeft((node.elementAt(i)));
+    sn.setRight((node.elementAt(j)));
+  
+    SequenceNode tmpi = (node.elementAt(i));
+    SequenceNode tmpj = (node.elementAt(j));
+  
+    findNewDistances(tmpi, tmpj, dist);
+  
+    tmpi.setParent(sn);
+    tmpj.setParent(sn);
+  
+    node.setElementAt(sn, i);
+  
+    /*
+     * move the members of cluster(j) to cluster(i)
+     * and mark cluster j as out of the game
+     */
+    clusters.get(i).or(clusters.get(j));
+    clusters.get(j).clear();
+    done.set(j);
+  }
+
+  /*
+   * Computes and stores new distances for nodei and nodej, given the previous
+   * distance between them
+   */
+  protected abstract void findNewDistances(SequenceNode nodei,
+          SequenceNode nodej, double previousDistance);
+
+  /**
+   * Calculates and saves the distance between the combination of cluster(i) and
+   * cluster(j) and all other clusters. The form of the calculation depends on
+   * the tree clustering method being used.
+   * 
+   * @param i
+   * @param j
+   */
+  protected abstract void findClusterDistance(int i, int j);
+
+  /**
+   * Start by making a cluster for each individual sequence
+   */
+  void makeLeaves()
+  {
+    clusters = new Vector<BitSet>();
+  
+    for (int i = 0; i < noseqs; i++)
+    {
+      SequenceNode sn = new SequenceNode();
+  
+      sn.setElement(sequences[i]);
+      sn.setName(sequences[i].getName());
+      node.addElement(sn);
+      BitSet bs = new BitSet();
+      bs.set(i);
+      clusters.addElement(bs);
+    }
+  }
+
+  public AlignmentView getOriginalData()
+  {
+    return seqStrings;
+  }
+
+}
diff --git a/src/jalview/analysis/TreeModel.java b/src/jalview/analysis/TreeModel.java
new file mode 100644 (file)
index 0000000..5a41802
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * 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.analysis;
+
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.NodeTransformI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.io.NewickFile;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A model of a tree, either computed by Jalview or loaded from a file or other
+ * resource or service
+ */
+public class TreeModel
+{
+
+  SequenceI[] sequences;
+
+  /* 
+   * SequenceData is a string representation of what the user
+   * sees. The display may contain hidden columns.
+   */
+  private AlignmentView seqData;
+
+  int noseqs;
+
+  SequenceNode top;
+
+  double maxDistValue;
+
+  double maxheight;
+
+  int ycount;
+
+  Vector<SequenceNode> node;
+
+  boolean hasDistances = true; // normal case for jalview trees
+
+  boolean hasBootstrap = false; // normal case for jalview trees
+
+  private boolean hasRootDistance = true;
+
+  /**
+   * Create a new TreeModel object with leaves associated with sequences in
+   * seqs, and (optionally) original alignment data represented by Cigar strings
+   * 
+   * @param seqs
+   *          SequenceI[]
+   * @param odata
+   *          Cigar[]
+   * @param treefile
+   *          NewickFile
+   */
+  public TreeModel(SequenceI[] seqs, AlignmentView odata, NewickFile treefile)
+  {
+    this(seqs, treefile.getTree(), treefile.HasDistances(), treefile
+            .HasBootstrap(), treefile.HasRootDistance());
+    seqData = odata;
+
+    associateLeavesToSequences(seqs);
+  }
+
+  /**
+   * Constructor given a calculated tree
+   * 
+   * @param tree
+   */
+  public TreeModel(TreeBuilder tree)
+  {
+    this(tree.getSequences(), tree.getTopNode(), tree.hasDistances(), tree
+            .hasBootstrap(), tree.hasRootDistance());
+    seqData = tree.getOriginalData();
+  }
+
+  /**
+   * Constructor given sequences, root node and tree property flags
+   * 
+   * @param seqs
+   * @param root
+   * @param hasDist
+   * @param hasBoot
+   * @param hasRootDist
+   */
+  public TreeModel(SequenceI[] seqs, SequenceNode root, boolean hasDist,
+          boolean hasBoot, boolean hasRootDist)
+  {
+    this.sequences = seqs;
+    top = root;
+
+    hasDistances = hasDist;
+    hasBootstrap = hasBoot;
+    hasRootDistance = hasRootDist;
+
+    maxheight = findHeight(top);
+  }
+
+  /**
+   * @param seqs
+   */
+  public void associateLeavesToSequences(SequenceI[] seqs)
+  {
+    SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs);
+
+    Vector<SequenceNode> leaves = findLeaves(top);
+
+    int i = 0;
+    int namesleft = seqs.length;
+
+    SequenceNode j;
+    SequenceI nam;
+    String realnam;
+    Vector<SequenceI> one2many = new Vector<SequenceI>();
+    // int countOne2Many = 0;
+    while (i < leaves.size())
+    {
+      j = leaves.elementAt(i++);
+      realnam = j.getName();
+      nam = null;
+
+      if (namesleft > -1)
+      {
+        nam = algnIds.findIdMatch(realnam);
+      }
+
+      if (nam != null)
+      {
+        j.setElement(nam);
+        if (one2many.contains(nam))
+        {
+          // countOne2Many++;
+          // if (jalview.bin.Cache.log.isDebugEnabled())
+          // jalview.bin.Cache.log.debug("One 2 many relationship for
+          // "+nam.getName());
+        }
+        else
+        {
+          one2many.addElement(nam);
+          namesleft--;
+        }
+      }
+      else
+      {
+        j.setElement(new Sequence(realnam, "THISISAPLACEHLDER"));
+        j.setPlaceholder(true);
+      }
+    }
+    // if (jalview.bin.Cache.log.isDebugEnabled() && countOne2Many>0) {
+    // jalview.bin.Cache.log.debug("There were "+countOne2Many+" alignment
+    // sequence ids (out of "+one2many.size()+" unique ids) linked to two or
+    // more leaves.");
+    // }
+    // one2many.clear();
+  }
+
+  /**
+   * Generate a string representation of the Tree
+   * 
+   * @return Newick File with all tree data available
+   */
+  public String print()
+  {
+    NewickFile fout = new NewickFile(getTopNode());
+
+    return fout.print(hasBootstrap(), hasDistances(),
+            hasRootDistance()); // output all data available for tree
+  }
+
+  /**
+   * 
+   * used when the alignment associated to a tree has changed.
+   * 
+   * @param list
+   *          Sequence set to be associated with tree nodes
+   */
+  public void updatePlaceHolders(List<SequenceI> list)
+  {
+    Vector<SequenceNode> leaves = findLeaves(top);
+
+    int sz = leaves.size();
+    SequenceIdMatcher seqmatcher = null;
+    int i = 0;
+
+    while (i < sz)
+    {
+      SequenceNode leaf = leaves.elementAt(i++);
+
+      if (list.contains(leaf.element()))
+      {
+        leaf.setPlaceholder(false);
+      }
+      else
+      {
+        if (seqmatcher == null)
+        {
+          // Only create this the first time we need it
+          SequenceI[] seqs = new SequenceI[list.size()];
+
+          for (int j = 0; j < seqs.length; j++)
+          {
+            seqs[j] = list.get(j);
+          }
+
+          seqmatcher = new SequenceIdMatcher(seqs);
+        }
+
+        SequenceI nam = seqmatcher.findIdMatch(leaf.getName());
+
+        if (nam != null)
+        {
+          if (!leaf.isPlaceholder())
+          {
+            // remapping the node to a new sequenceI - should remove any refs to
+            // old one.
+            // TODO - make many sequenceI to one leaf mappings possible!
+            // (JBPNote)
+          }
+          leaf.setPlaceholder(false);
+          leaf.setElement(nam);
+        }
+        else
+        {
+          if (!leaf.isPlaceholder())
+          {
+            // Construct a new placeholder sequence object for this leaf
+            leaf.setElement(new Sequence(leaf.getName(),
+                    "THISISAPLACEHLDER"));
+          }
+          leaf.setPlaceholder(true);
+
+        }
+      }
+    }
+  }
+
+  /**
+   * rename any nodes according to their associated sequence. This will modify
+   * the tree's metadata! (ie the original NewickFile or newly generated
+   * BinaryTree's label data)
+   */
+  public void renameAssociatedNodes()
+  {
+    applyToNodes(new NodeTransformI()
+    {
+
+      @Override
+      public void transform(BinaryNode nd)
+      {
+        Object el = nd.element();
+        if (el != null && el instanceof SequenceI)
+        {
+          nd.setName(((SequenceI) el).getName());
+        }
+      }
+    });
+  }
+
+  /**
+   * Search for leaf nodes below (or at) the given node
+   * 
+   * @param nd
+   *          root node to search from
+   * 
+   * @return
+   */
+  public Vector<SequenceNode> findLeaves(SequenceNode nd)
+  {
+    Vector<SequenceNode> leaves = new Vector<SequenceNode>();
+    findLeaves(nd, leaves);
+    return leaves;
+  }
+
+  /**
+   * Search for leaf nodes.
+   * 
+   * @param nd
+   *          root node to search from
+   * @param leaves
+   *          Vector of leaves to add leaf node objects too.
+   * 
+   * @return Vector of leaf nodes on binary tree
+   */
+  Vector<SequenceNode> findLeaves(SequenceNode nd,
+          Vector<SequenceNode> leaves)
+  {
+    if (nd == null)
+    {
+      return leaves;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null)) // Interior node
+    // detection
+    {
+      leaves.addElement(nd);
+
+      return leaves;
+    }
+    else
+    {
+      /*
+       * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
+       * leaves.addElement(node); }
+       */
+      findLeaves((SequenceNode) nd.left(), leaves);
+      findLeaves((SequenceNode) nd.right(), leaves);
+    }
+
+    return leaves;
+  }
+
+  /**
+   * printNode is mainly for debugging purposes.
+   * 
+   * @param nd
+   *          SequenceNode
+   */
+  void printNode(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      System.out.println("Leaf = " + ((SequenceI) nd.element()).getName());
+      System.out.println("Dist " + nd.dist);
+      System.out.println("Boot " + nd.getBootstrap());
+    }
+    else
+    {
+      System.out.println("Dist " + nd.dist);
+      printNode((SequenceNode) nd.left());
+      printNode((SequenceNode) nd.right());
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public double getMaxHeight()
+  {
+    return maxheight;
+  }
+
+  /**
+   * Makes a list of groups, where each group is represented by a node whose
+   * height (distance from the root node), as a fraction of the height of the
+   * whole tree, is greater than the given threshold. This corresponds to
+   * selecting the nodes immediately to the right of a vertical line
+   * partitioning the tree (if the tree is drawn with root to the left). Each
+   * such node represents a group that contains all of the sequences linked to
+   * the child leaf nodes.
+   * 
+   * @param threshold
+   * @see #getGroups()
+   */
+  public List<SequenceNode> groupNodes(float threshold)
+  {
+    List<SequenceNode> groups = new ArrayList<SequenceNode>();
+    _groupNodes(groups, getTopNode(), threshold);
+    return groups;
+  }
+
+  protected void _groupNodes(List<SequenceNode> groups, SequenceNode nd,
+          float threshold)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if ((nd.height / maxheight) > threshold)
+    {
+      groups.add(nd);
+    }
+    else
+    {
+      _groupNodes(groups, (SequenceNode) nd.left(), threshold);
+      _groupNodes(groups, (SequenceNode) nd.right(), threshold);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public double findHeight(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return maxheight;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+
+      if (nd.height > maxheight)
+      {
+        return nd.height;
+      }
+      else
+      {
+        return maxheight;
+      }
+    }
+    else
+    {
+      if (nd.parent() != null)
+      {
+        nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+      }
+      else
+      {
+        maxheight = 0;
+        nd.height = (float) 0.0;
+      }
+
+      maxheight = findHeight((SequenceNode) (nd.left()));
+      maxheight = findHeight((SequenceNode) (nd.right()));
+    }
+
+    return maxheight;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  void printN(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if ((nd.left() != null) && (nd.right() != null))
+    {
+      printN((SequenceNode) nd.left());
+      printN((SequenceNode) nd.right());
+    }
+    else
+    {
+      System.out.println(" name = " + ((SequenceI) nd.element()).getName());
+    }
+
+    System.out.println(" dist = " + nd.dist + " " + nd.count + " "
+            + nd.height);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  public void reCount(SequenceNode nd)
+  {
+    ycount = 0;
+    // _lycount = 0;
+    // _lylimit = this.node.size();
+    _reCount(nd);
+  }
+
+  // private long _lycount = 0, _lylimit = 0;
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  void _reCount(SequenceNode nd)
+  {
+    // if (_lycount<_lylimit)
+    // {
+    // System.err.println("Warning: depth of _recount greater than number of nodes.");
+    // }
+    if (nd == null)
+    {
+      return;
+    }
+    // _lycount++;
+
+    if ((nd.left() != null) && (nd.right() != null))
+    {
+
+      _reCount((SequenceNode) nd.left());
+      _reCount((SequenceNode) nd.right());
+
+      SequenceNode l = (SequenceNode) nd.left();
+      SequenceNode r = (SequenceNode) nd.right();
+
+      nd.count = l.count + r.count;
+      nd.ycount = (l.ycount + r.ycount) / 2;
+    }
+    else
+    {
+      nd.count = 1;
+      nd.ycount = ycount++;
+    }
+    // _lycount--;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  public void swapNodes(SequenceNode nd)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    SequenceNode tmp = (SequenceNode) nd.left();
+
+    nd.setLeft(nd.right());
+    nd.setRight(tmp);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   * @param dir
+   *          DOCUMENT ME!
+   */
+  void changeDirection(SequenceNode nd, SequenceNode dir)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if (nd.parent() != top)
+    {
+      changeDirection((SequenceNode) nd.parent(), nd);
+
+      SequenceNode tmp = (SequenceNode) nd.parent();
+
+      if (dir == nd.left())
+      {
+        nd.setParent(dir);
+        nd.setLeft(tmp);
+      }
+      else if (dir == nd.right())
+      {
+        nd.setParent(dir);
+        nd.setRight(tmp);
+      }
+    }
+    else
+    {
+      if (dir == nd.left())
+      {
+        nd.setParent(nd.left());
+
+        if (top.left() == nd)
+        {
+          nd.setRight(top.right());
+        }
+        else
+        {
+          nd.setRight(top.left());
+        }
+      }
+      else
+      {
+        nd.setParent(nd.right());
+
+        if (top.left() == nd)
+        {
+          nd.setLeft(top.right());
+        }
+        else
+        {
+          nd.setLeft(top.left());
+        }
+      }
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public SequenceNode getTopNode()
+  {
+    return top;
+  }
+
+  /**
+   * 
+   * @return true if tree has real distances
+   */
+  public boolean hasDistances()
+  {
+    return hasDistances;
+  }
+
+  /**
+   * 
+   * @return true if tree has real bootstrap values
+   */
+  public boolean hasBootstrap()
+  {
+    return hasBootstrap;
+  }
+
+  public boolean hasRootDistance()
+  {
+    return hasRootDistance;
+  }
+
+  /**
+   * apply the given transform to all the nodes in the tree.
+   * 
+   * @param nodeTransformI
+   */
+  public void applyToNodes(NodeTransformI nodeTransformI)
+  {
+    for (Enumeration<SequenceNode> nodes = node.elements(); nodes
+            .hasMoreElements(); nodeTransformI.transform(nodes
+            .nextElement()))
+    {
+      ;
+    }
+  }
+
+  public AlignmentView getOriginalData()
+  {
+    return seqData;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/DistanceScoreModel.java b/src/jalview/analysis/scoremodels/DistanceScoreModel.java
new file mode 100644 (file)
index 0000000..0dd7617
--- /dev/null
@@ -0,0 +1,40 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.math.MatrixI;
+
+public abstract class DistanceScoreModel implements ScoreModelI
+{
+  /**
+   * A similarity score is calculated by first computing a distance score, and
+   * then reversing the min-max range of the score values
+   */
+  @Override
+  public MatrixI findSimilarities(AlignmentView seqData,
+          SimilarityParamsI options)
+  {
+    MatrixI distances = findDistances(seqData, options);
+
+    MatrixI similarities = distanceToSimilarity(distances);
+
+    return similarities;
+  }
+
+  /**
+   * Converts distance scores to similarity scores, by reversing the range of
+   * score values so that max becomes min and vice versa. The input matrix is
+   * not modified.
+   * 
+   * @param distances
+   */
+  public static MatrixI distanceToSimilarity(MatrixI distances)
+  {
+    MatrixI similarities = distances.copy();
+
+    similarities.reverseRange(false);
+
+    return similarities;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/FeatureDistanceModel.java b/src/jalview/analysis/scoremodels/FeatureDistanceModel.java
new file mode 100644 (file)
index 0000000..056ecdb
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * 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.analysis.scoremodels;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SeqCigar;
+import jalview.datamodel.SequenceFeature;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
+import jalview.util.SetUtils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class FeatureDistanceModel extends DistanceScoreModel
+{
+  private static final String NAME = "Sequence Feature Similarity";
+
+  private String description;
+
+  FeatureRenderer fr;
+
+  /**
+   * Constructor
+   */
+  public FeatureDistanceModel()
+  {
+  }
+
+  @Override
+  public ScoreModelI getInstance(AlignmentViewPanel view)
+  {
+    FeatureDistanceModel instance;
+    try
+    {
+      instance = this.getClass().newInstance();
+      instance.configureFromAlignmentView(view);
+      return instance;
+    } catch (InstantiationException | IllegalAccessException e)
+    {
+      System.err.println("Error in " + getClass().getName()
+              + ".getInstance(): " + e.getMessage());
+      return null;
+    }
+  }
+
+  boolean configureFromAlignmentView(AlignmentViewPanel view)
+
+  {
+    fr = view.cloneFeatureRenderer();
+    return true;
+  }
+
+  /**
+   * Calculates a distance measure [i][j] between each pair of sequences as the
+   * average number of features they have but do not share. That is, find the
+   * features each sequence pair has at each column, ignore feature types they
+   * have in common, and count the rest. The totals are normalised by the number
+   * of columns processed.
+   * <p>
+   * The parameters argument provides settings for treatment of gap-residue
+   * aligned positions, and whether the score is over the longer or shorter of
+   * each pair of sequences
+   * 
+   * @param seqData
+   * @param params
+   */
+  @Override
+  public MatrixI findDistances(AlignmentView seqData,
+          SimilarityParamsI params)
+  {
+    SeqCigar[] seqs = seqData.getSequences();
+    int noseqs = seqs.length;
+    int cpwidth = 0;// = seqData.getWidth();
+    double[][] distances = new double[noseqs][noseqs];
+    List<String> dft = null;
+    if (fr != null)
+    {
+      dft = fr.getDisplayedFeatureTypes();
+    }
+    if (dft == null || dft.isEmpty())
+    {
+      return new Matrix(distances);
+    }
+
+    // need to get real position for view position
+    int[] viscont = seqData.getVisibleContigs();
+
+    /*
+     * scan each column, compute and add to each distance[i, j]
+     * the number of feature types that seqi and seqj do not share
+     */
+    for (int vc = 0; vc < viscont.length; vc += 2)
+    {
+      for (int cpos = viscont[vc]; cpos <= viscont[vc + 1]; cpos++)
+      {
+        cpwidth++;
+
+        /*
+         * first record feature types in this column for each sequence
+         */
+        Map<SeqCigar, Set<String>> sfap = findFeatureTypesAtColumn(
+                seqs, cpos);
+
+        /*
+         * count feature types on either i'th or j'th sequence but not both
+         * and add this 'distance' measure to the total for [i, j] for j > i
+         */
+        for (int i = 0; i < (noseqs - 1); i++)
+        {
+          for (int j = i + 1; j < noseqs; j++)
+          {
+            SeqCigar sc1 = seqs[i];
+            SeqCigar sc2 = seqs[j];
+            Set<String> set1 = sfap.get(sc1);
+            Set<String> set2 = sfap.get(sc2);
+            boolean gap1 = set1 == null;
+            boolean gap2 = set2 == null;
+
+            /*
+             * gap-gap always scores zero
+             * residue-residue is always scored
+             * include gap-residue score if params say to do so
+             */
+            if ((!gap1 && !gap2) || params.includeGaps())
+            {
+              int seqDistance = SetUtils.countDisjunction(set1, set2);
+              distances[i][j] += seqDistance;
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * normalise the distance scores (summed over columns) by the
+     * number of visible columns used in the calculation
+     * and fill in the bottom half of the matrix
+     */
+    // TODO JAL-2424 cpwidth may be out by 1 - affects scores but not tree shape
+    for (int i = 0; i < noseqs; i++)
+    {
+      for (int j = i + 1; j < noseqs; j++)
+      {
+        distances[i][j] /= cpwidth;
+        distances[j][i] = distances[i][j];
+      }
+    }
+    return new Matrix(distances);
+  }
+
+  /**
+   * Builds and returns a map containing a (possibly empty) list (one per
+   * SeqCigar) of visible feature types at the given column position. The map
+   * has no entry for sequences which are gapped at the column position.
+   * 
+   * @param seqs
+   * @param columnPosition
+   * @return
+   */
+  protected Map<SeqCigar, Set<String>> findFeatureTypesAtColumn(
+          SeqCigar[] seqs, int columnPosition)
+  {
+    Map<SeqCigar, Set<String>> sfap = new HashMap<SeqCigar, Set<String>>();
+    for (SeqCigar seq : seqs)
+    {
+      int spos = seq.findPosition(columnPosition);
+      if (spos != -1)
+      {
+        Set<String> types = new HashSet<String>();
+        List<SequenceFeature> sfs = fr.findFeaturesAtRes(seq.getRefSeq(),
+                spos);
+        for (SequenceFeature sf : sfs)
+        {
+          types.add(sf.getType());
+        }
+        sfap.put(seq, types);
+      }
+    }
+    return sfap;
+  }
+
+  @Override
+  public String getName()
+  {
+    return NAME;
+  }
+
+  @Override
+  public String getDescription()
+  {
+    return description;
+  }
+
+  @Override
+  public boolean isDNA()
+  {
+    return true;
+  }
+
+  @Override
+  public boolean isProtein()
+  {
+    return true;
+  }
+
+  @Override
+  public String toString()
+  {
+    return "Score between sequences based on hamming distance between binary vectors marking features displayed at each column";
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/FeatureScoreModel.java b/src/jalview/analysis/scoremodels/FeatureScoreModel.java
deleted file mode 100644 (file)
index 7c81912..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.analysis.scoremodels;
-
-import jalview.api.analysis.ScoreModelI;
-import jalview.api.analysis.ViewBasedAnalysisI;
-import jalview.datamodel.AlignmentView;
-import jalview.datamodel.SeqCigar;
-import jalview.datamodel.SequenceFeature;
-
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-public class FeatureScoreModel implements ScoreModelI, ViewBasedAnalysisI
-{
-  jalview.api.FeatureRenderer fr;
-
-  @Override
-  public boolean configureFromAlignmentView(
-          jalview.api.AlignmentViewPanel view)
-  {
-    fr = view.cloneFeatureRenderer();
-    return true;
-  }
-
-  @Override
-  public float[][] findDistances(AlignmentView seqData)
-  {
-    int nofeats = 0;
-    List<String> dft = fr.getDisplayedFeatureTypes();
-    nofeats = dft.size();
-    SeqCigar[] seqs = seqData.getSequences();
-    int noseqs = seqs.length;
-    int cpwidth = 0;// = seqData.getWidth();
-    float[][] distance = new float[noseqs][noseqs];
-    if (nofeats == 0)
-    {
-      for (float[] d : distance)
-      {
-        for (int i = 0; i < d.length; d[i++] = 0f)
-        {
-          ;
-        }
-      }
-      return distance;
-    }
-    // need to get real position for view position
-    int[] viscont = seqData.getVisibleContigs();
-    for (int vc = 0; vc < viscont.length; vc += 2)
-    {
-
-      for (int cpos = viscont[vc]; cpos <= viscont[vc + 1]; cpos++)
-      {
-        cpwidth++;
-        // get visible features at cpos under view's display settings and
-        // compare them
-        List<Hashtable<String, SequenceFeature>> sfap = new ArrayList<Hashtable<String, SequenceFeature>>();
-        for (int i = 0; i < noseqs; i++)
-        {
-          Hashtable<String, SequenceFeature> types = new Hashtable<String, SequenceFeature>();
-          int spos = seqs[i].findPosition(cpos);
-          if (spos != -1)
-          {
-            List<SequenceFeature> sfs = fr.findFeaturesAtRes(
-                    seqs[i].getRefSeq(), spos);
-            for (SequenceFeature sf : sfs)
-            {
-              types.put(sf.getType(), sf);
-            }
-          }
-          sfap.add(types);
-        }
-        for (int i = 0; i < (noseqs - 1); i++)
-        {
-          if (cpos == 0)
-          {
-            distance[i][i] = 0f;
-          }
-          for (int j = i + 1; j < noseqs; j++)
-          {
-            int sfcommon = 0;
-            // compare the two lists of features...
-            Hashtable<String, SequenceFeature> fi = sfap.get(i), fk, fj = sfap
-                    .get(j);
-            if (fi.size() > fj.size())
-            {
-              fk = fj;
-            }
-            else
-            {
-              fk = fi;
-              fi = fj;
-            }
-            for (String k : fi.keySet())
-            {
-              SequenceFeature sfj = fk.get(k);
-              if (sfj != null)
-              {
-                sfcommon++;
-              }
-            }
-            distance[i][j] += (fi.size() + fk.size() - 2f * sfcommon);
-            distance[j][i] += distance[i][j];
-          }
-        }
-      }
-    }
-    for (int i = 0; i < noseqs; i++)
-    {
-      for (int j = i + 1; j < noseqs; j++)
-      {
-        distance[i][j] /= cpwidth;
-        distance[j][i] = distance[i][j];
-      }
-    }
-    return distance;
-  }
-
-  @Override
-  public String getName()
-  {
-    return "Sequence Feature Similarity";
-  }
-
-  @Override
-  public boolean isDNA()
-  {
-    return true;
-  }
-
-  @Override
-  public boolean isProtein()
-  {
-    return true;
-  }
-
-  @Override
-  public String toString()
-  {
-    return "Score between sequences based on hamming distance between binary vectors marking features displayed at each column";
-  }
-}
diff --git a/src/jalview/analysis/scoremodels/PIDModel.java b/src/jalview/analysis/scoremodels/PIDModel.java
new file mode 100644 (file)
index 0000000..721ba45
--- /dev/null
@@ -0,0 +1,243 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.analysis.PairwiseScoreModelI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
+import jalview.util.Comparison;
+
+/**
+ * A class to provide sequence pairwise similarity based on residue identity.
+ * Instances of this class are immutable and thread-safe, so the same object is
+ * returned from calls to getInstance().
+ */
+public class PIDModel extends SimilarityScoreModel implements
+        PairwiseScoreModelI
+{
+  private static final String NAME = "PID";
+
+  /**
+   * Constructor
+   */
+  public PIDModel()
+  {
+  }
+
+  @Override
+  public String getName()
+  {
+    return NAME;
+  }
+
+  /**
+   * Answers null for description. If a display name is needed, use getName() or
+   * an internationalized string built from the name.
+   */
+  @Override
+  public String getDescription()
+  {
+    return null;
+  }
+
+  @Override
+  public boolean isDNA()
+  {
+    return true;
+  }
+
+  @Override
+  public boolean isProtein()
+  {
+    return true;
+  }
+
+  /**
+   * Answers 1 if c and d are the same residue (ignoring case), and not gap
+   * characters. Answers 0 for non-matching or gap characters.
+   */
+  @Override
+  public float getPairwiseScore(char c, char d)
+  {
+    c = toUpper(c);
+    d = toUpper(d);
+    if (c == d && !Comparison.isGap(c))
+    {
+      return 1f;
+    }
+    return 0f;
+  }
+
+  /**
+   * @param c
+   */
+  protected static char toUpper(char c)
+  {
+    if ('a' <= c && c <= 'z')
+    {
+      c += 'A' - 'a';
+    }
+    return c;
+  }
+
+  /**
+   * Computes similarity scores based on pairwise percentage identity of
+   * sequences. For consistency with Jalview 2.10.1's SeqSpace mode PCA
+   * calculation, the percentage scores are rescaled to the width of the
+   * sequences (as if counts of identical residues). This method is thread-safe.
+   */
+  @Override
+  public MatrixI findSimilarities(AlignmentView seqData,
+          SimilarityParamsI options)
+  {
+    String[] seqs = seqData.getSequenceStrings(Comparison.GAP_DASH);
+
+    MatrixI result = findSimilarities(seqs, options);
+
+    result.multiply(seqData.getWidth() / 100d);
+
+    return result;
+  }
+
+  /**
+   * A distance score is computed in the usual way (by reversing the range of
+   * the similarity score results), and then rescaled to percentage values
+   * (reversing the rescaling to count values done in findSimilarities). This
+   * method is thread-safe.
+   */
+  @Override
+  public MatrixI findDistances(AlignmentView seqData,
+          SimilarityParamsI options)
+  {
+    MatrixI result = super.findDistances(seqData, options);
+
+    if (seqData.getWidth() != 0)
+    {
+      result.multiply(100d / seqData.getWidth());
+    }
+
+    return result;
+  }
+
+  /**
+   * Compute percentage identity scores, using the gap treatment and
+   * normalisation specified by the options parameter
+   * 
+   * @param seqs
+   * @param options
+   * @return
+   */
+  protected MatrixI findSimilarities(String[] seqs,
+          SimilarityParamsI options)
+  {
+    // TODO reuse code in ScoreMatrix instead somehow
+    double[][] values = new double[seqs.length][];
+    for (int row = 0; row < seqs.length; row++)
+    {
+      values[row] = new double[seqs.length];
+      for (int col = 0; col < seqs.length; col++)
+      {
+        double total = computePID(seqs[row], seqs[col], options);
+        values[row][col] = total;
+      }
+    }
+    return new Matrix(values);
+  }
+
+  /**
+   * Computes a percentage identity for two sequences, using the algorithm
+   * choices specified by the options parameter
+   * 
+   * @param seq1
+   * @param seq2
+   * @param options
+   * @return
+   */
+  public static double computePID(String seq1, String seq2,
+          SimilarityParamsI options)
+  {
+    int len1 = seq1.length();
+    int len2 = seq2.length();
+    int width = Math.max(len1, len2);
+    int total = 0;
+    int divideBy = 0;
+
+    for (int i = 0; i < width; i++)
+    {
+      if (i >= len1 || i >= len2)
+      {
+        /*
+         * off the end of one sequence; stop if we are only matching
+         * on the shorter sequence length, else treat as trailing gap
+         */
+        if (options.denominateByShortestLength())
+        {
+          break;
+        }
+        if (options.includeGaps())
+        {
+          divideBy++;
+        }
+        if (options.matchGaps())
+        {
+          total++;
+        }
+        continue;
+      }
+      char c1 = seq1.charAt(i);
+      char c2 = seq2.charAt(i);
+      boolean gap1 = Comparison.isGap(c1);
+      boolean gap2 = Comparison.isGap(c2);
+
+      if (gap1 && gap2)
+      {
+        /*
+         * gap-gap: include if options say so, if so
+         * have to score as identity; else ignore
+         */
+        if (options.includeGappedColumns())
+        {
+          divideBy++;
+          total++;
+        }
+        continue;
+      }
+
+      if (gap1 || gap2)
+      {
+        /*
+         * gap-residue: include if options say so, 
+         * count as match if options say so
+         */
+        if (options.includeGaps())
+        {
+          divideBy++;
+        }
+        if (options.matchGaps())
+        {
+          total++;
+        }
+        continue;
+      }
+
+      /*
+       * remaining case is gap-residue
+       */
+      if (toUpper(c1) == toUpper(c2))
+      {
+        total++;
+      }
+      divideBy++;
+    }
+
+    return divideBy == 0 ? 0D : 100D * total / divideBy;
+  }
+
+  @Override
+  public ScoreModelI getInstance(AlignmentViewPanel avp)
+  {
+    return this;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/PIDScoreModel.java b/src/jalview/analysis/scoremodels/PIDScoreModel.java
deleted file mode 100644 (file)
index 0f7a67a..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.analysis.scoremodels;
-
-import jalview.api.analysis.ScoreModelI;
-import jalview.datamodel.AlignmentView;
-import jalview.util.Comparison;
-
-public class PIDScoreModel implements ScoreModelI
-{
-
-  @Override
-  public float[][] findDistances(AlignmentView seqData)
-  {
-    String[] sequenceString = seqData
-            .getSequenceStrings(Comparison.GapChars.charAt(0));
-    int noseqs = sequenceString.length;
-    float[][] distance = new float[noseqs][noseqs];
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i; j < noseqs; j++)
-      {
-        if (j == i)
-        {
-          distance[i][i] = 0;
-        }
-        else
-        {
-          distance[i][j] = 100 - Comparison.PID(sequenceString[i],
-                  sequenceString[j]);
-
-          distance[j][i] = distance[i][j];
-        }
-      }
-    }
-    return distance;
-  }
-
-  @Override
-  public String getName()
-  {
-    return "PID";
-  }
-
-  @Override
-  public boolean isDNA()
-  {
-    return true;
-  }
-
-  @Override
-  public boolean isProtein()
-  {
-    return true;
-  }
-
-}
diff --git a/src/jalview/analysis/scoremodels/PairwiseSeqScoreModel.java b/src/jalview/analysis/scoremodels/PairwiseSeqScoreModel.java
deleted file mode 100644 (file)
index 2ff2518..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.analysis.scoremodels;
-
-import jalview.api.analysis.ScoreModelI;
-import jalview.datamodel.AlignmentView;
-import jalview.util.Comparison;
-
-public abstract class PairwiseSeqScoreModel implements ScoreModelI
-{
-  abstract public int getPairwiseScore(char c, char d);
-
-  public float[][] findDistances(AlignmentView seqData)
-  {
-    String[] sequenceString = seqData
-            .getSequenceStrings(Comparison.GapChars.charAt(0));
-    int noseqs = sequenceString.length;
-    float[][] distance = new float[noseqs][noseqs];
-
-    int maxscore = 0;
-    int end = sequenceString[0].length();
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i; j < noseqs; j++)
-      {
-        int score = 0;
-
-        for (int k = 0; k < end; k++)
-        {
-          try
-          {
-            score += getPairwiseScore(sequenceString[i].charAt(k),
-                    sequenceString[j].charAt(k));
-          } catch (Exception ex)
-          {
-            System.err.println("err creating " + getName() + " tree");
-            ex.printStackTrace();
-          }
-        }
-
-        distance[i][j] = (float) score;
-
-        if (score > maxscore)
-        {
-          maxscore = score;
-        }
-      }
-    }
-
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i; j < noseqs; j++)
-      {
-        distance[i][j] = (float) maxscore - distance[i][j];
-        distance[j][i] = distance[i][j];
-      }
-    }
-    return distance;
-  }
-
-  abstract public int[][] getMatrix();
-}
diff --git a/src/jalview/analysis/scoremodels/ScoreMatrix.java b/src/jalview/analysis/scoremodels/ScoreMatrix.java
new file mode 100644 (file)
index 0000000..efaeb43
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+ * 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.analysis.scoremodels;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.analysis.PairwiseScoreModelI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
+import jalview.util.Comparison;
+
+import java.util.Arrays;
+
+/**
+ * A class that models a substitution score matrix for any given alphabet of
+ * symbols. Instances of this class are immutable and thread-safe, so the same
+ * object is returned from calls to getInstance().
+ */
+public class ScoreMatrix extends SimilarityScoreModel implements
+        PairwiseScoreModelI
+{
+  private static final char GAP_CHARACTER = Comparison.GAP_DASH;
+
+  /*
+   * an arbitrary score to assign for identity of an unknown symbol
+   * (this is the value on the diagonal in the * column of the NCBI matrix)
+   * (though a case could be made for using the minimum diagonal value)
+   */
+  private static final int UNKNOWN_IDENTITY_SCORE = 1;
+
+  /*
+   * Jalview 2.10.1 treated gaps as X (peptide) or N (nucleotide)
+   * for pairwise scoring; 2.10.2 uses gap score (last column) in
+   * score matrix (JAL-2397)
+   * Set this flag to true (via Groovy) for 2.10.1 behaviour
+   */
+  private static boolean scoreGapAsAny = false;
+
+  public static final short UNMAPPED = (short) -1;
+
+  private static final String BAD_ASCII_ERROR = "Unexpected character %s in getPairwiseScore";
+
+  private static final int MAX_ASCII = 127;
+
+  /*
+   * the name of the model as shown in menus
+   * each score model in use should have a unique name
+   */
+  private String name;
+
+  /*
+   * a description for the model as shown in tooltips
+   */
+  private String description;
+
+  /*
+   * the characters that the model provides scores for
+   */
+  private char[] symbols;
+
+  /*
+   * the score matrix; both dimensions must equal the number of symbols
+   * matrix[i][j] is the substitution score for replacing symbols[i] with symbols[j]
+   */
+  private float[][] matrix;
+
+  /*
+   * quick lookup to convert from an ascii character value to the index
+   * of the corresponding symbol in the score matrix 
+   */
+  private short[] symbolIndex;
+
+  /*
+   * true for Protein Score matrix, false for dna score matrix
+   */
+  private boolean peptide;
+
+  private float minValue;
+
+  private float maxValue;
+  
+  /**
+   * Constructor given a name, symbol alphabet, and matrix of scores for pairs
+   * of symbols. The matrix should be square and of the same size as the
+   * alphabet, for example 20x20 for a 20 symbol alphabet.
+   * 
+   * @param theName
+   *          Unique, human readable name for the matrix
+   * @param alphabet
+   *          the symbols to which scores apply
+   * @param values
+   *          Pairwise scores indexed according to the symbol alphabet
+   */
+  public ScoreMatrix(String theName, char[] alphabet, float[][] values)
+  {
+    this(theName, null, alphabet, values);
+  }
+
+  /**
+   * Constructor given a name, description, symbol alphabet, and matrix of
+   * scores for pairs of symbols. The matrix should be square and of the same
+   * size as the alphabet, for example 20x20 for a 20 symbol alphabet.
+   * 
+   * @param theName
+   *          Unique, human readable name for the matrix
+   * @param theDescription
+   *          descriptive display name suitable for use in menus
+   * @param alphabet
+   *          the symbols to which scores apply
+   * @param values
+   *          Pairwise scores indexed according to the symbol alphabet
+   */
+  public ScoreMatrix(String theName, String theDescription,
+          char[] alphabet, float[][] values)
+  {
+    if (alphabet.length != values.length)
+    {
+      throw new IllegalArgumentException(
+              "score matrix size must match alphabet size");
+    }
+    for (float[] row : values)
+    {
+      if (row.length != alphabet.length)
+      {
+        throw new IllegalArgumentException(
+                "score matrix size must be square");
+      }
+    }
+
+    this.matrix = values;
+    this.name = theName;
+    this.description = theDescription;
+    this.symbols = alphabet;
+
+    symbolIndex = buildSymbolIndex(alphabet);
+
+    findMinMax();
+
+    /*
+     * crude heuristic for now...
+     */
+    peptide = alphabet.length >= 20;
+  }
+
+  /**
+   * Record the minimum and maximum score values
+   */
+  protected void findMinMax()
+  {
+    float min = Float.MAX_VALUE;
+    float max = -Float.MAX_VALUE;
+    if (matrix != null)
+    {
+      for (float[] row : matrix)
+      {
+        if (row != null)
+        {
+          for (float f : row)
+          {
+            min = Math.min(min, f);
+            max = Math.max(max, f);
+          }
+        }
+      }
+    }
+    minValue = min;
+    maxValue = max;
+  }
+
+  /**
+   * Returns an array A where A[i] is the position in the alphabet array of the
+   * character whose value is i. For example if the alphabet is { 'A', 'D', 'X'
+   * } then A['D'] = A[68] = 1.
+   * <p>
+   * Unmapped characters (not in the alphabet) get an index of -1.
+   * <p>
+   * Mappings are added automatically for lower case symbols (for non case
+   * sensitive scoring), unless they are explicitly present in the alphabet (are
+   * scored separately in the score matrix).
+   * <p>
+   * the gap character (space, dash or dot) included in the alphabet (if any) is
+   * recorded in a field
+   * 
+   * @param alphabet
+   * @return
+   */
+  short[] buildSymbolIndex(char[] alphabet)
+  {
+    short[] index = new short[MAX_ASCII + 1];
+    Arrays.fill(index, UNMAPPED);
+    short pos = 0;
+    for (char c : alphabet)
+    {
+      if (c <= MAX_ASCII)
+      {
+        index[c] = pos;
+      }
+
+      /*
+       * also map lower-case character (unless separately mapped)
+       */
+      if (c >= 'A' && c <= 'Z')
+      {
+        short lowerCase = (short) (c + ('a' - 'A'));
+        if (index[lowerCase] == UNMAPPED)
+        {
+          index[lowerCase] = pos;
+        }
+      }
+      pos++;
+    }
+    return index;
+  }
+
+  @Override
+  public String getName()
+  {
+    return name;
+  }
+
+  @Override
+  public String getDescription()
+  {
+    return description;
+  }
+
+  @Override
+  public boolean isDNA()
+  {
+    return !peptide;
+  }
+
+  @Override
+  public boolean isProtein()
+  {
+    return peptide;
+  }
+
+  /**
+   * Returns a copy of the score matrix as used in getPairwiseScore. If using
+   * this matrix directly, callers <em>must</em> also call
+   * <code>getMatrixIndex</code> in order to get the matrix index for each
+   * character (symbol).
+   * 
+   * @return
+   * @see #getMatrixIndex(char)
+   */
+  public float[][] getMatrix()
+  {
+    float[][] v = new float[matrix.length][matrix.length];
+    for (int i = 0; i < matrix.length; i++)
+    {
+      v[i] = Arrays.copyOf(matrix[i], matrix[i].length);
+    }
+    return v;
+  }
+
+  /**
+   * Answers the matrix index for a given character, or -1 if unmapped in the
+   * matrix. Use this method only if using <code>getMatrix</code> in order to
+   * compute scores directly (without symbol lookup) for efficiency.
+   * 
+   * @param c
+   * @return
+   * @see #getMatrix()
+   */
+  public int getMatrixIndex(char c)
+  {
+    if (c < symbolIndex.length)
+    {
+      return symbolIndex[c];
+    }
+    else
+    {
+      return UNMAPPED;
+    }
+  }
+
+  /**
+   * Returns the pairwise score for substituting c with d. If either c or d is
+   * an unexpected character, returns 1 for identity (c == d), else the minimum
+   * score value in the matrix.
+   */
+  @Override
+  public float getPairwiseScore(char c, char d)
+  {
+    if (c >= symbolIndex.length)
+    {
+      System.err.println(String.format(BAD_ASCII_ERROR, c));
+      return 0;
+    }
+    if (d >= symbolIndex.length)
+    {
+      System.err.println(String.format(BAD_ASCII_ERROR, d));
+      return 0;
+    }
+
+    int cIndex = symbolIndex[c];
+    int dIndex = symbolIndex[d];
+    if (cIndex != UNMAPPED && dIndex != UNMAPPED)
+    {
+      return matrix[cIndex][dIndex];
+    }
+
+    /*
+     * one or both symbols not found in the matrix
+     * currently scoring as 1 (for identity) or the minimum
+     * matrix score value (otherwise)
+     * (a case could be made for using minimum row/column value instead)
+     */
+    return c == d ? UNKNOWN_IDENTITY_SCORE : getMinimumScore();
+  }
+
+  /**
+   * pretty print the matrix
+   */
+  @Override
+  public String toString()
+  {
+    return outputMatrix(false);
+  }
+
+  /**
+   * Print the score matrix, optionally formatted as html, with the alphabet
+   * symbols as column headings and at the start of each row.
+   * <p>
+   * The non-html format should give an output which can be parsed as a score
+   * matrix file
+   * 
+   * @param html
+   * @return
+   */
+  public String outputMatrix(boolean html)
+  {
+    StringBuilder sb = new StringBuilder(512);
+
+    /*
+     * heading row with alphabet
+     */
+    if (html)
+    {
+      sb.append("<table border=\"1\">");
+      sb.append(html ? "<tr><th></th>" : "");
+    }
+    else
+    {
+      sb.append("ScoreMatrix ").append(getName()).append("\n");
+    }
+    for (char sym : symbols)
+    {
+      if (html)
+      {
+        sb.append("<th>&nbsp;").append(sym).append("&nbsp;</th>");
+      }
+      else
+      {
+        sb.append("\t").append(sym);
+      }
+    }
+    sb.append(html ? "</tr>\n" : "\n");
+
+    /*
+     * table of scores
+     */
+    for (char c1 : symbols)
+    {
+      if (html)
+      {
+        sb.append("<tr><td>");
+      }
+      sb.append(c1).append(html ? "</td>" : "");
+      for (char c2 : symbols)
+      {
+        sb.append(html ? "<td>" : "\t")
+                .append(matrix[symbolIndex[c1]][symbolIndex[c2]])
+                .append(html ? "</td>" : "");
+      }
+      sb.append(html ? "</tr>\n" : "\n");
+    }
+    if (html)
+    {
+      sb.append("</table>");
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Answers the number of symbols coded for (also equal to the number of rows
+   * and columns of the score matrix)
+   * 
+   * @return
+   */
+  public int getSize()
+  {
+    return symbols.length;
+  }
+
+  /**
+   * Computes an NxN matrix where N is the number of sequences, and entry [i, j]
+   * is sequence[i] pairwise multiplied with sequence[j], as a sum of scores
+   * computed using the current score matrix. For example
+   * <ul>
+   * <li>Sequences:</li>
+   * <li>FKL</li>
+   * <li>R-D</li>
+   * <li>QIA</li>
+   * <li>GWC</li>
+   * <li>Score matrix is BLOSUM62</li>
+   * <li>Gaps treated same as X (unknown)</li>
+   * <li>product [0, 0] = F.F + K.K + L.L = 6 + 5 + 4 = 15</li>
+   * <li>product [1, 1] = R.R + -.- + D.D = 5 + -1 + 6 = 10</li>
+   * <li>product [2, 2] = Q.Q + I.I + A.A = 5 + 4 + 4 = 13</li>
+   * <li>product [3, 3] = G.G + W.W + C.C = 6 + 11 + 9 = 26</li>
+   * <li>product[0, 1] = F.R + K.- + L.D = -3 + -1 + -3 = -8
+   * <li>and so on</li>
+   * </ul>
+   * This method is thread-safe.
+   */
+  @Override
+  public MatrixI findSimilarities(AlignmentView seqstrings,
+          SimilarityParamsI options)
+  {
+    char gapChar = scoreGapAsAny ? (seqstrings.isNa() ? 'N' : 'X')
+            : GAP_CHARACTER;
+    String[] seqs = seqstrings.getSequenceStrings(gapChar);
+    return findSimilarities(seqs, options);
+  }
+
+  /**
+   * Computes pairwise similarities of a set of sequences using the given
+   * parameters
+   * 
+   * @param seqs
+   * @param params
+   * @return
+   */
+  protected MatrixI findSimilarities(String[] seqs, SimilarityParamsI params)
+  {
+    double[][] values = new double[seqs.length][];
+    for (int row = 0; row < seqs.length; row++)
+    {
+      values[row] = new double[seqs.length];
+      for (int col = 0; col < seqs.length; col++)
+      {
+        double total = computeSimilarity(seqs[row], seqs[col], params);
+        values[row][col] = total;
+      }
+    }
+    return new Matrix(values);
+  }
+
+  /**
+   * Calculates the pairwise similarity of two strings using the given
+   * calculation parameters
+   * 
+   * @param seq1
+   * @param seq2
+   * @param params
+   * @return
+   */
+  protected double computeSimilarity(String seq1, String seq2,
+          SimilarityParamsI params)
+  {
+    int len1 = seq1.length();
+    int len2 = seq2.length();
+    double total = 0;
+
+    int width = Math.max(len1, len2);
+    for (int i = 0; i < width; i++)
+    {
+      if (i >= len1 || i >= len2)
+      {
+        /*
+         * off the end of one sequence; stop if we are only matching
+         * on the shorter sequence length, else treat as trailing gap
+         */
+        if (params.denominateByShortestLength())
+        {
+          break;
+        }
+      }
+
+      char c1 = i >= len1 ? GAP_CHARACTER : seq1.charAt(i);
+      char c2 = i >= len2 ? GAP_CHARACTER : seq2.charAt(i);
+      boolean gap1 = Comparison.isGap(c1);
+      boolean gap2 = Comparison.isGap(c2);
+
+      if (gap1 && gap2)
+      {
+        /*
+         * gap-gap: include if options say so, else ignore
+         */
+        if (!params.includeGappedColumns())
+        {
+          continue;
+        }
+      }
+      else if (gap1 || gap2)
+      {
+        /*
+         * gap-residue: score if options say so
+         */
+        if (!params.includeGaps())
+        {
+          continue;
+        }
+      }
+      float score = getPairwiseScore(c1, c2);
+      total += score;
+    }
+    return total;
+  }
+
+  /**
+   * Answers a hashcode computed from the symbol alphabet and the matrix score
+   * values
+   */
+  @Override
+  public int hashCode()
+  {
+    int hs = Arrays.hashCode(symbols);
+    for (float[] row : matrix)
+    {
+      hs = hs * 31 + Arrays.hashCode(row);
+    }
+    return hs;
+  }
+
+  /**
+   * Answers true if the argument is a ScoreMatrix with the same symbol alphabet
+   * and score values, else false
+   */
+  @Override
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof ScoreMatrix))
+    {
+      return false;
+    }
+    ScoreMatrix sm = (ScoreMatrix) obj;
+    if (Arrays.equals(symbols, sm.symbols)
+            && Arrays.deepEquals(matrix, sm.matrix))
+    {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns the alphabet the matrix scores for, as a string of characters
+   * 
+   * @return
+   */
+  String getSymbols()
+  {
+    return new String(symbols);
+  }
+
+  public float getMinimumScore()
+  {
+    return minValue;
+  }
+
+  public float getMaximumScore()
+  {
+    return maxValue;
+  }
+
+  @Override
+  public ScoreModelI getInstance(AlignmentViewPanel avp)
+  {
+    return this;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/ScoreModels.java b/src/jalview/analysis/scoremodels/ScoreModels.java
new file mode 100644 (file)
index 0000000..654136a
--- /dev/null
@@ -0,0 +1,141 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.api.analysis.ScoreModelI;
+import jalview.io.DataSourceType;
+import jalview.io.FileParse;
+import jalview.io.ScoreMatrixFile;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A class that can register and serve instances of ScoreModelI
+ */
+public class ScoreModels
+{
+  private final ScoreMatrix BLOSUM62;
+
+  private final ScoreMatrix PAM250;
+
+  private final ScoreMatrix DNA;
+
+  private static ScoreModels instance = new ScoreModels();
+
+  private Map<String, ScoreModelI> models;
+
+  public static ScoreModels getInstance()
+  {
+    return instance;
+  }
+
+  /**
+   * Private constructor to enforce use of singleton. Registers Jalview's
+   * "built-in" score models:
+   * <ul>
+   * <li>BLOSUM62</li>
+   * <li>PAM250</li>
+   * <li>PID</li>
+   * <li>DNA</li>
+   * <li>Sequence Feature Similarity</li>
+   * </ul>
+   */
+  private ScoreModels()
+  {
+    /*
+     * using LinkedHashMap keeps models ordered as added
+     */
+    models = new LinkedHashMap<String, ScoreModelI>();
+    BLOSUM62 = loadScoreMatrix("scoreModel/blosum62.scm");
+    PAM250 = loadScoreMatrix("scoreModel/pam250.scm");
+    registerScoreModel(new PIDModel());
+    DNA = loadScoreMatrix("scoreModel/dna.scm");
+    registerScoreModel(new FeatureDistanceModel());
+  }
+
+  /**
+   * Tries to load a score matrix from the given resource file, and if
+   * successful, registers it.
+   * 
+   * @param string
+   * @return
+   */
+  ScoreMatrix loadScoreMatrix(String resourcePath)
+  {
+    try
+    {
+      /*
+       * delegate parsing to ScoreMatrixFile
+       */
+      FileParse fp = new FileParse(resourcePath, DataSourceType.CLASSLOADER);
+      ScoreMatrix sm = new ScoreMatrixFile(fp).parseMatrix();
+      registerScoreModel(sm);
+      return sm;
+    } catch (IOException e)
+    {
+      System.err.println("Error reading " + resourcePath + ": "
+              + e.getMessage());
+    }
+    return null;
+  }
+
+  /**
+   * Answers an iterable set of the registered score models. Currently these are
+   * returned in the order in which they were registered.
+   * 
+   * @return
+   */
+  public Iterable<ScoreModelI> getModels()
+  {
+    return models.values();
+  }
+
+  /**
+   * Returns an instance of a score model for the given name. If the model is of
+   * 'view dependent' type (e.g. feature similarity), instantiates a new
+   * instance configured for the given view. Otherwise returns a cached instance
+   * of the score model.
+   * 
+   * @param name
+   * @param avp
+   * @return
+   */
+  public ScoreModelI getScoreModel(String name, AlignmentViewPanel avp)
+  {
+    ScoreModelI model = models.get(name);
+    return model == null ? null : model.getInstance(avp);
+  }
+
+  public void registerScoreModel(ScoreModelI sm)
+  {
+    ScoreModelI sm2 = models.get(sm.getName());
+    if (sm2 != null)
+    {
+      System.err.println("Warning: replacing score model " + sm2.getName());
+    }
+    models.put(sm.getName(), sm);
+  }
+
+  /**
+   * Returns the default peptide or nucleotide score model, currently BLOSUM62
+   * or DNA
+   * 
+   * @param forPeptide
+   * @return
+   */
+  public ScoreMatrix getDefaultModel(boolean forPeptide)
+  {
+    return forPeptide ? BLOSUM62 : DNA;
+  }
+
+  public ScoreMatrix getBlosum62()
+  {
+    return BLOSUM62;
+  }
+
+  public ScoreMatrix getPam250()
+  {
+    return PAM250;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/SimilarityParams.java b/src/jalview/analysis/scoremodels/SimilarityParams.java
new file mode 100644 (file)
index 0000000..e5751ca
--- /dev/null
@@ -0,0 +1,130 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.analysis.SimilarityParamsI;
+
+/**
+ * A class to hold parameters that configure the pairwise similarity
+ * calculation. Based on the paper
+ * 
+ * <pre>
+ * Quantification of the variation in percentage identity for protein sequence alignments
+ * Raghava, GP and Barton, GJ
+ * BMC Bioinformatics. 2006 Sep 19;7:415
+ * </pre>
+ * 
+ * @see https://www.ncbi.nlm.nih.gov/pubmed/16984632
+ */
+public class SimilarityParams implements SimilarityParamsI
+{
+  /**
+   * Based on Jalview's Comparison.PID method, which includes gaps and counts
+   * them as matching; it counts over the length of the shorter sequence
+   */
+  public static final SimilarityParamsI Jalview = new SimilarityParams(
+          true, true, true, true);
+
+  /**
+   * 'SeqSpace' mode PCA calculation includes gaps but does not count them as
+   * matching; it uses the longest sequence length
+   */
+  public static final SimilarityParamsI SeqSpace = new SimilarityParams(
+          true, false, true, true);
+
+  /**
+   * as described in the Raghava-Barton paper
+   * <ul>
+   * <li>ignores gap-gap</li>
+   * <li>does not score gap-residue</li>
+   * <li>includes gap-residue in lengths</li>
+   * <li>matches on longer of two sequences</li>
+   * </ul>
+   */
+  public static final SimilarityParamsI PID1 = new SimilarityParams(false,
+          false, true, false);
+
+  /**
+   * as described in the Raghava-Barton paper
+   * <ul>
+   * <li>ignores gap-gap</li>
+   * <li>ignores gap-residue</li>
+   * <li>matches on longer of two sequences</li>
+   * </ul>
+   */
+  public static final SimilarityParamsI PID2 = new SimilarityParams(false,
+          false, false, false);
+
+  /**
+   * as described in the Raghava-Barton paper
+   * <ul>
+   * <li>ignores gap-gap</li>
+   * <li>ignores gap-residue</li>
+   * <li>matches on shorter of sequences only</li>
+   * </ul>
+   */
+  public static final SimilarityParamsI PID3 = new SimilarityParams(false,
+          false, false, true);
+
+  /**
+   * as described in the Raghava-Barton paper
+   * <ul>
+   * <li>ignores gap-gap</li>
+   * <li>does not score gap-residue</li>
+   * <li>includes gap-residue in lengths</li>
+   * <li>matches on shorter of sequences only</li>
+   * </ul>
+   */
+  public static final SimilarityParamsI PID4 = new SimilarityParams(false,
+          false, true, true);
+
+  private boolean includeGappedColumns;
+
+  private boolean matchGaps;
+
+  private boolean includeGaps;
+
+  private boolean denominateByShortestLength;
+
+  /**
+   * Constructor
+   * 
+   * @param includeGapGap
+   * @param matchGapResidue
+   * @param includeGapResidue
+   *          if true, gapped positions are counted for normalisation by length
+   * @param shortestLength
+   *          if true, the denominator is the shorter sequence length (possibly
+   *          including gaps)
+   */
+  public SimilarityParams(boolean includeGapGap, boolean matchGapResidue,
+          boolean includeGapResidue, boolean shortestLength)
+  {
+    includeGappedColumns = includeGapGap;
+    matchGaps = matchGapResidue;
+    includeGaps = includeGapResidue;
+    denominateByShortestLength = shortestLength;
+  }
+
+  @Override
+  public boolean includeGaps()
+  {
+    return includeGaps;
+  }
+
+  @Override
+  public boolean denominateByShortestLength()
+  {
+    return denominateByShortestLength;
+  }
+
+  @Override
+  public boolean includeGappedColumns()
+  {
+    return includeGappedColumns;
+  }
+
+  @Override
+  public boolean matchGaps()
+  {
+    return matchGaps;
+  }
+}
diff --git a/src/jalview/analysis/scoremodels/SimilarityScoreModel.java b/src/jalview/analysis/scoremodels/SimilarityScoreModel.java
new file mode 100644 (file)
index 0000000..dae1f62
--- /dev/null
@@ -0,0 +1,43 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.math.MatrixI;
+
+public abstract class SimilarityScoreModel implements ScoreModelI
+{
+
+  /**
+   * Computed similarity scores are converted to distance scores by subtracting
+   * every value from the maximum value. That is, maximum similarity corresponds
+   * to zero distance, and smaller similarities to larger distances.
+   */
+  @Override
+  public MatrixI findDistances(AlignmentView seqData,
+          SimilarityParamsI options)
+  {
+    MatrixI similarities = findSimilarities(seqData, options);
+
+    MatrixI distances = similarityToDistance(similarities);
+
+    return distances;
+  }
+
+  /**
+   * Converts a matrix of similarity scores to distance scores, by reversing the
+   * range of the scores, mapping the maximum to zero. The input matrix is not
+   * modified.
+   * 
+   * @param similarities
+   */
+  public static MatrixI similarityToDistance(MatrixI similarities)
+  {
+    MatrixI distances = similarities.copy();
+
+    distances.reverseRange(true);
+
+    return distances;
+  }
+
+}
 package jalview.analysis.scoremodels;
 
 import jalview.analysis.AlignSeq;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.SequenceI;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
 import jalview.util.Comparison;
 
-public class SWScoreModel implements ScoreModelI
+/**
+ * A class that computes pairwise similarity scores using the Smith-Waterman
+ * alignment algorithm
+ */
+public class SmithWatermanModel extends SimilarityScoreModel
 {
+  private static final String NAME = "Smith Waterman Score";
+
+  private String description;
+
+  /**
+   * Constructor
+   */
+  public SmithWatermanModel()
+  {
+  }
 
   @Override
-  public float[][] findDistances(AlignmentView seqData)
+  public MatrixI findSimilarities(AlignmentView seqData,
+          SimilarityParamsI options)
   {
     SequenceI[] sequenceString = seqData.getVisibleAlignment(
-            Comparison.GapChars.charAt(0)).getSequencesArray();
+            Comparison.GAP_SPACE).getSequencesArray();
     int noseqs = sequenceString.length;
-    float[][] distance = new float[noseqs][noseqs];
+    double[][] distances = new double[noseqs][noseqs];
 
-    float max = -1;
+    double max = -1;
 
     for (int i = 0; i < (noseqs - 1); i++)
     {
@@ -48,31 +67,22 @@ public class SWScoreModel implements ScoreModelI
         as.calcScoreMatrix();
         as.traceAlignment();
         as.printAlignment(System.out);
-        distance[i][j] = (float) as.maxscore;
+        distances[i][j] = as.maxscore;
 
-        if (max < distance[i][j])
+        if (max < distances[i][j])
         {
-          max = distance[i][j];
+          max = distances[i][j];
         }
       }
     }
 
-    for (int i = 0; i < (noseqs - 1); i++)
-    {
-      for (int j = i; j < noseqs; j++)
-      {
-        distance[i][j] = max - distance[i][j];
-        distance[j][i] = distance[i][j];
-      }
-    }
-
-    return distance;
+    return new Matrix(distances);
   }
 
   @Override
   public String getName()
   {
-    return "Smith Waterman Score";
+    return NAME;
   }
 
   @Override
@@ -87,8 +97,15 @@ public class SWScoreModel implements ScoreModelI
     return true;
   }
 
-  public String toString()
+  @Override
+  public String getDescription()
+  {
+    return description;
+  }
+
+  @Override
+  public ScoreModelI getInstance(AlignmentViewPanel avp)
   {
-    return "Score between two sequences aligned with Smith Waterman with default Peptide/Nucleotide matrix";
+    return this;
   }
 }
index 06fa179..2870373 100644 (file)
@@ -34,8 +34,10 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
+import java.awt.Font;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -47,7 +49,13 @@ import java.util.Map;
 public interface AlignViewportI extends ViewStyleI
 {
 
-  int getEndRes();
+  /**
+   * Get the ranges object containing details of the start and end sequences and
+   * residues
+   * 
+   * @return
+   */
+  public ViewportRanges getRanges();
 
   /**
    * calculate the height for visible annotation, revalidating bounds where
@@ -120,6 +128,13 @@ public interface AlignViewportI extends ViewStyleI
   AlignmentAnnotation getAlignmentConsensusAnnotation();
 
   /**
+   * get the container for alignment gap annotation
+   * 
+   * @return
+   */
+  AlignmentAnnotation getAlignmentGapAnnotation();
+
+  /**
    * get the container for cDNA complement consensus annotation
    * 
    * @return
@@ -459,4 +474,29 @@ public interface AlignViewportI extends ViewStyleI
   SearchResultsI getSearchResults();
 
   ContactListI getContactList(AlignmentAnnotation _aa, int column);
+
+  /**
+   * Updates view settings with the given font. You may need to call
+   * AlignmentPanel.fontChanged to update the layout geometry.
+   * 
+   * @param setGrid
+   *          when true, charWidth/height is set according to font metrics
+   */
+  void setFont(Font newFont, boolean b);
+
+  /**
+   * Answers true if split screen protein and cDNA use the same font
+   * 
+   * @return
+   */
+  @Override
+  boolean isProteinFontAsCdna();
+
+  /**
+   * Set the flag for whether split screen protein and cDNA use the same font
+   * 
+   * @return
+   */
+  @Override
+  void setProteinFontAsCdna(boolean b);
 }
diff --git a/src/jalview/api/AlignmentColsCollectionI.java b/src/jalview/api/AlignmentColsCollectionI.java
new file mode 100644 (file)
index 0000000..603da98
--- /dev/null
@@ -0,0 +1,13 @@
+package jalview.api;
+
+public interface AlignmentColsCollectionI extends Iterable<Integer>
+{
+  /**
+   * Answers if the column at the given position is hidden.
+   * 
+   * @param c
+   *          the column index to check
+   * @return true if the column at the position is hidden
+   */
+  public boolean isHidden(int c);
+}
diff --git a/src/jalview/api/AlignmentRowsCollectionI.java b/src/jalview/api/AlignmentRowsCollectionI.java
new file mode 100644 (file)
index 0000000..09b039d
--- /dev/null
@@ -0,0 +1,24 @@
+package jalview.api;
+
+import jalview.datamodel.SequenceI;
+
+public interface AlignmentRowsCollectionI extends Iterable<Integer>
+{
+  /**
+   * Answers if the sequence at the given position is hidden.
+   * 
+   * @param r
+   *          the row index to check
+   * @return true if the sequence at r is hidden
+   */
+  public boolean isHidden(int r);
+
+  /**
+   * Answers the sequence at the given position in the alignment
+   * 
+   * @param r
+   *          the row index to locate
+   * @return the sequence
+   */
+  public SequenceI getSequence(int r);
+}
index 2bf2782..1b579ad 100644 (file)
@@ -20,7 +20,7 @@
  */
 package jalview.api;
 
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 
 /**
@@ -50,7 +50,7 @@ public interface ComplexAlignFile
    * 
    * @return
    */
-  public ColumnSelection getColumnSelection();
+  public HiddenColumns getHiddenColumns();
 
   /**
    * Retrieves hidden sequences from a complex file parser
index f54231e..7123b8c 100644 (file)
@@ -24,6 +24,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 
 import java.awt.Color;
+import java.awt.Graphics;
 import java.util.List;
 import java.util.Map;
 
@@ -37,18 +38,32 @@ public interface FeatureRenderer
 {
 
   /**
-   * compute the perceived colour for a given column position in sequenceI,
-   * taking transparency and feature visibility into account.
+   * Computes the feature colour for a given sequence and column position,
+   * taking into account sequence feature locations, feature colour schemes,
+   * render ordering, feature and feature group visibility, and transparency.
+   * <p>
+   * The graphics argument should be provided if transparency is applied
+   * (getTransparency() < 1). With feature transparency, visible features are
+   * written to the graphics context and the composite colour may be read off
+   * from it. In this case, the returned feature colour is not the composite
+   * colour but that of the last feature drawn.
+   * <p>
+   * If no transparency applies, then the graphics argument may be null, and the
+   * returned colour is the one that would be drawn for the feature.
+   * <p>
+   * Returns null if there is no visible feature at the position.
+   * <p>
+   * This is provided to support rendering of feature colours other than on the
+   * sequence alignment, including by structure viewers and the overview window.
+   * Note this method takes no account of whether the sequence or column is
+   * hidden.
    * 
-   * @param col
-   *          - background colour (due to alignment/group shading schemes, etc).
-   * @param sequenceI
-   *          - sequence providing features
-   * @param r
-   *          - column position
+   * @param sequence
+   * @param column
+   * @param g
    * @return
    */
-  Color findFeatureColour(Color col, SequenceI sequenceI, int r);
+  Color findFeatureColour(SequenceI sequence, int column, Graphics g);
 
   /**
    * trigger the feature discovery process for a newly created feature renderer.
@@ -170,4 +185,19 @@ public interface FeatureRenderer
    */
   void setVisible(String featureType);
 
+  /**
+   * Sets the transparency value, between 0 (full transparency) and 1 (no
+   * transparency)
+   * 
+   * @param value
+   */
+  void setTransparency(float value);
+
+  /**
+   * Returns the transparency value, between 0 (full transparency) and 1 (no
+   * transparency)
+   * 
+   * @return
+   */
+  float getTransparency();
 }
index d708902..54f7fb6 100644 (file)
 package jalview.api;
 
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 
 import java.awt.Color;
 
 public interface SequenceRenderer
 {
 
-  Color getResidueBoxColour(SequenceI sequenceI, int r);
-
-  Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr);
+  Color getResidueColour(SequenceI seq, int position,
+          FeatureColourFinder finder);
 
 }
index c795f3f..367a0de 100644 (file)
@@ -100,7 +100,7 @@ public interface SiftsClientI
    * @return Sequence<->Structure mapping as int[][]
    * @throws SiftsException
    */
-  public StringBuffer getMappingOutput(MappingOutputPojo mop)
+  public StringBuilder getMappingOutput(MappingOutputPojo mop)
           throws SiftsException;
 
   /**
index db82dcf..2b554ea 100644 (file)
@@ -257,4 +257,18 @@ public interface ViewStyleI
    * @return
    */
   void setScaleProteinAsCdna(boolean b);
+
+  /**
+   * Answers true if split screen protein and cDNA use the same font
+   * 
+   * @return
+   */
+  boolean isProteinFontAsCdna();
+
+  /**
+   * Set the flag for whether split screen protein and cDNA use the same font
+   * 
+   * @return
+   */
+  void setProteinFontAsCdna(boolean b);
 }
diff --git a/src/jalview/api/analysis/PairwiseScoreModelI.java b/src/jalview/api/analysis/PairwiseScoreModelI.java
new file mode 100644 (file)
index 0000000..ecada36
--- /dev/null
@@ -0,0 +1,22 @@
+package jalview.api.analysis;
+
+/**
+ * An interface that describes classes that can compute similarity (aka
+ * substitution) scores for pairs of residues
+ */
+public interface PairwiseScoreModelI
+{
+  /**
+   * Answers a similarity score between two sequence characters (for
+   * substitution of the first by the second). Typically the highest scores are
+   * for identity, and the lowest for substitution of a residue by one with very
+   * different properties.
+   * 
+   * @param c
+   * @param d
+   * @return
+   */
+  abstract public float getPairwiseScore(char c, char d);
+  // TODO make this static when Java 8
+
+}
index 31a1c32..7f138cd 100644 (file)
@@ -1,36 +1,79 @@
-/*
- * 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.api.analysis;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentView;
+import jalview.math.MatrixI;
 
 public interface ScoreModelI
 {
-
-  float[][] findDistances(AlignmentView seqData);
-
+  /**
+   * Answers a name for the score model, suitable for display in menus. Names
+   * should be unique across score models in use.
+   * 
+   * @return
+   * @see jalview.analysis.scoremodels.ScoreModels#forName(String)
+   */
   String getName();
 
+  /**
+   * Answers an informative description of the model, suitable for use in
+   * tooltips. Descriptions may be internationalised, and need not be unique
+   * (but should be).
+   * 
+   * @return
+   */
+  String getDescription();
+
+  /**
+   * Answers true if this model is applicable for nucleotide data (so should be
+   * shown in menus in that context)
+   * 
+   * @return
+   */
   boolean isDNA();
 
+  /**
+   * Answers true if this model is applicable for peptide data (so should be
+   * shown in menus in that context)
+   * 
+   * @return
+   */
   boolean isProtein();
 
+  // TODO getName, isDNA, isProtein can be static methods in Java 8
+
+  /**
+   * Returns a distance score for the given sequence regions, that is, a matrix
+   * whose value [i][j] is the distance of sequence i from sequence j by some
+   * measure. The options parameter provides configuration choices for how the
+   * similarity score is calculated.
+   * 
+   * @param seqData
+   * @param options
+   * @return
+   */
+
+  MatrixI findDistances(AlignmentView seqData, SimilarityParamsI options);
+
+  /**
+   * Returns a similarity score for the given sequence regions, that is, a
+   * matrix whose value [i][j] is the similarity of sequence i to sequence j by
+   * some measure. The options parameter provides configuration choices for how
+   * the similarity score is calculated.
+   * 
+   * @param seqData
+   * @param options
+   * @return
+   */
+  MatrixI findSimilarities(AlignmentView seqData, SimilarityParamsI options);
+
+  /**
+   * Returns a score model object configured for the given alignment view.
+   * Depending on the score model, this may just be a singleton instance, or a
+   * new instance configured with data from the view.
+   * 
+   * @param avp
+   * @return
+   */
+  ScoreModelI getInstance(AlignmentViewPanel avp);
 }
diff --git a/src/jalview/api/analysis/SimilarityParamsI.java b/src/jalview/api/analysis/SimilarityParamsI.java
new file mode 100644 (file)
index 0000000..581449f
--- /dev/null
@@ -0,0 +1,43 @@
+package jalview.api.analysis;
+
+/**
+ * A description of options when computing percentage identity of two aligned
+ * sequences
+ */
+public interface SimilarityParamsI
+{
+  /**
+   * Answers true if gap-gap aligned positions should be included in the
+   * calculation
+   * 
+   * @return
+   */
+  boolean includeGappedColumns();
+
+  /**
+   * Answers true if gap-residue alignment is considered a match
+   * 
+   * @return
+   */
+  // TODO is this specific to a PID score only?
+  // score matrix will compute whatever is configured for gap-residue
+  boolean matchGaps();
+
+  /**
+   * Answers true if gaps are included in the calculation. This may affect the
+   * calculated score, the denominator (normalisation factor) of the score, or
+   * both. Gap-gap positions are included if this and includeGappedColumns both
+   * answer true.
+   * 
+   * @return
+   */
+  boolean includeGaps();
+
+  /**
+   * Answers true if only the shortest sequence length is used to divide the
+   * total score, false if the longest sequence length
+   * 
+   * @return
+   */
+  boolean denominateByShortestLength();
+}
diff --git a/src/jalview/appletgui/#OverviewPanel.java# b/src/jalview/appletgui/#OverviewPanel.java#
new file mode 100755 (executable)
index 0000000..14c3158
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * 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.appletgui;
+
+import jalview.datamodel.SequenceI;
+import jalview.viewmodel.OverviewDimensions;
+import jalview.datamodel.AlignmentI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Panel;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+public class OverviewPanel extends Panel implements Runnable,
+        MouseMotionListener, MouseListener
+{
+  private OverviewDimensions od;
+
+  private Image miniMe;
+
+  private Image offscreen;
+
+  private AlignViewport av;
+
+  private AlignmentPanel ap;
+
+  private boolean resizing = false;
+
+  // This is set true if the user resizes whilst
+  // the overview is being calculated
+  private boolean resizeAgain = false;
+
+  // Can set different properties in this seqCanvas than
+  // main visible SeqCanvas
+  private SequenceRenderer sr;
+
+  private FeatureRenderer fr;
+
+  private Frame nullFrame;
+
+  public OverviewPanel(AlignmentPanel alPanel)
+  {
+    this.av = alPanel.av;
+    this.ap = alPanel;
+    setLayout(null);
+    nullFrame = new Frame();
+    nullFrame.addNotify();
+
+    sr = new SequenceRenderer(av);
+    sr.graphics = nullFrame.getGraphics();
+    sr.renderGaps = false;
+    sr.forOverview = true;
+    fr = new FeatureRenderer(av);
+
+    od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
+
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
+    addComponentListener(new ComponentAdapter()
+    {
+
+      @Override
+      public void componentResized(ComponentEvent evt)
+      {
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
+        {
+          updateOverviewImage();
+        }
+      }
+    });
+
+    addMouseMotionListener(this);
+
+    addMouseListener(this);
+
+    updateOverviewImage();
+
+  }
+
+  @Override
+  public void mouseEntered(MouseEvent evt)
+  {
+  }
+
+  @Override
+  public void mouseExited(MouseEvent evt)
+  {
+  }
+
+  @Override
+  public void mouseClicked(MouseEvent evt)
+  {
+  }
+
+  @Override
+  public void mouseMoved(MouseEvent evt)
+  {
+  }
+
+  @Override
+  public void mousePressed(MouseEvent evt)
+  {
+    mouseAction(evt);
+  }
+
+  @Override
+  public void mouseReleased(MouseEvent evt)
+  {
+    mouseAction(evt);
+  }
+
+  @Override
+  public void mouseDragged(MouseEvent evt)
+  {
+    mouseAction(evt);
+  }
+
+  private void mouseAction(MouseEvent evt)
+  {
+    od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av
+            .getRanges());
+    ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
+    ap.paintAlignment(false);
+  }
+
+  /**
+   * Updates the overview image when the related alignment panel is updated
+   */
+  public void updateOverviewImage()
+  {
+    if (resizing)
+    {
+      resizeAgain = true;
+      return;
+    }
+
+    if (av.isShowSequenceFeatures())
+    {
+      fr.transferSettings(ap.seqPanel.seqCanvas.fr);
+    }
+
+    resizing = true;
+
+    if ((getSize().width > 0) && (getSize().height > 0))
+    {
+      od.setWidth(getSize().width);
+      od.setHeight(getSize().height);
+    }
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    Thread thread = new Thread(this);
+    thread.start();
+    repaint();
+  }
+
+  @Override
+  public void run()
+  {
+    miniMe = null;
+
+    if (av.isShowSequenceFeatures())
+    {
+      fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
+    }
+
+    if (getSize().width > 0 && getSize().height > 0)
+    {
+      od.setWidth(getSize().width);
+      od.setHeight(getSize().height);
+    }
+
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
+    offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
+
+    Graphics mg = miniMe.getGraphics();
+<<<<<<< HEAD
+
+    int alwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getAbsoluteHeight();
+    float sampleCol = alwidth / (float) od.getWidth();
+    float sampleRow = alheight / (float) od.getSequencesHeight();
+=======
+    float sampleCol = (float) alwidth / (float) width;
+    float sampleRow = (float) alheight / (float) sequencesHeight;
+
+    int lastcol = 0, lastrow = 0;
+    int xstart = 0, ystart = 0;
+    Color color = Color.yellow;
+    int row, col, sameRow = 0, sameCol = 0;
+    jalview.datamodel.SequenceI seq;
+    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
+            .hasHiddenColumns();
+    boolean hiddenRow = false;
+    AlignmentI alignment = av.getAlignment();
+
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    for (row = 0; row <= sequencesHeight; row++)
+    {
+      if (resizeAgain)
+      {
+        break;
+      }
+      if ((int) (row * sampleRow) == lastrow)
+      {
+        sameRow++;
+        continue;
+      }
+
+      hiddenRow = false;
+      if (hasHiddenRows)
+      {
+        seq = alignment.getHiddenSequences().getHiddenSequence(lastrow);
+        if (seq == null)
+        {
+          int index = alignment.getHiddenSequences()
+                  .findIndexWithoutHiddenSeqs(lastrow);
+
+          seq = alignment.getSequenceAt(index);
+        }
+        else
+        {
+          hiddenRow = true;
+        }
+      }
+      else
+      {
+        seq = alignment.getSequenceAt(lastrow);
+      }
+
+      for (col = 0; col < width; col++)
+      {
+        if ((int) (col * sampleCol) == lastcol
+                && (int) (row * sampleRow) == lastrow)
+        {
+          sameCol++;
+          continue;
+        }
+
+        lastcol = (int) (col * sampleCol);
+
+        if (seq.getLength() > lastcol)
+        {
+          color = sr.getResidueColour(seq, lastcol, finder);
+        }
+        else
+        {
+          color = Color.white;
+        }
+
+        if (hiddenRow
+                || (hasHiddenCols && !av.getColumnSelection().isVisible(
+                        lastcol)))
+        {
+          color = color.darker().darker();
+        }
+
+        mg.setColor(color);
+        if (sameCol == 1 && sameRow == 1)
+        {
+          mg.drawLine(xstart, ystart, xstart, ystart);
+        }
+        else
+        {
+          mg.fillRect(xstart, ystart, sameCol, sameRow);
+        }
+>>>>>>> bug/JAL-2436featureRendererThreading
+
+    buildImage(sampleRow, sampleCol, mg);
+
+    if (av.isShowAnnotation())
+    {
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
+      {
+        mg.translate(col, od.getSequencesHeight());
+        ap.annotationPanel.renderer.drawGraph(mg,
+                av.getAlignmentConservationAnnotation(),
+                av.getAlignmentConservationAnnotation().annotations,
+                (int) (sampleCol) + 1, od.getGraphHeight(),
+                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+        mg.translate(-col, -od.getSequencesHeight());
+      }
+    }
+    System.gc();
+
+    resizing = false;
+
+    setBoxPosition();
+
+    if (resizeAgain)
+    {
+      resizeAgain = false;
+      updateOverviewImage();
+    }
+  }
+
+  /*
+   * Build the overview panel image
+   */
+  private void buildImage(float sampleRow, float sampleCol, Graphics mg)
+  {
+    int lastcol = 0;
+    int lastrow = 0;
+    int xstart = 0;
+    int ystart = 0;
+    Color color = Color.yellow;
+    int sameRow = 0;
+    int sameCol = 0;
+
+    SequenceI seq = null;
+
+    final boolean hasHiddenCols = av.hasHiddenColumns();
+    boolean hiddenRow = false;
+
+    for (int row = 0; row <= od.getSequencesHeight() && !resizeAgain; row++)
+    {
+      if ((int) (row * sampleRow) == lastrow)
+      {
+        sameRow++;
+      }
+      else
+      {
+        // get the sequence which would be at alignment index 'lastrow' if no
+        // columns were hidden, and determine whether it is hidden or not
+        hiddenRow = av.getAlignment().isHidden(lastrow);
+        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
+
+        for (int col = 0; col < od.getWidth(); col++)
+        {
+          if ((int) (col * sampleCol) == lastcol
+                  && (int) (row * sampleRow) == lastrow)
+          {
+            sameCol++;
+          }
+          else
+          {
+            lastcol = (int) (col * sampleCol);
+
+            color = getColumnColourFromSequence(seq, hiddenRow,
+                    hasHiddenCols, lastcol);
+
+            mg.setColor(color);
+            if (sameCol == 1 && sameRow == 1)
+            {
+              mg.drawLine(xstart, ystart, xstart, ystart);
+            }
+            else
+            {
+              mg.fillRect(xstart, ystart, sameCol, sameRow);
+            }
+
+            xstart = col;
+            sameCol = 1;
+          }
+        }
+        lastrow = (int) (row * sampleRow);
+        ystart = row;
+        sameRow = 1;
+      }
+    }
+  }
+
+  /*
+   * Find the colour of a sequence at a specified column position
+   */
+  private Color getColumnColourFromSequence(
+          jalview.datamodel.SequenceI seq, boolean hiddenRow,
+          boolean hasHiddenCols, int lastcol)
+  {
+    Color color;
+    if (seq.getLength() > lastcol)
+    {
+      color = sr.getResidueBoxColour(seq, lastcol);
+
+      if (av.isShowSequenceFeatures())
+      {
+        color = fr.findFeatureColour(color, seq, lastcol);
+      }
+    }
+    else
+    {
+      color = Color.white;
+    }
+
+    if (hiddenRow
+            || (hasHiddenCols && !av.getColumnSelection()
+                    .isVisible(lastcol)))
+    {
+      color = color.darker().darker();
+    }
+    return color;
+  }
+
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   */
+  public void setBoxPosition()
+  {
+    od.setBoxPosition(av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+    repaint();
+  }
+
+  @Override
+  public void update(Graphics g)
+  {
+    paint(g);
+  }
+
+  @Override
+  public void paint(Graphics g)
+  {
+    Graphics og = offscreen.getGraphics();
+    if (miniMe != null)
+    {
+      og.drawImage(miniMe, 0, 0, this);
+      og.setColor(Color.red);
+      od.drawBox(og);
+      g.drawImage(offscreen, 0, 0, this);
+    }
+  }
+
+}
index 8fd317a..77ec373 100644 (file)
@@ -65,6 +65,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -819,9 +820,9 @@ public class APopupMenu extends java.awt.PopupMenu implements
         return;
       }
 
-      int rsize = 0, gSize = sg.getSize();
-      SequenceI[] rseqs, seqs = new SequenceI[gSize];
-      SequenceFeature[] tfeatures, features = new SequenceFeature[gSize];
+      int gSize = sg.getSize();
+      List<SequenceI> seqs = new ArrayList<SequenceI>();
+      List<SequenceFeature> features = new ArrayList<SequenceFeature>();
 
       for (int i = 0; i < gSize; i++)
       {
@@ -829,25 +830,17 @@ public class APopupMenu extends java.awt.PopupMenu implements
         int end = sg.findEndRes(sg.getSequenceAt(i));
         if (start <= end)
         {
-          seqs[rsize] = sg.getSequenceAt(i);
-          features[rsize] = new SequenceFeature(null, null, null, start,
-                  end, "Jalview");
-          rsize++;
+          seqs.add(sg.getSequenceAt(i));
+          features.add(new SequenceFeature(null, null, null, start, end,
+                  "Jalview"));
         }
       }
-      rseqs = new SequenceI[rsize];
-      tfeatures = new SequenceFeature[rsize];
-      System.arraycopy(seqs, 0, rseqs, 0, rsize);
-      System.arraycopy(features, 0, tfeatures, 0, rsize);
-      features = tfeatures;
-      seqs = rseqs;
 
       if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
               features, true, ap))
       {
         ap.alignFrame.sequenceFeatures.setState(true);
         ap.av.setShowSequenceFeatures(true);
-        ;
         ap.highlightSearchResults(null);
       }
     }
index 6a0b390..2eed311 100644 (file)
@@ -22,6 +22,9 @@ package jalview.appletgui;
 
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.scoremodels.PIDModel;
+import jalview.analysis.scoremodels.ScoreModels;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
 import jalview.api.AlignViewportI;
@@ -43,6 +46,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
@@ -75,6 +79,7 @@ import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Canvas;
@@ -168,14 +173,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   }
 
   public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
-          ColumnSelection columnSelection, JalviewLite applet,
+          HiddenColumns hidden, JalviewLite applet,
           String title, boolean embedded)
   {
-    this(al, hiddenSeqs, columnSelection, applet, title, embedded, true);
+    this(al, hiddenSeqs, hidden, applet, title, embedded, true);
   }
 
   public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
-          ColumnSelection columnSelection, JalviewLite applet,
+          HiddenColumns hidden, JalviewLite applet,
           String title, boolean embedded, boolean addToDisplay)
   {
     if (applet != null)
@@ -218,9 +223,9 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       viewport.hideSequence(hiddenSeqs);
     }
-    if (columnSelection != null)
+    if (hidden != null)
     {
-      viewport.setColumnSelection(columnSelection);
+      viewport.getAlignment().setHiddenColumns(hidden);
     }
     viewport.setScaleAboveWrapped(scaleAbove.getState());
 
@@ -420,6 +425,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   @Override
   public void keyPressed(KeyEvent evt)
   {
+    ViewportRanges ranges = viewport.getRanges();
+
     if (viewport.cursorMode
             && ((evt.getKeyCode() >= KeyEvent.VK_0 && evt.getKeyCode() <= KeyEvent.VK_9) || (evt
                     .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt
@@ -571,8 +578,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
               new String[] { (viewport.cursorMode ? "on" : "off") }));
       if (viewport.cursorMode)
       {
-        alignPanel.seqPanel.seqCanvas.cursorX = viewport.startRes;
-        alignPanel.seqPanel.seqCanvas.cursorY = viewport.startSeq;
+        alignPanel.seqPanel.seqCanvas.cursorX = ranges.getStartRes();
+        alignPanel.seqPanel.seqCanvas.cursorY = ranges.getStartSeq();
       }
       break;
 
@@ -594,24 +601,22 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     case KeyEvent.VK_PAGE_UP:
       if (viewport.getWrapAlignment())
       {
-        alignPanel.scrollUp(true);
+        ranges.scrollUp(true);
       }
       else
       {
-        alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                - viewport.endSeq + viewport.startSeq);
+        ranges.pageUp();
       }
       break;
 
     case KeyEvent.VK_PAGE_DOWN:
       if (viewport.getWrapAlignment())
       {
-        alignPanel.scrollUp(false);
+        ranges.scrollUp(false);
       }
       else
       {
-        alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                + viewport.endSeq - viewport.startSeq);
+        ranges.pageDown();
       }
       break;
 
@@ -1068,6 +1073,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       delete_actionPerformed();
     }
+    else if (source == createGroup)
+    {
+      createGroup_actionPerformed();
+    }
+    else if (source == unGroup)
+    {
+      unGroup_actionPerformed();
+    }
     else if (source == grpsFromSelection)
     {
       makeGrpsFromSelection_actionPerformed();
@@ -1913,7 +1926,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       copiedHiddenColumns = new Vector();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-      for (int[] region : viewport.getColumnSelection().getHiddenColumns())
+      for (int[] region : viewport.getAlignment().getHiddenColumns()
+              .getHiddenRegions())
       {
         copiedHiddenColumns.addElement(new int[] {
             region[0] - hiddenOffset, region[1] - hiddenOffset });
@@ -2063,7 +2077,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
             seqs, 0, viewport.getAlignment().getWidth(),
             viewport.getAlignment()));
 
-    viewport.setEndSeq(viewport.getAlignment().getHeight());
+    viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight());
     viewport.getAlignment().getWidth();
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
@@ -2299,6 +2313,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   void trimAlignment(boolean trimLeft)
   {
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
     ColumnSelection colSel = viewport.getColumnSelection();
     int column;
 
@@ -2321,20 +2337,20 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       }
       else
       {
-        seqs = viewport.getAlignment().getSequencesArray();
+        seqs = al.getSequencesArray();
       }
 
       TrimRegionCommand trimRegion;
       if (trimLeft)
       {
         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
-                column, viewport.getAlignment());
-        viewport.setStartRes(0);
+                column, al);
+        ranges.setStartRes(0);
       }
       else
       {
         trimRegion = new TrimRegionCommand("Remove Right", false, seqs,
-                column, viewport.getAlignment());
+                column, al);
       }
 
       statusBar.setText(MessageManager.formatMessage(
@@ -2343,23 +2359,25 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
                       .toString() }));
       addHistoryItem(trimRegion);
 
-      for (SequenceGroup sg : viewport.getAlignment().getGroups())
+      for (SequenceGroup sg : al.getGroups())
       {
         if ((trimLeft && !sg.adjustForRemoveLeft(column))
                 || (!trimLeft && !sg.adjustForRemoveRight(column)))
         {
-          viewport.getAlignment().deleteGroup(sg);
+          al.deleteGroup(sg);
         }
       }
 
-      viewport.firePropertyChange("alignment", null, viewport
-              .getAlignment().getSequences());
+      viewport.firePropertyChange("alignment", null, al.getSequences());
     }
   }
 
   public void removeGappedColumnMenuItem_actionPerformed()
   {
-    int start = 0, end = viewport.getAlignment().getWidth() - 1;
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
+    int start = 0;
+    int end = ranges.getAbsoluteAlignmentWidth() - 1;
 
     SequenceI[] seqs;
     if (viewport.getSelectionGroup() != null)
@@ -2387,22 +2405,24 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     // This is to maintain viewport position on first residue
     // of first sequence
-    SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    SequenceI seq = al.getSequenceAt(0);
+    int startRes = seq.findPosition(ranges.getStartRes());
     // ShiftList shifts;
     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
     // edit.alColumnChanges=shifts.getInverse();
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
-    viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-            .getSequences());
+    ranges.setStartRes(seq.findIndex(startRes) - 1);
+    viewport.firePropertyChange("alignment", null, al.getSequences());
 
   }
 
   public void removeAllGapsMenuItem_actionPerformed()
   {
-    int start = 0, end = viewport.getAlignment().getWidth() - 1;
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
+    int start = 0;
+    int end = ranges.getAbsoluteAlignmentWidth() - 1;
 
     SequenceI[] seqs;
     if (viewport.getSelectionGroup() != null)
@@ -2419,16 +2439,15 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     // This is to maintain viewport position on first residue
     // of first sequence
-    SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    SequenceI seq = al.getSequenceAt(0);
+    int startRes = seq.findPosition(ranges.getStartRes());
 
     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
-            viewport.getAlignment()));
+            al));
 
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    ranges.setStartRes(seq.findIndex(startRes) - 1);
 
-    viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-            .getSequences());
+    viewport.firePropertyChange("alignment", null, al.getSequences());
 
   }
 
@@ -2673,9 +2692,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     boolean selected = conservationMenuItem.getState();
     modifyConservation.setEnabled(selected);
     viewport.setConservationSelected(selected);
-
-    // viewport.setAbovePIDThreshold(false);
-    // abovePIDThreshold.setState(false);
+    viewport.getResidueShading().setConservationApplied(selected);
 
     changeColour(viewport.getGlobalColourScheme());
 
@@ -2694,8 +2711,11 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     boolean selected = abovePIDThreshold.getState();
     modifyPID.setEnabled(selected);
     viewport.setAbovePIDThreshold(selected);
-    // conservationMenuItem.setState(false);
-    // viewport.setConservationSelected(false);
+    if (!selected)
+    {
+      viewport.getResidueShading().setThreshold(0,
+              viewport.isIgnoreGapsConsensus());
+    }
 
     changeColour(viewport.getGlobalColourScheme());
 
@@ -2713,7 +2733,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   {
     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
     AlignmentSorter.sortByPID(viewport.getAlignment(), viewport
-            .getAlignment().getSequenceAt(0), null);
+            .getAlignment().getSequenceAt(0));
 
     addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
             viewport.getAlignment()));
@@ -2805,25 +2825,31 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   public void averageDistanceTreeMenuItem_actionPerformed()
   {
-    NewTreePanel("AV", "PID", "Average distance tree using PID");
+    newTreePanel(TreeBuilder.AVERAGE_DISTANCE, new PIDModel().getName(),
+            "Average distance tree using PID");
   }
 
   public void neighbourTreeMenuItem_actionPerformed()
   {
-    NewTreePanel("NJ", "PID", "Neighbour joining tree using PID");
+    newTreePanel(TreeBuilder.NEIGHBOUR_JOINING, new PIDModel().getName(),
+            "Neighbour joining tree using PID");
   }
 
   protected void njTreeBlosumMenuItem_actionPerformed()
   {
-    NewTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
+    newTreePanel(TreeBuilder.NEIGHBOUR_JOINING, ScoreModels.getInstance()
+            .getBlosum62().getName(),
+            "Neighbour joining tree using BLOSUM62");
   }
 
   protected void avTreeBlosumMenuItem_actionPerformed()
   {
-    NewTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
+    newTreePanel(TreeBuilder.AVERAGE_DISTANCE, ScoreModels.getInstance()
+            .getBlosum62().getName(),
+            "Average distance tree using BLOSUM62");
   }
 
-  void NewTreePanel(String type, String pwType, String title)
+  void newTreePanel(String type, String pwType, String title)
   {
     // are the sequences aligned?
     if (!viewport.getAlignment().isAligned(false))
@@ -3329,7 +3355,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
             .getString("action.make_groups_selection"));
     grpsFromSelection.addActionListener(this);
     createGroup.setLabel(MessageManager.getString("action.create_group"));
+    createGroup.addActionListener(this);
     unGroup.setLabel(MessageManager.getString("action.remove_group"));
+    unGroup.addActionListener(this);
+
     annotationColumnSelection.setLabel(MessageManager
             .getString("action.select_by_annotation"));
     annotationColumnSelection.addActionListener(this);
@@ -4178,9 +4207,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
    * @param csel
    *          - columns to be selected on the alignment
    */
-  public void select(SequenceGroup sel, ColumnSelection csel)
+  public void select(SequenceGroup sel, ColumnSelection csel,
+          HiddenColumns hidden)
   {
-    alignPanel.seqPanel.selection(sel, csel, null);
+    alignPanel.seqPanel.selection(sel, csel, hidden, null);
   }
 
   public void scrollTo(int row, int column)
index fc087c6..73cd9e9 100644 (file)
  */
 package jalview.appletgui;
 
-import jalview.analysis.NJTree;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureSettingsModelI;
 import jalview.bin.JalviewLite;
 import jalview.commands.CommandI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.Sequence;
@@ -35,16 +36,16 @@ import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShader;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.UserColourScheme;
-import jalview.structure.CommandListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.Font;
+import java.awt.FontMetrics;
 
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, VamsasSource, CommandListener
+        SelectionSource
 {
   boolean cursorMode = false;
 
@@ -52,7 +53,7 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean validCharWidth = true;
 
-  NJTree currentTree = null;
+  TreeModel currentTree = null;
 
   public jalview.bin.JalviewLite applet;
 
@@ -71,16 +72,13 @@ public class AlignViewport extends AlignmentViewport implements
 
   public AlignViewport(AlignmentI al, JalviewLite applet)
   {
-    super();
+    super(al);
     calculator = new jalview.workers.AlignCalcManager();
     this.applet = applet;
-    alignment = al;
+
     // we always pad gaps
     this.setPadGaps(true);
-    this.startRes = 0;
-    this.endRes = al.getWidth() - 1;
-    this.startSeq = 0;
-    this.endSeq = al.getHeight() - 1;
+
     if (applet != null)
     {
       // get the width and height scaling factors if they were specified
@@ -131,7 +129,7 @@ public class AlignViewport extends AlignmentViewport implements
         }
       }
     }
-    setFont(font);
+    setFont(font, true);
 
     MAC = new jalview.util.Platform().isAMac();
 
@@ -151,6 +149,9 @@ public class AlignViewport extends AlignmentViewport implements
       showConsensus = applet.getDefaultParameter("showConsensus",
               showConsensus);
 
+      showOccupancy = applet.getDefaultParameter("showOccupancy",
+              showOccupancy);
+
       setShowUnconserved(applet.getDefaultParameter("showUnconserved",
               getShowUnconserved()));
 
@@ -271,7 +272,11 @@ public class AlignViewport extends AlignmentViewport implements
 
   private float heightScale = 1, widthScale = 1;
 
-  public void setFont(Font f)
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void setFont(Font f, boolean setGrid)
   {
     font = f;
     if (nullFrame == null)
@@ -280,14 +285,17 @@ public class AlignViewport extends AlignmentViewport implements
       nullFrame.addNotify();
     }
 
-    java.awt.FontMetrics fm = nullFrame.getGraphics().getFontMetrics(font);
-    setCharHeight((int) (heightScale * fm.getHeight()));
-    setCharWidth((int) (widthScale * fm.charWidth('M')));
+    if (setGrid)
+    {
+      FontMetrics fm = nullFrame.getGraphics().getFontMetrics(font);
+      setCharHeight((int) (heightScale * fm.getHeight()));
+      setCharWidth((int) (widthScale * fm.charWidth('M')));
+    }
 
     if (isUpperCasebold())
     {
       Font f2 = new Font(f.getName(), Font.BOLD, f.getSize());
-      fm = nullFrame.getGraphics().getFontMetrics(f2);
+      FontMetrics fm = nullFrame.getGraphics().getFontMetrics(f2);
       setCharWidth((int) (widthScale * (fm.stringWidth("MMMMMMMMMMM") / 10)));
     }
   }
@@ -299,15 +307,15 @@ public class AlignViewport extends AlignmentViewport implements
 
   public void resetSeqLimits(int height)
   {
-    setEndSeq(height / getCharHeight());
+    ranges.setEndSeq(height / getCharHeight());
   }
 
-  public void setCurrentTree(NJTree tree)
+  public void setCurrentTree(TreeModel tree)
   {
     currentTree = tree;
   }
 
-  public NJTree getCurrentTree()
+  public TreeModel getCurrentTree()
   {
     return currentTree;
   }
@@ -335,7 +343,8 @@ public class AlignViewport extends AlignmentViewport implements
   {
     getStructureSelectionManager().sendSelection(
             new SequenceGroup(getSelectionGroup()),
-            new ColumnSelection(getColumnSelection()), this);
+            new ColumnSelection(getColumnSelection()),
+            new HiddenColumns(getAlignment().getHiddenColumns()), this);
   }
 
   /**
@@ -440,8 +449,9 @@ public class AlignViewport extends AlignmentViewport implements
     int seqOffset = findComplementScrollTarget(sr);
     if (!sr.isEmpty())
     {
-      complementPanel.setFollowingComplementScroll(true);
+      complementPanel.setToScrollComplementPanel(false);
       complementPanel.scrollToCentre(sr, seqOffset);
+      complementPanel.setToScrollComplementPanel(true);
     }
   }
 
index e97c347..e402b9b 100644 (file)
@@ -28,6 +28,8 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
 import jalview.structure.StructureSelectionManager;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -41,10 +43,11 @@ import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
 public class AlignmentPanel extends Panel implements AdjustmentListener,
-        AlignmentViewPanel
+        AlignmentViewPanel, ViewportListenerI
 {
 
   public AlignViewport av;
@@ -65,6 +68,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   AnnotationLabels alabels;
 
+  ViewportRanges vpRanges;
+
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
 
@@ -73,6 +78,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   {
     alignFrame = null;
     av = null;
+    vpRanges = null;
     seqPanel = null;
     seqPanelHolder = null;
     sequenceHolderPanel = null;
@@ -96,6 +102,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
     alignFrame = af;
     this.av = av;
+    vpRanges = av.getRanges();
     seqPanel = new SeqPanel(av, this);
     idPanel = new IdPanel(av, this);
     scalePanel = new ScalePanel(av, this);
@@ -126,7 +133,26 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        setScrollValues(av.getStartRes(), av.getStartSeq());
+        // reset the viewport ranges when the alignment panel is resized
+        // in particular, this initialises the end residue value when Jalview
+        // is initialised
+        if (av.getWrapAlignment())
+        {
+          int widthInRes = seqPanel.seqCanvas
+                  .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
+          vpRanges.setViewportWidth(widthInRes);
+        }
+        else
+        {
+          int widthInRes = seqPanel.seqCanvas.getWidth()
+                  / av.getCharWidth();
+          int heightInSeq = seqPanel.seqCanvas.getHeight()
+                  / av.getCharHeight();
+
+          vpRanges.setViewportWidth(widthInRes);
+          vpRanges.setViewportHeight(heightInSeq);
+        }
+        // setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
         if (getSize().height > 0
                 && annotationPanelHolder.getSize().height > 0)
         {
@@ -159,6 +185,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
         }
       }
     });
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   @Override
@@ -225,11 +252,6 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     annotationPanel.repaint();
     validate();
     repaint();
-
-    if (overviewPanel != null)
-    {
-      overviewPanel.updateOverviewImage();
-    }
   }
 
   public void setIdWidth(int w, int h)
@@ -383,9 +405,10 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
        */
       if (centre)
       {
-        int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
+        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
         start = Math.max(start - offset, 0);
-        end = Math.min(end + offset, seq.getEnd() - 1);
+        end = end + offset - 1;
+        // end = Math.min(end + offset, seq.getEnd() - 1);
       }
 
       if (start < 0)
@@ -414,11 +437,12 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     int start = -1;
     if (av.hasHiddenColumns())
     {
-      start = av.getColumnSelection().findColumnPosition(ostart);
-      end = av.getColumnSelection().findColumnPosition(end);
+      AlignmentI al = av.getAlignment();
+      start = al.getHiddenColumns().findColumnPosition(ostart);
+      end = al.getHiddenColumns().findColumnPosition(end);
       if (start == end)
       {
-        if (!scrollToNearest && !av.getColumnSelection().isVisible(ostart))
+        if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart))
         {
           // don't scroll - position isn't visible
           return false;
@@ -468,33 +492,34 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       // setScrollValues(start, seqIndex);
       // }
       // logic copied from jalview.gui.AlignmentPanel:
-      if ((startv = av.getStartRes()) >= start)
+      if ((startv = vpRanges.getStartRes()) >= start)
       {
         /*
          * Scroll left to make start of search results visible
          */
         setScrollValues(start - 1, seqIndex);
       }
-      else if ((endv = av.getEndRes()) <= end)
+      else if ((endv = vpRanges.getEndRes()) <= end)
       {
         /*
          * Scroll right to make end of search results visible
          */
         setScrollValues(startv + 1 + end - endv, seqIndex);
       }
-      else if ((starts = av.getStartSeq()) > seqIndex)
+      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
       {
         /*
          * Scroll up to make start of search results visible
          */
-        setScrollValues(av.getStartRes(), seqIndex);
+        setScrollValues(vpRanges.getStartRes(), seqIndex);
       }
-      else if ((ends = av.getEndSeq()) <= seqIndex)
+      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
       {
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
+        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                + 1);
       }
       /*
        * Else results are already visible - no need to scroll
@@ -502,27 +527,13 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     }
     else
     {
-      scrollToWrappedVisible(start);
-    }
-    if (redrawOverview && overviewPanel != null)
-    {
-      overviewPanel.setBoxPosition();
+      vpRanges.scrollToWrappedVisible(start);
     }
+
     paintAlignment(redrawOverview);
     return true;
   }
 
-  void scrollToWrappedVisible(int res)
-  {
-    int cwidth = seqPanel.seqCanvas
-            .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-    if (res <= av.getStartRes() || res >= (av.getStartRes() + cwidth))
-    {
-      vscroll.setValue(res / cwidth);
-      av.startRes = vscroll.getValue() * cwidth;
-    }
-  }
-
   public OverviewPanel getOverviewPanel()
   {
     return overviewPanel;
@@ -632,8 +643,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   public void setWrapAlignment(boolean wrap)
   {
-    av.startSeq = 0;
-    av.startRes = 0;
+    vpRanges.setStartSeq(0);
+    vpRanges.setStartRes(0);
     scalePanelHolder.setVisible(!wrap);
 
     hscroll.setVisible(!wrap);
@@ -664,219 +675,175 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   int vextent = 0;
 
-  // return value is true if the scroll is valid
-  public boolean scrollUp(boolean up)
+  public void setScrollValues(int xpos, int ypos)
   {
-    if (up)
+    int x = xpos;
+    int y = ypos;
+
+    if (av.getWrapAlignment())
     {
-      if (vscroll.getValue() < 1)
-      {
-        return false;
-      }
-      setScrollValues(hscroll.getValue(), vscroll.getValue() - 1);
+      setScrollingForWrappedPanel(x);
     }
     else
     {
-      if (vextent + vscroll.getValue() >= av.getAlignment().getHeight())
-      {
-        return false;
-      }
-      setScrollValues(hscroll.getValue(), vscroll.getValue() + 1);
-    }
-
-    repaint();
-    return true;
-  }
+      int width = av.getAlignment().getWidth();
+      int height = av.getAlignment().getHeight();
 
-  public boolean scrollRight(boolean right)
-  {
-    if (!right)
-    {
-      if (hscroll.getValue() < 1)
+      if (av.hasHiddenColumns())
       {
-        return false;
+        width = av.getAlignment().getHiddenColumns()
+                .findColumnPosition(width);
       }
-      setScrollValues(hscroll.getValue() - 1, vscroll.getValue());
-    }
-    else
-    {
-      if (hextent + hscroll.getValue() >= av.getAlignment().getWidth())
+      if (x < 0)
       {
-        return false;
+        x = 0;
       }
-      setScrollValues(hscroll.getValue() + 1, vscroll.getValue());
-    }
-
-    repaint();
-    return true;
-  }
-
-  public void setScrollValues(int x, int y)
-  {
-    int width = av.getAlignment().getWidth();
-    int height = av.getAlignment().getHeight();
-
-    if (av.hasHiddenColumns())
-    {
-      width = av.getColumnSelection().findColumnPosition(width);
-    }
-    if (x < 0)
-    {
-      x = 0;
-    }
-    ;
-
-    hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
-    vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
-
-    if (hextent > width)
-    {
-      hextent = width;
-    }
 
-    if (vextent > height)
-    {
-      vextent = height;
-    }
+      hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
+      vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
 
-    if ((hextent + x) > width)
-    {
-      System.err.println("hextent was " + hextent + " and x was " + x);
+      if (hextent > width)
+      {
+        hextent = width;
+      }
 
-      x = width - hextent;
-    }
+      if (vextent > height)
+      {
+        vextent = height;
+      }
 
-    if ((vextent + y) > height)
-    {
-      y = height - vextent;
-    }
+      if ((hextent + x) > width)
+      {
+        System.err.println("hextent was " + hextent + " and x was " + x);
 
-    if (y < 0)
-    {
-      y = 0;
-    }
+        x = width - hextent;
+      }
 
-    if (x < 0)
-    {
-      System.err.println("x was " + x);
-      x = 0;
-    }
+      if ((vextent + y) > height)
+      {
+        y = height - vextent;
+      }
 
-    av.setStartSeq(y);
+      if (y < 0)
+      {
+        y = 0;
+      }
 
-    int endSeq = y + vextent;
-    if (endSeq > av.getAlignment().getHeight())
-    {
-      endSeq = av.getAlignment().getHeight();
-    }
+      if (x < 0)
+      {
+        System.err.println("x was " + x);
+        x = 0;
+      }
 
-    av.setEndSeq(endSeq);
-    av.setStartRes(x);
-    av.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
-            .getCharWidth())) - 1);
+      hscroll.setValues(x, hextent, 0, width);
+      vscroll.setValues(y, vextent, 0, height);
 
-    hscroll.setValues(x, hextent, 0, width);
-    vscroll.setValues(y, vextent, 0, height);
+      // AWT scrollbar does not fire adjustmentValueChanged for setValues
+      // so also call adjustment code!
+      adjustHorizontal(x);
+      adjustVertical(y);
 
-    if (overviewPanel != null)
-    {
-      overviewPanel.setBoxPosition();
+      sendViewPosition();
     }
-    sendViewPosition();
-
   }
 
+  /**
+   * Respond to adjustment event when horizontal or vertical scrollbar is
+   * changed
+   * 
+   * @param evt
+   *          adjustment event encoding whether apvscroll, hscroll or vscroll
+   *          changed
+   */
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = av.getStartRes();
-    int oldY = av.getStartSeq();
-
+    // Note that this event is NOT fired by the AWT scrollbar when setValues is
+    // called. Instead manually call adjustHorizontal and adjustVertical
+    // directly.
     if (evt == null || evt.getSource() == apvscroll)
     {
       annotationPanel.setScrollOffset(apvscroll.getValue(), false);
       alabels.setScrollOffset(apvscroll.getValue(), false);
-      // annotationPanel.image=null;
-      // alabels.image=null;
-      // alabels.repaint();
-      // annotationPanel.repaint();
     }
     if (evt == null || evt.getSource() == hscroll)
     {
       int x = hscroll.getValue();
-      av.setStartRes(x);
-      av.setEndRes(x + seqPanel.seqCanvas.getSize().width
-              / av.getCharWidth() - 1);
+      adjustHorizontal(x);
     }
 
     if (evt == null || evt.getSource() == vscroll)
     {
       int offy = vscroll.getValue();
-      if (av.getWrapAlignment())
-      {
-        int rowSize = seqPanel.seqCanvas
-                .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-        av.setStartRes(vscroll.getValue() * rowSize);
-        av.setEndRes((vscroll.getValue() + 1) * rowSize);
-      }
-      else
-      {
-        av.setStartSeq(offy);
-        av.setEndSeq(offy + seqPanel.seqCanvas.getSize().height
-                / av.getCharHeight());
-      }
+      adjustVertical(offy);
     }
 
-    if (overviewPanel != null)
+  }
+
+  private void adjustHorizontal(int x)
+  {
+    int oldX = vpRanges.getStartRes();
+    int oldwidth = vpRanges.getViewportWidth();
+    int width = seqPanel.seqCanvas.getWidth() / av.getCharWidth();
+
+    // if we're scrolling to the position we're already at, stop
+    // this prevents infinite recursion of events when the scroll/viewport
+    // ranges values are the same
+    if ((x == oldX) && (width == oldwidth))
     {
-      overviewPanel.setBoxPosition();
+      return;
     }
+    vpRanges.setViewportStartAndWidth(x, width);
 
-    int scrollX = av.startRes - oldX;
-    int scrollY = av.startSeq - oldY;
-
-    if (av.getWrapAlignment() || !fastPaint || av.MAC)
+    if (av.getWrapAlignment() || !fastPaint)
     {
       repaint();
     }
-    else
+    sendViewPosition();
+  }
+
+  private void adjustVertical(int offy)
+  {
+    int oldX = vpRanges.getStartRes();
+    int oldwidth = vpRanges.getViewportWidth();
+    int oldY = vpRanges.getStartSeq();
+    int oldheight = vpRanges.getViewportHeight();
+
+    if (av.getWrapAlignment())
     {
-      // Make sure we're not trying to draw a panel
-      // larger than the visible window
-      if (scrollX > av.endRes - av.startRes)
+      int rowSize = seqPanel.seqCanvas
+              .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
+
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((offy * rowSize == oldX) && (oldwidth == rowSize))
       {
-        scrollX = av.endRes - av.startRes;
+        return;
       }
-      else if (scrollX < av.startRes - av.endRes)
+      else if (offy > -1)
       {
-        scrollX = av.startRes - av.endRes;
+        vpRanges.setViewportStartAndWidth(offy * rowSize, rowSize);
       }
+    }
+    else
+    {
+      int height = seqPanel.seqCanvas.getHeight() / av.getCharHeight();
 
-      idPanel.idCanvas.fastPaint(scrollY);
-      seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
-
-      scalePanel.repaint();
-      if (av.isShowAnnotation())
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((offy == oldY) && (height == oldheight))
       {
-        annotationPanel.fastPaint(av.getStartRes() - oldX);
+        return;
       }
+      vpRanges.setViewportStartAndHeight(offy, height);
     }
-    sendViewPosition();
-
-    /*
-     * If there is one, scroll the (Protein/cDNA) complementary alignment to
-     * match, unless we are ourselves doing that.
-     */
-    if (isFollowingComplementScroll())
-    {
-      setFollowingComplementScroll(false);
-    }
-    else
+    if (av.getWrapAlignment() || !fastPaint)
     {
-      AlignmentPanel ap = getComplementPanel();
-      av.scrollComplementaryAlignment(ap);
+      repaint();
     }
-
+    sendViewPosition();
   }
 
   /**
@@ -927,7 +894,6 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
      * This is like AlignmentI.findIndex(seq) but here we are matching the
      * dataset sequence not the aligned sequence
      */
-    int sequenceIndex = 0;
     boolean matched = false;
     for (SequenceI seq : seqs)
     {
@@ -936,7 +902,6 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
         matched = true;
         break;
       }
-      sequenceIndex++;
     }
     if (!matched)
     {
@@ -947,16 +912,15 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
      * Scroll to position but centring the target residue. Also set a state flag
      * to prevent adjustmentValueChanged performing this recursively.
      */
-    setFollowingComplementScroll(true);
-    // this should be scrollToPosition(sr,verticalOffset,
     scrollToPosition(sr, seqOffset, true, true);
   }
 
   private void sendViewPosition()
   {
     StructureSelectionManager.getStructureSelectionManager(av.applet)
-            .sendViewPosition(this, av.startRes, av.endRes, av.startSeq,
-                    av.endSeq);
+            .sendViewPosition(this, vpRanges.getStartRes(),
+                    vpRanges.getEndRes(), vpRanges.getStartSeq(),
+                    vpRanges.getEndSeq());
   }
 
   /**
@@ -1002,30 +966,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       idPanel.idCanvas.setSize(d.width, canvasHeight);
     }
 
-    if (av.getWrapAlignment())
-    {
-      int maxwidth = av.getAlignment().getWidth();
-
-      if (av.hasHiddenColumns())
-      {
-        maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
-      }
-
-      int canvasWidth = seqPanel.seqCanvas
-              .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-
-      if (canvasWidth > 0)
-      {
-        int max = maxwidth / canvasWidth;
-        vscroll.setMaximum(1 + max);
-        vscroll.setUnitIncrement(1);
-        vscroll.setVisibleAmount(1);
-      }
-    }
-    else
-    {
-      setScrollValues(av.getStartRes(), av.getStartSeq());
-    }
+    setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
 
     seqPanel.seqCanvas.repaint();
     idPanel.idCanvas.repaint();
@@ -1041,6 +982,37 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   }
 
+  /*
+   * Set vertical scroll bar parameters for wrapped panel
+   * @param res 
+   *    the residue to scroll to
+   */
+  private void setScrollingForWrappedPanel(int res)
+  {
+    // get the width of the alignment in residues
+    int maxwidth = av.getAlignment().getWidth();
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
+    }
+
+    // get the width of the canvas in residues
+    int canvasWidth = seqPanel.seqCanvas
+            .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
+    if (canvasWidth > 0)
+    {
+      // position we want to scroll to is number of canvasWidth's to get there
+      int current = res / canvasWidth;
+
+      // max scroll position: add one because extent is 1 and scrollbar value
+      // can only be set to at most max - extent
+      int max = maxwidth / canvasWidth + 1;
+      vscroll.setUnitIncrement(1);
+      vscroll.setValues(current, 1, 0, max);
+    }
+  }
+
   protected Panel sequenceHolderPanel = new Panel();
 
   protected Scrollbar vscroll = new Scrollbar();
@@ -1065,9 +1037,9 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   /*
    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
-   * true, suppresses invoking the same method recursively.
+   * false, suppresses invoking the same method recursively.
    */
-  private boolean followingComplementScroll;
+  private boolean scrollComplementaryPanel = true;
 
   private void jbInit() throws Exception
   {
@@ -1176,14 +1148,42 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
    * 
    * @param b
    */
-  protected void setFollowingComplementScroll(boolean b)
+  protected void setToScrollComplementPanel(boolean b)
+  {
+    this.scrollComplementaryPanel = b;
+  }
+
+  /**
+   * Get whether to scroll complement panel
+   * 
+   * @return true if cDNA/protein complement panels should be scrolled
+   */
+  protected boolean isSetToScrollComplementPanel()
   {
-    this.followingComplementScroll = b;
+    return this.scrollComplementaryPanel;
   }
 
-  protected boolean isFollowingComplementScroll()
+  @Override
+  /**
+   * Property change event fired when a change is made to the viewport ranges 
+   * object associated with this alignment panel's viewport
+   */
+  public void propertyChange(PropertyChangeEvent evt)
   {
-    return this.followingComplementScroll;
+    // update this panel's scroll values based on the new viewport ranges values
+    int x = vpRanges.getStartRes();
+    int y = vpRanges.getStartSeq();
+    setScrollValues(x, y);
+
+    // now update any complementary alignment (its viewport ranges object
+    // is different so does not get automatically updated)
+    if (isSetToScrollComplementPanel())
+    {
+      setToScrollComplementPanel(false);
+      av.scrollComplementaryAlignment(getComplementPanel());
+      setToScrollComplementPanel(true);
+    }
+
   }
 
 }
index 487b75c..f516bc9 100644 (file)
@@ -46,7 +46,8 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
-import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Vector;
 
 public class AnnotationColourChooser extends Panel implements
@@ -60,9 +61,15 @@ public class AnnotationColourChooser extends Panel implements
 
   ColourSchemeI oldcs;
 
-  Hashtable oldgroupColours;
+  Map<SequenceGroup, ColourSchemeI> oldgroupColours;
 
-  jalview.datamodel.AlignmentAnnotation currentAnnotation;
+  /*
+   * map from annotation to its menu item display label
+   * - so we know which item to pre-select on restore
+   */
+  private Map<AlignmentAnnotation, String> annotationLabels;
+
+  AlignmentAnnotation currentAnnotation;
 
   boolean adjusting = false;
 
@@ -78,17 +85,10 @@ public class AnnotationColourChooser extends Panel implements
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new Hashtable();
+      oldgroupColours = new HashMap<SequenceGroup, ColourSchemeI>();
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-        if (sg.getColourScheme() != null)
-        {
-          oldgroupColours.put(sg, sg.getColourScheme());
-        }
-        else
-        {
-          oldgroupColours.put(sg, "null");
-        }
+        oldgroupColours.put(sg, sg.getColourScheme());
       }
     }
     this.av = av;
@@ -119,24 +119,7 @@ public class AnnotationColourChooser extends Panel implements
       // seqAssociated.setState(acg.isSeqAssociated());
     }
 
-    Vector<String> list = new Vector<String>();
-    int index = 1;
-    for (int i = 0; i < anns.length; i++)
-    {
-      String label = anns[i].label;
-      if (anns[i].sequenceRef != null)
-      {
-        label = label + "_" + anns[i].sequenceRef.getName();
-      }
-      if (!list.contains(label))
-      {
-        list.addElement(label);
-      }
-      else
-      {
-        list.addElement(label + "_" + (index++));
-      }
-    }
+    Vector<String> list = getAnnotationItems();
 
     for (int i = 0; i < list.size(); i++)
     {
@@ -153,7 +136,8 @@ public class AnnotationColourChooser extends Panel implements
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      annotations.select(acg.getAnnotation());
+      String label = annotationLabels.get(acg.getAnnotation());
+      annotations.select(label);
       switch (acg.getAboveThreshold())
       {
       case AnnotationColourGradient.NO_THRESHOLD:
@@ -170,7 +154,7 @@ public class AnnotationColourChooser extends Panel implements
                 MessageManager
                         .getString("error.implementation_error_dont_know_threshold_annotationcolourgradient"));
       }
-      thresholdIsMin.setState(acg.thresholdIsMinMax);
+      thresholdIsMin.setState(acg.isThresholdIsMinMax());
       thresholdValue.setText("" + acg.getAnnotationThreshold());
     }
 
@@ -186,6 +170,51 @@ public class AnnotationColourChooser extends Panel implements
     validate();
   }
 
+  /**
+   * Builds and returns a list of menu items (display text) for choice of
+   * annotation. Also builds a map between annotations and their display labels.
+   * 
+   * @return
+   */
+  protected Vector<String> getAnnotationItems()
+  {
+    // TODO remove duplication with gui.AnnotationRowFilter
+    // TODO add 'per sequence only' option / parameter
+
+    annotationLabels = new HashMap<AlignmentAnnotation, String>();
+    Vector<String> list = new Vector<String>();
+    AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
+    if (anns == null)
+    {
+      return list;
+    }
+    int index = 1;
+    for (int i = 0; i < anns.length; i++)
+    {
+      String label = anns[i].label;
+      if (anns[i].sequenceRef != null)
+      {
+        /*
+         * be helpful and include sequence id in label for
+         * sequence-associated annotation (JAL-2236)
+         */
+        label = label + "_" + anns[i].sequenceRef.getName();
+      }
+      if (!list.contains(label))
+      {
+        list.addElement(label);
+        annotationLabels.put(anns[i], label);
+      }
+      else
+      {
+        label = label + "_" + (index++);
+        list.addElement(label);
+        annotationLabels.put(anns[i], label);
+      }
+    }
+    return list;
+  }
+
   private void setDefaultMinMax()
   {
     minColour.setBackground(av.applet.getDefaultColourParameter(
@@ -501,7 +530,7 @@ public class AnnotationColourChooser extends Panel implements
       acg.setPredefinedColours(true);
     }
 
-    acg.thresholdIsMinMax = thresholdIsMin.getState();
+    acg.setThresholdIsMinMax(thresholdIsMin.getState());
 
     av.setGlobalColourScheme(acg);
 
@@ -510,7 +539,6 @@ public class AnnotationColourChooser extends Panel implements
     {
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-
         if (sg.getColourScheme() == null)
         {
           continue;
@@ -527,7 +555,6 @@ public class AnnotationColourChooser extends Panel implements
                   currentAnnotation, minColour.getBackground(), maxColour
                           .getBackground(), aboveThreshold));
         }
-
       }
     }
 
@@ -543,20 +570,10 @@ public class AnnotationColourChooser extends Panel implements
     {
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-        Object cs = oldgroupColours.get(sg);
-        if (cs instanceof ColourSchemeI)
-        {
-          sg.setColourScheme((ColourSchemeI) cs);
-        }
-        else
-        {
-          // probably the "null" string we set it to if it was null originally.
-          sg.setColourScheme(null);
-        }
+        sg.setColourScheme(oldgroupColours.get(sg));
       }
     }
     ap.paintAlignment(true);
-
   }
 
   @Override
index a8dff62..60775d3 100644 (file)
@@ -21,7 +21,7 @@
 package jalview.appletgui;
 
 import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.util.MessageManager;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
@@ -110,7 +110,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
   private int actionOption = ACTION_OPTION_SELECT;
 
-  private ColumnSelection oldColumnSelection;
+  private HiddenColumns oldHiddenColumns;
 
   public AnnotationColumnChooser()
   {
@@ -140,7 +140,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     {
       return;
     }
-    setOldColumnSelection(av.getColumnSelection());
+    setOldHiddenColumns(av.getAlignment().getHiddenColumns());
     adjusting = true;
     Vector<String> list = new Vector<String>();
     int index = 1;
@@ -167,20 +167,22 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     }
 
     populateThresholdComboBox(threshold);
-
+    AnnotationColumnChooser lastChooser = av
+            .getAnnotationColumnSelectionState();
     // restore Object state from the previous session if one exists
-    if (av.getAnnotationColumnSelectionState() != null)
+    if (lastChooser != null)
     {
-      currentSearchPanel = av.getAnnotationColumnSelectionState()
+      currentSearchPanel = lastChooser
               .getCurrentSearchPanel();
-      currentStructureFilterPanel = av.getAnnotationColumnSelectionState()
+      currentStructureFilterPanel = lastChooser
               .getCurrentStructureFilterPanel();
-      annotations.select(av.getAnnotationColumnSelectionState()
+      annotations.select(lastChooser
               .getAnnotations().getSelectedIndex());
-      threshold.select(av.getAnnotationColumnSelectionState()
+      threshold.select(lastChooser
               .getThreshold().getSelectedIndex());
-      actionOption = av.getAnnotationColumnSelectionState()
+      actionOption = lastChooser
               .getActionOption();
+      percentThreshold.setState(lastChooser.percentThreshold.getState());
     }
 
     try
@@ -204,6 +206,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     thresholdValue.setEnabled(false);
     thresholdValue.setColumns(7);
+    thresholdValue.setCaretPosition(0);
 
     ok.addActionListener(this);
     cancel.addActionListener(this);
@@ -219,6 +222,9 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     // thresholdPanel.setFont(JvSwingUtils.getLabelFont());
     // thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]"));
 
+    percentThreshold.setLabel("As percentage");
+    percentThreshold.addItemListener(this);
+
     actionPanel.setBackground(Color.white);
     // actionPanel.setFont(JvSwingUtils.getLabelFont());
 
@@ -242,6 +248,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     thresholdPanel.add(getThreshold());
     thresholdPanel.add(slider);
     thresholdPanel.add(thresholdValue);
+    thresholdPanel.add(percentThreshold);
 
     actionPanel.add(ok);
     actionPanel.add(cancel);
@@ -282,26 +289,26 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
   @SuppressWarnings("unchecked")
   public void reset()
   {
-    if (this.getOldColumnSelection() != null)
+    if (this.getOldHiddenColumns() != null)
     {
       av.getColumnSelection().clear();
 
       if (av.getAnnotationColumnSelectionState() != null)
       {
-        ColumnSelection oldSelection = av
+        HiddenColumns oldHidden = av
                 .getAnnotationColumnSelectionState()
-                .getOldColumnSelection();
-        if (oldSelection != null && oldSelection.getHiddenColumns() != null
-                && !oldSelection.getHiddenColumns().isEmpty())
+                .getOldHiddenColumns();
+        if (oldHidden != null && oldHidden.getHiddenRegions() != null
+                && !oldHidden.getHiddenRegions().isEmpty())
         {
-          for (Iterator<int[]> itr = oldSelection.getHiddenColumns()
+          for (Iterator<int[]> itr = oldHidden.getHiddenRegions()
                   .iterator(); itr.hasNext();)
           {
             int positions[] = itr.next();
             av.hideColumns(positions[0], positions[1]);
           }
         }
-        av.setColumnSelection(oldSelection);
+        av.getAlignment().setHiddenColumns(oldHidden);
       }
       ap.paintAlignment(true);
     }
@@ -313,7 +320,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
   {
     if (!adjusting)
     {
-      thresholdValue.setText((slider.getValue() / 1000f) + "");
+      setThresholdValueText();
       valueChanged(!sliderDragging);
     }
   }
@@ -399,12 +406,14 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     slider.setEnabled(true);
     thresholdValue.setEnabled(true);
+    percentThreshold.setEnabled(true);
 
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
       slider.setEnabled(false);
       thresholdValue.setEnabled(false);
       thresholdValue.setText("");
+      percentThreshold.setEnabled(false);
       // build filter params
     }
     else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
@@ -425,16 +434,17 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
       slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
       slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
-      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
+      setThresholdValueText();
       // slider.setMajorTickSpacing((int) (range / 10f));
       slider.setEnabled(true);
       thresholdValue.setEnabled(true);
+      percentThreshold.setEnabled(true);
       adjusting = false;
 
       // build filter params
       filterParams
               .setThresholdType(AnnotationFilterParameter.ThresholdType.NO_THRESHOLD);
-      if (getCurrentAnnotation().graph != AlignmentAnnotation.NO_GRAPH)
+      if (getCurrentAnnotation().isQuantitative())
       {
         filterParams
                 .setThresholdValue(getCurrentAnnotation().threshold.value);
@@ -508,16 +518,16 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     ap.paintAlignment(true);
   }
 
-  public ColumnSelection getOldColumnSelection()
+  public HiddenColumns getOldHiddenColumns()
   {
-    return oldColumnSelection;
+    return oldHiddenColumns;
   }
 
-  public void setOldColumnSelection(ColumnSelection currentColumnSelection)
+  public void setOldHiddenColumns(HiddenColumns currentHiddenColumns)
   {
-    if (currentColumnSelection != null)
+    if (currentHiddenColumns != null)
     {
-      this.oldColumnSelection = new ColumnSelection(currentColumnSelection);
+      this.oldHiddenColumns = new HiddenColumns(currentHiddenColumns);
     }
   }
 
@@ -574,13 +584,21 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     {
       threshold_actionPerformed(null);
     }
+    else if (e.getSource() == percentThreshold)
+    {
+      if (!adjusting)
+      {
+        percentageValue_actionPerformed();
+      }
+
+    }
   }
 
   public void selectedAnnotationChanged()
   {
     String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW;
     if (av.getAlignment().getAlignmentAnnotation()[getAnnotations()
-            .getSelectedIndex()].graph != AlignmentAnnotation.NO_GRAPH)
+            .getSelectedIndex()].isQuantitative())
     {
       currentView = AnnotationColumnChooser.GRAPH_VIEW;
     }
@@ -877,19 +895,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
   @Override
   public void actionPerformed(ActionEvent evt)
   {
-    if (evt.getSource() == thresholdValue)
-    {
-      try
-      {
-        float f = new Float(thresholdValue.getText()).floatValue();
-        slider.setValue((int) (f * 1000));
-        adjustmentValueChanged(null);
-      } catch (NumberFormatException ex)
-      {
-      }
-    }
 
-    else if (evt.getSource() == ok)
+    if (evt.getSource() == ok)
     {
       ok_actionPerformed(null);
     }
index b28ccc7..307301d 100755 (executable)
@@ -339,7 +339,8 @@ public class AnnotationLabels extends Panel implements ActionListener,
                 av.calcPanelHeight());
         f.height += dif;
         ap.seqPanelHolder.setPreferredSize(f);
-        ap.setScrollValues(av.getStartRes(), av.getStartSeq());
+        ap.setScrollValues(av.getRanges().getStartRes(), av.getRanges()
+                .getStartSeq());
         ap.validate();
         // ap.paintAlignment(true);
         ap.addNotify();
@@ -838,7 +839,8 @@ public class AnnotationLabels extends Panel implements ActionListener,
     if (av.hasHiddenColumns())
     {
       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
-      for (int[] region : av.getColumnSelection().getHiddenColumns())
+      for (int[] region : av.getAlignment().getHiddenColumns()
+              .getHiddenRegions())
       {
         jalview.appletgui.AlignFrame.copiedHiddenColumns
                 .addElement(new int[] { region[0], region[1] });
index 0f8baaf..e2d7e82 100755 (executable)
@@ -29,6 +29,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.viewmodel.ViewportListenerI;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -47,10 +48,11 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
 
 public class AnnotationPanel extends Panel implements AwtRenderPanelI,
         AdjustmentListener, ActionListener, MouseListener,
-        MouseMotionListener
+        MouseMotionListener, ViewportListenerI
 {
   AlignViewport av;
 
@@ -122,6 +124,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
   {
     this.av = av;
     renderer = new AnnotationRenderer();
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   @Override
@@ -165,7 +168,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
     {
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (av.getColumnSelection().isVisible(index))
+        if (av.getAlignment().getHiddenColumns().isVisible(index))
         {
           anot[index] = null;
         }
@@ -189,7 +192,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       {
         // TODO: JAL-2001 - provide a fast method to list visible selected
         // columns
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -211,7 +214,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -271,7 +274,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -462,11 +465,13 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       }
     }
 
-    int column = evt.getX() / av.getCharWidth() + av.getStartRes();
+    int column = evt.getX() / av.getCharWidth()
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
-      column = av.getColumnSelection().adjustForHiddenColumns(column);
+      column = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(column);
     }
 
     if (row > -1 && column < aa[row].annotations.length
@@ -618,7 +623,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
     gg.setColor(Color.white);
     gg.fillRect(0, 0, getSize().width, getSize().height);
-    drawComponent(gg, av.startRes, av.endRes + 1);
+    drawComponent(gg, av.getRanges().getStartRes(), av.getRanges()
+            .getEndRes() + 1);
 
     g.drawImage(image, 0, 0, this);
   }
@@ -635,7 +641,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
     gg.copyArea(0, 0, imgWidth, getSize().height,
             -horizontal * av.getCharWidth(), 0);
-    int sr = av.startRes, er = av.endRes + 1, transX = 0;
+    int sr = av.getRanges().getStartRes(), er = av.getRanges().getEndRes() + 1, transX = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
@@ -747,4 +753,15 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       return null;
     }
   }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport range changes (e.g. alignment panel was scrolled)
+    if (evt.getPropertyName().equals("startres")
+            || evt.getPropertyName().equals("endres"))
+    {
+      fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+  }
 }
index 8d67d71..315ce3b 100644 (file)
@@ -31,7 +31,6 @@ import java.awt.Panel;
 import java.awt.Scrollbar;
 import java.awt.TextField;
 import java.awt.event.ActionEvent;
-import java.util.Vector;
 
 @SuppressWarnings("serial")
 public abstract class AnnotationRowFilter extends Panel
@@ -60,6 +59,8 @@ public abstract class AnnotationRowFilter extends Panel
 
   protected Scrollbar slider = new Scrollbar(Scrollbar.HORIZONTAL);
 
+  protected Checkbox percentThreshold = new Checkbox();
+
   protected TextField thresholdValue = new TextField(20);
 
   protected Frame frame;
@@ -132,18 +133,54 @@ public abstract class AnnotationRowFilter extends Panel
     updateView();
   }
 
+  /**
+   * update the text field from the threshold slider. preserves state of
+   * 'adjusting' so safe to call in init.
+   */
+  protected void setThresholdValueText()
+  {
+    boolean oldadj = adjusting;
+    adjusting = true;
+    if (percentThreshold.getState())
+    {
+      double scl = slider.getMaximum() - slider.getMinimum();
+      scl = (slider.getValue() - slider.getMinimum()) / scl;
+      thresholdValue.setText(100f * scl + "");
+    }
+    else
+    {
+      thresholdValue.setText((slider.getValue() / 1000f) + "");
+    }
+    thresholdValue.setCaretPosition(0);
+    adjusting = oldadj;
+  }
+  
   public void thresholdValue_actionPerformed(ActionEvent e)
   {
     try
     {
       float f = Float.parseFloat(thresholdValue.getText());
-      slider.setValue((int) (f * 1000));
-      updateView();
+      if (percentThreshold.getState())
+      {
+        int pos = slider.getMinimum()
+                + (int) ((slider.getMaximum() - slider.getMinimum()) * f / 100f);
+        slider.setValue(pos);
+      }
+      else
+      {
+        slider.setValue((int) (f * 1000));
+      }
+      valueChanged(false);
     } catch (NumberFormatException ex)
     {
     }
   }
 
+  protected void percentageValue_actionPerformed()
+  {
+    setThresholdValueText();
+  }
+
   protected void populateThresholdComboBox(Choice threshold)
   {
     threshold.addItem(MessageManager
index f938cad..9b8a235 100644 (file)
@@ -54,21 +54,7 @@ class AppletJmolBinding extends JalviewJmolBinding
   public jalview.api.FeatureRenderer getFeatureRenderer(
           AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (appletJmolBinding.ap.av.isShowSequenceFeatures())
-    {
-      if (appletJmolBinding.fr == null)
-      {
-        appletJmolBinding.fr = new jalview.appletgui.FeatureRenderer(
-                appletJmolBinding.ap.av);
-      }
-
-      appletJmolBinding.fr
-              .transferSettings(appletJmolBinding.ap.seqPanel.seqCanvas
-                      .getFeatureRenderer());
-    }
-
-    return appletJmolBinding.fr;
+    return appletJmolBinding.ap.getFeatureRenderer();
   }
 
   @Override
index c658734..1e806a5 100644 (file)
@@ -25,7 +25,7 @@ import jalview.api.ComplexAlignFile;
 import jalview.api.FeaturesSourceI;
 import jalview.bin.JalviewLite;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.AlignmentFileReaderI;
@@ -257,8 +257,8 @@ public class CutAndPasteTransfer extends Panel implements ActionListener,
 
         if (source instanceof ComplexAlignFile)
         {
-          ColumnSelection colSel = ((ComplexAlignFile) source)
-                  .getColumnSelection();
+            HiddenColumns colSel = ((ComplexAlignFile) source)
+                    .getHiddenColumns();
           SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
                   .getHiddenSequences();
           boolean showSeqFeatures = ((ComplexAlignFile) source)
index 189fe88..b369318 100644 (file)
@@ -82,10 +82,10 @@ public class ExtJmol extends JalviewJmolBinding
   @Override
   public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
+    AlignmentPanel alignPanel = (AlignmentPanel) alignment;
+    if (alignPanel.av.isShowSequenceFeatures())
     {
-      return ap.getFeatureRenderer();
+      return alignPanel.getFeatureRenderer();
     }
     else
     {
index 67ca8e9..3c2715f 100644 (file)
@@ -36,7 +36,9 @@ import java.awt.Button;
 import java.awt.Choice;
 import java.awt.Color;
 import java.awt.Dimension;
+import java.awt.FlowLayout;
 import java.awt.Font;
+import java.awt.Frame;
 import java.awt.Graphics;
 import java.awt.GridLayout;
 import java.awt.Label;
@@ -48,7 +50,10 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.awt.event.TextEvent;
+import java.awt.event.TextListener;
 import java.util.Hashtable;
+import java.util.List;
 
 /**
  * DOCUMENT ME!
@@ -59,6 +64,13 @@ import java.util.Hashtable;
 public class FeatureRenderer extends
         jalview.renderer.seqfeatures.FeatureRenderer
 {
+  /*
+   * creating a new feature defaults to the type and group as
+   * the last one created
+   */
+  static String lastFeatureAdded = "feature_1";
+
+  static String lastFeatureGroupAdded = "Jalview";
 
   // Holds web links for feature groups and feature types
   // in the form label|link
@@ -75,12 +87,6 @@ public class FeatureRenderer extends
 
   }
 
-  static String lastFeatureAdded;
-
-  static String lastFeatureGroupAdded;
-
-  static String lastDescriptionAdded;
-
   int featureIndex = 0;
 
   boolean deleteFeature = false;
@@ -165,13 +171,25 @@ public class FeatureRenderer extends
 
   }
 
-  boolean amendFeatures(final SequenceI[] sequences,
-          final SequenceFeature[] features, boolean newFeatures,
+  /**
+   * Shows a dialog allowing the user to create, or amend or delete, sequence
+   * features. If null in the supplied feature(s), feature type and group
+   * default to those for the last feature created (with initial defaults of
+   * "feature_1" and "Jalview").
+   * 
+   * @param sequences
+   * @param features
+   * @param create
+   * @param ap
+   * @return
+   */
+  boolean amendFeatures(final List<SequenceI> sequences,
+          final List<SequenceFeature> features, boolean create,
           final AlignmentPanel ap)
   {
-    Panel bigPanel = new Panel(new BorderLayout());
+    final Panel bigPanel = new Panel(new BorderLayout());
     final TextField name = new TextField(16);
-    final TextField source = new TextField(16);
+    final TextField group = new TextField(16);
     final TextArea description = new TextArea(3, 35);
     final TextField start = new TextField(8);
     final TextField end = new TextField(8);
@@ -179,6 +197,22 @@ public class FeatureRenderer extends
     Button deleteButton = new Button("Delete");
     deleteFeature = false;
 
+    name.addTextListener(new TextListener()
+    {
+      @Override
+      public void textValueChanged(TextEvent e)
+      {
+        warnIfTypeHidden(ap.alignFrame, name.getText());
+      }
+    });
+    group.addTextListener(new TextListener()
+    {
+      @Override
+      public void textValueChanged(TextEvent e)
+      {
+        warnIfGroupHidden(ap.alignFrame, group.getText());
+      }
+    });
     colourPanel = new FeatureColourPanel();
     colourPanel.setSize(110, 15);
     final FeatureRenderer fr = this;
@@ -190,22 +224,20 @@ public class FeatureRenderer extends
 
     // /////////////////////////////////////
     // /MULTIPLE FEATURES AT SELECTED RESIDUE
-    if (!newFeatures && features.length > 1)
+    if (!create && features.size() > 1)
     {
       panel = new Panel(new GridLayout(4, 1));
       tmp = new Panel();
       tmp.add(new Label("Select Feature: "));
       overlaps = new Choice();
-      for (int i = 0; i < features.length; i++)
+      for (SequenceFeature sf : features)
       {
-        String item = features[i].getType() + "/" + features[i].getBegin()
-                + "-" + features[i].getEnd();
-
-        if (features[i].getFeatureGroup() != null)
+        String item = sf.getType() + "/" + sf.getBegin() + "-"
+                + sf.getEnd();
+        if (sf.getFeatureGroup() != null)
         {
-          item += " (" + features[i].getFeatureGroup() + ")";
+          item += " (" + sf.getFeatureGroup() + ")";
         }
-
         overlaps.addItem(item);
       }
 
@@ -220,15 +252,16 @@ public class FeatureRenderer extends
           if (index != -1)
           {
             featureIndex = index;
-            name.setText(features[index].getType());
-            description.setText(features[index].getDescription());
-            source.setText(features[index].getFeatureGroup());
-            start.setText(features[index].getBegin() + "");
-            end.setText(features[index].getEnd() + "");
+            SequenceFeature sf = features.get(index);
+            name.setText(sf.getType());
+            description.setText(sf.getDescription());
+            group.setText(sf.getFeatureGroup());
+            start.setText(sf.getBegin() + "");
+            end.setText(sf.getEnd() + "");
 
             SearchResultsI highlight = new SearchResults();
-            highlight.addResult(sequences[0], features[index].getBegin(),
-                    features[index].getEnd());
+            highlight.addResult(sequences.get(0), sf.getBegin(),
+                    sf.getEnd());
 
             ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
 
@@ -236,8 +269,8 @@ public class FeatureRenderer extends
           FeatureColourI col = getFeatureStyle(name.getText());
           if (col == null)
           {
-            Color generatedColour = ColorUtils
-                    .createColourFromName(name.getText());
+            Color generatedColour = ColorUtils.createColourFromName(name
+                    .getText());
             col = new FeatureColour(generatedColour);
           }
 
@@ -258,7 +291,7 @@ public class FeatureRenderer extends
     tmp = new Panel();
     panel.add(tmp);
     tmp.add(new Label(MessageManager.getString("label.group:"), Label.RIGHT));
-    tmp.add(source);
+    tmp.add(group);
 
     tmp = new Panel();
     panel.add(tmp);
@@ -272,7 +305,7 @@ public class FeatureRenderer extends
             Label.RIGHT));
     panel.add(new ScrollPane().add(description));
 
-    if (!newFeatures)
+    if (!create)
     {
       bigPanel.add(panel, BorderLayout.SOUTH);
 
@@ -290,46 +323,32 @@ public class FeatureRenderer extends
       bigPanel.add(panel, BorderLayout.CENTER);
     }
 
-    if (lastFeatureAdded == null)
-    {
-      if (features[0].type != null)
-      {
-        lastFeatureAdded = features[0].type;
-      }
-      else
-      {
-        lastFeatureAdded = "feature_1";
-      }
-    }
-
-    if (lastFeatureGroupAdded == null)
-    {
-      if (features[0].featureGroup != null)
-      {
-        lastFeatureGroupAdded = features[0].featureGroup;
-      }
-      else
-      {
-        lastFeatureAdded = "Jalview";
-      }
-    }
-
-    String title = newFeatures ? MessageManager
+    /*
+     * use defaults for type and group (and update them on Confirm) only
+     * if feature type has not been supplied by the caller
+     * (e.g. for Amend, or create features from Find) 
+     */
+    SequenceFeature firstFeature = features.get(0);
+    boolean useLastDefaults = firstFeature.getType() == null;
+    String featureType = useLastDefaults ? lastFeatureAdded : firstFeature
+            .getType();
+    String featureGroup = useLastDefaults ? lastFeatureGroupAdded
+            : firstFeature.getFeatureGroup();
+
+    String title = create ? MessageManager
             .getString("label.create_new_sequence_features")
             : MessageManager.formatMessage("label.amend_delete_features",
-                    new String[] { sequences[0].getName() });
+                    new String[] { sequences.get(0).getName() });
 
     final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
             240);
 
     dialog.setMainPanel(bigPanel);
 
-    if (newFeatures)
-    {
-      name.setText(lastFeatureAdded);
-      source.setText(lastFeatureGroupAdded);
-    }
-    else
+    name.setText(featureType);
+    group.setText(featureGroup);
+
+    if (!create)
     {
       dialog.ok.setLabel(MessageManager.getString("label.amend"));
       dialog.buttonPanel.add(deleteButton, 1);
@@ -342,13 +361,11 @@ public class FeatureRenderer extends
           dialog.setVisible(false);
         }
       });
-      name.setText(features[0].getType());
-      source.setText(features[0].getFeatureGroup());
     }
 
-    start.setText(features[0].getBegin() + "");
-    end.setText(features[0].getEnd() + "");
-    description.setText(features[0].getDescription());
+    start.setText(firstFeature.getBegin() + "");
+    end.setText(firstFeature.getEnd() + "");
+    description.setText(firstFeature.getDescription());
     // lookup (or generate) the feature colour
     FeatureColourI fcol = getFeatureStyle(name.getText());
     // simply display the feature color in a box
@@ -375,30 +392,28 @@ public class FeatureRenderer extends
 
     FeaturesFile ffile = new FeaturesFile();
 
-    if (dialog.accept)
-    {
-      // This ensures that the last sequence
-      // is refreshed and new features are rendered
-      lastSeq = null;
-      lastFeatureAdded = name.getText().trim();
-      lastFeatureGroupAdded = source.getText().trim();
-      lastDescriptionAdded = description.getText().replace('\n', ' ');
-    }
-
-    if (lastFeatureGroupAdded != null && lastFeatureGroupAdded.length() < 1)
+    /*
+     * only update default type and group if we used defaults
+     */
+    String enteredType = name.getText().trim();
+    if (dialog.accept && useLastDefaults)
     {
-      lastFeatureGroupAdded = null;
+      lastFeatureAdded = enteredType;
+      lastFeatureGroupAdded = group.getText().trim();
     }
 
-    if (!newFeatures)
+    if (!create)
     {
-
-      SequenceFeature sf = features[featureIndex];
+      SequenceFeature sf = features.get(featureIndex);
       if (dialog.accept)
       {
-        sf.type = lastFeatureAdded;
-        sf.featureGroup = lastFeatureGroupAdded;
-        sf.description = lastDescriptionAdded;
+        sf.type = enteredType;
+        sf.featureGroup = group.getText().trim();
+        if (sf.featureGroup != null && sf.featureGroup.length() < 1)
+        {
+          sf.featureGroup = null;
+        }
+        sf.description = description.getText().replace('\n', ' ');
         if (!colourPanel.isGcol)
         {
           // update colour - otherwise its already done.
@@ -410,42 +425,46 @@ public class FeatureRenderer extends
           sf.end = Integer.parseInt(end.getText());
         } catch (NumberFormatException ex)
         {
+          //
         }
+        boolean typeOrGroupChanged = (!featureType.equals(sf.type) || !featureGroup
+                .equals(sf.featureGroup));
 
         ffile.parseDescriptionHTML(sf, false);
-        setVisible(lastFeatureAdded); // if user edited name then make sure new
-                                      // type is visible
+        if (typeOrGroupChanged)
+        {
+          featuresAdded();
+        }
       }
       if (deleteFeature)
       {
-        sequences[0].deleteFeature(sf);
+        sequences.get(0).deleteFeature(sf);
+        // ensure Feature Settings reflects removal of feature / group
+        featuresAdded();
       }
-
     }
     else
     {
+      /*
+       * adding feature(s)
+       */
       if (dialog.accept && name.getText().length() > 0)
       {
-        for (int i = 0; i < sequences.length; i++)
+        for (int i = 0; i < sequences.size(); i++)
         {
-          features[i].type = lastFeatureAdded;
-          features[i].featureGroup = lastFeatureGroupAdded;
-          features[i].description = lastDescriptionAdded;
-          sequences[i].addSequenceFeature(features[i]);
-          ffile.parseDescriptionHTML(features[i], false);
+          features.get(i).type = enteredType;
+          features.get(i).featureGroup = group.getText().trim();
+          features.get(i).description = description.getText()
+                  .replace('\n', ' ');
+          sequences.get(i).addSequenceFeature(features.get(i));
+          ffile.parseDescriptionHTML(features.get(i), false);
         }
 
         Color newColour = colourPanel.getBackground();
         // setColour(lastFeatureAdded, fcol);
 
-        if (lastFeatureGroupAdded != null)
-        {
-          setGroupVisibility(lastFeatureGroupAdded, true);
-        }
-        setColour(lastFeatureAdded, new FeatureColour(newColour)); // was fcol
-        setVisible(lastFeatureAdded);
-        findAllFeatures(false); // different to original applet behaviour ?
-        // findAllFeatures();
+        setColour(enteredType, new FeatureColour(newColour)); // was fcol
+        featuresAdded();
       }
       else
       {
@@ -464,4 +483,43 @@ public class FeatureRenderer extends
 
     return true;
   }
+
+  protected void warnIfGroupHidden(Frame frame, String group)
+  {
+    if (featureGroups.containsKey(group) && !featureGroups.get(group))
+    {
+      String msg = MessageManager.formatMessage("label.warning_hidden",
+              MessageManager.getString("label.group"), group);
+      showWarning(frame, msg);
+    }
+  }
+
+  protected void warnIfTypeHidden(Frame frame, String type)
+  {
+    if (getRenderOrder().contains(type))
+    {
+      if (!showFeatureOfType(type))
+      {
+        String msg = MessageManager.formatMessage("label.warning_hidden",
+                MessageManager.getString("label.feature_type"), type);
+        showWarning(frame, msg);
+      }
+    }
+  }
+
+  /**
+   * @param frame
+   * @param msg
+   */
+  protected void showWarning(Frame frame, String msg)
+  {
+    JVDialog d = new JVDialog(frame, "", true, 350, 200);
+    Panel mp = new Panel();
+    d.ok.setLabel(MessageManager.getString("action.ok"));
+    d.cancel.setVisible(false);
+    mp.setLayout(new FlowLayout());
+    mp.add(new Label(msg));
+    d.setMainPanel(mp);
+    d.setVisible(true);
+  }
 }
index 2c454a4..9d2f601 100755 (executable)
@@ -58,8 +58,10 @@ import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.util.Arrays;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.Vector;
 
 public class FeatureSettings extends Panel implements ItemListener,
@@ -106,6 +108,7 @@ public class FeatureSettings extends Panel implements ItemListener,
     {
       fr.findAllFeatures(true); // was default - now true to make all visible
     }
+    groupPanel = new Panel();
 
     discoverAllFeatureData();
 
@@ -133,17 +136,15 @@ public class FeatureSettings extends Panel implements ItemListener,
 
     add(lowerPanel, BorderLayout.SOUTH);
 
-    if (groupPanel != null)
-    {
-      groupPanel.setLayout(new GridLayout(
-              (fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote - this was
-                                                        // scaled on number of
-                                                        // visible groups. seems
-                                                        // broken
-      groupPanel.validate();
+    groupPanel.setLayout(new GridLayout(
+            (fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote - this was
+                                                      // scaled on number of
+                                                      // visible groups. seems
+                                                      // broken
+    groupPanel.validate();
+
+    add(groupPanel, BorderLayout.NORTH);
 
-      add(groupPanel, BorderLayout.NORTH);
-    }
     frame = new Frame();
     frame.add(this);
     final FeatureSettings me = this;
@@ -326,44 +327,43 @@ public class FeatureSettings extends Panel implements ItemListener,
     if (fr.getAllFeatureColours() != null
             && fr.getAllFeatureColours().size() > 0)
     {
-      rebuildGroups();
+      // rebuildGroups();
 
     }
     resetTable(false);
   }
 
   /**
-   * rebuilds the group panel
+   * Answers the visibility of the given group, and adds a checkbox for it if
+   * there is not one already
    */
-  public void rebuildGroups()
+  public boolean checkGroupState(String group)
   {
-    boolean rdrw = false;
-    if (groupPanel == null)
-    {
-      groupPanel = new Panel();
-    }
-    else
-    {
-      rdrw = true;
-      groupPanel.removeAll();
-    }
-    // TODO: JAL-964 - smoothly incorporate new group entries if panel already
-    // displayed and new groups present
-    for (String group : fr.getFeatureGroups())
-    {
-      boolean vis = fr.checkGroupVisibility(group, false);
-      Checkbox check = new MyCheckbox(group, vis, false);
-      check.addMouseListener(this);
-      check.setFont(new Font("Serif", Font.BOLD, 12));
-      check.addItemListener(groupItemListener);
-      // note - visibility seems to correlate with displayed. ???wtf ??
-      check.setVisible(vis);
-      groupPanel.add(check);
-    }
-    if (rdrw)
+    boolean visible = fr.checkGroupVisibility(group, true);
+
+    /*
+     * is there already a checkbox for this group?
+     */
+    for (int g = 0; g < groupPanel.getComponentCount(); g++)
     {
-      groupPanel.validate();
+      if (((Checkbox) groupPanel.getComponent(g)).getLabel().equals(group))
+      {
+        ((Checkbox) groupPanel.getComponent(g)).setState(visible);
+        return visible;
+      }
     }
+
+    /*
+     * add a new checkbox
+     */
+    Checkbox check = new MyCheckbox(group, visible, false);
+    check.addMouseListener(this);
+    check.setFont(new Font("Serif", Font.BOLD, 12));
+    check.addItemListener(groupItemListener);
+    groupPanel.add(check);
+
+    groupPanel.validate();
+    return visible;
   }
 
   // This routine adds and removes checkboxes depending on
@@ -373,7 +373,9 @@ public class FeatureSettings extends Panel implements ItemListener,
     SequenceFeature[] tmpfeatures;
     String group = null, type;
     Vector<String> visibleChecks = new Vector<String>();
+    Set<String> foundGroups = new HashSet<String>();
     AlignmentI alignment = av.getAlignment();
+
     for (int i = 0; i < alignment.getHeight(); i++)
     {
       if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
@@ -386,8 +388,9 @@ public class FeatureSettings extends Panel implements ItemListener,
       while (index < tmpfeatures.length)
       {
         group = tmpfeatures[index].featureGroup;
+        foundGroups.add(group);
 
-        if (group == null || fr.checkGroupVisibility(group, true))
+        if (group == null || checkGroupState(group))
         {
           type = tmpfeatures[index].getType();
           if (!visibleChecks.contains(type))
@@ -399,6 +402,11 @@ public class FeatureSettings extends Panel implements ItemListener,
       }
     }
 
+    /*
+     * remove any checkboxes for groups not present
+     */
+    pruneGroups(foundGroups);
+
     Component[] comps;
     int cSize = featurePanel.getComponentCount();
     MyCheckbox check;
@@ -458,6 +466,25 @@ public class FeatureSettings extends Panel implements ItemListener,
   }
 
   /**
+   * Remove from the groups panel any checkboxes for groups that are not in the
+   * foundGroups set. This enables removing a group from the display when the
+   * last feature in that group is deleted.
+   * 
+   * @param foundGroups
+   */
+  protected void pruneGroups(Set<String> foundGroups)
+  {
+    for (int g = 0; g < groupPanel.getComponentCount(); g++)
+    {
+      Checkbox checkbox = (Checkbox) groupPanel.getComponent(g);
+      if (!foundGroups.contains(checkbox.getLabel()))
+      {
+        groupPanel.remove(checkbox);
+      }
+    }
+  }
+
+  /**
    * update the checklist of feature types with the given type
    * 
    * @param groupsChanged
@@ -696,8 +723,7 @@ public class FeatureSettings extends Panel implements ItemListener,
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
     fr.setTransparency((100 - transparency.getValue()) / 100f);
-    ap.seqPanel.seqCanvas.repaint();
-
+    ap.paintAlignment(true);
   }
 
   class MyCheckbox extends Checkbox
index d2fe69c..f7ebab6 100644 (file)
@@ -41,6 +41,8 @@ import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Vector;
 
 public class Finder extends Panel implements ActionListener
@@ -113,19 +115,16 @@ public class Finder extends Panel implements ActionListener
 
   public void createNewGroup_actionPerformed()
   {
-    SequenceI[] seqs = new SequenceI[searchResults.getSize()];
-    SequenceFeature[] features = new SequenceFeature[searchResults
-            .getSize()];
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    String searchString = textfield.getText().trim();
 
-    int i = 0;
     for (SearchResultMatchI match : searchResults.getResults())
     {
-      seqs[i] = match.getSequence().getDatasetSequence();
-
-      features[i] = new SequenceFeature(textfield.getText().trim(),
+      seqs.add(match.getSequence().getDatasetSequence());
+      features.add(new SequenceFeature(searchString,
               "Search Results", null, match.getStart(), match.getEnd(),
-              "Search Results");
-      i++;
+ "Search Results"));
     }
 
     if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
index 59f6957..991fb96 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.appletgui;
 
-import jalview.api.ViewStyleI;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -59,6 +58,8 @@ public class FontChooser extends Panel implements ItemListener
 
   private Checkbox scaleAsCdna = new Checkbox();
 
+  private Checkbox fontAsCdna = new Checkbox();
+
   private Button ok = new Button();
 
   private Button cancel = new Button();
@@ -69,10 +70,20 @@ public class FontChooser extends Panel implements ItemListener
 
   private Font oldFont;
 
+  private Font oldComplementFont;
+
   private int oldCharWidth = 0;
 
+  /*
+   * the state of 'scale protein to cDNA' on opening the dialog
+   */
   private boolean oldScaleProtein = false;
 
+  /*
+   * the state of 'same font for protein and cDNA' on opening the dialog
+   */
+  boolean oldMirrorFont;
+
   private Font lastSelected = null;
 
   private int lastSelStyle = 0;
@@ -83,6 +94,8 @@ public class FontChooser extends Panel implements ItemListener
 
   private Frame frame;
 
+  boolean inSplitFrame = false;
+
   /**
    * Constructor for a TreePanel font chooser
    * 
@@ -112,8 +125,9 @@ public class FontChooser extends Panel implements ItemListener
   {
     this.ap = ap;
     oldFont = ap.av.getFont();
-    oldCharWidth = ap.av.getViewStyle().getCharWidth();
-    oldScaleProtein = ap.av.getViewStyle().isScaleProteinAsCdna();
+    oldCharWidth = ap.av.getCharWidth();
+    oldScaleProtein = ap.av.isScaleProteinAsCdna();
+    oldMirrorFont = ap.av.isProteinFontAsCdna();
 
     try
     {
@@ -152,7 +166,7 @@ public class FontChooser extends Panel implements ItemListener
     this.frame = new Frame();
     frame.add(this);
     jalview.bin.JalviewLite.addFrame(frame,
-            MessageManager.getString("action.change_font"), 440, 115);
+            MessageManager.getString("action.change_font"), 440, 145);
 
     init = false;
   }
@@ -160,6 +174,7 @@ public class FontChooser extends Panel implements ItemListener
   /**
    * Actions on change of font name, size or style.
    */
+  @Override
   public void itemStateChanged(ItemEvent evt)
   {
     final Object source = evt.getSource();
@@ -179,6 +194,29 @@ public class FontChooser extends Panel implements ItemListener
     {
       scaleAsCdna_actionPerformed();
     }
+    else if (source == fontAsCdna)
+    {
+      mirrorFont_actionPerformed();
+    }
+  }
+
+  /**
+   * Action on checking or unchecking 'use same font across split screen'
+   * option. When checked, the font settings are copied to the other half of the
+   * split screen. When unchecked, the other half is restored to its initial
+   * settings.
+   */
+  protected void mirrorFont_actionPerformed()
+  {
+    boolean selected = fontAsCdna.getState();
+    ap.av.setProteinFontAsCdna(selected);
+    ap.av.getCodingComplement().setProteinFontAsCdna(selected);
+
+    if (!selected)
+    {
+      ap.av.getCodingComplement().setFont(oldComplementFont, true);
+    }
+    changeFont();
   }
 
   /**
@@ -205,18 +243,23 @@ public class FontChooser extends Panel implements ItemListener
     if (ap != null)
     {
       ap.av.setScaleProteinAsCdna(oldScaleProtein);
+      ap.av.setProteinFontAsCdna(oldMirrorFont);
+
       if (ap.av.getCodingComplement() != null)
       {
         ap.av.getCodingComplement().setScaleProteinAsCdna(oldScaleProtein);
-        ap.alignFrame.getSplitFrame().repaint();
+        ap.av.getCodingComplement().setProteinFontAsCdna(oldMirrorFont);
+        ap.av.getCodingComplement().setFont(oldComplementFont, true);
+        SplitFrame splitFrame = ap.alignFrame.getSplitFrame();
+        splitFrame.adjustLayout();
+        splitFrame.getComplement(ap.alignFrame).alignPanel.fontChanged();
+        splitFrame.repaint();
       }
 
-      ap.av.setFont(oldFont);
-      ViewStyleI style = ap.av.getViewStyle();
-      if (style.getCharWidth() != oldCharWidth)
+      ap.av.setFont(oldFont, true);
+      if (ap.av.getCharWidth() != oldCharWidth)
       {
-        style.setCharWidth(oldCharWidth);
-        ap.av.setViewStyle(style);
+        ap.av.setCharWidth(oldCharWidth);
       }
       ap.paintAlignment(true);
     }
@@ -276,8 +319,23 @@ public class FontChooser extends Panel implements ItemListener
     }
     else if (ap != null)
     {
-      ap.av.setFont(newFont);
+      ap.av.setFont(newFont, true);
       ap.fontChanged();
+
+      /*
+       * and change font in other half of split frame if any
+       */
+      if (inSplitFrame)
+      {
+        if (fontAsCdna.getState())
+        {
+          ap.av.getCodingComplement().setFont(newFont, true);
+        }
+        SplitFrame splitFrame = ap.alignFrame.getSplitFrame();
+        splitFrame.adjustLayout();
+        splitFrame.getComplement(ap.alignFrame).alignPanel.fontChanged();
+        splitFrame.repaint();
+      }
     }
     // remember last selected
     lastSelected = newFont;
@@ -344,6 +402,11 @@ public class FontChooser extends Panel implements ItemListener
     scaleAsCdna.addItemListener(this);
     scaleAsCdna.setState(ap.av.isScaleProteinAsCdna());
 
+    fontAsCdna.setLabel(MessageManager.getString("label.font_as_cdna"));
+    fontAsCdna.setFont(VERDANA_11PT);
+    fontAsCdna.addItemListener(this);
+    fontAsCdna.setState(ap.av.isProteinFontAsCdna());
+
     ok.setFont(VERDANA_11PT);
     ok.setLabel(MessageManager.getString("action.ok"));
     ok.addActionListener(new ActionListener()
@@ -388,7 +451,8 @@ public class FontChooser extends Panel implements ItemListener
     stylePanel.add(fontStyle, BorderLayout.CENTER);
     sizePanel.add(sizeLabel, BorderLayout.WEST);
     sizePanel.add(fontSize, BorderLayout.CENTER);
-    scalePanel.add(scaleAsCdna, BorderLayout.CENTER);
+    scalePanel.add(scaleAsCdna, BorderLayout.NORTH);
+    scalePanel.add(fontAsCdna, BorderLayout.SOUTH);
     okCancelPanel.add(ok, null);
     okCancelPanel.add(cancel, null);
 
@@ -402,6 +466,9 @@ public class FontChooser extends Panel implements ItemListener
     this.add(optionsPanel, BorderLayout.NORTH);
     if (ap.alignFrame.getSplitFrame() != null)
     {
+      inSplitFrame = true;
+      oldComplementFont = ((AlignViewport) ap.av.getCodingComplement())
+              .getFont();
       this.add(scalePanel, BorderLayout.CENTER);
     }
     this.add(okCancelPanel, BorderLayout.SOUTH);
@@ -416,9 +483,7 @@ public class FontChooser extends Panel implements ItemListener
     ap.av.setScaleProteinAsCdna(scaleAsCdna.getState());
     ap.av.getCodingComplement().setScaleProteinAsCdna(
             scaleAsCdna.getState());
-    ap.alignFrame.getSplitFrame().adjustLayout();
-    ap.paintAlignment(true);
-    ap.alignFrame.getSplitFrame().repaint();
+    changeFont();
   }
 
 }
index d72e91f..74bbcf5 100755 (executable)
 package jalview.appletgui;
 
 import jalview.datamodel.SequenceI;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Panel;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
-public class IdCanvas extends Panel
+public class IdCanvas extends Panel implements ViewportListenerI
 {
   protected AlignViewport av;
 
@@ -54,6 +57,7 @@ public class IdCanvas extends Panel
     setLayout(null);
     this.av = av;
     PaintRefresher.Register(this, av.getSequenceSetId());
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   public void drawIdString(Graphics gg, boolean hiddenRows, SequenceI s,
@@ -103,28 +107,32 @@ public class IdCanvas extends Panel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     gg.copyArea(0, 0, getSize().width, imgHeight, 0,
             -vertical * av.getCharHeight());
 
-    int ss = av.startSeq, es = av.endSeq, transY = 0;
+    int ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transY = 0;
     if (vertical > 0) // scroll down
     {
       ss = es - vertical;
-      if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
+      if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
+                                     // at a
+                                 // time
       {
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - vertical * av.getCharHeight();
+        transY = imgHeight - ((vertical + 1) * av.getCharHeight());
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -180,7 +188,7 @@ public class IdCanvas extends Panel
     gg.setFont(italic);
 
     gg.fillRect(0, 0, getSize().width, getSize().height);
-    drawIds(av.startSeq, av.endSeq);
+    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
     g.drawImage(image, 0, 0, this);
   }
 
@@ -213,7 +221,8 @@ public class IdCanvas extends Panel
 
       if (av.hasHiddenColumns())
       {
-        maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+        maxwidth = av.getAlignment().getHiddenColumns()
+                .findColumnPosition(maxwidth) - 1;
       }
 
       int annotationHeight = 0;
@@ -233,9 +242,10 @@ public class IdCanvas extends Panel
 
       int cHeight = alheight * avcharHeight + hgap + annotationHeight;
 
-      int rowSize = av.getEndRes() - av.getStartRes();
+      int rowSize = av.getRanges().getEndRes()
+              - av.getRanges().getStartRes();
       // Draw the rest of the panels
-      for (int ypos = hgap, row = av.startRes; (ypos <= getSize().height)
+      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getSize().height)
               && (row < maxwidth); ypos += cHeight, row += rowSize)
       {
         for (int i = starty; i < alheight; i++)
@@ -263,7 +273,7 @@ public class IdCanvas extends Panel
     {
       // Now draw the id strings
       SequenceI seq;
-      for (int i = starty; i < endy; i++)
+      for (int i = starty; i <= endy; i++)
       {
 
         seq = av.getAlignment().getSequenceAt(i);
@@ -387,4 +397,15 @@ public class IdCanvas extends Panel
     }
     return false;
   }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport range changes (e.g. alignment panel was scrolled)
+    if (evt.getPropertyName().equals("startseq")
+            || evt.getPropertyName().equals("endseq"))
+    {
+      fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+  }
 }
index b03a638..66eb053 100755 (executable)
@@ -35,6 +35,7 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
@@ -225,6 +226,10 @@ public class IdPanel extends Panel implements MouseListener,
     String id = sq.getName();
 
     // get the default url with the sequence details filled in
+    if (urlProvider == null)
+    {
+      return;
+    }
     String url = urlProvider.getPrimaryUrl(id);
     String target = urlProvider.getPrimaryTarget(id);
     try
@@ -253,13 +258,13 @@ public class IdPanel extends Panel implements MouseListener,
       return;
     }
 
-    if (mouseDragging && e.getY() < 0 && av.getStartSeq() > 0)
+    if (mouseDragging && e.getY() < 0 && av.getRanges().getStartSeq() > 0)
     {
       scrollThread = new ScrollThread(true);
     }
 
     if (mouseDragging && e.getY() >= getSize().height
-            && av.getAlignment().getHeight() > av.getEndSeq())
+            && av.getAlignment().getHeight() > av.getRanges().getEndSeq())
     {
       scrollThread = new ScrollThread(false);
     }
@@ -287,8 +292,15 @@ public class IdPanel extends Panel implements MouseListener,
 
       // build a new links menu based on the current links + any non-positional
       // features
-      List<String> nlinks = urlProvider.getLinksForMenu();
-
+      List<String> nlinks;
+      if (urlProvider != null)
+      {
+        nlinks = urlProvider.getLinksForMenu();
+      }
+      else
+      {
+        nlinks = new ArrayList<String>();
+      }
       SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures();
       for (int sl = 0; sf != null && sl < sf.length; sl++)
       {
@@ -398,9 +410,10 @@ public class IdPanel extends Panel implements MouseListener,
     int index = av.getAlignment().findIndex(list.get(0));
 
     // do we need to scroll the panel?
-    if (av.getStartSeq() > index || av.getEndSeq() < index)
+    if (av.getRanges().getStartSeq() > index
+            || av.getRanges().getEndSeq() < index)
     {
-      alignPanel.setScrollValues(av.getStartRes(), index);
+      av.getRanges().setStartSeq(index);
     }
   }
 
@@ -428,13 +441,13 @@ public class IdPanel extends Panel implements MouseListener,
       running = true;
       while (running)
       {
-        if (alignPanel.scrollUp(up))
+        if (av.getRanges().scrollUp(up))
         {
           // scroll was ok, so add new sequence to selection
-          int seq = av.getStartSeq();
+          int seq = av.getRanges().getStartSeq();
           if (!up)
           {
-            seq = av.getEndSeq();
+            seq = av.getRanges().getEndSeq();
           }
 
           if (seq < lastid)
diff --git a/src/jalview/appletgui/OverviewCanvas.java b/src/jalview/appletgui/OverviewCanvas.java
new file mode 100644 (file)
index 0000000..23e82df
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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.appletgui;
+
+import jalview.renderer.OverviewRenderer;
+import jalview.viewmodel.OverviewDimensions;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+
+public class OverviewCanvas extends Component
+{
+  // This is set true if the alignment view changes whilst
+  // the overview is being calculated
+  private volatile boolean restart = false;
+
+  private volatile boolean updaterunning = false;
+
+  private OverviewDimensions od;
+
+  private Image miniMe;
+
+  private Image offscreen;
+
+  private AlignViewport av;
+
+  // Can set different properties in this seqCanvas than
+  // main visible SeqCanvas
+  private SequenceRenderer sr;
+
+  private jalview.renderer.seqfeatures.FeatureRenderer fr;
+
+  private Frame nullFrame;
+
+  public OverviewCanvas(OverviewDimensions overviewDims,
+          AlignViewport alignvp)
+  {
+    od = overviewDims;
+    av = alignvp;
+
+    nullFrame = new Frame();
+    nullFrame.addNotify();
+
+    sr = new SequenceRenderer(av);
+    sr.graphics = nullFrame.getGraphics();
+    sr.renderGaps = false;
+    sr.forOverview = true;
+    fr = new jalview.renderer.seqfeatures.FeatureRenderer(av);
+  }
+
+  /**
+   * Update the overview dimensions object used by the canvas (e.g. if we change
+   * from showing hidden columns to hiding them or vice versa)
+   * 
+   * @param overviewDims
+   */
+  public void resetOviewDims(OverviewDimensions overviewDims)
+  {
+    od = overviewDims;
+  }
+
+  /**
+   * Signals to drawing code that the associated alignment viewport has changed
+   * and a redraw will be required
+   */
+  public boolean restartDraw()
+  {
+    synchronized (this)
+    {
+      if (updaterunning)
+      {
+        restart = true;
+      }
+      else
+      {
+        updaterunning = true;
+      }
+      return restart;
+    }
+  }
+
+  public void draw(boolean showSequenceFeatures, boolean showAnnotation,
+          FeatureRenderer transferRenderer)
+  {
+    miniMe = null;
+
+    if (showSequenceFeatures)
+    {
+      fr.transferSettings(transferRenderer);
+    }
+
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    OverviewRenderer or = new OverviewRenderer(sr, fr, od);
+    miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
+    offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
+
+    miniMe = or.draw(od.getRows(av.getAlignment()),
+            od.getColumns(av.getAlignment()));
+
+    Graphics mg = miniMe.getGraphics();
+
+    // checks for conservation annotation to make sure overview works for DNA
+    // too
+    if (showAnnotation)
+    {
+      mg.translate(0, od.getSequencesHeight());
+      or.drawGraph(mg, av.getAlignmentConservationAnnotation(),
+              av.getCharWidth(), od.getGraphHeight(),
+              od.getColumns(av.getAlignment()));
+      mg.translate(0, -od.getSequencesHeight());
+    }
+    System.gc();
+
+    if (restart)
+    {
+      restart = false;
+      draw(showSequenceFeatures, showAnnotation, transferRenderer);
+    }
+    else
+    {
+      updaterunning = false;
+    }
+  }
+
+  @Override
+  public void update(Graphics g)
+  {
+    paint(g);
+  }
+
+  @Override
+  public void paint(Graphics g)
+  {
+    Graphics og = offscreen.getGraphics();
+    if (miniMe != null)
+    {
+      og.drawImage(miniMe, 0, 0, this);
+      og.setColor(Color.red);
+      od.drawBox(og);
+      g.drawImage(offscreen, 0, 0, this);
+    }
+  }
+
+}
index 9b2be4c..b3c4a37 100755 (executable)
  */
 package jalview.appletgui;
 
-import jalview.datamodel.AlignmentI;
-
-import java.awt.Color;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.viewmodel.OverviewDimensions;
+import jalview.viewmodel.OverviewDimensionsHideHidden;
+import jalview.viewmodel.OverviewDimensionsShowHidden;
+import jalview.viewmodel.ViewportListenerI;
+
+import java.awt.BorderLayout;
+import java.awt.CheckboxMenuItem;
 import java.awt.Dimension;
-import java.awt.Frame;
-import java.awt.Graphics;
-import java.awt.Image;
 import java.awt.Panel;
+import java.awt.PopupMenu;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
 
 public class OverviewPanel extends Panel implements Runnable,
-        MouseMotionListener, MouseListener
+        MouseMotionListener, MouseListener, ViewportListenerI
 {
-  Image miniMe;
-
-  Image offscreen;
-
-  AlignViewport av;
-
-  AlignmentPanel ap;
-
-  float scalew = 1f;
+  private OverviewDimensions od;
 
-  float scaleh = 1f;
+  private OverviewCanvas oviewCanvas;
 
-  public int width, sequencesHeight;
+  private AlignViewport av;
 
-  int graphHeight = 20;
+  private AlignmentPanel ap;
 
-  int boxX = -1, boxY = -1, boxWidth = -1, boxHeight = -1;
+  private boolean showHidden = true;
 
-  boolean resizing = false;
+  private boolean updateRunning = false;
 
-  // Can set different properties in this seqCanvas than
-  // main visible SeqCanvas
-  SequenceRenderer sr;
-
-  FeatureRenderer fr;
-
-  Frame nullFrame;
-
-  public OverviewPanel(AlignmentPanel ap)
+  public OverviewPanel(AlignmentPanel alPanel)
   {
-    this.av = ap.av;
-    this.ap = ap;
+    this.av = alPanel.av;
+    this.ap = alPanel;
     setLayout(null);
-    nullFrame = new Frame();
-    nullFrame.addNotify();
 
-    sr = new SequenceRenderer(av);
-    sr.graphics = nullFrame.getGraphics();
-    sr.renderGaps = false;
-    sr.forOverview = true;
-    fr = new FeatureRenderer(av);
+    od = new OverviewDimensionsShowHidden(av.getRanges(),
+            (av.isShowAnnotation() && av.getSequenceConsensusHash() != null));
 
-    // scale the initial size of overviewpanel to shape of alignment
-    float initialScale = (float) av.getAlignment().getWidth()
-            / (float) av.getAlignment().getHeight();
+    oviewCanvas = new OverviewCanvas(od, av);
+    setLayout(new BorderLayout());
+    add(oviewCanvas, BorderLayout.CENTER);
 
-    if (av.getSequenceConsensusHash() == null)
-    {
-      graphHeight = 0;
-    }
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-    {
-      // wider
-      width = 400;
-      sequencesHeight = (int) (400f / initialScale);
-      if (sequencesHeight < 40)
-      {
-        sequencesHeight = 40;
-      }
-    }
-    else
-    {
-      // taller
-      width = (int) (400f * initialScale);
-      sequencesHeight = 300;
-      if (width < 120)
-      {
-        width = 120;
-      }
-    }
+    av.getRanges().addPropertyChangeListener(this);
 
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
     addComponentListener(new ComponentAdapter()
     {
 
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        if (getSize().width != width
-                || getSize().height != sequencesHeight + graphHeight)
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
         {
           updateOverviewImage();
         }
@@ -145,6 +109,10 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mouseClicked(MouseEvent evt)
   {
+    if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    {
+      showPopupMenu(evt);
+    }
   }
 
   @Override
@@ -155,337 +123,138 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
   @Override
   public void mouseReleased(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
   @Override
   public void mouseDragged(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
-  void checkValid()
+  private void mouseAction(MouseEvent evt)
   {
-    if (boxY < 0)
-    {
-      boxY = 0;
-    }
-
-    if (boxY > (sequencesHeight - boxHeight))
+    if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
     {
-      boxY = sequencesHeight - boxHeight + 1;
-    }
-
-    if (boxX < 0)
-    {
-      boxX = 0;
-    }
-
-    if (boxX > (width - boxWidth))
-    {
-      if (av.hasHiddenColumns())
+      if (!Platform.isAMac())
       {
-        // Try smallest possible box
-        boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
+        showPopupMenu(evt);
       }
-      boxX = width - boxWidth;
-    }
-
-    int col = (int) (boxX / scalew / av.getCharWidth());
-    int row = (int) (boxY / scaleh / av.getCharHeight());
-
-    if (av.hasHiddenColumns())
-    {
-      if (!av.getColumnSelection().isVisible(col))
-      {
-        return;
-      }
-
-      col = av.getColumnSelection().findColumnPosition(col);
     }
-
-    if (av.hasHiddenRows())
+    else
     {
-      row = av.getAlignment().getHiddenSequences()
-              .findIndexWithoutHiddenSeqs(row);
+      od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
+              .getHiddenSequences(), av.getAlignment().getHiddenColumns());
+      ap.paintAlignment(false);
     }
-
-    ap.setScrollValues(col, row);
-    ap.paintAlignment(false);
   }
 
   /**
-   * DOCUMENT ME!
+   * Updates the overview image when the related alignment panel is updated
    */
   public void updateOverviewImage()
   {
-    if (resizing)
+    if ((getSize().width > 0) && (getSize().height > 0))
     {
-      resizeAgain = true;
-      return;
+      od.setWidth(getSize().width);
+      od.setHeight(getSize().height);
     }
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    if (av.isShowSequenceFeatures())
+    synchronized (this)
     {
-      fr.transferSettings(ap.seqPanel.seqCanvas.fr);
-    }
-
-    resizing = true;
+      if (updateRunning)
+      {
+        oviewCanvas.restartDraw();
+        return;
+      }
 
-    if ((getSize().width > 0) && (getSize().height > 0))
-    {
-      width = getSize().width;
-      sequencesHeight = getSize().height - graphHeight;
+      updateRunning = true;
     }
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
-
     Thread thread = new Thread(this);
     thread.start();
     repaint();
+    updateRunning = false;
   }
 
-  // This is set true if the user resizes whilst
-  // the overview is being calculated
-  boolean resizeAgain = false;
-
   @Override
   public void run()
   {
-    miniMe = null;
-    int alwidth = av.getAlignment().getWidth();
-    int alheight = av.getAlignment().getHeight()
-            + av.getAlignment().getHiddenSequences().getSize();
-
-    if (av.isShowSequenceFeatures())
-    {
-      fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
-    }
-
-    if (getSize().width > 0 && getSize().height > 0)
-    {
-      width = getSize().width;
-      sequencesHeight = getSize().height - graphHeight;
-    }
-
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
-
-    int fullsizeWidth = alwidth * av.getCharWidth();
-    int fullsizeHeight = alheight * av.getCharHeight();
-
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    miniMe = nullFrame.createImage(width, sequencesHeight + graphHeight);
-    offscreen = nullFrame.createImage(width, sequencesHeight + graphHeight);
-
-    Graphics mg = miniMe.getGraphics();
-    float sampleCol = (float) alwidth / (float) width;
-    float sampleRow = (float) alheight / (float) sequencesHeight;
-
-    int lastcol = 0, lastrow = 0;
-    int xstart = 0, ystart = 0;
-    Color color = Color.yellow;
-    int row, col, sameRow = 0, sameCol = 0;
-    jalview.datamodel.SequenceI seq;
-    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-            .hasHiddenColumns();
-    boolean hiddenRow = false;
-    AlignmentI alignment = av.getAlignment();
-    for (row = 0; row <= sequencesHeight; row++)
-    {
-      if (resizeAgain)
-      {
-        break;
-      }
-      if ((int) (row * sampleRow) == lastrow)
-      {
-        sameRow++;
-        continue;
-      }
-
-      hiddenRow = false;
-      if (hasHiddenRows)
-      {
-        seq = alignment.getHiddenSequences().getHiddenSequence(lastrow);
-        if (seq == null)
-        {
-          int index = alignment.getHiddenSequences()
-                  .findIndexWithoutHiddenSeqs(lastrow);
-
-          seq = alignment.getSequenceAt(index);
-        }
-        else
-        {
-          hiddenRow = true;
-        }
-      }
-      else
-      {
-        seq = alignment.getSequenceAt(lastrow);
-      }
-
-      for (col = 0; col < width; col++)
-      {
-        if ((int) (col * sampleCol) == lastcol
-                && (int) (row * sampleRow) == lastrow)
-        {
-          sameCol++;
-          continue;
-        }
-
-        lastcol = (int) (col * sampleCol);
-
-        if (seq.getLength() > lastcol)
-        {
-          color = sr.getResidueBoxColour(seq, lastcol);
-
-          if (av.isShowSequenceFeatures())
-          {
-            color = fr.findFeatureColour(color, seq, lastcol);
-          }
-        }
-        else
-        {
-          color = Color.white; // White
-        }
-
-        if (hiddenRow
-                || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                        lastcol)))
-        {
-          color = color.darker().darker();
-        }
-
-        mg.setColor(color);
-        if (sameCol == 1 && sameRow == 1)
-        {
-          mg.drawLine(xstart, ystart, xstart, ystart);
-        }
-        else
-        {
-          mg.fillRect(xstart, ystart, sameCol, sameRow);
-        }
-
-        xstart = col;
-        sameCol = 1;
-      }
-      lastrow = (int) (row * sampleRow);
-      ystart = row;
-      sameRow = 1;
-    }
-
-    if (av.getAlignmentConservationAnnotation() != null)
-    {
-      for (col = 0; col < width; col++)
-      {
-        if (resizeAgain)
-        {
-          break;
-        }
-        lastcol = (int) (col * sampleCol);
-        {
-          mg.translate(col, sequencesHeight);
-          ap.annotationPanel.renderer.drawGraph(mg,
-                  av.getAlignmentConservationAnnotation(),
-                  av.getAlignmentConservationAnnotation().annotations,
-                  (int) (sampleCol) + 1, graphHeight,
-                  (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-          mg.translate(-col, -sequencesHeight);
-        }
-      }
-    }
-    System.gc();
-
-    resizing = false;
-
+    oviewCanvas.draw(av.isShowSequenceFeatures(),
+            (av.isShowAnnotation() && av
+                    .getAlignmentConservationAnnotation() != null),
+            ap.seqPanel.seqCanvas.getFeatureRenderer());
     setBoxPosition();
-
-    if (resizeAgain)
-    {
-      resizeAgain = false;
-      updateOverviewImage();
-    }
   }
 
-  public void setBoxPosition()
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   */
+  private void setBoxPosition()
   {
-    int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-    int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-            .getHiddenSequences().getSize())
-            * av.getCharHeight();
-
-    int startRes = av.getStartRes();
-    int endRes = av.getEndRes();
-
-    if (av.hasHiddenColumns())
-    {
-      startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-      endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-    }
-
-    int startSeq = av.startSeq;
-    int endSeq = av.endSeq;
-
-    if (av.hasHiddenRows())
-    {
-      startSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(startSeq);
-
-      endSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(endSeq);
-
-    }
-
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    boxX = (int) (startRes * av.getCharWidth() * scalew);
-    boxY = (int) (startSeq * av.getCharHeight() * scaleh);
+    od.setBoxPosition(av.getAlignment()
+.getHiddenSequences(), av
+            .getAlignment().getHiddenColumns());
+    repaint();
+  }
 
-    if (av.hasHiddenColumns())
-    {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
-    }
-    else
+  /*
+   * Displays the popup menu and acts on user input
+   */
+  private void showPopupMenu(MouseEvent e)
+  {
+    PopupMenu popup = new PopupMenu();
+    ItemListener menuListener = new ItemListener()
     {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
-    }
-
-    boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
-
-    repaint();
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        toggleHiddenColumns();
+      }
+    };
+    CheckboxMenuItem item = new CheckboxMenuItem(
+            MessageManager.getString("label.togglehidden"));
+    item.setState(showHidden);
+    popup.add(item);
+    item.addItemListener(menuListener);
+    this.add(popup);
+    popup.show(this, e.getX(), e.getY());
   }
 
   @Override
-  public void update(Graphics g)
+  public void propertyChange(PropertyChangeEvent evt)
   {
-    paint(g);
+    setBoxPosition();
   }
 
-  @Override
-  public void paint(Graphics g)
+  /*
+   * Toggle overview display between showing hidden columns and hiding hidden columns
+   */
+  private void toggleHiddenColumns()
   {
-    Graphics og = offscreen.getGraphics();
-    if (miniMe != null)
+    if (showHidden)
     {
-      og.drawImage(miniMe, 0, 0, this);
-      og.setColor(Color.red);
-      og.drawRect(boxX, boxY, boxWidth, boxHeight);
-      og.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
-      g.drawImage(offscreen, 0, 0, this);
+      showHidden = false;
+      od = new OverviewDimensionsHideHidden(av.getRanges(),
+              (av.isShowAnnotation() && av
+                      .getAlignmentConservationAnnotation() != null));
     }
+    else
+    {
+      showHidden = true;
+      od = new OverviewDimensionsShowHidden(av.getRanges(),
+              (av.isShowAnnotation() && av
+                      .getAlignmentConservationAnnotation() != null));
+    }
+    oviewCanvas.resetOviewDims(od);
+    updateOverviewImage();
   }
-
 }
index c5ec0c1..5dc57f9 100644 (file)
  */
 package jalview.appletgui;
 
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.analysis.ScoreModelI;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SeqCigar;
 import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
@@ -56,7 +59,7 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
 
   int top = 0;
 
-  public PCAPanel(AlignViewport av)
+  public PCAPanel(AlignViewport viewport)
   {
     try
     {
@@ -73,19 +76,19 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
       zCombobox.addItem("dim " + i);
     }
 
-    this.av = av;
-    boolean selected = av.getSelectionGroup() != null
-            && av.getSelectionGroup().getSize() > 0;
-    AlignmentView seqstrings = av.getAlignmentView(selected);
-    boolean nucleotide = av.getAlignment().isNucleotide();
+    this.av = viewport;
+    boolean selected = viewport.getSelectionGroup() != null
+            && viewport.getSelectionGroup().getSize() > 0;
+    AlignmentView seqstrings = viewport.getAlignmentView(selected);
+    boolean nucleotide = viewport.getAlignment().isNucleotide();
     SequenceI[] seqs;
     if (!selected)
     {
-      seqs = av.getAlignment().getSequencesArray();
+      seqs = viewport.getAlignment().getSequencesArray();
     }
     else
     {
-      seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
+      seqs = viewport.getSelectionGroup().getSequencesInOrder(viewport.getAlignment());
     }
     SeqCigar sq[] = seqstrings.getSequences();
     int length = sq[0].getWidth();
@@ -99,9 +102,13 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
         return;
       }
     }
-    pcaModel = new PCAModel(seqstrings, seqs, nucleotide);
 
-    rc = new RotatableCanvas(av);
+    ScoreModelI scoreModel = ScoreModels.getInstance().getDefaultModel(
+            !nucleotide);
+    pcaModel = new PCAModel(seqstrings, seqs, nucleotide, scoreModel,
+            SimilarityParams.SeqSpace);
+
+    rc = new RotatableCanvas(viewport);
     embedMenuIfNeeded(rc);
     add(rc, BorderLayout.CENTER);
 
@@ -116,6 +123,7 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
   /**
    * DOCUMENT ME!
    */
+  @Override
   public void run()
   {
     // TODO progress indicator
@@ -164,6 +172,7 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
     rc.paint(rc.getGraphics());
   }
 
+  @Override
   public void actionPerformed(ActionEvent evt)
   {
     if (evt.getSource() == inputData)
@@ -183,6 +192,7 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
     }
   }
 
+  @Override
   public void itemStateChanged(ItemEvent evt)
   {
     if (evt.getSource() == xCombobox)
@@ -206,6 +216,9 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
       if (!pcaModel.isNucleotide())
       {
         pcaModel.setNucleotide(true);
+        ScoreModelI scoreModel = ScoreModels.getInstance().getDefaultModel(
+                false);
+        pcaModel.setScoreModel(scoreModel);
         new Thread(this).start();
       }
     }
@@ -214,6 +227,9 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
       if (pcaModel.isNucleotide())
       {
         pcaModel.setNucleotide(false);
+        ScoreModelI scoreModel = ScoreModels.getInstance().getDefaultModel(
+                true);
+        pcaModel.setScoreModel(scoreModel);
         new Thread(this).start();
       }
     }
@@ -265,7 +281,7 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
     }
     ;
     Object[] alAndColsel = pcaModel.getSeqtrings()
-            .getAlignmentAndColumnSelection(gc);
+            .getAlignmentAndHiddenColumns(gc);
 
     if (alAndColsel != null && alAndColsel[0] != null)
     {
@@ -273,7 +289,8 @@ public class PCAPanel extends EmbmenuFrame implements Runnable,
       AlignFrame af = new AlignFrame(al, av.applet,
               "Original Data for PCA", false);
 
-      af.viewport.setHiddenColumns((ColumnSelection) alAndColsel[1]);
+      af.viewport.getAlignment().setHiddenColumns(
+              (HiddenColumns) alAndColsel[1]);
     }
   }
 
index ed07b63..ec3e246 100755 (executable)
 package jalview.appletgui;
 
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.util.MessageManager;
+import jalview.viewmodel.ViewportListenerI;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -38,10 +40,11 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
 public class ScalePanel extends Panel implements MouseMotionListener,
-        MouseListener
+        MouseListener, ViewportListenerI
 {
 
   protected int offy = 4;
@@ -71,17 +74,18 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     addMouseListener(this);
     addMouseMotionListener(this);
 
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    int x = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
     final int res;
 
     if (av.hasHiddenColumns())
     {
-      res = av.getColumnSelection().adjustForHiddenColumns(x);
+      res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
     }
     else
     {
@@ -170,7 +174,7 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       });
       pop.add(item);
 
-      if (av.getColumnSelection().hasManyHiddenColumns())
+      if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns())
       {
         item = new MenuItem(MessageManager.getString("action.reveal_all"));
         item.addActionListener(new ActionListener()
@@ -229,7 +233,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   {
     mouseDragging = false;
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (res > av.getAlignment().getWidth())
     {
@@ -238,7 +243,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
 
     if (av.hasHiddenColumns())
     {
-      res = av.getColumnSelection().adjustForHiddenColumns(res);
+      res = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(res);
     }
 
     if (!stretchingGroup)
@@ -276,9 +282,10 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     mouseDragging = true;
     ColumnSelection cs = av.getColumnSelection();
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
     res = Math.max(0, res);
-    res = cs.adjustForHiddenColumns(res);
+    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
     min = Math.min(res, min);
     max = Math.max(res, max);
@@ -324,12 +331,14 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       return;
     }
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
-    res = av.getColumnSelection().adjustForHiddenColumns(res);
+    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
 
     reveal = null;
-    for (int[] region : av.getColumnSelection().getHiddenColumns())
+    for (int[] region : av.getAlignment().getHiddenColumns()
+            .getHiddenRegions())
     {
       if (res + 1 == region[0] || res - 1 == region[1])
       {
@@ -350,7 +359,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   @Override
   public void paint(Graphics g)
   {
-    drawScale(g, av.getStartRes(), av.getEndRes(), getSize().width,
+    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
+            getSize().width,
             getSize().height);
   }
 
@@ -366,21 +376,22 @@ public class ScalePanel extends Panel implements MouseMotionListener,
 
     // Fill the selected columns
     ColumnSelection cs = av.getColumnSelection();
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
     int avCharWidth = av.getCharWidth();
     int avcharHeight = av.getCharHeight();
     if (cs != null)
     {
       gg.setColor(new Color(220, 0, 0));
-      boolean hasHiddenColumns = cs.hasHiddenColumns();
+      boolean hasHiddenColumns = hidden.hasHiddenColumns();
       for (int sel : cs.getSelected())
       {
         // TODO: JAL-2001 - provide a fast method to list visible selected in a
         // given range
         if (hasHiddenColumns)
         {
-          if (cs.isVisible(sel))
+          if (hidden.isVisible(sel))
           {
-            sel = cs.findColumnPosition(sel);
+            sel = hidden.findColumnPosition(sel);
           }
           else
           {
@@ -439,10 +450,10 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       if (av.getShowHiddenMarkers())
       {
         int widthx = 1 + endx - startx;
-        for (int i = 0; i < cs.getHiddenColumns().size(); i++)
+        for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
         {
 
-          res = cs.findHiddenRegionPosition(i) - startx;
+          res = hidden.findHiddenRegionPosition(i) - startx;
 
           if (res < 0 || res > widthx)
           {
@@ -458,4 +469,11 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     }
   }
 
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport change events (e.g. alignment panel was scrolled)
+    repaint();
+  }
+
 }
index 5d6bb07..46a908e 100755 (executable)
 package jalview.appletgui;
 
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Panel;
+import java.beans.PropertyChangeEvent;
 
-public class SeqCanvas extends Panel
+public class SeqCanvas extends Panel implements ViewportListenerI
 {
   FeatureRenderer fr;
 
@@ -63,6 +67,8 @@ public class SeqCanvas extends Panel
     sr = new SequenceRenderer(av);
     PaintRefresher.Register(this, av.getSequenceSetId());
     updateViewport();
+
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   int avcharHeight = 0, avcharWidth = 0;
@@ -122,14 +128,17 @@ public class SeqCanvas extends Panel
     ypos += avcharHeight;
     if (av.hasHiddenColumns())
     {
-      startx = av.getColumnSelection().adjustForHiddenColumns(startx);
-      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+      startx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(startx);
+      endx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(endx);
     }
 
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     // WEST SCALE
@@ -169,7 +178,8 @@ public class SeqCanvas extends Panel
 
     if (av.hasHiddenColumns())
     {
-      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+      endx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(endx);
     }
 
     SequenceI seq;
@@ -211,17 +221,19 @@ public class SeqCanvas extends Panel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     updateViewport();
 
     // Its possible on certain browsers that the call to fastpaint
     // is faster than it can paint, so this check here catches
     // this possibility
-    if (lastsr + horizontal != av.startRes)
+    if (lastsr + horizontal != ranges.getStartRes())
     {
-      horizontal = av.startRes - lastsr;
+      horizontal = ranges.getStartRes() - lastsr;
     }
 
-    lastsr = av.startRes;
+    lastsr = ranges.getStartRes();
 
     fastPaint = true;
     gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
@@ -229,7 +241,9 @@ public class SeqCanvas extends Panel
             imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
             -vertical * avcharHeight);
 
-    int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0;
+    int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
+            .getStartSeq(), es = ranges
+            .getEndSeq(), transX = 0, transY = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
@@ -244,21 +258,23 @@ public class SeqCanvas extends Panel
     else if (vertical > 0) // scroll down
     {
       ss = es - vertical;
-      if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
+      if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
+                                     // at a
+                                 // time
       {
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - vertical * avcharHeight;
+        transY = imgHeight - ((vertical + 1) * avcharHeight);
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -329,13 +345,16 @@ public class SeqCanvas extends Panel
     gg.setColor(Color.white);
     gg.fillRect(0, 0, imgWidth, imgHeight);
 
+    ViewportRanges ranges = av.getRanges();
+
     if (av.getWrapAlignment())
     {
-      drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);
+      drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
     }
     else
     {
-      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+              ranges.getStartSeq(), ranges.getEndSeq(), 0);
     }
 
     g.drawImage(img, 0, 0, this);
@@ -421,7 +440,7 @@ public class SeqCanvas extends Panel
 
     av.setWrappedWidth(cWidth);
 
-    av.endRes = av.startRes + cWidth;
+    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
 
     int endx;
     int ypos = hgap;
@@ -430,7 +449,8 @@ public class SeqCanvas extends Panel
 
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
@@ -464,12 +484,13 @@ public class SeqCanvas extends Panel
       }
       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
       {
+        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
         g.setColor(Color.blue);
         int res;
-        for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
+        for (int i = 0; i < hidden.getHiddenRegions()
                 .size(); i++)
         {
-          res = av.getColumnSelection().findHiddenRegionPosition(i)
+          res = hidden.findHiddenRegionPosition(i)
                   - startRes;
 
           if (res < 0 || res > endx - startRes)
@@ -491,7 +512,7 @@ public class SeqCanvas extends Panel
         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
       }
 
-      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
+      drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
       g.setClip(null);
 
       if (av.isShowAnnotation())
@@ -548,7 +569,8 @@ public class SeqCanvas extends Panel
 
       if (av.hasHiddenColumns())
       {
-        for (int[] region : av.getColumnSelection().getHiddenColumns())
+        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+        for (int[] region : hidden.getHiddenRegions())
         {
           int hideStart = region[0];
           int hideEnd = region[1];
@@ -570,7 +592,7 @@ public class SeqCanvas extends Panel
             g1.setColor(Color.blue);
             g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
                     0 + offset, (blockEnd - blockStart + 1) * avcharWidth
-                            - 1, (endSeq - startSeq) * avcharHeight
+                            - 1, (endSeq - startSeq + 1) * avcharHeight
                             + offset);
           }
 
@@ -610,7 +632,7 @@ public class SeqCanvas extends Panel
 
     // / First draw the sequences
     // ///////////////////////////
-    for (int i = startSeq; i < endSeq; i++)
+    for (int i = startSeq; i <= endSeq; i++)
     {
       nextSeq = av.getAlignment().getSequenceAt(i);
 
@@ -625,7 +647,7 @@ public class SeqCanvas extends Panel
       if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                + ((i - startSeq) * avcharHeight));
+                + ((i - startSeq) * avcharHeight), false);
       }
 
       // / Highlight search Results once all sequences have been drawn
@@ -694,7 +716,7 @@ public class SeqCanvas extends Panel
         int bottom = -1;
         int alHeight = av.getAlignment().getHeight() - 1;
 
-        for (i = startSeq; i < endSeq; i++)
+        for (i = startSeq; i <= endSeq; i++)
         {
           sx = (group.getStartRes() - startRes) * avcharWidth;
           sy = offset + ((i - startSeq) * avcharHeight);
@@ -848,4 +870,35 @@ public class SeqCanvas extends Panel
     repaint();
   }
 
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    if (!av.getWrapAlignment())
+    {
+      if (evt.getPropertyName().equals("startres")
+              || evt.getPropertyName().equals("endres"))
+      {
+        // Make sure we're not trying to draw a panel
+        // larger than the visible window
+        ViewportRanges vpRanges = av.getRanges();
+        int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+        if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
+        {
+          scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
+        }
+        else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
+        {
+          scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
+        }
+        fastPaint(scrollX, 0);
+      }
+      else if (evt.getPropertyName().equals("startseq")
+              || evt.getPropertyName().equals("endseq"))
+      {
+        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+      }
+    }
+
+  }
+
 }
index 8cfd2dc..4aa205e 100644 (file)
@@ -25,6 +25,7 @@ import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -38,9 +39,11 @@ import jalview.structure.SelectionSource;
 import jalview.structure.SequenceListener;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Font;
@@ -51,6 +54,10 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
 import java.util.Vector;
 
 public class SeqPanel extends Panel implements MouseMotionListener,
@@ -179,19 +186,22 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     seqCanvas.cursorX += dx;
     seqCanvas.cursorY += dy;
     if (av.hasHiddenColumns()
-            && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+            && !av.getAlignment().getHiddenColumns()
+                    .isVisible(seqCanvas.cursorX))
     {
       int original = seqCanvas.cursorX - dx;
       int maxWidth = av.getAlignment().getWidth();
 
-      while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
+      while (!av.getAlignment().getHiddenColumns()
+              .isVisible(seqCanvas.cursorX)
               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
       {
         seqCanvas.cursorX += dx;
       }
 
       if (seqCanvas.cursorX >= maxWidth
-              || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+              || !av.getAlignment().getHiddenColumns()
+                      .isVisible(seqCanvas.cursorX))
       {
         seqCanvas.cursorX = original;
       }
@@ -222,31 +232,33 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     endEditing();
     if (av.getWrapAlignment())
     {
-      ap.scrollToWrappedVisible(seqCanvas.cursorX);
+      av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
     }
     else
     {
-      while (seqCanvas.cursorY < av.startSeq)
+      ViewportRanges ranges = av.getRanges();
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      while (seqCanvas.cursorY < ranges.getStartSeq())
       {
-        ap.scrollUp(true);
+        ranges.scrollUp(true);
       }
-      while (seqCanvas.cursorY + 1 > av.endSeq)
+      while (seqCanvas.cursorY > ranges.getEndSeq())
       {
-        ap.scrollUp(false);
+        ranges.scrollUp(false);
       }
-      while (seqCanvas.cursorX < av.getColumnSelection()
-              .adjustForHiddenColumns(av.startRes))
+      while (seqCanvas.cursorX < hidden.adjustForHiddenColumns(ranges
+              .getStartRes()))
       {
 
-        if (!ap.scrollRight(false))
+        if (!ranges.scrollRight(false))
         {
           break;
         }
       }
-      while (seqCanvas.cursorX > av.getColumnSelection()
-              .adjustForHiddenColumns(av.endRes))
+      while (seqCanvas.cursorX > hidden.adjustForHiddenColumns(ranges
+              .getEndRes()))
       {
-        if (!ap.scrollRight(true))
+        if (!ranges.scrollRight(true))
         {
           break;
         }
@@ -405,13 +417,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
    * 
    * @param sequence
    *          aligned sequence object
-   * @param res
+   * @param column
    *          alignment column
    * @param seq
    *          index of sequence in alignment
-   * @return position of res in sequence
+   * @return position of column in sequence or -1 if at gap
    */
-  void setStatusMessage(SequenceI sequence, int res, int seq)
+  void setStatusMessage(SequenceI sequence, int column, int seq)
   {
     // TODO remove duplication of identical gui method
     StringBuilder text = new StringBuilder(32);
@@ -422,7 +434,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     /*
      * Try to translate the display character to residue name (null for gap).
      */
-    final String displayChar = String.valueOf(sequence.getCharAt(res));
+    final String displayChar = String.valueOf(sequence.getCharAt(column));
     if (av.getAlignment().isNucleotide())
     {
       residue = ResidueProperties.nucleotideName.get(displayChar);
@@ -445,7 +457,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     int pos = -1;
     if (residue != null)
     {
-      pos = sequence.findPosition(res);
+      pos = sequence.findPosition(column);
       text.append(" (").append(Integer.toString(pos)).append(")");
     }
 
@@ -555,20 +567,23 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         av.setSelectionGroup(null);
       }
 
-      SequenceFeature[] features = findFeaturesAtRes(sequence,
-              sequence.findPosition(findRes(evt)));
+      int column = findRes(evt);
+      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+      List<SequenceFeature> features = findFeaturesAtRes(sequence,
+              sequence.findPosition(column));
+      if (isGapped)
+      {
+        removeAdjacentFeatures(features, column + 1, sequence);
+      }
 
-      if (features != null && features.length > 0)
+      if (!features.isEmpty())
       {
         SearchResultsI highlight = new SearchResults();
-        highlight.addResult(sequence, features[0].getBegin(),
-                features[0].getEnd());
+        highlight.addResult(sequence, features.get(0).getBegin(), features
+                .get(0).getEnd());
         seqCanvas.highlightSearchResults(highlight);
-      }
-      if (features != null && features.length > 0)
-      {
         seqCanvas.getFeatureRenderer().amendFeatures(
-                new SequenceI[] { sequence }, features, false, ap);
+                Collections.singletonList(sequence), features, false, ap);
 
         seqCanvas.highlightSearchResults(null);
       }
@@ -578,13 +593,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   @Override
   public void mouseReleased(MouseEvent evt)
   {
+    boolean didDrag = mouseDragging; // did we come here after a drag
     mouseDragging = false;
     mouseWheelPressed = false;
-    ap.paintAlignment(true);
 
     if (!editingSeqs)
     {
-      doMouseReleasedDefineMode(evt);
+      doMouseReleasedDefineMode(evt, didDrag);
       return;
     }
 
@@ -624,19 +639,20 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getStartRes() / cwidth;
+      wrappedBlock += av.getRanges().getStartRes() / cwidth;
 
       res = wrappedBlock * cwidth + x / av.getCharWidth();
 
     }
     else
     {
-      res = (x / av.getCharWidth()) + av.getStartRes();
+      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
     }
 
     if (av.hasHiddenColumns())
     {
-      res = av.getColumnSelection().adjustForHiddenColumns(res);
+      res = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(res);
     }
 
     return res;
@@ -681,7 +697,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
     else
     {
-      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
+      seq = Math.min((y / av.getCharHeight())
+              + av.getRanges().getStartSeq(),
+              av
               .getAlignment().getHeight() - 1);
       if (seq < 0)
       {
@@ -736,10 +754,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   {
     if (av.isFollowHighlight())
     {
+      // don't allow highlight of protein/cDNA to also scroll a complementary
+      // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
+      // over residue to change abruptly, causing highlighted residue in panel 2
+      // to change, causing a scroll in panel 1 etc)
+      ap.setToScrollComplementPanel(false);
       if (ap.scrollToPosition(results, true))
       {
         ap.alignFrame.repaint();
       }
+      ap.setToScrollComplementPanel(true);
     }
     setStatusMessage(results);
     seqCanvas.highlightSearchResults(results);
@@ -762,10 +786,10 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   @Override
   public void mouseMoved(MouseEvent evt)
   {
-    int res = findRes(evt);
+    final int column = findRes(evt);
     int seq = findSeq(evt);
 
-    if (seq >= av.getAlignment().getHeight() || seq < 0 || res < 0)
+    if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
     {
       if (tooltip != null)
       {
@@ -775,7 +799,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-    if (res > sequence.getLength())
+    if (column > sequence.getLength())
     {
       if (tooltip != null)
       {
@@ -784,38 +808,34 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       return;
     }
 
-    int respos = sequence.findPosition(res);
-    if (ssm != null)
+    final char ch = sequence.getCharAt(column);
+    boolean isGapped = Comparison.isGap(ch);
+    // find residue at column (or nearest if at a gap)
+    int respos = sequence.findPosition(column);
+
+    if (ssm != null && !isGapped)
     {
-      mouseOverSequence(sequence, res, respos);
+      mouseOverSequence(sequence, column, respos);
     }
 
     StringBuilder text = new StringBuilder();
     text.append("Sequence ").append(Integer.toString(seq + 1))
             .append(" ID: ").append(sequence.getName());
 
-    String obj = null;
-    final String ch = String.valueOf(sequence.getCharAt(res));
-    if (av.getAlignment().isNucleotide())
+    if (!isGapped)
     {
-      obj = ResidueProperties.nucleotideName.get(ch);
-      if (obj != null)
+      if (av.getAlignment().isNucleotide())
       {
-        text.append(" Nucleotide: ").append(obj);
+        String base = ResidueProperties.nucleotideName.get(ch);
+        text.append(" Nucleotide: ").append(base == null ? ch : base);
       }
-    }
-    else
-    {
-      obj = "X".equalsIgnoreCase(ch) ? "X" : ResidueProperties.aa2Triplet
-              .get(ch);
-      if (obj != null)
+      else
       {
-        text.append(" Residue: ").append(obj);
+        String residue = (ch == 'x' || ch == 'X') ? "X"
+                : ResidueProperties.aa2Triplet
+                .get(String.valueOf(ch));
+        text.append(" Residue: ").append(residue == null ? ch : residue);
       }
-    }
-
-    if (obj != null)
-    {
       text.append(" (").append(Integer.toString(respos)).append(")");
     }
 
@@ -827,7 +847,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       for (int g = 0; g < groups.length; g++)
       {
-        if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
+        if (groups[g].getStartRes() <= column && groups[g].getEndRes() >= column)
         {
           if (!groups[g].getName().startsWith("JTreeGroup")
                   && !groups[g].getName().startsWith("JGroup"))
@@ -843,33 +863,37 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
     }
 
-    // use aa to see if the mouse pointer is on a
-    SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
-            sequence.findPosition(res));
-
-    int index = 0;
-    while (index < allFeatures.length)
+    /*
+     * add feature details to tooltip, including any that straddle
+     * a gapped position
+     */
+    if (av.isShowSequenceFeatures())
     {
-      SequenceFeature sf = allFeatures[index];
-
-      tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
-
-      if (sf.getDescription() != null)
+      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
+              sequence.findPosition(column));
+      if (isGapped)
       {
-        tooltipText.append(" " + sf.getDescription());
+        removeAdjacentFeatures(allFeatures, column + 1, sequence);
       }
-
-      if (sf.getValue("status") != null)
+      for (SequenceFeature sf : allFeatures)
       {
-        String status = sf.getValue("status").toString();
-        if (status.length() > 0)
+        tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
+
+        if (sf.getDescription() != null)
         {
-          tooltipText.append(" (" + sf.getValue("status") + ")");
+          tooltipText.append(" " + sf.getDescription());
         }
-      }
-      tooltipText.append("\n");
 
-      index++;
+        if (sf.getValue("status") != null)
+        {
+          String status = sf.getValue("status").toString();
+          if (status.length() > 0)
+          {
+            tooltipText.append(" (" + sf.getValue("status") + ")");
+          }
+        }
+        tooltipText.append("\n");
+      }
     }
 
     if (tooltip == null)
@@ -882,9 +906,38 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
   }
 
-  SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
+  /**
+   * Removes from the list of features any that start after, or end before, the
+   * given column position. This allows us to retain only those features
+   * adjacent to a gapped position that straddle the position. Contact features
+   * that 'straddle' the position are also removed, since they are not 'at' the
+   * position.
+   * 
+   * @param features
+   * @param column
+   *          alignment column (1..)
+   * @param sequence
+   */
+  protected void removeAdjacentFeatures(List<SequenceFeature> features,
+          int column, SequenceI sequence)
+  {
+    // TODO should this be an AlignViewController method (shared by gui)?
+    ListIterator<SequenceFeature> it = features.listIterator();
+    while (it.hasNext())
+    {
+      SequenceFeature sf = it.next();
+      if (sf.isContactFeature()
+              || sequence.findIndex(sf.getBegin()) > column
+              || sequence.findIndex(sf.getEnd()) < column)
+      {
+        it.remove();
+      }
+    }
+  }
+
+  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
   {
-    Vector tmp = new Vector();
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
     SequenceFeature[] features = sequence.getSequenceFeatures();
     if (features != null)
     {
@@ -907,15 +960,12 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         if ((features[i].getBegin() <= res)
                 && (features[i].getEnd() >= res))
         {
-          tmp.addElement(features[i]);
+          result.add(features[i]);
         }
       }
     }
 
-    features = new SequenceFeature[tmp.size()];
-    tmp.copyInto(features);
-
-    return features;
+    return result;
   }
 
   Tooltip tooltip;
@@ -955,7 +1005,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
           fontSize = 1;
         }
 
-        av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
+        av.setFont(
+                new Font(av.font.getName(), av.font.getStyle(), fontSize),
+                true);
         av.setCharWidth(oldWidth);
       }
       else
@@ -1127,8 +1179,10 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     if (av.hasHiddenColumns())
     {
       fixedColumns = true;
-      int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
-      int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
+      int y1 = av.getAlignment().getHiddenColumns()
+              .getHiddenBoundaryLeft(startres);
+      int y2 = av.getAlignment().getHiddenColumns()
+              .getHiddenBoundaryRight(startres);
 
       if ((insertGap && startres > y1 && lastres < y1)
               || (!insertGap && startres < y2 && lastres > y2))
@@ -1199,8 +1253,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         {
           if (sg.getSize() == av.getAlignment().getHeight())
           {
-            if ((av.hasHiddenColumns() && startres < av
-                    .getColumnSelection().getHiddenBoundaryRight(startres)))
+            if ((av.hasHiddenColumns() && startres < av.getAlignment()
+                    .getHiddenColumns().getHiddenBoundaryRight(startres)))
             {
               endEditing();
               return;
@@ -1423,57 +1477,35 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
     stretchGroup = av.getSelectionGroup();
 
-    if (stretchGroup == null)
+    if (stretchGroup == null || !stretchGroup.contains(sequence, res))
     {
       stretchGroup = av.getAlignment().findGroup(sequence, res);
-      av.setSelectionGroup(stretchGroup);
-    }
-
-    if (stretchGroup == null
-            || !stretchGroup.getSequences(null).contains(sequence)
-            || stretchGroup.getStartRes() > res
-            || stretchGroup.getEndRes() < res)
-    {
-      stretchGroup = null;
-
-      SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
-
-      if (allGroups != null)
+      if (stretchGroup != null)
       {
-        for (int i = 0; i < allGroups.length; i++)
-        {
-          if (allGroups[i].getStartRes() <= res
-                  && allGroups[i].getEndRes() >= res)
-          {
-            stretchGroup = allGroups[i];
-            break;
-          }
-        }
+        // only update the current selection if the popup menu has a group to
+        // focus on
+        av.setSelectionGroup(stretchGroup);
       }
-      av.setSelectionGroup(stretchGroup);
     }
 
     // DETECT RIGHT MOUSE BUTTON IN AWT
     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
     {
-      SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
+      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
               sequence.findPosition(res));
 
       Vector<String> links = null;
-      if (allFeatures != null)
+      for (SequenceFeature sf : allFeatures)
       {
-        for (int i = 0; i < allFeatures.length; i++)
+        if (sf.links != null)
         {
-          if (allFeatures[i].links != null)
+          if (links == null)
           {
-            if (links == null)
-            {
-              links = new Vector<String>();
-            }
-            for (int j = 0; j < allFeatures[i].links.size(); j++)
-            {
-              links.addElement(allFeatures[i].links.elementAt(j));
-            }
+            links = new Vector<String>();
+          }
+          for (int j = 0; j < sf.links.size(); j++)
+          {
+            links.addElement(sf.links.elementAt(j));
           }
         }
       }
@@ -1517,7 +1549,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
   }
 
-  public void doMouseReleasedDefineMode(MouseEvent evt)
+  public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag)
   {
     if (stretchGroup == null)
     {
@@ -1527,7 +1559,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     // but defer colourscheme update until hidden sequences are passed in
     boolean vischange = stretchGroup.recalcConservation(true);
     // here we rely on stretchGroup == av.getSelection()
-    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
+    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup()
+            && afterDrag;
     if (stretchGroup.cs != null)
     {
       stretchGroup.cs.alignmentChanged(stretchGroup,
@@ -1662,8 +1695,10 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       oldSeq = -1;
     }
 
-    if (res > av.endRes || res < av.startRes || y < av.startSeq
-            || y > av.endSeq)
+    if (res > av.getRanges().getEndRes()
+            || res < av.getRanges().getStartRes()
+            || y < av.getRanges().getStartSeq()
+            || y > av.getRanges().getEndSeq())
     {
       mouseExited(evt);
     }
@@ -1761,25 +1796,27 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         if (evt != null)
         {
 
-          if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
+          if (mouseDragging && evt.getY() < 0
+                  && av.getRanges().getStartSeq() > 0)
           {
-            running = ap.scrollUp(true);
+            running = av.getRanges().scrollUp(true);
           }
 
           if (mouseDragging && evt.getY() >= getSize().height
-                  && av.getAlignment().getHeight() > av.getEndSeq())
+                  && av.getAlignment().getHeight() > av.getRanges()
+                          .getEndSeq())
           {
-            running = ap.scrollUp(false);
+            running = av.getRanges().scrollUp(false);
           }
 
           if (mouseDragging && evt.getX() < 0)
           {
-            running = ap.scrollRight(false);
+            running = av.getRanges().scrollRight(false);
           }
 
           else if (mouseDragging && evt.getX() >= getSize().width)
           {
-            running = ap.scrollRight(true);
+            running = av.getRanges().scrollRight(true);
           }
         }
 
@@ -1798,7 +1835,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
    */
   @Override
   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
+          HiddenColumns hidden, SelectionSource source)
   {
     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
     // handles selection messages...
@@ -1815,7 +1852,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
      * Check for selection in a view of which this one is a dna/protein
      * complement.
      */
-    if (selectionFromTranslation(seqsel, colsel, source))
+    if (selectionFromTranslation(seqsel, colsel, hidden, source))
     {
       return;
     }
@@ -1881,15 +1918,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         }
         else
         {
-          av.getColumnSelection().setElementsFrom(colsel);
+          av.getColumnSelection().setElementsFrom(colsel,
+                  av.getAlignment().getHiddenColumns());
         }
       }
       repaint |= av.isColSelChanged(true);
     }
     if (copycolsel
             && av.hasHiddenColumns()
-            && (av.getColumnSelection() == null || av.getColumnSelection()
-                    .getHiddenColumns() == null))
+            && (av.getColumnSelection() == null || av.getAlignment()
+                    .getHiddenColumns().getHiddenRegions() == null))
     {
       System.err.println("Bad things");
     }
@@ -1909,8 +1947,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollTo(int row, int column)
   {
 
-    row = row < 0 ? ap.av.startSeq : row;
-    column = column < 0 ? ap.av.startRes : column;
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
     ap.scrollTo(column, column, row, true, true);
   }
 
@@ -1922,8 +1960,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollToRow(int row)
   {
 
-    row = row < 0 ? ap.av.startSeq : row;
-    ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true);
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges()
+            .getStartRes(), row, true, true);
   }
 
   /**
@@ -1934,8 +1973,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollToColumn(int column)
   {
 
-    column = column < 0 ? ap.av.startRes : column;
-    ap.scrollTo(column, column, ap.av.startSeq, true, true);
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
+    ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true);
   }
 
   /**
@@ -1948,7 +1987,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
    * @param source
    */
   protected boolean selectionFromTranslation(SequenceGroup seqsel,
-          ColumnSelection colsel, SelectionSource source)
+          ColumnSelection colsel, HiddenColumns hidden,
+          SelectionSource source)
   {
     if (!(source instanceof AlignViewportI))
     {
@@ -1971,9 +2011,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     /*
      * Map column selection
      */
-    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
-            av);
+    // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
+    // av);
+    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns hs = new HiddenColumns();
+    MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
     av.setColumnSelection(cs);
+    av.getAlignment().setHiddenColumns(hs);
 
     ap.scalePanelHolder.repaint();
     ap.repaint();
index 86d1f98..38031e4 100755 (executable)
  */
 package jalview.appletgui;
 
-import jalview.api.FeatureRenderer;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 
 import java.awt.Color;
 import java.awt.Font;
@@ -69,8 +69,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     this.renderGaps = renderGaps;
   }
 
-  @Override
-  public Color getResidueBoxColour(SequenceI seq, int i)
+  protected Color getResidueBoxColour(SequenceI seq, int i)
   {
     allGroups = av.getAlignment().findAllGroups(seq);
 
@@ -96,27 +95,27 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
    * 
    * @param seq
    * @param position
-   * @param fr
+   * @param finder
    * @return
    */
   @Override
   public Color getResidueColour(final SequenceI seq, int position,
-          FeatureRenderer fr)
+          FeatureColourFinder finder)
   {
     // TODO replace 8 or so code duplications with calls to this method
     // (refactored as needed)
     Color col = getResidueBoxColour(seq, position);
 
-    if (fr != null)
+    if (finder != null)
     {
-      col = fr.findFeatureColour(col, seq, position);
+      col = finder.findFeatureColour(col, seq, position);
     }
     return col;
   }
 
   void getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
   {
-    if (shader != null)
+    if (shader.getColourScheme() != null)
     {
       resBoxColour = shader.findColour(seq.getCharAt(i), i, seq);
     }
index e30879c..272a2b3 100755 (executable)
@@ -21,7 +21,7 @@
 package jalview.appletgui;
 
 import jalview.analysis.Conservation;
-import jalview.analysis.NJTree;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
@@ -48,12 +48,13 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Vector;
 
 public class TreeCanvas extends Panel implements MouseListener,
         MouseMotionListener
 {
-  NJTree tree;
+  TreeModel tree;
 
   ScrollPane scrollPane;
 
@@ -115,13 +116,13 @@ public class TreeCanvas extends Panel implements MouseListener,
     selected.addOrRemove(sequence, true);
   }
 
-  public void setTree(NJTree tree)
+  public void setTree(TreeModel tree2)
   {
-    this.tree = tree;
-    tree.findHeight(tree.getTopNode());
+    this.tree = tree2;
+    tree2.findHeight(tree2.getTopNode());
 
     // Now have to calculate longest name based on the leaves
-    Vector<SequenceNode> leaves = tree.findLeaves(tree.getTopNode());
+    Vector<SequenceNode> leaves = tree2.findLeaves(tree2.getTopNode());
     boolean has_placeholders = false;
     longestName = "";
 
@@ -146,7 +147,7 @@ public class TreeCanvas extends Panel implements MouseListener,
   }
 
   public void drawNode(Graphics g, SequenceNode node, float chunk,
-          float scale, int width, int offx, int offy)
+          double scale, int width, int offx, int offy)
   {
     if (node == null)
     {
@@ -157,8 +158,8 @@ public class TreeCanvas extends Panel implements MouseListener,
     {
       // Drawing leaf node
 
-      float height = node.height;
-      float dist = node.dist;
+      double height = node.height;
+      double dist = node.dist;
 
       int xstart = (int) ((height - dist) * scale) + offx;
       int xend = (int) (height * scale) + offx;
@@ -240,8 +241,8 @@ public class TreeCanvas extends Panel implements MouseListener,
       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
               offy);
 
-      float height = node.height;
-      float dist = node.dist;
+      double height = node.height;
+      double dist = node.dist;
 
       int xstart = (int) ((height - dist) * scale) + offx;
       int xend = (int) (height * scale) + offx;
@@ -260,10 +261,11 @@ public class TreeCanvas extends Panel implements MouseListener,
         g.fillRect(xend - 2, ypos - 2, 4, 4);
       }
 
-      int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
-              + offy;
-      int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
+      int ystart = (int) (node.left() == null ? 0 : (((SequenceNode) node
+              .left()).ycount * chunk))
               + offy;
+      int yend = (int) (node.right() == null ? 0 : (((SequenceNode) node
+              .right()).ycount * chunk)) + offy;
 
       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
       nodeHash.put(node, pos);
@@ -338,7 +340,7 @@ public class TreeCanvas extends Panel implements MouseListener,
 
     SequenceNode top = tree.getTopNode();
 
-    float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
+    double wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
     if (top.count == 0)
     {
       top.count = ((SequenceNode) top.left()).count
@@ -350,7 +352,7 @@ public class TreeCanvas extends Panel implements MouseListener,
   }
 
   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
-          float scale, int width, int offx, int offy)
+          double scale, int width, int offx, int offy)
   {
     if (node == null)
     {
@@ -359,7 +361,7 @@ public class TreeCanvas extends Panel implements MouseListener,
 
     if (node.left() == null && node.right() == null)
     {
-      float height = node.height;
+      double height = node.height;
       // float dist = node.dist;
 
       // int xstart = (int) ( (height - dist) * scale) + offx;
@@ -465,7 +467,7 @@ public class TreeCanvas extends Panel implements MouseListener,
     // for
     // scrollbar
 
-    float wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
+    double wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
 
     SequenceNode top = tree.getTopNode();
 
@@ -593,8 +595,7 @@ public class TreeCanvas extends Panel implements MouseListener,
         threshold = (float) (x - offx)
                 / (float) (getSize().width - labelLength - 2 * offx);
 
-        tree.getGroups().removeAllElements();
-        tree.groupNodes(tree.getTopNode(), threshold);
+        List<SequenceNode> groups = tree.groupNodes(threshold);
         setColor(tree.getTopNode(), Color.black);
 
         av.setSelectionGroup(null);
@@ -608,7 +609,7 @@ public class TreeCanvas extends Panel implements MouseListener,
           codingComplement.clearSequenceColours();
         }
 
-        colourGroups();
+        colourGroups(groups);
 
       }
     }
@@ -618,17 +619,16 @@ public class TreeCanvas extends Panel implements MouseListener,
 
   }
 
-  void colourGroups()
+  void colourGroups(List<SequenceNode> groups)
   {
-    for (int i = 0; i < tree.getGroups().size(); i++)
+    for (int i = 0; i < groups.size(); i++)
     {
 
       Color col = new Color((int) (Math.random() * 255),
               (int) (Math.random() * 255), (int) (Math.random() * 255));
-      setColor(tree.getGroups().elementAt(i), col.brighter());
+      setColor(groups.get(i), col.brighter());
 
-      Vector<SequenceNode> l = tree.findLeaves(tree.getGroups()
-              .elementAt(i));
+      Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
 
       Vector<SequenceI> sequences = new Vector<SequenceI>();
       for (int j = 0; j < l.size(); j++)
index b4b8ec2..c7bf6aa 100644 (file)
  */
 package jalview.appletgui;
 
+import jalview.analysis.AverageDistanceTree;
 import jalview.analysis.NJTree;
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.TreeModel;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.api.analysis.ScoreModelI;
-import jalview.api.analysis.ViewBasedAnalysisI;
 import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.io.NewickFile;
-import jalview.schemes.ResidueProperties;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -58,17 +60,18 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
 
   TreeCanvas treeCanvas;
 
-  NJTree tree;
+  TreeModel tree;
 
   AlignmentPanel ap;
 
   AlignViewport av;
 
-  public NJTree getTree()
+  public TreeModel getTree()
   {
     return tree;
   }
 
+  @Override
   public void finalize() throws Throwable
   {
     ap = null;
@@ -78,21 +81,8 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
 
   /**
    * Creates a new TreePanel object.
-   * 
-   * @param av
-   *          DOCUMENT ME!
-   * @param seqVector
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
-   * @param pwtype
-   *          DOCUMENT ME!
-   * @param s
-   *          DOCUMENT ME!
-   * @param e
-   *          DOCUMENT ME!
    */
-  public TreePanel(AlignmentPanel ap, String type, String pwtype)
+  public TreePanel(AlignmentPanel alignPanel, String type, String pwtype)
   {
     try
     {
@@ -103,22 +93,12 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
       ex.printStackTrace();
     }
 
-    initTreePanel(ap, type, pwtype, null);
+    initTreePanel(alignPanel, type, pwtype, null);
   }
 
   /**
    * Creates a new TreePanel object.
    * 
-   * @param av
-   *          DOCUMENT ME!
-   * @param seqVector
-   *          DOCUMENT ME!
-   * @param newtree
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
-   * @param pwtype
-   *          DOCUMENT ME!
    */
   public TreePanel(AlignmentPanel ap, String type, String pwtype,
           NewickFile newtree)
@@ -159,7 +139,7 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
     // yields unaligned seqs)
     // or create a selection box around columns in alignment view
     // test Alignment(SeqCigar[])
-    if (tree.seqData != null)
+    if (tree.getOriginalData() != null)
     {
       char gc = '-';
       try
@@ -170,9 +150,9 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
       } catch (Exception ex)
       {
       }
-      ;
-      Object[] alAndColsel = tree.seqData
-              .getAlignmentAndColumnSelection(gc);
+
+      Object[] alAndColsel = tree.getOriginalData()
+              .getAlignmentAndHiddenColumns(gc);
 
       if (alAndColsel != null && alAndColsel[0] != null)
       {
@@ -180,7 +160,8 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
         AlignFrame af = new AlignFrame(al, av.applet,
                 "Original Data for Tree", false);
 
-        af.viewport.setHiddenColumns((ColumnSelection) alAndColsel[1]);
+        af.viewport.getAlignment().setHiddenColumns(
+                (HiddenColumns) alAndColsel[1]);
       }
     }
     else
@@ -200,62 +181,23 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
       this.newtree = newtree;
     }
 
+    @Override
     public void run()
     {
       if (newtree != null)
       {
-        if (odata == null)
-        {
-          tree = new NJTree(av.getAlignment().getSequencesArray(), newtree);
-        }
-        else
-        {
-          tree = new NJTree(av.getAlignment().getSequencesArray(), odata,
-                  newtree);
-        }
-
+        tree = new TreeModel(av.getAlignment().getSequencesArray(), odata,
+                newtree);
       }
       else
       {
-        int start, end;
-        SequenceI[] seqs;
-        boolean selview = av.getSelectionGroup() != null
-                && av.getSelectionGroup().getSize() > 1;
-        AlignmentView seqStrings = av.getAlignmentView(selview);
-        if (!selview)
-        {
-          start = 0;
-          end = av.getAlignment().getWidth();
-          seqs = av.getAlignment().getSequencesArray();
-        }
-        else
-        {
-          start = av.getSelectionGroup().getStartRes();
-          end = av.getSelectionGroup().getEndRes() + 1;
-          seqs = av.getSelectionGroup().getSequencesInOrder(
-                  av.getAlignment());
-        }
-        ScoreModelI sm = ResidueProperties.getScoreModel(pwtype);
-        if (sm instanceof ViewBasedAnalysisI)
-        {
-          try
-          {
-            sm = sm.getClass().newInstance();
-            ((ViewBasedAnalysisI) sm)
-                    .configureFromAlignmentView(treeCanvas.ap);
-          } catch (Exception q)
-          {
-            System.err.println("Couldn't create a scoremodel instance for "
-                    + sm.getName());
-            q.printStackTrace();
-          }
-          tree = new NJTree(seqs, seqStrings, type, pwtype, sm, start, end);
-        }
-        else
-        {
-          tree = new NJTree(seqs, seqStrings, type, pwtype, null, start,
-                  end);
-        }
+        ScoreModelI sm1 = ScoreModels.getInstance().getScoreModel(pwtype,
+                treeCanvas.ap);
+        ScoreModelI sm = sm1;
+        TreeBuilder njtree = type.equals(TreeBuilder.NEIGHBOUR_JOINING) ? new NJTree(
+                av, sm, SimilarityParams.Jalview)
+                : new AverageDistanceTree(av, sm, SimilarityParams.Jalview);
+        tree = new TreeModel(njtree);
       }
 
       tree.reCount(tree.getTopNode());
@@ -286,6 +228,7 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
     }
   }
 
+  @Override
   public void actionPerformed(ActionEvent evt)
   {
     if (evt.getSource() == newickOutput)
@@ -302,6 +245,7 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
     }
   }
 
+  @Override
   public void itemStateChanged(ItemEvent evt)
   {
     if (evt.getSource() == fitToWindow)
index 95e36b5..845110e 100644 (file)
  */
 package jalview.appletgui;
 
+import jalview.analysis.AAFrequency;
 import jalview.api.FeatureColourI;
 import jalview.datamodel.SequenceGroup;
 import jalview.renderer.ResidueShader;
+import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.FeatureColour;
+import jalview.schemes.PIDColourScheme;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
 import jalview.util.MessageManager;
@@ -579,35 +582,25 @@ public class UserDefinedColours extends Panel implements ActionListener,
       return;
     }
 
-    Color[] newColours = new Color[24];
-    for (int i = 0; i < 24; i++)
-    {
-      newColours[i] = oldColours.elementAt(i);
-      buttonPanel.getComponent(i).setBackground(newColours[i]);
-    }
-
-    UserColourScheme ucs = new UserColourScheme(newColours);
-
     if (ap != null)
     {
       if (seqGroup != null)
       {
-        seqGroup.cs = new ResidueShader(ucs);
+        seqGroup.cs = new ResidueShader(oldColourScheme);
+        if (oldColourScheme instanceof PIDColourScheme
+                || oldColourScheme instanceof Blosum62ColourScheme)
+        {
+          seqGroup.cs.setConsensus(AAFrequency.calculate(
+                  seqGroup.getSequences(ap.av.getHiddenRepSequences()), 0,
+                  ap.av.getAlignment().getWidth()));
+        }
       }
       else
       {
-        ap.av.setGlobalColourScheme(ucs);
+        ap.av.setGlobalColourScheme(oldColourScheme);
       }
       ap.paintAlignment(true);
     }
-    else if (jmol != null)
-    {
-      jmol.setJalviewColourScheme(ucs);
-    }
-    else if (pdbcanvas != null)
-    {
-      pdbcanvas.pdb.setColours(ucs);
-    }
 
     frame.setVisible(false);
   }
index 48c1ee9..da3cb92 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.bin;
 
 import jalview.datamodel.PDBEntry;
 import jalview.gui.UserDefinedColours;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureImportSettings;
@@ -1037,7 +1038,7 @@ public class Cache
       String file = st.nextToken();
       try
       {
-        UserColourScheme ucs = ColourSchemes.loadColourScheme(file);
+        UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
         if (ucs != null)
         {
           if (coloursFound.length() > 0)
index 966e952..954bb34 100755 (executable)
@@ -578,18 +578,14 @@ public class Jalview
         data = aparser.getValue("tree", true);
         if (data != null)
         {
-          jalview.io.NewickFile fin = null;
           try
           {
             System.out.println("CMD [-tree " + data
                     + "] executed successfully!");
-            fin = new NewickFile(data,
+            NewickFile nf = new NewickFile(data,
                     AppletFormatAdapter.checkProtocol(data));
-            if (fin != null)
-            {
-              af.getViewport().setCurrentTree(
-                      af.ShowNewickTree(fin, data).getTree());
-            }
+            af.getViewport().setCurrentTree(
+                    af.showNewickTree(nf, data).getTree());
           } catch (IOException ex)
           {
             System.err.println("Couldn't add tree " + data);
index 7fa5147..dbc707d 100644 (file)
@@ -31,6 +31,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
@@ -485,7 +486,8 @@ public class JalviewLite extends Applet implements
         @Override
         public void run()
         {
-          alf.select(sel, csel);
+          alf.select(sel, csel, alf.getAlignViewport().getAlignment()
+                  .getHiddenColumns());
         }
       });
     }
@@ -2273,9 +2275,9 @@ public class JalviewLite extends Applet implements
           SequenceI repseq = alignFrame.viewport.getAlignment()
                   .getSequenceAt(0);
           alignFrame.viewport.getAlignment().setSeqrep(repseq);
-          ColumnSelection cs = new ColumnSelection();
+          HiddenColumns cs = new HiddenColumns();
           cs.hideInsertionsFor(repseq);
-          alignFrame.viewport.setColumnSelection(cs);
+          alignFrame.viewport.getAlignment().setHiddenColumns(cs);
           alignFrame.alignPanel.fontChanged();
           alignFrame.alignPanel.setScrollValues(0, 0);
           result = true;
index e0326c0..25d770d 100755 (executable)
@@ -63,6 +63,8 @@ public class Alignment implements AlignmentI
 
   HiddenSequences hiddenSequences;
 
+  HiddenColumns hiddenCols;
+
   public Hashtable alignmentProperties;
 
   private List<AlignedCodonFrame> codonFrameList;
@@ -71,7 +73,8 @@ public class Alignment implements AlignmentI
   {
     groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
     hiddenSequences = new HiddenSequences(this);
-    codonFrameList = new ArrayList<AlignedCodonFrame>();
+    hiddenCols = new HiddenColumns();
+    codonFrameList = new ArrayList<>();
 
     nucleotide = Comparison.isNucleotide(seqs);
 
@@ -126,7 +129,7 @@ public class Alignment implements AlignmentI
   public Alignment(SeqCigar[] alseqs)
   {
     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
-            gapCharacter, new ColumnSelection(), null);
+            gapCharacter, new HiddenColumns(), null);
     initAlignment(seqs);
   }
 
@@ -186,14 +189,7 @@ public class Alignment implements AlignmentI
     return AlignmentUtils.getSequencesByName(this);
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+
   @Override
   public SequenceI getSequenceAt(int i)
   {
@@ -207,6 +203,28 @@ public class Alignment implements AlignmentI
     return null;
   }
 
+  @Override
+  public SequenceI getSequenceAtAbsoluteIndex(int i)
+  {
+    SequenceI seq = null;
+    if (getHiddenSequences().getSize() > 0)
+    {
+      seq = getHiddenSequences().getHiddenSequence(i);
+      if (seq == null)
+      {
+        // didn't find the sequence in the hidden sequences, get it from the
+        // alignment
+        int index = getHiddenSequences().findIndexWithoutHiddenSeqs(i);
+        seq = getSequenceAt(index);
+      }
+    }
+    else
+    {
+      seq = getSequenceAt(i);
+    }
+    return seq;
+  }
+
   /**
    * Adds a sequence to the alignment. Recalculates maxLength and size. Note
    * this currently does not recalculate whether or not the alignment is
@@ -321,30 +339,21 @@ public class Alignment implements AlignmentI
     }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param s
-   *          DOCUMENT ME!
-   */
   @Override
   public void deleteSequence(SequenceI s)
   {
-    deleteSequence(findIndex(s));
+    synchronized (sequences)
+    {
+      deleteSequence(findIndex(s));
+    }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   */
   @Override
   public void deleteSequence(int i)
   {
-    if (i > -1 && i < getHeight())
+    synchronized (sequences)
     {
-      synchronized (sequences)
+      if (i > -1 && i < getHeight())
       {
         sequences.remove(i);
         hiddenSequences.adjustHeightSequenceDeleted(i);
@@ -352,6 +361,18 @@ public class Alignment implements AlignmentI
     }
   }
 
+  @Override
+  public void deleteHiddenSequence(int i)
+  {
+    synchronized (sequences)
+    {
+      if (i > -1 && i < getHeight())
+      {
+        sequences.remove(i);
+      }
+    }
+  }
+
   /*
    * (non-Javadoc)
    * 
@@ -385,7 +406,7 @@ public class Alignment implements AlignmentI
   @Override
   public SequenceGroup[] findAllGroups(SequenceI s)
   {
-    ArrayList<SequenceGroup> temp = new ArrayList<SequenceGroup>();
+    ArrayList<SequenceGroup> temp = new ArrayList<>();
 
     synchronized (groups)
     {
@@ -436,7 +457,7 @@ public class Alignment implements AlignmentI
             return;
           }
         }
-        sg.setContext(this);
+        sg.setContext(this, true);
         groups.add(sg);
       }
     }
@@ -513,7 +534,7 @@ public class Alignment implements AlignmentI
       }
       for (SequenceGroup sg : groups)
       {
-        sg.setContext(null);
+        sg.setContext(null, false);
       }
       groups.clear();
     }
@@ -529,7 +550,7 @@ public class Alignment implements AlignmentI
       {
         removeAnnotationForGroup(g);
         groups.remove(g);
-        g.setContext(null);
+        g.setContext(null, false);
       }
     }
   }
@@ -669,22 +690,19 @@ public class Alignment implements AlignmentI
     return -1;
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+
   @Override
   public int getHeight()
   {
     return sequences.size();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+  @Override
+  public int getAbsoluteHeight()
+  {
+    return sequences.size() + getHiddenSequences().getSize();
+  }
+
   @Override
   public int getWidth()
   {
@@ -770,6 +788,12 @@ public class Alignment implements AlignmentI
     return true;
   }
 
+  @Override
+  public boolean isHidden(int alignmentIndex)
+  {
+    return (getHiddenSequences().getHiddenSequence(alignmentIndex) != null);
+  }
+
   /**
    * Delete all annotations, including auto-calculated if the flag is set true.
    * Returns true if at least one annotation was deleted, else false.
@@ -991,6 +1015,10 @@ public class Alignment implements AlignmentI
     }
     else if (dataset == null && data != null)
     {
+      if (data == this)
+      {
+        throw new IllegalArgumentException("Circular dataset reference");
+      }
       if (!(data instanceof Alignment))
       {
         throw new Error(
@@ -1040,21 +1068,18 @@ public class Alignment implements AlignmentI
         currentSeq = currentSeq.createDatasetSequence();
       }
     }
-    if (seqs.contains(currentSeq))
-    {
-      return;
-    }
-    List<SequenceI> toProcess = new ArrayList<SequenceI>();
+
+    List<SequenceI> toProcess = new ArrayList<>();
     toProcess.add(currentSeq);
     while (toProcess.size() > 0)
     {
       // use a queue ?
       SequenceI curDs = toProcess.remove(0);
-      if (seqs.contains(curDs))
+
+      if (!seqs.add(curDs))
       {
         continue;
       }
-      seqs.add(curDs);
       // iterate over database references, making sure we add forward referenced
       // sequences
       if (curDs.getDBRefs() != null)
@@ -1097,7 +1122,7 @@ public class Alignment implements AlignmentI
       return;
     }
     // try to avoid using SequenceI.equals at this stage, it will be expensive
-    Set<SequenceI> seqs = new LinkedIdentityHashSet<SequenceI>();
+    Set<SequenceI> seqs = new LinkedIdentityHashSet<>();
 
     for (int i = 0; i < getHeight(); i++)
     {
@@ -1303,6 +1328,12 @@ public class Alignment implements AlignmentI
   }
 
   @Override
+  public HiddenColumns getHiddenColumns()
+  {
+    return hiddenCols;
+  }
+
+  @Override
   public CigarArray getCompactAlignment()
   {
     synchronized (sequences)
@@ -1376,7 +1407,7 @@ public class Alignment implements AlignmentI
     {
       return null;
     }
-    List<AlignedCodonFrame> cframes = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> cframes = new ArrayList<>();
     for (AlignedCodonFrame acf : getCodonFrames())
     {
       if (acf.involvesSequence(seq))
@@ -1453,7 +1484,7 @@ public class Alignment implements AlignmentI
     if (sqs != null)
     {
       // avoid self append deadlock by
-      List<SequenceI> toappendsq = new ArrayList<SequenceI>();
+      List<SequenceI> toappendsq = new ArrayList<>();
       synchronized (sqs)
       {
         for (SequenceI addedsq : sqs)
@@ -1594,7 +1625,7 @@ public class Alignment implements AlignmentI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    List<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    List<AlignmentAnnotation> aa = new ArrayList<>();
     AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation();
     if (alignmentAnnotation != null)
     {
@@ -1611,20 +1642,18 @@ public class Alignment implements AlignmentI
     return aa;
   }
 
-  /**
-   * Returns an iterable collection of any annotations that match on given
-   * sequence ref, calcId and label (ignoring null values).
-   */
   @Override
   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
     for (AlignmentAnnotation ann : getAlignmentAnnotation())
     {
-      if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
-              && ann.sequenceRef != null && ann.sequenceRef == seq
-              && ann.label != null && ann.label.equals(label))
+      if ((calcId == null || (ann.getCalcId() != null && ann.getCalcId()
+              .equals(calcId)))
+              && (seq == null || (ann.sequenceRef != null && ann.sequenceRef == seq))
+              && (label == null || (ann.label != null && ann.label
+                      .equals(label))))
       {
         aa.add(ann);
       }
@@ -1822,7 +1851,7 @@ public class Alignment implements AlignmentI
   @Override
   public Set<String> getSequenceNames()
   {
-    Set<String> names = new HashSet<String>();
+    Set<String> names = new HashSet<>();
     for (SequenceI seq : getSequences())
     {
       names.add(seq.getName());
@@ -1922,6 +1951,11 @@ public class Alignment implements AlignmentI
     }
     return new int[] { startPos, endPos };
   }
+  @Override
+  public void setHiddenColumns(HiddenColumns cols)
+  {
+    hiddenCols = cols;
+  }
 
   Map<Object, ContactMatrixI> contactmaps = new HashMap<Object, ContactMatrixI>();
   @Override
index 43a1b06..72e5668 100755 (executable)
@@ -170,10 +170,14 @@ public class AlignmentAnnotation
    */
   private Map<Integer, Annotation> sequenceMapping;
 
-  /** DOCUMENT ME!! */
+  /**
+   * lower range for quantitative data
+   */
   public float graphMin;
 
-  /** DOCUMENT ME!! */
+  /**
+   * Upper range for quantitative data
+   */
   public float graphMax;
 
   /**
@@ -865,6 +869,10 @@ public class AlignmentAnnotation
   @Override
   public String toString()
   {
+    if (annotations == null)
+    {
+      return "";
+    }
     StringBuilder buffer = new StringBuilder(256);
 
     for (int i = 0; i < annotations.length; i++)
@@ -1144,14 +1152,14 @@ public class AlignmentAnnotation
    * @param colSel
    */
   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
-          ColumnSelection colSel)
+          HiddenColumns hidden)
   {
     this(alignmentAnnotation);
     if (annotations == null)
     {
       return;
     }
-    colSel.makeVisibleAnnotation(this);
+    hidden.makeVisibleAnnotation(this);
   }
 
   public void setPadGaps(boolean padgaps, char gapchar)
@@ -1488,4 +1496,13 @@ public class AlignmentAnnotation
   {
     return counter++;
   }
+
+  /**
+   * 
+   * @return true for rows that have a range of values in their annotation set
+   */
+  public boolean isQuantitative()
+  {
+    return graphMin < graphMax;
+  }
 }
index d7391cd..cfc45ae 100755 (executable)
@@ -31,13 +31,22 @@ import java.util.Set;
 public interface AlignmentI extends AnnotatedCollectionI
 {
   /**
-   * Calculates the number of sequences in an alignment
+   * Calculates the number of sequences in an alignment, excluding hidden
+   * sequences
    * 
    * @return Number of sequences in alignment
    */
   int getHeight();
 
   /**
+   * Calculates the number of sequences in an alignment, including hidden
+   * sequences
+   * 
+   * @return Number of sequences in alignment
+   */
+  int getAbsoluteHeight();
+
+  /**
    * 
    * Calculates the maximum width of the alignment, including gaps.
    * 
@@ -65,6 +74,15 @@ public interface AlignmentI extends AnnotatedCollectionI
   boolean isAligned(boolean includeHidden);
 
   /**
+   * Answers if the sequence at alignmentIndex is hidden
+   * 
+   * @param alignmentIndex
+   *          the index to check
+   * @return true if the sequence is hidden
+   */
+  boolean isHidden(int alignmentIndex);
+
+  /**
    * Gets sequences as a Synchronized collection
    * 
    * @return All sequences in alignment.
@@ -90,6 +108,17 @@ public interface AlignmentI extends AnnotatedCollectionI
   SequenceI getSequenceAt(int i);
 
   /**
+   * Find a specific sequence in this alignment.
+   * 
+   * @param i
+   *          Index of required sequence in full alignment, i.e. if all columns
+   *          were visible
+   * 
+   * @return SequenceI at given index.
+   */
+  SequenceI getSequenceAtAbsoluteIndex(int i);
+
+  /**
    * Returns a map of lists of sequences keyed by sequence name.
    * 
    * @return
@@ -118,7 +147,9 @@ public interface AlignmentI extends AnnotatedCollectionI
   SequenceI replaceSequenceAt(int i, SequenceI seq);
 
   /**
-   * Deletes a sequence from the alignment
+   * Deletes a sequence from the alignment. Updates hidden sequences to account
+   * for the removed sequence. Do NOT use this method to delete sequences which
+   * are just hidden.
    * 
    * @param s
    *          Sequence to be deleted.
@@ -126,7 +157,9 @@ public interface AlignmentI extends AnnotatedCollectionI
   void deleteSequence(SequenceI s);
 
   /**
-   * Deletes a sequence from the alignment.
+   * Deletes a sequence from the alignment. Updates hidden sequences to account
+   * for the removed sequence. Do NOT use this method to delete sequences which
+   * are just hidden.
    * 
    * @param i
    *          Index of sequence to be deleted.
@@ -134,6 +167,14 @@ public interface AlignmentI extends AnnotatedCollectionI
   void deleteSequence(int i);
 
   /**
+   * Deletes a sequence in the alignment which has been hidden.
+   * 
+   * @param i
+   *          Index of sequence to be deleted
+   */
+  void deleteHiddenSequence(int i);
+
+  /**
    * Finds sequence in alignment using sequence name as query.
    * 
    * @param name
@@ -157,10 +198,11 @@ public interface AlignmentI extends AnnotatedCollectionI
 
   /**
    * Returns the first group (in the order in which groups were added) that
-   * includes the given sequence and aligned position (base 0), or null if none
-   * found
+   * includes the given sequence instance and aligned position (base 0), or null
+   * if none found
    * 
    * @param seq
+   *          - must be contained in the alignment (not a dataset sequence)
    * @param position
    * 
    * @return
@@ -316,6 +358,8 @@ public interface AlignmentI extends AnnotatedCollectionI
 
   HiddenSequences getHiddenSequences();
 
+  HiddenColumns getHiddenColumns();
+
   /**
    * Compact representation of alignment
    * 
@@ -556,4 +600,6 @@ public interface AlignmentI extends AnnotatedCollectionI
   ContactListI getContactListFor(AlignmentAnnotation _aa, int column);
 
   AlignmentAnnotation addContactList(ContactMatrixI cm);
+
+  public void setHiddenColumns(HiddenColumns cols);
 }
index 9db9f38..9ca70f2 100644 (file)
@@ -141,13 +141,14 @@ public class AlignmentView
    *          the view
    */
   public AlignmentView(AlignmentI alignment,
-          ColumnSelection columnSelection, SequenceGroup selection,
+ HiddenColumns hidden,
+          SequenceGroup selection,
           boolean hasHiddenColumns, boolean selectedRegionOnly,
           boolean recordGroups)
   {
     // refactored from AlignViewport.getAlignmentView(selectedOnly);
     this(new jalview.datamodel.CigarArray(alignment,
-            (hasHiddenColumns ? columnSelection : null),
+            (hasHiddenColumns ? hidden : null),
             (selectedRegionOnly ? selection : null)),
             (selectedRegionOnly && selection != null) ? selection
                     .getStartRes() : 0);
@@ -330,13 +331,13 @@ public class AlignmentView
    *          char
    * @return Object[] { SequenceI[], ColumnSelection}
    */
-  public Object[] getAlignmentAndColumnSelection(char gapCharacter)
+  public Object[] getAlignmentAndHiddenColumns(char gapCharacter)
   {
-    ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns hidden = new HiddenColumns();
 
     return new Object[] {
-        SeqCigar.createAlignmentSequences(sequences, gapCharacter, colsel,
-                contigs), colsel };
+        SeqCigar.createAlignmentSequences(sequences, gapCharacter, hidden,
+                contigs), hidden };
   }
 
   /**
@@ -710,7 +711,8 @@ public class AlignmentView
     if (contigs != null && contigs.length > 0)
     {
       SequenceI[] alignment = new SequenceI[sequences.length];
-      ColumnSelection columnselection = new ColumnSelection();
+      // ColumnSelection columnselection = new ColumnSelection();
+      HiddenColumns hidden = new HiddenColumns();
       if (contigs != null && contigs.length > 0)
       {
         int start = 0;
@@ -823,7 +825,7 @@ public class AlignmentView
             }
           }
           // mark hidden segment as hidden in the new alignment
-          columnselection.hideColumns(nwidth, nwidth + contigs[contig + 2]
+          hidden.hideColumns(nwidth, nwidth + contigs[contig + 2]
                   - 1);
           nwidth += contigs[contig + 2];
         }
@@ -901,7 +903,7 @@ public class AlignmentView
           }
         }
       }
-      return new Object[] { alignment, columnselection };
+      return new Object[] { alignment, hidden };
     }
     else
     {
@@ -916,11 +918,11 @@ public class AlignmentView
       }
       if (nvismsa[0] != null)
       {
-        return new Object[] { nvismsa[0], new ColumnSelection() };
+        return new Object[] { nvismsa[0], new HiddenColumns() };
       }
       else
       {
-        return getAlignmentAndColumnSelection(gapCharacter);
+        return getAlignmentAndHiddenColumns(gapCharacter);
       }
     }
   }
@@ -970,14 +972,14 @@ public class AlignmentView
       if (start < fwidth)
       {
         viscontigs[nvis] = start;
-        viscontigs[nvis + 1] = fwidth; // end is inclusive
+        viscontigs[nvis + 1] = fwidth - 1; // end is inclusive
         nvis += 2;
       }
       return viscontigs;
     }
     else
     {
-      return new int[] { 0, width };
+      return new int[] { 0, width - 1 };
     }
   }
 
@@ -1135,7 +1137,7 @@ public class AlignmentView
   }
 
   public static void testSelectionViews(AlignmentI alignment,
-          ColumnSelection csel, SequenceGroup selection)
+          HiddenColumns hidden, SequenceGroup selection)
   {
     System.out.println("Testing standard view creation:\n");
     AlignmentView view = null;
@@ -1143,7 +1145,7 @@ public class AlignmentView
     {
       System.out
               .println("View with no hidden columns, no limit to selection, no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, false,
+      view = new AlignmentView(alignment, hidden, selection, false, false,
               false);
       summariseAlignmentView(view, System.out);
 
@@ -1157,7 +1159,7 @@ public class AlignmentView
     {
       System.out
               .println("View with no hidden columns, no limit to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, false,
+      view = new AlignmentView(alignment, hidden, selection, false, false,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1170,7 +1172,7 @@ public class AlignmentView
     {
       System.out
               .println("View with no hidden columns, limited to selection and no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, true,
+      view = new AlignmentView(alignment, hidden, selection, false, true,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1183,7 +1185,7 @@ public class AlignmentView
     {
       System.out
               .println("View with no hidden columns, limited to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, true,
+      view = new AlignmentView(alignment, hidden, selection, false, true,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1196,7 +1198,7 @@ public class AlignmentView
     {
       System.out
               .println("View *with* hidden columns, no limit to selection, no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, false,
+      view = new AlignmentView(alignment, hidden, selection, true, false,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1209,7 +1211,7 @@ public class AlignmentView
     {
       System.out
               .println("View *with* hidden columns, no limit to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, false,
+      view = new AlignmentView(alignment, hidden, selection, true, false,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1222,7 +1224,7 @@ public class AlignmentView
     {
       System.out
               .println("View *with* hidden columns, limited to selection and no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, true,
+      view = new AlignmentView(alignment, hidden, selection, true, true,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
@@ -1235,7 +1237,8 @@ public class AlignmentView
     {
       System.out
               .println("View *with* hidden columns, limited to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, true, true);
+      view = new AlignmentView(alignment, hidden, selection, true, true,
+              true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
diff --git a/src/jalview/datamodel/AllColsCollection.java b/src/jalview/datamodel/AllColsCollection.java
new file mode 100644 (file)
index 0000000..f84ba95
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.datamodel;
+
+import jalview.api.AlignmentColsCollectionI;
+
+import java.util.Iterator;
+
+public class AllColsCollection implements AlignmentColsCollectionI
+{
+  int start;
+  int end;
+
+  HiddenColumns hidden;
+  
+  public AllColsCollection(int s, int e, AlignmentI al)
+  {
+    start = s;
+    end = e;
+    hidden = al.getHiddenColumns();
+  }
+  
+  @Override
+  public Iterator<Integer> iterator()
+  {
+    return new AllColsIterator(start,end,hidden);
+  }
+
+  @Override
+  public boolean isHidden(int c)
+  {
+    return !hidden.isVisible(c);
+  }
+}
diff --git a/src/jalview/datamodel/AllColsIterator.java b/src/jalview/datamodel/AllColsIterator.java
new file mode 100644 (file)
index 0000000..c1296d5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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.datamodel;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator which iterates over all columns or rows in an alignment, whether
+ * hidden or visible.
+ * 
+ * @author kmourao
+ *
+ */
+public class AllColsIterator implements Iterator<Integer>
+{
+  private int last;
+
+  private int next;
+
+  private int current;
+
+  public AllColsIterator(int firstcol, int lastcol, HiddenColumns hiddenCols)
+  {
+    last = lastcol;
+    next = firstcol;
+    current = firstcol;
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return next <= last;
+  }
+
+  @Override
+  public Integer next()
+  {
+    if (next > last)
+    {
+      throw new NoSuchElementException();
+    }
+    current = next;
+    next++;
+
+    return current;
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
+
diff --git a/src/jalview/datamodel/AllRowsCollection.java b/src/jalview/datamodel/AllRowsCollection.java
new file mode 100644 (file)
index 0000000..502ace4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.datamodel;
+
+import jalview.api.AlignmentRowsCollectionI;
+
+import java.util.Iterator;
+
+public class AllRowsCollection implements AlignmentRowsCollectionI
+{
+  int start;
+
+  int end;
+
+  AlignmentI alignment;
+
+  HiddenSequences hidden;
+
+  public AllRowsCollection(int s, int e, AlignmentI al)
+  {
+    start = s;
+    end = e;
+    alignment = al;
+    hidden = al.getHiddenSequences();
+  }
+
+  @Override
+  public Iterator<Integer> iterator()
+  {
+    return new AllRowsIterator(start, end, alignment);
+  }
+
+  @Override
+  public boolean isHidden(int seq)
+  {
+    return hidden.isHidden(seq);
+  }
+
+  @Override
+  public SequenceI getSequence(int seq)
+  {
+    return alignment.getSequenceAtAbsoluteIndex(seq);
+  }
+}
+
diff --git a/src/jalview/datamodel/AllRowsIterator.java b/src/jalview/datamodel/AllRowsIterator.java
new file mode 100644 (file)
index 0000000..b6d45f8
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.datamodel;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator which iterates over all columns or rows in an alignment, whether
+ * hidden or visible.
+ * 
+ * @author kmourao
+ *
+ */
+public class AllRowsIterator implements Iterator<Integer>
+{
+  private int last;
+
+  private int next;
+
+  private int current;
+
+  private AlignmentI al;
+
+  public AllRowsIterator(int firstrow, int lastrow, AlignmentI alignment)
+  {
+    last = lastrow;
+    current = firstrow;
+    next = firstrow;
+    al = alignment;
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return next <= last;
+  }
+
+  @Override
+  public Integer next()
+  {
+    if (next > last)
+    {
+      throw new NoSuchElementException();
+    }
+    current = next;
+    next++;
+
+    return current;
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
+
+
index 3f6c515..2963fd5 100644 (file)
@@ -40,6 +40,17 @@ public interface AnnotatedCollectionI extends SequenceCollectionI
    */
   Iterable<AlignmentAnnotation> findAnnotation(String calcId);
 
+  /**
+   * Returns an iterable collection of any annotations that match on given
+   * sequence ref, calcId and label (ignoring null values).
+   * 
+   * @param seq
+   *          null or reference sequence to select annotation for
+   * @param calcId
+   *          null or the calcId to select annotation for
+   * @param label
+   *          null or the label to select annotation for
+   */
   Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label);
 
index 71ebbb3..8de8eb2 100755 (executable)
@@ -30,6 +30,12 @@ import java.awt.Color;
  */
 public class Annotation
 {
+  /**
+   * the empty annotation - proxy for null entries in annotation row
+   */
+  public static final Annotation EMPTY_ANNOTATION = new Annotation("", "",
+          ' ', 0f);
+
   /** Character label - also shown below histogram */
   public String displayCharacter = "";
 
@@ -192,4 +198,18 @@ public class Annotation
     }
     return sb.toString();
   }
+
+  /**
+   * @return true if annot is 'whitespace' annotation (zero score, whitespace or
+   *         zero length display character, label, description
+   */
+  public boolean isWhitespace()
+  {
+    return ((value == 0f)
+            && ((description == null) || (description.trim()
+                    .length() == 0))
+            && ((displayCharacter == null) || (displayCharacter
+                    .trim().length() == 0))
+            && (secondaryStructure == '\0' || (secondaryStructure == ' ')) && colour == null);
+  }
 }
index ed57dce..b7e15a6 100755 (executable)
@@ -20,8 +20,8 @@
  */
 package jalview.datamodel;
 
+import jalview.analysis.scoremodels.ScoreMatrix;
 import jalview.schemes.ResidueProperties;
-import jalview.schemes.ScoreMatrix;
 
 /**
  * Encode a sequence as a numeric vector using either classic residue binary
@@ -69,13 +69,9 @@ public class BinarySequence extends Sequence
   {
     int nores = (isNa) ? ResidueProperties.maxNucleotideIndex
             : ResidueProperties.maxProteinIndex;
-    // Set all matrix to 0
+
     dbinary = new double[getSequence().length * nores];
 
-    for (int i = 0; i < dbinary.length; i++)
-    {
-      dbinary[i] = 0.0;
-    }
     return nores;
   }
 
@@ -116,32 +112,26 @@ public class BinarySequence extends Sequence
   /**
    * ancode using substitution matrix given in matrix
    * 
-   * @param matrix
+   * @param smtrx
    */
-  public void matrixEncode(final ScoreMatrix matrix)
+  public void matrixEncode(final ScoreMatrix smtrx)
           throws InvalidSequenceTypeException
   {
-    if (isNa != matrix.isDNA())
+    if (isNa != smtrx.isDNA())
     {
       throw new InvalidSequenceTypeException("matrix "
-              + matrix.getClass().getCanonicalName()
+              + smtrx.getClass().getCanonicalName()
               + " is not a valid matrix for "
               + (isNa ? "nucleotide" : "protein") + "sequences");
     }
-    matrixEncode(matrix.isDNA() ? ResidueProperties.nucleotideIndex
-            : ResidueProperties.aaIndex, matrix.getMatrix());
+    matrixEncode(smtrx.isDNA() ? ResidueProperties.nucleotideIndex
+            : ResidueProperties.aaIndex, smtrx.getMatrix());
   }
 
-  private void matrixEncode(final int[] aaIndex, final int[][] matrix)
+  private void matrixEncode(final int[] aaIndex, final float[][] matrix)
   {
-    // Set all matrix to 0
-    // dbinary = new double[getSequence().length * 21];
-
     int nores = initMatrixGetNoRes();
 
-    // for (int i = 0; i < dbinary.length; i++) {
-    // dbinary[i] = 0.0;
-    // }
     for (int i = 0, iSize = getSequence().length; i < iSize; i++)
     {
       int aanum = nores - 1;
index f6e5862..837a10b 100644 (file)
@@ -85,12 +85,12 @@ public class CigarArray extends CigarBase
    * @param columnSelection
    * @param selectionGroup
    */
-  public CigarArray(AlignmentI alignment, ColumnSelection columnSelection,
+  public CigarArray(AlignmentI alignment, HiddenColumns hidden,
           SequenceGroup selectionGroup)
   {
     this(constructSeqCigarArray(alignment, selectionGroup));
     constructFromAlignment(alignment,
-            columnSelection != null ? columnSelection.getHiddenColumns()
+            hidden != null ? hidden.getHiddenRegions()
                     : null, selectionGroup);
   }
 
@@ -218,7 +218,7 @@ public class CigarArray extends CigarBase
   }
 
   /**
-   * @see Cigar.getSequenceAndDeletions
+   * @see CigarBase.getSequenceAndDeletions
    * @param GapChar
    *          char
    * @return Object[][]
index 98a7fe2..eb2d174 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.datamodel;
 
-import jalview.util.Comparison;
-import jalview.util.ShiftList;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
 
@@ -29,7 +27,6 @@ import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
-import java.util.Vector;
 
 /**
  * Data class holding the selected columns and hidden column ranges for a view.
@@ -268,12 +265,6 @@ public class ColumnSelection
 
   IntList selection = new IntList();
 
-  /*
-   * list of hidden column [start, end] ranges; the list is maintained in
-   * ascending start column order
-   */
-  Vector<int[]> hiddenColumns;
-
   /**
    * Add a column to the selection
    * 
@@ -397,502 +388,19 @@ public class ColumnSelection
     return selection.getMinColumn();
   }
 
-  /**
-   * propagate shift in alignment columns to column selection
-   * 
-   * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
-   */
-  public List<int[]> compensateForEdit(int start, int change)
-  {
-    List<int[]> deletedHiddenColumns = null;
-    selection.compensateForEdits(start, change);
-
-    if (hiddenColumns != null)
-    {
-      deletedHiddenColumns = new ArrayList<int[]>();
-      int hSize = hiddenColumns.size();
-      for (int i = 0; i < hSize; i++)
-      {
-        int[] region = hiddenColumns.elementAt(i);
-        if (region[0] > start && start + change > region[1])
-        {
-          deletedHiddenColumns.add(region);
-
-          hiddenColumns.removeElementAt(i);
-          i--;
-          hSize--;
-          continue;
-        }
-
-        if (region[0] > start)
-        {
-          region[0] -= change;
-          region[1] -= change;
-        }
-
-        if (region[0] < 0)
-        {
-          region[0] = 0;
-        }
-
-      }
-
-      this.revealHiddenColumns(0);
-    }
-
-    return deletedHiddenColumns;
-  }
-
-  /**
-   * propagate shift in alignment columns to column selection special version of
-   * compensateForEdit - allowing for edits within hidden regions
-   * 
-   * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
-   */
-  private void compensateForDelEdits(int start, int change)
-  {
-
-    selection.compensateForEdits(start, change);
-
-    if (hiddenColumns != null)
-    {
-      for (int i = 0; i < hiddenColumns.size(); i++)
-      {
-        int[] region = hiddenColumns.elementAt(i);
-        if (region[0] >= start)
-        {
-          region[0] -= change;
-        }
-        if (region[1] >= start)
-        {
-          region[1] -= change;
-        }
-        if (region[1] < region[0])
-        {
-          hiddenColumns.removeElementAt(i--);
-        }
-
-        if (region[0] < 0)
-        {
-          region[0] = 0;
-        }
-        if (region[1] < 0)
-        {
-          region[1] = 0;
-        }
-      }
-    }
-  }
-
-  /**
-   * Adjust hidden column boundaries based on a series of column additions or
-   * deletions in visible regions.
-   * 
-   * @param shiftrecord
-   * @return
-   */
-  public ShiftList compensateForEdits(ShiftList shiftrecord)
-  {
-    if (shiftrecord != null)
-    {
-      final List<int[]> shifts = shiftrecord.getShifts();
-      if (shifts != null && shifts.size() > 0)
-      {
-        int shifted = 0;
-        for (int i = 0, j = shifts.size(); i < j; i++)
-        {
-          int[] sh = shifts.get(i);
-          // compensateForEdit(shifted+sh[0], sh[1]);
-          compensateForDelEdits(shifted + sh[0], sh[1]);
-          shifted -= sh[1];
-        }
-      }
-      return shiftrecord.getInverse();
-    }
-    return null;
-  }
-
-  /**
-   * removes intersection of position,length ranges in deletions from the
-   * start,end regions marked in intervals.
-   * 
-   * @param shifts
-   * @param intervals
-   * @return
-   */
-  private boolean pruneIntervalVector(final List<int[]> shifts,
-          Vector<int[]> intervals)
-  {
-    boolean pruned = false;
-    int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
-    int hr[] = intervals.elementAt(i);
-    int sr[] = shifts.get(s);
-    while (i <= j && s <= t)
-    {
-      boolean trailinghn = hr[1] >= sr[0];
-      if (!trailinghn)
-      {
-        if (i < j)
-        {
-          hr = intervals.elementAt(++i);
-        }
-        else
-        {
-          i++;
-        }
-        continue;
-      }
-      int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
-      if (endshift < hr[0] || endshift < sr[0])
-      { // leadinghc disjoint or not a deletion
-        if (s < t)
-        {
-          sr = shifts.get(++s);
-        }
-        else
-        {
-          s++;
-        }
-        continue;
-      }
-      boolean leadinghn = hr[0] >= sr[0];
-      boolean leadinghc = hr[0] < endshift;
-      boolean trailinghc = hr[1] < endshift;
-      if (leadinghn)
-      {
-        if (trailinghc)
-        { // deleted hidden region.
-          intervals.removeElementAt(i);
-          pruned = true;
-          j--;
-          if (i <= j)
-          {
-            hr = intervals.elementAt(i);
-          }
-          continue;
-        }
-        if (leadinghc)
-        {
-          hr[0] = endshift; // clip c terminal region
-          leadinghn = !leadinghn;
-          pruned = true;
-        }
-      }
-      if (!leadinghn)
-      {
-        if (trailinghc)
-        {
-          if (trailinghn)
-          {
-            hr[1] = sr[0] - 1;
-            pruned = true;
-          }
-        }
-        else
-        {
-          // sr contained in hr
-          if (s < t)
-          {
-            sr = shifts.get(++s);
-          }
-          else
-          {
-            s++;
-          }
-          continue;
-        }
-      }
-    }
-    return pruned; // true if any interval was removed or modified by
-    // operations.
-  }
-
-  /**
-   * remove any hiddenColumns or selected columns and shift remaining based on a
-   * series of position, range deletions.
-   * 
-   * @param deletions
-   */
-  public void pruneDeletions(ShiftList deletions)
-  {
-    if (deletions != null)
-    {
-      final List<int[]> shifts = deletions.getShifts();
-      if (shifts != null && shifts.size() > 0)
-      {
-        // delete any intervals intersecting.
-        if (hiddenColumns != null)
-        {
-          pruneIntervalVector(shifts, hiddenColumns);
-          if (hiddenColumns != null && hiddenColumns.size() == 0)
-          {
-            hiddenColumns = null;
-          }
-        }
-        if (selection != null && selection.size() > 0)
-        {
-          selection.pruneColumnList(shifts);
-          if (selection != null && selection.size() == 0)
-          {
-            selection = null;
-          }
-        }
-        // and shift the rest.
-        this.compensateForEdits(deletions);
-      }
-    }
-  }
-
-  /**
-   * This Method is used to return all the HiddenColumn regions
-   * 
-   * @return empty list or List of hidden column intervals
-   */
-  public List<int[]> getHiddenColumns()
-  {
-    return hiddenColumns == null ? Collections.<int[]> emptyList()
-            : hiddenColumns;
-  }
-
-  /**
-   * Return absolute column index for a visible column index
-   * 
-   * @param column
-   *          int column index in alignment view (count from zero)
-   * @return alignment column index for column
-   */
-  public int adjustForHiddenColumns(int column)
-  {
-    int result = column;
-    if (hiddenColumns != null)
-    {
-      for (int i = 0; i < hiddenColumns.size(); i++)
-      {
-        int[] region = hiddenColumns.elementAt(i);
-        if (result >= region[0])
-        {
-          result += region[1] - region[0] + 1;
-        }
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Use this method to find out where a column will appear in the visible
-   * alignment when hidden columns exist. If the column is not visible, then the
-   * left-most visible column will always be returned.
-   * 
-   * @param hiddenColumn
-   *          int
-   * @return int
-   */
-  public int findColumnPosition(int hiddenColumn)
-  {
-    int result = hiddenColumn;
-    if (hiddenColumns != null)
-    {
-      int index = 0;
-      int[] region;
-      do
-      {
-        region = hiddenColumns.elementAt(index++);
-        if (hiddenColumn > region[1])
-        {
-          result -= region[1] + 1 - region[0];
-        }
-      } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
-      if (hiddenColumn > region[0] && hiddenColumn < region[1])
-      {
-        return region[0] + hiddenColumn - result;
-      }
-    }
-    return result; // return the shifted position after removing hidden columns.
-  }
-
-  /**
-   * Use this method to determine where the next hiddenRegion starts
-   * 
-   * @param hiddenRegion
-   *          index of hidden region (counts from 0)
-   * @return column number in visible view
-   */
-  public int findHiddenRegionPosition(int hiddenRegion)
-  {
-    int result = 0;
-    if (hiddenColumns != null)
-    {
-      int index = 0;
-      int gaps = 0;
-      do
-      {
-        int[] region = hiddenColumns.elementAt(index);
-        if (hiddenRegion == 0)
-        {
-          return region[0];
-        }
-
-        gaps += region[1] + 1 - region[0];
-        result = region[1] + 1;
-        index++;
-      } while (index <= hiddenRegion);
-
-      result -= gaps;
-    }
-
-    return result;
-  }
-
-  /**
-   * THis method returns the rightmost limit of a region of an alignment with
-   * hidden columns. In otherwords, the next hidden column.
-   * 
-   * @param index
-   *          int
-   */
-  public int getHiddenBoundaryRight(int alPos)
-  {
-    if (hiddenColumns != null)
-    {
-      int index = 0;
-      do
-      {
-        int[] region = hiddenColumns.elementAt(index);
-        if (alPos < region[0])
-        {
-          return region[0];
-        }
-
-        index++;
-      } while (index < hiddenColumns.size());
-    }
-
-    return alPos;
-
-  }
-
-  /**
-   * This method returns the leftmost limit of a region of an alignment with
-   * hidden columns. In otherwords, the previous hidden column.
-   * 
-   * @param index
-   *          int
-   */
-  public int getHiddenBoundaryLeft(int alPos)
-  {
-    if (hiddenColumns != null)
-    {
-      int index = hiddenColumns.size() - 1;
-      do
-      {
-        int[] region = hiddenColumns.elementAt(index);
-        if (alPos > region[1])
-        {
-          return region[1];
-        }
-
-        index--;
-      } while (index > -1);
-    }
-
-    return alPos;
-
-  }
-
-  public void hideSelectedColumns()
+  public void hideSelectedColumns(AlignmentI al)
   {
     synchronized (selection)
     {
       for (int[] selregions : selection.getRanges())
       {
-        hideColumns(selregions[0], selregions[1]);
+        al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
       }
       selection.clear();
     }
 
   }
 
-  /**
-   * Adds the specified column range to the hidden columns
-   * 
-   * @param start
-   * @param end
-   */
-  public void hideColumns(int start, int end)
-  {
-    if (hiddenColumns == null)
-    {
-      hiddenColumns = new Vector<int[]>();
-    }
-
-    /*
-     * traverse existing hidden ranges and insert / amend / append as
-     * appropriate
-     */
-    for (int i = 0; i < hiddenColumns.size(); i++)
-    {
-      int[] region = hiddenColumns.elementAt(i);
-
-      if (end < region[0] - 1)
-      {
-        /*
-         * insert discontiguous preceding range
-         */
-        hiddenColumns.insertElementAt(new int[] { start, end }, i);
-        return;
-      }
-
-      if (end <= region[1])
-      {
-        /*
-         * new range overlaps existing, or is contiguous preceding it - adjust
-         * start column
-         */
-        region[0] = Math.min(region[0], start);
-        return;
-      }
-
-      if (start <= region[1] + 1)
-      {
-        /*
-         * new range overlaps existing, or is contiguous following it - adjust
-         * start and end columns
-         */
-        region[0] = Math.min(region[0], start);
-        region[1] = Math.max(region[1], end);
-
-        /*
-         * also update or remove any subsequent ranges 
-         * that are overlapped
-         */
-        while (i < hiddenColumns.size() - 1)
-        {
-          int[] nextRegion = hiddenColumns.get(i + 1);
-          if (nextRegion[0] > end + 1)
-          {
-            /*
-             * gap to next hidden range - no more to update
-             */
-            break;
-          }
-          region[1] = Math.max(nextRegion[1], end);
-          hiddenColumns.remove(i + 1);
-        }
-        return;
-      }
-    }
-
-    /*
-     * remaining case is that the new range follows everything else
-     */
-    hiddenColumns.addElement(new int[] { start, end });
-  }
 
   /**
    * Hides the specified column and any adjacent selected columns
@@ -900,7 +408,7 @@ public class ColumnSelection
    * @param res
    *          int
    */
-  public void hideColumns(int col)
+  public void hideSelectedColumns(int col, HiddenColumns hidden)
   {
     /*
      * deselect column (whether selected or not!)
@@ -933,72 +441,12 @@ public class ColumnSelection
       min = max;
     }
 
-    hideColumns(min, max);
+    hidden.hideColumns(min, max);
   }
 
-  /**
-   * Unhides, and adds to the selection list, all hidden columns
-   */
-  public void revealAllHiddenColumns()
-  {
-    if (hiddenColumns != null)
-    {
-      for (int i = 0; i < hiddenColumns.size(); i++)
-      {
-        int[] region = hiddenColumns.elementAt(i);
-        for (int j = region[0]; j < region[1] + 1; j++)
-        {
-          addElement(j);
-        }
-      }
-    }
 
-    hiddenColumns = null;
-  }
 
-  /**
-   * Reveals, and marks as selected, the hidden column range with the given
-   * start column
-   * 
-   * @param start
-   */
-  public void revealHiddenColumns(int start)
-  {
-    for (int i = 0; i < hiddenColumns.size(); i++)
-    {
-      int[] region = hiddenColumns.elementAt(i);
-      if (start == region[0])
-      {
-        for (int j = region[0]; j < region[1] + 1; j++)
-        {
-          addElement(j);
-        }
 
-        hiddenColumns.removeElement(region);
-        break;
-      }
-    }
-    if (hiddenColumns.size() == 0)
-    {
-      hiddenColumns = null;
-    }
-  }
-
-  public boolean isVisible(int column)
-  {
-    if (hiddenColumns != null)
-    {
-      for (int[] region : hiddenColumns)
-      {
-        if (column >= region[0] && column <= region[1])
-        {
-          return false;
-        }
-      }
-    }
-
-    return true;
-  }
 
   /**
    * Copy constructor
@@ -1010,21 +458,6 @@ public class ColumnSelection
     if (copy != null)
     {
       selection = new IntList(copy.selection);
-      if (copy.hiddenColumns != null)
-      {
-        hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
-        for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
-        {
-          int[] rh, cp;
-          rh = copy.hiddenColumns.elementAt(i);
-          if (rh != null)
-          {
-            cp = new int[rh.length];
-            System.arraycopy(rh, 0, cp, 0, rh.length);
-            hiddenColumns.addElement(cp);
-          }
-        }
-      }
     }
   }
 
@@ -1035,312 +468,10 @@ public class ColumnSelection
   {
   }
 
-  public String[] getVisibleSequenceStrings(int start, int end,
-          SequenceI[] seqs)
-  {
-    int i, iSize = seqs.length;
-    String selections[] = new String[iSize];
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
-    {
-      for (i = 0; i < iSize; i++)
-      {
-        StringBuffer visibleSeq = new StringBuffer();
-        List<int[]> regions = getHiddenColumns();
-
-        int blockStart = start, blockEnd = end;
-        int[] region;
-        int hideStart, hideEnd;
-
-        for (int j = 0; j < regions.size(); j++)
-        {
-          region = regions.get(j);
-          hideStart = region[0];
-          hideEnd = region[1];
-
-          if (hideStart < start)
-          {
-            continue;
-          }
-
-          blockStart = Math.min(blockStart, hideEnd + 1);
-          blockEnd = Math.min(blockEnd, hideStart);
-
-          if (blockStart > blockEnd)
-          {
-            break;
-          }
-
-          visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
-
-          blockStart = hideEnd + 1;
-          blockEnd = end;
-        }
-
-        if (end > blockStart)
-        {
-          visibleSeq.append(seqs[i].getSequence(blockStart, end));
-        }
-
-        selections[i] = visibleSeq.toString();
-      }
-    }
-    else
-    {
-      for (i = 0; i < iSize; i++)
-      {
-        selections[i] = seqs[i].getSequenceAsString(start, end);
-      }
-    }
-
-    return selections;
-  }
-
-  /**
-   * return all visible segments between the given start and end boundaries
-   * 
-   * @param start
-   *          (first column inclusive from 0)
-   * @param end
-   *          (last column - not inclusive)
-   * @return int[] {i_start, i_end, ..} where intervals lie in
-   *         start<=i_start<=i_end<end
-   */
-  public int[] getVisibleContigs(int start, int end)
-  {
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
-    {
-      List<int[]> visiblecontigs = new ArrayList<int[]>();
-      List<int[]> regions = getHiddenColumns();
-
-      int vstart = start;
-      int[] region;
-      int hideStart, hideEnd;
-
-      for (int j = 0; vstart < end && j < regions.size(); j++)
-      {
-        region = regions.get(j);
-        hideStart = region[0];
-        hideEnd = region[1];
-
-        if (hideEnd < vstart)
-        {
-          continue;
-        }
-        if (hideStart > vstart)
-        {
-          visiblecontigs.add(new int[] { vstart, hideStart - 1 });
-        }
-        vstart = hideEnd + 1;
-      }
-
-      if (vstart < end)
-      {
-        visiblecontigs.add(new int[] { vstart, end - 1 });
-      }
-      int[] vcontigs = new int[visiblecontigs.size() * 2];
-      for (int i = 0, j = visiblecontigs.size(); i < j; i++)
-      {
-        int[] vc = visiblecontigs.get(i);
-        visiblecontigs.set(i, null);
-        vcontigs[i * 2] = vc[0];
-        vcontigs[i * 2 + 1] = vc[1];
-      }
-      visiblecontigs.clear();
-      return vcontigs;
-    }
-    else
-    {
-      return new int[] { start, end - 1 };
-    }
-  }
-
-  /**
-   * Locate the first and last position visible for this sequence. if seq isn't
-   * visible then return the position of the left and right of the hidden
-   * boundary region, and the corresponding alignment column indices for the
-   * extent of the sequence
-   * 
-   * @param seq
-   * @return int[] { visible start, visible end, first seqpos, last seqpos,
-   *         alignment index for seq start, alignment index for seq end }
-   */
-  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
-  {
-    int fpos = seq.getStart(), lpos = seq.getEnd();
-    int start = 0;
-
-    if (hiddenColumns == null || hiddenColumns.size() == 0)
-    {
-      int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
-      return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
-    }
-
-    // Simply walk along the sequence whilst watching for hidden column
-    // boundaries
-    List<int[]> regions = getHiddenColumns();
-    int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
-            .getLength(), hideEnd = -1;
-    int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
-    boolean foundStart = false;
-    for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
-            && p < pLen; p++)
-    {
-      if (!Comparison.isGap(seq.getCharAt(p)))
-      {
-        // keep track of first/last column
-        // containing sequence data regardless of visibility
-        if (firstP == -1)
-        {
-          firstP = p;
-        }
-        lastP = p;
-        // update hidden region start/end
-        while (hideEnd < p && rcount < regions.size())
-        {
-          int[] region = regions.get(rcount++);
-          visPrev = visNext;
-          visNext += region[0] - visPrev;
-          hideStart = region[0];
-          hideEnd = region[1];
-        }
-        if (hideEnd < p)
-        {
-          hideStart = seq.getLength();
-        }
-        // update visible boundary for sequence
-        if (p < hideStart)
-        {
-          if (!foundStart)
-          {
-            fpos = spos;
-            start = p;
-            foundStart = true;
-          }
-          lastvispos = p;
-          lpos = spos;
-        }
-        // look for next sequence position
-        spos++;
-      }
-    }
-    if (foundStart)
-    {
-      return new int[] { findColumnPosition(start),
-          findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
-    }
-    // otherwise, sequence was completely hidden
-    return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
-  }
-
-  /**
-   * delete any columns in alignmentAnnotation that are hidden (including
-   * sequence associated annotation).
-   * 
-   * @param alignmentAnnotation
-   */
-  public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
-  {
-    makeVisibleAnnotation(-1, -1, alignmentAnnotation);
-  }
-
-  /**
-   * delete any columns in alignmentAnnotation that are hidden (including
-   * sequence associated annotation).
-   * 
-   * @param start
-   *          remove any annotation to the right of this column
-   * @param end
-   *          remove any annotation to the left of this column
-   * @param alignmentAnnotation
-   *          the annotation to operate on
-   */
-  public void makeVisibleAnnotation(int start, int end,
-          AlignmentAnnotation alignmentAnnotation)
-  {
-    if (alignmentAnnotation.annotations == null)
-    {
-      return;
-    }
-    if (start == end && end == -1)
-    {
-      start = 0;
-      end = alignmentAnnotation.annotations.length;
-    }
-    if (hiddenColumns != null && hiddenColumns.size() > 0)
-    {
-      // then mangle the alignmentAnnotation annotation array
-      Vector<Annotation[]> annels = new Vector<Annotation[]>();
-      Annotation[] els = null;
-      List<int[]> regions = getHiddenColumns();
-      int blockStart = start, blockEnd = end;
-      int[] region;
-      int hideStart, hideEnd, w = 0;
-
-      for (int j = 0; j < regions.size(); j++)
-      {
-        region = regions.get(j);
-        hideStart = region[0];
-        hideEnd = region[1];
-
-        if (hideStart < start)
-        {
-          continue;
-        }
 
-        blockStart = Math.min(blockStart, hideEnd + 1);
-        blockEnd = Math.min(blockEnd, hideStart);
 
-        if (blockStart > blockEnd)
-        {
-          break;
-        }
 
-        annels.addElement(els = new Annotation[blockEnd - blockStart]);
-        System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
-                0, els.length);
-        w += els.length;
-        blockStart = hideEnd + 1;
-        blockEnd = end;
-      }
 
-      if (end > blockStart)
-      {
-        annels.addElement(els = new Annotation[end - blockStart + 1]);
-        if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
-        {
-          // copy just the visible segment of the annotation row
-          System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                  els, 0, els.length);
-        }
-        else
-        {
-          // copy to the end of the annotation row
-          System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                  els, 0,
-                  (alignmentAnnotation.annotations.length - blockStart));
-        }
-        w += els.length;
-      }
-      if (w == 0)
-      {
-        return;
-      }
-
-      alignmentAnnotation.annotations = new Annotation[w];
-      w = 0;
-
-      for (Annotation[] chnk : annels)
-      {
-        System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
-                chnk.length);
-        w += chnk.length;
-      }
-    }
-    else
-    {
-      alignmentAnnotation.restrict(start, end);
-    }
-  }
 
   /**
    * Invert the column selection from first to end-1. leaves hiddenColumns
@@ -1349,9 +480,9 @@ public class ColumnSelection
    * @param first
    * @param end
    */
-  public void invertColumnSelection(int first, int width)
+  public void invertColumnSelection(int first, int width, AlignmentI al)
   {
-    boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
+    boolean hasHidden = al.getHiddenColumns().hasHidden();
     for (int i = first; i < width; i++)
     {
       if (contains(i))
@@ -1360,7 +491,7 @@ public class ColumnSelection
       }
       else
       {
-        if (!hasHidden || isVisible(i))
+        if (!hasHidden || al.getHiddenColumns().isVisible(i))
         {
           addElement(i);
         }
@@ -1369,195 +500,41 @@ public class ColumnSelection
   }
 
   /**
-   * add in any unselected columns from the given column selection, excluding
-   * any that are hidden.
+   * set the selected columns to the given column selection, excluding any
+   * columns that are hidden.
    * 
    * @param colsel
    */
-  public void addElementsFrom(ColumnSelection colsel)
-  {
-    if (colsel != null && !colsel.isEmpty())
-    {
-      for (Integer col : colsel.getSelected())
-      {
-        if (hiddenColumns != null && isVisible(col.intValue()))
-        {
-          selection.add(col);
-        }
-      }
-    }
-  }
-
-  /**
-   * set the selected columns the given column selection, excluding any columns
-   * that are hidden.
-   * 
-   * @param colsel
-   */
-  public void setElementsFrom(ColumnSelection colsel)
+  public void setElementsFrom(ColumnSelection colsel,
+          HiddenColumns hiddenColumns)
   {
     selection = new IntList();
     if (colsel.selection != null && colsel.selection.size() > 0)
     {
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
+      if (hiddenColumns.hasHidden())
       {
         // only select visible columns in this columns selection
-        addElementsFrom(colsel);
-      }
-      else
-      {
-        // add everything regardless
         for (Integer col : colsel.getSelected())
         {
-          addElement(col);
-        }
-      }
-    }
-  }
-
-  /**
-   * Add gaps into the sequences aligned to profileseq under the given
-   * AlignmentView
-   * 
-   * @param profileseq
-   * @param al
-   *          - alignment to have gaps inserted into it
-   * @param input
-   *          - alignment view where sequence corresponding to profileseq is
-   *          first entry
-   * @return new Column selection for new alignment view, with insertions into
-   *         profileseq marked as hidden.
-   */
-  public static ColumnSelection propagateInsertions(SequenceI profileseq,
-          AlignmentI al, AlignmentView input)
-  {
-    int profsqpos = 0;
-
-    // return propagateInsertions(profileseq, al, )
-    char gc = al.getGapCharacter();
-    Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
-    ColumnSelection nview = (ColumnSelection) alandcolsel[1];
-    SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
-    nview.propagateInsertions(profileseq, al, origseq);
-    return nview;
-  }
-
-  /**
-   * 
-   * @param profileseq
-   *          - sequence in al which corresponds to origseq
-   * @param al
-   *          - alignment which is to have gaps inserted into it
-   * @param origseq
-   *          - sequence corresponding to profileseq which defines gap map for
-   *          modifying al
-   */
-  public void propagateInsertions(SequenceI profileseq, AlignmentI al,
-          SequenceI origseq)
-  {
-    char gc = al.getGapCharacter();
-    // recover mapping between sequence's non-gap positions and positions
-    // mapping to view.
-    pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
-    int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
-    int spos = 0;
-    int offset = 0;
-    // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
-    // alandcolsel[0])[0].gapMap()))
-    // add profile to visible contigs
-    for (int v = 0; v < viscontigs.length; v += 2)
-    {
-      if (viscontigs[v] > spos)
-      {
-        StringBuffer sb = new StringBuffer();
-        for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
-        {
-          sb.append(gc);
-        }
-        for (int s = 0, ns = al.getHeight(); s < ns; s++)
-        {
-          SequenceI sqobj = al.getSequenceAt(s);
-          if (sqobj != profileseq)
+          if (hiddenColumns != null
+                  && hiddenColumns.isVisible(col.intValue()))
           {
-            String sq = al.getSequenceAt(s).getSequenceAsString();
-            if (sq.length() <= spos + offset)
-            {
-              // pad sequence
-              int diff = spos + offset - sq.length() - 1;
-              if (diff > 0)
-              {
-                // pad gaps
-                sq = sq + sb;
-                while ((diff = spos + offset - sq.length() - 1) > 0)
-                {
-                  // sq = sq
-                  // + ((diff >= sb.length()) ? sb.toString() : sb
-                  // .substring(0, diff));
-                  if (diff >= sb.length())
-                  {
-                    sq += sb.toString();
-                  }
-                  else
-                  {
-                    char[] buf = new char[diff];
-                    sb.getChars(0, diff, buf, 0);
-                    sq += buf.toString();
-                  }
-                }
-              }
-              sq += sb.toString();
-            }
-            else
-            {
-              al.getSequenceAt(s).setSequence(
-                      sq.substring(0, spos + offset) + sb.toString()
-                              + sq.substring(spos + offset));
-            }
+            selection.add(col);
           }
         }
-        // offset+=sb.length();
-      }
-      spos = viscontigs[v + 1] + 1;
-    }
-    if ((offset + spos) < profileseq.getLength())
-    {
-      // pad the final region with gaps.
-      StringBuffer sb = new StringBuffer();
-      for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
-      {
-        sb.append(gc);
       }
-      for (int s = 0, ns = al.getHeight(); s < ns; s++)
+      else
       {
-        SequenceI sqobj = al.getSequenceAt(s);
-        if (sqobj == profileseq)
-        {
-          continue;
-        }
-        String sq = sqobj.getSequenceAsString();
-        // pad sequence
-        int diff = origseq.getLength() - sq.length();
-        while (diff > 0)
+        // add everything regardless
+        for (Integer col : colsel.getSelected())
         {
-          // sq = sq
-          // + ((diff >= sb.length()) ? sb.toString() : sb
-          // .substring(0, diff));
-          if (diff >= sb.length())
-          {
-            sq += sb.toString();
-          }
-          else
-          {
-            char[] buf = new char[diff];
-            sb.getChars(0, diff, buf, 0);
-            sq += buf.toString();
-          }
-          diff = origseq.getLength() - sq.length();
+          addElement(col);
         }
       }
     }
   }
 
+
   /**
    * 
    * @return true if there are columns marked
@@ -1567,45 +544,13 @@ public class ColumnSelection
     return (selection != null && selection.size() > 0);
   }
 
-  /**
-   * 
-   * @return true if there are columns hidden
-   */
-  public boolean hasHiddenColumns()
-  {
-    return hiddenColumns != null && hiddenColumns.size() > 0;
-  }
 
-  /**
-   * 
-   * @return true if there are more than one set of columns hidden
-   */
-  public boolean hasManyHiddenColumns()
-  {
-    return hiddenColumns != null && hiddenColumns.size() > 1;
-  }
-
-  /**
-   * mark the columns corresponding to gap characters as hidden in the column
-   * selection
-   * 
-   * @param sr
-   */
-  public void hideInsertionsFor(SequenceI sr)
-  {
-    List<int[]> inserts = sr.getInsertions();
-    for (int[] r : inserts)
-    {
-      hideColumns(r[0], r[1]);
-    }
-  }
 
   public boolean filterAnnotations(Annotation[] annotations,
           AnnotationFilterParameter filterParams)
   {
     // JBPNote - this method needs to be refactored to become independent of
     // viewmodel package
-    this.revealAllHiddenColumns();
     this.clear();
     int count = 0;
     do
@@ -1688,21 +633,12 @@ public class ColumnSelection
   }
 
   /**
-   * Returns a hashCode built from selected columns and hidden column ranges
+   * Returns a hashCode built from selected columns ranges
    */
   @Override
   public int hashCode()
   {
-    int hashCode = selection.hashCode();
-    if (hiddenColumns != null)
-    {
-      for (int[] hidden : hiddenColumns)
-      {
-        hashCode = 31 * hashCode + hidden[0];
-        hashCode = 31 * hashCode + hidden[1];
-      }
-    }
-    return hashCode;
+    return selection.hashCode();
   }
 
   /**
@@ -1733,27 +669,6 @@ public class ColumnSelection
       return false;
     }
 
-    /*
-     * check hidden columns are either both null, or match
-     */
-    if (this.hiddenColumns == null)
-    {
-      return (that.hiddenColumns == null);
-    }
-    if (that.hiddenColumns == null
-            || that.hiddenColumns.size() != this.hiddenColumns.size())
-    {
-      return false;
-    }
-    int i = 0;
-    for (int[] thisRange : hiddenColumns)
-    {
-      int[] thatRange = that.hiddenColumns.get(i++);
-      if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
-      {
-        return false;
-      }
-    }
     return true;
   }
 
diff --git a/src/jalview/datamodel/HiddenColumns.java b/src/jalview/datamodel/HiddenColumns.java
new file mode 100644 (file)
index 0000000..f0d99e5
--- /dev/null
@@ -0,0 +1,1299 @@
+package jalview.datamodel;
+
+import jalview.util.Comparison;
+import jalview.util.ShiftList;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.List;
+import java.util.Vector;
+
+public class HiddenColumns
+{
+  /*
+   * list of hidden column [start, end] ranges; the list is maintained in
+   * ascending start column order
+   */
+  private Vector<int[]> hiddenColumns;
+
+  /**
+   * This Method is used to return all the HiddenColumn regions
+   * 
+   * @return empty list or List of hidden column intervals
+   */
+  public List<int[]> getHiddenRegions()
+  {
+    return hiddenColumns == null ? Collections.<int[]> emptyList()
+            : hiddenColumns;
+  }
+
+  /**
+   * Find the number of hidden columns
+   * 
+   * @return number of hidden columns
+   */
+  public int getSize()
+  {
+    int size = 0;
+    if (hasHidden())
+    {
+      for (int[] range : hiddenColumns)
+      {
+        size += range[1] - range[0] + 1;
+      }
+    }
+    return size;
+  }
+
+  /**
+   * Answers if there are any hidden columns
+   * 
+   * @return true if there are hidden columns
+   */
+  public boolean hasHidden()
+  {
+    return (hiddenColumns != null) && (!hiddenColumns.isEmpty());
+  }
+
+  @Override
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof HiddenColumns))
+    {
+      return false;
+    }
+    HiddenColumns that = (HiddenColumns) obj;
+
+    /*
+     * check hidden columns are either both null, or match
+     */
+    if (this.hiddenColumns == null)
+    {
+      return (that.hiddenColumns == null);
+    }
+    if (that.hiddenColumns == null
+            || that.hiddenColumns.size() != this.hiddenColumns.size())
+    {
+      return false;
+    }
+    int i = 0;
+    for (int[] thisRange : hiddenColumns)
+    {
+      int[] thatRange = that.hiddenColumns.get(i++);
+      if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Return absolute column index for a visible column index
+   * 
+   * @param column
+   *          int column index in alignment view (count from zero)
+   * @return alignment column index for column
+   */
+  public int adjustForHiddenColumns(int column)
+  {
+    int result = column;
+    if (hiddenColumns != null)
+    {
+      for (int i = 0; i < hiddenColumns.size(); i++)
+      {
+        int[] region = hiddenColumns.elementAt(i);
+        if (result >= region[0])
+        {
+          result += region[1] - region[0] + 1;
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Use this method to find out where a column will appear in the visible
+   * alignment when hidden columns exist. If the column is not visible, then the
+   * left-most visible column will always be returned.
+   * 
+   * @param hiddenColumn
+   *          the column index in the full alignment including hidden columns
+   * @return the position of the column in the visible alignment
+   */
+  public int findColumnPosition(int hiddenColumn)
+  {
+    int result = hiddenColumn;
+    if (hiddenColumns != null)
+    {
+      int index = 0;
+      int[] region;
+      do
+      {
+        region = hiddenColumns.elementAt(index++);
+        if (hiddenColumn > region[1])
+        {
+          result -= region[1] + 1 - region[0];
+        }
+      } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
+
+      if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
+      {
+        // Here the hidden column is within a region, so
+        // we want to return the position of region[0]-1, adjusted for any
+        // earlier hidden columns.
+        // Calculate the difference between the actual hidden col position
+        // and region[0]-1, and then subtract from result to convert result from
+        // the adjusted hiddenColumn value to the adjusted region[0]-1 value
+
+        // However, if the region begins at 0 we cannot return region[0]-1
+        // just return 0
+        if (region[0] == 0)
+        {
+          return 0;
+        }
+        else
+        {
+          return result - (hiddenColumn - region[0] + 1);
+        }
+      }
+    }
+    return result; // return the shifted position after removing hidden columns.
+  }
+
+  /**
+   * Find the visible column which is a given visible number of columns to the
+   * left of another visible column. i.e. for a startColumn x, the column which
+   * is distance 1 away will be column x-1.
+   * 
+   * @param visibleDistance
+   *          the number of visible columns to offset by
+   * @param startColumn
+   *          the column to start from
+   * @return the position of the column in the visible alignment
+   */
+  public int subtractVisibleColumns(int visibleDistance, int startColumn)
+  {
+    int distance = visibleDistance;
+
+    // in case startColumn is in a hidden region, move it to the left
+    int start = adjustForHiddenColumns(findColumnPosition(startColumn));
+
+    // get index of hidden region to left of start
+    int index = getHiddenIndexLeft(start);
+    if (index == -1)
+    {
+      // no hidden regions to left of startColumn
+      return start - distance;
+    }
+
+    // walk backwards through the alignment subtracting the counts of visible
+    // columns from distance
+    int[] region;
+    int gap = 0;
+    int nextstart = start;
+
+    while ((index > -1) && (distance - gap > 0))
+    {
+      // subtract the gap to right of region from distance
+      distance -= gap;
+      start = nextstart;
+
+      // calculate the next gap
+      region = hiddenColumns.get(index);
+      gap = start - region[1];
+
+      // set start to just to left of current region
+      nextstart = region[0] - 1;
+      index--;
+    }
+
+    if (distance - gap > 0)
+    {
+      // fell out of loop because there are no more hidden regions
+      distance -= gap;
+      return nextstart - distance;
+    }
+    return start - distance;
+
+  }
+
+  /**
+   * Use this method to determine where the next hiddenRegion starts
+   * 
+   * @param hiddenRegion
+   *          index of hidden region (counts from 0)
+   * @return column number in visible view
+   */
+  public int findHiddenRegionPosition(int hiddenRegion)
+  {
+    int result = 0;
+    if (hiddenColumns != null)
+    {
+      int index = 0;
+      int gaps = 0;
+      do
+      {
+        int[] region = hiddenColumns.elementAt(index);
+        if (hiddenRegion == 0)
+        {
+          return region[0];
+        }
+
+        gaps += region[1] + 1 - region[0];
+        result = region[1] + 1;
+        index++;
+      } while (index <= hiddenRegion);
+
+      result -= gaps;
+    }
+
+    return result;
+  }
+
+  /**
+   * This method returns the rightmost limit of a region of an alignment with
+   * hidden columns. In otherwords, the next hidden column.
+   * 
+   * @param index
+   *          int
+   */
+  public int getHiddenBoundaryRight(int alPos)
+  {
+    if (hiddenColumns != null)
+    {
+      int index = 0;
+      do
+      {
+        int[] region = hiddenColumns.elementAt(index);
+        if (alPos < region[0])
+        {
+          return region[0];
+        }
+
+        index++;
+      } while (index < hiddenColumns.size());
+    }
+
+    return alPos;
+
+  }
+
+  /**
+   * This method returns the leftmost limit of a region of an alignment with
+   * hidden columns. In otherwords, the previous hidden column.
+   * 
+   * @param index
+   *          int
+   */
+  public int getHiddenBoundaryLeft(int alPos)
+  {
+    if (hiddenColumns != null)
+    {
+      int index = hiddenColumns.size() - 1;
+      do
+      {
+        int[] region = hiddenColumns.elementAt(index);
+        if (alPos > region[1])
+        {
+          return region[1];
+        }
+
+        index--;
+      } while (index > -1);
+    }
+
+    return alPos;
+
+  }
+
+  /**
+   * This method returns the index of the hidden region to the left of a column
+   * position. If the column is in a hidden region it returns the index of the
+   * region to the left. If there is no hidden region to the left it returns -1.
+   * 
+   * @param pos
+   *          int
+   */
+  private int getHiddenIndexLeft(int pos)
+  {
+    if (hiddenColumns != null)
+    {
+      int index = hiddenColumns.size() - 1;
+      do
+      {
+        int[] region = hiddenColumns.elementAt(index);
+        if (pos > region[1])
+        {
+          return index;
+        }
+
+        index--;
+      } while (index > -1);
+    }
+
+    return -1;
+
+  }
+
+  /**
+   * Adds the specified column range to the hidden columns
+   * 
+   * @param start
+   * @param end
+   */
+  public void hideColumns(int start, int end)
+  {
+    if (hiddenColumns == null)
+    {
+      hiddenColumns = new Vector<int[]>();
+    }
+
+    /*
+     * traverse existing hidden ranges and insert / amend / append as
+     * appropriate
+     */
+    for (int i = 0; i < hiddenColumns.size(); i++)
+    {
+      int[] region = hiddenColumns.elementAt(i);
+
+      if (end < region[0] - 1)
+      {
+        /*
+         * insert discontiguous preceding range
+         */
+        hiddenColumns.insertElementAt(new int[] { start, end }, i);
+        return;
+      }
+
+      if (end <= region[1])
+      {
+        /*
+         * new range overlaps existing, or is contiguous preceding it - adjust
+         * start column
+         */
+        region[0] = Math.min(region[0], start);
+        return;
+      }
+
+      if (start <= region[1] + 1)
+      {
+        /*
+         * new range overlaps existing, or is contiguous following it - adjust
+         * start and end columns
+         */
+        region[0] = Math.min(region[0], start);
+        region[1] = Math.max(region[1], end);
+
+        /*
+         * also update or remove any subsequent ranges 
+         * that are overlapped
+         */
+        while (i < hiddenColumns.size() - 1)
+        {
+          int[] nextRegion = hiddenColumns.get(i + 1);
+          if (nextRegion[0] > end + 1)
+          {
+            /*
+             * gap to next hidden range - no more to update
+             */
+            break;
+          }
+          region[1] = Math.max(nextRegion[1], end);
+          hiddenColumns.remove(i + 1);
+        }
+        return;
+      }
+    }
+
+    /*
+     * remaining case is that the new range follows everything else
+     */
+    hiddenColumns.addElement(new int[] { start, end });
+  }
+
+  public boolean isVisible(int column)
+  {
+    if (hiddenColumns != null)
+    {
+      for (int[] region : hiddenColumns)
+      {
+        if (column >= region[0] && column <= region[1])
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * ColumnSelection
+   */
+  public HiddenColumns()
+  {
+  }
+
+  /**
+   * Copy constructor
+   * 
+   * @param copy
+   */
+  public HiddenColumns(HiddenColumns copy)
+  {
+    if (copy != null)
+    {
+      if (copy.hiddenColumns != null)
+      {
+        hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
+        for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
+        {
+          int[] rh, cp;
+          rh = copy.hiddenColumns.elementAt(i);
+          if (rh != null)
+          {
+            cp = new int[rh.length];
+            System.arraycopy(rh, 0, cp, 0, rh.length);
+            hiddenColumns.addElement(cp);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * propagate shift in alignment columns to column selection
+   * 
+   * @param start
+   *          beginning of edit
+   * @param left
+   *          shift in edit (+ve for removal, or -ve for inserts)
+   */
+  public List<int[]> compensateForEdit(int start, int change,
+          ColumnSelection sel)
+  {
+    List<int[]> deletedHiddenColumns = null;
+
+    if (hiddenColumns != null)
+    {
+      deletedHiddenColumns = new ArrayList<int[]>();
+      int hSize = hiddenColumns.size();
+      for (int i = 0; i < hSize; i++)
+      {
+        int[] region = hiddenColumns.elementAt(i);
+        if (region[0] > start && start + change > region[1])
+        {
+          deletedHiddenColumns.add(region);
+
+          hiddenColumns.removeElementAt(i);
+          i--;
+          hSize--;
+          continue;
+        }
+
+        if (region[0] > start)
+        {
+          region[0] -= change;
+          region[1] -= change;
+        }
+
+        if (region[0] < 0)
+        {
+          region[0] = 0;
+        }
+
+      }
+
+      this.revealHiddenColumns(0, sel);
+    }
+
+    return deletedHiddenColumns;
+  }
+
+  /**
+   * propagate shift in alignment columns to column selection special version of
+   * compensateForEdit - allowing for edits within hidden regions
+   * 
+   * @param start
+   *          beginning of edit
+   * @param left
+   *          shift in edit (+ve for removal, or -ve for inserts)
+   */
+  public void compensateForDelEdits(int start, int change)
+  {
+    if (hiddenColumns != null)
+    {
+      for (int i = 0; i < hiddenColumns.size(); i++)
+      {
+        int[] region = hiddenColumns.elementAt(i);
+        if (region[0] >= start)
+        {
+          region[0] -= change;
+        }
+        if (region[1] >= start)
+        {
+          region[1] -= change;
+        }
+        if (region[1] < region[0])
+        {
+          hiddenColumns.removeElementAt(i--);
+        }
+
+        if (region[0] < 0)
+        {
+          region[0] = 0;
+        }
+        if (region[1] < 0)
+        {
+          region[1] = 0;
+        }
+      }
+    }
+  }
+
+  /**
+   * return all visible segments between the given start and end boundaries
+   * 
+   * @param start
+   *          (first column inclusive from 0)
+   * @param end
+   *          (last column - not inclusive)
+   * @return int[] {i_start, i_end, ..} where intervals lie in
+   *         start<=i_start<=i_end<end
+   */
+  public int[] getVisibleContigs(int start, int end)
+  {
+    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    {
+      List<int[]> visiblecontigs = new ArrayList<int[]>();
+      List<int[]> regions = getHiddenRegions();
+
+      int vstart = start;
+      int[] region;
+      int hideStart, hideEnd;
+
+      for (int j = 0; vstart < end && j < regions.size(); j++)
+      {
+        region = regions.get(j);
+        hideStart = region[0];
+        hideEnd = region[1];
+
+        if (hideEnd < vstart)
+        {
+          continue;
+        }
+        if (hideStart > vstart)
+        {
+          visiblecontigs.add(new int[] { vstart, hideStart - 1 });
+        }
+        vstart = hideEnd + 1;
+      }
+
+      if (vstart < end)
+      {
+        visiblecontigs.add(new int[] { vstart, end - 1 });
+      }
+      int[] vcontigs = new int[visiblecontigs.size() * 2];
+      for (int i = 0, j = visiblecontigs.size(); i < j; i++)
+      {
+        int[] vc = visiblecontigs.get(i);
+        visiblecontigs.set(i, null);
+        vcontigs[i * 2] = vc[0];
+        vcontigs[i * 2 + 1] = vc[1];
+      }
+      visiblecontigs.clear();
+      return vcontigs;
+    }
+    else
+    {
+      return new int[] { start, end - 1 };
+    }
+  }
+
+  public String[] getVisibleSequenceStrings(int start, int end,
+          SequenceI[] seqs)
+  {
+    int i, iSize = seqs.length;
+    String selections[] = new String[iSize];
+    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    {
+      for (i = 0; i < iSize; i++)
+      {
+        StringBuffer visibleSeq = new StringBuffer();
+        List<int[]> regions = getHiddenRegions();
+
+        int blockStart = start, blockEnd = end;
+        int[] region;
+        int hideStart, hideEnd;
+
+        for (int j = 0; j < regions.size(); j++)
+        {
+          region = regions.get(j);
+          hideStart = region[0];
+          hideEnd = region[1];
+
+          if (hideStart < start)
+          {
+            continue;
+          }
+
+          blockStart = Math.min(blockStart, hideEnd + 1);
+          blockEnd = Math.min(blockEnd, hideStart);
+
+          if (blockStart > blockEnd)
+          {
+            break;
+          }
+
+          visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
+
+          blockStart = hideEnd + 1;
+          blockEnd = end;
+        }
+
+        if (end > blockStart)
+        {
+          visibleSeq.append(seqs[i].getSequence(blockStart, end));
+        }
+
+        selections[i] = visibleSeq.toString();
+      }
+    }
+    else
+    {
+      for (i = 0; i < iSize; i++)
+      {
+        selections[i] = seqs[i].getSequenceAsString(start, end);
+      }
+    }
+
+    return selections;
+  }
+
+  /**
+   * Locate the first and last position visible for this sequence. if seq isn't
+   * visible then return the position of the left and right of the hidden
+   * boundary region, and the corresponding alignment column indices for the
+   * extent of the sequence
+   * 
+   * @param seq
+   * @return int[] { visible start, visible end, first seqpos, last seqpos,
+   *         alignment index for seq start, alignment index for seq end }
+   */
+  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
+  {
+    int fpos = seq.getStart(), lpos = seq.getEnd();
+    int start = 0;
+
+    if (hiddenColumns == null || hiddenColumns.size() == 0)
+    {
+      int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
+      return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
+    }
+
+    // Simply walk along the sequence whilst watching for hidden column
+    // boundaries
+    List<int[]> regions = getHiddenRegions();
+    int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
+            .getLength(), hideEnd = -1;
+    int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
+    boolean foundStart = false;
+    for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
+            && p < pLen; p++)
+    {
+      if (!Comparison.isGap(seq.getCharAt(p)))
+      {
+        // keep track of first/last column
+        // containing sequence data regardless of visibility
+        if (firstP == -1)
+        {
+          firstP = p;
+        }
+        lastP = p;
+        // update hidden region start/end
+        while (hideEnd < p && rcount < regions.size())
+        {
+          int[] region = regions.get(rcount++);
+          visPrev = visNext;
+          visNext += region[0] - visPrev;
+          hideStart = region[0];
+          hideEnd = region[1];
+        }
+        if (hideEnd < p)
+        {
+          hideStart = seq.getLength();
+        }
+        // update visible boundary for sequence
+        if (p < hideStart)
+        {
+          if (!foundStart)
+          {
+            fpos = spos;
+            start = p;
+            foundStart = true;
+          }
+          lastvispos = p;
+          lpos = spos;
+        }
+        // look for next sequence position
+        spos++;
+      }
+    }
+    if (foundStart)
+    {
+      return new int[] { findColumnPosition(start),
+          findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
+    }
+    // otherwise, sequence was completely hidden
+    return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
+  }
+
+  /**
+   * delete any columns in alignmentAnnotation that are hidden (including
+   * sequence associated annotation).
+   * 
+   * @param alignmentAnnotation
+   */
+  public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
+  {
+    makeVisibleAnnotation(-1, -1, alignmentAnnotation);
+  }
+
+  /**
+   * delete any columns in alignmentAnnotation that are hidden (including
+   * sequence associated annotation).
+   * 
+   * @param start
+   *          remove any annotation to the right of this column
+   * @param end
+   *          remove any annotation to the left of this column
+   * @param alignmentAnnotation
+   *          the annotation to operate on
+   */
+  public void makeVisibleAnnotation(int start, int end,
+          AlignmentAnnotation alignmentAnnotation)
+  {
+    if (alignmentAnnotation.annotations == null)
+    {
+      return;
+    }
+    if (start == end && end == -1)
+    {
+      start = 0;
+      end = alignmentAnnotation.annotations.length;
+    }
+    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    {
+      // then mangle the alignmentAnnotation annotation array
+      Vector<Annotation[]> annels = new Vector<Annotation[]>();
+      Annotation[] els = null;
+      List<int[]> regions = getHiddenRegions();
+      int blockStart = start, blockEnd = end;
+      int[] region;
+      int hideStart, hideEnd, w = 0;
+
+      for (int j = 0; j < regions.size(); j++)
+      {
+        region = regions.get(j);
+        hideStart = region[0];
+        hideEnd = region[1];
+
+        if (hideStart < start)
+        {
+          continue;
+        }
+
+        blockStart = Math.min(blockStart, hideEnd + 1);
+        blockEnd = Math.min(blockEnd, hideStart);
+
+        if (blockStart > blockEnd)
+        {
+          break;
+        }
+
+        annels.addElement(els = new Annotation[blockEnd - blockStart]);
+        System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
+                0, els.length);
+        w += els.length;
+        blockStart = hideEnd + 1;
+        blockEnd = end;
+      }
+
+      if (end > blockStart)
+      {
+        annels.addElement(els = new Annotation[end - blockStart + 1]);
+        if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
+        {
+          // copy just the visible segment of the annotation row
+          System.arraycopy(alignmentAnnotation.annotations, blockStart,
+                  els, 0, els.length);
+        }
+        else
+        {
+          // copy to the end of the annotation row
+          System.arraycopy(alignmentAnnotation.annotations, blockStart,
+                  els, 0,
+                  (alignmentAnnotation.annotations.length - blockStart));
+        }
+        w += els.length;
+      }
+      if (w == 0)
+      {
+        return;
+      }
+
+      alignmentAnnotation.annotations = new Annotation[w];
+      w = 0;
+
+      for (Annotation[] chnk : annels)
+      {
+        System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
+                chnk.length);
+        w += chnk.length;
+      }
+    }
+    else
+    {
+      alignmentAnnotation.restrict(start, end);
+    }
+  }
+
+  /**
+   * 
+   * @return true if there are columns hidden
+   */
+  public boolean hasHiddenColumns()
+  {
+    return hiddenColumns != null && hiddenColumns.size() > 0;
+  }
+
+  /**
+   * 
+   * @return true if there are more than one set of columns hidden
+   */
+  public boolean hasManyHiddenColumns()
+  {
+    return hiddenColumns != null && hiddenColumns.size() > 1;
+  }
+
+  /**
+   * mark the columns corresponding to gap characters as hidden in the column
+   * selection
+   * 
+   * @param sr
+   */
+  public void hideInsertionsFor(SequenceI sr)
+  {
+    List<int[]> inserts = sr.getInsertions();
+    for (int[] r : inserts)
+    {
+      hideColumns(r[0], r[1]);
+    }
+  }
+
+  /**
+   * Unhides, and adds to the selection list, all hidden columns
+   */
+  public void revealAllHiddenColumns(ColumnSelection sel)
+  {
+    if (hiddenColumns != null)
+    {
+      for (int i = 0; i < hiddenColumns.size(); i++)
+      {
+        int[] region = hiddenColumns.elementAt(i);
+        for (int j = region[0]; j < region[1] + 1; j++)
+        {
+          sel.addElement(j);
+        }
+      }
+    }
+
+    hiddenColumns = null;
+  }
+
+  /**
+   * Reveals, and marks as selected, the hidden column range with the given
+   * start column
+   * 
+   * @param start
+   */
+  public void revealHiddenColumns(int start, ColumnSelection sel)
+  {
+    for (int i = 0; i < hiddenColumns.size(); i++)
+    {
+      int[] region = hiddenColumns.elementAt(i);
+      if (start == region[0])
+      {
+        for (int j = region[0]; j < region[1] + 1; j++)
+        {
+          sel.addElement(j);
+        }
+
+        hiddenColumns.removeElement(region);
+        break;
+      }
+    }
+    if (hiddenColumns.size() == 0)
+    {
+      hiddenColumns = null;
+    }
+  }
+
+  /**
+   * removes intersection of position,length ranges in deletions from the
+   * start,end regions marked in intervals.
+   * 
+   * @param shifts
+   * @param intervals
+   * @return
+   */
+  private boolean pruneIntervalVector(final List<int[]> shifts,
+          Vector<int[]> intervals)
+  {
+    boolean pruned = false;
+    int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
+    int hr[] = intervals.elementAt(i);
+    int sr[] = shifts.get(s);
+    while (i <= j && s <= t)
+    {
+      boolean trailinghn = hr[1] >= sr[0];
+      if (!trailinghn)
+      {
+        if (i < j)
+        {
+          hr = intervals.elementAt(++i);
+        }
+        else
+        {
+          i++;
+        }
+        continue;
+      }
+      int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
+      if (endshift < hr[0] || endshift < sr[0])
+      { // leadinghc disjoint or not a deletion
+        if (s < t)
+        {
+          sr = shifts.get(++s);
+        }
+        else
+        {
+          s++;
+        }
+        continue;
+      }
+      boolean leadinghn = hr[0] >= sr[0];
+      boolean leadinghc = hr[0] < endshift;
+      boolean trailinghc = hr[1] < endshift;
+      if (leadinghn)
+      {
+        if (trailinghc)
+        { // deleted hidden region.
+          intervals.removeElementAt(i);
+          pruned = true;
+          j--;
+          if (i <= j)
+          {
+            hr = intervals.elementAt(i);
+          }
+          continue;
+        }
+        if (leadinghc)
+        {
+          hr[0] = endshift; // clip c terminal region
+          leadinghn = !leadinghn;
+          pruned = true;
+        }
+      }
+      if (!leadinghn)
+      {
+        if (trailinghc)
+        {
+          if (trailinghn)
+          {
+            hr[1] = sr[0] - 1;
+            pruned = true;
+          }
+        }
+        else
+        {
+          // sr contained in hr
+          if (s < t)
+          {
+            sr = shifts.get(++s);
+          }
+          else
+          {
+            s++;
+          }
+          continue;
+        }
+      }
+    }
+    return pruned; // true if any interval was removed or modified by
+    // operations.
+  }
+
+  /**
+   * remove any hiddenColumns or selected columns and shift remaining based on a
+   * series of position, range deletions.
+   * 
+   * @param deletions
+   */
+  public void pruneDeletions(List<int[]> shifts)
+  {
+    // delete any intervals intersecting.
+    if (hiddenColumns != null)
+    {
+      pruneIntervalVector(shifts, hiddenColumns);
+      if (hiddenColumns != null && hiddenColumns.size() == 0)
+      {
+        hiddenColumns = null;
+      }
+    }
+  }
+
+  /**
+   * Add gaps into the sequences aligned to profileseq under the given
+   * AlignmentView
+   * 
+   * @param profileseq
+   * @param al
+   *          - alignment to have gaps inserted into it
+   * @param input
+   *          - alignment view where sequence corresponding to profileseq is
+   *          first entry
+   * @return new HiddenColumns for new alignment view, with insertions into
+   *         profileseq marked as hidden.
+   */
+  public static HiddenColumns propagateInsertions(SequenceI profileseq,
+          AlignmentI al, AlignmentView input)
+  {
+    int profsqpos = 0;
+
+    char gc = al.getGapCharacter();
+    Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
+    HiddenColumns nview = (HiddenColumns) alandhidden[1];
+    SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
+    nview.propagateInsertions(profileseq, al, origseq);
+    return nview;
+  }
+
+  /**
+   * 
+   * @param profileseq
+   *          - sequence in al which corresponds to origseq
+   * @param al
+   *          - alignment which is to have gaps inserted into it
+   * @param origseq
+   *          - sequence corresponding to profileseq which defines gap map for
+   *          modifying al
+   */
+  private void propagateInsertions(SequenceI profileseq, AlignmentI al,
+          SequenceI origseq)
+  {
+    char gc = al.getGapCharacter();
+    // recover mapping between sequence's non-gap positions and positions
+    // mapping to view.
+    pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
+    int[] viscontigs = al.getHiddenColumns().getVisibleContigs(0,
+            profileseq.getLength());
+    int spos = 0;
+    int offset = 0;
+
+    // add profile to visible contigs
+    for (int v = 0; v < viscontigs.length; v += 2)
+    {
+      if (viscontigs[v] > spos)
+      {
+        StringBuffer sb = new StringBuffer();
+        for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
+        {
+          sb.append(gc);
+        }
+        for (int s = 0, ns = al.getHeight(); s < ns; s++)
+        {
+          SequenceI sqobj = al.getSequenceAt(s);
+          if (sqobj != profileseq)
+          {
+            String sq = al.getSequenceAt(s).getSequenceAsString();
+            if (sq.length() <= spos + offset)
+            {
+              // pad sequence
+              int diff = spos + offset - sq.length() - 1;
+              if (diff > 0)
+              {
+                // pad gaps
+                sq = sq + sb;
+                while ((diff = spos + offset - sq.length() - 1) > 0)
+                {
+                  // sq = sq
+                  // + ((diff >= sb.length()) ? sb.toString() : sb
+                  // .substring(0, diff));
+                  if (diff >= sb.length())
+                  {
+                    sq += sb.toString();
+                  }
+                  else
+                  {
+                    char[] buf = new char[diff];
+                    sb.getChars(0, diff, buf, 0);
+                    sq += buf.toString();
+                  }
+                }
+              }
+              sq += sb.toString();
+            }
+            else
+            {
+              al.getSequenceAt(s).setSequence(
+                      sq.substring(0, spos + offset) + sb.toString()
+                              + sq.substring(spos + offset));
+            }
+          }
+        }
+        // offset+=sb.length();
+      }
+      spos = viscontigs[v + 1] + 1;
+    }
+    if ((offset + spos) < profileseq.getLength())
+    {
+      // pad the final region with gaps.
+      StringBuffer sb = new StringBuffer();
+      for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
+      {
+        sb.append(gc);
+      }
+      for (int s = 0, ns = al.getHeight(); s < ns; s++)
+      {
+        SequenceI sqobj = al.getSequenceAt(s);
+        if (sqobj == profileseq)
+        {
+          continue;
+        }
+        String sq = sqobj.getSequenceAsString();
+        // pad sequence
+        int diff = origseq.getLength() - sq.length();
+        while (diff > 0)
+        {
+          // sq = sq
+          // + ((diff >= sb.length()) ? sb.toString() : sb
+          // .substring(0, diff));
+          if (diff >= sb.length())
+          {
+            sq += sb.toString();
+          }
+          else
+          {
+            char[] buf = new char[diff];
+            sb.getChars(0, diff, buf, 0);
+            sq += buf.toString();
+          }
+          diff = origseq.getLength() - sq.length();
+        }
+      }
+    }
+  }
+
+  /**
+   * remove any hiddenColumns or selected columns and shift remaining based on a
+   * series of position, range deletions.
+   * 
+   * @param deletions
+   */
+  private void pruneDeletions(ShiftList deletions)
+  {
+    if (deletions != null)
+    {
+      final List<int[]> shifts = deletions.getShifts();
+      if (shifts != null && shifts.size() > 0)
+      {
+        pruneDeletions(shifts);
+
+        // and shift the rest.
+        this.compensateForEdits(deletions);
+      }
+    }
+  }
+
+  /**
+   * Adjust hidden column boundaries based on a series of column additions or
+   * deletions in visible regions.
+   * 
+   * @param shiftrecord
+   * @return
+   */
+  private ShiftList compensateForEdits(ShiftList shiftrecord)
+  {
+    if (shiftrecord != null)
+    {
+      final List<int[]> shifts = shiftrecord.getShifts();
+      if (shifts != null && shifts.size() > 0)
+      {
+        int shifted = 0;
+        for (int i = 0, j = shifts.size(); i < j; i++)
+        {
+          int[] sh = shifts.get(i);
+          compensateForDelEdits(shifted + sh[0], sh[1]);
+          shifted -= sh[1];
+        }
+      }
+      return shiftrecord.getInverse();
+    }
+    return null;
+  }
+
+  /**
+   * Returns a hashCode built from hidden column ranges
+   */
+  @Override
+  public int hashCode()
+  {
+    int hashCode = 1;
+    if (hiddenColumns != null)
+    {
+      for (int[] hidden : hiddenColumns)
+      {
+        hashCode = 31 * hashCode + hidden[0];
+        hashCode = 31 * hashCode + hidden[1];
+      }
+    }
+    return hashCode;
+  }
+
+  /**
+   * Hide columns corresponding to the marked bits
+   * 
+   * @param inserts
+   *          - columns map to bits starting from zero
+   */
+  public void hideMarkedBits(BitSet inserts)
+  {
+    for (int firstSet = inserts.nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
+            .nextSetBit(lastSet))
+    {
+      lastSet = inserts.nextClearBit(firstSet);
+      hideColumns(firstSet, lastSet - 1);
+    }
+  }
+
+  /**
+   * 
+   * @param inserts
+   *          BitSet where hidden columns will be marked
+   */
+  public void markHiddenRegions(BitSet inserts)
+  {
+    if (hiddenColumns == null)
+    {
+      return;
+    }
+    for (int[] range : hiddenColumns)
+    {
+      inserts.set(range[0], range[1] + 1);
+    }
+  }
+
+}
index 9e2cf72..a98b10e 100755 (executable)
@@ -154,8 +154,8 @@ public class HiddenSequences
       hiddenSequences = new SequenceI[alignment.getHeight()];
     }
 
-    int alignmentIndex = alignment.findIndex(sequence);
-    alignmentIndex = adjustForHiddenSeqs(alignmentIndex);
+    int absAlignmentIndex = alignment.findIndex(sequence);
+    int alignmentIndex = adjustForHiddenSeqs(absAlignmentIndex);
 
     if (hiddenSequences[alignmentIndex] != null)
     {
@@ -164,13 +164,19 @@ public class HiddenSequences
 
     hiddenSequences[alignmentIndex] = sequence;
 
-    alignment.deleteSequence(sequence);
+    alignment.deleteHiddenSequence(absAlignmentIndex);
   }
 
   public List<SequenceI> showAll(
           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
   {
-    List<SequenceI> revealedSeqs = new ArrayList<SequenceI>();
+    List<SequenceI> revealedSeqs = new ArrayList<>();
+
+    if (hiddenSequences == null)
+    {
+      return revealedSeqs;
+    }
+
     for (int i = 0; i < hiddenSequences.length; i++)
     {
       if (hiddenSequences[i] != null)
@@ -199,7 +205,7 @@ public class HiddenSequences
   public List<SequenceI> showSequence(int alignmentIndex,
           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
   {
-    List<SequenceI> revealedSeqs = new ArrayList<SequenceI>();
+    List<SequenceI> revealedSeqs = new ArrayList<>();
     SequenceI repSequence = alignment.getSequenceAt(alignmentIndex);
     if (repSequence != null && hiddenRepSequences != null
             && hiddenRepSequences.containsKey(repSequence))
@@ -246,6 +252,12 @@ public class HiddenSequences
     return hiddenSequences == null ? null : hiddenSequences[alignmentIndex];
   }
 
+  /**
+   * Convert absolute alignment index to visible alignment index
+   * 
+   * @param alignmentIndex
+   * @return
+   */
   public int findIndexWithoutHiddenSeqs(int alignmentIndex)
   {
     if (hiddenSequences == null)
@@ -254,8 +266,14 @@ public class HiddenSequences
     }
     int index = 0;
     int hiddenSeqs = 0;
+    int diff = 0;
     if (hiddenSequences.length <= alignmentIndex)
     {
+      // if the alignmentIndex runs past the end of hidden sequences
+      // and therefore actually past the end of the alignment
+      // store the difference to add back on at the end, so that behaviour
+      // is consistent with hidden columns behaviour (used by overview panel)
+      diff = alignmentIndex - hiddenSequences.length + 1;
       alignmentIndex = hiddenSequences.length - 1;
     }
 
@@ -268,9 +286,50 @@ public class HiddenSequences
       index++;
     }
 
-    return (alignmentIndex - hiddenSeqs);
+    return (alignmentIndex - hiddenSeqs + diff);
   }
 
+  /**
+   * Find the visible row which is a given visible number of rows above another
+   * visible row. i.e. for a startRow x, the row which is distance 1 away will
+   * be row x-1.
+   * 
+   * @param visibleDistance
+   *          the number of visible rows to offset by
+   * @param startRow
+   *          the row to start from
+   * @return the position of the row in the visible alignment
+   */
+  public int subtractVisibleRows(int visibleDistance, int startRow)
+  {
+    // walk upwards through the alignment
+    // count all the non-null sequences until we have visibleDistance counted
+    // then return the next visible sequence
+    if (hiddenSequences == null)
+    {
+      return startRow - visibleDistance;
+    }
+
+    int index = startRow;
+    int count = 0;
+    while ((index > -1) && (count < visibleDistance))
+    {
+      if (hiddenSequences[index] == null)
+      {
+        // count visible sequences
+        count++;
+      }
+      index--;
+    }
+    return index;
+  }
+
+  /**
+   * Convert alignment index from visible alignment to absolute alignment
+   * 
+   * @param alignmentIndex
+   * @return
+   */
   public int adjustForHiddenSeqs(int alignmentIndex)
   {
     if (hiddenSequences == null)
@@ -350,4 +409,20 @@ public class HiddenSequences
 
     return false;
   }
+
+  /**
+   * Answers if a sequence is hidden
+   * 
+   * @param seq
+   *          (absolute) index to test
+   * @return true if sequence at index seq is hidden
+   */
+  public boolean isHidden(int seq)
+  {
+    if (hiddenSequences != null)
+    {
+      return (hiddenSequences[seq] != null);
+    }
+    return false;
+  }
 }
diff --git a/src/jalview/datamodel/Profile.java~ b/src/jalview/datamodel/Profile.java~
new file mode 100644 (file)
index 0000000..5464596
--- /dev/null
@@ -0,0 +1,143 @@
+package jalview.datamodel;
+
+
+/**
+ * A profile for one column of an alignment
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class Profile implements ProfileI
+{
+  /*
+   * an object holding counts of symbols in the profile
+   */
+  private ResidueCount counts;
+
+  /*
+   * the number of sequences (gapped or not) in the profile
+   */
+  private int height;
+
+  /*
+   * the number of non-gapped sequences in the profile
+   */
+  private int gapped;
+
+  /*
+   * the highest count for any residue in the profile
+   */
+  private int maxCount;
+
+  /*
+   * the residue (e.g. K) or residues (e.g. KQW) with the
+   * highest count in the profile
+   */
+  private String modalResidue;
+
+  /**
+   * Constructor which allows derived data to be stored without having to store
+   * the full profile
+   * 
+   * @param seqCount
+   *          the number of sequences in the profile
+   * @param gaps
+   *          the number of gapped sequences
+   * @param max
+   *          the highest count for any residue
+   * @param modalres
+   *          the residue (or concatenated residues) with the highest count
+   */
+  public Profile(int seqCount, int gaps, int max, String modalRes)
+  {
+    this.height = seqCount;
+    this.gapped = gaps;
+    this.maxCount = max;
+    this.modalResidue = modalRes;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#setCounts(jalview.datamodel.ResidueCount)
+   */
+  @Override
+  public void setCounts(ResidueCount residueCounts)
+  {
+    this.counts = residueCounts;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getPercentageIdentity(boolean)
+   */
+  @Override
+  public float getPercentageIdentity(boolean ignoreGaps)
+  {
+    if (height == 0)
+    {
+      return 0f;
+    }
+    float pid = 0f;
+    if (ignoreGaps && gapped < height)
+    {
+      pid = (maxCount * 100f) / (height - gapped);
+    }
+    else
+    {
+      pid = (maxCount * 100f) / height;
+    }
+    return pid;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getCounts()
+   */
+  @Override
+  public ResidueCount getCounts()
+  {
+    return counts;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getHeight()
+   */
+  @Override
+  public int getHeight()
+  {
+    return height;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getGapped()
+   */
+  @Override
+  public int getGapped()
+  {
+    return gapped;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getMaxCount()
+   */
+  @Override
+  public int getMaxCount()
+  {
+    return maxCount;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getModalResidue()
+   */
+  @Override
+  public String getModalResidue()
+  {
+    return modalResidue;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getNonGapped()
+   */
+  @Override
+  public int getNonGapped()
+  {
+    return height - gapped;
+  }
+}
diff --git a/src/jalview/datamodel/ProfileI.java~ b/src/jalview/datamodel/ProfileI.java~
new file mode 100644 (file)
index 0000000..cf2b394
--- /dev/null
@@ -0,0 +1,67 @@
+package jalview.datamodel;
+
+public interface ProfileI
+{
+
+  /**
+   * Set the full profile of counts
+   * 
+   * @param residueCounts
+   */
+  public abstract void setCounts(ResidueCount residueCounts);
+
+  /**
+   * Returns the percentage identity of the profile, i.e. the highest proportion
+   * of conserved (equal) symbols. The percentage is as a fraction of all
+   * sequences, or only ungapped sequences if flag ignoreGaps is set true.
+   * 
+   * @param ignoreGaps
+   * @return
+   */
+  public abstract float getPercentageIdentity(boolean ignoreGaps);
+
+  /**
+   * Returns the full symbol counts for this profile
+   * 
+   * @return
+   */
+  public abstract ResidueCount getCounts();
+
+  /**
+   * Returns the number of sequences in the profile
+   * 
+   * @return
+   */
+  public abstract int getHeight();
+
+  /**
+   * Returns the number of sequences in the profile which had a gap character
+   * (or were too short to be included in this column's profile)
+   * 
+   * @return
+   */
+  public abstract int getGapped();
+
+  /**
+   * Returns the highest count for any symbol(s) in the profile
+   * 
+   * @return
+   */
+  public abstract int getMaxCount();
+
+  /**
+   * Returns the symbol (or concatenated symbols) which have the highest count
+   * in the profile, or an empty string if there were no symbols counted
+   * 
+   * @return
+   */
+  public abstract String getModalResidue();
+
+  /**
+   * Answers the number of non-gapped sequences in the profile
+   * 
+   * @return
+   */
+  public abstract int getNonGapped();
+
+}
\ No newline at end of file
diff --git a/src/jalview/datamodel/Profiles.java~ b/src/jalview/datamodel/Profiles.java~
new file mode 100644 (file)
index 0000000..98a8c6d
--- /dev/null
@@ -0,0 +1,43 @@
+package jalview.datamodel;
+
+public class Profiles implements ProfilesI
+{
+
+  private ProfileI[] profiles;
+
+  public Profiles(ProfileI[] p)
+  {
+    profiles = p;
+  }
+
+  /**
+   * Returns the profile for the given column, or null if none found
+   * 
+   * @param col
+   */
+  @Override
+  public ProfileI get(int col)
+  {
+    return profiles != null && col >= 0 && col < profiles.length ? profiles[col]
+            : null;
+  }
+
+  /**
+   * Returns the first column (base 0) covered by the profiles
+   */
+  @Override
+  public int getStartColumn()
+  {
+    return 0;
+  }
+
+  /**
+   * Returns the last column (base 0) covered by the profiles
+   */
+  @Override
+  public int getEndColumn()
+  {
+    return profiles == null ? 0 : profiles.length - 1;
+  }
+
+}
diff --git a/src/jalview/datamodel/ProfilesI.java~ b/src/jalview/datamodel/ProfilesI.java~
new file mode 100644 (file)
index 0000000..6719de6
--- /dev/null
@@ -0,0 +1,12 @@
+package jalview.datamodel;
+
+public interface ProfilesI
+{
+
+  ProfileI get(int i);
+
+  int getStartColumn();
+
+  int getEndColumn();
+
+}
diff --git a/src/jalview/datamodel/ResidueCount.java~ b/src/jalview/datamodel/ResidueCount.java~
new file mode 100644 (file)
index 0000000..0d0348c
--- /dev/null
@@ -0,0 +1,621 @@
+package jalview.datamodel;
+
+import jalview.util.Comparison;
+import jalview.util.Format;
+import jalview.util.QuickSort;
+import jalview.util.SparseCount;
+
+/**
+ * A class to count occurrences of residues in a profile, optimised for speed
+ * and memory footprint.
+ * @author gmcarstairs
+ *
+ */
+public class ResidueCount
+{
+  /**
+   * A data bean to hold the results of counting symbols
+   */
+  public class SymbolCounts
+  {
+    /**
+     * the symbols seen (as char values), in no particular order
+     */
+    public final char[] symbols;
+
+    /**
+     * the counts for each symbol, in the same order as the symbols
+     */
+    public final int[] values;
+
+    SymbolCounts(char[] s, int[] v)
+    {
+      symbols = s;
+      values = v;
+    }
+  }
+
+  private static final int TOUPPERCASE = 'A' - 'a';
+
+  /*
+   * nucleotide symbols to count (including N unknown)
+   */
+  private static final String NUCS = "ACGNTU";
+
+  /*
+   * amino acid symbols to count (including X unknown)
+   * NB we also include U so as to support counting of RNA bases
+   * in the "don't know" case of nucleotide / peptide
+   */
+  private static final String AAS = "ACDEFGHIKLMNPQRSTUVWXY";
+
+  private static final int GAP_COUNT = 0;
+
+  /*
+   * fast lookup tables holding the index into our count
+   * arrays of each symbol; index 0 is reserved for gap counting
+   */
+  private static int[] NUC_INDEX = new int[26];
+
+  private static int[] AA_INDEX = new int[26];
+  static
+  {
+    for (int i = 0; i < NUCS.length(); i++)
+    {
+      NUC_INDEX[NUCS.charAt(i) - 'A'] = i + 1;
+    }
+    for (int i = 0; i < AAS.length(); i++)
+    {
+      AA_INDEX[AAS.charAt(i) - 'A'] = i + 1;
+    }
+  }
+
+  /*
+   * counts array, just big enough for the nucleotide or peptide
+   * character set (plus gap counts in position 0)
+   */
+  private short[] counts;
+
+  /*
+   * alternative array of int counts for use if any count 
+   * exceeds the maximum value of short (32767)
+   */
+  private int[] intCounts;
+
+  /*
+   * flag set if we switch from short to int counts
+   */
+  private boolean useIntCounts;
+
+  /*
+   * general-purpose counter, only for use for characters
+   * that are not in the expected alphabet
+   */
+  private SparseCount otherData;
+
+  /*
+   * keeps track of the maximum count value recorded
+   * (if this class ever allows decrements, would need to
+   * calculate this on request instead) 
+   */
+  int maxCount;
+
+  /*
+   * if we think we are counting nucleotide, can get by with smaller
+   * array to hold counts
+   */
+  private boolean isNucleotide;
+
+  /**
+   * Default constructor allocates arrays able to count either nucleotide or
+   * peptide bases. Use this constructor if not sure which the data is.
+   */
+  public ResidueCount()
+  {
+    this(false);
+  }
+
+  /**
+   * Constructor that allocates an array just big enough for the anticipated
+   * characters, plus one position to count gaps
+   */
+  public ResidueCount(boolean nucleotide)
+  {
+    isNucleotide = nucleotide;
+    int charsToCount = nucleotide ? NUCS.length() : AAS.length();
+    counts = new short[charsToCount + 1];
+  }
+
+  /**
+   * Increments the count for the given character. The supplied character may be
+   * upper or lower case but counts are for the upper case only. Gap characters
+   * (space, ., -) are all counted together.
+   * 
+   * @param c
+   * @return the new value of the count for the character
+   */
+  public int add(final char c)
+  {
+    char u = toUpperCase(c);
+    int newValue = 0;
+    int offset = getOffset(u);
+
+    /*
+     * offset 0 is reserved for gap counting, so 0 here means either
+     * an unexpected character, or a gap character passed in error
+     */
+    if (offset == 0)
+    {
+      if (Comparison.isGap(u))
+      {
+        newValue = addGap();
+      }
+      else
+      {
+        newValue = addOtherCharacter(u);
+      }
+    }
+    else
+    {
+      newValue = increment(offset);
+    }
+    return newValue;
+  }
+
+  /**
+   * Increment the count at the specified offset. If this would result in short
+   * overflow, promote to counting int values instead.
+   * 
+   * @param offset
+   * @return the new value of the count at this offset
+   */
+  int increment(int offset)
+  {
+    int newValue = 0;
+    if (useIntCounts)
+    {
+      newValue = intCounts[offset];
+      intCounts[offset] = ++newValue;
+    }
+    else
+    {
+      if (counts[offset] == Short.MAX_VALUE)
+      {
+        handleOverflow();
+        newValue = intCounts[offset];
+        intCounts[offset] = ++newValue;
+      }
+      else
+      {
+        newValue = counts[offset];
+        counts[offset] = (short) ++newValue;
+      }
+    }
+    maxCount = Math.max(maxCount, newValue);
+    return newValue;
+  }
+
+  /**
+   * Switch from counting in short to counting in int
+   */
+  synchronized void handleOverflow()
+  {
+    intCounts = new int[counts.length];
+    for (int i = 0; i < counts.length; i++)
+    {
+      intCounts[i] = counts[i];
+    }
+    counts = null;
+    useIntCounts = true;
+  }
+
+  /**
+   * Returns this character's offset in the count array
+   * 
+   * @param c
+   * @return
+   */
+  int getOffset(char c)
+  {
+    int offset = 0;
+    if ('A' <= c && c <= 'Z')
+    {
+      offset = isNucleotide ? NUC_INDEX[c - 'A'] : AA_INDEX[c - 'A'];
+    }
+    return offset;
+  }
+
+  /**
+   * @param c
+   * @return
+   */
+  protected char toUpperCase(final char c)
+  {
+    char u = c;
+    if ('a' <= c && c <= 'z')
+    {
+      u = (char) (c + TOUPPERCASE);
+    }
+    return u;
+  }
+
+  /**
+   * Increment count for some unanticipated character. The first time this
+   * called, a SparseCount is instantiated to hold these 'extra' counts.
+   * 
+   * @param c
+   * @return the new value of the count for the character
+   */
+  int addOtherCharacter(char c)
+  {
+    if (otherData == null)
+    {
+      otherData = new SparseCount();
+    }
+    int newValue = otherData.add(c, 1);
+    maxCount = Math.max(maxCount, newValue);
+    return newValue;
+  }
+
+  /**
+   * Set count for some unanticipated character. The first time this called, a
+   * SparseCount is instantiated to hold these 'extra' counts.
+   * 
+   * @param c
+   * @param value
+   */
+  void setOtherCharacter(char c, int value)
+  {
+    if (otherData == null)
+    {
+      otherData = new SparseCount();
+    }
+    otherData.put(c, value);
+  }
+
+  /**
+   * Increment count of gap characters
+   * 
+   * @return the new count of gaps
+   */
+  public int addGap()
+  {
+    int newValue;
+    if (useIntCounts)
+    {
+      newValue = ++intCounts[GAP_COUNT];
+    }
+    else
+    {
+      newValue = ++counts[GAP_COUNT];
+    }
+    return newValue;
+  }
+
+  /**
+   * Answers true if we are counting ints (only after overflow of short counts)
+   * 
+   * @return
+   */
+  boolean isCountingInts()
+  {
+    return useIntCounts;
+  }
+
+  /**
+   * Sets the count for the given character. The supplied character may be upper
+   * or lower case but counts are for the upper case only.
+   * 
+   * @param c
+   * @param count
+   */
+  public void put(char c, int count)
+  {
+    char u = toUpperCase(c);
+    int offset = getOffset(u);
+
+    /*
+     * offset 0 is reserved for gap counting, so 0 here means either
+     * an unexpected character, or a gap character passed in error
+     */
+    if (offset == 0)
+    {
+      if (Comparison.isGap(u))
+      {
+        set(0, count);
+      }
+      else
+      {
+        setOtherCharacter(u, count);
+        maxCount = Math.max(maxCount, count);
+      }
+    }
+    else
+    {
+      set(offset, count);
+      maxCount = Math.max(maxCount, count);
+    }
+  }
+
+  /**
+   * Sets the count at the specified offset. If this would result in short
+   * overflow, promote to counting int values instead.
+   * 
+   * @param offset
+   * @param value
+   */
+  void set(int offset, int value)
+  {
+    if (useIntCounts)
+    {
+      intCounts[offset] = value;
+    }
+    else
+    {
+      if (value > Short.MAX_VALUE || value < Short.MIN_VALUE)
+      {
+        handleOverflow();
+        intCounts[offset] = value;
+      }
+      else
+      {
+        counts[offset] = (short) value;
+      }
+    }
+  }
+
+  /**
+   * Returns the count for the given character, or zero if no count held
+   * 
+   * @param c
+   * @return
+   */
+  public int getCount(char c)
+  {
+    char u = toUpperCase(c);
+    int offset = getOffset(u);
+    if (offset == 0)
+    {
+      if (!Comparison.isGap(u))
+      {
+        // should have called getGapCount()
+        return otherData == null ? 0 : otherData.get(u);
+      }
+    }
+    return useIntCounts ? intCounts[offset] : counts[offset];
+  }
+
+  public int getGapCount()
+  {
+    return useIntCounts ? intCounts[0] : counts[0];
+  }
+
+  /**
+   * Answers true if this object wraps a counter for unexpected characters
+   * 
+   * @return
+   */
+  boolean isUsingOtherData()
+  {
+    return otherData != null;
+  }
+
+  /**
+   * Returns the character (or concatenated characters) for the symbol(s) with
+   * the given count in the profile. Can be used to get the modal residue by
+   * supplying the modal count value. Returns an empty string if no symbol has
+   * the given count. The symbols are in alphabetic order of standard peptide or
+   * nucleotide characters, followed by 'other' symbols if any.
+   * 
+   * @return
+   */
+  public String getResiduesForCount(int count)
+  {
+    if (count == 0)
+    {
+      return "";
+    }
+
+    /*
+     * find counts for the given value and append the
+     * corresponding symbol
+     */
+    StringBuilder modal = new StringBuilder();
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] == count)
+        {
+          modal.append(isNucleotide ? NUCS.charAt(i - 1) : AAS
+                  .charAt(i - 1));
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] == count)
+        {
+          modal.append(isNucleotide ? NUCS.charAt(i - 1) : AAS
+                  .charAt(i - 1));
+        }
+      }
+    }
+    if (otherData != null)
+    {
+      for (int i = 0; i < otherData.size(); i++)
+      {
+        if (otherData.valueAt(i) == count)
+        {
+          modal.append((char) otherData.keyAt(i));
+        }
+      }
+    }
+    return modal.toString();
+  }
+
+  /**
+   * Returns the highest count for any symbol(s) in the profile (excluding gap)
+   * 
+   * @return
+   */
+  public int getModalCount()
+  {
+    return maxCount;
+  }
+
+  /**
+   * Returns the number of distinct symbols with a non-zero count (excluding the
+   * gap symbol)
+   * 
+   * @return
+   */
+  public int size() {
+    int size = 0;
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] > 0)
+        {
+          size++;
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] > 0)
+        {
+          size++;
+        }
+      }
+    }
+
+    /*
+     * include 'other' characters recorded (even if count is zero
+     * though that would be a strange use case)
+     */
+    if (otherData != null)
+    {
+      size += otherData.size();
+    }
+
+    return size;
+  }
+
+  /**
+   * Returns a data bean holding those symbols that have a non-zero count
+   * (excluding the gap symbol), with their counts.
+   * 
+   * @return
+   */
+  public SymbolCounts getSymbolCounts()
+  {
+    int size = size();
+    char[] symbols = new char[size];
+    int[] values = new int[size];
+    int j = 0;
+
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] > 0)
+        {
+          char symbol = isNucleotide ? NUCS.charAt(i - 1) : AAS
+                  .charAt(i - 1);
+          symbols[j] = symbol;
+          values[j] = intCounts[i];
+          j++;
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] > 0)
+        {
+          char symbol = isNucleotide ? NUCS.charAt(i - 1) : AAS
+                  .charAt(i - 1);
+          symbols[j] = symbol;
+          values[j] = counts[i];
+          j++;
+        }
+      }
+    }
+    if (otherData != null)
+    {
+      for (int i = 0; i < otherData.size(); i++)
+      {
+        symbols[j] = (char) otherData.keyAt(i);
+        values[j] = otherData.valueAt(i);
+        j++;
+      }
+    }
+
+    return new SymbolCounts(symbols, values);
+  }
+
+  /**
+   * Returns a tooltip string showing residues in descending order of their
+   * percentage frequency in the profile
+   * 
+   * @param normaliseBy
+   *          the divisor for residue counts (may or may not include gapped
+   *          sequence count)
+   * @param percentageDecPl
+   *          the number of decimal places to show in percentages
+   * @return
+   */
+  public String getTooltip(int normaliseBy, int percentageDecPl)
+  {
+    SymbolCounts symbolCounts = getSymbolCounts();
+    char[] ca = symbolCounts.symbols;
+    int[] vl = symbolCounts.values;
+
+    /*
+     * sort characters into ascending order of their counts
+     */
+    QuickSort.sort(vl, ca);
+
+    /*
+     * traverse in reverse order (highest count first) to build tooltip
+     */
+    boolean first = true;
+    StringBuilder sb = new StringBuilder(64);
+    for (int c = ca.length - 1; c >= 0; c--)
+    {
+      final char residue = ca[c];
+      // TODO combine residues which share a percentage
+      // (see AAFrequency.completeCdnaConsensus)
+      float tval = (vl[c] * 100f) / normaliseBy;
+      sb.append(first ? "" : "; ").append(residue).append(" ");
+      Format.appendPercentage(sb, tval, percentageDecPl);
+      sb.append("%");
+      first = false;
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns a string representation of the symbol counts, for debug purposes.
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append("[ ");
+    SymbolCounts sc = getSymbolCounts();
+    for (int i = 0; i < sc.symbols.length; i++)
+    {
+      sb.append(sc.symbols[i]).append(":").append(sc.values[i]).append(" ");
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+}
diff --git a/src/jalview/datamodel/SearchResultMatchI.java~ b/src/jalview/datamodel/SearchResultMatchI.java~
new file mode 100644 (file)
index 0000000..732f1dc
--- /dev/null
@@ -0,0 +1,30 @@
+package jalview.datamodel;
+
+/**
+ * An interface that describes one matched region of an alignment, as one
+ * contiguous portion of a single dataset sequence
+ */
+public interface SearchResultMatchI
+{
+  /**
+   * Returns the matched sequence
+   * 
+   * @return
+   */
+  SequenceI getSequence();
+
+  /**
+   * Returns the start position of the match in the sequence (base 1)
+   * 
+   * @return
+   */
+  int getStart();
+
+  /**
+   * Returns the end position of the match in the sequence (base 1)
+   * 
+   * @return
+   */
+  int getEnd();
+
+}
\ No newline at end of file
diff --git a/src/jalview/datamodel/SearchResultsI.java~ b/src/jalview/datamodel/SearchResultsI.java~
new file mode 100644 (file)
index 0000000..93183f2
--- /dev/null
@@ -0,0 +1,84 @@
+package jalview.datamodel;
+
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * An interface describing the result of a search or other operation which
+ * highlights matched regions of an alignment
+ */
+public interface SearchResultsI
+{
+
+  /**
+   * Adds one region to the results
+   * 
+   * @param seq
+   *          Sequence
+   * @param start
+   *          int
+   * @param end
+   *          int
+   * @return
+   */
+  SearchResultMatchI addResult(SequenceI seq, int start, int end);
+
+  /**
+   * Answers true if the search results include the given sequence (or its
+   * dataset sequence), else false
+   * 
+   * @param sequence
+   * @return
+   */
+  boolean involvesSequence(SequenceI sequence);
+
+  /**
+   * Returns an array of [from, to, from, to..] matched columns (base 0) between
+   * the given start and end columns of the given sequence. Returns null if no
+   * matches overlap the specified region.
+   * <p>
+   * Implementations should provide an optimised method to return locations to
+   * highlight on a visible portion of an alignment.
+   * 
+   * @param sequence
+   * @param start
+   *          first column of range (base 0, inclusive)
+   * @param end
+   *          last column of range base 0, inclusive)
+   * @return int[]
+   */
+  int[] getResults(SequenceI sequence, int start, int end);
+
+  /**
+   * Returns the number of matches found
+   * 
+   * @return
+   */
+  int getSize();
+
+  /**
+   * Returns true if no search result matches are held.
+   * 
+   * @return
+   */
+  boolean isEmpty();
+
+  /**
+   * Returns the list of matches.
+   * 
+   * @return
+   */
+  List<SearchResultMatchI> getResults();
+
+  /**
+   * Set bits in a bitfield for all columns in the given sequence collection
+   * that are highlighted
+   * 
+   * @param sqcol
+   *          the set of sequences to search for highlighted regions
+   * @param bs
+   *          bitset to set
+   * @return number of bits set
+   */
+  int markColumns(SequenceCollectionI sqcol, BitSet bs);
+}
\ No newline at end of file
index 98b0de5..9cc7b4a 100644 (file)
@@ -494,18 +494,18 @@ public class SeqCigar extends CigarSimple
   /**
    * create an alignment from the given array of cigar sequences and gap
    * character, and marking the given segments as visible in the given
-   * columselection.
+   * hiddenColumns.
    * 
    * @param alseqs
    * @param gapCharacter
-   * @param colsel
-   *          - columnSelection where hidden regions are marked
+   * @param hidden
+   *          - hiddenColumns where hidden regions are marked
    * @param segments
    *          - visible regions of alignment
    * @return SequenceI[]
    */
   public static SequenceI[] createAlignmentSequences(SeqCigar[] alseqs,
-          char gapCharacter, ColumnSelection colsel, int[] segments)
+          char gapCharacter, HiddenColumns hidden, int[] segments)
   {
     SequenceI[] seqs = new SequenceI[alseqs.length];
     StringBuffer[] g_seqs = new StringBuffer[alseqs.length];
@@ -577,7 +577,7 @@ public class SeqCigar extends CigarSimple
           if (segments == null)
           {
             // add a hidden column for this deletion
-            colsel.hideColumns(inspos, inspos + insert.length - 1);
+            hidden.hideColumns(inspos, inspos + insert.length - 1);
           }
         }
       }
@@ -598,7 +598,7 @@ public class SeqCigar extends CigarSimple
       {
         // int start=shifts.shift(segments[i]-1)+1;
         // int end=shifts.shift(segments[i]+segments[i+1]-1)-1;
-        colsel.hideColumns(segments[i + 1], segments[i + 1]
+        hidden.hideColumns(segments[i + 1], segments[i + 1]
                 + segments[i + 2] - 1);
       }
     }
index c8b94ce..8176221 100755 (executable)
@@ -29,6 +29,7 @@ import jalview.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
@@ -314,12 +315,11 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   @Override
-  public synchronized void addSequenceFeature(SequenceFeature sf)
+  public synchronized boolean addSequenceFeature(SequenceFeature sf)
   {
     if (sequenceFeatures == null && datasetSequence != null)
     {
-      datasetSequence.addSequenceFeature(sf);
-      return;
+      return datasetSequence.addSequenceFeature(sf);
     }
     if (sequenceFeatures == null)
     {
@@ -330,7 +330,7 @@ public class Sequence extends ASequence implements SequenceI
     {
       if (sequenceFeatures[i].equals(sf))
       {
-        return;
+        return false;
       }
     }
 
@@ -339,6 +339,7 @@ public class Sequence extends ASequence implements SequenceI
     temp[sequenceFeatures.length] = sf;
 
     sequenceFeatures = temp;
+    return true;
   }
 
   @Override
@@ -819,6 +820,40 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   @Override
+  public BitSet getInsertionsAsBits()
+  {
+    BitSet map = new BitSet();
+    int lastj = -1, j = 0;
+    int pos = start;
+    int seqlen = sequence.length;
+    while ((j < seqlen))
+    {
+      if (jalview.util.Comparison.isGap(sequence[j]))
+      {
+        if (lastj == -1)
+        {
+          lastj = j;
+        }
+      }
+      else
+      {
+        if (lastj != -1)
+        {
+          map.set(lastj, j);
+          lastj = -1;
+        }
+      }
+      j++;
+    }
+    if (lastj != -1)
+    {
+      map.set(lastj, j);
+      lastj = -1;
+    }
+    return map;
+  }
+
+  @Override
   public void deleteChars(int i, int j)
   {
     int newstart = start, newend = end;
index 4f9d530..463b909 100755 (executable)
@@ -52,6 +52,12 @@ public class SequenceGroup implements AnnotatedCollectionI
   boolean colourText = false;
 
   /**
+   * True if the group is defined as a group on the alignment, false if it is
+   * just a selection.
+   */
+  boolean isDefined = false;
+
+  /**
    * after Olivier's non-conserved only character display
    */
   boolean showNonconserved = false;
@@ -59,7 +65,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * group members
    */
-  private List<SequenceI> sequences = new ArrayList<SequenceI>();
+  private List<SequenceI> sequences = new ArrayList<>();
 
   /**
    * representative sequence for this group (if any)
@@ -104,13 +110,23 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   private boolean normaliseSequenceLogo;
 
-  /**
-   * @return the includeAllConsSymbols
+  /*
+   * visibility of rows or represented rows covered by group
    */
-  public boolean isShowSequenceLogo()
-  {
-    return showSequenceLogo;
-  }
+  private boolean hidereps = false;
+
+  /*
+   * visibility of columns intersecting this group
+   */
+  private boolean hidecols = false;
+
+  AlignmentAnnotation consensus = null;
+
+  AlignmentAnnotation conservation = null;
+
+  private boolean showConsensusHistogram;
+
+  private AnnotatedCollectionI context;
 
   /**
    * Creates a new SequenceGroup object.
@@ -161,7 +177,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     this();
     if (seqsel != null)
     {
-      sequences = new ArrayList<SequenceI>();
+      sequences = new ArrayList<>();
       sequences.addAll(seqsel.sequences);
       if (seqsel.groupName != null)
       {
@@ -172,13 +188,17 @@ public class SequenceGroup implements AnnotatedCollectionI
       colourText = seqsel.colourText;
       startRes = seqsel.startRes;
       endRes = seqsel.endRes;
-      cs = seqsel.cs;
+      cs = new ResidueShader(seqsel.getColourScheme());
       if (seqsel.description != null)
       {
         description = new String(seqsel.description);
       }
       hidecols = seqsel.hidecols;
       hidereps = seqsel.hidereps;
+      showNonconserved = seqsel.showNonconserved;
+      showSequenceLogo = seqsel.showSequenceLogo;
+      normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
+      showConsensusHistogram = seqsel.showConsensusHistogram;
       idColour = seqsel.idColour;
       outlineColour = seqsel.outlineColour;
       seqrep = seqsel.seqrep;
@@ -195,6 +215,11 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
   }
 
+  public boolean isShowSequenceLogo()
+  {
+    return showSequenceLogo;
+  }
+
   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
   {
     int iSize = sequences.size();
@@ -311,7 +336,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     else
     {
-      List<SequenceI> allSequences = new ArrayList<SequenceI>();
+      List<SequenceI> allSequences = new ArrayList<>();
       for (SequenceI seq : sequences)
       {
         allSequences.add(seq);
@@ -951,11 +976,6 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * visibility of rows or represented rows covered by group
-   */
-  private boolean hidereps = false;
-
-  /**
    * set visibility of sequences covered by (if no sequence representative is
    * defined) or represented by this group.
    * 
@@ -977,11 +997,6 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * visibility of columns intersecting this group
-   */
-  private boolean hidecols = false;
-
-  /**
    * set intended visibility of columns covered by this group
    * 
    * @param visibility
@@ -1015,7 +1030,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     SequenceGroup sgroup = new SequenceGroup(this);
     SequenceI[] insect = getSequencesInOrder(alignment);
-    sgroup.sequences = new ArrayList<SequenceI>();
+    sgroup.sequences = new ArrayList<>();
     for (int s = 0; insect != null && s < insect.length; s++)
     {
       if (map == null || map.containsKey(insect[s]))
@@ -1043,13 +1058,6 @@ public class SequenceGroup implements AnnotatedCollectionI
     this.showNonconserved = displayNonconserved;
   }
 
-  AlignmentAnnotation consensus = null, conservation = null;
-
-  /**
-   * flag indicating if consensus histogram should be rendered
-   */
-  private boolean showConsensusHistogram;
-
   /**
    * set this alignmentAnnotation object as the one used to render consensus
    * annotation
@@ -1242,7 +1250,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     // TODO add in other methods like 'getAlignmentAnnotation(String label),
     // etc'
-    ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
     synchronized (sequences)
     {
       for (SequenceI seq : sequences)
@@ -1274,7 +1282,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    List<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    List<AlignmentAnnotation> aa = new ArrayList<>();
     if (calcId == null)
     {
       return aa;
@@ -1289,22 +1297,18 @@ public class SequenceGroup implements AnnotatedCollectionI
     return aa;
   }
 
-  /**
-   * Returns a list of annotations that match the specified sequenceRef, calcId
-   * and label, ignoring null values.
-   * 
-   * @return list of AlignmentAnnotation objects
-   */
   @Override
   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
     for (AlignmentAnnotation ann : getAlignmentAnnotation())
     {
-      if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
-              && ann.sequenceRef != null && ann.sequenceRef == seq
-              && ann.label != null && ann.label.equals(label))
+      if ((calcId == null || (ann.getCalcId() != null && ann.getCalcId()
+              .equals(calcId)))
+              && (seq == null || (ann.sequenceRef != null && ann.sequenceRef == seq))
+              && (label == null || (ann.label != null && ann.label
+                      .equals(label))))
       {
         aa.add(ann);
       }
@@ -1344,12 +1348,29 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
   }
 
-  private AnnotatedCollectionI context;
+  /**
+   * Sets the alignment or group context for this group, and whether it is
+   * defined as a group
+   * 
+   * @param ctx
+   *          the context for the group
+   * @param defined
+   *          whether the group is defined on the alignment or is just a
+   *          selection
+   * @throws IllegalArgumentException
+   *           if setting the context would result in a circular reference chain
+   */
+  public void setContext(AnnotatedCollectionI ctx, boolean defined)
+  {
+    setContext(ctx);
+    this.isDefined = defined;
+  }
 
   /**
    * Sets the alignment or group context for this group
    * 
    * @param ctx
+   *          the context for the group
    * @throws IllegalArgumentException
    *           if setting the context would result in a circular reference chain
    */
@@ -1358,7 +1379,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     AnnotatedCollectionI ref = ctx;
     while (ref != null)
     {
-      if (ref == this)
+      if (ref == this || ref.getContext() == ctx)
       {
         throw new IllegalArgumentException(
                 "Circular reference in SequenceGroup.context");
@@ -1379,6 +1400,11 @@ public class SequenceGroup implements AnnotatedCollectionI
     return context;
   }
 
+  public boolean isDefined()
+  {
+    return isDefined;
+  }
+
   public void setColourScheme(ColourSchemeI scheme)
   {
     if (cs == null)
@@ -1411,4 +1437,24 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     return false;
   }
+
+  /**
+   * @param seq
+   * @return true if seq is a member of the group
+   */
+
+  public boolean contains(SequenceI seq1)
+  {
+    return sequences.contains(seq1);
+  }
+
+  /**
+   * @param seq
+   * @param apos
+   * @return true if startRes<=apos and endRes>=apos and seq is in the group
+   */
+  public boolean contains(SequenceI seq, int apos)
+  {
+    return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
+  }
 }
index aec68ab..12ddf60 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.datamodel;
 
+import java.util.BitSet;
 import java.util.List;
 import java.util.Vector;
 
@@ -189,12 +190,13 @@ public interface SequenceI extends ASequenceI
   public int findIndex(int pos);
 
   /**
-   * Returns the sequence position for an alignment position
+   * Returns the sequence position for an alignment position.
    * 
    * @param i
    *          column index in alignment (from 0..<length)
    * 
-   * @return residue number for residue (left of and) nearest ith column
+   * @return TODO: JAL-2562 - residue number for residue (left of and) nearest
+   *         ith column
    */
   public int findPosition(int i);
 
@@ -337,7 +339,14 @@ public interface SequenceI extends ASequenceI
    */
   public void addDBRef(DBRefEntry entry);
 
-  public void addSequenceFeature(SequenceFeature sf);
+  /**
+   * Adds the given sequence feature and returns true, or returns false if it is
+   * already present on the sequence
+   * 
+   * @param sf
+   * @return
+   */
+  public boolean addSequenceFeature(SequenceFeature sf);
 
   public void deleteFeature(SequenceFeature sf);
 
@@ -468,4 +477,11 @@ public interface SequenceI extends ASequenceI
    *         list
    */
   public List<DBRefEntry> getPrimaryDBRefs();
+
+  /**
+   * 
+   * @return BitSet corresponding to index [0,length) where Comparison.isGap()
+   *         returns true.
+   */
+  BitSet getInsertionsAsBits();
 }
index b2f054c..fa12419 100755 (executable)
@@ -31,13 +31,13 @@ import java.awt.Color;
 public class SequenceNode extends BinaryNode
 {
   /** DOCUMENT ME!! */
-  public float dist;
+  public double dist;
 
   /** DOCUMENT ME!! */
   public int count;
 
   /** DOCUMENT ME!! */
-  public float height;
+  public double height;
 
   /** DOCUMENT ME!! */
   public float ycount;
@@ -178,7 +178,9 @@ public class SequenceNode extends BinaryNode
       {
         char q = name.charAt(c);
         if ('0' <= q && q <= '9')
+        {
           continue;
+        }
         return true;
       }
     }
diff --git a/src/jalview/datamodel/VisibleColsCollection.java b/src/jalview/datamodel/VisibleColsCollection.java
new file mode 100644 (file)
index 0000000..86233ab
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.datamodel;
+
+import jalview.api.AlignmentColsCollectionI;
+
+import java.util.Iterator;
+
+public class VisibleColsCollection implements AlignmentColsCollectionI
+{
+  int start;
+  int end;
+
+  HiddenColumns hidden;
+
+  public VisibleColsCollection(int s, int e, AlignmentI al)
+  {
+    start = s;
+    end = e;
+    hidden = al.getHiddenColumns();
+  }
+
+  @Override
+  public Iterator<Integer> iterator()
+  {
+    return new VisibleColsIterator(start, end, hidden);
+  }
+
+  @Override
+  public boolean isHidden(int c)
+  {
+    return false;
+  }
+
+}
diff --git a/src/jalview/datamodel/VisibleColsIterator.java b/src/jalview/datamodel/VisibleColsIterator.java
new file mode 100644 (file)
index 0000000..70de1e3
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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.datamodel;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator which iterates over all visible columns in an alignment
+ * 
+ * @author kmourao
+ *
+ */
+public class VisibleColsIterator implements Iterator<Integer>
+{
+  private int last;
+
+  private int current;
+
+  private int next;
+
+  private List<int[]> hidden;
+
+  private int lasthiddenregion;
+
+  public VisibleColsIterator(int firstcol, int lastcol,
+          HiddenColumns hiddenCols)
+  {
+    last = lastcol;
+    current = firstcol;
+    next = firstcol;
+    hidden = hiddenCols.getHiddenRegions();
+    lasthiddenregion = -1;
+
+    if (hidden != null)
+    {
+      int i = 0;
+      for (i = 0; i < hidden.size(); ++i)
+      {
+        if (current >= hidden.get(i)[0] && current <= hidden.get(i)[1])
+        {
+          // current is hidden, move to right
+          current = hidden.get(i)[1] + 1;
+          next = current;
+        }
+        if (current < hidden.get(i)[0])
+        {
+          break;
+        }
+      }
+      lasthiddenregion = i - 1;
+
+      for (i = hidden.size() - 1; i >= 0; --i)
+      {
+        if (last >= hidden.get(i)[0] && last <= hidden.get(i)[1])
+        {
+          // last is hidden, move to left
+          last = hidden.get(i)[0] - 1;
+        }
+        if (last > hidden.get(i)[1])
+        {
+          break;
+        }
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return next <= last;
+  }
+
+  @Override
+  public Integer next()
+  {
+    if (next > last)
+    {
+      throw new NoSuchElementException();
+    }
+    current = next;
+    if ((hidden != null) && (lasthiddenregion + 1 < hidden.size()))
+    {
+      // still some more hidden regions
+      if (next + 1 < hidden.get(lasthiddenregion + 1)[0])
+      {
+        // next+1 is still before the next hidden region
+        next++;
+      }
+      else if ((next + 1 >= hidden.get(lasthiddenregion + 1)[0])
+              && (next + 1 <= hidden.get(lasthiddenregion + 1)[1]))
+      {
+        // next + 1 is in the next hidden region
+        next = hidden.get(lasthiddenregion + 1)[1] + 1;
+        lasthiddenregion++;
+      }
+    }
+    else
+    {
+      // finished with hidden regions, just increment normally
+      next++;
+    }
+    return current;
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
+
diff --git a/src/jalview/datamodel/VisibleRowsCollection.java b/src/jalview/datamodel/VisibleRowsCollection.java
new file mode 100644 (file)
index 0000000..ce8e8da
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.datamodel;
+
+import jalview.api.AlignmentRowsCollectionI;
+
+import java.util.Iterator;
+
+public class VisibleRowsCollection implements AlignmentRowsCollectionI
+{
+  int start;
+
+  int end;
+
+  AlignmentI alignment;
+
+  public VisibleRowsCollection(int s, int e, AlignmentI al)
+  {
+    start = s;
+    end = e;
+    alignment = al;
+  }
+
+  @Override
+  public Iterator<Integer> iterator()
+  {
+    return new VisibleRowsIterator(start, end, alignment);
+  }
+
+  @Override
+  public boolean isHidden(int seq)
+  {
+    return false;
+  }
+
+  @Override
+  public SequenceI getSequence(int seq)
+  {
+    return alignment.getSequenceAtAbsoluteIndex(seq);
+  }
+}
+
diff --git a/src/jalview/datamodel/VisibleRowsIterator.java b/src/jalview/datamodel/VisibleRowsIterator.java
new file mode 100644 (file)
index 0000000..a9c782d
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.datamodel;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator which iterates over all visible rows in an alignment
+ * 
+ * @author kmourao
+ *
+ */
+public class VisibleRowsIterator implements Iterator<Integer>
+{
+  private int last;
+
+  private int current;
+
+  private int next;
+
+  private HiddenSequences hidden;
+
+  private AlignmentI al;
+
+  /**
+   * Create an iterator for all visible rows in the alignment
+   * 
+   * @param firstrow
+   *          absolute row index to start from
+   * @param lastrow
+   *          absolute row index to end at
+   * @param alignment
+   *          alignment to work with
+   */
+  public VisibleRowsIterator(int firstrow, int lastrow, AlignmentI alignment)
+  {
+    al = alignment;
+    current = firstrow;
+    last = lastrow;
+    hidden = al.getHiddenSequences();
+    while (last > current && hidden.isHidden(last))
+    {
+      last--;
+    }
+    current = firstrow;
+    while (current < last && hidden.isHidden(current))
+    {
+      current++;
+    }
+    next = current;
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return next <= last;
+  }
+
+  @Override
+  public Integer next()
+  {
+    if (next > last)
+    {
+      throw new NoSuchElementException();
+    }
+    current = next;
+    do
+    {
+      next++;
+    } while (next <= last && hidden.isHidden(next));
+    return current;
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
+
index 4033dcc..26bd142 100644 (file)
@@ -19,8 +19,10 @@ package jalview.ext.android;
 /*
  * Copied to Jalview September 2016.
  * Only the members of this class required for SparseIntArray were copied.
- * Method binarySearch(short[] array, int size, short value) added to support
+ * Change Log:
+ * Sep 2016: Method binarySearch(short[] array, int size, short value) added to support
  * SparseShortArray.
+ * Jan 2017: EMPTY_DOUBLES added
  */
 class ContainerHelpers
 {
@@ -28,6 +30,8 @@ class ContainerHelpers
 
   static final int[] EMPTY_INTS = new int[0];
 
+  static final double[] EMPTY_DOUBLES = new double[0];
+
   static final long[] EMPTY_LONGS = new long[0];
 
   static final Object[] EMPTY_OBJECTS = new Object[0];
diff --git a/src/jalview/ext/android/SparseDoubleArray.java b/src/jalview/ext/android/SparseDoubleArray.java
new file mode 100644 (file)
index 0000000..eaf059c
--- /dev/null
@@ -0,0 +1,443 @@
+package jalview.ext.android;
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * SparseDoubleArray map integers to doubles. Unlike a normal array of integers,
+ * there can be gaps in the indices. It is intended to be more memory efficient
+ * than using a HashMap to map Integer to Double, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra
+ * entry object for each mapping.
+ *
+ * <p>
+ * Note that this container keeps its mappings in an array data structure, using
+ * a binary search to find keys. The implementation is not intended to be
+ * appropriate for data structures that may contain large numbers of items. It
+ * is generally slower than a traditional HashMap, since lookups require a
+ * binary search and adds and removes require inserting and deleting entries in
+ * the array. For containers holding up to hundreds of items, the performance
+ * difference is not significant, less than 50%.
+ * </p>
+ *
+ * <p>
+ * It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)<code>.
+ * </p>
+ */
+
+/*
+ * Change log:
+ * Jan 2017 cloned from SparseIntArray for Jalview to support SparseMatrix
+ * - SparseDoubleArray(double[]) constructor added
+ * - multiply() added for more efficient multiply (or divide) of a value
+ */
+public class SparseDoubleArray implements Cloneable
+{
+  private int[] mKeys;
+
+  private double[] mValues;
+
+  private int mSize;
+
+  /**
+   * Creates a new SparseDoubleArray containing no mappings.
+   */
+  public SparseDoubleArray()
+  {
+    this(10);
+  }
+
+  /**
+   * Creates a new SparseDoubleArray containing no mappings that will not
+   * require any additional memory allocation to store the specified number of
+   * mappings. If you supply an initial capacity of 0, the sparse array will be
+   * initialized with a light-weight representation not requiring any additional
+   * array allocations.
+   */
+  public SparseDoubleArray(int initialCapacity)
+  {
+    if (initialCapacity == 0)
+    {
+      mKeys = ContainerHelpers.EMPTY_INTS;
+      mValues = ContainerHelpers.EMPTY_DOUBLES;
+    }
+    else
+    {
+      initialCapacity = idealDoubleArraySize(initialCapacity);
+      mKeys = new int[initialCapacity];
+      mValues = new double[initialCapacity];
+    }
+    mSize = 0;
+  }
+
+  /**
+   * Constructor given an array of double values; stores the non-zero values
+   * 
+   * @param row
+   */
+  public SparseDoubleArray(double[] row)
+  {
+    this();
+    for (int i = 0; i < row.length; i++)
+    {
+      if (row[i] != 0d)
+      {
+        put(i, row[i]);
+      }
+    }
+  }
+
+  @Override
+  public SparseDoubleArray clone()
+  {
+    SparseDoubleArray clone = null;
+    try
+    {
+      clone = (SparseDoubleArray) super.clone();
+      clone.mKeys = mKeys.clone();
+      clone.mValues = mValues.clone();
+    } catch (CloneNotSupportedException cnse)
+    {
+      /* ignore */
+    }
+    return clone;
+  }
+
+  /**
+   * Gets the value mapped from the specified key, or <code>0</code> if no such
+   * mapping has been made.
+   */
+  public double get(int key)
+  {
+    return get(key, 0d);
+  }
+
+  /**
+   * Gets the int mapped from the specified key, or the specified value if no
+   * such mapping has been made.
+   */
+  public double get(int key, double valueIfKeyNotFound)
+  {
+    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+    if (i < 0)
+    {
+      return valueIfKeyNotFound;
+    }
+    else
+    {
+      return mValues[i];
+    }
+  }
+
+  /**
+   * Removes the mapping from the specified key, if there was any.
+   */
+  public void delete(int key)
+  {
+    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+    if (i >= 0)
+    {
+      removeAt(i);
+    }
+  }
+
+  /**
+   * Removes the mapping at the given index.
+   */
+  public void removeAt(int index)
+  {
+    System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+    System.arraycopy(mValues, index + 1, mValues, index, mSize
+            - (index + 1));
+    mSize--;
+  }
+
+  /**
+   * Adds a mapping from the specified key to the specified value, replacing the
+   * previous mapping from the specified key if there was one.
+   */
+  public void put(int key, double value)
+  {
+    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+    if (i >= 0)
+    {
+      mValues[i] = value;
+    }
+    else
+    {
+      i = ~i;
+      if (mSize >= mKeys.length)
+      {
+        int n = idealDoubleArraySize(mSize + 1);
+        int[] nkeys = new int[n];
+        double[] nvalues = new double[n];
+        // Log.e("SparseDoubleArray", "grow " + mKeys.length + " to " + n);
+        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+        System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+        mKeys = nkeys;
+        mValues = nvalues;
+      }
+      if (mSize - i != 0)
+      {
+        // Log.e("SparseDoubleArray", "move " + (mSize - i));
+        System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+        System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+      }
+      mKeys[i] = key;
+      mValues[i] = value;
+      mSize++;
+    }
+  }
+
+  /**
+   * Returns the number of key-value mappings that this SparseDoubleArray
+   * currently stores.
+   */
+  public int size()
+  {
+    return mSize;
+  }
+
+  /**
+   * Given an index in the range <code>0...size()-1</code>, returns the key from
+   * the <code>index</code>th key-value mapping that this SparseDoubleArray
+   * stores.
+   *
+   * <p>
+   * The keys corresponding to indices in ascending order are guaranteed to be
+   * in ascending order, e.g., <code>keyAt(0)</code> will return the smallest
+   * key and <code>keyAt(size()-1)</code> will return the largest key.
+   * </p>
+   */
+  public int keyAt(int index)
+  {
+    return mKeys[index];
+  }
+
+  /**
+   * Given an index in the range <code>0...size()-1</code>, returns the value
+   * from the <code>index</code>th key-value mapping that this SparseDoubleArray
+   * stores.
+   *
+   * <p>
+   * The values corresponding to indices in ascending order are guaranteed to be
+   * associated with keys in ascending order, e.g., <code>valueAt(0)</code> will
+   * return the value associated with the smallest key and
+   * <code>valueAt(size()-1)</code> will return the value associated with the
+   * largest key.
+   * </p>
+   */
+  public double valueAt(int index)
+  {
+    return mValues[index];
+  }
+
+  /**
+   * Returns the index for which {@link #keyAt} would return the specified key,
+   * or a negative number if the specified key is not mapped.
+   */
+  public int indexOfKey(int key)
+  {
+    return ContainerHelpers.binarySearch(mKeys, mSize, key);
+  }
+
+  /**
+   * Returns an index for which {@link #valueAt} would return the specified key,
+   * or a negative number if no keys map to the specified value. Beware that
+   * this is a linear search, unlike lookups by key, and that multiple keys can
+   * map to the same value and this will find only one of them.
+   */
+  public int indexOfValue(double value)
+  {
+    for (int i = 0; i < mSize; i++)
+    {
+      if (mValues[i] == value)
+      {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Removes all key-value mappings from this SparseDoubleArray.
+   */
+  public void clear()
+  {
+    mSize = 0;
+  }
+
+  /**
+   * Puts a key/value pair into the array, optimizing for the case where the key
+   * is greater than all existing keys in the array.
+   */
+  public void append(int key, double value)
+  {
+    if (mSize != 0 && key <= mKeys[mSize - 1])
+    {
+      put(key, value);
+      return;
+    }
+    int pos = mSize;
+    if (pos >= mKeys.length)
+    {
+      int n = idealDoubleArraySize(pos + 1);
+      int[] nkeys = new int[n];
+      double[] nvalues = new double[n];
+      // Log.e("SparseDoubleArray", "grow " + mKeys.length + " to " + n);
+      System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+      System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+      mKeys = nkeys;
+      mValues = nvalues;
+    }
+    mKeys[pos] = key;
+    mValues[pos] = value;
+    mSize = pos + 1;
+  }
+
+  /**
+   * Created by analogy with
+   * com.android.internal.util.ArrayUtils#idealLongArraySize
+   * 
+   * @param i
+   * @return
+   */
+  public static int idealDoubleArraySize(int need)
+  {
+    return idealByteArraySize(need * 8) / 8;
+  }
+
+  /**
+   * Inlined here by copying from com.android.internal.util.ArrayUtils
+   * 
+   * @param i
+   * @return
+   */
+  public static int idealByteArraySize(int need)
+  {
+    for (int i = 4; i < 32; i++)
+    {
+      if (need <= (1 << i) - 12)
+      {
+        return (1 << i) - 12;
+      }
+    }
+
+    return need;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>
+   * This implementation composes a string by iterating over its mappings.
+   */
+  @Override
+  public String toString()
+  {
+    if (size() <= 0)
+    {
+      return "{}";
+    }
+    StringBuilder buffer = new StringBuilder(mSize * 28);
+    buffer.append('{');
+    for (int i = 0; i < mSize; i++)
+    {
+      if (i > 0)
+      {
+        buffer.append(", ");
+      }
+      int key = keyAt(i);
+      buffer.append(key);
+      buffer.append('=');
+      double value = valueAt(i);
+      buffer.append(value);
+    }
+    buffer.append('}');
+    return buffer.toString();
+  }
+
+  /**
+   * Method (copied from put) added for Jalview to efficiently increment a key's
+   * value if present, else add it with the given value. This avoids a double
+   * binary search (once to get the value, again to put the updated value).
+   * 
+   * @param key
+   * @oparam toAdd
+   * @return the new value for the key
+   */
+  public double add(int key, double toAdd)
+  {
+    double newValue = toAdd;
+    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+    if (i >= 0)
+    {
+      mValues[i] += toAdd;
+      newValue = mValues[i];
+    }
+    else
+    {
+      i = ~i;
+      if (mSize >= mKeys.length)
+      {
+        int n = idealDoubleArraySize(mSize + 1);
+        int[] nkeys = new int[n];
+        double[] nvalues = new double[n];
+        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+        System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+        mKeys = nkeys;
+        mValues = nvalues;
+      }
+      if (mSize - i != 0)
+      {
+        System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+        System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+      }
+      mKeys[i] = key;
+      mValues[i] = toAdd;
+      mSize++;
+    }
+    return newValue;
+  }
+
+  /**
+   * Method added for Jalview to efficiently multiply a key's value if present,
+   * else do nothing. This avoids a double binary search (once to get the value,
+   * again to put the updated value).
+   * 
+   * @param key
+   * @oparam toAdd
+   * @return the new value for the key
+   */
+  public double divide(int key, double divisor)
+  {
+    double newValue = 0d;
+    if (divisor == 0d)
+    {
+      return newValue;
+    }
+    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+    if (i >= 0)
+    {
+      mValues[i] /= divisor;
+      newValue = mValues[i];
+    }
+    return newValue;
+  }
+}
index 24e3e95..37c787b 100644 (file)
@@ -190,7 +190,22 @@ public class EnsemblGene extends EnsemblSeqProxy
           geneIds.add(geneId);
         }
       }
+      else if (isProteinIdentifier(acc))
+      {
+        String tscriptId = new EnsemblLookup(getDomain()).getParent(acc);
+        if (tscriptId != null)
+        {
+          String geneId = new EnsemblLookup(getDomain())
+                  .getParent(tscriptId);
 
+          if (geneId != null && !geneIds.contains(geneId))
+          {
+            geneIds.add(geneId);
+          }
+        }
+        // NOTE - acc is lost if it resembles an ENS.+ ID but isn't actually
+        // resolving to one... e.g. ENSMICP00000009241
+      }
       /*
        * if given a gene or other external name, lookup and fetch 
        * the corresponding gene for all model organisms 
index ab3b197..2437588 100644 (file)
@@ -57,13 +57,13 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   /*
    * update these constants when Jalview has been checked / updated for
-   * changes to Ensembl REST API
+   * changes to Ensembl REST API (ref JAL-2105)
    * @see https://github.com/Ensembl/ensembl-rest/wiki/Change-log
    * @see http://rest.ensembl.org/info/rest?content-type=application/json
    */
-  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "4.8";
+  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "5.0";
 
-  private static final String LATEST_ENSEMBL_REST_VERSION = "4.8";
+  private static final String LATEST_ENSEMBL_REST_VERSION = "5.0";
 
   private static final String REST_CHANGE_LOG = "https://github.com/Ensembl/ensembl-rest/wiki/Change-log";
 
@@ -76,6 +76,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   private final static long VERSION_RETEST_INTERVAL = 1000L * 3600; // 1 hr
 
+  private static final Regex PROTEIN_REGEX = new Regex(
+          "(ENS)([A-Z]{3}|)P[0-9]{11}$");
+
   private static final Regex TRANSCRIPT_REGEX = new Regex(
           "(ENS)([A-Z]{3}|)T[0-9]{11}$");
 
@@ -125,6 +128,18 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   /**
    * Answers true if the query matches the regular expression pattern for an
+   * Ensembl protein stable identifier
+   * 
+   * @param query
+   * @return
+   */
+  public boolean isProteinIdentifier(String query)
+  {
+    return query == null ? false : PROTEIN_REGEX.search(query);
+  }
+
+  /**
+   * Answers true if the query matches the regular expression pattern for an
    * Ensembl gene stable identifier
    * 
    * @param query
@@ -513,9 +528,11 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
       boolean laterVersion = StringUtils.compareVersions(version, expected) == 1;
       if (laterVersion)
       {
-        System.err.println(String.format(
-                "Expected %s REST version %s but found %s, see %s",
-                getDbSource(), expected, version, REST_CHANGE_LOG));
+        System.err
+                .println(String
+                        .format("EnsemblRestClient expected %s REST version %s but found %s, see %s",
+                                getDbSource(), expected, version,
+                                REST_CHANGE_LOG));
       }
       info.restVersion = version;
     } catch (Throwable t)
index 732bc0f..233707b 100644 (file)
@@ -38,6 +38,7 @@ import jalview.io.gff.SequenceOntologyI;
 import jalview.util.Comparison;
 import jalview.util.DBRefUtils;
 import jalview.util.MapList;
+import jalview.util.RangeComparator;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
@@ -110,26 +111,6 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
   }
 
   /**
-   * A comparator to sort ranges into ascending start position order
-   */
-  private class RangeSorter implements Comparator<int[]>
-  {
-    boolean forwards;
-
-    RangeSorter(boolean forward)
-    {
-      forwards = forward;
-    }
-
-    @Override
-    public int compare(int[] o1, int[] o2)
-    {
-      return (forwards ? 1 : -1) * Integer.compare(o1[0], o2[0]);
-    }
-
-  }
-
-  /**
    * Default constructor (to use rest.ensembl.org)
    */
   public EnsemblSeqProxy()
@@ -626,7 +607,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
      * a final sort is needed since Ensembl returns CDS sorted within source
      * (havana / ensembl_havana)
      */
-    Collections.sort(regions, new RangeSorter(direction == 1));
+    Collections.sort(regions, new RangeComparator(direction == 1));
 
     List<int[]> to = Arrays.asList(new int[] { start,
         start + mappedLength - 1 });
index bf80831..5de554b 100644 (file)
  */
 package jalview.ext.jmol;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
@@ -34,6 +35,7 @@ import jalview.structure.AtomSpec;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.MessageManager;
 
 import java.awt.Color;
 import java.awt.Container;
@@ -43,6 +45,7 @@ import java.io.File;
 import java.net.URL;
 import java.security.AccessControlException;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -161,7 +164,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public void closeViewer()
   {
     // remove listeners for all structures in viewer
-    getSsm().removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getStructureFiles());
     viewer.dispose();
     lastCommand = null;
     viewer = null;
@@ -219,30 +222,19 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    *          TODO
    */
   public void superposeStructures(AlignmentI alignment, int refStructure,
-          ColumnSelection hiddenCols)
+          HiddenColumns hiddenCols)
   {
     superposeStructures(new AlignmentI[] { alignment },
             new int[] { refStructure },
-            new ColumnSelection[] { hiddenCols });
+ new HiddenColumns[] { hiddenCols });
   }
 
   /**
-   * Construct and send a command to align structures against a reference
-   * structure, based on one or more sequence alignments
-   * 
-   * @param _alignment
-   *          an array of alignments to process
-   * @param _refStructure
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param _hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * {@inheritDoc}
    */
   @Override
-  public void superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  public String superposeStructures(AlignmentI[] _alignment,
+          int[] _refStructure, HiddenColumns[] _hiddenCols)
   {
     while (viewer.isScriptExecuting())
     {
@@ -258,10 +250,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
      * get the distinct structure files modelled
      * (a file with multiple chains may map to multiple sequences)
      */
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
     if (!waitForFileLoad(files))
     {
-      return;
+      return null;
     }
 
     StringBuilder selectioncom = new StringBuilder(256);
@@ -277,6 +269,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       nSeconds = " " + (2.0 / files.length) + " ";
       // if (nSeconds).substring(0,5)+" ";
     }
+
     // see JAL-1345 - should really automatically turn off the animation for
     // large numbers of structures, but Jmol doesn't seem to allow that.
     // nSeconds = " ";
@@ -285,7 +278,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     {
       int refStructure = _refStructure[a];
       AlignmentI alignment = _alignment[a];
-      ColumnSelection hiddenCols = _hiddenCols[a];
+      HiddenColumns hiddenCols = _hiddenCols[a];
       if (a > 0
               && selectioncom.length() > 0
               && !selectioncom.substring(selectioncom.length() - 1).equals(
@@ -302,14 +295,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       }
 
       /*
-       * 'matched' array will hold 'true' for visible alignment columns where
+       * 'matched' bit j will be set for visible alignment columns j where
        * all sequences have a residue with a mapping to the PDB structure
        */
-      // TODO could use a BitSet for matched
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
+      BitSet matched = new BitSet();
+      for (int m = 0; m < alignment.getWidth(); m++)
       {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
       }
 
       SuperposeData[] structures = new SuperposeData[files.length];
@@ -334,17 +329,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       }
 
       String[] selcom = new String[files.length];
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
+      int nmatched = matched.cardinality();
       if (nmatched < 4)
       {
-        // TODO: bail out here because superposition illdefined?
+        return (MessageManager.formatMessage(
+"label.insufficient_residues",
+                nmatched));
       }
 
       /*
@@ -359,35 +349,35 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           boolean run = false;
           StringBuilder molsel = new StringBuilder();
           molsel.append("{");
-          for (int r = 0; r < matched.length; r++)
+
+          int nextColumnMatch = matched.nextSetBit(0);
+          while (nextColumnMatch != -1)
           {
-            if (matched[r])
+            int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch];
+            if (lpos != pdbResNo - 1)
             {
-              int pdbResNo = structures[pdbfnum].pdbResNo[r];
-              if (lpos != pdbResNo - 1)
+              // discontinuity
+              if (lpos != -1)
               {
-                // discontinuity
-                if (lpos != -1)
-                {
-                  molsel.append(lpos);
-                  molsel.append(chainCd);
-                  molsel.append("|");
-                }
-                run = false;
+                molsel.append(lpos);
+                molsel.append(chainCd);
+                molsel.append("|");
               }
-              else
+              run = false;
+            }
+            else
+            {
+              // continuous run - and lpos >-1
+              if (!run)
               {
-                // continuous run - and lpos >-1
-                if (!run)
-                {
-                  // at the beginning, so add dash
-                  molsel.append(lpos);
-                  molsel.append("-");
-                }
-                run = true;
+                // at the beginning, so add dash
+                molsel.append(lpos);
+                molsel.append("-");
               }
-              lpos = pdbResNo;
+              run = true;
             }
+            lpos = pdbResNo;
+            nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
           }
           /*
            * add final selection phrase
@@ -473,6 +463,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
               + selectioncom.toString() + "); cartoons; ");
       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
     }
+
+    return null;
   }
 
   public void evalStateCommand(String command)
@@ -507,17 +499,15 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   /**
    * @param files
    * @param sr
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   @Override
   protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
   {
     return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, fr, alignment);
+            getSequence(), sr, viewPanel);
   }
 
   /**
@@ -587,7 +577,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   private int getModelNum(String modelFileName)
   {
-    String[] mfn = getPdbFile();
+    String[] mfn = getStructureFiles();
     if (mfn == null)
     {
       return -1;
@@ -611,8 +601,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   // ////////////////////////////////
   // /StructureListener
-  @Override
-  public synchronized String[] getPdbFile()
+  // @Override
+  public synchronized String[] getPdbFilex()
   {
     if (viewer == null)
     {
@@ -677,6 +667,32 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return modelFileNames;
   }
 
+  @Override
+  public synchronized String[] getStructureFiles()
+  {
+    List<String> mset = new ArrayList<String>();
+    if (viewer == null)
+    {
+      return new String[0];
+    }
+
+    if (modelFileNames == null)
+    {
+      int modelCount = viewer.ms.mc;
+      String filePath = null;
+      for (int i = 0; i < modelCount; ++i)
+      {
+        filePath = viewer.ms.getModelFileName(i);
+        if (!mset.contains(filePath))
+        {
+          mset.add(filePath);
+        }
+      }
+      modelFileNames = mset.toArray(new String[mset.size()]);
+    }
+
+    return modelFileNames;
+  }
   /**
    * map from string to applet
    */
@@ -1045,7 +1061,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     chainNames = new ArrayList<String>();
     chainFile = new Hashtable<String, String>();
     boolean notifyLoaded = false;
-    String[] modelfilenames = getPdbFile();
+    String[] modelfilenames = getStructureFiles();
     // first check if we've lost any structures
     if (oldmodels != null && oldmodels.length > 0)
     {
index d5676c5..3e7ca59 100644 (file)
  */
 package jalview.ext.jmol;
 
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Routines for generating Jmol commands for Jalview/Jmol binding another
@@ -50,11 +55,15 @@ public class JmolCommands
    */
   public static StructureMappingcommandSet[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
-
-    ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+    AlignmentI al = viewport.getAlignment();
+    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
 
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
@@ -74,9 +83,9 @@ public class JmolCommands
         for (int sp, m = 0; m < mapping.length; m++)
         {
           if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+                  && (sp = al.findIndex(sequence[pdbfnum][s])) > -1)
           {
-            SequenceI asp = alignment.getSequenceAt(sp);
+            SequenceI asp = al.getSequenceAt(sp);
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
@@ -93,12 +102,17 @@ public class JmolCommands
 
               lastPos = pos;
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+              Color col = sr.getResidueColour(sequence[pdbfnum][s], r,
+                      finder);
 
-              if (fr != null)
+              /*
+               * shade hidden regions darker
+               */
+              if (!cs.isVisible(r))
               {
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
+                col = Color.GRAY;
               }
+
               String newSelcom = (mapping[m].getChain() != " " ? ":"
                       + mapping[m].getChain() : "")
                       + "/"
diff --git a/src/jalview/ext/jmol/JmolParser.java~ b/src/jalview/ext/jmol/JmolParser.java~
new file mode 100644 (file)
index 0000000..9b32846
--- /dev/null
@@ -0,0 +1,670 @@
+/*
+ * 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.ext.jmol;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.io.FileParse;
+import jalview.io.StructureFile;
+import jalview.schemes.ResidueProperties;
+import jalview.structure.StructureImportSettings;
+import jalview.util.Format;
+import jalview.util.MessageManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javajs.awt.Dimension;
+
+import org.jmol.api.JmolStatusListener;
+import org.jmol.api.JmolViewer;
+import org.jmol.c.CBK;
+import org.jmol.c.STR;
+import org.jmol.modelset.ModelSet;
+import org.jmol.viewer.Viewer;
+
+import MCview.Atom;
+import MCview.PDBChain;
+import MCview.Residue;
+
+/**
+ * Import and process files with Jmol for file like PDB, mmCIF
+ * 
+ * @author jprocter
+ * 
+ */
+public class JmolParser extends StructureFile implements JmolStatusListener
+{
+  Viewer viewer = null;
+
+  public JmolParser(String inFile, String type) throws IOException
+  {
+    super(inFile, type);
+  }
+
+  public JmolParser(FileParse fp) throws IOException
+  {
+    super(fp);
+  }
+
+  public JmolParser()
+  {
+  }
+
+  /**
+   * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
+   * resulting object model to generate Jalview-style sequences, with secondary
+   * structure annotation added where available (i.e. where it has been computed
+   * by Jmol using DSSP).
+   * 
+   * @see jalview.io.AlignFile#parse()
+   */
+  @Override
+  public void parse() throws IOException
+  {
+    setChains(new Vector<PDBChain>());
+    Viewer jmolModel = getJmolData();
+    jmolModel.openReader(getDataName(), getDataName(), getReader());
+    waitForScript(jmolModel);
+
+    /*
+     * Convert one or more Jmol Model objects to Jalview sequences
+     */
+    if (jmolModel.ms.mc > 0)
+    {
+      // ideally we do this
+      // try
+      // {
+      // setStructureFileType(jmolModel.evalString("show _fileType"));
+      // } catch (Exception q)
+      // {
+      // }
+      // ;
+      // instead, we distinguish .cif from non-.cif by filename
+      setStructureFileType(getDataName().toLowerCase().endsWith(".cif") ? PDBEntry.Type.MMCIF
+              .toString() : "PDB");
+
+      transformJmolModelToJalview(jmolModel.ms);
+    }
+  }
+
+  /**
+   * create a headless jmol instance for dataprocessing
+   * 
+   * @return
+   */
+  private Viewer getJmolData()
+  {
+    if (viewer == null)
+    {
+      try
+      {
+        /*
+         * params -o (output to sysout) -n (nodisplay) -x (exit when finished)
+         * see http://wiki.jmol.org/index.php/Jmol_Application
+         */
+        viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
+                null, "-x -o -n", this);
+        // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
+        viewer.setBooleanProperty("defaultStructureDSSP", true);
+      } catch (ClassCastException x)
+      {
+        throw new Error(MessageManager.formatMessage(
+                "error.jmol_version_not_compatible_with_jalview_version",
+                new String[] { JmolViewer.getJmolVersion() }), x);
+      }
+    }
+    return viewer;
+  }
+
+  public void transformJmolModelToJalview(ModelSet ms) throws IOException
+  {
+    try
+    {
+      String lastID = "";
+      List<SequenceI> rna = new ArrayList<SequenceI>();
+      List<SequenceI> prot = new ArrayList<SequenceI>();
+      PDBChain tmpchain;
+      String pdbId = (String) ms.getInfo(0, "title");
+
+      if (pdbId == null)
+      {
+        setId(safeName(getDataName()));
+        setPDBIdAvailable(false);
+      }
+      else
+      {
+        setId(pdbId);
+        setPDBIdAvailable(true);
+      }
+      List<Atom> significantAtoms = convertSignificantAtoms(ms);
+      for (Atom tmpatom : significantAtoms)
+      {
+        try
+        {
+          tmpchain = findChain(tmpatom.chain);
+          if (tmpatom.resNumIns.trim().equals(lastID))
+          {
+            // phosphorylated protein - seen both CA and P..
+            continue;
+          }
+          tmpchain.atoms.addElement(tmpatom);
+        } catch (Exception e)
+        {
+          tmpchain = new PDBChain(getId(), tmpatom.chain);
+          getChains().add(tmpchain);
+          tmpchain.atoms.addElement(tmpatom);
+        }
+        lastID = tmpatom.resNumIns.trim();
+      }
+      xferSettings();
+
+      makeResidueList();
+      makeCaBondList();
+
+<<<<<<< HEAD
+=======
+      if (getId() == null)
+      {
+        // always use resource name, not the hardwired file
+        // Does the value of ID get used ? Behaviour needs to be
+        // documented and tested
+        setId(getDataName());
+      }
+>>>>>>> spike/JAL-2040_JAL-2137_phyre2
+      for (PDBChain chain : getChains())
+      {
+        SequenceI chainseq = postProcessChain(chain);
+        if (isRNA(chainseq))
+        {
+          rna.add(chainseq);
+        }
+        else
+        {
+          prot.add(chainseq);
+        }
+
+        if (StructureImportSettings.isProcessSecondaryStructure())
+        {
+          createAnnotation(chainseq, chain, ms.at);
+        }
+      }
+    } catch (OutOfMemoryError er)
+    {
+      System.out
+              .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
+      throw new IOException(
+              MessageManager
+                      .getString("exception.outofmemory_loading_mmcif_file"));
+    }
+  }
+
+  private List<Atom> convertSignificantAtoms(ModelSet ms)
+  {
+    List<Atom> significantAtoms = new ArrayList<Atom>();
+    HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
+    org.jmol.modelset.Atom prevAtom = null;
+    for (org.jmol.modelset.Atom atom : ms.at)
+    {
+      if (atom.getAtomName().equalsIgnoreCase("CA")
+              || atom.getAtomName().equalsIgnoreCase("P"))
+      {
+        if (!atomValidated(atom, prevAtom, chainTerMap))
+        {
+          continue;
+        }
+        Atom curAtom = new Atom(atom.x, atom.y, atom.z);
+        curAtom.atomIndex = atom.getIndex();
+        curAtom.chain = atom.getChainIDStr();
+        curAtom.insCode = atom.group.getInsertionCode() == '\000' ? ' '
+                : atom.group.getInsertionCode();
+        curAtom.name = atom.getAtomName();
+        curAtom.number = atom.getAtomNumber();
+        curAtom.resName = atom.getGroup3(true);
+        curAtom.resNumber = atom.getResno();
+        curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
+                .getIndex()] : Float.valueOf(atom.getOccupancy100());
+        String fmt = new Format("%4i").form(curAtom.resNumber);
+        curAtom.resNumIns = (fmt + curAtom.insCode);
+        curAtom.tfactor = atom.getBfactor100() / 100f;
+        curAtom.type = 0;
+        // significantAtoms.add(curAtom);
+        // ignore atoms from subsequent models
+        if (!significantAtoms.contains(curAtom))
+        {
+          significantAtoms.add(curAtom);
+        }
+        prevAtom = atom;
+      }
+    }
+    return significantAtoms;
+  }
+
+  private boolean atomValidated(org.jmol.modelset.Atom curAtom,
+          org.jmol.modelset.Atom prevAtom,
+          HashMap<String, org.jmol.modelset.Atom> chainTerMap)
+  {
+    // System.out.println("Atom: " + curAtom.getAtomNumber()
+    // + "   Last atom index " + curAtom.group.lastAtomIndex);
+    if (chainTerMap == null || prevAtom == null)
+    {
+      return true;
+    }
+    String curAtomChId = curAtom.getChainIDStr();
+    String prevAtomChId = prevAtom.getChainIDStr();
+    // new chain encoutered
+    if (!prevAtomChId.equals(curAtomChId))
+    {
+      // On chain switch add previous chain termination to xTerMap if not exists
+      if (!chainTerMap.containsKey(prevAtomChId))
+      {
+        chainTerMap.put(prevAtomChId, prevAtom);
+      }
+      // if current atom belongs to an already terminated chain and the resNum
+      // diff < 5 then mark as valid and update termination Atom
+      if (chainTerMap.containsKey(curAtomChId))
+      {
+        if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
+        {
+          return false;
+        }
+        if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
+        {
+          chainTerMap.put(curAtomChId, curAtom);
+          return true;
+        }
+        return false;
+      }
+    }
+    // atom with previously terminated chain encountered
+    else if (chainTerMap.containsKey(curAtomChId))
+    {
+      if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
+      {
+        return false;
+      }
+      if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
+      {
+        chainTerMap.put(curAtomChId, curAtom);
+        return true;
+      }
+      return false;
+    }
+    // HETATM with resNum jump > 2
+    return !(curAtom.isHetero() && ((curAtom.getResno() - prevAtom
+            .getResno()) > 2));
+  }
+
+  private void createAnnotation(SequenceI sequence, PDBChain chain,
+          org.jmol.modelset.Atom[] jmolAtoms)
+  {
+    char[] secstr = new char[sequence.getLength()];
+    char[] secstrcode = new char[sequence.getLength()];
+
+    // Ensure Residue size equals Seq size
+    if (chain.residues.size() != sequence.getLength())
+    {
+      return;
+    }
+    int annotIndex = 0;
+    for (Residue residue : chain.residues)
+    {
+      Atom repAtom = residue.getAtoms().get(0);
+      STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
+              .getProteinStructureSubType();
+      setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
+              secstrcode);
+      ++annotIndex;
+    }
+    addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
+            secstrcode, chain.id, sequence.getStart());
+  }
+
+  /**
+   * Helper method that adds an AlignmentAnnotation for secondary structure to
+   * the sequence, provided at least one secondary structure prediction has been
+   * made
+   * 
+   * @param modelTitle
+   * @param seq
+   * @param secstr
+   * @param secstrcode
+   * @param chainId
+   * @param firstResNum
+   * @return
+   */
+  protected void addSecondaryStructureAnnotation(String modelTitle,
+          SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
+          int firstResNum)
+  {
+    char[] seq = sq.getSequence();
+    boolean ssFound = false;
+    Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
+    for (int p = 0; p < seq.length; p++)
+    {
+      if (secstr[p] >= 'A' && secstr[p] <= 'z')
+      {
+        try
+        {
+          asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
+                  secstrcode[p], Float.NaN);
+          ssFound = true;
+        } catch (Exception e)
+        {
+          // e.printStackTrace();
+        }
+      }
+    }
+
+    if (ssFound)
+    {
+      String mt = modelTitle == null ? getDataName() : modelTitle;
+      mt += chainId;
+      AlignmentAnnotation ann = new AlignmentAnnotation(
+              "Secondary Structure", "Secondary Structure for " + mt,
+              asecstr);
+      ann.belowAlignment = true;
+      ann.visible = true;
+      ann.autoCalculated = false;
+      ann.setCalcId(getClass().getName());
+      ann.adjustForAlignment();
+      ann.validateRangeAndDisplay();
+      annotations.add(ann);
+      sq.addAlignmentAnnotation(ann);
+    }
+  }
+
+  private void waitForScript(Viewer jmd)
+  {
+    while (jmd.isScriptExecuting())
+    {
+      try
+      {
+        Thread.sleep(50);
+
+      } catch (InterruptedException x)
+      {
+      }
+    }
+  }
+
+  /**
+   * Convert Jmol's secondary structure code to Jalview's, and stored it in the
+   * secondary structure arrays at the given sequence position
+   * 
+   * @param proteinStructureSubType
+   * @param pos
+   * @param secstr
+   * @param secstrcode
+   */
+  protected void setSecondaryStructure(STR proteinStructureSubType,
+          int pos, char[] secstr, char[] secstrcode)
+  {
+    switch (proteinStructureSubType)
+    {
+    case HELIX310:
+      secstr[pos] = '3';
+      break;
+    case HELIX:
+    case HELIXALPHA:
+      secstr[pos] = 'H';
+      break;
+    case HELIXPI:
+      secstr[pos] = 'P';
+      break;
+    case SHEET:
+      secstr[pos] = 'E';
+      break;
+    default:
+      secstr[pos] = 0;
+    }
+
+    switch (proteinStructureSubType)
+    {
+    case HELIX310:
+    case HELIXALPHA:
+    case HELIXPI:
+    case HELIX:
+      secstrcode[pos] = 'H';
+      break;
+    case SHEET:
+      secstrcode[pos] = 'E';
+      break;
+    default:
+      secstrcode[pos] = 0;
+    }
+  }
+
+  /**
+   * Convert any non-standard peptide codes to their standard code table
+   * equivalent. (Initial version only does Selenomethionine MSE->MET.)
+   * 
+   * @param threeLetterCode
+   * @param seq
+   * @param pos
+   */
+  protected void replaceNonCanonicalResidue(String threeLetterCode,
+          char[] seq, int pos)
+  {
+    String canonical = ResidueProperties
+            .getCanonicalAminoAcid(threeLetterCode);
+    if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
+    {
+      seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
+    }
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public String print()
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented
+   */
+  @Override
+  public void setCallbackFunction(String callbackType,
+          String callbackFunction)
+  {
+  }
+
+  @Override
+  public void notifyCallback(CBK cbType, Object[] data)
+  {
+    String strInfo = (data == null || data[1] == null ? null : data[1]
+            .toString());
+    switch (cbType)
+    {
+    case ECHO:
+      sendConsoleEcho(strInfo);
+      break;
+    case SCRIPT:
+      notifyScriptTermination((String) data[2],
+              ((Integer) data[3]).intValue());
+      break;
+    case MEASURE:
+      String mystatus = (String) data[3];
+      if (mystatus.indexOf("Picked") >= 0
+              || mystatus.indexOf("Sequence") >= 0)
+      {
+        // Picking mode
+        sendConsoleMessage(strInfo);
+      }
+      else if (mystatus.indexOf("Completed") >= 0)
+      {
+        sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
+                strInfo.length() - 1));
+      }
+      break;
+    case MESSAGE:
+      sendConsoleMessage(data == null ? null : strInfo);
+      break;
+    case PICK:
+      sendConsoleMessage(strInfo);
+      break;
+    default:
+      break;
+    }
+  }
+
+  String lastConsoleEcho = "";
+
+  private void sendConsoleEcho(String string)
+  {
+    lastConsoleEcho += string;
+    lastConsoleEcho += "\n";
+  }
+
+  String lastConsoleMessage = "";
+
+  private void sendConsoleMessage(String string)
+  {
+    lastConsoleMessage += string;
+    lastConsoleMessage += "\n";
+  }
+
+  int lastScriptTermination = -1;
+
+  String lastScriptMessage = "";
+
+  private void notifyScriptTermination(String string, int intValue)
+  {
+    lastScriptMessage += string;
+    lastScriptMessage += "\n";
+    lastScriptTermination = intValue;
+  }
+
+  @Override
+  public boolean notifyEnabled(CBK callbackPick)
+  {
+    switch (callbackPick)
+    {
+    case MESSAGE:
+    case SCRIPT:
+    case ECHO:
+    case LOADSTRUCT:
+    case ERROR:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public String eval(String strEval)
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public float[][] functionXY(String functionName, int x, int y)
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public String createImage(String fileName, String imageType,
+          Object text_or_bytes, int quality)
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public Map<String, Object> getRegistryInfo()
+  {
+    return null;
+  }
+
+  /**
+   * Not implemented
+   */
+  @Override
+  public void showUrl(String url)
+  {
+  }
+
+  /**
+   * Not implemented - returns null
+   */
+  @Override
+  public Dimension resizeInnerPanel(String data)
+  {
+    return null;
+  }
+
+  @Override
+  public Map<String, Object> getJSpecViewProperty(String arg0)
+  {
+    return null;
+  }
+
+  public boolean isPredictSecondaryStructure()
+  {
+    return predictSecondaryStructure;
+  }
+
+  public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
+  {
+    this.predictSecondaryStructure = predictSecondaryStructure;
+  }
+
+  public boolean isVisibleChainAnnotation()
+  {
+    return visibleChainAnnotation;
+  }
+
+  public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
+  {
+    this.visibleChainAnnotation = visibleChainAnnotation;
+  }
+
+}
diff --git a/src/jalview/ext/rbvi/chimera/AtomSpecModel.java b/src/jalview/ext/rbvi/chimera/AtomSpecModel.java
new file mode 100644 (file)
index 0000000..d62cc3c
--- /dev/null
@@ -0,0 +1,180 @@
+package jalview.ext.rbvi.chimera;
+
+import jalview.util.RangeComparator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class to model a Chimera atomspec pattern, for example
+ * 
+ * <pre>
+ * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
+ * </pre>
+ * 
+ * where
+ * <ul>
+ * <li>#0 is a model number</li>
+ * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
+ * <li>.A is a chain identifier</li>
+ * <li>residue ranges are separated by comma</li>
+ * <li>atomspecs for distinct models are separated by | (or)</li>
+ * </ul>
+ * 
+ * <pre>
+ * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
+ * </pre>
+ */
+public class AtomSpecModel
+{
+  private Map<Integer, Map<String, List<int[]>>> atomSpec;
+
+  /**
+   * Constructor
+   */
+  public AtomSpecModel()
+  {
+    atomSpec = new TreeMap<Integer, Map<String, List<int[]>>>();
+  }
+
+  /**
+   * Adds one contiguous range to this atom spec
+   * 
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  public void addRange(int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour and model
+     */
+    Map<String, List<int[]>> modelData = atomSpec.get(model);
+    if (modelData == null)
+    {
+      atomSpec.put(model, modelData = new TreeMap<String, List<int[]>>());
+    }
+
+    /*
+     * Get/initialize map of data for colour, model and chain
+     */
+    List<int[]> chainData = modelData.get(chain);
+    if (chainData == null)
+    {
+      chainData = new ArrayList<int[]>();
+      modelData.put(chain, chainData);
+    }
+
+    /*
+     * Add the start/end positions
+     */
+    chainData.add(new int[] { startPos, endPos });
+    // TODO add intelligently, using a RangeList class
+  }
+
+  /**
+   * Returns the range(s) formatted as a Chimera atomspec
+   * 
+   * @return
+   */
+  public String getAtomSpec()
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModel = true;
+    for (Integer model : atomSpec.keySet())
+    {
+      if (!firstModel)
+      {
+        sb.append("|");
+      }
+      firstModel = false;
+      sb.append("#").append(model).append(":");
+
+      boolean firstPositionForModel = true;
+      final Map<String, List<int[]>> modelData = atomSpec.get(model);
+
+      for (String chain : modelData.keySet())
+      {
+        chain = chain.trim();
+
+        List<int[]> rangeList = modelData.get(chain);
+
+        /*
+         * sort ranges into ascending start position order
+         */
+        Collections.sort(rangeList, new RangeComparator(true));
+
+        int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+        int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
+
+        Iterator<int[]> iterator = rangeList.iterator();
+        while (iterator.hasNext())
+        {
+          int[] range = iterator.next();
+          if (range[0] <= end + 1)
+          {
+            /*
+             * range overlaps or is contiguous with the last one
+             * - so just extend the end position, and carry on
+             * (unless this is the last in the list)
+             */
+            end = Math.max(end, range[1]);
+          }
+          else
+          {
+            /*
+             * we have a break so append the last range
+             */
+            appendRange(sb, start, end, chain, firstPositionForModel);
+            firstPositionForModel = false;
+            start = range[0];
+            end = range[1];
+          }
+        }
+
+        /*
+         * and append the last range
+         */
+        if (!rangeList.isEmpty())
+        {
+          appendRange(sb, start, end, chain, firstPositionForModel);
+          firstPositionForModel = false;
+        }
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * @param sb
+   * @param start
+   * @param end
+   * @param chain
+   * @param firstPositionForModel
+   */
+  protected void appendRange(StringBuilder sb, int start, int end,
+          String chain, boolean firstPositionForModel)
+  {
+    if (!firstPositionForModel)
+    {
+      sb.append(",");
+    }
+    if (end == start)
+    {
+      sb.append(start);
+    }
+    else
+    {
+      sb.append(start).append("-").append(end);
+    }
+    if (chain.length() > 0)
+    {
+      sb.append(".").append(chain);
+    }
+  }
+}
index 8f6b2f1..62aaa1c 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
@@ -32,11 +37,10 @@ import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
 
 /**
  * Routines for generating Chimera commands for Jalview/Chimera binding
@@ -47,26 +51,32 @@ import java.util.TreeMap;
 public class ChimeraCommands
 {
 
+  public static final String NAMESPACE_PREFIX = "jv_";
+
   /**
-   * utility to construct the commands to colour chains by the given alignment
-   * for passing to Chimera
-   * 
-   * @returns Object[] { Object[] { <model being coloured>,
+   * Constructs Chimera commands to colour residues as per the Jalview alignment
    * 
+   * @param ssm
+   * @param files
+   * @param sequence
+   * @param sr
+   * @param fr
+   * @param viewPanel
+   * @return
    */
   public static StructureMappingcommandSet[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
-    Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> colourMap = buildColoursMap(
-            ssm, files, sequence, sr, fr, alignment);
+    Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
+            sequence, sr, viewPanel);
 
     List<String> colourCommands = buildColourCommands(colourMap);
 
     StructureMappingcommandSet cs = new StructureMappingcommandSet(
             ChimeraCommands.class, null,
-            colourCommands.toArray(new String[0]));
+            colourCommands.toArray(new String[colourCommands.size()]));
 
     return new StructureMappingcommandSet[] { cs };
   }
@@ -76,18 +86,18 @@ public class ChimeraCommands
    * 'color' commands (one per distinct colour used). The format of each command
    * is
    * 
-   * <blockquote> color colorname #modelnumber:range.chain e.g. color #00ff00
-   * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
-   * 
-   * @see http 
-   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec
-   *      .html </pre>
+   * <pre>
+   * <blockquote> 
+   * color colorname #modelnumber:range.chain 
+   * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * </blockquote>
+   * </pre>
    * 
    * @param colourMap
    * @return
    */
   protected static List<String> buildColourCommands(
-          Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> colourMap)
+          Map<Object, AtomSpecModel> colourMap)
   {
     /*
      * This version concatenates all commands into a single String (semi-colon
@@ -97,8 +107,9 @@ public class ChimeraCommands
     List<String> commands = new ArrayList<String>();
     StringBuilder sb = new StringBuilder(256);
     boolean firstColour = true;
-    for (Color colour : colourMap.keySet())
+    for (Object key : colourMap.keySet())
     {
+      Color colour = (Color) key;
       String colourCode = ColorUtils.toTkCode(colour);
       if (!firstColour)
       {
@@ -106,55 +117,68 @@ public class ChimeraCommands
       }
       sb.append("color ").append(colourCode).append(" ");
       firstColour = false;
-      boolean firstModelForColour = true;
-      final Map<Integer, Map<String, List<int[]>>> colourData = colourMap
-              .get(colour);
-      for (Integer model : colourData.keySet())
+      final AtomSpecModel colourData = colourMap.get(colour);
+      sb.append(colourData.getAtomSpec());
+    }
+    commands.add(sb.toString());
+    return commands;
+  }
+
+  /**
+   * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
+   * builds a Chimera format atom spec
+   * 
+   * @param modelAndChainRanges
+   */
+  protected static String getAtomSpec(
+          Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModelForColour = true;
+    for (Integer model : modelAndChainRanges.keySet())
+    {
+      boolean firstPositionForModel = true;
+      if (!firstModelForColour)
       {
-        boolean firstPositionForModel = true;
-        if (!firstModelForColour)
-        {
-          sb.append("|");
-        }
-        firstModelForColour = false;
-        sb.append("#").append(model).append(":");
+        sb.append("|");
+      }
+      firstModelForColour = false;
+      sb.append("#").append(model).append(":");
 
-        final Map<String, List<int[]>> modelData = colourData.get(model);
-        for (String chain : modelData.keySet())
+      final Map<String, List<int[]>> modelData = modelAndChainRanges
+              .get(model);
+      for (String chain : modelData.keySet())
+      {
+        boolean hasChain = !"".equals(chain.trim());
+        for (int[] range : modelData.get(chain))
         {
-          boolean hasChain = !"".equals(chain.trim());
-          for (int[] range : modelData.get(chain))
+          if (!firstPositionForModel)
           {
-            if (!firstPositionForModel)
-            {
-              sb.append(",");
-            }
-            if (range[0] == range[1])
-            {
-              sb.append(range[0]);
-            }
-            else
-            {
-              sb.append(range[0]).append("-").append(range[1]);
-            }
-            if (hasChain)
-            {
-              sb.append(".").append(chain);
-            }
-            firstPositionForModel = false;
+            sb.append(",");
           }
+          if (range[0] == range[1])
+          {
+            sb.append(range[0]);
+          }
+          else
+          {
+            sb.append(range[0]).append("-").append(range[1]);
+          }
+          if (hasChain)
+          {
+            sb.append(".").append(chain);
+          }
+          firstPositionForModel = false;
         }
       }
     }
-    commands.add(sb.toString());
-    return commands;
+    return sb.toString();
   }
 
   /**
    * <pre>
-   * Build a data structure which maps contiguous subsequences for each colour. 
-   * This generates a data structure from which we can easily generate the 
-   * Chimera command for colour by sequence.
+   * Build a data structure which records contiguous subsequences for each colour. 
+   * From this we can easily generate the Chimera command for colour by sequence.
    * Color
    *     Model number
    *         Chain
@@ -162,13 +186,19 @@ public class ChimeraCommands
    * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
    * </pre>
    */
-  protected static Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> buildColoursMap(
+  protected static Map<Object, AtomSpecModel> buildColoursMap(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
-    Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> colourMap = new LinkedHashMap<Color, SortedMap<Integer, Map<String, List<int[]>>>>();
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+    AlignmentI al = viewport.getAlignment();
+    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<Object, AtomSpecModel>();
     Color lastColour = null;
+
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
@@ -186,9 +216,9 @@ public class ChimeraCommands
         {
           final SequenceI seq = sequence[pdbfnum][s];
           if (mapping[m].getSequence() == seq
-                  && (sp = alignment.findIndex(seq)) > -1)
+                  && (sp = al.findIndex(seq)) > -1)
           {
-            SequenceI asp = alignment.getSequenceAt(sp);
+            SequenceI asp = al.getSequenceAt(sp);
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
@@ -203,7 +233,16 @@ public class ChimeraCommands
                 continue;
               }
 
-              Color colour = sr.getResidueColour(seq, r, fr);
+              Color colour = sr.getResidueColour(seq, r, finder);
+
+              /*
+               * darker colour for hidden regions
+               */
+              if (!cs.isVisible(r))
+              {
+                colour = Color.GRAY;
+              }
+
               final String chain = mapping[m].getChain();
 
               /*
@@ -230,8 +269,8 @@ public class ChimeraCommands
             // final colour range
             if (lastColour != null)
             {
-              addColourRange(colourMap, lastColour, pdbfnum, startPos,
-                      lastPos, lastChain);
+              addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos,
+                      lastChain);
             }
             // break;
           }
@@ -244,51 +283,266 @@ public class ChimeraCommands
   /**
    * Helper method to add one contiguous colour range to the colour map.
    * 
-   * @param colourMap
-   * @param colour
+   * @param map
+   * @param key
    * @param model
    * @param startPos
    * @param endPos
    * @param chain
    */
-  protected static void addColourRange(
-          Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> colourMap,
-          Color colour, int model, int startPos, int endPos, String chain)
+  protected static void addColourRange(Map<Object, AtomSpecModel> map,
+          Object key, int model, int startPos, int endPos, String chain)
   {
     /*
      * Get/initialize map of data for the colour
      */
-    SortedMap<Integer, Map<String, List<int[]>>> colourData = colourMap
-            .get(colour);
-    if (colourData == null)
+    AtomSpecModel atomSpec = map.get(key);
+    if (atomSpec == null)
     {
-      colourMap
-              .put(colour,
-                      colourData = new TreeMap<Integer, Map<String, List<int[]>>>());
+      atomSpec = new AtomSpecModel();
+      map.put(key, atomSpec);
     }
 
-    /*
-     * Get/initialize map of data for the colour and model
-     */
-    Map<String, List<int[]>> modelData = colourData.get(model);
-    if (modelData == null)
+    atomSpec.addRange(model, startPos, endPos, chain);
+  }
+
+  /**
+   * Constructs and returns Chimera commands to set attributes on residues
+   * corresponding to features in Jalview. Attribute names are the Jalview
+   * feature type, with a "jv_" prefix.
+   * 
+   * @param ssm
+   * @param files
+   * @param seqs
+   * @param viewPanel
+   * @return
+   */
+  public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] seqs, AlignmentViewPanel viewPanel)
+  {
+    Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
+            ssm, files, seqs, viewPanel);
+
+    List<String> commands = buildSetAttributeCommands(featureMap);
+
+    StructureMappingcommandSet cs = new StructureMappingcommandSet(
+            ChimeraCommands.class, null,
+            commands.toArray(new String[commands.size()]));
+
+    return cs;
+  }
+
+  /**
+   * <pre>
+   * Helper method to build a map of 
+   *   { featureType, { feature value, AtomSpecModel } }
+   * </pre>
+   * 
+   * @param ssm
+   * @param files
+   * @param seqs
+   * @param viewPanel
+   * @return
+   */
+  protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] seqs, AlignmentViewPanel viewPanel)
+  {
+    Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
+
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    if (fr == null)
     {
-      colourData.put(model, modelData = new TreeMap<String, List<int[]>>());
+      return theMap;
     }
 
-    /*
-     * Get/initialize map of data for colour, model and chain
-     */
-    List<int[]> chainData = modelData.get(chain);
-    if (chainData == null)
+    List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
+    if (visibleFeatures.isEmpty())
     {
-      modelData.put(chain, chainData = new ArrayList<int[]>());
+      return theMap;
     }
 
+    AlignmentI alignment = viewPanel.getAlignment();
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+
+      if (mapping == null || mapping.length < 1)
+      {
+        continue;
+      }
+
+      for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
+      {
+        for (int m = 0; m < mapping.length; m++)
+        {
+          final SequenceI seq = seqs[pdbfnum][seqNo];
+          int sp = alignment.findIndex(seq);
+          if (mapping[m].getSequence() == seq && sp > -1)
+          {
+            /*
+             * found a sequence with a mapping to a structure;
+             * now scan its features
+             */
+            SequenceI asp = alignment.getSequenceAt(sp);
+
+            scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap,
+                    pdbfnum);
+          }
+        }
+      }
+    }
+    return theMap;
+  }
+
+  /**
+   * Inspect features on the sequence; for each feature that is visible,
+   * determine its mapped ranges in the structure (if any) according to the
+   * given mapping, and add them to the map
+   * 
+   * @param visibleFeatures
+   * @param mapping
+   * @param seq
+   * @param theMap
+   * @param modelNumber
+   */
+  protected static void scanSequenceFeatures(List<String> visibleFeatures,
+          StructureMapping mapping, SequenceI seq,
+          Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+  {
+    SequenceFeature[] sfs = seq.getSequenceFeatures();
+    if (sfs == null)
+    {
+      return;
+    }
+
+    for (SequenceFeature sf : sfs)
+    {
+      String type = sf.getType();
+
+      /*
+       * Only copy visible features, don't copy any which originated
+       * from Chimera, and suppress uninteresting ones (e.g. RESNUM)
+       */
+      boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
+              .equals(sf.getFeatureGroup());
+      if (isFromViewer || !visibleFeatures.contains(type))
+      {
+        continue;
+      }
+      List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
+              sf.getEnd());
+
+      if (!mappedRanges.isEmpty())
+      {
+        String value = sf.getDescription();
+        if (value == null || value.length() == 0)
+        {
+          value = type;
+        }
+        float score = sf.getScore();
+        if (score != 0f && !Float.isNaN(score))
+        {
+          value = Float.toString(score);
+        }
+        Map<Object, AtomSpecModel> featureValues = theMap.get(type);
+        if (featureValues == null)
+        {
+          featureValues = new HashMap<Object, AtomSpecModel>();
+          theMap.put(type, featureValues);
+        }
+        for (int[] range : mappedRanges)
+        {
+          addColourRange(featureValues, value, modelNumber, range[0], range[1],
+                  mapping.getChain());
+        }
+      }
+    }
+  }
+
+  /**
+   * Traverse the map of features/values/models/chains/positions to construct a
+   * list of 'setattr' commands (one per distinct feature type and value).
+   * <p>
+   * The format of each command is
+   * 
+   * <pre>
+   * <blockquote> setattr r <featureName> " " #modelnumber:range.chain 
+   * e.g. setattr r jv:chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * </blockquote>
+   * </pre>
+   * 
+   * @param featureMap
+   * @return
+   */
+  protected static List<String> buildSetAttributeCommands(
+          Map<String, Map<Object, AtomSpecModel>> featureMap)
+  {
+    List<String> commands = new ArrayList<String>();
+    for (String featureType : featureMap.keySet())
+    {
+      String attributeName = makeAttributeName(featureType);
+
+      /*
+       * clear down existing attributes for this feature
+       */
+      // 'problem' - sets attribute to None on all residues - overkill?
+      // commands.add("~setattr r " + attributeName + " :*");
+
+      Map<Object, AtomSpecModel> values = featureMap.get(featureType);
+      for (Object value : values.keySet())
+      {
+        /*
+         * for each distinct value recorded for this feature type,
+         * add a command to set the attribute on the mapped residues
+         * Put values in single quotes, encoding any embedded single quotes
+         */
+        StringBuilder sb = new StringBuilder(128);
+        String featureValue = value.toString();
+        featureValue = featureValue.replaceAll("\\'", "&#39;");
+        sb.append("setattr r ").append(attributeName).append(" '")
+                .append(featureValue).append("' ");
+        sb.append(values.get(value).getAtomSpec());
+        commands.add(sb.toString());
+      }
+    }
+
+    return commands;
+  }
+
+  /**
+   * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied
+   * for a 'Jalview' namespace, and any non-alphanumeric character is converted
+   * to an underscore.
+   * 
+   * @param featureType
+   * @return <pre>
+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+   * </pre>
+   */
+  protected static String makeAttributeName(String featureType)
+  {
+    StringBuilder sb = new StringBuilder();
+    if (featureType != null)
+    {
+      for (char c : featureType.toCharArray())
+      {
+        sb.append(Character.isLetterOrDigit(c) ? c : '_');
+      }
+    }
+    String attName = NAMESPACE_PREFIX + sb.toString();
+
     /*
-     * Add the start/end positions
+     * Chimera treats an attribute name ending in 'color' as colour-valued;
+     * Jalview doesn't, so prevent this by appending an underscore
      */
-    chainData.add(new int[] { startPos, endPos });
+    if (attName.toUpperCase().endsWith("COLOR"))
+    {
+      attName += "_";
+    }
+
+    return attName;
   }
 
 }
index ba5e332..507ddbc 100644 (file)
@@ -129,7 +129,7 @@ public class ChimeraListener extends AbstractRequestHandler implements
   }
 
   /**
-   * Handler a ModelChanged notification from Chimera
+   * Handle a ModelChanged notification from Chimera
    * 
    * @param substring
    */
index b05c168..b954677 100644 (file)
 package jalview.ext.rbvi.chimera;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SearchResultMatchI;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.httpserver.AbstractRequestHandler;
 import jalview.io.DataSourceType;
@@ -40,8 +42,14 @@ import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.net.BindException;
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -54,6 +62,8 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 
 public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 {
+  public static final String CHIMERA_FEATURE_GROUP = "Chimera";
+
   // Chimera clause to exclude alternate locations in atom selection
   private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
@@ -98,14 +108,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
 
-  /*
-   * the default or current model displayed if the model cannot be identified
-   * from the selection message
-   */
-  private int frameNo = 0;
-
-  private String lastCommand;
-
   String lastHighlightCommand;
 
   /*
@@ -170,13 +172,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       if (getSsm() != null)
       {
         getSsm().addStructureViewerListener(this);
-        // ssm.addSelectionListener(this);
-        FeatureRenderer fr = getFeatureRenderer(null);
-        if (fr != null)
-        {
-          fr.featuresAdded();
-        }
-        refreshGUI();
       }
       return true;
     } catch (Exception q)
@@ -294,7 +289,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   public void closeViewer(boolean closeChimera)
   {
-    getSsm().removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getStructureFiles());
     if (closeChimera)
     {
       viewer.exitChimera();
@@ -304,7 +299,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       chimeraListener.shutdown();
       chimeraListener = null;
     }
-    lastCommand = null;
     viewer = null;
 
     if (chimeraMonitor != null)
@@ -339,29 +333,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Construct and send a command to align structures against a reference
-   * structure, based on one or more sequence alignments
-   * 
-   * @param _alignment
-   *          an array of alignments to process
-   * @param _refStructure
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param _hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * {@inheritDoc}
    */
   @Override
-  public void superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  public String superposeStructures(AlignmentI[] _alignment,
+          int[] _refStructure, HiddenColumns[] _hiddenCols)
   {
     StringBuilder allComs = new StringBuilder(128);
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
 
     if (!waitForFileLoad(files))
     {
-      return;
+      return null;
     }
 
     refreshPdbEntries();
@@ -370,7 +353,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     {
       int refStructure = _refStructure[a];
       AlignmentI alignment = _alignment[a];
-      ColumnSelection hiddenCols = _hiddenCols[a];
+      HiddenColumns hiddenCols = _hiddenCols[a];
 
       if (refStructure >= files.length)
       {
@@ -380,13 +363,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
 
       /*
-       * 'matched' array will hold 'true' for visible alignment columns where
+       * 'matched' bit i will be set for visible alignment columns i where
        * all sequences have a residue with a mapping to the PDB structure
        */
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
+      BitSet matched = new BitSet();
+      for (int m = 0; m < alignment.getWidth(); m++)
       {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
       }
 
       SuperposeData[] structures = new SuperposeData[files.length];
@@ -410,17 +396,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         refStructure = candidateRefStructure;
       }
 
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
+      int nmatched = matched.cardinality();
       if (nmatched < 4)
       {
-        // TODO: bail out here because superposition illdefined?
+        return MessageManager.formatMessage("label.insufficient_residues",
+                nmatched);
       }
 
       /*
@@ -433,41 +413,41 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         int lpos = -1;
         boolean run = false;
         StringBuilder molsel = new StringBuilder();
-        for (int r = 0; r < matched.length; r++)
+
+        int nextColumnMatch = matched.nextSetBit(0);
+        while (nextColumnMatch != -1)
         {
-          if (matched[r])
+          int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
+          if (lpos != pdbResNum - 1)
           {
-            int pdbResNum = structures[pdbfnum].pdbResNo[r];
-            if (lpos != pdbResNum - 1)
+            /*
+             * discontiguous - append last residue now
+             */
+            if (lpos != -1)
             {
-              /*
-               * discontiguous - append last residue now
-               */
-              if (lpos != -1)
-              {
-                molsel.append(String.valueOf(lpos));
-                molsel.append(chainCd);
-                molsel.append(",");
-              }
-              run = false;
+              molsel.append(String.valueOf(lpos));
+              molsel.append(chainCd);
+              molsel.append(",");
             }
-            else
+            run = false;
+          }
+          else
+          {
+            /*
+             * extending a contiguous run
+             */
+            if (!run)
             {
               /*
-               * extending a contiguous run
+               * start the range selection
                */
-              if (!run)
-              {
-                /*
-                 * start the range selection
-                 */
-                molsel.append(String.valueOf(lpos));
-                molsel.append("-");
-              }
-              run = true;
+              molsel.append(String.valueOf(lpos));
+              molsel.append("-");
             }
-            lpos = pdbResNum;
+            run = true;
           }
+          lpos = pdbResNum;
+          nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
         }
 
         /*
@@ -543,6 +523,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
                 .append(";" + command.toString());
       }
     }
+
+    String error = null;
     if (selectioncom.length() > 0)
     {
       // TODO: visually distinguish regions that were superposed
@@ -556,9 +538,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
       allComs.append("; ~display all; chain @CA|P; ribbon ")
               .append(selectioncom.toString()).append("; focus");
-      sendChimeraCommand(allComs.toString(), false);
+      List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+              true);
+      for (String reply : chimeraReplies)
+      {
+        if (reply.toLowerCase().contains("unequal numbers of atoms"))
+        {
+          error = reply;
+        }
+      }
     }
-
+    return error;
   }
 
   /**
@@ -587,7 +577,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * to the Chimera command 'list models type molecule', see
      * ChimeraManager.getModelList().
      */
-    List<ChimeraModel> maps = chimeraMaps.get(getPdbFile()[pdbfnum]);
+    List<ChimeraModel> maps = chimeraMaps.get(getStructureFiles()[pdbfnum]);
     boolean hasSubModels = maps != null && maps.size() > 1;
     return "#" + String.valueOf(pdbfnum) + (hasSubModels ? ".1" : "");
   }
@@ -631,31 +621,42 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Send a command to Chimera, and optionally log any responses.
+   * Send a command to Chimera, and optionally log and return any responses.
+   * <p>
+   * Does nothing, and returns null, if the command is the same as the last one
+   * sent [why?].
    * 
    * @param command
-   * @param logResponse
+   * @param getResponse
    */
-  public void sendChimeraCommand(final String command, boolean logResponse)
+  public List<String> sendChimeraCommand(final String command,
+          boolean getResponse)
   {
     if (viewer == null)
     {
       // ? thread running after viewer shut down
-      return;
+      return null;
     }
+    List<String> reply = null;
     viewerCommandHistory(false);
-    if (lastCommand == null || !lastCommand.equals(command))
+    if (true /*lastCommand == null || !lastCommand.equals(command)*/)
     {
       // trim command or it may never find a match in the replyLog!!
       List<String> lastReply = viewer.sendChimeraCommand(command.trim(),
-              logResponse);
-      if (logResponse && debug)
+              getResponse);
+      if (getResponse)
       {
-        log("Response from command ('" + command + "') was:\n" + lastReply);
+        reply = lastReply;
+        if (debug)
+        {
+          log("Response from command ('" + command + "') was:\n"
+                  + lastReply);
+        }
       }
     }
     viewerCommandHistory(true);
-    lastCommand = command;
+
+    return reply;
   }
 
   /**
@@ -689,17 +690,15 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   /**
    * @param files
    * @param sr
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   @Override
   protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
   {
     return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, fr, alignment);
+            getSequence(), sr, viewPanel);
   }
 
   /**
@@ -742,10 +741,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   private int _modelFileNameMap[];
 
+
   // ////////////////////////////////
   // /StructureListener
   @Override
-  public synchronized String[] getPdbFile()
+  public synchronized String[] getStructureFiles()
   {
     if (viewer == null)
     {
@@ -837,59 +837,64 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * Parse model number, residue and chain for each selected position,
      * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
      */
+    List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(selection);
+
+    /*
+     * Broadcast the selection (which may be empty, if the user just cleared all
+     * selections)
+     */
+    getSsm().mouseOverStructure(atomSpecs);
+  }
+
+  /**
+   * Converts a list of Chimera atomspecs to a list of AtomSpec representing the
+   * corresponding residues (if any) in Jalview
+   * 
+   * @param structureSelection
+   * @return
+   */
+  protected List<AtomSpec> convertStructureResiduesToAlignment(
+          List<String> structureSelection)
+  {
     List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
-    for (String atomSpec : selection)
+    for (String atomSpec : structureSelection)
     {
-      int colonPos = atomSpec.indexOf(":");
-      if (colonPos == -1)
-      {
-        continue; // malformed
-      }
-
-      int hashPos = atomSpec.indexOf("#");
-      String modelSubmodel = atomSpec.substring(hashPos + 1, colonPos);
-      int dotPos = modelSubmodel.indexOf(".");
-      int modelId = 0;
       try
       {
-        modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
-                : modelSubmodel.substring(0, dotPos));
-      } catch (NumberFormatException e)
+        AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+        String pdbfilename = getPdbFileForModel(spec.getModelNumber());
+        spec.setPdbFile(pdbfilename);
+        atomSpecs.add(spec);
+      } catch (IllegalArgumentException e)
       {
-        // ignore, default to model 0
+        System.err.println("Failed to parse atomspec: " + atomSpec);
       }
+    }
+    return atomSpecs;
+  }
 
-      String residueChain = atomSpec.substring(colonPos + 1);
-      dotPos = residueChain.indexOf(".");
-      int pdbResNum = Integer.parseInt(dotPos == -1 ? residueChain
-              : residueChain.substring(0, dotPos));
-
-      String chainId = dotPos == -1 ? "" : residueChain
-              .substring(dotPos + 1);
-
-      /*
-       * Work out the pdbfilename from the model number
-       */
-      String pdbfilename = modelFileNames[frameNo];
-      findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+  /**
+   * @param modelId
+   * @return
+   */
+  protected String getPdbFileForModel(int modelId)
+  {
+    /*
+     * Work out the pdbfilename from the model number
+     */
+    String pdbfilename = modelFileNames[0];
+    findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+    {
+      for (ChimeraModel cm : chimeraMaps.get(pdbfile))
       {
-        for (ChimeraModel cm : chimeraMaps.get(pdbfile))
+        if (cm.getModelNumber() == modelId)
         {
-          if (cm.getModelNumber() == modelId)
-          {
-            pdbfilename = pdbfile;
-            break findfileloop;
-          }
+          pdbfilename = pdbfile;
+          break findfileloop;
         }
       }
-      atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
     }
-
-    /*
-     * Broadcast the selection (which may be empty, if the user just cleared all
-     * selections)
-     */
-    getSsm().mouseOverStructure(atomSpecs);
+    return pdbfilename;
   }
 
   private void log(String message)
@@ -1036,6 +1041,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
+   * Returns a list of chains mapped in this viewer. Note this list is not
+   * currently scoped per structure.
+   * 
+   * @return
+   */
+  @Override
+  public List<String> getChainNames()
+  {
+    return chainNames;
+  }
+
+  /**
    * Send a 'focus' command to Chimera to recentre the visible display
    */
   public void focusView()
@@ -1071,13 +1088,207 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
   }
 
+  /**
+   * Constructs and send commands to Chimera to set attributes on residues for
+   * features visible in Jalview
+   * 
+   * @param avp
+   * @return
+   */
+  public int sendFeaturesToViewer(AlignmentViewPanel avp)
+  {
+    // TODO refactor as required to pull up to an interface
+    AlignmentI alignment = avp.getAlignment();
+
+    String[] files = getStructureFiles();
+    if (files == null)
+    {
+      return 0;
+    }
+
+    StructureMappingcommandSet commandSet = ChimeraCommands
+            .getSetAttributeCommandsForFeatures(getSsm(), files,
+                    getSequence(), avp);
+    String[] commands = commandSet.commands;
+    if (commands.length > 10)
+    {
+      sendCommandsByFile(commands);
+    }
+    else
+    {
+      for (String command : commands)
+      {
+        sendAsynchronousCommand(command, null);
+      }
+    }
+    return commands.length;
+  }
+
+  /**
+   * Write commands to a temporary file, and send a command to Chimera to open
+   * the file as a commands script. For use when sending a large number of
+   * separate commands would overload the REST interface mechanism.
+   * 
+   * @param commands
+   */
+  protected void sendCommandsByFile(String[] commands)
+  {
+    try
+    {
+      File tmp = File.createTempFile("chim", ".com");
+      tmp.deleteOnExit();
+      PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
+      for (String command : commands)
+      {
+        out.println(command);
+      }
+      out.flush();
+      out.close();
+      String path = tmp.getAbsolutePath();
+      sendAsynchronousCommand("open cmd:" + path, null);
+    } catch (IOException e)
+    {
+      System.err
+              .println("Sending commands to Chimera via file failed with "
+                      + e.getMessage());
+    }
+  }
 
-  @Override
-  public List<String> getChainNames()
+  /**
+   * Get Chimera residues which have the named attribute, find the mapped
+   * positions in the Jalview sequence(s), and set as sequence features
+   * 
+   * @param attName
+   * @param alignmentPanel
+   */
+  public void copyStructureAttributesToFeatures(String attName,
+          AlignmentViewPanel alignmentPanel)
   {
-    return chainNames;
+    // todo pull up to AAStructureBindingModel (and interface?)
+
+    /*
+     * ask Chimera to list residues with the attribute, reporting its value
+     */
+    // this alternative command
+    // list residues spec ':*/attName' attr attName
+    // doesn't report 'None' values (which is good), but
+    // fails for 'average.bfactor' (which is bad):
+
+    String cmd = "list residues attr '" + attName + "'";
+    List<String> residues = sendChimeraCommand(cmd, true);
+
+    boolean featureAdded = createFeaturesForAttributes(attName, residues);
+    if (featureAdded)
+    {
+      alignmentPanel.getFeatureRenderer().featuresAdded();
+    }
   }
 
+  /**
+   * Create features in Jalview for the given attribute name and structure
+   * residues.
+   * 
+   * <pre>
+   * The residue list should be 0, 1 or more reply lines of the format: 
+   *     residue id #0:5.A isHelix -155.000836316 index 5 
+   * or 
+   *     residue id #0:6.A isHelix None
+   * </pre>
+   * 
+   * @param attName
+   * @param residues
+   * @return
+   */
+  protected boolean createFeaturesForAttributes(String attName,
+          List<String> residues)
+  {
+    boolean featureAdded = false;
+    String featureGroup = getViewerFeatureGroup();
+
+    for (String residue : residues)
+    {
+      AtomSpec spec = null;
+      String[] tokens = residue.split(" ");
+      if (tokens.length < 5)
+      {
+        continue;
+      }
+      String atomSpec = tokens[2];
+      String attValue = tokens[4];
+
+      /*
+       * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix)
+       */
+      if ("None".equalsIgnoreCase(attValue)
+              || "False".equalsIgnoreCase(attValue))
+      {
+        continue;
+      }
+
+      try
+      {
+        spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+      } catch (IllegalArgumentException e)
+      {
+        System.err.println("Problem parsing atomspec " + atomSpec);
+        continue;
+      }
+
+      String chainId = spec.getChain();
+      String description = attValue;
+      float score = Float.NaN;
+      try
+      {
+        score = Float.valueOf(attValue);
+        description = chainId;
+      } catch (NumberFormatException e)
+      {
+        // was not a float value
+      }
+
+      String pdbFile = getPdbFileForModel(spec.getModelNumber());
+      spec.setPdbFile(pdbFile);
+
+      List<AtomSpec> atoms = Collections.singletonList(spec);
+
+      /*
+       * locate the mapped position in the alignment (if any)
+       */
+      SearchResultsI sr = getSsm()
+              .findAlignmentPositionsForStructurePositions(atoms);
+
+      /*
+       * expect one matched alignment position, or none 
+       * (if the structure position is not mapped)
+       */
+      for (SearchResultMatchI m : sr.getResults())
+      {
+        SequenceI seq = m.getSequence();
+        int start = m.getStart();
+        int end = m.getEnd();
+        SequenceFeature sf = new SequenceFeature(attName, description,
+                start, end, score, featureGroup);
+        // todo: should SequenceFeature have an explicit property for chain?
+        // note: repeating the action shouldn't duplicate features
+        featureAdded |= seq.addSequenceFeature(sf);
+      }
+    }
+    return featureAdded;
+  }
+
+  /**
+   * Answers the feature group name to apply to features created in Jalview from
+   * Chimera attributes
+   * 
+   * @return
+   */
+  protected String getViewerFeatureGroup()
+  {
+    // todo pull up to interface
+    return CHIMERA_FEATURE_GROUP;
+  }
+
+
   public Hashtable<String, String> getChainFile()
   {
     return chainFile;
index a41e10e..d65f1d5 100644 (file)
  */
 package jalview.ext.varna;
 
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
 
@@ -47,7 +47,8 @@ public class VarnaCommands
    */
   public static String[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+          SequenceI[][] sequence, SequenceRenderer sr,
+          FeatureColourFinder finder,
           AlignmentI alignment)
   {
     ArrayList<String> str = new ArrayList<String>();
@@ -58,7 +59,9 @@ public class VarnaCommands
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
 
       if (mapping == null || mapping.length < 1)
+      {
         continue;
+      }
 
       int lastPos = -1;
       for (int s = 0; s < sequence[pdbfnum].length; s++)
@@ -79,14 +82,15 @@ public class VarnaCommands
               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
 
               if (pos < 1 || pos == lastPos)
+              {
                 continue;
+              }
 
               lastPos = pos;
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+              Color col = sr.getResidueColour(sequence[pdbfnum][s], r,
+                      finder);
 
-              if (fr != null)
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
               String newSelcom = (mapping[m].getChain() != " " ? ":"
                       + mapping[m].getChain() : "")
                       + "/"
index f86c3bc..99c0c51 100644 (file)
@@ -137,4 +137,12 @@ public interface GFTSPanelI
    * @return
    */
   public Map<String, Integer> getTempUserPrefs();
+
+  /**
+   * Returns unique key used for storing an FTSs instance cache items in the
+   * cache data structure
+   * 
+   * @return
+   */
+  public String getCacheKey();
 }
index a69d9f8..f1db383 100644 (file)
@@ -28,6 +28,7 @@ import jalview.gui.Desktop;
 import jalview.gui.IProgressIndicator;
 import jalview.gui.JvSwingUtils;
 import jalview.gui.SequenceFetcher;
+import jalview.io.cache.JvCacheableInputBox;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -38,6 +39,8 @@ import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
@@ -61,7 +64,6 @@ import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
-import javax.swing.JTextField;
 import javax.swing.Timer;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -70,6 +72,7 @@ import javax.swing.event.DocumentListener;
 import javax.swing.event.InternalFrameEvent;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableColumn;
+import javax.swing.text.JTextComponent;
 
 /**
  * This class provides the swing GUI layout for FTS Panel and implements most of
@@ -95,7 +98,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   protected JButton btn_cancel = new JButton();
 
-  protected JTextField txt_search = new JTextField(30);
+  protected JvCacheableInputBox<String> txt_search;
 
   protected SequenceFetcher seqFetcher;
 
@@ -146,6 +149,10 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   protected HashSet<String> paginatorCart = new HashSet<String>();
 
+  private static final int MIN_WIDTH = 670;
+
+  private static final int MIN_HEIGHT = 300;
+
   protected static final DecimalFormat totalNumberformatter = new DecimalFormat(
           "###,###");
 
@@ -234,6 +241,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     try
     {
       jbInit();
+      mainFrame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
       mainFrame.addFocusListener(new FocusAdapter()
       {
         @Override
@@ -257,6 +265,9 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
    */
   private void jbInit() throws Exception
   {
+
+    txt_search = new JvCacheableInputBox<String>(getCacheKey());
+    populateCmbSearchTargetOptions();
     Integer width = getTempUserPrefs().get("FTSPanel.width") == null ? 800
             : getTempUserPrefs().get("FTSPanel.width");
     Integer height = getTempUserPrefs().get("FTSPanel.height") == null ? 400
@@ -448,49 +459,51 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     scrl_searchResult.setPreferredSize(new Dimension(width, height));
 
     cmb_searchTarget.setFont(new java.awt.Font("Verdana", 0, 12));
-    cmb_searchTarget.addActionListener(new ActionListener()
+    cmb_searchTarget.addItemListener(new ItemListener()
     {
       @Override
-      public void actionPerformed(ActionEvent e)
+      public void itemStateChanged(ItemEvent e)
       {
-        String tooltipText;
-        if ("all".equalsIgnoreCase(getCmbSearchTarget().getSelectedItem()
-                .toString()))
+        if (e.getStateChange() == ItemEvent.SELECTED)
         {
-          tooltipText = MessageManager.getString("label.search_all");
-        }
-        else if ("pdb id".equalsIgnoreCase(getCmbSearchTarget()
-                .getSelectedItem().toString()))
-        {
-          tooltipText = MessageManager
-                  .getString("label.separate_multiple_accession_ids");
-        }
-        else
-        {
-          tooltipText = MessageManager.formatMessage(
-                  "label.separate_multiple_query_values",
-                  new Object[] { getCmbSearchTarget().getSelectedItem()
-                          .toString() });
+          String tooltipText;
+          if ("all".equalsIgnoreCase(getCmbSearchTarget().getSelectedItem()
+                  .toString()))
+          {
+            tooltipText = MessageManager.getString("label.search_all");
+          }
+          else if ("pdb id".equalsIgnoreCase(getCmbSearchTarget()
+                  .getSelectedItem().toString()))
+          {
+            tooltipText = MessageManager
+                    .getString("label.separate_multiple_accession_ids");
+          }
+          else
+          {
+            tooltipText = MessageManager.formatMessage(
+                    "label.separate_multiple_query_values",
+                    new Object[] { getCmbSearchTarget().getSelectedItem()
+                            .toString() });
+          }
+          txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
+                  tooltipText));
+          searchAction(true);
         }
-        txt_search.setToolTipText(JvSwingUtils.wrapTooltip(true,
-                tooltipText));
-        searchAction(true);
       }
     });
-
-    populateCmbSearchTargetOptions();
 
     txt_search.setFont(new java.awt.Font("Verdana", 0, 12));
 
-    txt_search.addKeyListener(new KeyAdapter()
+    txt_search.getEditor().getEditorComponent()
+            .addKeyListener(new KeyAdapter()
     {
       @Override
       public void keyPressed(KeyEvent e)
       {
         if (e.getKeyCode() == KeyEvent.VK_ENTER)
         {
-          if (txt_search.getText() == null
-                  || txt_search.getText().isEmpty())
+          if (getTypedText() == null || getTypedText().isEmpty())
           {
             return;
           }
@@ -499,27 +512,29 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
           if (primaryKeyName.equalsIgnoreCase(getCmbSearchTarget()
                   .getSelectedItem().toString()))
           {
-            transferToSequenceFetcher(txt_search.getText());
+            transferToSequenceFetcher(getTypedText());
           }
         }
       }
     });
-
     final DeferredTextInputListener listener = new DeferredTextInputListener(
             1500, new ActionListener()
             {
               @Override
               public void actionPerformed(ActionEvent e)
               {
-                if (!getTypedText().equalsIgnoreCase(lastSearchTerm))
+                String typed = getTypedText();
+                if (!typed.equalsIgnoreCase(lastSearchTerm))
                 {
                   searchAction(true);
                   paginatorCart.clear();
-                  lastSearchTerm = getTypedText();
+                  lastSearchTerm = typed;
                 }
               }
             }, false);
-    txt_search.getDocument().addDocumentListener(listener);
+    ((JTextComponent) txt_search.getEditor().getEditorComponent())
+            .getDocument().addDocumentListener(listener);
+
     txt_search.addFocusListener(new FocusListener()
     {
       @Override
@@ -635,16 +650,12 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   protected void closeAction()
   {
-    // System.out.println(">>>>>>>>>> closing internal frame!!!");
-    // System.out.println("width : " + this.getWidth());
-    // System.out.println("heigh : " + this.getHeight());
-    // System.out.println("x : " + mainFrame.getX());
-    // System.out.println("y : " + mainFrame.getY());
     getTempUserPrefs().put("FTSPanel.width", this.getWidth());
     getTempUserPrefs().put("FTSPanel.height", pnl_results.getHeight());
     getTempUserPrefs().put("FTSPanel.x", mainFrame.getX());
     getTempUserPrefs().put("FTSPanel.y", mainFrame.getY());
     mainFrame.dispose();
+    txt_search.persistCache();
   }
 
   public class DeferredTextInputListener implements DocumentListener
@@ -692,7 +703,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
   {
     if (previousWantedFields == null)
     {
-      return true;
+      return false;
     }
 
     return Arrays.equals(getFTSRestClient()
@@ -719,7 +730,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     return cmb_searchTarget;
   }
 
-  public JTextField getTxtSearch()
+  public JComboBox<String> getTxtSearch()
   {
     return txt_search;
   }
@@ -815,7 +826,6 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   public void transferToSequenceFetcher(String ids)
   {
-    // mainFrame.dispose();
     seqFetcher.getTextArea().setText(ids);
     Thread worker = new Thread(seqFetcher);
     worker.start();
@@ -824,7 +834,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
   @Override
   public String getTypedText()
   {
-    return txt_search.getText().trim();
+    return txt_search.getUserInput();
   }
 
   @Override
@@ -875,6 +885,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
   {
     lbl_blank.setVisible(!isSearchInProgress);
     lbl_loading.setVisible(isSearchInProgress);
+    txt_search.setEditable(!isSearchInProgress);
   }
 
   @Override
@@ -929,8 +940,6 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
               .toString();
       paginatorCart.add(idStr);
     }
-    // System.out.println("Paginator shopping cart size : "
-    // + paginatorCart.size());
   }
 
   public void updateSummaryTableSelections()
@@ -949,9 +958,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     {
       e.printStackTrace();
     }
-    // System.out.println(">>>>>> got here : 1");
     int totalRows = resultTable.getRowCount();
-    // resultTable.clearSelection();
     for (int row = 0; row < totalRows; row++)
     {
       String id = (String) resultTable.getValueAt(row, primaryKeyColIndex);
@@ -965,9 +972,6 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   public void refreshPaginatorState()
   {
-    // System.out.println("resultSet count : " + resultSetCount);
-    // System.out.println("offSet : " + offSet);
-    // System.out.println("page limit : " + pageLimit);
     setPrevPageButtonEnabled(false);
     setNextPageButtonEnabled(false);
     if (resultSetCount == 0 && pageLimit == 0)
@@ -989,4 +993,5 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     mainFrame.setTitle(getFTSFrameTitle());
   }
 
+
 }
index 1dfabce..b944b9b 100644 (file)
@@ -39,10 +39,11 @@ public class PDBFTSPanel extends GFTSPanel
   private static String defaultFTSFrameTitle = MessageManager
           .getString("label.pdb_sequence_fetcher");
 
-  private String ftsFrameTitle = defaultFTSFrameTitle;
 
   private static Map<String, Integer> tempUserPrefs = new HashMap<String, Integer>();
 
+  private static final String PDB_FTS_CACHE_KEY = "CACHE.PDB_FTS";
+
   public PDBFTSPanel(SequenceFetcher seqFetcher)
   {
     super();
@@ -55,6 +56,7 @@ public class PDBFTSPanel extends GFTSPanel
   @Override
   public void searchAction(boolean isFreshSearch)
   {
+    mainFrame.requestFocusInWindow();
     if (isFreshSearch)
     {
       offSet = 0;
@@ -64,7 +66,6 @@ public class PDBFTSPanel extends GFTSPanel
       @Override
       public void run()
       {
-        ftsFrameTitle = defaultFTSFrameTitle;
         reset();
         boolean allowEmptySequence = false;
         if (getTypedText().length() > 0)
@@ -76,7 +77,7 @@ public class PDBFTSPanel extends GFTSPanel
                   .getSelectedItem()).getCode();
           wantedFields = PDBFTSRestClient.getInstance()
                   .getAllDefaultDisplayedFTSDataColumns();
-          String searchTerm = decodeSearchTerm(txt_search.getText(),
+          String searchTerm = decodeSearchTerm(getTypedText(),
                   searchTarget);
 
           FTSRestRequest request = new FTSRestRequest();
@@ -143,6 +144,7 @@ public class PDBFTSPanel extends GFTSPanel
           refreshPaginatorState();
           updateSummaryTableSelections();
         }
+        txt_search.updateCache();
       }
     }.start();
   }
@@ -200,7 +202,7 @@ public class PDBFTSPanel extends GFTSPanel
       e.printStackTrace();
     }
     int[] selectedRows = getResultTable().getSelectedRows();
-    String searchTerm = txt_search.getText();
+    String searchTerm = getTypedText();
     for (int summaryRow : selectedRows)
     {
       String idStr = getResultTable().getValueAt(summaryRow,
@@ -261,7 +263,7 @@ public class PDBFTSPanel extends GFTSPanel
   @Override
   public String getFTSFrameTitle()
   {
-    return ftsFrameTitle;
+    return defaultFTSFrameTitle;
   }
 
   @Override
@@ -276,4 +278,12 @@ public class PDBFTSPanel extends GFTSPanel
     return tempUserPrefs;
   }
 
+
+  @Override
+  public String getCacheKey()
+  {
+    return PDB_FTS_CACHE_KEY;
+  }
+
+
 }
index 27bfca8..a23df4c 100644 (file)
@@ -157,14 +157,14 @@ public class UniProtFTSRestClient extends FTSRestClient
     if (foundDataRow != null && foundDataRow.length > 0)
     {
       result = new ArrayList<FTSData>();
-      String titleRow = getDataColumnsFieldsAsTabDelimitedString(uniprotRestRequest
-              .getWantedFields());
-      // System.out.println(">>>>Title row : " + titleRow);
+      boolean firstRow = true;
       for (String dataRow : foundDataRow)
       {
-        if (dataRow.equalsIgnoreCase(titleRow))
+        // The first data row is usually the header data. This should be
+        // filtered out from the rest of the data See: JAL-2485
+        if (firstRow)
         {
-          // System.out.println(">>>>>>>>>> matched!!!");
+          firstRow = false;
           continue;
         }
         // System.out.println(dataRow);
index f04e4fa..ace3600 100644 (file)
@@ -40,10 +40,11 @@ public class UniprotFTSPanel extends GFTSPanel
   private static String defaultFTSFrameTitle = MessageManager
           .getString("label.uniprot_sequence_fetcher");
 
-  private String ftsFrameTitle = defaultFTSFrameTitle;
 
   private static Map<String, Integer> tempUserPrefs = new HashMap<String, Integer>();
 
+  private static final String UNIPROT_FTS_CACHE_KEY = "CACHE.UNIPROT_FTS";
+
   public UniprotFTSPanel(SequenceFetcher seqFetcher)
   {
     super();
@@ -57,6 +58,7 @@ public class UniprotFTSPanel extends GFTSPanel
   @Override
   public void searchAction(boolean isFreshSearch)
   {
+    mainFrame.requestFocusInWindow();
     if (isFreshSearch)
     {
       offSet = 0;
@@ -66,20 +68,18 @@ public class UniprotFTSPanel extends GFTSPanel
       @Override
       public void run()
       {
-        ftsFrameTitle = defaultFTSFrameTitle;
         reset();
-        if (getTypedText().length() > 0)
+        String searchInput = getTypedText();
+        if (searchInput.length() > 0)
         {
           setSearchInProgress(true);
           long startTime = System.currentTimeMillis();
-
+          searchInput = getTypedText();
           String searchTarget = ((FTSDataColumnI) cmb_searchTarget
                   .getSelectedItem()).getAltCode();
-
           wantedFields = UniProtFTSRestClient.getInstance()
                   .getAllDefaultDisplayedFTSDataColumns();
-          String searchTerm = decodeSearchTerm(txt_search.getText(),
-                  searchTarget);
+          String searchTerm = decodeSearchTerm(searchInput, searchTarget);
 
           FTSRestRequest request = new FTSRestRequest();
           request.setFieldToSearchBy(searchTarget);
@@ -143,6 +143,7 @@ public class UniprotFTSPanel extends GFTSPanel
           refreshPaginatorState();
           updateSummaryTableSelections();
         }
+        txt_search.updateCache();
       }
     }.start();
 
@@ -226,7 +227,7 @@ public class UniprotFTSPanel extends GFTSPanel
   @Override
   public String getFTSFrameTitle()
   {
-    return ftsFrameTitle;
+    return defaultFTSFrameTitle;
   }
 
   @Override
@@ -235,4 +236,9 @@ public class UniprotFTSPanel extends GFTSPanel
     return tempUserPrefs;
   }
 
+  @Override
+  public String getCacheKey()
+  {
+    return UNIPROT_FTS_CACHE_KEY;
+  }
 }
diff --git a/src/jalview/gui/#OverviewPanel.java# b/src/jalview/gui/#OverviewPanel.java#
new file mode 100755 (executable)
index 0000000..ce3abb5
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+ * 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.gui;
+
+import jalview.datamodel.SequenceI;
+import jalview.renderer.AnnotationRenderer;
+import jalview.viewmodel.OverviewDimensions;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JPanel;
+
+/**
+ * Panel displaying an overview of the full alignment, with an interactive box
+ * representing the viewport onto the alignment.
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class OverviewPanel extends JPanel implements Runnable
+{
+  private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
+
+  private final AnnotationRenderer renderer = new AnnotationRenderer();
+
+  private OverviewDimensions od;
+
+  private BufferedImage miniMe;
+
+  private BufferedImage lastMiniMe = null;
+
+  private AlignViewport av;
+
+  private AlignmentPanel ap;
+
+  //
+  private boolean resizing = false;
+
+  // This is set true if the user resizes whilst
+  // the overview is being calculated
+  private boolean resizeAgain = false;
+
+  // Can set different properties in this seqCanvas than
+  // main visible SeqCanvas
+  private SequenceRenderer sr;
+
+  private jalview.renderer.seqfeatures.FeatureRenderer fr;
+
+  /**
+   * Creates a new OverviewPanel object.
+   * 
+   * @param alPanel
+   *          The alignment panel which is shown in the overview panel
+   */
+  public OverviewPanel(AlignmentPanel alPanel)
+  {
+    this.av = alPanel.av;
+    this.ap = alPanel;
+    setLayout(null);
+
+    sr = new SequenceRenderer(av);
+    sr.renderGaps = false;
+    sr.forOverview = true;
+    fr = new FeatureRenderer(alPanel);
+
+    od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
+
+    addComponentListener(new ComponentAdapter()
+    {
+      @Override
+      public void componentResized(ComponentEvent evt)
+      {
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
+        {
+          updateOverviewImage();
+        }
+      }
+    });
+
+    addMouseMotionListener(new MouseMotionAdapter()
+    {
+      @Override
+      public void mouseDragged(MouseEvent evt)
+      {
+        if (!av.getWrapAlignment())
+        {
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av
+                  .getColumnSelection(), av.getRanges());
+          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
+        }
+      }
+    });
+
+    addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent evt)
+      {
+        if (!av.getWrapAlignment())
+        {
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av
+                  .getColumnSelection(), av.getRanges());
+          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
+        }
+      }
+    });
+
+    updateOverviewImage();
+  }
+
+  /**
+   * Updates the overview image when the related alignment panel is updated
+   */
+  public void updateOverviewImage()
+  {
+    if (resizing)
+    {
+      resizeAgain = true;
+      return;
+    }
+
+    resizing = true;
+
+    if ((getWidth() > 0) && (getHeight() > 0))
+    {
+      od.setWidth(getWidth());
+      od.setHeight(getHeight());
+    }
+
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    Thread thread = new Thread(this);
+    thread.start();
+    repaint();
+  }
+
+  @Override
+  public void run()
+  {
+    miniMe = null;
+
+    if (av.isShowSequenceFeatures())
+    {
+      fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
+    }
+
+    // why do we need to set preferred size again? was set in
+    // updateOverviewImage
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
+            BufferedImage.TYPE_INT_RGB);
+
+    Graphics mg = miniMe.getGraphics();
+    mg.setColor(Color.orange);
+    mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
+
+<<<<<<< HEAD
+    // calculate sampleCol and sampleRow
+    // alignment width is max number of residues/bases
+    // alignment height is number of sequences
+    int alwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getAbsoluteHeight();
+
+    // sampleCol or sampleRow is the width/height allocated to each residue
+    // in particular, sometimes we may need more than one row/col of the
+    // BufferedImage allocated
+    // sampleCol is how much of a residue to assign to each pixel
+    // sampleRow is how many sequences to assign to each pixel
+    float sampleCol = alwidth / (float) od.getWidth();
+    float sampleRow = alheight / (float) od.getSequencesHeight();
+
+    buildImage(sampleRow, sampleCol);
+=======
+    float sampleCol = (float) alwidth / (float) width;
+    float sampleRow = (float) alheight / (float) sequencesHeight;
+
+    int lastcol = -1, lastrow = -1;
+    Color color = Color.white;
+    int row, col;
+    jalview.datamodel.SequenceI seq;
+    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
+            .hasHiddenColumns();
+    boolean hiddenRow = false;
+    // get hidden row and hidden column map once at beginning.
+    // clone featureRenderer settings to avoid race conditions... if state is
+    // updated just need to refresh again
+
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+
+    for (row = 0; row < sequencesHeight; row++)
+    {
+      if (resizeAgain)
+      {
+        break;
+      }
+      if ((int) (row * sampleRow) == lastrow)
+      {
+        // No need to recalculate the colours,
+        // Just copy from the row above
+        for (col = 0; col < width; col++)
+        {
+          if (resizeAgain)
+          {
+            break;
+          }
+          miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
+        }
+        continue;
+      }
+
+      lastrow = (int) (row * sampleRow);
+
+      hiddenRow = false;
+      if (hasHiddenRows)
+      {
+        seq = av.getAlignment().getHiddenSequences()
+                .getHiddenSequence(lastrow);
+        if (seq == null)
+        {
+          int index = av.getAlignment().getHiddenSequences()
+                  .findIndexWithoutHiddenSeqs(lastrow);
+
+          seq = av.getAlignment().getSequenceAt(index);
+        }
+        else
+        {
+          hiddenRow = true;
+        }
+      }
+      else
+      {
+        seq = av.getAlignment().getSequenceAt(lastrow);
+      }
+
+      if (seq == null)
+      {
+        System.out.println(lastrow + " null");
+        continue;
+      }
+
+      for (col = 0; col < width; col++)
+      {
+        if (resizeAgain)
+        {
+          break;
+        }
+        if ((int) (col * sampleCol) == lastcol
+                && (int) (row * sampleRow) == lastrow)
+        {
+          miniMe.setRGB(col, row, color.getRGB());
+          continue;
+        }
+
+        lastcol = (int) (col * sampleCol);
+
+        if (seq.getLength() > lastcol)
+        {
+          color = sr.getResidueColour(seq, lastcol, finder);
+        }
+        else
+        {
+          color = Color.WHITE;
+        }
+
+        if (hiddenRow
+                || (hasHiddenCols && !av.getColumnSelection().isVisible(
+                        lastcol)))
+        {
+          color = color.darker().darker();
+        }
+
+        miniMe.setRGB(col, row, color.getRGB());
+>>>>>>> bug/JAL-2436featureRendererThreading
+
+    if (av.isShowAnnotation())
+    {
+      renderer.updateFromAlignViewport(av);
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
+      {
+        mg.translate(col, od.getSequencesHeight());
+        renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
+                av.getAlignmentConservationAnnotation().annotations,
+                (int) (sampleCol) + 1, od.getGraphHeight(),
+                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+        mg.translate(-col, -od.getSequencesHeight());
+
+      }
+    }
+    System.gc();
+
+    resizing = false;
+
+    if (resizeAgain)
+    {
+      resizeAgain = false;
+      updateOverviewImage();
+    }
+    else
+    {
+      lastMiniMe = miniMe;
+    }
+
+    setBoxPosition();
+  }
+
+  /*
+   * Build the overview panel image
+   */
+  private void buildImage(float sampleRow, float sampleCol)
+  {
+    int lastcol = -1;
+    int lastrow = -1;
+    int color = Color.white.getRGB();
+
+    SequenceI seq = null;
+
+    final boolean hasHiddenCols = av.hasHiddenColumns();
+    boolean hiddenRow = false;
+    // get hidden row and hidden column map once at beginning.
+    // clone featureRenderer settings to avoid race conditions... if state is
+    // updated just need to refresh again
+    for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
+    {
+      boolean doCopy = true;
+      int currentrow = (int) (row * sampleRow);
+      if (currentrow != lastrow)
+      {
+        doCopy = false;
+
+        lastrow = currentrow;
+
+        // get the sequence which would be at alignment index 'lastrow' if no
+        // rows were hidden, and determine whether it is hidden or not
+        hiddenRow = av.getAlignment().isHidden(lastrow);
+        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
+      }
+
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
+      {
+        if (doCopy)
+        {
+          color = miniMe.getRGB(col, row - 1);
+        }
+        else if ((int) (col * sampleCol) != lastcol
+                || (int) (row * sampleRow) != lastrow)
+        {
+          lastcol = (int) (col * sampleCol);
+          color = getColumnColourFromSequence(seq, hiddenRow, hasHiddenCols,
+                  lastcol);
+        }
+        // else we just use the color we already have , so don't need to set it
+
+        miniMe.setRGB(col, row, color);
+      }
+    }
+  }
+
+  /*
+   * Find the colour of a sequence at a specified column position
+   */
+  private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
+          boolean hiddenRow, boolean hasHiddenCols, int lastcol)
+  {
+    int color;
+
+    if (seq == null)
+    {
+      color = Color.white.getRGB();
+    }
+    else if (seq.getLength() > lastcol)
+    {
+      color = sr.getResidueBoxColour(seq, lastcol).getRGB();
+
+      if (av.isShowSequenceFeatures())
+      {
+        color = fr.findFeatureColour(color, seq, lastcol);
+      }
+    }
+    else
+    {
+      color = Color.white.getRGB();
+    }
+
+    if (hiddenRow
+            || (hasHiddenCols && !av.getColumnSelection()
+                    .isVisible(lastcol)))
+    {
+      color = new Color(color).darker().darker().getRGB();
+    }
+
+    return color;
+  }
+
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   */
+  public void setBoxPosition()
+  {
+    od.setBoxPosition(av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+    repaint();
+  }
+
+
+  @Override
+  public void paintComponent(Graphics g)
+  {
+    if (resizing || resizeAgain)
+    {
+      if (lastMiniMe == null)
+      {
+        g.setColor(Color.white);
+        g.fillRect(0, 0, getWidth(), getHeight());
+      }
+      else
+      {
+        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
+      }
+      g.setColor(TRANS_GREY);
+      g.fillRect(0, 0, getWidth(), getHeight());
+    }
+    else if (lastMiniMe != null)
+    {
+      g.drawImage(lastMiniMe, 0, 0, this);
+      if (lastMiniMe != miniMe)
+      {
+        g.setColor(TRANS_GREY);
+        g.fillRect(0, 0, getWidth(), getHeight());
+      }
+    }
+
+    g.setColor(Color.red);
+    od.drawBox(g);
+  }
+}
index b5fc817..a9a970f 100644 (file)
@@ -34,7 +34,7 @@ import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureSettingsControllerI;
 import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
-import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.commands.CommandI;
@@ -53,6 +53,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SeqCigar;
@@ -69,6 +70,7 @@ import jalview.io.FileFormat;
 import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FileLoader;
+import jalview.io.FileParse;
 import jalview.io.FormatAdapter;
 import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
@@ -77,15 +79,16 @@ import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.io.JnetAnnotationMaker;
 import jalview.io.NewickFile;
+import jalview.io.ScoreMatrixFile;
 import jalview.io.TCoffeeScoreFile;
 import jalview.jbgui.GAlignFrame;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
-import jalview.schemes.ResidueProperties;
 import jalview.schemes.TCoffeeColourScheme;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
 import jalview.ws.jws1.Discoverer;
@@ -160,6 +163,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   AlignViewport viewport;
 
+  ViewportRanges vpRanges;
+
   public AlignViewControllerI avc;
 
   List<AlignmentPanel> alignPanels = new ArrayList<AlignmentPanel>();
@@ -229,7 +234,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param height
    *          height of frame.
    */
-  public AlignFrame(AlignmentI al, ColumnSelection hiddenColumns,
+  public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
           int width, int height)
   {
     this(al, hiddenColumns, width, height, null);
@@ -246,7 +251,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param sequenceSetId
    *          (may be null)
    */
-  public AlignFrame(AlignmentI al, ColumnSelection hiddenColumns,
+  public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
           int width, int height, String sequenceSetId)
   {
     this(al, hiddenColumns, width, height, sequenceSetId, null);
@@ -265,7 +270,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param viewId
    *          (may be null)
    */
-  public AlignFrame(AlignmentI al, ColumnSelection hiddenColumns,
+  public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
           int width, int height, String sequenceSetId, String viewId)
   {
     setSize(width, height);
@@ -284,7 +289,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
-          ColumnSelection hiddenColumns, int width, int height)
+          HiddenColumns hiddenColumns, int width, int height)
   {
     setSize(width, height);
 
@@ -331,6 +336,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
     }
 
+    vpRanges = viewport.getRanges();
     avc = new jalview.controller.AlignViewController(this, viewport,
             alignPanel);
     if (viewport.getAlignmentConservationAnnotation() == null)
@@ -360,7 +366,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     setMenusFromViewport(viewport);
     buildSortByAnnotationScoresMenu();
-    buildTreeMenu();
+    calculateTree.addActionListener(new ActionListener()
+    {
+
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        openTreePcaDialog();
+      }
+    });
     buildColourMenu();
 
     if (Desktop.desktop != null)
@@ -640,8 +654,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   new String[] { (viewport.cursorMode ? "on" : "off") }));
           if (viewport.cursorMode)
           {
-            alignPanel.getSeqPanel().seqCanvas.cursorX = viewport.startRes;
-            alignPanel.getSeqPanel().seqCanvas.cursorY = viewport.startSeq;
+            alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges
+                    .getStartRes();
+            alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges
+                    .getStartSeq();
           }
           alignPanel.getSeqPanel().seqCanvas.repaint();
           break;
@@ -675,23 +691,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         case KeyEvent.VK_PAGE_UP:
           if (viewport.getWrapAlignment())
           {
-            alignPanel.scrollUp(true);
+            vpRanges.scrollUp(true);
           }
           else
           {
-            alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                    - viewport.endSeq + viewport.startSeq);
+            vpRanges.pageUp();
           }
           break;
         case KeyEvent.VK_PAGE_DOWN:
           if (viewport.getWrapAlignment())
           {
-            alignPanel.scrollUp(false);
+            vpRanges.scrollUp(false);
           }
           else
           {
-            alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                    + viewport.endSeq - viewport.startSeq);
+            vpRanges.pageDown();
           }
           break;
         }
@@ -1177,8 +1191,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               exportData.getAlignment(), // class cast exceptions will
               // occur in the distant future
               exportData.getOmitHidden(), exportData.getStartEndPostions(),
-              f.getCacheSuffixDefault(format),
-              viewport.getColumnSelection());
+              f.getCacheSuffixDefault(format), viewport.getAlignment()
+                      .getHiddenColumns());
 
       if (output == null)
       {
@@ -1256,8 +1270,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       cap.setText(new FormatAdapter(alignPanel, exportData.getSettings())
               .formatSequences(format, exportData.getAlignment(),
                       exportData.getOmitHidden(),
-                      exportData.getStartEndPostions(),
-                      viewport.getColumnSelection()));
+ exportData
+                              .getStartEndPostions(), viewport
+                              .getAlignment().getHiddenColumns()));
       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
               "label.alignment_output_command",
               new Object[] { e.getActionCommand() }), 600, 500);
@@ -1306,8 +1321,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       alignmentToExport = viewport.getAlignment();
     }
     alignmentStartEnd = alignmentToExport
-            .getVisibleStartAndEndIndex(viewport.getColumnSelection()
-                    .getHiddenColumns());
+            .getVisibleStartAndEndIndex(viewport.getAlignment()
+                    .getHiddenColumns()
+                    .getHiddenRegions());
     AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
             omitHidden, alignmentStartEnd, settings);
     return ed;
@@ -1871,7 +1887,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       hiddenColumns = new ArrayList<int[]>();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes(), hiddenCutoff = viewport
               .getSelectionGroup().getEndRes();
-      for (int[] region : viewport.getColumnSelection().getHiddenColumns())
+      for (int[] region : viewport.getAlignment().getHiddenColumns()
+              .getHiddenRegions())
       {
         if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
         {
@@ -2141,7 +2158,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
 
         // propagate alignment changed.
-        viewport.setEndSeq(alignment.getHeight());
+        vpRanges.setEndSeq(alignment.getHeight());
         if (annotationAdded)
         {
           // Duplicate sequence annotation in all views.
@@ -2545,7 +2562,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
                 column, viewport.getAlignment());
-        viewport.setStartRes(0);
+        vpRanges.setStartRes(0);
       }
       else
       {
@@ -2612,13 +2629,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    int startRes = seq.findPosition(vpRanges.getStartRes());
     // ShiftList shifts;
     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
     // edit.alColumnChanges=shifts.getInverse();
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
 
@@ -2651,12 +2668,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    int startRes = seq.findPosition(vpRanges.getStartRes());
 
     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
             viewport.getAlignment()));
 
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
 
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
@@ -2744,6 +2761,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      */
     newap.av.replaceMappings(viewport.getAlignment());
 
+    /*
+     * start up cDNA consensus (if applicable) now mappings are in place
+     */
+    if (newap.av.initComplementConsensus())
+    {
+      newap.refresh(true); // adjust layout of annotations
+    }
+
     newap.av.viewName = getNewViewName(viewTitle);
 
     addAlignmentPanel(newap, true);
@@ -3253,7 +3278,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     frame.setContentPane(overview);
     Desktop.addInternalFrame(frame, MessageManager.formatMessage(
             "label.overview_params", new Object[] { this.getTitle() }),
-            frame.getWidth(), frame.getHeight());
+            true, frame.getWidth(), frame.getHeight(), true, true);
     frame.pack();
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
@@ -3321,7 +3346,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      */
     if (ResidueColourScheme.USER_DEFINED.equals(name))
     {
-      new UserDefinedColours(alignPanel, null);
+      new UserDefinedColours(alignPanel);
       return;
     }
 
@@ -3342,10 +3367,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void changeColour(ColourSchemeI cs)
   {
     // TODO: pull up to controller method
-    if (cs != null)
-    {
-      ColourMenuHelper.setColourSelected(colourMenu, cs.getSchemeName());
-    }
+    ColourMenuHelper.setColourSelected(colourMenu, cs);
 
     viewport.setGlobalColourScheme(cs);
 
@@ -3431,7 +3453,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
     AlignmentSorter.sortByPID(viewport.getAlignment(), viewport
-            .getAlignment().getSequenceAt(0), null);
+            .getAlignment().getSequenceAt(0));
     addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
             viewport.getAlignment()));
     alignPanel.paintAlignment(true);
@@ -3525,35 +3547,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  @Override
-  public void PCAMenuItem_actionPerformed(ActionEvent e)
-  {
-    if (((viewport.getSelectionGroup() != null)
-            && (viewport.getSelectionGroup().getSize() < 4) && (viewport
-            .getSelectionGroup().getSize() > 0))
-            || (viewport.getAlignment().getHeight() < 4))
-    {
-      JvOptionPane
-              .showInternalMessageDialog(
-                      this,
-                      MessageManager
-                              .getString("label.principal_component_analysis_must_take_least_four_input_sequences"),
-                      MessageManager
-                              .getString("label.sequence_selection_insufficient"),
-                      JvOptionPane.WARNING_MESSAGE);
-
-      return;
-    }
-
-    new PCAPanel(alignPanel);
-  }
-
   @Override
   public void autoCalculate_actionPerformed(ActionEvent e)
   {
@@ -3578,83 +3571,24 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  @Override
-  public void averageDistanceTreeMenuItem_actionPerformed(ActionEvent e)
-  {
-    newTreePanel("AV", "PID", "Average distance tree using PID");
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  @Override
-  public void neighbourTreeMenuItem_actionPerformed(ActionEvent e)
-  {
-    newTreePanel("NJ", "PID", "Neighbour joining tree using PID");
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  @Override
-  protected void njTreeBlosumMenuItem_actionPerformed(ActionEvent e)
-  {
-    newTreePanel("NJ", "BL", "Neighbour joining tree using BLOSUM62");
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  @Override
-  protected void avTreeBlosumMenuItem_actionPerformed(ActionEvent e)
-  {
-    newTreePanel("AV", "BL", "Average distance tree using BLOSUM62");
-  }
-
-  /**
-   * DOCUMENT ME!
+   * Constructs a tree panel and adds it to the desktop
    * 
    * @param type
-   *          DOCUMENT ME!
-   * @param pwType
-   *          DOCUMENT ME!
-   * @param title
-   *          DOCUMENT ME!
+   *          tree type (NJ or AV)
+   * @param modelName
+   *          name of score model used to compute the tree
+   * @param options
+   *          parameters for the distance or similarity calculation
    */
-  void newTreePanel(String type, String pwType, String title)
+  void newTreePanel(String type, String modelName, SimilarityParamsI options)
   {
+    String frameTitle = "";
     TreePanel tp;
 
+    boolean onSelection = false;
     if (viewport.getSelectionGroup() != null
             && viewport.getSelectionGroup().getSize() > 0)
     {
-      if (viewport.getSelectionGroup().getSize() < 3)
-      {
-        JvOptionPane
-                .showMessageDialog(
-                        Desktop.desktop,
-                        MessageManager
-                                .getString("label.you_need_more_two_sequences_selected_build_tree"),
-                        MessageManager
-                                .getString("label.not_enough_sequences"),
-                        JvOptionPane.WARNING_MESSAGE);
-        return;
-      }
-
       SequenceGroup sg = viewport.getSelectionGroup();
 
       /* Decide if the selection is a column region */
@@ -3674,45 +3608,29 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           return;
         }
       }
-
-      title = title + " on region";
-      tp = new TreePanel(alignPanel, type, pwType);
+      onSelection = true;
     }
     else
     {
-      // are the visible sequences aligned?
-      if (!viewport.getAlignment().isAligned(false))
-      {
-        JvOptionPane
-                .showMessageDialog(
-                        Desktop.desktop,
-                        MessageManager
-                                .getString("label.sequences_must_be_aligned_before_creating_tree"),
-                        MessageManager
-                                .getString("label.sequences_not_aligned"),
-                        JvOptionPane.WARNING_MESSAGE);
-
-        return;
-      }
-
       if (viewport.getAlignment().getHeight() < 2)
       {
         return;
       }
-
-      tp = new TreePanel(alignPanel, type, pwType);
     }
 
-    title += " from ";
+    tp = new TreePanel(alignPanel, type, modelName, options);
+    frameTitle = tp.getPanelTitle() + (onSelection ? " on region" : "");
+
+    frameTitle += " from ";
 
     if (viewport.viewName != null)
     {
-      title += viewport.viewName + " of ";
+      frameTitle += viewport.viewName + " of ";
     }
 
-    title += this.title;
+    frameTitle += this.title;
 
-    Desktop.addInternalFrame(tp, title, 600, 500);
+    Desktop.addInternalFrame(tp, frameTitle, 600, 500);
   }
 
   /**
@@ -3834,48 +3752,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * call. Listeners are added to remove the menu item when the treePanel is
    * closed, and adjust the tree leaf to sequence mapping when the alignment is
    * modified.
-   * 
-   * @param treePanel
-   *          Displayed tree window.
-   * @param title
-   *          SortBy menu item title.
    */
   @Override
-  public void buildTreeMenu()
+  public void buildTreeSortMenu()
   {
-    calculateTree.removeAll();
-    // build the calculate menu
-
-    for (final String type : new String[] { "NJ", "AV" })
-    {
-      String treecalcnm = MessageManager.getString("label.tree_calc_"
-              + type.toLowerCase());
-      for (final String pwtype : ResidueProperties.scoreMatrices.keySet())
-      {
-        JMenuItem tm = new JMenuItem();
-        ScoreModelI sm = ResidueProperties.scoreMatrices.get(pwtype);
-        if (sm.isDNA() == viewport.getAlignment().isNucleotide()
-                || sm.isProtein() == !viewport.getAlignment()
-                        .isNucleotide())
-        {
-          String smn = MessageManager.getStringOrReturn(
-                  "label.score_model_", sm.getName());
-          final String title = MessageManager.formatMessage(
-                  "label.treecalc_title", treecalcnm, smn);
-          tm.setText(title);//
-          tm.addActionListener(new java.awt.event.ActionListener()
-          {
-            @Override
-            public void actionPerformed(ActionEvent e)
-            {
-              newTreePanel(type, pwtype, title);
-            }
-          });
-          calculateTree.add(tm);
-        }
-
-      }
-    }
     sortByTreeMenu.removeAll();
 
     List<Component> comps = PaintRefresher.components.get(viewport
@@ -4026,13 +3906,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
-      String choice = chooser.getSelectedFile().getPath();
-      jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
-      jalview.io.NewickFile fin = null;
+      String filePath = chooser.getSelectedFile().getPath();
+      Cache.setProperty("LAST_DIRECTORY", filePath);
+      NewickFile fin = null;
       try
       {
-        fin = new NewickFile(choice, DataSourceType.FILE);
-        viewport.setCurrentTree(ShowNewickTree(fin, choice).getTree());
+        fin = new NewickFile(filePath, DataSourceType.FILE);
+        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
       } catch (Exception ex)
       {
         JvOptionPane
@@ -4054,25 +3934,19 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-  public TreePanel ShowNewickTree(NewickFile nf, String title)
+  public TreePanel showNewickTree(NewickFile nf, String treeTitle)
   {
-    return ShowNewickTree(nf, title, 600, 500, 4, 5);
+    return showNewickTree(nf, treeTitle, 600, 500, 4, 5);
   }
 
-  public TreePanel ShowNewickTree(NewickFile nf, String title,
-          AlignmentView input)
-  {
-    return ShowNewickTree(nf, title, input, 600, 500, 4, 5);
-  }
-
-  public TreePanel ShowNewickTree(NewickFile nf, String title, int w,
+  public TreePanel showNewickTree(NewickFile nf, String treeTitle, int w,
           int h, int x, int y)
   {
-    return ShowNewickTree(nf, title, null, w, h, x, y);
+    return showNewickTree(nf, treeTitle, null, w, h, x, y);
   }
 
   /**
-   * Add a treeviewer for the tree extracted from a newick file object to the
+   * Add a treeviewer for the tree extracted from a Newick file object to the
    * current alignment view
    * 
    * @param nf
@@ -4091,7 +3965,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          position
    * @return TreePanel handle
    */
-  public TreePanel ShowNewickTree(NewickFile nf, String title,
+  public TreePanel showNewickTree(NewickFile nf, String treeTitle,
           AlignmentView input, int w, int h, int x, int y)
   {
     TreePanel tp = null;
@@ -4102,7 +3976,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
       if (nf.getTree() != null)
       {
-        tp = new TreePanel(alignPanel, "FromFile", title, nf, input);
+        tp = new TreePanel(alignPanel, nf, treeTitle, input);
 
         tp.setSize(w, h);
 
@@ -4111,7 +3985,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           tp.setLocation(x, y);
         }
 
-        Desktop.addInternalFrame(tp, title, w, h);
+        Desktop.addInternalFrame(tp, treeTitle, w, h);
       }
     } catch (Exception ex)
     {
@@ -4684,10 +4558,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
-   * Attempt to load a "dropped" file or URL string: First by testing whether
-   * it's an Annotation file, then a JNet file, and finally a features file. If
-   * all are false then the user may have dropped an alignment file onto this
-   * AlignFrame.
+   * Attempt to load a "dropped" file or URL string, by testing in turn for
+   * <ul>
+   * <li>an Annotation file</li>
+   * <li>a JNet file</li>
+   * <li>a features file</li>
+   * <li>else try to interpret as an alignment file</li>
+   * </ul>
    * 
    * @param file
    *          either a filename or a URL string.
@@ -4761,7 +4638,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           {
             format = new IdentifyFile().identify(file, sourceType);
           }
-          if (FileFormat.Jnet.equals(format))
+          if (FileFormat.ScoreMatrix == format)
+          {
+            ScoreMatrixFile sm = new ScoreMatrixFile(new FileParse(file,
+                    sourceType));
+            sm.parse();
+            // todo: i18n this message
+            statusBar
+                    .setText(MessageManager.formatMessage(
+                            "label.successfully_loaded_matrix",
+                            sm.getMatrixName()));
+          }
+          else if (FileFormat.Jnet.equals(format))
           {
             JPredFile predictions = new JPredFile(file, sourceType);
             new JnetAnnotationMaker();
@@ -4769,9 +4657,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                     viewport.getAlignment(), 0, false);
             SequenceI repseq = viewport.getAlignment().getSequenceAt(0);
             viewport.getAlignment().setSeqrep(repseq);
-            ColumnSelection cs = new ColumnSelection();
+            HiddenColumns cs = new HiddenColumns();
             cs.hideInsertionsFor(repseq);
-            viewport.setColumnSelection(cs);
+            viewport.getAlignment().setHiddenColumns(cs);
             isAnnotation = true;
           }
           // else if (IdentifyFile.FeaturesFile.equals(format))
@@ -5694,10 +5582,19 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     colourMenu.add(annotationColour);
 
     ColourSchemeI colourScheme = viewport.getGlobalColourScheme();
-    String schemeName = colourScheme == null ? null : colourScheme
-            .getSchemeName();
+    ColourMenuHelper.setColourSelected(colourMenu, colourScheme);
+  }
 
-    ColourMenuHelper.setColourSelected(colourMenu, schemeName);
+  /**
+   * Open a dialog (if not already open) that allows the user to select and
+   * calculate PCA or Tree analysis
+   */
+  protected void openTreePcaDialog()
+  {
+    if (alignPanel.getCalculationDialog() == null)
+    {
+      new CalculationChooser(AlignFrame.this);
+    }
   }
 }
 
index e0efa7c..86e1144 100644 (file)
@@ -22,7 +22,7 @@ package jalview.gui;
 
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
-import jalview.analysis.NJTree;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureColourI;
@@ -35,6 +35,7 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -46,7 +47,6 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.UserColourScheme;
-import jalview.structure.CommandListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
@@ -57,6 +57,7 @@ import jalview.ws.params.AutoCalcSetting;
 import java.awt.Container;
 import java.awt.Dimension;
 import java.awt.Font;
+import java.awt.FontMetrics;
 import java.awt.Rectangle;
 import java.util.ArrayList;
 import java.util.Hashtable;
@@ -72,11 +73,11 @@ import javax.swing.JInternalFrame;
  * @version $Revision: 1.141 $
  */
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, CommandListener
+        SelectionSource
 {
   Font font;
 
-  NJTree currentTree = null;
+  TreeModel currentTree = null;
 
   boolean cursorMode = false;
 
@@ -105,7 +106,7 @@ public class AlignViewport extends AlignmentViewport implements
    */
   public AlignViewport(AlignmentI al)
   {
-    setAlignment(al);
+    super(al);
     init();
   }
 
@@ -123,6 +124,7 @@ public class AlignViewport extends AlignmentViewport implements
 
   public AlignViewport(AlignmentI al, String seqsetid, String viewid)
   {
+    super(al);
     sequenceSetID = seqsetid;
     viewId = viewid;
     // TODO remove these once 2.4.VAMSAS release finished
@@ -135,8 +137,8 @@ public class AlignViewport extends AlignmentViewport implements
     {
       Cache.log.debug("Setting viewport's view id : " + viewId);
     }
-    setAlignment(al);
     init();
+
   }
 
   /**
@@ -147,12 +149,12 @@ public class AlignViewport extends AlignmentViewport implements
    * @param hiddenColumns
    *          ColumnSelection
    */
-  public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
+  public AlignViewport(AlignmentI al, HiddenColumns hiddenColumns)
   {
-    setAlignment(al);
+    super(al);
     if (hiddenColumns != null)
     {
-      colSel = hiddenColumns;
+      al.setHiddenColumns(hiddenColumns);
     }
     init();
   }
@@ -165,7 +167,7 @@ public class AlignViewport extends AlignmentViewport implements
    * @param seqsetid
    *          (may be null)
    */
-  public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
+  public AlignViewport(AlignmentI al, HiddenColumns hiddenColumns,
           String seqsetid)
   {
     this(al, hiddenColumns, seqsetid, null);
@@ -181,9 +183,10 @@ public class AlignViewport extends AlignmentViewport implements
    * @param viewid
    *          (may be null)
    */
-  public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
+  public AlignViewport(AlignmentI al, HiddenColumns hiddenColumns,
           String seqsetid, String viewid)
   {
+    super(al);
     sequenceSetID = seqsetid;
     viewId = viewid;
     // TODO remove these once 2.4.VAMSAS release finished
@@ -196,10 +199,10 @@ public class AlignViewport extends AlignmentViewport implements
     {
       Cache.log.debug("Setting viewport's view id : " + viewId);
     }
-    setAlignment(al);
+
     if (hiddenColumns != null)
     {
-      colSel = hiddenColumns;
+      al.setHiddenColumns(hiddenColumns);
     }
     init();
   }
@@ -238,10 +241,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   void init()
   {
-    this.startRes = 0;
-    this.endRes = alignment.getWidth() - 1;
-    this.startSeq = 0;
-    this.endSeq = alignment.getHeight() - 1;
     applyViewProperties();
 
     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
@@ -282,6 +281,8 @@ public class AlignViewport extends AlignmentViewport implements
               false);
       showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
       showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
+
+      showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true);
     }
     initAutoAnnotation();
     String colourProperty = alignment.isNucleotide() ? Preferences.DEFAULT_COLOUR_NUC
@@ -351,23 +352,19 @@ public class AlignViewport extends AlignmentViewport implements
   boolean validCharWidth;
 
   /**
-   * update view settings with the given font. You may need to call
-   * alignPanel.fontChanged to update the layout geometry
-   * 
-   * @param setGrid
-   *          when true, charWidth/height is set according to font mentrics
+   * {@inheritDoc}
    */
+  @Override
   public void setFont(Font f, boolean setGrid)
   {
     font = f;
 
     Container c = new Container();
 
-    java.awt.FontMetrics fm = c.getFontMetrics(font);
-    int w = viewStyle.getCharWidth(), ww = fm.charWidth('M'), h = viewStyle
-            .getCharHeight();
     if (setGrid)
     {
+      FontMetrics fm = c.getFontMetrics(font);
+      int ww = fm.charWidth('M');
       setCharHeight(fm.getHeight());
       setCharWidth(ww);
     }
@@ -494,7 +491,7 @@ public class AlignViewport extends AlignmentViewport implements
    * @param tree
    *          DOCUMENT ME!
    */
-  public void setCurrentTree(NJTree tree)
+  public void setCurrentTree(TreeModel tree)
   {
     currentTree = tree;
   }
@@ -504,7 +501,7 @@ public class AlignViewport extends AlignmentViewport implements
    * 
    * @return DOCUMENT ME!
    */
-  public NJTree getCurrentTree()
+  public TreeModel getCurrentTree()
   {
     return currentTree;
   }
@@ -530,7 +527,7 @@ public class AlignViewport extends AlignmentViewport implements
     {
       end = alignment.getWidth();
     }
-    viscontigs = colSel.getVisibleContigs(start, end);
+    viscontigs = alignment.getHiddenColumns().getVisibleContigs(start, end);
     return viscontigs;
   }
 
@@ -600,7 +597,9 @@ public class AlignViewport extends AlignmentViewport implements
     jalview.structure.StructureSelectionManager
             .getStructureSelectionManager(Desktop.instance).sendSelection(
                     new SequenceGroup(getSelectionGroup()),
-                    new ColumnSelection(getColumnSelection()), this);
+                    new ColumnSelection(getColumnSelection()),
+                    new HiddenColumns(getAlignment().getHiddenColumns()),
+                    this);
   }
 
   /**
@@ -855,7 +854,7 @@ public class AlignViewport extends AlignmentViewport implements
       }
     }
 
-    setEndSeq(getAlignment().getHeight());
+    ranges.setEndSeq(getAlignment().getHeight());
     firePropertyChange("alignment", null, getAlignment().getSequences());
   }
 
@@ -1050,8 +1049,9 @@ public class AlignViewport extends AlignmentViewport implements
       // TODO would like next line without cast but needs more refactoring...
       final AlignmentPanel complementPanel = ((AlignViewport) getCodingComplement())
               .getAlignPanel();
-      complementPanel.setDontScrollComplement(true);
+      complementPanel.setToScrollComplementPanel(false);
       complementPanel.scrollToCentre(sr, verticalOffset);
+      complementPanel.setToScrollComplementPanel(true);
     }
   }
 
index e61b042..395f6b3 100644 (file)
@@ -25,6 +25,7 @@ import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -35,6 +36,8 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -46,6 +49,8 @@ import java.awt.Graphics;
 import java.awt.Insets;
 import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
 import java.awt.print.PageFormat;
 import java.awt.print.Printable;
 import java.awt.print.PrinterException;
@@ -65,10 +70,13 @@ import javax.swing.SwingUtilities;
  * @version $Revision: 1.161 $
  */
 public class AlignmentPanel extends GAlignmentPanel implements
-        AdjustmentListener, Printable, AlignmentViewPanel
+        AdjustmentListener, Printable, AlignmentViewPanel,
+        ViewportListenerI
 {
   public AlignViewport av;
 
+  ViewportRanges vpRanges;
+
   OverviewPanel overviewPanel;
 
   private SeqPanel seqPanel;
@@ -91,18 +99,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
 
-  int hextent = 0;
+  private int hextent = 0;
 
-  int vextent = 0;
+  private int vextent = 0;
 
   /*
    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
-   * true, suppresses invoking the same method recursively.
+   * false, suppresses invoking the same method recursively.
    */
-  private boolean dontScrollComplement;
+  private boolean scrollComplementaryPanel = true;
 
   private PropertyChangeListener propertyChangeListener;
 
+  private CalculationChooser calculationDialog;
+
   /**
    * Creates a new AlignmentPanel object.
    * 
@@ -113,6 +123,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     alignFrame = af;
     this.av = av;
+    vpRanges = av.getRanges();
     setSeqPanel(new SeqPanel(av, this));
     setIdPanel(new IdPanel(av, this));
 
@@ -136,6 +147,34 @@ public class AlignmentPanel extends GAlignmentPanel implements
     hscroll.addAdjustmentListener(this);
     vscroll.addAdjustmentListener(this);
 
+    addComponentListener(new ComponentAdapter()
+    {
+      @Override
+      public void componentResized(ComponentEvent evt)
+      {
+        // reset the viewport ranges when the alignment panel is resized
+        // in particular, this initialises the end residue value when Jalview
+        // is initialised
+        if (av.getWrapAlignment())
+        {
+          int widthInRes = getSeqPanel().seqCanvas
+                  .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+          vpRanges.setViewportWidth(widthInRes);
+        }
+        else
+        {
+          int widthInRes = getSeqPanel().seqCanvas.getWidth()
+                  / av.getCharWidth();
+          int heightInSeq = getSeqPanel().seqCanvas.getHeight()
+                  / av.getCharHeight();
+          
+          vpRanges.setViewportWidth(widthInRes);
+          vpRanges.setViewportHeight(heightInSeq);
+        }
+      }
+
+    });
+
     final AlignmentPanel ap = this;
     propertyChangeListener = new PropertyChangeListener()
     {
@@ -150,6 +189,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
     };
     av.addPropertyChangeListener(propertyChangeListener);
+
+    av.getRanges().addPropertyChangeListener(this);
     fontChanged();
     adjustAnnotationHeight();
     updateLayout();
@@ -165,6 +206,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     av.alignmentChanged(this);
 
+    if (getCalculationDialog() != null)
+    {
+      getCalculationDialog().validateCalcTypes();
+    }
+
     alignFrame.updateEditMenuBar();
 
     paintAlignment(true);
@@ -195,10 +241,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
     getIdPanel().getIdCanvas().setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(d);
 
-    if (overviewPanel != null)
-    {
-      overviewPanel.setBoxPosition();
-    }
     if (this.alignFrame.getSplitViewContainer() != null)
     {
       ((SplitFrame) this.alignFrame.getSplitViewContainer()).adjustLayout();
@@ -367,9 +409,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
       int start = r[0];
       int end = r[1];
-      // DEBUG
-      // System.err.println(this.av.viewName + " Seq : " + seqIndex
-      // + " Scroll to " + start + "," + end);
 
       /*
        * To centre results, scroll to positions half the visible width
@@ -377,7 +416,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
        */
       if (centre)
       {
-        int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
+        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2
+                - 1;
         start = Math.max(start - offset, 0);
         end = end + offset - 1;
       }
@@ -391,11 +431,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
       if (av.hasHiddenColumns())
       {
-        start = av.getColumnSelection().findColumnPosition(start);
-        end = av.getColumnSelection().findColumnPosition(end);
+        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+        start = hidden.findColumnPosition(start);
+        end = hidden.findColumnPosition(end);
         if (start == end)
         {
-          if (!av.getColumnSelection().isVisible(r[0]))
+          if (!hidden.isVisible(r[0]))
           {
             // don't scroll - position isn't visible
             return false;
@@ -408,40 +449,36 @@ public class AlignmentPanel extends GAlignmentPanel implements
        */
       seqIndex = Math.max(0, seqIndex - verticalOffset);
 
-      // System.out.println("start=" + start + ", end=" + end + ", startv="
-      // + av.getStartRes() + ", endv=" + av.getEndRes() + ", starts="
-      // + av.getStartSeq() + ", ends=" + av.getEndSeq());
       if (!av.getWrapAlignment())
       {
-        if ((startv = av.getStartRes()) >= start)
+        if ((startv = vpRanges.getStartRes()) >= start)
         {
           /*
            * Scroll left to make start of search results visible
            */
-          // setScrollValues(start - 1, seqIndex); // plus one residue
           setScrollValues(start, seqIndex);
         }
-        else if ((endv = av.getEndRes()) <= end)
+        else if ((endv = vpRanges.getEndRes()) <= end)
         {
           /*
            * Scroll right to make end of search results visible
            */
-          // setScrollValues(startv + 1 + end - endv, seqIndex); // plus one
           setScrollValues(startv + end - endv, seqIndex);
         }
-        else if ((starts = av.getStartSeq()) > seqIndex)
+        else if ((starts = vpRanges.getStartSeq()) > seqIndex)
         {
           /*
            * Scroll up to make start of search results visible
            */
-          setScrollValues(av.getStartRes(), seqIndex);
+          setScrollValues(vpRanges.getStartRes(), seqIndex);
         }
-        else if ((ends = av.getEndSeq()) <= seqIndex)
+        else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
         {
           /*
            * Scroll down to make end of search results visible
            */
-          setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
+          setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                  + 1);
         }
         /*
          * Else results are already visible - no need to scroll
@@ -449,29 +486,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
       else
       {
-        scrollToWrappedVisible(start);
+        vpRanges.scrollToWrappedVisible(start);
       }
     }
-    if (redrawOverview && overviewPanel != null)
-    {
-      overviewPanel.setBoxPosition();
-    }
+
     paintAlignment(redrawOverview);
     return true;
   }
 
-  void scrollToWrappedVisible(int res)
-  {
-    int cwidth = getSeqPanel().seqCanvas
-            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-    if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
-    {
-      vscroll.setValue((res / cwidth));
-      av.startRes = vscroll.getValue() * cwidth;
-    }
-
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -591,7 +613,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     fontChanged();
     setAnnotationVisible(av.isShowAnnotation());
     boolean wrap = av.getWrapAlignment();
-    av.startSeq = 0;
+    vpRanges.setStartSeq(0);
     scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
     idwidthAdjuster.setVisible(!wrap);
@@ -607,75 +629,28 @@ public class AlignmentPanel extends GAlignmentPanel implements
       annotationSpaceFillerHolder.setVisible(true);
     }
 
-    idSpaceFillerPanel1.setVisible(!wrap);
-
-    repaint();
-  }
-
-  // return value is true if the scroll is valid
-  public boolean scrollUp(boolean up)
-  {
-    if (up)
+    if (wrap)
     {
-      if (vscroll.getValue() < 1)
-      {
-        return false;
-      }
-
-      fastPaint = false;
-      vscroll.setValue(vscroll.getValue() - 1);
+      int widthInRes = getSeqPanel().seqCanvas
+              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+      vpRanges.setViewportWidth(widthInRes);
     }
     else
     {
-      if ((vextent + vscroll.getValue()) >= av.getAlignment().getHeight())
-      {
-        return false;
-      }
+      int widthInRes = (getSeqPanel().seqCanvas.getWidth() / av
+              .getCharWidth()) - 1;
+      int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
+              .getCharHeight()) - 1;
 
-      fastPaint = false;
-      vscroll.setValue(vscroll.getValue() + 1);
+      vpRanges.setViewportWidth(widthInRes);
+      vpRanges.setViewportHeight(heightInSeq);
     }
 
-    fastPaint = true;
+    idSpaceFillerPanel1.setVisible(!wrap);
 
-    return true;
+    repaint();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param right
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public boolean scrollRight(boolean right)
-  {
-    if (!right)
-    {
-      if (hscroll.getValue() < 1)
-      {
-        return false;
-      }
-
-      fastPaint = false;
-      hscroll.setValue(hscroll.getValue() - 1);
-    }
-    else
-    {
-      if ((hextent + hscroll.getValue()) >= av.getAlignment().getWidth())
-      {
-        return false;
-      }
-
-      fastPaint = false;
-      hscroll.setValue(hscroll.getValue() + 1);
-    }
-
-    fastPaint = true;
-
-    return true;
-  }
 
   /**
    * Adjust row/column scrollers to show a visible position in the alignment.
@@ -686,172 +661,170 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          visible row to scroll to
    * 
    */
-  public void setScrollValues(int x, int y)
+  public void setScrollValues(int xpos, int ypos)
   {
-    // System.err.println("Scroll " + this.av.viewName + " to " + x + "," + y);
+    int x = xpos;
+    int y = ypos;
+
     if (av == null || av.getAlignment() == null)
     {
       return;
     }
-    int width = av.getAlignment().getWidth();
-    int height = av.getAlignment().getHeight();
 
-    if (av.hasHiddenColumns())
+    if (av.getWrapAlignment())
     {
-      width = av.getColumnSelection().findColumnPosition(width);
+      setScrollingForWrappedPanel(x);
     }
+    else
+    {
+      int width = av.getAlignment().getWidth();
+      int height = av.getAlignment().getHeight();
 
-    av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
-            .getCharWidth())) - 1);
+      if (av.hasHiddenColumns())
+      {
+        // reset the width to exclude hidden columns
+        width = av.getAlignment().getHiddenColumns().findColumnPosition(width);
+      }
 
-    hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
-    vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
+      hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
+      vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
-    if (hextent > width)
-    {
-      hextent = width;
-    }
+      if (hextent > width)
+      {
+        hextent = width;
+      }
 
-    if (vextent > height)
-    {
-      vextent = height;
-    }
+      if (vextent > height)
+      {
+        vextent = height;
+      }
 
-    if ((hextent + x) > width)
-    {
-      x = width - hextent;
-    }
+      if ((hextent + x) > width)
+      {
+        x = width - hextent;
+      }
 
-    if ((vextent + y) > height)
-    {
-      y = height - vextent;
-    }
+      if ((vextent + y) > height)
+      {
+        y = height - vextent;
+      }
 
-    if (y < 0)
-    {
-      y = 0;
-    }
+      if (y < 0)
+      {
+        y = 0;
+      }
 
-    if (x < 0)
-    {
-      x = 0;
-    }
+      if (x < 0)
+      {
+        x = 0;
+      }
 
-    /*
-     * each scroll adjustment triggers adjustmentValueChanged, which resets the
-     * 'do not scroll complement' flag; ensure it is the same for both
-     * operations
-     */
-    boolean flag = isDontScrollComplement();
-    hscroll.setValues(x, hextent, 0, width);
-    setDontScrollComplement(flag);
-    vscroll.setValues(y, vextent, 0, height);
+      // update the scroll values
+      hscroll.setValues(x, hextent, 0, width);
+      vscroll.setValues(y, vextent, 0, height);
+    }
   }
 
   /**
-   * DOCUMENT ME!
+   * Respond to adjustment event when horizontal or vertical scrollbar is
+   * changed
    * 
    * @param evt
-   *          DOCUMENT ME!
+   *          adjustment event encoding whether hscroll or vscroll changed
    */
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = av.getStartRes();
-    int oldY = av.getStartSeq();
-
-    if (evt.getSource() == hscroll)
-    {
-      int x = hscroll.getValue();
-      av.setStartRes(x);
-      av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
-              .getCharWidth())) - 1);
-    }
+    int oldX = vpRanges.getStartRes();
+    int oldwidth = vpRanges.getViewportWidth();
+    int oldY = vpRanges.getStartSeq();
+    int oldheight = vpRanges.getViewportHeight();
 
-    if (evt.getSource() == vscroll)
+    if (av.getWrapAlignment())
     {
-      int offy = vscroll.getValue();
-
-      if (av.getWrapAlignment())
+      if (evt.getSource() == hscroll)
+      {
+        return; // no horizontal scroll when wrapped
+      }
+      else if (evt.getSource() == vscroll)
       {
-        if (offy > -1)
+        int offy = vscroll.getValue();
+        int rowSize = getSeqPanel().seqCanvas
+                .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+
+        // if we're scrolling to the position we're already at, stop
+        // this prevents infinite recursion of events when the scroll/viewport
+        // ranges values are the same
+        if ((offy * rowSize == oldX) && (oldwidth == rowSize))
         {
-          int rowSize = getSeqPanel().seqCanvas
-                  .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-          av.setStartRes(offy * rowSize);
-          av.setEndRes((offy + 1) * rowSize);
+          return;
         }
-        else
+        else if (offy > -1)
         {
-          // This is only called if file loaded is a jar file that
-          // was wrapped when saved and user has wrap alignment true
-          // as preference setting
-          SwingUtilities.invokeLater(new Runnable()
-          {
-            @Override
-            public void run()
-            {
-              setScrollValues(av.getStartRes(), av.getStartSeq());
-            }
-          });
+          vpRanges.setViewportStartAndWidth(offy * rowSize, rowSize);
         }
       }
       else
       {
-        av.setStartSeq(offy);
-        av.setEndSeq(offy
-                + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()));
+        // This is only called if file loaded is a jar file that
+        // was wrapped when saved and user has wrap alignment true
+        // as preference setting
+        SwingUtilities.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
+        {
+            // When updating scrolling to use ViewportChange events, this code
+            // could not be validated and it is not clear if it is now being
+            // called. Log warning here in case it is called and unforeseen
+            // problems occur
+            Cache.log
+                    .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
+
+            // scroll to start of panel
+            vpRanges.setStartRes(0);
+            vpRanges.setStartSeq(0);
+          }
+        });
       }
-    }
-
-    if (overviewPanel != null)
-    {
-      overviewPanel.setBoxPosition();
-    }
-
-    int scrollX = av.startRes - oldX;
-    int scrollY = av.startSeq - oldY;
-
-    if (av.getWrapAlignment() || !fastPaint)
-    {
       repaint();
     }
     else
     {
-      // Make sure we're not trying to draw a panel
-      // larger than the visible window
-      if (scrollX > av.endRes - av.startRes)
-      {
-        scrollX = av.endRes - av.startRes;
-      }
-      else if (scrollX < av.startRes - av.endRes)
+      // horizontal scroll
+      if (evt.getSource() == hscroll)
       {
-        scrollX = av.startRes - av.endRes;
-      }
+        int x = hscroll.getValue();
+        int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 
-      if (scrollX != 0 || scrollY != 0)
+        // if we're scrolling to the position we're already at, stop
+        // this prevents infinite recursion of events when the scroll/viewport
+        // ranges values are the same
+        if ((x == oldX) && (width == oldwidth))
+        {
+          return;
+        }
+        vpRanges.setViewportStartAndWidth(x, width);
+      }
+      else if (evt.getSource() == vscroll)
       {
-        getIdPanel().getIdCanvas().fastPaint(scrollY);
-        getSeqPanel().seqCanvas.fastPaint(scrollX, scrollY);
-        getScalePanel().repaint();
-
-        if (av.isShowAnnotation() && scrollX != 0)
+        int y = vscroll.getValue();
+        int height = getSeqPanel().seqCanvas.getHeight()
+                / av.getCharHeight();
+
+        // if we're scrolling to the position we're already at, stop
+        // this prevents infinite recursion of events when the scroll/viewport
+        // ranges values are the same
+        if ((y == oldY) && (height == oldheight))
         {
-          getAnnotationPanel().fastPaint(scrollX);
+          return;
         }
+        vpRanges.setViewportStartAndHeight(y, height);
+      }
+      if (!fastPaint)
+      {
+        repaint();
       }
-    }
-    /*
-     * If there is one, scroll the (Protein/cDNA) complementary alignment to
-     * match, unless we are ourselves doing that.
-     */
-    if (isDontScrollComplement())
-    {
-      setDontScrollComplement(false);
-    }
-    else
-    {
-      av.scrollComplementaryAlignment();
     }
   }
 
@@ -897,36 +870,39 @@ public class AlignmentPanel extends GAlignmentPanel implements
     validate();
 
     /*
-     * set scroll bar positions; first suppress this being 'followed' in any
-     * complementary split pane
+     * set scroll bar positions
      */
-    setDontScrollComplement(true);
+    setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
+  }
 
-    if (av.getWrapAlignment())
+  /*
+   * Set vertical scroll bar parameters for wrapped panel
+   * @param res 
+   *    the residue to scroll to
+   */
+  private void setScrollingForWrappedPanel(int res)
+  {
+    // get the width of the alignment in residues
+    int maxwidth = av.getAlignment().getWidth();
+    if (av.hasHiddenColumns())
     {
-      int maxwidth = av.getAlignment().getWidth();
-
-      if (av.hasHiddenColumns())
-      {
-        maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
-      }
-
-      int canvasWidth = getSeqPanel().seqCanvas
-              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-      if (canvasWidth > 0)
-      {
-        int max = maxwidth
-                / getSeqPanel().seqCanvas
-                        .getWrappedCanvasWidth(getSeqPanel().seqCanvas
-                                .getWidth()) + 1;
-        vscroll.setMaximum(max);
-        vscroll.setUnitIncrement(1);
-        vscroll.setVisibleAmount(1);
-      }
+        maxwidth = av.getAlignment().getHiddenColumns()
+                .findColumnPosition(maxwidth) - 1;
     }
-    else
+
+    // get the width of the canvas in residues
+    int canvasWidth = getSeqPanel().seqCanvas
+            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+    if (canvasWidth > 0)
     {
-      setScrollValues(av.getStartRes(), av.getStartSeq());
+      // position we want to scroll to is number of canvasWidth's to get there
+      int current = res / canvasWidth;
+
+      // max scroll position: add one because extent is 1 and scrollbar value
+      // can only be set to at most max - extent
+      int max = maxwidth / canvasWidth + 1;
+      vscroll.setUnitIncrement(1);
+      vscroll.setValues(current, 1, 0, max);
     }
   }
 
@@ -1196,7 +1172,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
@@ -1390,7 +1367,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth);
     }
 
     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
@@ -1620,7 +1598,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
@@ -1638,6 +1617,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
     PaintRefresher.RemoveComponent(this);
 
+    closeChildFrames();
+
     /*
      * try to ensure references are nulled
      */
@@ -1669,6 +1650,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
+   * Close any open dialogs that would be orphaned when this one is closed
+   */
+  protected void closeChildFrames()
+  {
+    if (calculationDialog != null)
+    {
+      calculationDialog.closeFrame();
+    }
+  }
+
+  /**
    * hides or shows dynamic annotation rows based on groups and av state flags
    */
   public void updateAnnotation()
@@ -1866,13 +1858,82 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * 
    * @param b
    */
-  protected void setDontScrollComplement(boolean b)
+  protected void setToScrollComplementPanel(boolean b)
   {
-    this.dontScrollComplement = b;
+    this.scrollComplementaryPanel = b;
   }
 
-  protected boolean isDontScrollComplement()
+  /**
+   * Get whether to scroll complement panel
+   * 
+   * @return true if cDNA/protein complement panels should be scrolled
+   */
+  protected boolean isSetToScrollComplementPanel()
+  {
+    return this.scrollComplementaryPanel;
+  }
+
+  /**
+   * Redraw sensibly.
+   * 
+   * @adjustHeight if true, try to recalculate panel height for visible
+   *               annotations
+   */
+  protected void refresh(boolean adjustHeight)
+  {
+    validateAnnotationDimensions(adjustHeight);
+    addNotify();
+    if (adjustHeight)
+    {
+      // sort, repaint, update overview
+      paintAlignment(true);
+    }
+    else
+    {
+      // lightweight repaint
+      repaint();
+    }
+  }
+
+  @Override
+  /**
+   * Property change event fired when a change is made to the viewport ranges 
+   * object associated with this alignment panel's viewport
+   */
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // update this panel's scroll values based on the new viewport ranges values
+    int x = vpRanges.getStartRes();
+    int y = vpRanges.getStartSeq();
+    setScrollValues(x, y);
+
+    // now update any complementary alignment (its viewport ranges object
+    // is different so does not get automatically updated)
+    if (isSetToScrollComplementPanel())
+    {
+      setToScrollComplementPanel(false);
+      av.scrollComplementaryAlignment();
+      setToScrollComplementPanel(true);
+    }
+  }
+
+  /**
+   * Set the reference to the PCA/Tree chooser dialog for this panel. This
+   * reference should be nulled when the dialog is closed.
+   * 
+   * @param calculationChooser
+   */
+  public void setCalculationDialog(CalculationChooser calculationChooser)
+  {
+    calculationDialog = calculationChooser;
+  }
+
+  /**
+   * Returns the reference to the PCA/Tree chooser dialog for this panel (null
+   * if none is open)
+   */
+  public CalculationChooser getCalculationDialog()
   {
-    return this.dontScrollComplement;
+    return calculationDialog;
   }
 }
index f6352d7..253a7ec 100644 (file)
@@ -21,6 +21,8 @@
 package jalview.gui;
 
 import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.GraphLine;
 import jalview.datamodel.SequenceGroup;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
@@ -35,9 +37,11 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Hashtable;
+import java.util.Vector;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
 import javax.swing.JInternalFrame;
@@ -49,27 +53,25 @@ import net.miginfocom.swing.MigLayout;
 @SuppressWarnings("serial")
 public class AnnotationColourChooser extends AnnotationRowFilter
 {
+  private static final int ONETHOUSAND = 1000;
 
-  ColourSchemeI oldcs;
+  private ColourSchemeI oldcs;
 
-  Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
+  private JButton defColours;
 
-  /**
-   * enabled if the user is dragging the slider - try to keep updates to a
-   * minimun
-   */
+  private Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
 
-  JComboBox<String> annotations;
+  private JCheckBox useOriginalColours = new JCheckBox();
 
-  JButton defColours = new JButton();
+  private JPanel minColour = new JPanel();
 
-  JPanel jPanel1 = new JPanel();
+  private JPanel maxColour = new JPanel();
 
-  JPanel jPanel2 = new JPanel();
+  private JCheckBox thresholdIsMin = new JCheckBox();
 
-  BorderLayout borderLayout1 = new BorderLayout();
+  protected static final int MIN_WIDTH = 500;
 
-  private JComboBox<String> threshold = new JComboBox<String>();
+  protected static final int MIN_HEIGHT = 240;
 
   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
   {
@@ -92,7 +94,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     Desktop.addInternalFrame(frame,
             MessageManager.getString("label.colour_by_annotation"), 520,
             215);
-
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
     addSliderChangeListener();
     addSliderMouseListeners();
 
@@ -108,7 +110,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      currentColours.setSelected(acg.isPredefinedColours()
+      useOriginalColours.setSelected(acg.isPredefinedColours()
               || acg.getBaseColour() != null);
       if (!acg.isPredefinedColours() && acg.getBaseColour() == null)
       {
@@ -118,15 +120,17 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       seqAssociated.setSelected(acg.isSeqAssociated());
 
     }
-    annotations = new JComboBox<String>(
-            getAnnotationItems(seqAssociated.isSelected()));
+    Vector<String> annotItems = getAnnotationItems(seqAssociated
+            .isSelected());
+    annotations = new JComboBox<String>(annotItems);
 
     populateThresholdComboBox(threshold);
 
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      annotations.setSelectedItem(acg.getAnnotation());
+      String label = getAnnotationMenuLabel(acg.getAnnotation());
+      annotations.setSelectedItem(label);
       switch (acg.getAboveThreshold())
       {
       case AnnotationColourGradient.NO_THRESHOLD:
@@ -143,16 +147,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter
                 MessageManager
                         .getString("error.implementation_error_dont_know_about_threshold_setting"));
       }
-      thresholdIsMin.setSelected(acg.thresholdIsMinMax);
+      thresholdIsMin.setSelected(acg.isThresholdIsMinMax());
       thresholdValue.setText("" + acg.getAnnotationThreshold());
     }
 
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-    }
+    jbInit();
     adjusting = false;
 
     updateView();
@@ -160,19 +159,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     frame.pack();
   }
 
-  public AnnotationColourChooser()
+  @Override
+  protected void jbInit()
   {
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
+    super.jbInit();
 
-  private void jbInit() throws Exception
-  {
     minColour.setFont(JvSwingUtils.getLabelFont());
     minColour.setBorder(BorderFactory.createEtchedBorder());
     minColour.setPreferredSize(new Dimension(40, 20));
@@ -203,26 +194,8 @@ public class AnnotationColourChooser extends AnnotationRowFilter
         }
       }
     });
-    ok.setOpaque(false);
-    ok.setText(MessageManager.getString("action.ok"));
-    ok.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        ok_actionPerformed();
-      }
-    });
-    cancel.setOpaque(false);
-    cancel.setText(MessageManager.getString("action.cancel"));
-    cancel.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        cancel_actionPerformed();
-      }
-    });
+
+    defColours = new JButton();
     defColours.setOpaque(false);
     defColours.setText(MessageManager.getString("action.set_defaults"));
     defColours.setToolTipText(MessageManager
@@ -237,48 +210,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       }
     });
 
-    annotations.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        annotations_actionPerformed();
-      }
-    });
-    getThreshold().addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        threshold_actionPerformed();
-      }
-    });
-    thresholdValue.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-    slider.setPaintLabels(false);
-    slider.setPaintTicks(true);
-    slider.setBackground(Color.white);
-    slider.setEnabled(false);
-    slider.setOpaque(false);
-    slider.setPreferredSize(new Dimension(100, 32));
-    thresholdValue.setEnabled(false);
-    thresholdValue.setColumns(7);
-    currentColours.setFont(JvSwingUtils.getLabelFont());
-    currentColours.setOpaque(false);
-    currentColours.setText(MessageManager
+    useOriginalColours.setFont(JvSwingUtils.getLabelFont());
+    useOriginalColours.setOpaque(false);
+    useOriginalColours.setText(MessageManager
             .getString("label.use_original_colours"));
-    currentColours.addActionListener(new ActionListener()
+    useOriginalColours.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        currentColours_actionPerformed();
+        originalColours_actionPerformed();
       }
     });
     thresholdIsMin.setBackground(Color.white);
@@ -307,7 +248,9 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       }
     });
 
-    this.setLayout(borderLayout1);
+    this.setLayout(new BorderLayout());
+    JPanel jPanel1 = new JPanel();
+    JPanel jPanel2 = new JPanel();
     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
     jPanel1.setBackground(Color.white);
     jPanel2.setBackground(Color.white);
@@ -316,7 +259,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     jPanel1.add(cancel);
     jPanel2.add(annotations, "grow, wrap");
     jPanel2.add(seqAssociated);
-    jPanel2.add(currentColours);
+    jPanel2.add(useOriginalColours);
     JPanel colpanel = new JPanel(new FlowLayout());
     colpanel.setBackground(Color.white);
     colpanel.add(minColour);
@@ -391,7 +334,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
   {
     if (slider.isEnabled())
     {
-      if (currentColours.isSelected()
+      if (useOriginalColours.isSelected()
               && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
       {
         updateView();
@@ -403,24 +346,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     }
   }
 
-  public JComboBox<String> getThreshold()
+  public void originalColours_actionPerformed()
   {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox<String> threshold)
-  {
-    this.threshold = threshold;
-  }
-
-  public void currentColours_actionPerformed()
-  {
-    if (currentColours.isSelected())
+    boolean selected = useOriginalColours.isSelected();
+    if (selected)
     {
       reset();
     }
-    maxColour.setEnabled(!currentColours.isSelected());
-    minColour.setEnabled(!currentColours.isSelected());
+    maxColour.setEnabled(!selected);
+    minColour.setEnabled(!selected);
+    thresholdIsMin.setEnabled(!selected);
     updateView();
   }
 
@@ -441,7 +376,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
 
     slider.setEnabled(true);
     thresholdValue.setEnabled(true);
-    thresholdIsMin.setEnabled(true);
+    thresholdIsMin.setEnabled(!useOriginalColours.isSelected());
 
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
@@ -455,7 +390,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     {
       getCurrentAnnotation()
               .setThreshold(
-                      new jalview.datamodel.GraphLine(
+                      new GraphLine(
                               (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
                               "Threshold", Color.black));
     }
@@ -463,19 +398,19 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
     {
       adjusting = true;
-      float range = getCurrentAnnotation().graphMax * 1000
-              - getCurrentAnnotation().graphMin * 1000;
+      float range = getCurrentAnnotation().graphMax * ONETHOUSAND
+              - getCurrentAnnotation().graphMin * ONETHOUSAND;
 
-      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
-      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
-      slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
+      slider.setMinimum((int) (getCurrentAnnotation().graphMin * ONETHOUSAND));
+      slider.setMaximum((int) (getCurrentAnnotation().graphMax * ONETHOUSAND));
+      slider.setValue((int) (getCurrentAnnotation().threshold.value * ONETHOUSAND));
       thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
       slider.setMajorTickSpacing((int) (range / 10f));
       slider.setEnabled(true);
       thresholdValue.setEnabled(true);
       adjusting = false;
     }
-    colorAlignmContaining(getCurrentAnnotation(), selectedThresholdItem);
+    colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem);
 
     ap.alignmentChanged();
     // ensure all associated views (overviews, structures, etc) are notified of
@@ -483,4 +418,60 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     ap.paintAlignment(true);
   }
 
+  protected boolean colorAlignmentContaining(AlignmentAnnotation currentAnn, int selectedThresholdOption)
+  {
+  
+    AnnotationColourGradient acg = null;
+    if (useOriginalColours.isSelected())
+    {
+      acg = new AnnotationColourGradient(currentAnn,
+              av.getGlobalColourScheme(), selectedThresholdOption);
+    }
+    else
+    {
+      acg = new AnnotationColourGradient(currentAnn,
+              minColour.getBackground(), maxColour.getBackground(),
+              selectedThresholdOption);
+    }
+    acg.setSeqAssociated(seqAssociated.isSelected());
+  
+    if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f)
+    {
+      acg.setPredefinedColours(true);
+    }
+  
+    acg.setThresholdIsMinMax(thresholdIsMin.isSelected());
+  
+    av.setGlobalColourScheme(acg);
+  
+    if (av.getAlignment().getGroups() != null)
+    {
+  
+      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+      {
+        if (sg.cs == null)
+        {
+          continue;
+        }
+  
+        if (useOriginalColours.isSelected())
+        {
+          sg.setColourScheme(new AnnotationColourGradient(currentAnn, sg
+                  .getColourScheme(), selectedThresholdOption));
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+        }
+        else
+        {
+          sg.setColourScheme(new AnnotationColourGradient(currentAnn,
+                  minColour.getBackground(), maxColour.getBackground(),
+                  selectedThresholdOption));
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+        }
+      }
+    }
+    return false;
+  }
+
 }
index 1290d70..9c2a1b9 100644 (file)
@@ -21,8 +21,8 @@
 
 package jalview.gui;
 
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.io.cache.JvCacheableInputBox;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.util.MessageManager;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
@@ -35,6 +35,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
 import java.util.Iterator;
 
 import javax.swing.ButtonGroup;
@@ -44,10 +45,7 @@ import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
-import javax.swing.JTextField;
 import javax.swing.border.TitledBorder;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
 
 import net.miginfocom.swing.MigLayout;
 
@@ -55,28 +53,10 @@ import net.miginfocom.swing.MigLayout;
 public class AnnotationColumnChooser extends AnnotationRowFilter implements
         ItemListener
 {
-
-  private JComboBox<String> annotations;
-
-  private JPanel actionPanel = new JPanel();
-
-  private JPanel thresholdPanel = new JPanel();
-
   private JPanel switchableViewsPanel = new JPanel(new CardLayout());
 
-  private CardLayout switchableViewsLayout = (CardLayout) (switchableViewsPanel
-          .getLayout());
-
-  private JPanel noGraphFilterView = new JPanel();
-
-  private JPanel graphFilterView = new JPanel();
-
   private JPanel annotationComboBoxPanel = new JPanel();
 
-  private BorderLayout borderLayout1 = new BorderLayout();
-
-  private JComboBox<String> threshold = new JComboBox<String>();
-
   private StructureFilterPanel gStructureFilterPanel;
 
   private StructureFilterPanel ngStructureFilterPanel;
@@ -105,18 +85,11 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
   private int actionOption = ACTION_OPTION_SELECT;
 
-  private ColumnSelection oldColumnSelection;
+  private HiddenColumns oldHiddenColumns;
 
-  public AnnotationColumnChooser()
-  {
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
+  protected int MIN_WIDTH = 420;
+
+  protected int MIN_HEIGHT = 430;
 
   public AnnotationColumnChooser(AlignViewport av, final AlignmentPanel ap)
   {
@@ -127,6 +100,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     Desktop.addInternalFrame(frame,
             MessageManager.getString("label.select_by_annotation"), 520,
             215);
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
 
     addSliderChangeListener();
     addSliderMouseListeners();
@@ -135,25 +109,28 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     {
       return;
     }
-    setOldColumnSelection(av.getColumnSelection());
+    setOldHiddenColumns(av.getAlignment().getHiddenColumns());
     adjusting = true;
 
-    setAnnotations(new JComboBox<String>(getAnnotationItems(false)));
+    setAnnotations(new JComboBox<>(getAnnotationItems(false)));
     populateThresholdComboBox(threshold);
-
+    AnnotationColumnChooser lastChooser = av
+            .getAnnotationColumnSelectionState();
     // restore Object state from the previous session if one exists
-    if (av.getAnnotationColumnSelectionState() != null)
+    if (lastChooser != null)
     {
-      currentSearchPanel = av.getAnnotationColumnSelectionState()
+      currentSearchPanel = lastChooser
               .getCurrentSearchPanel();
-      currentStructureFilterPanel = av.getAnnotationColumnSelectionState()
+      currentStructureFilterPanel = lastChooser
               .getCurrentStructureFilterPanel();
-      annotations.setSelectedIndex(av.getAnnotationColumnSelectionState()
+      annotations.setSelectedIndex(lastChooser
               .getAnnotations().getSelectedIndex());
-      threshold.setSelectedIndex(av.getAnnotationColumnSelectionState()
+      threshold.setSelectedIndex(lastChooser
               .getThreshold().getSelectedIndex());
-      actionOption = av.getAnnotationColumnSelectionState()
+      actionOption = lastChooser
               .getActionOption();
+      percentThreshold.setSelected(lastChooser.percentThreshold
+              .isSelected());
     }
 
     try
@@ -169,72 +146,30 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     frame.pack();
   }
 
-  private void jbInit() throws Exception
+  @Override
+  protected void jbInit()
   {
-    ok.setOpaque(false);
-    ok.setText(MessageManager.getString("action.ok"));
-    ok.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        ok_actionPerformed();
-      }
-    });
-
-    cancel.setOpaque(false);
-    cancel.setText(MessageManager.getString("action.cancel"));
-    cancel.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        cancel_actionPerformed();
-      }
-    });
-
-    annotations.addItemListener(this);
-    annotations.setToolTipText(MessageManager
-            .getString("info.select_annotation_row"));
-    threshold.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        threshold_actionPerformed();
-      }
-    });
-
-    thresholdValue.setEnabled(false);
-    thresholdValue.setColumns(7);
-    thresholdValue.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-
-    slider.setPaintLabels(false);
-    slider.setPaintTicks(true);
-    slider.setBackground(Color.white);
-    slider.setEnabled(false);
-    slider.setOpaque(false);
-    slider.setPreferredSize(new Dimension(100, 32));
+    super.jbInit();
 
+    JPanel thresholdPanel = new JPanel();
     thresholdPanel.setBorder(new TitledBorder(MessageManager
             .getString("label.threshold_filter")));
     thresholdPanel.setBackground(Color.white);
     thresholdPanel.setFont(JvSwingUtils.getLabelFont());
     thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]"));
 
+    percentThreshold.setBackground(Color.white);
+    percentThreshold.setFont(JvSwingUtils.getLabelFont());
+
+    JPanel actionPanel = new JPanel();
     actionPanel.setBackground(Color.white);
     actionPanel.setFont(JvSwingUtils.getLabelFont());
 
+    JPanel graphFilterView = new JPanel();
     graphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
     graphFilterView.setBackground(Color.white);
 
+    JPanel noGraphFilterView = new JPanel();
     noGraphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
     noGraphFilterView.setBackground(Color.white);
 
@@ -249,8 +184,9 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     ngStructureFilterPanel = new StructureFilterPanel(this);
 
     thresholdPanel.add(getThreshold());
-    thresholdPanel.add(thresholdValue, "wrap");
-    thresholdPanel.add(slider, "grow, span, wrap");
+    thresholdPanel.add(percentThreshold, "wrap");
+    thresholdPanel.add(slider, "grow");
+    thresholdPanel.add(thresholdValue, "span, wrap");
 
     actionPanel.add(ok);
     actionPanel.add(cancel);
@@ -270,7 +206,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     switchableViewsPanel.add(graphFilterView,
             AnnotationColumnChooser.GRAPH_VIEW);
 
-    this.setLayout(borderLayout1);
+    this.setLayout(new BorderLayout());
     this.add(annotationComboBoxPanel, java.awt.BorderLayout.PAGE_START);
     this.add(switchableViewsPanel, java.awt.BorderLayout.CENTER);
     this.add(actionPanel, java.awt.BorderLayout.SOUTH);
@@ -280,7 +216,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     this.validate();
   }
 
-  public void updateThresholdPanelToolTip()
+  protected void updateThresholdPanelToolTip()
   {
     thresholdValue.setToolTipText("");
     slider.setToolTipText("");
@@ -297,28 +233,28 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
   }
 
   @Override
-  public void reset()
+  protected void reset()
   {
-    if (this.getOldColumnSelection() != null)
+    if (this.getOldHiddenColumns() != null)
     {
       av.getColumnSelection().clear();
 
       if (av.getAnnotationColumnSelectionState() != null)
       {
-        ColumnSelection oldSelection = av
+        HiddenColumns oldHidden = av
                 .getAnnotationColumnSelectionState()
-                .getOldColumnSelection();
-        if (oldSelection != null && oldSelection.getHiddenColumns() != null
-                && !oldSelection.getHiddenColumns().isEmpty())
+                .getOldHiddenColumns();
+        if (oldHidden != null && oldHidden.getHiddenRegions() != null
+                && !oldHidden.getHiddenRegions().isEmpty())
         {
-          for (Iterator<int[]> itr = oldSelection.getHiddenColumns()
+          for (Iterator<int[]> itr = oldHidden.getHiddenRegions()
                   .iterator(); itr.hasNext();)
           {
             int positions[] = itr.next();
             av.hideColumns(positions[0], positions[1]);
           }
         }
-        av.setColumnSelection(oldSelection);
+        av.getAlignment().setHiddenColumns(oldHidden);
       }
       ap.paintAlignment(true);
     }
@@ -338,26 +274,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     }
   }
 
-  public JComboBox<String> getThreshold()
-  {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox<String> threshold)
-  {
-    this.threshold = threshold;
-  }
-
-  public JComboBox<String> getAnnotations()
-  {
-    return annotations;
-  }
-
-  public void setAnnotations(JComboBox<String> annotations)
-  {
-    this.annotations = annotations;
-  }
-
   @Override
   public void updateView()
   {
@@ -377,12 +293,14 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     slider.setEnabled(true);
     thresholdValue.setEnabled(true);
+    percentThreshold.setEnabled(true);
 
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
       slider.setEnabled(false);
       thresholdValue.setEnabled(false);
       thresholdValue.setText("");
+      percentThreshold.setEnabled(false);
       // build filter params
     }
     else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
@@ -403,7 +321,9 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
       slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
       slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
-      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
+      
+      setThresholdValueText();
+
       slider.setMajorTickSpacing((int) (range / 10f));
       slider.setEnabled(true);
       thresholdValue.setEnabled(true);
@@ -412,7 +332,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       // build filter params
       filterParams
               .setThresholdType(AnnotationFilterParameter.ThresholdType.NO_THRESHOLD);
-      if (getCurrentAnnotation().graph != AlignmentAnnotation.NO_GRAPH)
+      if (getCurrentAnnotation().isQuantitative())
       {
         filterParams
                 .setThresholdValue(getCurrentAnnotation().threshold.value);
@@ -449,12 +369,10 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     if (currentSearchPanel != null)
     {
-
-      if (!currentSearchPanel.searchBox.getText().isEmpty())
+      if (!currentSearchPanel.searchBox.getUserInput().isEmpty())
       {
-        currentSearchPanel.description.setEnabled(true);
-        currentSearchPanel.displayName.setEnabled(true);
-        filterParams.setRegexString(currentSearchPanel.searchBox.getText());
+        filterParams.setRegexString(currentSearchPanel.searchBox
+                .getUserInput());
         if (currentSearchPanel.displayName.isSelected())
         {
           filterParams
@@ -466,17 +384,15 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
                   .addRegexSearchField(AnnotationFilterParameter.SearchableAnnotationField.DESCRIPTION);
         }
       }
-      else
-      {
-        currentSearchPanel.description.setEnabled(false);
-        currentSearchPanel.displayName.setEnabled(false);
-      }
     }
 
+    // show hidden columns here, before changing the column selection in
+    // filterAnnotations, because showing hidden columns has the side effect of
+    // adding them to the selection
+    av.showAllHiddenColumns();
     av.getColumnSelection().filterAnnotations(
             getCurrentAnnotation().annotations, filterParams);
 
-    av.showAllHiddenColumns();
     if (getActionOption() == ACTION_OPTION_HIDE)
     {
       av.hideSelectedColumns();
@@ -487,16 +403,16 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     ap.paintAlignment(true);
   }
 
-  public ColumnSelection getOldColumnSelection()
+  public HiddenColumns getOldHiddenColumns()
   {
-    return oldColumnSelection;
+    return oldHiddenColumns;
   }
 
-  public void setOldColumnSelection(ColumnSelection currentColumnSelection)
+  public void setOldHiddenColumns(HiddenColumns currentHiddenColumns)
   {
-    if (currentColumnSelection != null)
+    if (currentHiddenColumns != null)
     {
-      this.oldColumnSelection = new ColumnSelection(currentColumnSelection);
+      this.oldHiddenColumns = new HiddenColumns(currentHiddenColumns);
     }
   }
 
@@ -568,15 +484,17 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     selectedAnnotationChanged();
   }
 
+  @Override
   public void selectedAnnotationChanged()
   {
     String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW;
-    if (av.getAlignment().getAlignmentAnnotation()[annmap[getAnnotations()
-            .getSelectedIndex()]].graph != AlignmentAnnotation.NO_GRAPH)
+    if (av.getAlignment()
+            .getAlignmentAnnotation()[annmap[getAnnotations()
+            .getSelectedIndex()]].isQuantitative())
     {
       currentView = AnnotationColumnChooser.GRAPH_VIEW;
     }
-
+    saveCache();
     gSearchPanel.syncState();
     gFurtherActionPanel.syncState();
     gStructureFilterPanel.syncState();
@@ -585,6 +503,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     ngFurtherActionPanel.syncState();
     ngStructureFilterPanel.syncState();
 
+    CardLayout switchableViewsLayout = (CardLayout) switchableViewsPanel
+            .getLayout();
     switchableViewsLayout.show(switchableViewsPanel, currentView);
     updateView();
   }
@@ -805,7 +725,10 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
     private JCheckBox description = new JCheckBox();
 
-    private JTextField searchBox = new JTextField(10);
+    private static final String FILTER_BY_ANN_CACHE_KEY = "CACHE.SELECT_FILTER_BY_ANNOT";
+
+    public JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<String>(
+            FILTER_BY_ANN_CACHE_KEY);
 
     public SearchPanel(AnnotationColumnChooser aColChooser)
     {
@@ -815,32 +738,26 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       this.setBorder(new TitledBorder(MessageManager
               .getString("label.search_filter")));
 
-      JvSwingUtils.jvInitComponent(searchBox);
+      searchBox.setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXXX");
       searchBox.setToolTipText(MessageManager
               .getString("info.enter_search_text_here"));
-      searchBox.getDocument().addDocumentListener(new DocumentListener()
-      {
-        @Override
-        public void insertUpdate(DocumentEvent e)
-        {
-          searchStringAction();
-        }
+      searchBox.getEditor().getEditorComponent()
+              .addKeyListener(new java.awt.event.KeyAdapter()
+              {
+                @Override
+                public void keyPressed(KeyEvent e)
+                {
+                  if (e.getKeyCode() == KeyEvent.VK_ENTER)
+                  {
+                    e.consume();
+                    searchStringAction();
+                  }
+                }
+              });
 
-        @Override
-        public void removeUpdate(DocumentEvent e)
-        {
-          searchStringAction();
-        }
 
-        @Override
-        public void changedUpdate(DocumentEvent e)
-        {
-          searchStringAction();
-        }
-      });
 
       JvSwingUtils.jvInitComponent(displayName, "label.label");
-      displayName.setEnabled(false);
       displayName.addActionListener(new ActionListener()
       {
         @Override
@@ -851,7 +768,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       });
 
       JvSwingUtils.jvInitComponent(description, "label.description");
-      description.setEnabled(false);
       description.addActionListener(new ActionListener()
       {
         @Override
@@ -884,6 +800,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       aColChooser.setCurrentSearchPanel(this);
       aColChooser.updateView();
       updateSearchPanelToolTips();
+      searchBox.updateCache();
     }
 
     public void syncState()
@@ -897,7 +814,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
         displayName.setEnabled(sp.displayName.isEnabled());
         displayName.setSelected(sp.displayName.isSelected());
 
-        searchBox.setText(sp.searchBox.getText());
+        searchBox.setSelectedItem(sp.searchBox.getUserInput());
       }
       updateSearchPanelToolTips();
     }
@@ -919,4 +836,25 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     }
   }
 
+  @Override
+  public void ok_actionPerformed()
+  {
+    saveCache();
+    super.ok_actionPerformed();
+  }
+
+  @Override
+  public void cancel_actionPerformed()
+  {
+    saveCache();
+    super.cancel_actionPerformed();
+  }
+
+  private void saveCache()
+  {
+    gSearchPanel.searchBox.persistCache();
+    ngSearchPanel.searchBox.persistCache();
+    gSearchPanel.searchBox.updateCache();
+    ngSearchPanel.searchBox.updateCache();
+  }
 }
index 4b774d6..8ca1a4e 100755 (executable)
@@ -292,33 +292,11 @@ public class AnnotationLabels extends JPanel implements MouseListener,
       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
     }
 
-    refresh(fullRepaint);
+    ap.refresh(fullRepaint);
 
   }
 
   /**
-   * Redraw sensibly.
-   * 
-   * @adjustHeight if true, try to recalculate panel height for visible
-   *               annotations
-   */
-  protected void refresh(boolean adjustHeight)
-  {
-    ap.validateAnnotationDimensions(adjustHeight);
-    ap.addNotify();
-    if (adjustHeight)
-    {
-      // sort, repaint, update overview
-      ap.paintAlignment(true);
-    }
-    else
-    {
-      // lightweight repaint
-      ap.repaint();
-    }
-  }
-
-  /**
    * DOCUMENT ME!
    * 
    * @param e
@@ -420,7 +398,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
             // ann.visible = false;
             // }
             // }
-            refresh(true);
+            ap.refresh(true);
           }
         });
         pop.add(hideType);
@@ -969,12 +947,14 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     Alignment ds = new Alignment(dseqs);
     if (av.hasHiddenColumns())
     {
-      omitHidden = av.getColumnSelection().getVisibleSequenceStrings(0,
+      omitHidden = av.getAlignment().getHiddenColumns()
+              .getVisibleSequenceStrings(0,
               sq.getLength(), seqs);
     }
 
     int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
-    List<int[]> hiddenCols = av.getColumnSelection().getHiddenColumns();
+    List<int[]> hiddenCols = av.getAlignment().getHiddenColumns()
+            .getHiddenRegions();
     if (hiddenCols != null)
     {
       alignmentStartEnd = av.getAlignment().getVisibleStartAndEndIndex(
@@ -990,7 +970,8 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     if (av.hasHiddenColumns())
     {
       hiddenColumns = new ArrayList<int[]>();
-      for (int[] region : av.getColumnSelection().getHiddenColumns())
+      for (int[] region : av.getAlignment().getHiddenColumns()
+              .getHiddenRegions())
       {
         hiddenColumns.add(new int[] { region[0], region[1] });
       }
index ebfc52e..1026b07 100755 (executable)
@@ -23,12 +23,14 @@ package jalview.gui;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.AnnotationRenderer;
 import jalview.renderer.AwtRenderPanelI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
+import jalview.viewmodel.ViewportListenerI;
 
 import java.awt.AlphaComposite;
 import java.awt.Color;
@@ -49,13 +51,13 @@ import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import javax.swing.JColorChooser;
 import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.Scrollable;
@@ -70,7 +72,7 @@ import javax.swing.ToolTipManager;
  */
 public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         MouseListener, MouseWheelListener, MouseMotionListener,
-        ActionListener, AdjustmentListener, Scrollable
+        ActionListener, AdjustmentListener, Scrollable, ViewportListenerI
 {
   String HELIX = MessageManager.getString("label.helix");
 
@@ -156,6 +158,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     // and then set our own listener to consume all mousewheel events
     ap.annotationScroller.addMouseWheelListener(this);
     renderer = new AnnotationRenderer();
+
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   public AnnotationPanel(AlignViewport av)
@@ -172,11 +176,11 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       e.consume();
       if (e.getWheelRotation() > 0)
       {
-        ap.scrollRight(true);
+        av.getRanges().scrollRight(true);
       }
       else
       {
-        ap.scrollRight(false);
+        av.getRanges().scrollRight(false);
       }
     }
     else
@@ -291,7 +295,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (av.getColumnSelection().isVisible(index))
+        if (av.getAlignment().getHiddenColumns().isVisible(index))
         {
           anot[index] = null;
         }
@@ -315,7 +319,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -338,7 +342,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -398,7 +402,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       }
       for (int index : av.getColumnSelection().getSelected())
       {
-        if (!av.getColumnSelection().isVisible(index))
+        if (!av.getAlignment().getHiddenColumns().isVisible(index))
         {
           continue;
         }
@@ -441,6 +445,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     StringBuilder collatedInput = new StringBuilder(64);
     String last = "";
     ColumnSelection viscols = av.getColumnSelection();
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
 
     /*
      * the selection list (read-only view) is in selection order, not
@@ -451,7 +456,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     for (int index : selected)
     {
       // always check for current display state - just in case
-      if (!viscols.isVisible(index))
+      if (!hidden.isVisible(index))
       {
         continue;
       }
@@ -708,11 +713,13 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       return;
     }
 
-    int column = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int column = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
-      column = av.getColumnSelection().adjustForHiddenColumns(column);
+      column = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(column);
     }
 
     AlignmentAnnotation ann = aa[row];
@@ -905,7 +912,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         return;
       }
     }
-    imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth();
+    imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes() + 1)
+            * av.getCharWidth();
     if (imgWidth < 1)
     {
       return;
@@ -946,7 +954,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       imageFresh = true;
     }
 
-    drawComponent(gg, av.startRes, av.endRes + 1);
+    drawComponent(gg, av.getRanges().getStartRes(), av.getRanges()
+            .getEndRes() + 1);
     imageFresh = false;
     g.drawImage(image, 0, 0, this);
   }
@@ -976,8 +985,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     gg.copyArea(0, 0, imgWidth, getHeight(),
             -horizontal * av.getCharWidth(), 0);
     long mtime = System.currentTimeMillis();
-    int sr = av.startRes;
-    int er = av.endRes + 1;
+    int sr = av.getRanges().getStartRes();
+    int er = av.getRanges().getEndRes() + 1;
     int transX = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
@@ -1154,4 +1163,15 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       renderer.dispose();
     }
   }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport range changes (e.g. alignment panel was scrolled)
+    if (evt.getPropertyName().equals("startres")
+            || evt.getPropertyName().equals("endres"))
+    {
+      fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+  }
 }
index 166e1ad..a3ce528 100644 (file)
@@ -22,12 +22,21 @@ package jalview.gui;
 
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.GraphLine;
-import jalview.datamodel.SequenceGroup;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Vector;
 
 import javax.swing.JButton;
@@ -49,21 +58,11 @@ public abstract class AnnotationRowFilter extends JPanel
 
   protected int[] annmap;
 
-  protected boolean enableSeqAss = false;
-
-  private AlignmentAnnotation currentAnnotation;
-
   protected boolean adjusting = false;
 
-  protected JCheckBox currentColours = new JCheckBox();
-
-  protected JPanel minColour = new JPanel();
-
-  protected JPanel maxColour = new JPanel();
-
   protected JCheckBox seqAssociated = new JCheckBox();
 
-  protected JCheckBox thresholdIsMin = new JCheckBox();
+  protected JCheckBox percentThreshold = new JCheckBox();
 
   protected JSlider slider = new JSlider();
 
@@ -81,6 +80,38 @@ public abstract class AnnotationRowFilter extends JPanel
    */
   protected boolean sliderDragging = false;
 
+  protected JComboBox<String> threshold = new JComboBox<String>();
+
+  protected JComboBox<String> annotations;
+
+  /*
+   * map from annotation to its menu item display label
+   * - so we know which item to pre-select on restore
+   */
+  private Map<AlignmentAnnotation, String> annotationLabels;
+
+  private AlignmentAnnotation currentAnnotation;
+
+  /**
+   * Constructor
+   * 
+   * @param viewport
+   * @param alignPanel
+   */
+  public AnnotationRowFilter(AlignViewport viewport, final AlignmentPanel alignPanel)
+  {
+    this.av = viewport;
+    this.ap = alignPanel;
+    thresholdValue.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+        thresholdValue_actionPerformed();
+      }
+    });
+  }
+
   protected void addSliderChangeListener()
   {
 
@@ -91,13 +122,32 @@ public abstract class AnnotationRowFilter extends JPanel
       {
         if (!adjusting)
         {
-          thresholdValue.setText((slider.getValue() / 1000f) + "");
+          setThresholdValueText();
           valueChanged(!sliderDragging);
         }
       }
     });
   }
 
+  /**
+   * update the text field from the threshold slider. preserves state of
+   * 'adjusting' so safe to call in init.
+   */
+  protected void setThresholdValueText()
+  {
+    boolean oldadj = adjusting;
+    adjusting = true;
+    if (percentThreshold.isSelected())
+    {
+      thresholdValue.setText("" + (slider.getValue() - slider.getMinimum())
+              * 100f / (slider.getMaximum() - slider.getMinimum()));
+    }
+    else
+    {
+      thresholdValue.setText((slider.getValue() / 1000f) + "");
+    }
+    adjusting = oldadj;
+  }
   protected void addSliderMouseListeners()
   {
 
@@ -130,25 +180,27 @@ public abstract class AnnotationRowFilter extends JPanel
     });
   }
 
-  public AnnotationRowFilter(AlignViewport av, final AlignmentPanel ap)
-  {
-    this.av = av;
-    this.ap = ap;
-  }
-
-  public AnnotationRowFilter()
-  {
-
-  }
-
+/**
+ * Builds and returns a list of menu items (display text) for choice of
+ * annotation. Also builds maps between annotations, their positions in the
+ * list, and their display labels in the list.
+ * 
+ * @param isSeqAssociated
+ * @return
+ */
   public Vector<String> getAnnotationItems(boolean isSeqAssociated)
   {
+    annotationLabels = new HashMap<AlignmentAnnotation, String>();
+
     Vector<String> list = new Vector<String>();
     int index = 1;
     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
+    seqAssociated.setEnabled(false);
     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
     {
-      if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
+      AlignmentAnnotation annotation = av.getAlignment()
+              .getAlignmentAnnotation()[i];
+      if (annotation.sequenceRef == null)
       {
         if (isSeqAssociated)
         {
@@ -157,30 +209,29 @@ public abstract class AnnotationRowFilter extends JPanel
       }
       else
       {
-        enableSeqAss = true;
+        seqAssociated.setEnabled(true);
       }
-      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
+      String label = annotation.label;
       // add associated sequence ID if available
-      if (!isSeqAssociated
-              && av.getAlignment().getAlignmentAnnotation()[i].sequenceRef != null)
+      if (!isSeqAssociated && annotation.sequenceRef != null)
       {
-        label = label
-                + "_"
-                + av.getAlignment().getAlignmentAnnotation()[i].sequenceRef
-                        .getName();
+        label = label + "_" + annotation.sequenceRef.getName();
       }
       // make label unique
       if (!list.contains(label))
       {
         anmap[list.size()] = i;
         list.add(label);
+        annotationLabels.put(annotation, label);
       }
       else
       {
         if (!isSeqAssociated)
         {
           anmap[list.size()] = i;
-          list.add(label + "_" + (index++));
+          label = label + "_" + (index++);
+          list.add(label);
+          annotationLabels.put(annotation, label);
         }
       }
     }
@@ -203,11 +254,6 @@ public abstract class AnnotationRowFilter extends JPanel
     return selectedThresholdItem;
   }
 
-  public void modelChanged()
-  {
-    seqAssociated.setEnabled(enableSeqAss);
-  }
-
   public void ok_actionPerformed()
   {
     try
@@ -230,54 +276,75 @@ public abstract class AnnotationRowFilter extends JPanel
     }
   }
 
-  public void thresholdCheck_actionPerformed()
+  protected void thresholdCheck_actionPerformed()
   {
     updateView();
   }
 
-  public void annotations_actionPerformed()
+  protected void selectedAnnotationChanged()
   {
     updateView();
   }
 
-  public void threshold_actionPerformed()
+  protected void threshold_actionPerformed()
   {
     updateView();
   }
 
-  public void thresholdValue_actionPerformed()
+  protected void thresholdValue_actionPerformed()
   {
     try
     {
       float f = Float.parseFloat(thresholdValue.getText());
-      slider.setValue((int) (f * 1000));
+      if (percentThreshold.isSelected())
+      {
+        slider.setValue(slider.getMinimum()
+                + ((int) ((f / 100f) * (slider.getMaximum() - slider
+                        .getMinimum()))));
+      }
+      else
+      {
+        slider.setValue((int) (f * 1000));
+      }
       updateView();
     } catch (NumberFormatException ex)
     {
     }
   }
 
-  public void thresholdIsMin_actionPerformed()
+  protected void percentageValue_actionPerformed()
+  {
+    setThresholdValueText();
+  }
+
+  protected void thresholdIsMin_actionPerformed()
   {
     updateView();
   }
 
-  protected void populateThresholdComboBox(JComboBox<String> threshold)
+  protected void populateThresholdComboBox(JComboBox<String> thresh)
   {
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_no_threshold"));
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_above_threshold"));
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_below_threshold"));
   }
 
-  protected void seqAssociated_actionPerformed(JComboBox<String> annotations)
+  /**
+   * Rebuilds the drop-down list of annotations to choose from when the 'per
+   * sequence only' checkbox is checked or unchecked.
+   * 
+   * @param anns
+   */
+  protected void seqAssociated_actionPerformed(JComboBox<String> anns)
   {
     adjusting = true;
-    String cursel = (String) annotations.getSelectedItem();
-    boolean isvalid = false, isseqs = seqAssociated.isSelected();
-    annotations.removeAllItems();
+    String cursel = (String) anns.getSelectedItem();
+    boolean isvalid = false;
+    boolean isseqs = seqAssociated.isSelected();
+    anns.removeAllItems();
     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
     {
       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
@@ -285,20 +352,22 @@ public abstract class AnnotationRowFilter extends JPanel
         isvalid = true;
         cursel = anitem;
       }
-      annotations.addItem(anitem);
+      anns.addItem(anitem);
     }
-    adjusting = false;
     if (isvalid)
     {
-      annotations.setSelectedItem(cursel);
+      anns.setSelectedItem(cursel);
     }
     else
     {
-      if (annotations.getItemCount() > 0)
+      if (anns.getItemCount() > 0)
       {
-        annotations.setSelectedIndex(0);
+        anns.setSelectedIndex(0);
       }
     }
+    adjusting = false;
+
+    updateView();
   }
 
   protected void propagateSeqAssociatedThreshold(boolean allAnnotation,
@@ -329,76 +398,119 @@ public abstract class AnnotationRowFilter extends JPanel
     }
   }
 
-  protected boolean colorAlignmContaining(AlignmentAnnotation currentAnn,
-          int selectedThresholdOption)
+  public AlignmentAnnotation getCurrentAnnotation()
   {
+    return currentAnnotation;
+  }
 
-    AnnotationColourGradient acg = null;
-    if (currentColours.isSelected())
-    {
-      acg = new AnnotationColourGradient(currentAnn,
-              av.getGlobalColourScheme(), selectedThresholdOption);
-    }
-    else
-    {
-      acg = new AnnotationColourGradient(currentAnn,
-              minColour.getBackground(), maxColour.getBackground(),
-              selectedThresholdOption);
-    }
-    acg.setSeqAssociated(seqAssociated.isSelected());
+  protected void setCurrentAnnotation(AlignmentAnnotation annotation)
+  {
+    this.currentAnnotation = annotation;
+  }
+
+  protected abstract void valueChanged(boolean updateAllAnnotation);
+
+  protected abstract void updateView();
 
-    if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f)
+  protected abstract void reset();
+
+  protected String getAnnotationMenuLabel(AlignmentAnnotation ann)
+  {
+    return annotationLabels.get(ann);
+  }
+
+  protected void jbInit()
+  {
+    ok.setOpaque(false);
+    ok.setText(MessageManager.getString("action.ok"));
+    ok.addActionListener(new ActionListener()
     {
-      acg.setPredefinedColours(true);
-    }
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        ok_actionPerformed();
+      }
+    });
 
-    acg.thresholdIsMinMax = thresholdIsMin.isSelected();
+    cancel.setOpaque(false);
+    cancel.setText(MessageManager.getString("action.cancel"));
+    cancel.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        cancel_actionPerformed();
+      }
+    });
 
-    av.setGlobalColourScheme(acg);
+    annotations.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        selectedAnnotationChanged();
+      }
+    });
+    annotations.setToolTipText(MessageManager
+            .getString("info.select_annotation_row"));
 
-    if (av.getAlignment().getGroups() != null)
+    threshold.addActionListener(new ActionListener()
     {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        threshold_actionPerformed();
+      }
+    });
 
-      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+    thresholdValue.setEnabled(false);
+    thresholdValue.setColumns(7);
+    thresholdValue.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
       {
-        if (sg.cs == null)
-        {
-          continue;
-        }
+        thresholdValue_actionPerformed();
+      }
+    });
 
-        AnnotationColourGradient scheme = null;
-        if (currentColours.isSelected())
-        {
-          scheme = new AnnotationColourGradient(currentAnn,
-                  sg.getColourScheme(), selectedThresholdOption);
-        }
-        else
+    percentThreshold.setText(MessageManager.getString("label.as_percentage"));
+    percentThreshold.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        if (!adjusting)
         {
-          scheme = new AnnotationColourGradient(currentAnn,
-                  minColour.getBackground(), maxColour.getBackground(),
-                  selectedThresholdOption);
+          percentageValue_actionPerformed();
         }
-        scheme.setSeqAssociated(seqAssociated.isSelected());
-        sg.setColourScheme(scheme);
       }
-    }
-    return false;
+    });
+    slider.setPaintLabels(false);
+    slider.setPaintTicks(true);
+    slider.setBackground(Color.white);
+    slider.setEnabled(false);
+    slider.setOpaque(false);
+    slider.setPreferredSize(new Dimension(100, 32));
   }
 
-  public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation()
+  public JComboBox<String> getThreshold()
   {
-    return currentAnnotation;
+    return threshold;
   }
 
-  public void setCurrentAnnotation(
-          jalview.datamodel.AlignmentAnnotation currentAnnotation)
+  public void setThreshold(JComboBox<String> thresh)
   {
-    this.currentAnnotation = currentAnnotation;
+    this.threshold = thresh;
   }
 
-  public abstract void valueChanged(boolean updateAllAnnotation);
-
-  public abstract void updateView();
+  public JComboBox<String> getAnnotations()
+  {
+    return annotations;
+  }
 
-  public abstract void reset();
+  public void setAnnotations(JComboBox<String> anns)
+  {
+    this.annotations = anns;
+  }
 }
index ffb9639..68a847e 100644 (file)
@@ -45,7 +45,6 @@ import java.util.Vector;
 
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
-import javax.swing.JMenu;
 import javax.swing.JPanel;
 import javax.swing.JSplitPane;
 import javax.swing.SwingUtilities;
@@ -147,7 +146,7 @@ public class AppJmol extends StructureViewerBase
   {
     super.initMenus();
 
-    viewerActionMenu = new JMenu(MessageManager.getString("label.jmol"));
+    viewerActionMenu.setText(MessageManager.getString("label.jmol"));
 
     viewerColour
             .setText(MessageManager.getString("label.colour_with_jmol"));
@@ -411,8 +410,8 @@ public class AppJmol extends StructureViewerBase
     int waitFor = 35;
     int waitTotal = 0;
     while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
-            : !(jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
-                    .getPdbFile().length == files.size()))
+            : !(jmb.isFinishedInit() && jmb.getStructureFiles() != null && jmb
+                    .getStructureFiles().length == files.size()))
     {
       try
       {
@@ -430,7 +429,7 @@ public class AppJmol extends StructureViewerBase
 //        System.err.println("finished: " + jmb.isFinishedInit()
 //                + "; loaded: " + Arrays.toString(jmb.getPdbFile())
 //                + "; files: " + files.toString());
-        jmb.getPdbFile();
+        jmb.getStructureFiles();
         break;
       }
     }
@@ -495,7 +494,7 @@ public class AppJmol extends StructureViewerBase
     String pdbid = "";
     try
     {
-      String[] filesInViewer = jmb.getPdbFile();
+      String[] filesInViewer = jmb.getStructureFiles();
       // TODO: replace with reference fetching/transfer code (validate PDBentry
       // as a DBRef?)
       Pdb pdbclient = new Pdb();
index 75e0c5e..f822358 100644 (file)
@@ -40,8 +40,6 @@ public class AppJmolBinding extends JalviewJmolBinding
 {
   private AppJmol appJmolWindow;
 
-  private FeatureRenderer fr = null;
-
   public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol)
   {
@@ -50,26 +48,6 @@ public class AppJmolBinding extends JalviewJmolBinding
   }
 
   @Override
-  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    AlignmentPanel ap = (alignment == null) ? appJmolWindow
-            .getAlignmentPanel() : (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
-    {
-      if (fr == null)
-      {
-        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
-      }
-      else
-      {
-        ap.updateFeatureRenderer(fr);
-      }
-    }
-
-    return fr;
-  }
-
-  @Override
   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
   {
     return new SequenceRenderer(((AlignmentPanel) alignment).av);
@@ -215,4 +193,18 @@ public class AppJmolBinding extends JalviewJmolBinding
   {
     return appJmolWindow;
   }
+
+  @Override
+  public jalview.api.FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment)
+  {
+    AlignmentPanel ap = (alignment == null) ? appJmolWindow
+            .getAlignmentPanel() : (AlignmentPanel) alignment;
+    if (ap.av.isShowSequenceFeatures())
+    {
+      return ap.av.getAlignPanel().getSeqPanel().seqCanvas.fr;
+    }
+
+    return null;
+  }
 }
index f4a4f4d..a50de77 100644 (file)
@@ -23,6 +23,7 @@ package jalview.gui;
 import jalview.analysis.AlignSeq;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -400,7 +401,7 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
 
   @Override
   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
+          HiddenColumns hidden, SelectionSource source)
   {
     if (source != ap.av)
     {
index 8ab1e61..829fc3e 100644 (file)
@@ -384,7 +384,7 @@ public class AppVarnaBinding extends JalviewVarnaBinding
   }
 
   @Override
-  public String[] getPdbFile()
+  public String[] getStructureFiles()
   {
     return null;
   }
diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java
new file mode 100644 (file)
index 0000000..8a95594
--- /dev/null
@@ -0,0 +1,593 @@
+/*
+ * 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.gui;
+
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.SequenceGroup;
+import jalview.util.MessageManager;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyVetoException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+/**
+ * A dialog where a user can choose and action Tree or PCA calculation options
+ */
+public class CalculationChooser extends JPanel
+{
+  /*
+   * flag for whether gap matches residue in the PID calculation for a Tree
+   * - true gives Jalview 2.10.1 behaviour
+   * - set to false (using Groovy) for a more correct tree
+   * (JAL-374)
+   */
+  private static boolean treeMatchGaps = true;
+
+  private static final Font VERDANA_11PT = new Font("Verdana", 0, 11);
+
+  private static final int MIN_TREE_SELECTION = 3;
+
+  private static final int MIN_PCA_SELECTION = 4;
+
+  AlignFrame af;
+
+  JRadioButton pca;
+
+  JRadioButton neighbourJoining;
+
+  JRadioButton averageDistance;
+
+  JComboBox<String> modelNames;
+
+  JButton calculate;
+
+  private JInternalFrame frame;
+
+  private JCheckBox includeGaps;
+
+  private JCheckBox matchGaps;
+
+  private JCheckBox includeGappedColumns;
+
+  private JCheckBox shorterSequence;
+
+  final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
+
+  List<String> tips = new ArrayList<String>();
+
+  /**
+   * Constructor
+   * 
+   * @param af
+   */
+  public CalculationChooser(AlignFrame alignFrame)
+  {
+    this.af = alignFrame;
+    init();
+    af.alignPanel.setCalculationDialog(this);
+  }
+
+  /**
+   * Lays out the panel and adds it to the desktop
+   */
+  void init()
+  {
+    setLayout(new BorderLayout());
+    frame = new JInternalFrame();
+    frame.setContentPane(this);
+    this.setBackground(Color.white);
+    frame.addFocusListener(new FocusListener()
+    {
+
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+      }
+
+      @Override
+      public void focusGained(FocusEvent e)
+      {
+        validateCalcTypes();
+      }
+    });
+    /*
+     * Layout consists of 3 or 4 panels:
+     * - first with choice of PCA or tree method NJ or AV
+     * - second with choice of score model
+     * - third with score model parameter options [suppressed]
+     * - fourth with OK and Cancel
+     */
+    pca = new JRadioButton(
+            MessageManager.getString("label.principal_component_analysis"));
+    pca.setOpaque(false);
+    neighbourJoining = new JRadioButton(
+            MessageManager.getString("label.tree_calc_nj"));
+    neighbourJoining.setSelected(true);
+    averageDistance = new JRadioButton(
+            MessageManager.getString("label.tree_calc_av"));
+    neighbourJoining.setOpaque(false);
+
+    JPanel calcChoicePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    calcChoicePanel.setOpaque(false);
+
+    // first create the Tree calculation's border panel
+    JPanel treePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    treePanel.setOpaque(false);
+
+    treePanel.setBorder(BorderFactory.createTitledBorder(MessageManager
+            .getString("label.tree")));
+
+    // then copy the inset dimensions for the border-less PCA panel
+    JPanel pcaBorderless = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    Insets b = treePanel.getBorder().getBorderInsets(treePanel);
+    pcaBorderless.setBorder(BorderFactory.createEmptyBorder(2, b.left, 2,
+            b.right));
+    pcaBorderless.setOpaque(false);
+
+    pcaBorderless.add(pca, FlowLayout.LEFT);
+    calcChoicePanel.add(pcaBorderless, FlowLayout.LEFT);
+
+    treePanel.add(neighbourJoining);
+    treePanel.add(averageDistance);
+
+    calcChoicePanel.add(treePanel);
+
+    ButtonGroup calcTypes = new ButtonGroup();
+    calcTypes.add(pca);
+    calcTypes.add(neighbourJoining);
+    calcTypes.add(averageDistance);
+    
+    ActionListener calcChanged = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        validateCalcTypes();
+      }
+    };
+    pca.addActionListener(calcChanged);
+    neighbourJoining.addActionListener(calcChanged);
+    averageDistance.addActionListener(calcChanged);
+
+    /*
+     * score models drop-down - with added tooltips!
+     */
+    modelNames = buildModelOptionsList();
+
+    JPanel scoreModelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+    scoreModelPanel.setOpaque(false);
+    scoreModelPanel.add(modelNames);
+
+    /*
+     * score model parameters
+     */
+    JPanel paramsPanel = new JPanel(new GridLayout(5, 1));
+    paramsPanel.setOpaque(false);
+    includeGaps = new JCheckBox("Include gaps");
+    matchGaps = new JCheckBox("Match gaps");
+    includeGappedColumns = new JCheckBox("Include gapped columns");
+    shorterSequence = new JCheckBox("Match on shorter sequence");
+    paramsPanel.add(new JLabel("Pairwise sequence scoring options"));
+    paramsPanel.add(includeGaps);
+    paramsPanel.add(matchGaps);
+    paramsPanel.add(includeGappedColumns);
+    paramsPanel.add(shorterSequence);
+
+    /*
+     * OK / Cancel buttons
+     */
+    calculate = new JButton(MessageManager.getString("action.calculate"));
+    calculate.setFont(VERDANA_11PT);
+    calculate.addActionListener(new java.awt.event.ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        calculate_actionPerformed();
+      }
+    });
+    JButton close = new JButton(MessageManager.getString("action.close"));
+    close.setFont(VERDANA_11PT);
+    close.addActionListener(new java.awt.event.ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        close_actionPerformed();
+      }
+    });
+    JPanel actionPanel = new JPanel();
+    actionPanel.setOpaque(false);
+    actionPanel.add(calculate);
+    actionPanel.add(close);
+
+    boolean includeParams = false;
+    this.add(calcChoicePanel, BorderLayout.CENTER);
+    calcChoicePanel.add(scoreModelPanel);
+    if (includeParams)
+    {
+      scoreModelPanel.add(paramsPanel);
+    }
+    this.add(actionPanel, BorderLayout.SOUTH);
+
+    int width = 350;
+    int height = includeParams ? 420 : 240;
+
+    setMinimumSize(new Dimension(325, height - 10));
+    String title = MessageManager.getString("label.choose_calculation");
+    if (af.getViewport().viewName != null)
+    {
+      title = title + " (" + af.getViewport().viewName + ")";
+    }
+
+    Desktop.addInternalFrame(frame, title, width, height, false);
+    calcChoicePanel.doLayout();
+    revalidate();
+    /*
+     * null the AlignmentPanel's reference to the dialog when it is closed
+     */
+    frame.addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosed(InternalFrameEvent evt)
+      {
+        af.alignPanel.setCalculationDialog(null);
+      };
+    });
+
+    frame.setLayer(JLayeredPane.PALETTE_LAYER);
+  }
+
+  /**
+   * enable calculations applicable for the current alignment or selection.
+   */
+  protected void validateCalcTypes()
+  {
+    int size = af.getViewport().getAlignment().getHeight();
+    if (af.getViewport().getSelectionGroup() != null)
+    {
+      size = af.getViewport().getSelectionGroup().getSize();
+    }
+
+    /*
+     * disable calc options for which there is insufficient input data
+     * return value of true means enabled and selected
+     */
+    boolean checkPca = checkEnabled(pca, size, MIN_PCA_SELECTION);
+    boolean checkNeighbourJoining = checkEnabled(neighbourJoining, size,
+            MIN_TREE_SELECTION);
+    boolean checkAverageDistance = checkEnabled(averageDistance, size,
+            MIN_TREE_SELECTION);
+
+    if (checkPca || checkNeighbourJoining || checkAverageDistance)
+    {
+      calculate.setToolTipText(null);
+      calculate.setEnabled(true);
+    }
+    else
+    {
+      calculate.setEnabled(false);
+    }
+    updateScoreModels(modelNames, tips);
+  }
+
+  /**
+   * Check the input and disable a calculation's radio button if necessary. A
+   * tooltip is shown for disabled calculations.
+   * 
+   * @param calc
+   *          - radio button for the calculation being validated
+   * @param size
+   *          - size of input to calculation
+   * @param minsize
+   *          - minimum size for calculation
+   * @return true if size >= minsize and calc.isSelected
+   */
+  private boolean checkEnabled(JRadioButton calc, int size, int minsize)
+  {
+    String ttip = MessageManager.formatMessage(
+            "label.you_need_at_least_n_sequences", minsize);
+
+    calc.setEnabled(size >= minsize);
+    if (!calc.isEnabled())
+    {
+      calc.setToolTipText(ttip);
+    }
+    else
+    {
+      calc.setToolTipText(null);
+    }
+    if (calc.isSelected())
+    {
+      modelNames.setEnabled(calc.isEnabled());
+      if (calc.isEnabled())
+      {
+        return true;
+      }
+      else
+      {
+        calculate.setToolTipText(ttip);
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A rather elaborate helper method (blame Swing, not me) that builds a
+   * drop-down list of score models (by name) with descriptions as tooltips.
+   * There is also a tooltip shown for the currently selected item when hovering
+   * over it (without opening the list).
+   */
+  protected JComboBox<String> buildModelOptionsList()
+  {
+    final JComboBox<String> scoreModelsCombo = new JComboBox<String>();
+    scoreModelsCombo.setRenderer(renderer);
+
+    /*
+     * show tooltip on mouse over the combobox
+     * note the listener has to be on the components that make up
+     * the combobox, doesn't work if just on the combobox
+     */
+    final MouseAdapter mouseListener = new MouseAdapter()
+    {
+      @Override
+      public void mouseEntered(MouseEvent e)
+      {
+        scoreModelsCombo.setToolTipText(tips.get(scoreModelsCombo.getSelectedIndex()));
+      }
+
+      @Override
+      public void mouseExited(MouseEvent e)
+      {
+        scoreModelsCombo.setToolTipText(null);
+      }
+    };
+    for (Component c : scoreModelsCombo.getComponents())
+    {
+      c.addMouseListener(mouseListener);
+    }
+
+    updateScoreModels(scoreModelsCombo, tips);
+
+    /*
+     * set the list of tooltips on the combobox's renderer
+     */
+    renderer.setTooltips(tips);
+
+    return scoreModelsCombo;
+  }
+
+  private void updateScoreModels(JComboBox<String> comboBox,
+          List<String> toolTips)
+  {
+    Object curSel = comboBox.getSelectedItem();
+    toolTips.clear();
+    DefaultComboBoxModel<String> model = new DefaultComboBoxModel<String>();
+
+    /*
+     * now we can actually add entries to the combobox,
+     * remembering their descriptions for tooltips
+     */
+    ScoreModels scoreModels = ScoreModels.getInstance();
+    boolean selectedIsPresent = false;
+    for (ScoreModelI sm : scoreModels.getModels())
+    {
+      boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
+      if (sm.isDNA() && nucleotide || sm.isProtein() && !nucleotide)
+      {
+        if (curSel != null && sm.getName().equals(curSel))
+        {
+          selectedIsPresent = true;
+          curSel = sm.getName();
+        }
+        model.addElement(sm.getName());
+
+        /*
+         * tooltip is description if provided, else text lookup with
+         * fallback on the model name
+         */
+        String tooltip = sm.getDescription();
+        if (tooltip == null)
+        {
+          tooltip = MessageManager.getStringOrReturn("label.score_model_",
+                  sm.getName());
+        }
+        toolTips.add(tooltip);
+      }
+    }
+    if (selectedIsPresent)
+    {
+      model.setSelectedItem(curSel);
+    }
+    // finally, update the model
+    comboBox.setModel(model);
+  }
+
+  /**
+   * Open and calculate the selected tree or PCA on 'OK'
+   */
+  protected void calculate_actionPerformed()
+  {
+    boolean doPCA = pca.isSelected();
+    String modelName = modelNames.getSelectedItem().toString();
+    SimilarityParamsI params = getSimilarityParameters(doPCA);
+
+    if (doPCA)
+    {
+      openPcaPanel(modelName, params);
+    }
+    else
+    {
+      openTreePanel(modelName, params);
+    }
+
+    // closeFrame();
+  }
+
+  /**
+   * Open a new Tree panel on the desktop
+   * 
+   * @param modelName
+   * @param params
+   */
+  protected void openTreePanel(String modelName, SimilarityParamsI params)
+  {
+    /*
+     * gui validation shouldn't allow insufficient sequences here, but leave
+     * this check in in case this method gets exposed programmatically in future
+     */
+    AlignViewport viewport = af.getViewport();
+    SequenceGroup sg = viewport.getSelectionGroup();
+    if (sg != null && sg.getSize() < MIN_TREE_SELECTION)
+    {
+      JvOptionPane
+              .showMessageDialog(
+                      Desktop.desktop,
+                      MessageManager
+              .formatMessage("label.you_need_at_least_n_sequences",
+                      MIN_TREE_SELECTION),
+                      MessageManager
+                              .getString("label.not_enough_sequences"),
+                      JvOptionPane.WARNING_MESSAGE);
+      return;
+    }
+
+    String treeType = neighbourJoining.isSelected() ? TreeBuilder.NEIGHBOUR_JOINING
+            : TreeBuilder.AVERAGE_DISTANCE;
+    af.newTreePanel(treeType, modelName, params);
+  }
+
+  /**
+   * Open a new PCA panel on the desktop
+   * 
+   * @param modelName
+   * @param params
+   */
+  protected void openPcaPanel(String modelName, SimilarityParamsI params)
+  {
+    AlignViewport viewport = af.getViewport();
+
+    /*
+     * gui validation shouldn't allow insufficient sequences here, but leave
+     * this check in in case this method gets exposed programmatically in future
+     */
+    if (((viewport.getSelectionGroup() != null)
+            && (viewport.getSelectionGroup().getSize() < MIN_PCA_SELECTION) && (viewport
+            .getSelectionGroup().getSize() > 0))
+            || (viewport.getAlignment().getHeight() < MIN_PCA_SELECTION))
+    {
+      JvOptionPane.showInternalMessageDialog(this, MessageManager
+              .formatMessage("label.you_need_at_least_n_sequences",
+                      MIN_PCA_SELECTION), MessageManager
+              .getString("label.sequence_selection_insufficient"),
+              JvOptionPane.WARNING_MESSAGE);
+      return;
+    }
+    new PCAPanel(af.alignPanel, modelName, params);
+  }
+
+  /**
+   * 
+   */
+  protected void closeFrame()
+  {
+    try
+    {
+      frame.setClosed(true);
+    } catch (PropertyVetoException ex)
+    {
+    }
+  }
+
+  /**
+   * Returns a data bean holding parameters for similarity (or distance) model
+   * calculation
+   * 
+   * @param doPCA
+   * @return
+   */
+  protected SimilarityParamsI getSimilarityParameters(boolean doPCA)
+  {
+    // commented out: parameter choices read from gui widgets
+    // SimilarityParamsI params = new SimilarityParams(
+    // includeGappedColumns.isSelected(), matchGaps.isSelected(),
+    // includeGaps.isSelected(), shorterSequence.isSelected());
+
+    boolean includeGapGap = true;
+    boolean includeGapResidue = true;
+    boolean matchOnShortestLength = false;
+
+    /*
+     * 'matchGaps' flag is only used in the PID calculation
+     * - set to false for PCA so that PCA using PID reproduces SeqSpace PCA
+     * - set to true for Tree to reproduce Jalview 2.10.1 calculation
+     * - set to false for Tree for a more correct calculation (JAL-374)
+     */
+    boolean matchGap = doPCA ? false : treeMatchGaps;
+
+    return new SimilarityParams(includeGapGap, matchGap, includeGapResidue, matchOnShortestLength);
+  }
+
+  /**
+   * Closes dialog on Close button press
+   */
+  protected void close_actionPerformed()
+  {
+    try
+    {
+      frame.setClosed(true);
+    } catch (Exception ex)
+    {
+    }
+  }
+}
index 530f4fe..eadc2ad 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.FeatureRenderer;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.ChimeraCommands;
 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
@@ -35,16 +37,22 @@ import jalview.util.Platform;
 import jalview.ws.dbsources.Pdb;
 
 import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -58,8 +66,6 @@ public class ChimeraViewFrame extends StructureViewerBase
 {
   private JalviewChimeraBinding jmb;
 
-  private boolean allChainsSelected = false;
-
   private IProgressIndicator progressBar = null;
 
   /*
@@ -71,6 +77,10 @@ public class ChimeraViewFrame extends StructureViewerBase
 
   private Random random = new Random();
 
+  private int myWidth = 500;
+
+  private int myHeight = 150;
+
   /**
    * Initialise menu options.
    */
@@ -89,6 +99,106 @@ public class ChimeraViewFrame extends StructureViewerBase
     helpItem.setText(MessageManager.getString("label.chimera_help"));
     savemenu.setVisible(false); // not yet implemented
     viewMenu.add(fitToWindow);
+
+    /*
+     * exchange of Jalview features and Chimera attributes is for now
+     * an optionally enabled experimental feature
+     */
+    if (Desktop.instance.showExperimental())
+    {
+      JMenuItem writeFeatures = new JMenuItem(
+              MessageManager.getString("label.create_chimera_attributes"));
+      writeFeatures.setToolTipText(MessageManager
+              .getString("label.create_chimera_attributes_tip"));
+      writeFeatures.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          sendFeaturesToChimera();
+        }
+      });
+      viewerActionMenu.add(writeFeatures);
+
+      final JMenu fetchAttributes = new JMenu(
+              MessageManager.getString("label.fetch_chimera_attributes"));
+      fetchAttributes.setToolTipText(MessageManager
+              .getString("label.fetch_chimera_attributes_tip"));
+      fetchAttributes.addMouseListener(new MouseAdapter()
+      {
+
+        @Override
+        public void mouseEntered(MouseEvent e)
+        {
+          buildAttributesMenu(fetchAttributes);
+        }
+      });
+      viewerActionMenu.add(fetchAttributes);
+    }
+  }
+
+  /**
+   * Query Chimera for its residue attribute names and add them as items off the
+   * attributes menu
+   * 
+   * @param attributesMenu
+   */
+  protected void buildAttributesMenu(JMenu attributesMenu)
+  {
+    List<String> atts = jmb.sendChimeraCommand("list resattr", true);
+    if (atts == null)
+    {
+      return;
+    }
+    attributesMenu.removeAll();
+    Collections.sort(atts);
+    for (String att : atts)
+    {
+      final String attName = att.split(" ")[1];
+
+      /*
+       * ignore 'jv_*' attributes, as these are Jalview features that have
+       * been transferred to residue attributes in Chimera!
+       */
+      if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
+      {
+        JMenuItem menuItem = new JMenuItem(attName);
+        menuItem.addActionListener(new ActionListener()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            getChimeraAttributes(attName);
+          }
+        });
+        attributesMenu.add(menuItem);
+      }
+    }
+  }
+
+  /**
+   * Read residues in Chimera with the given attribute name, and set as features
+   * on the corresponding sequence positions (if any)
+   * 
+   * @param attName
+   */
+  protected void getChimeraAttributes(String attName)
+  {
+    jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
+  }
+
+  /**
+   * Send a command to Chimera to create residue attributes for Jalview features
+   * <p>
+   * The syntax is: setattr r <attName> <attValue> <atomSpec>
+   * <p>
+   * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
+   */
+  protected void sendFeaturesToChimera()
+  {
+    int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
+    statusBar.setText(MessageManager.formatMessage("label.attributes_set",
+            count));
   }
 
   /**
@@ -146,7 +256,6 @@ public class ChimeraViewFrame extends StructureViewerBase
           SequenceI[][] seqs)
   {
     createProgressBar();
-    // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs
     jmb = new JalviewChimeraBindingModel(this,
             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
     addAlignmentPanel(ap);
@@ -158,7 +267,7 @@ public class ChimeraViewFrame extends StructureViewerBase
       useAlignmentPanelForSuperposition(ap);
     }
     jmb.setColourBySequence(true);
-    setSize(400, 400); // probably should be a configurable/dynamic default here
+    setSize(myWidth, myHeight);
     initMenus();
 
     addingStructures = false;
@@ -270,7 +379,7 @@ public class ChimeraViewFrame extends StructureViewerBase
   void initChimera()
   {
     jmb.setFinishedInit(false);
-    jalview.gui.Desktop.addInternalFrame(this,
+    Desktop.addInternalFrame(this,
             jmb.getViewerTitle(getViewerName(), true), getBounds().width,
             getBounds().height);
 
@@ -294,12 +403,10 @@ public class ChimeraViewFrame extends StructureViewerBase
                         + chimeraSessionFile);
       }
     }
-    jmb.setFinishedInit(true);
 
     jmb.startChimeraListener();
   }
 
-
   /**
    * Show only the selected chain(s) in the viewer
    */
@@ -383,7 +490,7 @@ public class ChimeraViewFrame extends StructureViewerBase
     StructureFile pdb = null;
     try
     {
-      String[] curfiles = jmb.getPdbFile(); // files currently in viewer
+      String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
       // TODO: replace with reference fetching/transfer code (validate PDBentry
       // as a DBRef?)
       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
@@ -450,6 +557,7 @@ public class ChimeraViewFrame extends StructureViewerBase
 
     if (files.length() > 0)
     {
+      jmb.setFinishedInit(false);
       if (!addingStructures)
       {
         try
@@ -507,10 +615,21 @@ public class ChimeraViewFrame extends StructureViewerBase
           }
         }
       }
+
       jmb.refreshGUI();
       jmb.setFinishedInit(true);
       jmb.setLoadingFromArchive(false);
 
+      /*
+       * ensure that any newly discovered features (e.g. RESNUM)
+       * are added to any open feature settings dialog
+       */
+      FeatureRenderer fr = getBinding().getFeatureRenderer(null);
+      if (fr != null)
+      {
+        fr.featuresAdded();
+      }
+
       // refresh the sequence colours for the new structure(s)
       for (AlignmentPanel ap : _colourwith)
       {
@@ -656,7 +775,7 @@ public class ChimeraViewFrame extends StructureViewerBase
     {
       BrowserLauncher
               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
-    } catch (Exception ex)
+    } catch (IOException ex)
     {
     }
   }
@@ -754,4 +873,20 @@ public class ChimeraViewFrame extends StructureViewerBase
   {
     return "Chimera";
   }
+
+  /**
+   * Sends commands to align structures according to associated alignment(s).
+   * 
+   * @return
+   */
+  @Override
+  protected String alignStructs_withAllAlignPanels()
+  {
+    String reply = super.alignStructs_withAllAlignPanels();
+    if (reply != null)
+    {
+      statusBar.setText("Superposition failed: " + reply);
+    }
+    return reply;
+  }
 }
index 31780d6..b2b9574 100644 (file)
@@ -3,6 +3,7 @@ package jalview.gui;
 import jalview.bin.Cache;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.UserColourScheme;
@@ -22,6 +23,12 @@ public class ColourMenuHelper
 {
   public interface ColourChangeListener
   {
+    /**
+     * Change colour scheme to the selected scheme
+     * 
+     * @param name
+     *          the registered (unique) name of a colour scheme
+     */
     void changeColour_actionPerformed(String name);
   }
 
@@ -36,7 +43,7 @@ public class ColourMenuHelper
    * <li>Clustal</li>
    * <li>...other 'built-in' colours</li>
    * <li>...any user-defined colours</li>
-   * <li>User Defined..</li>
+   * <li>User Defined..(only for AlignFrame menu)</li>
    * </ul>
    * 
    * @param colourMenu
@@ -76,7 +83,7 @@ public class ColourMenuHelper
     }
 
     /*
-     * scan registered colour schemes (built-in or user-defined
+     * scan registered colour schemes (built-in or user-defined)
      * and add them to the menu (in the order they were registered)
      */
     Iterable<ColourSchemeI> colourSchemes = ColourSchemes.getInstance()
@@ -103,14 +110,14 @@ public class ColourMenuHelper
         /*
          * user-defined colour scheme loaded on startup or during the
          * Jalview session; right-click on this offers the option to
-         * remove it as a colour choice
+         * remove it as a colour choice (unless currently selected)
          */
         radioItem.addMouseListener(new MouseAdapter()
         {
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isPopupTrigger()) // Mac
+            if (evt.isPopupTrigger() && !radioItem.isSelected()) // Mac
             {
               offerRemoval();
             }
@@ -119,7 +126,7 @@ public class ColourMenuHelper
           @Override
           public void mouseReleased(MouseEvent evt)
           {
-            if (evt.isPopupTrigger()) // Windows
+            if (evt.isPopupTrigger() && !radioItem.isSelected()) // Windows
             {
               offerRemoval();
             }
@@ -170,6 +177,7 @@ public class ColourMenuHelper
       final String label = MessageManager.getString("action.user_defined");
       JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem(
               label);
+      userDefinedColour.setName(ResidueColourScheme.USER_DEFINED);
       userDefinedColour.addActionListener(new ActionListener()
       {
         @Override
@@ -186,20 +194,22 @@ public class ColourMenuHelper
   }
 
   /**
-   * Marks as selected the colour menu item matching the given name, or the
-   * first item ('None') if no match is found
+   * Marks as selected the colour menu item matching the given colour scheme, or
+   * the first item ('None') if no match is found. If the colour scheme is a
+   * user defined scheme, but not in the menu (this arises if a new scheme is
+   * defined and applied but not saved to file), then menu option
+   * "User Defined.." is selected.
    * 
    * @param colourMenu
-   * @param colourName
+   * @param cs
    */
-  public static void setColourSelected(JMenu colourMenu, String colourName)
+  public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
   {
-    if (colourName == null)
-    {
-      return;
-    }
+    String colourName = cs == null ? ResidueColourScheme.NONE : cs
+            .getSchemeName();
 
     JRadioButtonMenuItem none = null;
+    JRadioButtonMenuItem userDefined = null;
 
     /*
      * select the radio button whose name matches the colour name
@@ -209,38 +219,39 @@ public class ColourMenuHelper
     {
       if (menuItem instanceof JRadioButtonMenuItem)
       {
-        String buttonName = ((JRadioButtonMenuItem) menuItem).getName();
-        if (colourName.equals(buttonName))
+        JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) menuItem;
+        String buttonName = radioButton.getName();
+        if (buttonName.equals(colourName))
         {
-          ((JRadioButtonMenuItem) menuItem).setSelected(true);
+          radioButton.setSelected(true);
           return;
         }
         if (ResidueColourScheme.NONE.equals(buttonName))
         {
-          none = (JRadioButtonMenuItem) menuItem;
+          none = radioButton;
+        }
+        if (ResidueColourScheme.USER_DEFINED.equals(buttonName))
+        {
+          userDefined = radioButton;
         }
       }
     }
-    if (none != null)
+
+    /*
+     * no match by name; select User Defined.. if current scheme is a 
+     * user defined one, else select None
+     */
+    if (cs instanceof UserColourScheme && userDefined != null)
+    {
+      userDefined.setSelected(true);
+    }
+    else if (none != null)
     {
       none.setSelected(true);
     }
   }
 
   /**
-   * Marks as selected the colour menu item matching the given colour scheme, or
-   * the first item ('None') if no match is found
-   * 
-   * @param colourMenu
-   * @param cs
-   */
-  public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
-  {
-    setColourSelected(colourMenu, cs == null ? ResidueColourScheme.NONE
-            : cs.getSchemeName());
-  }
-
-  /**
    * Updates the USER_DEFINE_COLOURS preference to remove any de-registered
    * colour scheme
    */
@@ -258,7 +269,7 @@ public class ColourMenuHelper
     {
       try
       {
-        UserColourScheme ucs = ColourSchemes.loadColourScheme(file);
+        UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
         if (ucs != null
                 && ColourSchemes.getInstance().nameExists(ucs.getName()))
         {
diff --git a/src/jalview/gui/ComboBoxTooltipRenderer.java b/src/jalview/gui/ComboBoxTooltipRenderer.java
new file mode 100644 (file)
index 0000000..b776757
--- /dev/null
@@ -0,0 +1,42 @@
+package jalview.gui;
+
+import java.awt.Component;
+import java.util.List;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JComponent;
+import javax.swing.JList;
+
+/**
+ * A helper class to render a combobox with tooltips
+ * 
+ * @see http
+ *      ://stackoverflow.com/questions/480261/java-swing-mouseover-text-on-jcombobox
+ *      -items
+ */
+public class ComboBoxTooltipRenderer extends DefaultListCellRenderer
+{
+  private static final long serialVersionUID = 1L;
+
+  List<String> tooltips;
+
+  @Override
+  public Component getListCellRendererComponent(JList list, Object value,
+          int index, boolean isSelected, boolean cellHasFocus)
+  {
+
+    JComponent comp = (JComponent) super.getListCellRendererComponent(list,
+            value, index, isSelected, cellHasFocus);
+
+    if (-1 < index && null != value && null != tooltips)
+    {
+      list.setToolTipText(tooltips.get(index));
+    }
+    return comp;
+  }
+
+  public void setTooltips(List<String> tips)
+  {
+    this.tooltips = tips;
+  }
+}
index 280b4c0..de7574c 100644 (file)
@@ -84,6 +84,10 @@ public class Console extends WindowAdapter implements WindowListener,
   // are we attached to some parent Desktop
   Desktop parent = null;
 
+  private int MIN_WIDTH = 300;
+
+  private int MIN_HEIGHT = 250;
+
   public Console()
   {
     // create all components and add them
@@ -243,7 +247,9 @@ public class Console extends WindowAdapter implements WindowListener,
             .getLocalGraphicsEnvironment();
     String[] fontNames = ge.getAvailableFontFamilyNames();
     for (int n = 0; n < fontNames.length; n++)
+    {
       System.out.println(fontNames[n]);
+    }
     // Testing part: simple an error thrown anywhere in this JVM will be printed
     // on the Console
     // We do it with a seperate Thread becasue we don't wan't to break a Thread
@@ -259,9 +265,13 @@ public class Console extends WindowAdapter implements WindowListener,
     JFrame frame = new JFrame(string);
     frame.setName(string);
     if (x == -1)
-      x = (int) (i / 2);
+    {
+      x = i / 2;
+    }
     if (y == -1)
-      y = (int) (j / 2);
+    {
+      y = j / 2;
+    }
     frame.setBounds(x, y, i, j);
     return frame;
   }
@@ -298,6 +308,7 @@ public class Console extends WindowAdapter implements WindowListener,
       frame = initFrame("Jalview Java Console", bounds.width,
               bounds.height, bounds.x, bounds.y);
     }
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
     // desktop.add(frame);
     initConsole(false);
     JalviewAppender jappender = new JalviewAppender();
@@ -345,6 +356,7 @@ public class Console extends WindowAdapter implements WindowListener,
     // System.exit(0);
   }
 
+  @Override
   public synchronized void windowClosed(WindowEvent evt)
   {
     frame.setVisible(false);
@@ -365,6 +377,7 @@ public class Console extends WindowAdapter implements WindowListener,
     }
   }
 
+  @Override
   public synchronized void windowClosing(WindowEvent evt)
   {
     frame.setVisible(false); // default behaviour of JFrame
@@ -373,12 +386,14 @@ public class Console extends WindowAdapter implements WindowListener,
     // frame.dispose();
   }
 
+  @Override
   public synchronized void actionPerformed(ActionEvent evt)
   {
     trimBuffer(true);
     // textArea.setText("");
   }
 
+  @Override
   public synchronized void run()
   {
     try
@@ -410,7 +425,9 @@ public class Console extends WindowAdapter implements WindowListener,
           // lines++;
         }
         if (quit)
+        {
           return;
+        }
       }
 
       while (Thread.currentThread() == reader2)
@@ -439,7 +456,9 @@ public class Console extends WindowAdapter implements WindowListener,
           // lines++;
         }
         if (quit)
+        {
           return;
+        }
       }
       while (Thread.currentThread() == textAppender)
       {
@@ -531,6 +550,7 @@ public class Console extends WindowAdapter implements WindowListener,
     long time = System.nanoTime();
     javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         displayPipe.append(input); // change to stringBuffer
@@ -598,7 +618,9 @@ public class Console extends WindowAdapter implements WindowListener,
     {
       int available = in.available();
       if (available == 0)
+      {
         break;
+      }
       byte b[] = new byte[available];
       in.read(b);
       input = input + new String(b, 0, b.length);
index 7a0b0af..a5aa9eb 100644 (file)
@@ -28,7 +28,7 @@ import jalview.api.FeaturesDisplayedI;
 import jalview.api.FeaturesSourceI;
 import jalview.bin.Jalview;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.io.AlignmentFileReaderI;
 import jalview.io.AppletFormatAdapter;
@@ -284,8 +284,8 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
         AlignFrame af;
         if (source instanceof ComplexAlignFile)
         {
-          ColumnSelection colSel = ((ComplexAlignFile) source)
-                  .getColumnSelection();
+          HiddenColumns hidden = ((ComplexAlignFile) source)
+                  .getHiddenColumns();
           SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
                   .getHiddenSequences();
           boolean showSeqFeatures = ((ComplexAlignFile) source)
@@ -294,7 +294,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
                   .getGlobalColourScheme();
           FeaturesDisplayedI fd = ((ComplexAlignFile) source)
                   .getDisplayedFeatures();
-          af = new AlignFrame(al, hiddenSeqs, colSel,
+          af = new AlignFrame(al, hiddenSeqs, hidden,
                   AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
           af.getViewport().setShowSequenceFeatures(showSeqFeatures);
           af.getViewport().setFeaturesDisplayed(fd);
index dc16a57..d076ba0 100644 (file)
@@ -129,6 +129,15 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         DropTargetListener, ClipboardOwner, IProgressIndicator,
         jalview.api.StructureSelectionManagerProvider
 {
+  private static int DEFAULT_MIN_WIDTH = 300;
+
+  private static int DEFAULT_MIN_HEIGHT = 250;
+
+  private static int ALIGN_FRAME_DEFAULT_MIN_WIDTH = 600;
+
+  private static int ALIGN_FRAME_DEFAULT_MIN_HEIGHT = 70;
+
+  private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
 
   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
 
@@ -327,19 +336,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     instance = this;
     doVamsasClientCheck();
 
-    groovyShell = new JMenuItem();
-    groovyShell.setText(MessageManager.getString("label.groovy_console"));
-    groovyShell.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        groovyShell_actionPerformed();
-      }
-    });
-    toolsMenu.add(groovyShell);
-    groovyShell.setVisible(true);
-
     doConfigureStructurePrefs();
     setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -394,6 +390,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     showConsole(showjconsole);
 
     showNews.setVisible(false);
+    
+    experimentalFeatures.setSelected(showExperimental());
 
     getIdentifiersOrgData();
 
@@ -490,6 +488,19 @@ public class Desktop extends jalview.jbgui.GDesktop implements
             });
   }
 
+  /**
+   * Answers true if user preferences to enable experimental features is True
+   * (on), else false
+   * 
+   * @return
+   */
+  public boolean showExperimental()
+  {
+    String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
+            Boolean.FALSE.toString());
+    return Boolean.valueOf(experimental).booleanValue();
+  }
+
   public void doConfigureStructurePrefs()
   {
     // configure services
@@ -742,7 +753,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
   public static synchronized void addInternalFrame(
           final JInternalFrame frame, String title, int w, int h)
   {
-    addInternalFrame(frame, title, true, w, h, true);
+    addInternalFrame(frame, title, true, w, h, true, false);
   }
 
   /**
@@ -764,7 +775,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
           final JInternalFrame frame, String title, boolean makeVisible,
           int w, int h)
   {
-    addInternalFrame(frame, title, makeVisible, w, h, true);
+    addInternalFrame(frame, title, makeVisible, w, h, true, false);
   }
 
   /**
@@ -785,7 +796,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
           final JInternalFrame frame, String title, int w, int h,
           boolean resizable)
   {
-    addInternalFrame(frame, title, true, w, h, resizable);
+    addInternalFrame(frame, title, true, w, h, resizable, false);
   }
 
   /**
@@ -804,10 +815,12 @@ public class Desktop extends jalview.jbgui.GDesktop implements
    *          height
    * @param resizable
    *          Allow resize
+   * @param ignoreMinSize
+   *          Do not set the default minimum size for frame
    */
   public static synchronized void addInternalFrame(
           final JInternalFrame frame, String title, boolean makeVisible,
-          int w, int h, boolean resizable)
+          int w, int h, boolean resizable, boolean ignoreMinSize)
   {
 
     // TODO: allow callers to determine X and Y position of frame (eg. via
@@ -833,6 +846,23 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     openFrameCount++;
 
+    if (!ignoreMinSize)
+    {
+      frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH,
+              DEFAULT_MIN_HEIGHT));
+
+      // Set default dimension for Alignment Frame window.
+      // The Alignment Frame window could be added from a number of places,
+      // hence,
+      // I did this here in order not to miss out on any Alignment frame.
+      if (frame instanceof AlignFrame)
+      {
+        frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
+                ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
+      }
+    }
+
+
     frame.setVisible(makeVisible);
     frame.setClosable(true);
     frame.setResizable(resizable);
@@ -2492,8 +2522,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
   }
 
-  protected JMenuItem groovyShell;
-
   /**
    * Accessor method to quickly get all the AlignmentFrames loaded.
    * 
@@ -2579,6 +2607,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
   /**
    * Add Groovy Support to Jalview
    */
+  @Override
   public void groovyShell_actionPerformed()
   {
     try
@@ -2854,13 +2883,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
     {
-      if (jalview.ws.jws2.Jws2Discoverer.getDiscoverer().isRunning())
-      {
-        jalview.ws.jws2.Jws2Discoverer.getDiscoverer().setAborted(true);
-      }
       t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(
               changeSupport);
-
     }
     Thread t3 = null;
     {
@@ -3398,4 +3422,14 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       }
     }
   }
+
+  /**
+   * Sets the Preferences property for experimental features to True or False
+   * depending on the state of the controlling menu item
+   */
+  @Override
+  protected void showExperimental_actionPerformed(boolean selected)
+  {
+    Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
+  }
 }
index ed6a3c5..55c4323 100644 (file)
@@ -25,6 +25,7 @@ import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.io.FeaturesFile;
 import jalview.schemes.FeatureColour;
 import jalview.util.ColorUtils;
 import jalview.util.MessageManager;
@@ -42,17 +43,19 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSpinner;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
 
 /**
  * DOCUMENT ME!
@@ -61,56 +64,116 @@ import javax.swing.SwingConstants;
  * @version $Revision$
  */
 public class FeatureRenderer extends
-        jalview.renderer.seqfeatures.FeatureRenderer implements
-        jalview.api.FeatureRenderer
+        jalview.renderer.seqfeatures.FeatureRenderer
 {
+  /*
+   * defaults for creating a new feature are the last created
+   * feature type and group
+   */
+  static String lastFeatureAdded = "feature_1";
+
+  static String lastFeatureGroupAdded = "Jalview";
+
   Color resBoxColour;
 
   AlignmentPanel ap;
 
   /**
-   * Creates a new FeatureRenderer object.
+   * Creates a new FeatureRenderer object
    * 
-   * @param av
-   *          DOCUMENT ME!
+   * @param alignPanel
    */
-  public FeatureRenderer(AlignmentPanel ap)
+  public FeatureRenderer(AlignmentPanel alignPanel)
   {
-    super(ap.av);
-    this.ap = ap;
-    if (ap != null && ap.getSeqPanel() != null
-            && ap.getSeqPanel().seqCanvas != null
-            && ap.getSeqPanel().seqCanvas.fr != null)
+    super(alignPanel.av);
+    this.ap = alignPanel;
+    if (alignPanel.getSeqPanel() != null
+            && alignPanel.getSeqPanel().seqCanvas != null
+            && alignPanel.getSeqPanel().seqCanvas.fr != null)
     {
-      transferSettings(ap.getSeqPanel().seqCanvas.fr);
+      transferSettings(alignPanel.getSeqPanel().seqCanvas.fr);
     }
   }
 
-  // // /////////////
-  // // Feature Editing Dialog
-  // // Will be refactored in next release.
-
-  static String lastFeatureAdded;
-
-  static String lastFeatureGroupAdded;
-
-  static String lastDescriptionAdded;
-
   FeatureColourI oldcol, fcol;
 
   int featureIndex = 0;
 
-  boolean amendFeatures(final SequenceI[] sequences,
-          final SequenceFeature[] features, boolean newFeatures,
-          final AlignmentPanel ap)
+  /**
+   * Presents a dialog allowing the user to add new features, or amend or delete
+   * existing features. Currently this can be on
+   * <ul>
+   * <li>double-click on a sequence - Amend/Delete features at position</li>
+   * <li>Create sequence feature from pop-up menu on selected region</li>
+   * <li>Create features for pattern matches from Find</li>
+   * </ul>
+   * If the supplied feature type is null, show (and update on confirm) the type
+   * and group of the last new feature created (with initial defaults of
+   * "feature_1" and "Jalview").
+   * 
+   * @param sequences
+   *          the sequences features are to be created on (if creating
+   *          features), or a single sequence (if amending features)
+   * @param features
+   *          the current features at the position (if amending), or template
+   *          new feature(s) with start/end position set (if creating)
+   * @param create
+   *          true to create features, false to amend or delete
+   * @param alignPanel
+   * @return
+   */
+  protected boolean amendFeatures(final List<SequenceI> sequences,
+          final List<SequenceFeature> features, boolean create,
+          final AlignmentPanel alignPanel)
   {
-
     featureIndex = 0;
 
-    final JPanel bigPanel = new JPanel(new BorderLayout());
-    final JComboBox overlaps;
+    final JPanel mainPanel = new JPanel(new BorderLayout());
+
     final JTextField name = new JTextField(25);
-    final JTextField source = new JTextField(25);
+    name.getDocument().addDocumentListener(new DocumentListener()
+    {
+      @Override
+      public void insertUpdate(DocumentEvent e)
+      {
+        warnIfTypeHidden(mainPanel, name.getText());
+      }
+
+      @Override
+      public void removeUpdate(DocumentEvent e)
+      {
+        warnIfTypeHidden(mainPanel, name.getText());
+      }
+
+      @Override
+      public void changedUpdate(DocumentEvent e)
+      {
+        warnIfTypeHidden(mainPanel, name.getText());
+      }
+    });
+
+    final JTextField group = new JTextField(25);
+    group.getDocument().addDocumentListener(new DocumentListener()
+    {
+      @Override
+      public void insertUpdate(DocumentEvent e)
+      {
+        warnIfGroupHidden(mainPanel, group.getText());
+      }
+
+      @Override
+      public void removeUpdate(DocumentEvent e)
+      {
+        warnIfGroupHidden(mainPanel, group.getText());
+      }
+
+      @Override
+      public void changedUpdate(DocumentEvent e)
+      {
+        warnIfGroupHidden(mainPanel, group.getText());
+      }
+    });
+
     final JTextArea description = new JTextArea(3, 25);
     final JSpinner start = new JSpinner();
     final JSpinner end = new JSpinner();
@@ -136,14 +199,15 @@ public class FeatureRenderer extends
           if (col != null)
           {
             fcol = new FeatureColour(col);
-            updateColourButton(bigPanel, colour, new FeatureColour(col));
+            updateColourButton(mainPanel, colour, fcol);
           }
         }
         else
         {
           if (fcc == null)
           {
-            final String type = features[featureIndex].getType();
+            final String ft = features.get(featureIndex).getType();
+            final String type = ft == null ? lastFeatureAdded : ft;
             fcc = new FeatureColourChooser(me, type);
             fcc.setRequestFocusEnabled(true);
             fcc.requestFocus();
@@ -157,7 +221,7 @@ public class FeatureRenderer extends
                 fcol = fcc.getLastColour();
                 fcc = null;
                 setColour(type, fcol);
-                updateColourButton(bigPanel, colour, fcol);
+                updateColourButton(mainPanel, colour, fcol);
               }
             });
 
@@ -165,26 +229,27 @@ public class FeatureRenderer extends
         }
       }
     });
-    JPanel tmp = new JPanel();
-    JPanel panel = new JPanel(new GridLayout(3, 1));
+    JPanel gridPanel = new JPanel(new GridLayout(3, 1));
 
-    // /////////////////////////////////////
-    // /MULTIPLE FEATURES AT SELECTED RESIDUE
-    if (!newFeatures && features.length > 1)
+    if (!create && features.size() > 1)
     {
-      panel = new JPanel(new GridLayout(4, 1));
-      tmp = new JPanel();
-      tmp.add(new JLabel(MessageManager.getString("label.select_feature")
+      /*
+       * more than one feature at selected position - add a drop-down
+       * to choose the feature to amend
+       */
+      gridPanel = new JPanel(new GridLayout(4, 1));
+      JPanel choosePanel = new JPanel();
+      choosePanel.add(new JLabel(MessageManager
+              .getString("label.select_feature")
               + ":"));
-      overlaps = new JComboBox();
-      for (int i = 0; i < features.length; i++)
+      final JComboBox<String> overlaps = new JComboBox<String>();
+      for (SequenceFeature sf : features)
       {
-        overlaps.addItem(features[i].getType() + "/"
-                + features[i].getBegin() + "-" + features[i].getEnd()
-                + " (" + features[i].getFeatureGroup() + ")");
+        String text = sf.getType() + "/" + sf.getBegin() + "-"
+                + sf.getEnd() + " (" + sf.getFeatureGroup() + ")";
+        overlaps.addItem(text);
       }
-
-      tmp.add(overlaps);
+      choosePanel.add(overlaps);
 
       overlaps.addItemListener(new ItemListener()
       {
@@ -195,17 +260,18 @@ public class FeatureRenderer extends
           if (index != -1)
           {
             featureIndex = index;
-            name.setText(features[index].getType());
-            description.setText(features[index].getDescription());
-            source.setText(features[index].getFeatureGroup());
-            start.setValue(new Integer(features[index].getBegin()));
-            end.setValue(new Integer(features[index].getEnd()));
+            SequenceFeature sf = features.get(index);
+            name.setText(sf.getType());
+            description.setText(sf.getDescription());
+            group.setText(sf.getFeatureGroup());
+            start.setValue(new Integer(sf.getBegin()));
+            end.setValue(new Integer(sf.getEnd()));
 
             SearchResultsI highlight = new SearchResults();
-            highlight.addResult(sequences[0], features[index].getBegin(),
-                    features[index].getEnd());
+            highlight.addResult(sequences.get(0), sf.getBegin(),
+                    sf.getEnd());
 
-            ap.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
+            alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
 
           }
           FeatureColourI col = getFeatureStyle(name.getText());
@@ -215,32 +281,30 @@ public class FeatureRenderer extends
                     .createColourFromName(name.getText()));
           }
           oldcol = fcol = col;
-          updateColourButton(bigPanel, colour, col);
+          updateColourButton(mainPanel, colour, col);
         }
       });
 
-      panel.add(tmp);
+      gridPanel.add(choosePanel);
     }
-    // ////////
-    // ////////////////////////////////////
 
-    tmp = new JPanel();
-    panel.add(tmp);
-    tmp.add(new JLabel(MessageManager.getString("label.name:"),
+    JPanel namePanel = new JPanel();
+    gridPanel.add(namePanel);
+    namePanel.add(new JLabel(MessageManager.getString("label.name:"),
             JLabel.RIGHT));
-    tmp.add(name);
+    namePanel.add(name);
 
-    tmp = new JPanel();
-    panel.add(tmp);
-    tmp.add(new JLabel(MessageManager.getString("label.group:"),
+    JPanel groupPanel = new JPanel();
+    gridPanel.add(groupPanel);
+    groupPanel.add(new JLabel(MessageManager.getString("label.group:"),
             JLabel.RIGHT));
-    tmp.add(source);
+    groupPanel.add(group);
 
-    tmp = new JPanel();
-    panel.add(tmp);
-    tmp.add(new JLabel(MessageManager.getString("label.colour"),
+    JPanel colourPanel = new JPanel();
+    gridPanel.add(colourPanel);
+    colourPanel.add(new JLabel(MessageManager.getString("label.colour"),
             JLabel.RIGHT));
-    tmp.add(colour);
+    colourPanel.add(colour);
     colour.setPreferredSize(new Dimension(150, 15));
     colour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 9));
     colour.setForeground(Color.black);
@@ -248,126 +312,125 @@ public class FeatureRenderer extends
     colour.setVerticalAlignment(SwingConstants.CENTER);
     colour.setHorizontalTextPosition(SwingConstants.CENTER);
     colour.setVerticalTextPosition(SwingConstants.CENTER);
-    bigPanel.add(panel, BorderLayout.NORTH);
+    mainPanel.add(gridPanel, BorderLayout.NORTH);
 
-    panel = new JPanel();
-    panel.add(new JLabel(MessageManager.getString("label.description:"),
+    JPanel descriptionPanel = new JPanel();
+    descriptionPanel.add(new JLabel(MessageManager
+            .getString("label.description:"),
             JLabel.RIGHT));
     description.setFont(JvSwingUtils.getTextAreaFont());
     description.setLineWrap(true);
-    panel.add(new JScrollPane(description));
+    descriptionPanel.add(new JScrollPane(description));
 
-    if (!newFeatures)
+    if (!create)
     {
-      bigPanel.add(panel, BorderLayout.SOUTH);
+      mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
 
-      panel = new JPanel();
-      panel.add(new JLabel(MessageManager.getString("label.start"),
+      JPanel startEndPanel = new JPanel();
+      startEndPanel.add(new JLabel(MessageManager.getString("label.start"),
               JLabel.RIGHT));
-      panel.add(start);
-      panel.add(new JLabel(MessageManager.getString("label.end"),
+      startEndPanel.add(start);
+      startEndPanel.add(new JLabel(MessageManager.getString("label.end"),
               JLabel.RIGHT));
-      panel.add(end);
-      bigPanel.add(panel, BorderLayout.CENTER);
+      startEndPanel.add(end);
+      mainPanel.add(startEndPanel, BorderLayout.CENTER);
     }
     else
     {
-      bigPanel.add(panel, BorderLayout.CENTER);
+      mainPanel.add(descriptionPanel, BorderLayout.CENTER);
     }
 
-    if (lastFeatureAdded == null)
-    {
-      if (features[0].type != null)
-      {
-        lastFeatureAdded = features[0].type;
-      }
-      else
-      {
-        lastFeatureAdded = "feature_1";
-      }
-    }
-
-    if (lastFeatureGroupAdded == null)
-    {
-      if (features[0].featureGroup != null)
-      {
-        lastFeatureGroupAdded = features[0].featureGroup;
-      }
-      else
-      {
-        lastFeatureGroupAdded = "Jalview";
-      }
-    }
-
-    if (newFeatures)
-    {
-      name.setText(lastFeatureAdded);
-      source.setText(lastFeatureGroupAdded);
-    }
-    else
-    {
-      name.setText(features[0].getType());
-      source.setText(features[0].getFeatureGroup());
-    }
-
-    start.setValue(new Integer(features[0].getBegin()));
-    end.setValue(new Integer(features[0].getEnd()));
-    description.setText(features[0].getDescription());
-    updateColourButton(bigPanel, colour,
-            (oldcol = fcol = getFeatureStyle(name.getText())));
+    /*
+     * default feature type and group to that of the first feature supplied,
+     * or to the last feature created if not supplied (null value) 
+     */
+    SequenceFeature firstFeature = features.get(0);
+    boolean useLastDefaults = firstFeature.getType() == null;
+    final String featureType = useLastDefaults ? lastFeatureAdded
+            : firstFeature.getType();
+    final String featureGroup = useLastDefaults ? lastFeatureGroupAdded
+            : firstFeature.getFeatureGroup();
+    name.setText(featureType);
+    group.setText(featureGroup);
+
+    start.setValue(new Integer(firstFeature.getBegin()));
+    end.setValue(new Integer(firstFeature.getEnd()));
+    description.setText(firstFeature.getDescription());
+    updateColourButton(mainPanel, colour,
+            (oldcol = fcol = getFeatureStyle(featureType)));
     Object[] options;
-    if (!newFeatures)
+    if (!create)
     {
-      options = new Object[] { "Amend", "Delete", "Cancel" };
+      options = new Object[] { MessageManager.getString("label.amend"),
+          MessageManager.getString("action.delete"),
+          MessageManager.getString("action.cancel") };
     }
     else
     {
-      options = new Object[] { "OK", "Cancel" };
+      options = new Object[] { MessageManager.getString("action.ok"),
+          MessageManager.getString("action.cancel") };
     }
 
-    String title = newFeatures ? MessageManager
+    String title = create ? MessageManager
             .getString("label.create_new_sequence_features")
             : MessageManager.formatMessage("label.amend_delete_features",
-                    new String[] { sequences[0].getName() });
+                    new String[] { sequences.get(0).getName() });
 
+    /*
+     * show the dialog
+     */
     int reply = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
-            bigPanel, title, JvOptionPane.YES_NO_CANCEL_OPTION,
+            mainPanel, title, JvOptionPane.YES_NO_CANCEL_OPTION,
             JvOptionPane.QUESTION_MESSAGE, null, options,
             MessageManager.getString("action.ok"));
 
-    jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
+    FeaturesFile ffile = new FeaturesFile();
 
-    if (reply == JvOptionPane.OK_OPTION && name.getText().length() > 0)
+    String enteredType = name.getText().trim();
+    if (reply == JvOptionPane.OK_OPTION && enteredType.length() > 0)
     {
-      // This ensures that the last sequence
-      // is refreshed and new features are rendered
-      lastSeq = null;
-      lastFeatureAdded = name.getText().trim();
-      lastFeatureGroupAdded = source.getText().trim();
-      lastDescriptionAdded = description.getText().replaceAll("\n", " ");
-      // TODO: determine if the null feature group is valid
-      if (lastFeatureGroupAdded.length() < 1)
+      /*
+       * update default values only if creating using default values
+       */
+      if (useLastDefaults)
       {
-        lastFeatureGroupAdded = null;
+        lastFeatureAdded = enteredType;
+        lastFeatureGroupAdded = group.getText().trim();
+        // TODO: determine if the null feature group is valid
+        if (lastFeatureGroupAdded.length() < 1)
+        {
+          lastFeatureGroupAdded = null;
+        }
       }
     }
 
-    if (!newFeatures)
+    if (!create)
     {
-      SequenceFeature sf = features[featureIndex];
+      SequenceFeature sf = features.get(featureIndex);
 
       if (reply == JvOptionPane.NO_OPTION)
       {
-        sequences[0].getDatasetSequence().deleteFeature(sf);
+        /*
+         * NO_OPTION corresponds to the Delete button
+         */
+        sequences.get(0).getDatasetSequence().deleteFeature(sf);
+        // update Feature Settings for removal of feature / group
+        featuresAdded();
       }
       else if (reply == JvOptionPane.YES_OPTION)
       {
-        sf.type = lastFeatureAdded;
-        sf.featureGroup = lastFeatureGroupAdded;
-        sf.description = lastDescriptionAdded;
+        /*
+         * YES_OPTION corresponds to the Amend button
+         * need to refresh Feature Settings if type, group or colour changed
+         */
+        sf.type = enteredType;
+        sf.featureGroup = group.getText().trim();
+        sf.description = description.getText().replaceAll("\n", " ");
+        boolean refreshSettings = (!featureType.equals(sf.type) || !featureGroup
+                .equals(sf.featureGroup));
+        refreshSettings |= (fcol != oldcol);
 
         setColour(sf.type, fcol);
-        getFeaturesDisplayed().setVisible(sf.type);
 
         try
         {
@@ -378,33 +441,33 @@ public class FeatureRenderer extends
         }
 
         ffile.parseDescriptionHTML(sf, false);
+        if (refreshSettings)
+        {
+          featuresAdded();
+        }
       }
     }
     else
     // NEW FEATURES ADDED
     {
-      if (reply == JvOptionPane.OK_OPTION && lastFeatureAdded.length() > 0)
+      if (reply == JvOptionPane.OK_OPTION && enteredType.length() > 0)
       {
-        for (int i = 0; i < sequences.length; i++)
+        for (int i = 0; i < sequences.size(); i++)
         {
-          features[i].type = lastFeatureAdded;
+          SequenceFeature sf = features.get(i);
+          sf.type = enteredType;
           // fix for JAL-1538 - always set feature group here
-          features[i].featureGroup = lastFeatureGroupAdded;
-          features[i].description = lastDescriptionAdded;
-          sequences[i].addSequenceFeature(features[i]);
-          ffile.parseDescriptionHTML(features[i], false);
+          sf.featureGroup = group.getText().trim();
+          sf.description = description.getText().replaceAll("\n", " ");
+          sequences.get(i).addSequenceFeature(sf);
+          ffile.parseDescriptionHTML(sf, false);
         }
 
-        if (lastFeatureGroupAdded != null)
-        {
-          setGroupVisibility(lastFeatureGroupAdded, true);
-        }
-        setColour(lastFeatureAdded, fcol);
-        setVisible(lastFeatureAdded);
+        setColour(enteredType, fcol);
 
-        findAllFeatures(false);
+        featuresAdded();
 
-        ap.paintAlignment(true);
+        alignPanel.paintAlignment(true);
 
         return true;
       }
@@ -414,12 +477,48 @@ public class FeatureRenderer extends
       }
     }
 
-    ap.paintAlignment(true);
+    alignPanel.paintAlignment(true);
 
     return true;
   }
 
   /**
+   * Show a warning message if the entered type is one that is currently hidden
+   * 
+   * @param panel
+   * @param type
+   */
+  protected void warnIfTypeHidden(JPanel panel, String type)
+  {
+    if (getRenderOrder().contains(type))
+    {
+      if (!showFeatureOfType(type))
+      {
+        String msg = MessageManager.formatMessage("label.warning_hidden",
+                MessageManager.getString("label.feature_type"), type);
+        JvOptionPane.showMessageDialog(panel, msg, "",
+                JvOptionPane.OK_OPTION);
+      }
+    }
+  }
+
+  /**
+   * Show a warning message if the entered group is one that is currently hidden
+   * 
+   * @param panel
+   * @param group
+   */
+  protected void warnIfGroupHidden(JPanel panel, String group)
+  {
+    if (featureGroups.containsKey(group) && !featureGroups.get(group))
+    {
+      String msg = MessageManager.formatMessage("label.warning_hidden",
+              MessageManager.getString("label.group"), group);
+      JvOptionPane.showMessageDialog(panel, msg, "", JvOptionPane.OK_OPTION);
+    }
+  }
+
+  /**
    * update the amend feature button dependent on the given style
    * 
    * @param bigPanel
index 26f9964..34f0b4a 100644 (file)
@@ -40,6 +40,7 @@ import jalview.ws.dbsources.das.api.jalviewSourceI;
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
+import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.GridLayout;
@@ -60,6 +61,7 @@ import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
@@ -126,6 +128,15 @@ public class FeatureSettings extends JPanel implements
 
   JPanel transPanel = new JPanel(new GridLayout(1, 2));
 
+  private static final int MIN_WIDTH = 400;
+
+  private static final int MIN_HEIGHT = 400;
+
+  /**
+   * Constructor
+   * 
+   * @param af
+   */
   public FeatureSettings(AlignFrame af)
   {
     this.af = af;
@@ -278,6 +289,7 @@ public class FeatureSettings extends JPanel implements
               MessageManager.getString("label.sequence_feature_settings"),
               400, 450);
     }
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
 
     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
     {
@@ -525,27 +537,15 @@ public class FeatureSettings extends JPanel implements
   {
     boolean visible = fr.checkGroupVisibility(group, true);
 
-    if (groupPanel == null)
-    {
-      groupPanel = new JPanel();
-    }
-
-    boolean alreadyAdded = false;
     for (int g = 0; g < groupPanel.getComponentCount(); g++)
     {
       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
       {
-        alreadyAdded = true;
         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
-        break;
+        return visible;
       }
     }
 
-    if (alreadyAdded)
-    {
-
-      return visible;
-    }
     final String grp = group;
     final JCheckBox check = new JCheckBox(group, visible);
     check.setFont(new Font("Serif", Font.BOLD, 12));
@@ -584,6 +584,7 @@ public class FeatureSettings extends JPanel implements
     SequenceFeature[] tmpfeatures;
     String group = null, type;
     Vector<String> visibleChecks = new Vector<String>();
+    Set<String> foundGroups = new HashSet<String>();
 
     // Find out which features should be visible depending on which groups
     // are selected / deselected
@@ -602,6 +603,7 @@ public class FeatureSettings extends JPanel implements
       while (index < tmpfeatures.length)
       {
         group = tmpfeatures[index].featureGroup;
+        foundGroups.add(group);
 
         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
         {
@@ -702,24 +704,105 @@ public class FeatureSettings extends JPanel implements
         System.arraycopy(data[i], 0, originalData[i], 0, 3);
       }
     }
+    else
+    {
+      updateOriginalData(data);
+    }
 
     table.setModel(new FeatureTableModel(data));
     table.getColumnModel().getColumn(0).setPreferredWidth(200);
 
-    if (groupPanel != null)
-    {
-      groupPanel.setLayout(new GridLayout(
-              fr.getFeatureGroupsSize() / 4 + 1, 4));
-
-      groupPanel.validate();
-      bigPanel.add(groupPanel, BorderLayout.NORTH);
-    }
+    groupPanel.setLayout(new GridLayout(fr.getFeatureGroupsSize() / 4 + 1,
+            4));
+    pruneGroups(foundGroups);
+    groupPanel.validate();
 
     updateFeatureRenderer(data, groupChanged != null);
     resettingTable = false;
   }
 
   /**
+   * Updates 'originalData' (used for restore on Cancel) if we detect that
+   * changes have been made outwith this dialog
+   * <ul>
+   * <li>a new feature type added (and made visible)</li>
+   * <li>a feature colour changed (in the Amend Features dialog)</li>
+   * </ul>
+   * 
+   * @param foundData
+   */
+  protected void updateOriginalData(Object[][] foundData)
+  {
+    // todo LinkedHashMap instead of Object[][] would be nice
+
+    Object[][] currentData = ((FeatureTableModel) table.getModel())
+            .getData();
+    for (Object[] row : foundData)
+    {
+      String type = (String) row[0];
+      boolean found = false;
+      for (Object[] current : currentData)
+      {
+        if (type.equals(current[0]))
+        {
+          found = true;
+          /*
+           * currently dependent on object equality here;
+           * really need an equals method on FeatureColour
+           */
+          if (!row[1].equals(current[1]))
+          {
+            /*
+             * feature colour has changed externally - update originalData
+             */
+            for (Object[] original : originalData)
+            {
+              if (type.equals(original[0]))
+              {
+                original[1] = row[1];
+                break;
+              }
+            }
+          }
+          break;
+        }
+      }
+      if (!found)
+      {
+        /*
+         * new feature detected - add to original data (on top)
+         */
+        Object[][] newData = new Object[originalData.length + 1][3];
+        for (int i = 0; i < originalData.length; i++)
+        {
+          System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
+        }
+        newData[0] = row;
+        originalData = newData;
+      }
+    }
+  }
+
+  /**
+   * Remove from the groups panel any checkboxes for groups that are not in the
+   * foundGroups set. This enables removing a group from the display when the
+   * last feature in that group is deleted.
+   * 
+   * @param foundGroups
+   */
+  protected void pruneGroups(Set<String> foundGroups)
+  {
+    for (int g = 0; g < groupPanel.getComponentCount(); g++)
+    {
+      JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
+      if (!foundGroups.contains(checkbox.getText()))
+      {
+        groupPanel.remove(checkbox);
+      }
+    }
+  }
+
+  /**
    * reorder data based on the featureRenderers global priority list.
    * 
    * @param data
@@ -1059,6 +1142,10 @@ public class FeatureSettings extends JPanel implements
     settingsPane.setLayout(borderLayout2);
     dasSettingsPane.setLayout(borderLayout3);
     bigPanel.setLayout(borderLayout4);
+
+    groupPanel = new JPanel();
+    bigPanel.add(groupPanel, BorderLayout.NORTH);
+
     invert.setFont(JvSwingUtils.getLabelFont());
     invert.setText(MessageManager.getString("label.invert_selection"));
     invert.addActionListener(new ActionListener()
index af23ceb..457d871 100755 (executable)
@@ -28,8 +28,11 @@ import jalview.jbgui.GFinder;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 
+import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Vector;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -38,8 +41,8 @@ import javax.swing.AbstractAction;
 import javax.swing.JComponent;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
-import javax.swing.JOptionPane;
 import javax.swing.KeyStroke;
+import javax.swing.event.InternalFrameEvent;
 
 /**
  * Performs the menu option for searching the alignment, for the next or all
@@ -54,14 +57,18 @@ import javax.swing.KeyStroke;
  */
 public class Finder extends GFinder
 {
-  private static final int HEIGHT = 110;
+  private static final int MY_HEIGHT = 120;
 
-  private static final int WIDTH = 340;
+  private static final int MY_WIDTH = 400;
 
   AlignmentViewport av;
 
   AlignmentPanel ap;
 
+  private static final int MIN_WIDTH = 350;
+
+  private static final int MIN_HEIGHT = 120;
+
   JInternalFrame frame;
 
   int seqIndex = 0;
@@ -94,11 +101,19 @@ public class Finder extends GFinder
     frame = new JInternalFrame();
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosing(InternalFrameEvent e)
+      {
+        closeAction();
+      }
+    });
     addEscapeHandler();
     Desktop.addInternalFrame(frame, MessageManager.getString("label.find"),
-            WIDTH, HEIGHT);
-
-    textfield.requestFocus();
+            MY_WIDTH, MY_HEIGHT);
+    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+    searchBox.requestFocus();
   }
 
   /**
@@ -113,19 +128,11 @@ public class Finder extends GFinder
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        escapeActionPerformed();
+        closeAction();
       }
     });
   }
 
-  /**
-   * Close the panel on Escape key press
-   */
-  protected void escapeActionPerformed()
-  {
-    setVisible(false);
-    frame.dispose();
-  }
 
   /**
    * Performs the 'Find Next' action.
@@ -185,11 +192,11 @@ public class Finder extends GFinder
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
     for (int f = 0; f < frames.length; f++)
     {
-      JInternalFrame frame = frames[f];
-      if (frame != null && frame instanceof AlignFrame)
+      JInternalFrame alignFrame = frames[f];
+      if (alignFrame != null && alignFrame instanceof AlignFrame)
       {
-        av = ((AlignFrame) frame).viewport;
-        ap = ((AlignFrame) frame).alignPanel;
+        av = ((AlignFrame) alignFrame).viewport;
+        ap = ((AlignFrame) alignFrame).alignPanel;
         return true;
       }
     }
@@ -197,32 +204,36 @@ public class Finder extends GFinder
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Opens a dialog that allows the user to create sequence features for the
+   * find match results.
    */
   @Override
-  public void createNewGroup_actionPerformed(ActionEvent e)
+  public void createFeatures_actionPerformed()
   {
-    SequenceI[] seqs = new SequenceI[searchResults.getSize()];
-    SequenceFeature[] features = new SequenceFeature[searchResults
-            .getSize()];
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+
+    String searchString = searchBox.getEditor().getItem().toString().trim();
+    String desc = "Search Results";
 
-    int i = 0;
+    /*
+     * assemble dataset sequences, and template new sequence features,
+     * for the amend features dialog
+     */
     for (SearchResultMatchI match : searchResults.getResults())
     {
-      seqs[i] = match.getSequence().getDatasetSequence();
-
-      features[i] = new SequenceFeature(textfield.getText().trim(),
-              "Search Results", null, match.getStart(), match.getEnd(),
-              "Search Results");
-      i++;
+      seqs.add(match.getSequence().getDatasetSequence());
+      features.add(new SequenceFeature(searchString, desc, null, match
+              .getStart(), match.getEnd(), desc));
     }
 
     if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
             features, true, ap))
     {
+      /*
+       * ensure feature display is turned on to show the new features,
+       * and remove them as highlighted regions
+       */
       ap.alignFrame.showSeqFeatures.setSelected(true);
       av.setShowSequenceFeatures(true);
       ap.highlightSearchResults(null);
@@ -233,13 +244,13 @@ public class Finder extends GFinder
    * Search the alignment for the next or all matches. If 'all matches', a
    * dialog is shown with the number of sequence ids and subsequences matched.
    * 
-   * @param findAll
+   * @param doFindAll
    */
-  void doSearch(boolean findAll)
+  void doSearch(boolean doFindAll)
   {
-    createNewGroup.setEnabled(false);
+    createFeatures.setEnabled(false);
 
-    String searchString = textfield.getText().trim();
+    String searchString = searchBox.getUserInput().trim();
 
     if (isInvalidSearchString(searchString))
     {
@@ -254,7 +265,7 @@ public class Finder extends GFinder
     finder.setCaseSensitive(caseSensitive.isSelected());
     finder.setIncludeDescription(searchDescription.isSelected());
 
-    finder.setFindAll(findAll);
+    finder.setFindAll(doFindAll);
 
     finder.find(searchString); // returns true if anything was actually found
 
@@ -279,7 +290,7 @@ public class Finder extends GFinder
     if (searchResults.getSize() > 0)
     {
       haveResults = true;
-      createNewGroup.setEnabled(true);
+      createFeatures.setEnabled(true);
     }
     else
     {
@@ -301,7 +312,7 @@ public class Finder extends GFinder
     }
     else
     {
-      if (findAll)
+      if (doFindAll)
       {
         // then we report the matches that were found
         String message = (idMatch.size() > 0) ? "" + idMatch.size()
@@ -321,7 +332,7 @@ public class Finder extends GFinder
         seqIndex = 0;
       }
     }
-
+    searchBox.updateCache();
   }
 
   /**
@@ -373,4 +384,15 @@ public class Finder extends GFinder
     }
     return error;
   }
+
+  protected void closeAction()
+  {
+    frame.setVisible(false);
+    frame.dispose();
+    searchBox.persistCache();
+    if (getFocusedViewport())
+    {
+      ap.alignFrame.requestFocus();
+    }
+  }
 }
index 8220aea..06f29e9 100755 (executable)
@@ -26,12 +26,10 @@ import jalview.util.MessageManager;
 
 import java.awt.Font;
 import java.awt.FontMetrics;
-import java.awt.event.ActionEvent;
 import java.awt.geom.Rectangle2D;
 
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
-import javax.swing.JOptionPane;
 
 /**
  * DOCUMENT ME!
@@ -50,8 +48,22 @@ public class FontChooser extends GFontChooser
    */
   Font oldFont;
 
+  /*
+   * The font on opening the dialog (to be restored on Cancel)
+   * on the other half of a split frame (if applicable)
+   */
+  Font oldComplementFont;
+
+  /*
+   * the state of 'scale protein as cDNA' on opening the dialog
+   */
   boolean oldProteinScale;
 
+  /*
+   * the state of 'same font for protein and cDNA' on opening the dialog
+   */
+  boolean oldMirrorFont;
+
   boolean init = true;
 
   JInternalFrame frame;
@@ -63,34 +75,37 @@ public class FontChooser extends GFontChooser
 
   private boolean lastSelMono = false;
 
+  private boolean oldSmoothFont;
+
+  private boolean oldComplementSmooth;
+
   /**
-   * Creates a new FontChooser object.
+   * Creates a new FontChooser for a tree panel
    * 
-   * @param ap
-   *          DOCUMENT ME!
+   * @param treePanel
    */
-  public FontChooser(TreePanel tp)
+  public FontChooser(TreePanel treePanel)
   {
-    this.tp = tp;
-    ap = tp.treeCanvas.ap;
-    oldFont = tp.getTreeFont();
+    this.tp = treePanel;
+    ap = treePanel.treeCanvas.ap;
+    oldFont = treePanel.getTreeFont();
     defaultButton.setVisible(false);
     smoothFont.setEnabled(false);
     init();
   }
 
   /**
-   * Creates a new FontChooser object.
+   * Creates a new FontChooser for an alignment panel
    * 
-   * @param ap
-   *          DOCUMENT ME!
+   * @param alignPanel
    */
-  public FontChooser(AlignmentPanel ap)
+  public FontChooser(AlignmentPanel alignPanel)
   {
-    oldFont = ap.av.getFont();
-    oldProteinScale = ap.av.isScaleProteinAsCdna();
-
-    this.ap = ap;
+    oldFont = alignPanel.av.getFont();
+    oldProteinScale = alignPanel.av.isScaleProteinAsCdna();
+    oldMirrorFont = alignPanel.av.isProteinFontAsCdna();
+    oldSmoothFont = alignPanel.av.antiAlias;
+    this.ap = alignPanel;
     init();
   }
 
@@ -103,14 +118,19 @@ public class FontChooser extends GFontChooser
 
     /*
      * Enable 'scale protein as cDNA' in a SplitFrame view. The selection is
-     * stored in the ViewStyle of both dna and protein Viewport
+     * stored in the ViewStyle of both dna and protein Viewport. Also enable
+     * checkbox for copy font changes to other half of split frame.
      */
-    scaleAsCdna.setEnabled(false);
-    if (ap.av.getCodingComplement() != null)
+    boolean inSplitFrame = ap.av.getCodingComplement() != null;
+    if (inSplitFrame)
     {
-      scaleAsCdna.setEnabled(true);
+      oldComplementFont = ((AlignViewport) ap.av.getCodingComplement())
+              .getFont();
+      oldComplementSmooth = ((AlignViewport) ap.av.getCodingComplement()).antiAlias;
       scaleAsCdna.setVisible(true);
       scaleAsCdna.setSelected(ap.av.isScaleProteinAsCdna());
+      fontAsCdna.setVisible(true);
+      fontAsCdna.setSelected(ap.av.isProteinFontAsCdna());
     }
 
     if (tp != null)
@@ -122,7 +142,7 @@ public class FontChooser extends GFontChooser
     else
     {
       Desktop.addInternalFrame(frame,
-              MessageManager.getString("action.change_font"), 380, 200,
+              MessageManager.getString("action.change_font"), 380, 220,
               false);
     }
 
@@ -158,11 +178,19 @@ public class FontChooser extends GFontChooser
   }
 
   @Override
-  public void smoothFont_actionPerformed(ActionEvent e)
+  protected void smoothFont_actionPerformed()
   {
     ap.av.antiAlias = smoothFont.isSelected();
     ap.getAnnotationPanel().image = null;
     ap.paintAlignment(true);
+    if (ap.av.getCodingComplement() != null && ap.av.isProteinFontAsCdna())
+    {
+      ((AlignViewport) ap.av.getCodingComplement()).antiAlias = ap.av.antiAlias;
+      SplitFrame sv = (SplitFrame) ap.alignFrame.getSplitViewContainer();
+      sv.adjustLayout();
+      sv.repaint();
+    }
+
   }
 
   /**
@@ -172,7 +200,7 @@ public class FontChooser extends GFontChooser
    *          DOCUMENT ME!
    */
   @Override
-  protected void ok_actionPerformed(ActionEvent e)
+  protected void ok_actionPerformed()
   {
     try
     {
@@ -197,26 +225,32 @@ public class FontChooser extends GFontChooser
    *          DOCUMENT ME!
    */
   @Override
-  protected void cancel_actionPerformed(ActionEvent e)
+  protected void cancel_actionPerformed()
   {
     if (ap != null)
     {
       ap.av.setFont(oldFont, true);
       ap.av.setScaleProteinAsCdna(oldProteinScale);
+      ap.av.setProteinFontAsCdna(oldMirrorFont);
+      ap.av.antiAlias = oldSmoothFont;
       ap.paintAlignment(true);
-      if (scaleAsCdna.isEnabled())
+
+      if (scaleAsCdna.isVisible() && scaleAsCdna.isEnabled())
       {
-        ap.av.setScaleProteinAsCdna(oldProteinScale);
         ap.av.getCodingComplement().setScaleProteinAsCdna(oldProteinScale);
+        ap.av.getCodingComplement().setProteinFontAsCdna(oldMirrorFont);
+        ((AlignViewport) ap.av.getCodingComplement()).antiAlias = oldComplementSmooth;
+        ap.av.getCodingComplement().setFont(oldComplementFont, true);
+        SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+                .getSplitViewContainer();
+        splitFrame.adjustLayout();
+        splitFrame.repaint();
       }
     }
     else if (tp != null)
     {
       tp.setTreeFont(oldFont);
     }
-    fontName.setSelectedItem(oldFont.getName());
-    fontSize.setSelectedItem(oldFont.getSize());
-    fontStyle.setSelectedIndex(oldFont.getStyle());
 
     try
     {
@@ -287,6 +321,29 @@ public class FontChooser extends GFontChooser
     {
       ap.av.setFont(newFont, true);
       ap.fontChanged();
+
+      /*
+       * adjust other half of split frame if any, if either same
+       * font, or proportionate scaling, is selected
+       */
+      if (fontAsCdna.isEnabled())
+      {
+        if (fontAsCdna.isSelected())
+        {
+          /*
+           * copy the font
+           */
+          ap.av.getCodingComplement().setFont(newFont, true);
+        }
+
+        /*
+         * adjust layout for font change / reset / sizing
+         */
+        SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+                .getSplitViewContainer();
+        splitFrame.adjustLayout();
+        splitFrame.repaint();
+      }
     }
 
     monospaced.setSelected(mw == iw);
@@ -299,13 +356,10 @@ public class FontChooser extends GFontChooser
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Updates on change of selected font name
    */
   @Override
-  protected void fontName_actionPerformed(ActionEvent e)
+  protected void fontName_actionPerformed()
   {
     if (init)
     {
@@ -316,13 +370,10 @@ public class FontChooser extends GFontChooser
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Updates on change of selected font size
    */
   @Override
-  protected void fontSize_actionPerformed(ActionEvent e)
+  protected void fontSize_actionPerformed()
   {
     if (init)
     {
@@ -333,13 +384,10 @@ public class FontChooser extends GFontChooser
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Updates on change of selected font style
    */
   @Override
-  protected void fontStyle_actionPerformed(ActionEvent e)
+  protected void fontStyle_actionPerformed()
   {
     if (init)
     {
@@ -352,11 +400,9 @@ public class FontChooser extends GFontChooser
   /**
    * Make selected settings the defaults by storing them (via Cache class) in
    * the .jalview_properties file (the file is only written when Jalview exits)
-   * 
-   * @param e
    */
   @Override
-  public void defaultButton_actionPerformed(ActionEvent e)
+  public void defaultButton_actionPerformed()
   {
     Cache.setProperty("FONT_NAME", fontName.getSelectedItem().toString());
     Cache.setProperty("FONT_STYLE", fontStyle.getSelectedIndex() + "");
@@ -372,7 +418,7 @@ public class FontChooser extends GFontChooser
    * characters
    */
   @Override
-  protected void scaleAsCdna_actionPerformed(ActionEvent e)
+  protected void scaleAsCdna_actionPerformed()
   {
     ap.av.setScaleProteinAsCdna(scaleAsCdna.isSelected());
     ap.av.getCodingComplement().setScaleProteinAsCdna(
@@ -381,7 +427,28 @@ public class FontChooser extends GFontChooser
             .getSplitViewContainer();
     splitFrame.adjustLayout();
     splitFrame.repaint();
-    // ap.paintAlignment(true);
-    // TODO would like to repaint
+  }
+
+  /**
+   * Turn on/off mirroring of font across split frame. If turning on, also
+   * copies the current font across the split frame. If turning off, restores
+   * the other half of the split frame to its initial font.
+   */
+  @Override
+  protected void mirrorFonts_actionPerformed()
+  {
+    boolean selected = fontAsCdna.isSelected();
+    ap.av.setProteinFontAsCdna(selected);
+    ap.av.getCodingComplement().setProteinFontAsCdna(selected);
+
+    /*
+     * reset other half of split frame if turning option off
+     */
+    if (!selected)
+    {
+      ap.av.getCodingComplement().setFont(oldComplementFont, true);
+    }
+
+    changeFont();
   }
 }
index 37be8bc..5ce36cb 100755 (executable)
@@ -21,6 +21,8 @@
 package jalview.gui;
 
 import jalview.datamodel.SequenceI;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -30,6 +32,7 @@ import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
 import javax.swing.JPanel;
@@ -40,7 +43,7 @@ import javax.swing.JPanel;
  * @author $author$
  * @version $Revision$
  */
-public class IdCanvas extends JPanel
+public class IdCanvas extends JPanel implements ViewportListenerI
 {
   protected AlignViewport av;
 
@@ -79,6 +82,7 @@ public class IdCanvas extends JPanel
     setLayout(new BorderLayout());
     this.av = av;
     PaintRefresher.Register(this, av.getSequenceSetId());
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   /**
@@ -158,33 +162,35 @@ public class IdCanvas extends JPanel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     gg.copyArea(0, 0, getWidth(), imgHeight, 0,
             -vertical * av.getCharHeight());
 
-    int ss = av.startSeq;
-    int es = av.endSeq;
+    int ss = ranges.getStartSeq();
+    int es = ranges.getEndSeq();
     int transY = 0;
 
     if (vertical > 0) // scroll down
     {
       ss = es - vertical;
 
-      if (ss < av.startSeq)
+      if (ss < ranges.getStartSeq())
       { // ie scrolling too fast, more than a page at a time
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - (vertical * av.getCharHeight());
+        transY = imgHeight - ((vertical + 1) * av.getCharHeight());
       }
     }
-    else if (vertical < 0)
+    else if (vertical < 0) // scroll up
     {
       es = ss - vertical;
 
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -240,7 +246,7 @@ public class IdCanvas extends JPanel
     gg.setColor(Color.white);
     gg.fillRect(0, 0, getWidth(), imgHeight);
 
-    drawIds(av.getStartSeq(), av.endSeq);
+    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
 
     g.drawImage(image, 0, 0, this);
   }
@@ -287,7 +293,8 @@ public class IdCanvas extends JPanel
 
       if (av.hasHiddenColumns())
       {
-        maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+        maxwidth = av.getAlignment().getHiddenColumns()
+                .findColumnPosition(maxwidth) - 1;
       }
 
       int annotationHeight = 0;
@@ -314,10 +321,11 @@ public class IdCanvas extends JPanel
 
       int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
 
-      int rowSize = av.getEndRes() - av.getStartRes();
+      int rowSize = av.getRanges().getEndRes()
+              - av.getRanges().getStartRes();
 
       // Draw the rest of the panels
-      for (int ypos = hgap, row = av.startRes; (ypos <= getHeight())
+      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getHeight())
               && (row < maxwidth); ypos += cHeight, row += rowSize)
       {
         for (int i = starty; i < alheight; i++)
@@ -354,7 +362,7 @@ public class IdCanvas extends JPanel
 
       SequenceI sequence;
       // Now draw the id strings
-      for (int i = starty; i < endy; i++)
+      for (int i = starty; i <= endy; i++)
       {
         sequence = av.getAlignment().getSequenceAt(i);
 
@@ -511,4 +519,15 @@ public class IdCanvas extends JPanel
   {
     this.idfont = idfont;
   }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport range changes (e.g. alignment panel was scrolled)
+    if (evt.getPropertyName().equals("startseq")
+            || evt.getPropertyName().equals("endseq"))
+    {
+      fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+  }
 }
index 6ae19f0..6097089 100755 (executable)
@@ -152,22 +152,22 @@ public class IdPanel extends JPanel implements MouseListener,
     {
       if (e.isShiftDown())
       {
-        alignPanel.scrollRight(true);
+        av.getRanges().scrollRight(true);
       }
       else
       {
-        alignPanel.scrollUp(false);
+        av.getRanges().scrollUp(false);
       }
     }
     else
     {
       if (e.isShiftDown())
       {
-        alignPanel.scrollRight(false);
+        av.getRanges().scrollRight(false);
       }
       else
       {
-        alignPanel.scrollUp(true);
+        av.getRanges().scrollUp(true);
       }
     }
   }
@@ -242,13 +242,14 @@ public class IdPanel extends JPanel implements MouseListener,
       return;
     }
 
-    if (mouseDragging && (e.getY() < 0) && (av.getStartSeq() > 0))
+    if (mouseDragging && (e.getY() < 0)
+            && (av.getRanges().getStartSeq() > 0))
     {
       scrollThread = new ScrollThread(true);
     }
 
     if (mouseDragging && (e.getY() >= getHeight())
-            && (av.getAlignment().getHeight() > av.getEndSeq()))
+            && (av.getAlignment().getHeight() > av.getRanges().getEndSeq()))
     {
       scrollThread = new ScrollThread(false);
     }
@@ -442,9 +443,10 @@ public class IdPanel extends JPanel implements MouseListener,
     int index = av.getAlignment().findIndex(list.get(0));
 
     // do we need to scroll the panel?
-    if ((av.getStartSeq() > index) || (av.getEndSeq() < index))
+    if ((av.getRanges().getStartSeq() > index)
+            || (av.getRanges().getEndSeq() < index))
     {
-      alignPanel.setScrollValues(av.getStartRes(), index);
+      av.getRanges().setStartSeq(index);
     }
   }
 
@@ -483,14 +485,14 @@ public class IdPanel extends JPanel implements MouseListener,
 
       while (running)
       {
-        if (alignPanel.scrollUp(up))
+        if (av.getRanges().scrollUp(up))
         {
           // scroll was ok, so add new sequence to selection
-          int seq = av.getStartSeq();
+          int seq = av.getRanges().getStartSeq();
 
           if (!up)
           {
-            seq = av.getEndSeq();
+            seq = av.getRanges().getEndSeq();
           }
 
           if (seq < lastid)
index 2a3d788..d92f6c0 100644 (file)
@@ -88,9 +88,12 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
 
   private JLabel dbstatus, dbstatex;
 
+  private JPanel mainPanel = new JPanel(new BorderLayout());
+
   public JDatabaseTree(jalview.ws.SequenceFetcher sfetch)
   {
-    initDialogFrame(this, true, false,
+    mainPanel.add(this);
+    initDialogFrame(mainPanel, true, false,
             MessageManager
                     .getString("label.select_database_retrieval_source"),
             650, 490);
@@ -148,19 +151,19 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
     }
     // and sort the tree
     sortTreeNodes(root);
-    svp = new JScrollPane();
-    // svp.setAutoscrolls(true);
     dbviews = new JTree(new DefaultTreeModel(root, false));
     dbviews.setCellRenderer(new DbTreeRenderer(this));
 
     dbviews.getSelectionModel().setSelectionMode(
             TreeSelectionModel.SINGLE_TREE_SELECTION);
-    svp.getViewport().setView(dbviews);
-    // svp.getViewport().setMinimumSize(new Dimension(300,200));
-    // svp.setSize(300,250);
-    // JPanel panel=new JPanel();
-    // panel.setSize(new Dimension(350,220));
-    // panel.add(svp);
+    svp = new JScrollPane(dbviews);
+    svp.setMinimumSize(new Dimension(100, 200));
+    svp.setPreferredSize(new Dimension(200, 400));
+    svp.setMaximumSize(new Dimension(300, 600));
+
+    JPanel panel = new JPanel(new BorderLayout());
+    panel.setSize(new Dimension(350, 220));
+    panel.add(svp);
     dbviews.addTreeSelectionListener(new TreeSelectionListener()
     {
 
@@ -200,7 +203,6 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
     dbstat.add(dbstatex);
     jc.add(dbstat, BorderLayout.SOUTH);
     jc.validate();
-    // j.setPreferredSize(new Dimension(300,50));
     add(jc, BorderLayout.CENTER);
     ok.setEnabled(false);
     j.add(ok);
index 3ac453f..dee921e 100644 (file)
@@ -29,6 +29,7 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.GraphLine;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceGroup;
@@ -77,7 +78,6 @@ import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.FeatureColour;
-import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureSelectionManager;
@@ -87,6 +87,7 @@ import jalview.util.Platform;
 import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 import jalview.ws.jws2.Jws2Discoverer;
@@ -755,6 +756,7 @@ public class Jalview2XML
     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
 
     AlignViewport av = ap.av;
+    ViewportRanges vpRanges = av.getRanges();
 
     JalviewModel object = new JalviewModel();
     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
@@ -1105,7 +1107,7 @@ public class Jalview2XML
               Tree tree = new Tree();
               tree.setTitle(tp.getTitle());
               tree.setCurrentTree((av.currentTree == tp.getTree()));
-              tree.setNewick(tp.getTree().toString());
+              tree.setNewick(tp.getTree().print());
               tree.setThreshold(tp.treeCanvas.threshold);
 
               tree.setFitToWindow(tp.fitToWindow.getState());
@@ -1270,8 +1272,8 @@ public class Jalview2XML
       view.setWidth(size.width);
       view.setHeight(size.height);
 
-      view.setStartRes(av.startRes);
-      view.setStartSeq(av.startSeq);
+      view.setStartRes(vpRanges.getStartRes());
+      view.setStartSeq(vpRanges.getStartSeq());
 
       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
       {
@@ -1411,17 +1413,18 @@ public class Jalview2XML
 
       if (av.hasHiddenColumns())
       {
-        if (av.getColumnSelection() == null
-                || av.getColumnSelection().getHiddenColumns() == null)
+        jalview.datamodel.HiddenColumns hidden = av.getAlignment()
+                .getHiddenColumns();
+        if (hidden == null || hidden.getHiddenRegions() == null)
         {
           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
         }
         else
         {
-          for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
+          for (int c = 0; c < hidden.getHiddenRegions()
                   .size(); c++)
           {
-            int[] region = av.getColumnSelection().getHiddenColumns()
+            int[] region = hidden.getHiddenRegions()
                     .get(c);
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
@@ -1711,6 +1714,15 @@ public class Jalview2XML
     return matchedFile;
   }
 
+  /**
+   * Populates the AnnotationColours xml for save. This captures the settings of
+   * the options in the 'Colour by Annotation' dialog.
+   * 
+   * @param acg
+   * @param userColours
+   * @param jms
+   * @return
+   */
   private AnnotationColours constructAnnotationColours(
           AnnotationColourGradient acg, List<UserColourScheme> userColours,
           JalviewModelSequence jms)
@@ -1718,8 +1730,9 @@ public class Jalview2XML
     AnnotationColours ac = new AnnotationColours();
     ac.setAboveThreshold(acg.getAboveThreshold());
     ac.setThreshold(acg.getAnnotationThreshold());
-    ac.setAnnotation(acg.getAnnotation());
-    if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
+    // 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, jms));
@@ -2639,10 +2652,12 @@ public class Jalview2XML
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                    finalErrorMessage, "Error "
-                            + (saving ? "saving" : "loading")
-                            + " Jalview file", JvOptionPane.WARNING_MESSAGE);
+            JvOptionPane
+                    .showInternalMessageDialog(Desktop.desktop,
+                            finalErrorMessage, "Error "
+                                    + (saving ? "saving" : "loading")
+                                    + " Jalview file",
+                            JvOptionPane.WARNING_MESSAGE);
           }
         });
       }
@@ -3666,7 +3681,7 @@ public class Jalview2XML
         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
         if (tp == null)
         {
-          tp = af.ShowNewickTree(
+          tp = af.showNewickTree(
                   new jalview.io.NewickFile(tree.getNewick()),
                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
                   tree.getXpos(), tree.getYpos());
@@ -4448,8 +4463,8 @@ public class Jalview2XML
     af.viewport.setThresholdTextColour(view.getTextColThreshold());
     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
             .isShowUnconserved() : false);
-    af.viewport.setStartRes(view.getStartRes());
-    af.viewport.setStartSeq(view.getStartSeq());
+    af.viewport.getRanges().setStartRes(view.getStartRes());
+    // startSeq set in af.alignPanel.updateLayout below
     af.alignPanel.updateLayout();
     ColourSchemeI cs = null;
     // apply colourschemes
@@ -4689,12 +4704,21 @@ public class Jalview2XML
     return af;
   }
 
+  /**
+   * Reads saved data to restore Colour by Annotation settings
+   * 
+   * @param viewAnnColour
+   * @param af
+   * @param al
+   * @param jms
+   * @param checkGroupAnnColour
+   * @return
+   */
   private ColourSchemeI constructAnnotationColour(
           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
           JalviewModelSequence jms, boolean checkGroupAnnColour)
   {
     boolean propagateAnnColour = false;
-    ColourSchemeI cs = null;
     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
     if (checkGroupAnnColour && al.getGroups() != null
             && al.getGroups().size() > 0)
@@ -4702,7 +4726,7 @@ public class Jalview2XML
       // pre 2.8.1 behaviour
       // check to see if we should transfer annotation colours
       propagateAnnColour = true;
-      for (jalview.datamodel.SequenceGroup sg : al.getGroups())
+      for (SequenceGroup sg : al.getGroups())
       {
         if (sg.getColourScheme() instanceof AnnotationColourGradient)
         {
@@ -4710,107 +4734,84 @@ public class Jalview2XML
         }
       }
     }
-    // int find annotation
-    if (annAlignment.getAlignmentAnnotation() != null)
+
+    /*
+     * 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 (annAlignment.getAlignmentAnnotation()[i].label
-                .equals(viewAnnColour.getAnnotation()))
+        if (annotationId
+                .equals(annAlignment.getAlignmentAnnotation()[i].label))
         {
-          if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
-          {
-            annAlignment.getAlignmentAnnotation()[i]
-                    .setThreshold(new jalview.datamodel.GraphLine(
-                            viewAnnColour.getThreshold(), "Threshold",
-                            java.awt.Color.black)
-
-                    );
-          }
-
-          if (viewAnnColour.getColourScheme().equals(
-                  ResidueColourScheme.NONE))
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    new java.awt.Color(viewAnnColour.getMinColour()),
-                    new java.awt.Color(viewAnnColour.getMaxColour()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          else if (viewAnnColour.getColourScheme().startsWith("ucs"))
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    getUserColourScheme(jms,
-                            viewAnnColour.getColourScheme()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          else
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    ColourSchemeProperty.getColourScheme(al,
-                            viewAnnColour.getColourScheme()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          if (viewAnnColour.hasPerSequence())
-          {
-            ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
-                    .isPerSequence());
-          }
-          if (viewAnnColour.hasPredefinedColours())
-          {
-            ((AnnotationColourGradient) cs)
-                    .setPredefinedColours(viewAnnColour
-                            .isPredefinedColours());
-          }
-          if (propagateAnnColour && al.getGroups() != null)
-          {
-            // Also use these settings for all the groups
-            for (int g = 0; g < al.getGroups().size(); g++)
-            {
-              jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
-
-              if (sg.cs == null)
-              {
-                continue;
-              }
+          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(viewAnnColour.getThreshold(),
+              "Threshold", Color.black));
+    }
 
-              /*
-               * if (viewAnnColour.getColourScheme().equals(ResidueColourScheme.NONE)) { sg.cs =
-               * new AnnotationColourGradient(
-               * annAlignment.getAlignmentAnnotation()[i], new
-               * java.awt.Color(viewAnnColour. getMinColour()), new
-               * java.awt.Color(viewAnnColour. getMaxColour()),
-               * viewAnnColour.getAboveThreshold()); } else
-               */
-              {
-                sg.setColourScheme(new AnnotationColourGradient(
-                        annAlignment.getAlignmentAnnotation()[i], sg
-                                .getColourScheme(), viewAnnColour
-                                .getAboveThreshold()));
-                if (cs instanceof AnnotationColourGradient)
-                {
-                  if (viewAnnColour.hasPerSequence())
-                  {
-                    ((AnnotationColourGradient) cs)
-                            .setSeqAssociated(viewAnnColour.isPerSequence());
-                  }
-                  if (viewAnnColour.hasPredefinedColours())
-                  {
-                    ((AnnotationColourGradient) cs)
-                            .setPredefinedColours(viewAnnColour
-                                    .isPredefinedColours());
-                  }
-                }
-              }
+    AnnotationColourGradient cs = null;
+    if (viewAnnColour.getColourScheme().equals("None"))
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation, new Color(
+              viewAnnColour.getMinColour()), new Color(
+              viewAnnColour.getMaxColour()),
+              viewAnnColour.getAboveThreshold());
+    }
+    else if (viewAnnColour.getColourScheme().startsWith("ucs"))
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation, getUserColourScheme(
+              jms, viewAnnColour.getColourScheme()),
+              viewAnnColour.getAboveThreshold());
+    }
+    else
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation,
+              ColourSchemeProperty.getColourScheme(al,
+                      viewAnnColour.getColourScheme()),
+              viewAnnColour.getAboveThreshold());
+    }
 
-            }
-          }
+    boolean perSequenceOnly = viewAnnColour.isPerSequence();
+    boolean useOriginalColours = viewAnnColour.isPredefinedColours();
+    cs.setSeqAssociated(perSequenceOnly);
+    cs.setPredefinedColours(useOriginalColours);
 
-          break;
+    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(),
+                viewAnnColour.getAboveThreshold());
+        sg.setColourScheme(groupScheme);
+        groupScheme.setSeqAssociated(perSequenceOnly);
+        groupScheme.setPredefinedColours(useOriginalColours);
       }
     }
     return cs;
index e751a2c..8d71ccf 100755 (executable)
@@ -367,8 +367,8 @@ public class Jalview2XML_V1
 
     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
             view.getHeight());
-    af.viewport.setStartRes(view.getStartRes());
-    af.viewport.setStartSeq(view.getStartSeq());
+    af.viewport.getRanges().setStartRes(view.getStartRes());
+    // startSeq set in af.alignPanel.updateLayout below
     af.viewport.setShowAnnotation(view.getShowAnnotation());
     af.viewport.setAbovePIDThreshold(view.getPidSelected());
     af.viewport.setColourText(view.getShowColourText());
@@ -464,7 +464,7 @@ public class Jalview2XML_V1
 
           Tree tree = jms.getTree(t);
 
-          TreePanel tp = af.ShowNewickTree(
+          TreePanel tp = af.showNewickTree(
                   new jalview.io.NewickFile(tree.getNewick()),
                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
                   tree.getXpos(), tree.getYpos());
index a797872..c9b35d8 100644 (file)
@@ -28,13 +28,12 @@ import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.io.DataSourceType;
 import jalview.structure.StructureSelectionManager;
 
+import javax.swing.SwingUtilities;
+
 public class JalviewChimeraBindingModel extends JalviewChimeraBinding
 {
   private ChimeraViewFrame cvf;
 
-  private FeatureRenderer fr = null;
-
-
   public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
           StructureSelectionManager ssm, PDBEntry[] pdbentry,
           SequenceI[][] sequenceIs, DataSourceType protocol)
@@ -50,17 +49,10 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
             : (AlignmentPanel) alignment;
     if (ap.av.isShowSequenceFeatures())
     {
-      if (fr == null)
-      {
-        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
-      }
-      else
-      {
-        ap.updateFeatureRenderer(fr);
-      }
+      return ap.getSeqPanel().seqCanvas.fr;
     }
 
-    return fr;
+    return null;
   }
 
   @Override
@@ -122,24 +114,25 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
   protected void sendAsynchronousCommand(final String command,
           final String progressMsg)
   {
-    Thread thread = new Thread(new Runnable()
+    final long handle = progressMsg == null ? 0 : cvf
+            .startProgressBar(progressMsg);
+    SwingUtilities.invokeLater(new Runnable()
     {
-
       @Override
       public void run()
       {
-        long stm = cvf.startProgressBar(progressMsg);
         try
         {
           sendChimeraCommand(command, false);
         } finally
         {
-          cvf.stopProgressBar(null, stm);
+          if (progressMsg != null)
+          {
+            cvf.stopProgressBar(null, handle);
+          }
         }
       }
     });
-    thread.start();
-
   }
 
   @Override
index 8742253..05f5ffc 100644 (file)
@@ -23,6 +23,7 @@ package jalview.gui;
 import jalview.util.MessageManager;
 
 import java.awt.Container;
+import java.awt.Dimension;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -89,6 +90,9 @@ public abstract class JalviewDialog extends JPanel
     {
       frame.setSize(width, height);
     }
+    int minWidth = width - 100;
+    int minHeight = height - 100;
+    frame.setMinimumSize(new Dimension(minWidth, minHeight));
     frame.setContentPane(content);
     this.block = block;
 
diff --git a/src/jalview/gui/OverviewCanvas.java b/src/jalview/gui/OverviewCanvas.java
new file mode 100644 (file)
index 0000000..6f9fbbf
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * 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.gui;
+
+import jalview.api.AlignViewportI;
+import jalview.renderer.OverviewRenderer;
+import jalview.viewmodel.OverviewDimensions;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JComponent;
+
+public class OverviewCanvas extends JComponent
+{
+  private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
+
+  // This is set true if the alignment view changes whilst
+  // the overview is being calculated
+  private volatile boolean restart = false;
+
+  private volatile boolean updaterunning = false;
+
+  private BufferedImage miniMe;
+
+  private BufferedImage lastMiniMe = null;
+
+  // Can set different properties in this seqCanvas than
+  // main visible SeqCanvas
+  private SequenceRenderer sr;
+
+  private jalview.renderer.seqfeatures.FeatureRenderer fr;
+
+  private OverviewDimensions od;
+
+  private AlignViewportI av;
+
+  public OverviewCanvas(OverviewDimensions overviewDims,
+          AlignViewportI alignvp)
+  {
+    od = overviewDims;
+    av = alignvp;
+
+    sr = new SequenceRenderer(av);
+    sr.renderGaps = false;
+    sr.forOverview = true;
+    fr = new jalview.renderer.seqfeatures.FeatureRenderer(av);
+  }
+
+  /**
+   * Update the overview dimensions object used by the canvas (e.g. if we change
+   * from showing hidden columns to hiding them or vice versa)
+   * 
+   * @param overviewDims
+   */
+  public void resetOviewDims(OverviewDimensions overviewDims)
+  {
+    od = overviewDims;
+  }
+
+  /**
+   * Signals to drawing code that the associated alignment viewport has changed
+   * and a redraw will be required
+   */
+  public boolean restartDraw()
+  {
+    synchronized (this)
+    {
+      if (updaterunning)
+      {
+        restart = true;
+      }
+      else
+      {
+        updaterunning = true;
+      }
+      return restart;
+    }
+  }
+
+  /**
+   * Draw the overview sequences
+   * 
+   * @param showSequenceFeatures
+   *          true if sequence features are to be shown
+   * @param showAnnotation
+   *          true if the annotation is to be shown
+   * @param transferRenderer
+   *          the renderer to transfer feature colouring from
+   */
+  public void draw(boolean showSequenceFeatures, boolean showAnnotation,
+          FeatureRenderer transferRenderer)
+  {
+    miniMe = null;
+
+    if (showSequenceFeatures)
+    {
+      fr.transferSettings(transferRenderer);
+    }
+
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
+
+    OverviewRenderer or = new OverviewRenderer(sr, fr, od);
+    miniMe = or.draw(od.getRows(av.getAlignment()),
+            od.getColumns(av.getAlignment()));
+
+    Graphics mg = miniMe.getGraphics();
+
+    if (showAnnotation)
+    {
+      mg.translate(0, od.getSequencesHeight());
+      or.drawGraph(mg, av.getAlignmentConservationAnnotation(),
+              av.getCharWidth(), od.getGraphHeight(),
+              od.getColumns(av.getAlignment()));
+      mg.translate(0, -od.getSequencesHeight());
+    }
+    System.gc();
+
+    if (restart)
+    {
+      restart = false;
+      draw(showSequenceFeatures, showAnnotation, transferRenderer);
+    }
+    else
+    {
+      updaterunning = false;
+      lastMiniMe = miniMe;
+    }
+  }
+
+  @Override
+  public void paintComponent(Graphics g)
+  {
+    if (restart)
+    {
+      if (lastMiniMe == null)
+      {
+        g.setColor(Color.white);
+        g.fillRect(0, 0, getWidth(), getHeight());
+      }
+      else
+      {
+        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
+      }
+      g.setColor(TRANS_GREY);
+      g.fillRect(0, 0, getWidth(), getHeight());
+    }
+    else if (lastMiniMe != null)
+    {
+      g.drawImage(lastMiniMe, 0, 0, this);
+      if (lastMiniMe != miniMe)
+      {
+        g.setColor(TRANS_GREY);
+        g.fillRect(0, 0, getWidth(), getHeight());
+      }
+    }
+
+    g.setColor(Color.red);
+    od.drawBox(g);
+  }
+
+}
index 1c48690..3fa674e 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.renderer.AnnotationRenderer;
-
-import java.awt.Color;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.viewmodel.OverviewDimensions;
+import jalview.viewmodel.OverviewDimensionsHideHidden;
+import jalview.viewmodel.OverviewDimensionsShowHidden;
+import jalview.viewmodel.ViewportListenerI;
+
+import java.awt.BorderLayout;
 import java.awt.Dimension;
-import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
-import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
 
+import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
 
 /**
- * DOCUMENT ME!
+ * Panel displaying an overview of the full alignment, with an interactive box
+ * representing the viewport onto the alignment.
  * 
  * @author $author$
  * @version $Revision$
  */
-public class OverviewPanel extends JPanel implements Runnable
+public class OverviewPanel extends JPanel implements Runnable,
+        ViewportListenerI
 {
-  BufferedImage miniMe;
-
-  AlignViewport av;
-
-  AlignmentPanel ap;
-
-  final AnnotationRenderer renderer = new AnnotationRenderer();
-
-  float scalew = 1f;
-
-  float scaleh = 1f;
+  private OverviewDimensions od;
 
-  int width;
+  private OverviewCanvas oviewCanvas;
 
-  int sequencesHeight;
+  private AlignViewport av;
 
-  int graphHeight = 20;
+  private AlignmentPanel ap;
 
-  int boxX = -1;
+  private JCheckBoxMenuItem displayToggle;
 
-  int boxY = -1;
-
-  int boxWidth = -1;
-
-  int boxHeight = -1;
-
-  boolean resizing = false;
-
-  // Can set different properties in this seqCanvas than
-  // main visible SeqCanvas
-  SequenceRenderer sr;
-
-  jalview.renderer.seqfeatures.FeatureRenderer fr;
+  private boolean showHidden = true;
 
   /**
    * Creates a new OverviewPanel object.
    * 
-   * @param ap
-   *          DOCUMENT ME!
+   * @param alPanel
+   *          The alignment panel which is shown in the overview panel
    */
-  public OverviewPanel(AlignmentPanel ap)
+  public OverviewPanel(AlignmentPanel alPanel)
   {
-    this.av = ap.av;
-    this.ap = ap;
-    setLayout(null);
+    this.av = alPanel.av;
+    this.ap = alPanel;
 
-    sr = new SequenceRenderer(av);
-    sr.renderGaps = false;
-    sr.forOverview = true;
-    fr = new FeatureRenderer(ap);
+    od = new OverviewDimensionsShowHidden(av.getRanges(),
+            (av.isShowAnnotation() && av
+                    .getAlignmentConservationAnnotation() != null));
 
-    // scale the initial size of overviewpanel to shape of alignment
-    float initialScale = (float) av.getAlignment().getWidth()
-            / (float) av.getAlignment().getHeight();
+    setSize(od.getWidth(), od.getHeight());
 
-    if (av.getAlignmentConservationAnnotation() == null)
-    {
-      graphHeight = 0;
-    }
+    oviewCanvas = new OverviewCanvas(od, av);
+    setLayout(new BorderLayout());
+    add(oviewCanvas, BorderLayout.CENTER);
 
-    if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-    {
-      // wider
-      width = 400;
-      sequencesHeight = (int) (400f / initialScale);
-      if (sequencesHeight < 40)
-      {
-        sequencesHeight = 40;
-      }
-    }
-    else
-    {
-      // taller
-      width = (int) (400f * initialScale);
-      sequencesHeight = 300;
-
-      if (width < 120)
-      {
-        width = 120;
-      }
-    }
+    av.getRanges().addPropertyChangeListener(this);
 
     addComponentListener(new ComponentAdapter()
     {
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        if ((getWidth() != width)
-                || (getHeight() != (sequencesHeight + graphHeight)))
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
         {
           updateOverviewImage();
+          setBoxPosition();
         }
       }
     });
@@ -142,13 +107,13 @@ public class OverviewPanel extends JPanel implements Runnable
       @Override
       public void mouseDragged(MouseEvent evt)
       {
-        if (!av.getWrapAlignment())
+        if (!SwingUtilities.isRightMouseButton(evt)
+                && !av.getWrapAlignment())
         {
-          // TODO: feature: jv2.5 detect shift drag and update selection from
-          // it.
-          boxX = evt.getX();
-          boxY = evt.getY();
-          checkValid();
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av.getAlignment()
+                  .getHiddenColumns());
+
         }
       }
     });
@@ -158,364 +123,133 @@ public class OverviewPanel extends JPanel implements Runnable
       @Override
       public void mousePressed(MouseEvent evt)
       {
-        if (!av.getWrapAlignment())
+        if (SwingUtilities.isRightMouseButton(evt))
+        {
+          if (!Platform.isAMac())
+          {
+            showPopupMenu(evt);
+          }
+        }
+        else if (!av.getWrapAlignment())
+        {
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av.getAlignment()
+                  .getHiddenColumns());
+        }
+      }
+
+      @Override
+      public void mouseClicked(MouseEvent evt)
+      {
+        if (SwingUtilities.isRightMouseButton(evt))
         {
-          boxX = evt.getX();
-          boxY = evt.getY();
-          checkValid();
+          showPopupMenu(evt);
         }
       }
     });
 
+
     updateOverviewImage();
   }
 
-  /**
-   * DOCUMENT ME!
+  /*
+   * Displays the popup menu and acts on user input
    */
-  void checkValid()
+  private void showPopupMenu(MouseEvent e)
   {
-    if (boxY < 0)
-    {
-      boxY = 0;
-    }
-
-    if (boxY > (sequencesHeight - boxHeight))
+    JPopupMenu popup = new JPopupMenu();
+    ActionListener menuListener = new ActionListener()
     {
-      boxY = sequencesHeight - boxHeight + 1;
-    }
-
-    if (boxX < 0)
-    {
-      boxX = 0;
-    }
-
-    if (boxX > (width - boxWidth))
-    {
-      if (av.hasHiddenColumns())
-      {
-        // Try smallest possible box
-        boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
-      }
-      boxX = width - boxWidth;
-    }
-
-    int col = (int) (boxX / scalew / av.getCharWidth());
-    int row = (int) (boxY / scaleh / av.getCharHeight());
+      @Override
+      public void actionPerformed(ActionEvent event)
+      {
+        // switch on/off the hidden columns view
+        toggleHiddenColumns();
+        displayToggle.setSelected(showHidden);
+      }
+    };
+    displayToggle = new JCheckBoxMenuItem(
+            MessageManager.getString("label.togglehidden"));
+    displayToggle.setEnabled(true);
+    displayToggle.setSelected(showHidden);
+    popup.add(displayToggle);
+    displayToggle.addActionListener(menuListener);
+    popup.show(this, e.getX(), e.getY());
+  }
 
-    if (av.hasHiddenColumns())
+  /*
+   * Toggle overview display between showing hidden columns and hiding hidden columns
+   */
+  private void toggleHiddenColumns()
+  {
+    if (showHidden)
     {
-      if (!av.getColumnSelection().isVisible(col))
-      {
-        return;
-      }
-
-      col = av.getColumnSelection().findColumnPosition(col);
+      showHidden = false;
+      od = new OverviewDimensionsHideHidden(av.getRanges(),
+              (av.isShowAnnotation() && av
+                      .getAlignmentConservationAnnotation() != null));
     }
-
-    if (av.hasHiddenRows())
+    else
     {
-      row = av.getAlignment().getHiddenSequences()
-              .findIndexWithoutHiddenSeqs(row);
+      showHidden = true;
+      od = new OverviewDimensionsShowHidden(av.getRanges(),
+              (av.isShowAnnotation() && av
+                      .getAlignmentConservationAnnotation() != null));
     }
-
-    ap.setScrollValues(col, row);
-
+    oviewCanvas.resetOviewDims(od);
+    updateOverviewImage();
+    setBoxPosition();
   }
 
   /**
-   * DOCUMENT ME!
+   * Updates the overview image when the related alignment panel is updated
    */
   public void updateOverviewImage()
   {
-    if (resizing)
+    if ((getWidth() > 0) && (getHeight() > 0))
     {
-      resizeAgain = true;
-      return;
+      od.setWidth(getWidth());
+      od.setHeight(getHeight());
     }
+    
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    resizing = true;
-
-    if ((getWidth() > 0) && (getHeight() > 0))
+    if (oviewCanvas.restartDraw())
     {
-      width = getWidth();
-      sequencesHeight = getHeight() - graphHeight;
+      return;
     }
 
-    setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
-
     Thread thread = new Thread(this);
     thread.start();
     repaint();
-  }
 
-  // This is set true if the user resizes whilst
-  // the overview is being calculated
-  boolean resizeAgain = false;
+  }
 
-  /**
-   * DOCUMENT ME!
-   */
   @Override
   public void run()
   {
-    miniMe = null;
-
-    if (av.isShowSequenceFeatures())
-    {
-      fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
-    }
-
-    int alwidth = av.getAlignment().getWidth();
-    int alheight = av.getAlignment().getHeight()
-            + av.getAlignment().getHiddenSequences().getSize();
-
-    setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
-
-    int fullsizeWidth = alwidth * av.getCharWidth();
-    int fullsizeHeight = alheight * av.getCharHeight();
-
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    miniMe = new BufferedImage(width, sequencesHeight + graphHeight,
-            BufferedImage.TYPE_INT_RGB);
-
-    Graphics mg = miniMe.getGraphics();
-    mg.setColor(Color.orange);
-    mg.fillRect(0, 0, width, miniMe.getHeight());
-
-    float sampleCol = (float) alwidth / (float) width;
-    float sampleRow = (float) alheight / (float) sequencesHeight;
-
-    int lastcol = -1, lastrow = -1;
-    int color = Color.white.getRGB();
-    int row, col;
-    jalview.datamodel.SequenceI seq;
-    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-            .hasHiddenColumns();
-    boolean hiddenRow = false;
-    // get hidden row and hidden column map once at beginning.
-    // clone featureRenderer settings to avoid race conditions... if state is
-    // updated just need to refresh again
-    for (row = 0; row < sequencesHeight; row++)
-    {
-      if (resizeAgain)
-      {
-        break;
-      }
-      if ((int) (row * sampleRow) == lastrow)
-      {
-        // No need to recalculate the colours,
-        // Just copy from the row above
-        for (col = 0; col < width; col++)
-        {
-          if (resizeAgain)
-          {
-            break;
-          }
-          miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
-        }
-        continue;
-      }
-
-      lastrow = (int) (row * sampleRow);
-
-      hiddenRow = false;
-      if (hasHiddenRows)
-      {
-        seq = av.getAlignment().getHiddenSequences()
-                .getHiddenSequence(lastrow);
-        if (seq == null)
-        {
-          int index = av.getAlignment().getHiddenSequences()
-                  .findIndexWithoutHiddenSeqs(lastrow);
-
-          seq = av.getAlignment().getSequenceAt(index);
-        }
-        else
-        {
-          hiddenRow = true;
-        }
-      }
-      else
-      {
-        seq = av.getAlignment().getSequenceAt(lastrow);
-      }
-
-      if (seq == null)
-      {
-        System.out.println(lastrow + " null");
-        continue;
-      }
-
-      for (col = 0; col < width; col++)
-      {
-        if (resizeAgain)
-        {
-          break;
-        }
-        if ((int) (col * sampleCol) == lastcol
-                && (int) (row * sampleRow) == lastrow)
-        {
-          miniMe.setRGB(col, row, color);
-          continue;
-        }
-
-        lastcol = (int) (col * sampleCol);
-
-        if (seq.getLength() > lastcol)
-        {
-          color = sr.getResidueBoxColour(seq, lastcol).getRGB();
-
-          if (av.isShowSequenceFeatures())
-          {
-            color = fr.findFeatureColour(color, seq, lastcol);
-          }
-        }
-        else
-        {
-          color = -1; // White
-        }
-
-        if (hiddenRow
-                || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                        lastcol)))
-        {
-          color = new Color(color).darker().darker().getRGB();
-        }
-
-        miniMe.setRGB(col, row, color);
-
-      }
-    }
-
-    if (av.getAlignmentConservationAnnotation() != null)
-    {
-      renderer.updateFromAlignViewport(av);
-      for (col = 0; col < width; col++)
-      {
-        if (resizeAgain)
-        {
-          break;
-        }
-        lastcol = (int) (col * sampleCol);
-        {
-          mg.translate(col, sequencesHeight);
-          renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
-                  av.getAlignmentConservationAnnotation().annotations,
-                  (int) (sampleCol) + 1, graphHeight,
-                  (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-          mg.translate(-col, -sequencesHeight);
-        }
-      }
-    }
-    System.gc();
-
-    resizing = false;
-
-    if (resizeAgain)
-    {
-      resizeAgain = false;
-      updateOverviewImage();
-    }
-    else
-    {
-      lastMiniMe = miniMe;
-    }
-
+    oviewCanvas.draw(av.isShowSequenceFeatures(),
+            (av.isShowAnnotation() && av
+                    .getAlignmentConservationAnnotation() != null), ap
+                    .getSeqPanel().seqCanvas.getFeatureRenderer());
     setBoxPosition();
   }
 
   /**
-   * DOCUMENT ME!
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
    */
-  public void setBoxPosition()
+  private void setBoxPosition()
   {
-    int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-    int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-            .getHiddenSequences().getSize())
-            * av.getCharHeight();
-
-    int startRes = av.getStartRes();
-    int endRes = av.getEndRes();
-
-    if (av.hasHiddenColumns())
-    {
-      startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-      endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-    }
-
-    int startSeq = av.startSeq;
-    int endSeq = av.endSeq;
-
-    if (av.hasHiddenRows())
-    {
-      startSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(startSeq);
-
-      endSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(endSeq);
-
-    }
-
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    boxX = (int) (startRes * av.getCharWidth() * scalew);
-    boxY = (int) (startSeq * av.getCharHeight() * scaleh);
-
-    if (av.hasHiddenColumns())
-    {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
-    }
-    else
-    {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
-    }
-
-    boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
-
+    od.setBoxPosition(av.getAlignment().getHiddenSequences(), av
+            .getAlignment().getHiddenColumns());
     repaint();
   }
 
-  private BufferedImage lastMiniMe = null;
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g
-   *          DOCUMENT ME!
-   */
   @Override
-  public void paintComponent(Graphics g)
+  public void propertyChange(PropertyChangeEvent evt)
   {
-    if (resizing || resizeAgain)
-    {
-      if (lastMiniMe == null)
-      {
-        g.setColor(Color.white);
-        g.fillRect(0, 0, getWidth(), getHeight());
-      }
-      else
-      {
-        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
-      }
-      g.setColor(new Color(100, 100, 100, 25));
-      g.fillRect(0, 0, getWidth(), getHeight());
-    }
-    else if (lastMiniMe != null)
-    {
-      g.drawImage(lastMiniMe, 0, 0, this);
-      if (lastMiniMe != miniMe)
-      {
-        g.setColor(new Color(100, 100, 100, 25));
-        g.fillRect(0, 0, getWidth(), getHeight());
-      }
-    }
-    // TODO: render selected regions
-    g.setColor(Color.red);
-    g.drawRect(boxX, boxY, boxWidth, boxHeight);
-    g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+    setBoxPosition();
   }
 }
index 58ed008..d8e6b06 100644 (file)
  */
 package jalview.gui;
 
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.SeqCigar;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.jbgui.GPCAPanel;
-import jalview.schemes.ResidueProperties;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.PCAModel;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -70,29 +73,54 @@ public class PCAPanel extends GPCAPanel implements Runnable,
 
   PCAModel pcaModel;
 
+  private static final int MIN_WIDTH = 470;
+
+  private static final int MIN_HEIGHT = 250;
+
   int top = 0;
 
   /**
-   * Creates a new PCAPanel object.
+   * Creates a new PCAPanel object using default score model and parameters
    * 
-   * @param av
-   *          DOCUMENT ME!
-   * @param s
-   *          DOCUMENT ME!
+   * @param alignPanel
    */
-  public PCAPanel(AlignmentPanel ap)
+  public PCAPanel(AlignmentPanel alignPanel)
+  {
+    this(alignPanel, ScoreModels.getInstance()
+            .getDefaultModel(!alignPanel.av.getAlignment().isNucleotide())
+            .getName(), SimilarityParams.SeqSpace);
+  }
+
+  /**
+   * Constructor given sequence data, a similarity (or distance) score model
+   * name, and score calculation parameters
+   * 
+   * @param alignPanel
+   * @param modelName
+   * @param params
+   */
+  public PCAPanel(AlignmentPanel alignPanel, String modelName,
+          SimilarityParamsI params)
   {
     super();
-    this.av = ap.av;
-    this.ap = ap;
+    this.av = alignPanel.av;
+    this.ap = alignPanel;
+    boolean nucleotide = av.getAlignment().isNucleotide();
 
     progressBar = new ProgressBar(statusPanel, statusBar);
 
-    boolean sameLength = true;
+    addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosed(InternalFrameEvent e)
+      {
+        close_actionPerformed();
+      }
+    });
+
     boolean selected = av.getSelectionGroup() != null
             && av.getSelectionGroup().getSize() > 0;
     AlignmentView seqstrings = av.getAlignmentView(selected);
-    boolean nucleotide = av.getAlignment().isNucleotide();
     SequenceI[] seqs;
     if (!selected)
     {
@@ -102,41 +130,14 @@ public class PCAPanel extends GPCAPanel implements Runnable,
     {
       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
     }
-    SeqCigar sq[] = seqstrings.getSequences();
-    int length = sq[0].getWidth();
-
-    for (int i = 0; i < seqs.length; i++)
-    {
-      if (sq[i].getWidth() != length)
-      {
-        sameLength = false;
-        break;
-      }
-    }
 
-    if (!sameLength)
-    {
-      JvOptionPane.showMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.pca_sequences_not_aligned"),
-              MessageManager.getString("label.sequences_not_aligned"),
-              JvOptionPane.WARNING_MESSAGE);
-
-      return;
-    }
-
-    addInternalFrameListener(new InternalFrameAdapter()
-    {
-      @Override
-      public void internalFrameClosed(InternalFrameEvent e)
-      {
-        close_actionPerformed();
-      }
-    });
-
-    pcaModel = new PCAModel(seqstrings, seqs, nucleotide);
+    ScoreModelI scoreModel = ScoreModels.getInstance().getScoreModel(
+            modelName, ap);
+    pcaModel = new PCAModel(seqstrings, seqs, nucleotide, scoreModel,
+            params);
     PaintRefresher.Register(this, av.getSequenceSetId());
 
-    rc = new RotatableCanvas(ap);
+    rc = new RotatableCanvas(alignPanel);
     this.getContentPane().add(rc, BorderLayout.CENTER);
     Thread worker = new Thread(this);
     worker.start();
@@ -151,40 +152,52 @@ public class PCAPanel extends GPCAPanel implements Runnable,
     pcaModel = null;
   }
 
+  /**
+   * Repopulate the options and actions under the score model menu when it is
+   * selected. Options will depend on whether 'nucleotide' or 'peptide'
+   * modelling is selected (and also possibly on whether any additional score
+   * models have been added).
+   */
   @Override
-  protected void scoreMatrix_menuSelected()
+  protected void scoreModel_menuSelected()
   {
-    scoreMatrixMenu.removeAll();
-    for (final String sm : ResidueProperties.scoreMatrices.keySet())
-    {
-      if (ResidueProperties.getScoreMatrix(sm) != null)
+    scoreModelMenu.removeAll();
+    for (final ScoreModelI sm : ScoreModels.getInstance().getModels())
+    {
+      final String name = sm.getName();
+      JCheckBoxMenuItem jm = new JCheckBoxMenuItem(name);
+
+      /*
+       * if the score model doesn't provide a description, try to look one
+       * up in the text bundle, falling back on its name
+       */
+      String tooltip = sm.getDescription();
+      if (tooltip == null)
       {
-        // create an entry for this score matrix for use in PCA
-        JCheckBoxMenuItem jm = new JCheckBoxMenuItem();
-        jm.setText(MessageManager.getStringOrReturn("label.score_model_",
-                sm));
-        jm.setSelected(pcaModel.getScore_matrix().equals(sm));
-        if ((ResidueProperties.scoreMatrices.get(sm).isDNA() && ResidueProperties.scoreMatrices
-                .get(sm).isProtein())
-                || pcaModel.isNucleotide() == ResidueProperties.scoreMatrices
-                        .get(sm).isDNA())
+        tooltip = MessageManager.getStringOrReturn("label.score_model_",
+                name);
+      }
+      jm.setToolTipText(tooltip);
+      jm.setSelected(pcaModel.getScoreModelName().equals(name));
+      if ((pcaModel.isNucleotide() && sm.isDNA())
+              || (!pcaModel.isNucleotide() && sm.isProtein()))
+      {
+        jm.addActionListener(new ActionListener()
         {
-          final PCAPanel us = this;
-          jm.addActionListener(new ActionListener()
+          @Override
+          public void actionPerformed(ActionEvent e)
           {
-            @Override
-            public void actionPerformed(ActionEvent e)
+            if (!pcaModel.getScoreModelName().equals(name))
             {
-              if (!pcaModel.getScore_matrix().equals(sm))
-              {
-                pcaModel.setScore_matrix(sm);
-                Thread worker = new Thread(us);
-                worker.start();
-              }
+              ScoreModelI sm2 = ScoreModels.getInstance().getScoreModel(
+                      name, ap);
+              pcaModel.setScoreModel(sm2);
+              Thread worker = new Thread(PCAPanel.this);
+              worker.start();
             }
-          });
-          scoreMatrixMenu.add(jm);
-        }
+          }
+        });
+        scoreModelMenu.add(jm);
       }
     }
   }
@@ -231,7 +244,6 @@ public class PCAPanel extends GPCAPanel implements Runnable,
       // rc.invalidate();
       nuclSetting.setSelected(pcaModel.isNucleotide());
       protSetting.setSelected(!pcaModel.isNucleotide());
-      jvVersionSetting.setSelected(pcaModel.isJvCalcMode());
       top = pcaModel.getTop();
 
     } catch (OutOfMemoryError er)
@@ -249,6 +261,7 @@ public class PCAPanel extends GPCAPanel implements Runnable,
       addKeyListener(rc);
       Desktop.addInternalFrame(this, MessageManager
               .getString("label.principal_component_analysis"), 475, 450);
+      this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
     }
   }
 
@@ -258,7 +271,8 @@ public class PCAPanel extends GPCAPanel implements Runnable,
     if (!pcaModel.isNucleotide())
     {
       pcaModel.setNucleotide(true);
-      pcaModel.setScore_matrix("DNA");
+      pcaModel.setScoreModel(ScoreModels.getInstance().getDefaultModel(
+              false));
       Thread worker = new Thread(this);
       worker.start();
     }
@@ -272,20 +286,13 @@ public class PCAPanel extends GPCAPanel implements Runnable,
     if (pcaModel.isNucleotide())
     {
       pcaModel.setNucleotide(false);
-      pcaModel.setScore_matrix("BLOSUM62");
+      pcaModel.setScoreModel(ScoreModels.getInstance()
+              .getDefaultModel(true));
       Thread worker = new Thread(this);
       worker.start();
     }
   }
 
-  @Override
-  protected void jvVersionSetting_actionPerfomed(ActionEvent arg0)
-  {
-    pcaModel.setJvCalcMode(jvVersionSetting.isSelected());
-    Thread worker = new Thread(this);
-    worker.start();
-  }
-
   /**
    * DOCUMENT ME!
    */
@@ -399,7 +406,7 @@ public class PCAPanel extends GPCAPanel implements Runnable,
     }
     ;
     Object[] alAndColsel = pcaModel.getSeqtrings()
-            .getAlignmentAndColumnSelection(gc);
+            .getAlignmentAndHiddenColumns(gc);
 
     if (alAndColsel != null && alAndColsel[0] != null)
     {
@@ -416,8 +423,8 @@ public class PCAPanel extends GPCAPanel implements Runnable,
       if (true)
       {
         // make a new frame!
-        AlignFrame af = new AlignFrame(al,
-                (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
+        AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
+                AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
 
         // >>>This is a fix for the moment, until a better solution is
index 81c3d4f..9a6c760 100644 (file)
@@ -31,8 +31,8 @@ import jalview.commands.EditCommand.Action;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
@@ -43,12 +43,10 @@ import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FormatAdapter;
 import jalview.io.SequenceAnnotationReport;
-import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.PIDColourScheme;
-import jalview.schemes.ResidueColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
@@ -57,7 +55,9 @@ import jalview.util.UrlLink;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Hashtable;
@@ -1179,12 +1179,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         hideInsertions_actionPerformed(e);
       }
     });
-    /*
-     * annotationMenuItem.setText("By Annotation");
-     * annotationMenuItem.addActionListener(new ActionListener() { public void
-     * actionPerformed(ActionEvent actionEvent) {
-     * annotationMenuItem_actionPerformed(actionEvent); } });
-     */
+
     groupMenu.add(sequenceSelDetails);
     add(groupMenu);
     add(sequenceMenu);
@@ -1450,16 +1445,59 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-    if (sequence != null)
+
+    HiddenColumns hidden = new HiddenColumns();
+    BitSet inserts = new BitSet(), mask = new BitSet();
+
+    // set mask to preserve existing hidden columns outside selected group
+    if (ap.av.hasHiddenColumns())
     {
-      ColumnSelection cs = ap.av.getColumnSelection();
-      if (cs == null)
+      ap.av.getAlignment().getHiddenColumns().markHiddenRegions(mask);
+    }
+
+    boolean markedPopup = false;
+    // mark inserts in current selection
+    if (ap.av.getSelectionGroup() != null)
+    {
+      // mark just the columns in the selection group to be hidden
+      inserts.set(ap.av.getSelectionGroup().getStartRes(), ap.av
+              .getSelectionGroup().getEndRes() + 1);
+
+      // and clear that part of the mask
+      mask.andNot(inserts);
+
+      // now clear columns without gaps
+      for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
       {
-        cs = new ColumnSelection();
+        if (sq == sequence)
+        {
+          markedPopup = true;
+        }
+        inserts.and(sq.getInsertionsAsBits());
       }
-      cs.hideInsertionsFor(sequence);
-      ap.av.setColumnSelection(cs);
     }
+    else
+    {
+      // initially, mark all columns to be hidden
+      inserts.set(0, ap.av.getAlignment().getWidth());
+
+      // and clear out old hidden regions completely
+      mask.clear();
+    }
+
+    // now mark for sequence under popup if we haven't already done it
+    if (!markedPopup && sequence != null)
+    {
+      inserts.and(sequence.getInsertionsAsBits());
+    }
+
+    // finally, preserve hidden regions outside selection
+    inserts.or(mask);
+
+    // and set hidden columns accordingly
+    hidden.hideMarkedBits(inserts);
+
+    ap.av.getAlignment().setHiddenColumns(hidden);
     refresh();
   }
 
@@ -1620,24 +1658,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     refresh();
   }
 
-  public void annotationMenuItem_actionPerformed(ActionEvent actionEvent)
-  {
-    SequenceGroup sg = getGroup();
-    if (sg == null)
-    {
-      return;
-    }
-
-    AnnotationColourGradient acg = new AnnotationColourGradient(
-            sequence.getAnnotation()[0], null,
-            AnnotationColourGradient.NO_THRESHOLD);
-
-    acg.setPredefinedColours(true);
-    sg.setColourScheme(acg);
-
-    refresh();
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -1905,28 +1925,25 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       return;
     }
 
-    int rsize = 0, gSize = sg.getSize();
-    SequenceI[] rseqs, seqs = new SequenceI[gSize];
-    SequenceFeature[] tfeatures, features = new SequenceFeature[gSize];
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
 
+    /*
+     * assemble dataset sequences, and template new sequence features,
+     * for the amend features dialog
+     */
+    int gSize = sg.getSize();
     for (int i = 0; i < gSize; i++)
     {
       int start = sg.getSequenceAt(i).findPosition(sg.getStartRes());
       int end = sg.findEndRes(sg.getSequenceAt(i));
       if (start <= end)
       {
-        seqs[rsize] = sg.getSequenceAt(i).getDatasetSequence();
-        features[rsize] = new SequenceFeature(null, null, null, start, end,
-                "Jalview");
-        rsize++;
+        seqs.add(sg.getSequenceAt(i).getDatasetSequence());
+        features.add(new SequenceFeature(null, null, null, start, end, null));
       }
     }
-    rseqs = new SequenceI[rsize];
-    tfeatures = new SequenceFeature[rsize];
-    System.arraycopy(seqs, 0, rseqs, 0, rsize);
-    System.arraycopy(features, 0, tfeatures, 0, rsize);
-    features = tfeatures;
-    seqs = rseqs;
+
     if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
             features, true, ap))
     {
@@ -2008,28 +2025,19 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   public void changeColour_actionPerformed(String colourSchemeName)
   {
     SequenceGroup sg = getGroup();
-    if (ResidueColourScheme.USER_DEFINED.equals(colourSchemeName))
-    {
-      /*
-       * open a panel to load or configure a user-defined colour scheme
-       */
-      new UserDefinedColours(ap, sg);
-    }
-    else
+    /*
+     * switch to the chosen colour scheme (or null for None)
+     */
+    ColourSchemeI colourScheme = ColourSchemes.getInstance()
+            .getColourScheme(colourSchemeName, sg,
+                    ap.av.getHiddenRepSequences());
+    sg.setColourScheme(colourScheme);
+    if (colourScheme instanceof Blosum62ColourScheme
+            || colourScheme instanceof PIDColourScheme)
     {
-      /*
-       * switch to the chosen colour scheme (or null for None)
-       */
-      ColourSchemeI colourScheme = ColourSchemes.getInstance().getColourScheme(
-              colourSchemeName, sg, ap.av.getHiddenRepSequences());
-      sg.setColourScheme(colourScheme);
-      if (colourScheme instanceof Blosum62ColourScheme
-              || colourScheme instanceof PIDColourScheme)
-      {
-        sg.cs.setConsensus(AAFrequency.calculate(
-                sg.getSequences(ap.av.getHiddenRepSequences()),
-                sg.getStartRes(), sg.getEndRes() + 1));
-      }
+      sg.cs.setConsensus(AAFrequency.calculate(
+              sg.getSequences(ap.av.getHiddenRepSequences()),
+              sg.getStartRes(), sg.getEndRes() + 1));
     }
 
     refresh();
index cf80a6d..cccdd2e 100755 (executable)
@@ -107,6 +107,8 @@ public class Preferences extends GPreferences
 
   public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE";
 
+  public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY";
+
   private static final int MIN_FONT_SIZE = 1;
 
   private static final int MAX_FONT_SIZE = 30;
@@ -210,6 +212,7 @@ public class Preferences extends GPreferences
     openoverv.setSelected(Cache.getDefault("SHOW_OVERVIEW", false));
     showUnconserved
             .setSelected(Cache.getDefault("SHOW_UNCONSERVED", false));
+    showOccupancy.setSelected(Cache.getDefault(SHOW_OCCUPANCY, false));
     showGroupConsensus.setSelected(Cache.getDefault("SHOW_GROUP_CONSENSUS",
             false));
     showGroupConservation.setSelected(Cache.getDefault(
@@ -572,6 +575,8 @@ public class Preferences extends GPreferences
             Boolean.toString(idItalics.isSelected()));
     Cache.applicationProperties.setProperty("SHOW_UNCONSERVED",
             Boolean.toString(showUnconserved.isSelected()));
+    Cache.applicationProperties.setProperty(SHOW_OCCUPANCY,
+            Boolean.toString(showOccupancy.isSelected()));
     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSENSUS",
             Boolean.toString(showGroupConsensus.isSelected()));
     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSERVATION",
@@ -851,6 +856,7 @@ public class Preferences extends GPreferences
     conservation.setEnabled(annotations.isSelected());
     quality.setEnabled(annotations.isSelected());
     identity.setEnabled(annotations.isSelected());
+    showOccupancy.setEnabled(annotations.isSelected());
     showGroupConsensus.setEnabled(annotations.isSelected());
     showGroupConservation.setEnabled(annotations.isSelected());
     showConsensHistogram.setEnabled(annotations.isSelected()
index 8961f21..cb19539 100755 (executable)
 package jalview.gui;
 
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.viewmodel.ViewportListenerI;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -38,6 +40,7 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
 import javax.swing.JMenuItem;
@@ -51,7 +54,7 @@ import javax.swing.ToolTipManager;
  * supports a range of mouse operations to select, hide or reveal columns.
  */
 public class ScalePanel extends JPanel implements MouseMotionListener,
-        MouseListener
+        MouseListener, ViewportListenerI
 {
   protected int offy = 4;
 
@@ -90,6 +93,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
 
     addMouseListener(this);
     addMouseMotionListener(this);
+
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   /**
@@ -101,12 +106,12 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    int x = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
     final int res;
 
     if (av.hasHiddenColumns())
     {
-      x = av.getColumnSelection().adjustForHiddenColumns(x);
+      x = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
     }
 
     if (x >= av.getAlignment().getWidth())
@@ -172,7 +177,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
       });
       pop.add(item);
 
-      if (av.getColumnSelection().hasHiddenColumns())
+      if (av.getAlignment().getHiddenColumns().hasHiddenColumns())
       {
         item = new JMenuItem(MessageManager.getString("action.reveal_all"));
         item.addActionListener(new ActionListener()
@@ -282,11 +287,13 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   {
     mouseDragging = false;
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
-      res = av.getColumnSelection().adjustForHiddenColumns(res);
+      res = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(res);
     }
 
     if (res >= av.getAlignment().getWidth())
@@ -336,10 +343,12 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   {
     mouseDragging = true;
     ColumnSelection cs = av.getColumnSelection();
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
     res = Math.max(0, res);
-    res = cs.adjustForHiddenColumns(res);
+    res = hidden.adjustForHiddenColumns(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
     min = Math.min(res, min);
     max = Math.max(res, max);
@@ -389,13 +398,15 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
       return;
     }
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
-    res = av.getColumnSelection().adjustForHiddenColumns(res);
+    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
 
-    if (av.getColumnSelection().getHiddenColumns() != null)
+    if (av.getAlignment().getHiddenColumns().getHiddenRegions() != null)
     {
-      for (int[] region : av.getColumnSelection().getHiddenColumns())
+      for (int[] region : av.getAlignment().getHiddenColumns()
+              .getHiddenRegions())
       {
         if (res + 1 == region[0] || res - 1 == region[1])
         {
@@ -419,7 +430,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   @Override
   public void paintComponent(Graphics g)
   {
-    drawScale(g, av.getStartRes(), av.getEndRes(), getWidth(), getHeight());
+    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
+            getWidth(), getHeight());
   }
 
   // scalewidth will normally be screenwidth,
@@ -442,7 +454,9 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
 
     // Fill the selected columns
     ColumnSelection cs = av.getColumnSelection();
-    int avCharWidth = av.getCharWidth(), avCharHeight = av.getCharHeight();
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+    int avCharWidth = av.getCharWidth();
+    int avCharHeight = av.getCharHeight();
 
     if (cs != null)
     {
@@ -455,9 +469,9 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
 
         if (av.hasHiddenColumns())
         {
-          if (cs.isVisible(sel))
+          if (hidden.isVisible(sel))
           {
-            sel = cs.findColumnPosition(sel);
+            sel = hidden.findColumnPosition(sel);
           }
           else
           {
@@ -484,13 +498,13 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
       // draw any hidden column markers
       gg.setColor(Color.blue);
       int res;
-      if (av.getShowHiddenMarkers()
-              && av.getColumnSelection().getHiddenColumns() != null)
+
+      if (av.getShowHiddenMarkers() && hidden.getHiddenRegions() != null)
       {
-        for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
+        for (int i = 0; i < hidden.getHiddenRegions()
                 .size(); i++)
         {
-          res = av.getColumnSelection().findHiddenRegionPosition(i)
+          res = hidden.findHiddenRegionPosition(i)
                   - startx;
 
           if (res < 0 || res > widthx)
@@ -538,4 +552,11 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     }
   }
 
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    // Respond to viewport change events (e.g. alignment panel was scrolled)
+    repaint();
+  }
+
 }
index d015292..0e5e1b8 100755 (executable)
 package jalview.gui;
 
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BasicStroke;
 import java.awt.BorderLayout;
@@ -36,6 +39,7 @@ import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.Shape;
 import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
 import java.util.List;
 
 import javax.swing.JComponent;
@@ -46,7 +50,7 @@ import javax.swing.JComponent;
  * @author $author$
  * @version $Revision$
  */
-public class SeqCanvas extends JComponent
+public class SeqCanvas extends JComponent implements ViewportListenerI
 {
   final FeatureRenderer fr;
 
@@ -87,6 +91,8 @@ public class SeqCanvas extends JComponent
     setLayout(new BorderLayout());
     PaintRefresher.Register(this, av.getSequenceSetId());
     setBackground(Color.white);
+
+    av.getRanges().addPropertyChangeListener(this);
   }
 
   public SequenceRenderer getSequenceRenderer()
@@ -164,14 +170,17 @@ public class SeqCanvas extends JComponent
 
     if (av.hasHiddenColumns())
     {
-      startx = av.getColumnSelection().adjustForHiddenColumns(startx);
-      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+      startx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(startx);
+      endx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(endx);
     }
 
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     // WEST SCALE
@@ -223,7 +232,8 @@ public class SeqCanvas extends JComponent
 
     if (av.hasHiddenColumns())
     {
-      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+      endx = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(endx);
     }
 
     SequenceI seq;
@@ -279,43 +289,43 @@ public class SeqCanvas extends JComponent
     gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
             imgHeight, -horizontal * charWidth, -vertical * charHeight);
 
-    int sr = av.startRes;
-    int er = av.endRes;
-    int ss = av.startSeq;
-    int es = av.endSeq;
+    ViewportRanges ranges = av.getRanges();
+    int sr = ranges.getStartRes();
+    int er = ranges.getEndRes();
+    int ss = ranges.getStartSeq();
+    int es = ranges.getEndSeq();
     int transX = 0;
     int transY = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
-      er++;
       transX = (er - sr - horizontal) * charWidth;
       sr = er - horizontal;
     }
     else if (horizontal < 0)
     {
-      er = sr - horizontal - 1;
+      er = sr - horizontal;
     }
     else if (vertical > 0) // scroll down
     {
       ss = es - vertical;
 
-      if (ss < av.startSeq)
+      if (ss < ranges.getStartSeq())
       { // ie scrolling too fast, more than a page at a time
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - (vertical * charHeight);
+        transY = imgHeight - ((vertical + 1) * charHeight);
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
 
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -395,13 +405,15 @@ public class SeqCanvas extends JComponent
     gg.setColor(Color.white);
     gg.fillRect(0, 0, imgWidth, imgHeight);
 
+    ViewportRanges ranges = av.getRanges();
     if (av.getWrapAlignment())
     {
-      drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);
+      drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
     }
     else
     {
-      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+              ranges.getStartSeq(), ranges.getEndSeq(), 0);
     }
 
     g.drawImage(lcimg, 0, 0, this);
@@ -503,7 +515,7 @@ public class SeqCanvas extends JComponent
 
     av.setWrappedWidth(cWidth);
 
-    av.endRes = av.startRes + cWidth;
+    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth - 1);
 
     int endx;
     int ypos = hgap;
@@ -511,7 +523,8 @@ public class SeqCanvas extends JComponent
 
     if (av.hasHiddenColumns())
     {
-      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+      maxwidth = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(maxwidth) - 1;
     }
 
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
@@ -549,11 +562,10 @@ public class SeqCanvas extends JComponent
       {
         g.setColor(Color.blue);
         int res;
-        for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
-                .size(); i++)
+        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+        for (int i = 0; i < hidden.getHiddenRegions().size(); i++)
         {
-          res = av.getColumnSelection().findHiddenRegionPosition(i)
-                  - startRes;
+          res = hidden.findHiddenRegionPosition(i) - startRes;
 
           if (res < 0 || res > endx - startRes)
           {
@@ -584,7 +596,7 @@ public class SeqCanvas extends JComponent
                 (int) clip.getBounds().getHeight());
       }
 
-      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
+      drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
 
       if (av.isShowAnnotation())
       {
@@ -650,7 +662,8 @@ public class SeqCanvas extends JComponent
     }
     else
     {
-      List<int[]> regions = av.getColumnSelection().getHiddenColumns();
+      List<int[]> regions = av.getAlignment().getHiddenColumns()
+              .getHiddenRegions();
 
       int screenY = 0;
       int blockStart = startRes;
@@ -679,7 +692,7 @@ public class SeqCanvas extends JComponent
 
           g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
                   0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
-                  (endSeq - startSeq) * charHeight + offset);
+                  (endSeq - startSeq + 1) * charHeight + offset);
         }
 
         g1.translate(-screenY * charWidth, 0);
@@ -718,7 +731,7 @@ public class SeqCanvas extends JComponent
 
     // / First draw the sequences
     // ///////////////////////////
-    for (int i = startSeq; i < endSeq; i++)
+    for (int i = startSeq; i <= endSeq; i++)
     {
       nextSeq = av.getAlignment().getSequenceAt(i);
       if (nextSeq == null)
@@ -733,7 +746,7 @@ public class SeqCanvas extends JComponent
       if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                + ((i - startSeq) * charHeight));
+                + ((i - startSeq) * charHeight), false);
       }
 
       // / Highlight search Results once all sequences have been drawn
@@ -802,7 +815,7 @@ public class SeqCanvas extends JComponent
         int top = -1;
         int bottom = -1;
 
-        for (i = startSeq; i < endSeq; i++)
+        for (i = startSeq; i <= endSeq; i++)
         {
           sx = (group.getStartRes() - startRes) * charWidth;
           sy = offset + ((i - startSeq) * charHeight);
@@ -971,4 +984,34 @@ public class SeqCanvas extends JComponent
 
     repaint();
   }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    if (!av.getWrapAlignment())
+    {
+      if (evt.getPropertyName().equals("startres")
+              || evt.getPropertyName().equals("endres"))
+      {
+        // Make sure we're not trying to draw a panel
+        // larger than the visible window
+        ViewportRanges vpRanges = av.getRanges();
+        int scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+        if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
+        {
+          scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
+        }
+        else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
+        {
+          scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
+        }
+        fastPaint(scrollX, 0);
+      }
+      else if (evt.getPropertyName().equals("startseq")
+              || evt.getPropertyName().equals("endseq"))
+      {
+        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+      }
+    }
+  }
 }
diff --git a/src/jalview/gui/SeqCanvas.java.broken b/src/jalview/gui/SeqCanvas.java.broken
new file mode 100755 (executable)
index 0000000..c0a86df
--- /dev/null
@@ -0,0 +1,980 @@
+/*
+ * 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.gui;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.image.BufferedImage;
+import java.util.List;
+
+import javax.swing.JComponent;
+
+/**
+ * DOCUMENT ME!
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class SeqCanvas extends JComponent
+{
+  final FeatureRenderer fr;
+
+  final SequenceRenderer sr;
+
+  BufferedImage img;
+
+  Graphics2D gg;
+
+  int imgWidth;
+
+  int imgHeight;
+
+  AlignViewport av;
+
+  SearchResults searchResults = null;
+
+  boolean fastPaint = false;
+
+  int LABEL_WEST;
+
+  int LABEL_EAST;
+
+  int cursorX = 0;
+
+  int cursorY = 0;
+
+  /**
+   * Creates a new SeqCanvas object.
+   * 
+   * @param av
+   *          DOCUMENT ME!
+   */
+  public SeqCanvas(AlignmentPanel ap)
+  {
+    this.av = ap.av;
+    updateViewport();
+    fr = new FeatureRenderer(ap);
+    sr = new SequenceRenderer(av);
+    setLayout(new BorderLayout());
+    PaintRefresher.Register(this, av.getSequenceSetId());
+    setBackground(Color.white);
+  }
+
+  public SequenceRenderer getSequenceRenderer()
+  {
+    return sr;
+  }
+
+  public FeatureRenderer getFeatureRenderer()
+  {
+    return fr;
+  }
+
+  int charHeight = 0, charWidth = 0;
+
+  private void updateViewport()
+  {
+    charHeight = av.getCharHeight();
+    charWidth = av.getCharWidth();
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param startx
+   *          DOCUMENT ME!
+   * @param endx
+   *          DOCUMENT ME!
+   * @param ypos
+   *          DOCUMENT ME!
+   */
+  private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
+  {
+    updateViewport();
+    int scalestartx = startx - (startx % 10) + 10;
+
+    g.setColor(Color.black);
+    // NORTH SCALE
+    for (int i = scalestartx; i < endx; i += 10)
+    {
+      int value = i;
+      if (av.hasHiddenColumns())
+      {
+        value = av.getColumnSelection().adjustForHiddenColumns(value);
+      }
+
+      g.drawString(String.valueOf(value), (i - startx - 1) * charWidth,
+              ypos - (charHeight / 2));
+
+      g.drawLine(((i - startx - 1) * charWidth) + (charWidth / 2),
+              (ypos + 2) - (charHeight / 2), ((i - startx - 1) * charWidth)
+                      + (charWidth / 2), ypos - 2);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param startx
+   *          DOCUMENT ME!
+   * @param endx
+   *          DOCUMENT ME!
+   * @param ypos
+   *          DOCUMENT ME!
+   */
+  void drawWestScale(Graphics g, int startx, int endx, int ypos)
+  {
+    FontMetrics fm = getFontMetrics(av.getFont());
+    ypos += charHeight;
+
+    if (av.hasHiddenColumns())
+    {
+      startx = av.getColumnSelection().adjustForHiddenColumns(startx);
+      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+    }
+
+    int maxwidth = av.getAlignment().getWidth();
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+    }
+
+    // WEST SCALE
+    for (int i = 0; i < av.getAlignment().getHeight(); i++)
+    {
+      SequenceI seq = av.getAlignment().getSequenceAt(i);
+      int index = startx;
+      int value = -1;
+
+      while (index < endx)
+      {
+        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
+        {
+          index++;
+
+          continue;
+        }
+
+        value = av.getAlignment().getSequenceAt(i).findPosition(index);
+
+        break;
+      }
+
+      if (value != -1)
+      {
+        int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
+                - charWidth / 2;
+        g.drawString(value + "", x, (ypos + (i * charHeight))
+                - (charHeight / 5));
+      }
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param startx
+   *          DOCUMENT ME!
+   * @param endx
+   *          DOCUMENT ME!
+   * @param ypos
+   *          DOCUMENT ME!
+   */
+  void drawEastScale(Graphics g, int startx, int endx, int ypos)
+  {
+    ypos += charHeight;
+
+    if (av.hasHiddenColumns())
+    {
+      endx = av.getColumnSelection().adjustForHiddenColumns(endx);
+    }
+
+    SequenceI seq;
+    // EAST SCALE
+    for (int i = 0; i < av.getAlignment().getHeight(); i++)
+    {
+      seq = av.getAlignment().getSequenceAt(i);
+      int index = endx;
+      int value = -1;
+
+      while (index > startx)
+      {
+        if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
+        {
+          index--;
+
+          continue;
+        }
+
+        value = seq.findPosition(index);
+
+        break;
+      }
+
+      if (value != -1)
+      {
+        g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
+                - (charHeight / 5));
+      }
+    }
+  }
+
+  boolean fastpainting = false;
+
+  /**
+   * need to make this thread safe move alignment rendering in response to
+   * slider adjustment
+   * 
+   * @param horizontal
+   *          shift along
+   * @param vertical
+   *          shift up or down in repaint
+   */
+  public void fastPaint(int horizontal, int vertical)
+  {
+    if (fastpainting || gg == null)
+    {
+      return;
+    }
+    fastpainting = true;
+    fastPaint = true;
+    updateViewport();
+    gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
+            imgHeight, -horizontal * charWidth, -vertical * charHeight);
+
+    int sr = av.startRes;
+    int er = av.endRes;
+    int ss = av.startSeq;
+    int es = av.endSeq;
+    int transX = 0;
+    int transY = 0;
+
+    if (horizontal > 0) // scrollbar pulled right, image to the left
+    {
+      er++;
+      transX = (er - sr - horizontal) * charWidth;
+      sr = er - horizontal;
+    }
+    else if (horizontal < 0)
+    {
+      er = sr - horizontal - 1;
+    }
+    else if (vertical > 0) // scroll down
+    {
+      ss = es - vertical;
+
+      if (ss < av.startSeq)
+      { // ie scrolling too fast, more than a page at a time
+        ss = av.startSeq;
+      }
+      else
+      {
+        transY = imgHeight - (vertical * charHeight);
+      }
+    }
+    else if (vertical < 0)
+    {
+      es = ss - vertical;
+
+      if (es > av.endSeq)
+      {
+        es = av.endSeq;
+      }
+    }
+
+    gg.translate(transX, transY);
+    drawPanel(gg, sr, er, ss, es, 0);
+    gg.translate(-transX, -transY);
+
+    repaint();
+    fastpainting = false;
+  }
+
+  /**
+   * Definitions of startx and endx (hopefully): SMJS This is what I'm working
+   * towards! startx is the first residue (starting at 0) to display. endx is
+   * the last residue to display (starting at 0). starty is the first sequence
+   * to display (starting at 0). endy is the last sequence to display (starting
+   * at 0). NOTE 1: The av limits are set in setFont in this class and in the
+   * adjustment listener in SeqPanel when the scrollbars move.
+   */
+
+  // Set this to false to force a full panel paint
+  @Override
+  public void paintComponent(Graphics g)
+  {
+    updateViewport();
+    BufferedImage lcimg = img; // take reference since other threads may null
+    // img and call later.
+    super.paintComponent(g);
+
+    if (lcimg != null
+            && (fastPaint
+                    || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
+                    .getClipBounds().height)))
+    {
+      g.drawImage(lcimg, 0, 0, this);
+      fastPaint = false;
+      return;
+    }
+
+    // this draws the whole of the alignment
+    imgWidth = getWidth();
+    imgHeight = getHeight();
+
+    imgWidth -= (imgWidth % charWidth);
+    imgHeight -= (imgHeight % charHeight);
+
+    if ((imgWidth < 1) || (imgHeight < 1))
+    {
+      return;
+    }
+
+    if (lcimg == null || imgWidth != lcimg.getWidth()
+            || imgHeight != lcimg.getHeight())
+    {
+      try
+      {
+        lcimg = img = new BufferedImage(imgWidth, imgHeight,
+                BufferedImage.TYPE_INT_RGB);
+        gg = (Graphics2D) img.getGraphics();
+        gg.setFont(av.getFont());
+      } catch (OutOfMemoryError er)
+      {
+        System.gc();
+        System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
+        new OOMWarning("Creating alignment image for display", er);
+
+        return;
+      }
+    }
+
+    if (av.antiAlias)
+    {
+      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+              RenderingHints.VALUE_ANTIALIAS_ON);
+    }
+
+    gg.setColor(Color.white);
+    gg.fillRect(0, 0, imgWidth, imgHeight);
+
+    if (av.getWrapAlignment())
+    {
+      drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);
+    }
+    else
+    {
+      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+    }
+
+    g.drawImage(lcimg, 0, 0, this);
+
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param cwidth
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public int getWrappedCanvasWidth(int cwidth)
+  {
+    FontMetrics fm = getFontMetrics(av.getFont());
+
+    LABEL_EAST = 0;
+    LABEL_WEST = 0;
+
+    if (av.getScaleRightWrapped())
+    {
+      LABEL_EAST = fm.stringWidth(getMask());
+    }
+
+    if (av.getScaleLeftWrapped())
+    {
+      LABEL_WEST = fm.stringWidth(getMask());
+    }
+
+    return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
+  }
+
+  /**
+   * Generates a string of zeroes.
+   * 
+   * @return String
+   */
+  String getMask()
+  {
+    String mask = "00";
+    int maxWidth = 0;
+    int tmp;
+    for (int i = 0; i < av.getAlignment().getHeight(); i++)
+    {
+      tmp = av.getAlignment().getSequenceAt(i).getEnd();
+      if (tmp > maxWidth)
+      {
+        maxWidth = tmp;
+      }
+    }
+
+    for (int i = maxWidth; i > 0; i /= 10)
+    {
+      mask += "0";
+    }
+    return mask;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param canvasWidth
+   *          DOCUMENT ME!
+   * @param canvasHeight
+   *          DOCUMENT ME!
+   * @param startRes
+   *          DOCUMENT ME!
+   */
+  public void drawWrappedPanel(Graphics g, int canvasWidth,
+          int canvasHeight, int startRes)
+  {
+    updateViewport();
+    AlignmentI al = av.getAlignment();
+
+    FontMetrics fm = getFontMetrics(av.getFont());
+
+    if (av.getScaleRightWrapped())
+    {
+      LABEL_EAST = fm.stringWidth(getMask());
+    }
+
+    if (av.getScaleLeftWrapped())
+    {
+      LABEL_WEST = fm.stringWidth(getMask());
+    }
+
+    int hgap = charHeight;
+    if (av.getScaleAboveWrapped())
+    {
+      hgap += charHeight;
+    }
+
+    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
+    int cHeight = av.getAlignment().getHeight() * charHeight;
+
+    av.setWrappedWidth(cWidth);
+
+    av.endRes = av.startRes + cWidth;
+
+    int endx;
+    int ypos = hgap;
+    int maxwidth = av.getAlignment().getWidth() - 1;
+
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
+    }
+
+    while ((ypos <= canvasHeight) && (startRes < maxwidth))
+    {
+      endx = startRes + cWidth - 1;
+
+      if (endx > maxwidth)
+      {
+        endx = maxwidth;
+      }
+
+      g.setFont(av.getFont());
+      g.setColor(Color.black);
+
+      if (av.getScaleLeftWrapped())
+      {
+        drawWestScale(g, startRes, endx, ypos);
+      }
+
+      if (av.getScaleRightWrapped())
+      {
+        g.translate(canvasWidth - LABEL_EAST, 0);
+        drawEastScale(g, startRes, endx, ypos);
+        g.translate(-(canvasWidth - LABEL_EAST), 0);
+      }
+
+      g.translate(LABEL_WEST, 0);
+
+      if (av.getScaleAboveWrapped())
+      {
+        drawNorthScale(g, startRes, endx, ypos);
+      }
+
+      if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
+      {
+        g.setColor(Color.blue);
+        int res;
+        for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
+                .size(); i++)
+        {
+          res = av.getColumnSelection().findHiddenRegionPosition(i)
+                  - startRes;
+
+          if (res < 0 || res > endx - startRes)
+          {
+            continue;
+          }
+
+          gg.fillPolygon(
+                  new int[] { res * charWidth - charHeight / 4,
+                      res * charWidth + charHeight / 4, res * charWidth },
+                  new int[] { ypos - (charHeight / 2),
+                      ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
+                  3);
+
+        }
+      }
+
+      // When printing we have an extra clipped region,
+      // the Printable page which we need to account for here
+      Shape clip = g.getClip();
+
+      if (clip == null)
+      {
+        g.setClip(0, 0, cWidth * charWidth, canvasHeight);
+      }
+      else
+      {
+        g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
+                (int) clip.getBounds().getHeight());
+      }
+
+      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
+
+      if (av.isShowAnnotation())
+      {
+        g.translate(0, cHeight + ypos + 3);
+        if (annotations == null)
+        {
+          annotations = new AnnotationPanel(av);
+        }
+
+        annotations.renderer.drawComponent(annotations, av, g, -1,
+                startRes, endx + 1);
+        g.translate(0, -cHeight - ypos - 3);
+      }
+      g.setClip(clip);
+      g.translate(-LABEL_WEST, 0);
+
+      ypos += cHeight + getAnnotationHeight() + hgap;
+
+      startRes += cWidth;
+    }
+  }
+
+  AnnotationPanel annotations;
+
+  int getAnnotationHeight()
+  {
+    if (!av.isShowAnnotation())
+    {
+      return 0;
+    }
+
+    if (annotations == null)
+    {
+      annotations = new AnnotationPanel(av);
+    }
+
+    return annotations.adjustPanelHeight();
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g1
+   *          DOCUMENT ME!
+   * @param startRes
+   *          DOCUMENT ME!
+   * @param endRes
+   *          DOCUMENT ME!
+   * @param startSeq
+   *          DOCUMENT ME!
+   * @param endSeq
+   *          DOCUMENT ME!
+   * @param offset
+   *          DOCUMENT ME!
+   */
+  public void drawPanel(Graphics g1, int startRes, int endRes,
+          int startSeq, int endSeq, int offset)
+  {
+    updateViewport();
+    if (!av.hasHiddenColumns())
+    {
+      draw(g1, startRes, endRes, startSeq, endSeq, offset);
+    }
+    else
+    {
+      List<int[]> regions = av.getColumnSelection().getHiddenColumns();
+
+      int screenY = 0;
+      int blockStart = startRes;
+      int blockEnd = endRes;
+      int newY = 0, clip;
+      for (int[] region : regions)
+      {
+        int hideStart = region[0];
+        int hideEnd = region[1];
+
+        if (hideStart < blockStart)
+        {
+          blockStart += (hideEnd - hideStart) + 1;
+          continue;
+        }
+        blockEnd = hideStart - 1;
+
+        g1.translate(screenY * charWidth, 0);
+
+        // find end of this visible block
+        newY += blockEnd - blockStart + 1;
+
+        clip = newY - (endRes - startRes);
+        if (clip > 0)
+        {
+          blockEnd = blockStart + (endRes - startRes) - screenY;
+        }
+        // TODO: JAL-1722 - does this block need clipping ?
+        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
+        // TODO: JAL-1722 - is this hidden marker visible ?
+        if (clip < -1 && av.getShowHiddenMarkers())
+        {
+          g1.setColor(Color.blue);
+
+          g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
+                  0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
+                  (endSeq - startSeq) * charHeight + offset);
+        }
+
+        g1.translate(-screenY * charWidth, 0);
+
+        screenY = newY;
+        blockStart = hideEnd + 1;
+
+        if (clip > 0)
+        {
+          // already rendered last block
+          return;
+        }
+      }
+
+      if (screenY <= (endRes - startRes))
+      {
+        // remaining visible region to render
+        blockEnd = blockStart + (endRes - startRes) - screenY;
+        g1.translate(screenY * charWidth, 0);
+        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
+
+        g1.translate(-screenY * charWidth, 0);
+      }
+    }
+  }
+
+  // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
+  // int x1, int x2, int y1, int y2, int startx, int starty,
+  private void draw(Graphics g, int startRes, int endRes, int startSeq,
+          int endSeq, int offset)
+  {
+    g.setFont(av.getFont());
+    sr.prepare(g, av.isRenderGaps());
+
+    SequenceI nextSeq;
+
+    // / First draw the sequences
+    // ///////////////////////////
+    for (int i = startSeq; i < endSeq; i++)
+    {
+      nextSeq = av.getAlignment().getSequenceAt(i);
+      if (nextSeq == null)
+      {
+        // occasionally, a race condition occurs such that the alignment row is
+        // empty
+        continue;
+      }
+      sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
+              startRes, endRes, offset + ((i - startSeq) * charHeight));
+
+      if (av.isShowSequenceFeatures())
+      {
+        fr.drawSequence(g, nextSeq, startRes, endRes, offset
+                + ((i - startSeq) * charHeight));
+      }
+
+      // / Highlight search Results once all sequences have been drawn
+      // ////////////////////////////////////////////////////////
+      if (searchResults != null)
+      {
+        int[] visibleResults = searchResults.getResults(nextSeq, startRes,
+                endRes);
+        if (visibleResults != null)
+        {
+          for (int r = 0; r < visibleResults.length; r += 2)
+          {
+            sr.drawHighlightedText(nextSeq, visibleResults[r],
+                    visibleResults[r + 1], (visibleResults[r] - startRes)
+                            * charWidth, offset
+                            + ((i - startSeq) * charHeight));
+          }
+        }
+      }
+
+      if (av.cursorMode && cursorY == i && cursorX >= startRes
+              && cursorX <= endRes)
+      {
+        sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
+                offset + ((i - startSeq) * charHeight));
+      }
+    }
+
+    if (av.getSelectionGroup() != null
+            || av.getAlignment().getGroups().size() > 0)
+    {
+      drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
+    }
+
+  }
+
+  void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
+          int startSeq, int endSeq, int offset)
+  {
+    Graphics2D g = (Graphics2D) g1;
+    //
+    // ///////////////////////////////////
+    // Now outline any areas if necessary
+    // ///////////////////////////////////
+    SequenceGroup group = av.getSelectionGroup();
+
+    int sx = -1;
+    int sy = -1;
+    int ex = -1;
+    int groupIndex = -1;
+    int visWidth = (endRes - startRes + 1) * charWidth;
+
+    if ((group == null) && (av.getAlignment().getGroups().size() > 0))
+    {
+      group = av.getAlignment().getGroups().get(0);
+      groupIndex = 0;
+    }
+
+    if (group != null)
+    {
+      do
+      {
+        int oldY = -1;
+        int i = 0;
+        boolean inGroup = false;
+        int top = -1;
+        int bottom = -1;
+
+        for (i = startSeq; i < endSeq; i++)
+        {
+          sx = (group.getStartRes() - startRes) * charWidth;
+          sy = offset + ((i - startSeq) * charHeight);
+          ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
+
+          if (sx + ex < 0 || sx > visWidth)
+          {
+            continue;
+          }
+
+          if ((sx <= (endRes - startRes) * charWidth)
+                  && group.getSequences(null).contains(
+                          av.getAlignment().getSequenceAt(i)))
+          {
+            if ((bottom == -1)
+                    && !group.getSequences(null).contains(
+                            av.getAlignment().getSequenceAt(i + 1)))
+            {
+              bottom = sy + charHeight;
+            }
+
+            if (!inGroup)
+            {
+              if (((top == -1) && (i == 0))
+                      || !group.getSequences(null).contains(
+                              av.getAlignment().getSequenceAt(i - 1)))
+              {
+                top = sy;
+              }
+
+              oldY = sy;
+              inGroup = true;
+
+              if (group == av.getSelectionGroup())
+              {
+                g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+                        BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
+                        0f));
+                g.setColor(Color.RED);
+              }
+              else
+              {
+                g.setStroke(new BasicStroke());
+                g.setColor(group.getOutlineColour());
+              }
+            }
+          }
+          else
+          {
+            if (inGroup)
+            {
+              if (sx >= 0 && sx < visWidth)
+              {
+                g.drawLine(sx, oldY, sx, sy);
+              }
+
+              if (sx + ex < visWidth)
+              {
+                g.drawLine(sx + ex, oldY, sx + ex, sy);
+              }
+
+              if (sx < 0)
+              {
+                ex += sx;
+                sx = 0;
+              }
+
+              if (sx + ex > visWidth)
+              {
+                ex = visWidth;
+              }
+
+              else if (sx + ex >= (endRes - startRes + 1) * charWidth)
+              {
+                ex = (endRes - startRes + 1) * charWidth;
+              }
+
+              if (top != -1)
+              {
+                g.drawLine(sx, top, sx + ex, top);
+                top = -1;
+              }
+
+              if (bottom != -1)
+              {
+                g.drawLine(sx, bottom, sx + ex, bottom);
+                bottom = -1;
+              }
+
+              inGroup = false;
+            }
+          }
+        }
+
+        if (inGroup)
+        {
+          sy = offset + ((i - startSeq) * charHeight);
+          if (sx >= 0 && sx < visWidth)
+          {
+            g.drawLine(sx, oldY, sx, sy);
+          }
+
+          if (sx + ex < visWidth)
+          {
+            g.drawLine(sx + ex, oldY, sx + ex, sy);
+          }
+
+          if (sx < 0)
+          {
+            ex += sx;
+            sx = 0;
+          }
+
+          if (sx + ex > visWidth)
+          {
+            ex = visWidth;
+          }
+          else if (sx + ex >= (endRes - startRes + 1) * charWidth)
+          {
+            ex = (endRes - startRes + 1) * charWidth;
+          }
+
+          if (top != -1)
+          {
+            g.drawLine(sx, top, sx + ex, top);
+            top = -1;
+          }
+
+          if (bottom != -1)
+          {
+            g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
+            bottom = -1;
+          }
+
+          inGroup = false;
+        }
+
+        groupIndex++;
+
+        g.setStroke(new BasicStroke());
+
+        if (groupIndex >= av.getAlignment().getGroups().size())
+        {
+          break;
+        }
+
+        group = av.getAlignment().getGroups().get(groupIndex);
+
+      } while (groupIndex < av.getAlignment().getGroups().size());
+
+    }
+
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param results
+   *          DOCUMENT ME!
+   */
+  public void highlightSearchResults(SearchResults results)
+  {
+    img = null;
+
+    searchResults = results;
+
+    repaint();
+  }
+}
index e402d28..c6c2deb 100644 (file)
@@ -27,6 +27,7 @@ import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -59,7 +60,9 @@ import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.ListIterator;
 
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
@@ -181,7 +184,7 @@ public class SeqPanel extends JPanel implements MouseListener,
    * @param evt
    * @return
    */
-  int findRes(MouseEvent evt)
+  int findColumn(MouseEvent evt)
   {
     int res = 0;
     int x = evt.getX();
@@ -209,7 +212,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getStartRes() / cwidth;
+      wrappedBlock += av.getRanges().getStartRes() / cwidth;
 
       res = wrappedBlock * cwidth + x / av.getCharWidth();
 
@@ -222,17 +225,18 @@ public class SeqPanel extends JPanel implements MouseListener,
         // right-hand gutter
         x = seqCanvas.getX() + seqCanvas.getWidth();
       }
-      res = (x / av.getCharWidth()) + av.getStartRes();
-      if (res > av.getEndRes())
+      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
+      if (res > av.getRanges().getEndRes())
       {
         // moused off right
-        res = av.getEndRes();
+        res = av.getRanges().getEndRes();
       }
     }
 
     if (av.hasHiddenColumns())
     {
-      res = av.getColumnSelection().adjustForHiddenColumns(res);
+      res = av.getAlignment().getHiddenColumns()
+              .adjustForHiddenColumns(res);
     }
 
     return res;
@@ -262,7 +266,9 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     else
     {
-      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
+      seq = Math.min((y / av.getCharHeight())
+              + av.getRanges().getStartSeq(),
+              av
               .getAlignment().getHeight() - 1);
     }
 
@@ -336,20 +342,23 @@ public class SeqPanel extends JPanel implements MouseListener,
   {
     seqCanvas.cursorX += dx;
     seqCanvas.cursorY += dy;
+
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+
     if (av.hasHiddenColumns()
-            && !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+ && !hidden.isVisible(seqCanvas.cursorX))
     {
       int original = seqCanvas.cursorX - dx;
       int maxWidth = av.getAlignment().getWidth();
 
-      while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
+      while (!hidden.isVisible(seqCanvas.cursorX)
               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
       {
         seqCanvas.cursorX += dx;
       }
 
       if (seqCanvas.cursorX >= maxWidth
-              || !av.getColumnSelection().isVisible(seqCanvas.cursorX))
+              || !hidden.isVisible(seqCanvas.cursorX))
       {
         seqCanvas.cursorX = original;
       }
@@ -381,37 +390,11 @@ public class SeqPanel extends JPanel implements MouseListener,
     endEditing();
     if (av.getWrapAlignment())
     {
-      ap.scrollToWrappedVisible(seqCanvas.cursorX);
+      av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
     }
     else
     {
-      while (seqCanvas.cursorY < av.startSeq)
-      {
-        ap.scrollUp(true);
-      }
-      while (seqCanvas.cursorY + 1 > av.endSeq)
-      {
-        ap.scrollUp(false);
-      }
-      if (!av.getWrapAlignment())
-      {
-        while (seqCanvas.cursorX < av.getColumnSelection()
-                .adjustForHiddenColumns(av.startRes))
-        {
-          if (!ap.scrollRight(false))
-          {
-            break;
-          }
-        }
-        while (seqCanvas.cursorX > av.getColumnSelection()
-                .adjustForHiddenColumns(av.endRes))
-        {
-          if (!ap.scrollRight(true))
-          {
-            break;
-          }
-        }
-      }
+      av.getRanges().scrollToVisible(seqCanvas.cursorX, seqCanvas.cursorY);
     }
     setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
             seqCanvas.cursorX, seqCanvas.cursorY);
@@ -581,6 +564,7 @@ public class SeqPanel extends JPanel implements MouseListener,
   @Override
   public void mouseReleased(MouseEvent evt)
   {
+    boolean didDrag = mouseDragging; // did we come here after a drag
     mouseDragging = false;
     mouseWheelPressed = false;
 
@@ -593,7 +577,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (!editingSeqs)
     {
-      doMouseReleasedDefineMode(evt);
+      doMouseReleasedDefineMode(evt, didDrag);
       return;
     }
 
@@ -633,7 +617,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
 
     int seq = findSeq(evt);
-    int res = findRes(evt);
+    int res = findColumn(evt);
 
     if (seq < 0 || res < 0)
     {
@@ -687,17 +671,16 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (av.isFollowHighlight())
     {
-      /*
-       * if scrollToPosition requires a scroll adjustment, this flag prevents
-       * another scroll event being propagated back to the originator
-       * 
-       * @see AlignmentPanel#adjustmentValueChanged
-       */
-      ap.setDontScrollComplement(true);
+      // don't allow highlight of protein/cDNA to also scroll a complementary
+      // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
+      // over residue to change abruptly, causing highlighted residue in panel 2
+      // to change, causing a scroll in panel 1 etc)
+      ap.setToScrollComplementPanel(false);
       if (ap.scrollToPosition(results, false))
       {
         seqCanvas.revalidate();
       }
+      ap.setToScrollComplementPanel(true);
     }
     setStatusMessage(results);
     seqCanvas.highlightSearchResults(results);
@@ -732,25 +715,28 @@ public class SeqPanel extends JPanel implements MouseListener,
       mouseDragged(evt);
     }
 
-    int res = findRes(evt);
+    final int column = findColumn(evt);
     int seq = findSeq(evt);
-    int pos;
-    if (res < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
+    if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
     {
       return;
     }
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 
-    if (res >= sequence.getLength())
+    if (column >= sequence.getLength())
     {
       return;
     }
 
-    pos = setStatusMessage(sequence, res, seq);
-    if (ssm != null && pos > -1)
+    /*
+     * set status bar message, returning residue position in sequence
+     */
+    boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+    final int pos = setStatusMessage(sequence, column, seq);
+    if (ssm != null && !isGapped)
     {
-      mouseOverSequence(sequence, res, pos);
+      mouseOverSequence(sequence, column, pos);
     }
 
     tooltipText.setLength(6); // Cuts the buffer back to <html>
@@ -760,7 +746,8 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       for (int g = 0; g < groups.length; g++)
       {
-        if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
+        if (groups[g].getStartRes() <= column
+                && groups[g].getEndRes() >= column)
         {
           if (!groups[g].getName().startsWith("JTreeGroup")
                   && !groups[g].getName().startsWith("JGroup"))
@@ -776,14 +763,20 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
     }
 
-    // use aa to see if the mouse pointer is on a
+    /*
+     * add any features at the position to the tooltip; if over a gap, only
+     * add features that straddle the gap (pos may be the residue before or
+     * after the gap)
+     */
     if (av.isShowSequenceFeatures())
     {
-      int rpos;
       List<SequenceFeature> features = ap.getFeatureRenderer()
-              .findFeaturesAtRes(sequence.getDatasetSequence(),
-                      rpos = sequence.findPosition(res));
-      seqARep.appendFeatures(tooltipText, rpos, features,
+              .findFeaturesAtRes(sequence.getDatasetSequence(), pos);
+      if (isGapped)
+      {
+        removeAdjacentFeatures(features, column + 1, sequence);
+      }
+      seqARep.appendFeatures(tooltipText, pos, features,
               this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
     }
     if (tooltipText.length() == 6) // <html>
@@ -807,6 +800,35 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   }
 
+  /**
+   * Removes from the list of features any that start after, or end before, the
+   * given column position. This allows us to retain only those features
+   * adjacent to a gapped position that straddle the position. Contact features
+   * that 'straddle' the position are also removed, since they are not 'at' the
+   * position.
+   * 
+   * @param features
+   * @param column
+   *          alignment column (1..)
+   * @param sequence
+   */
+  protected void removeAdjacentFeatures(List<SequenceFeature> features,
+          final int column, SequenceI sequence)
+  {
+    // TODO should this be an AlignViewController method (and reused by applet)?
+    ListIterator<SequenceFeature> it = features.listIterator();
+    while (it.hasNext())
+    {
+      SequenceFeature sf = it.next();
+      if (sf.isContactFeature()
+              || sequence.findIndex(sf.getBegin()) > column
+              || sequence.findIndex(sf.getEnd()) < column)
+      {
+        it.remove();
+      }
+    }
+  }
+
   private Point lastp = null;
 
   /*
@@ -850,17 +872,22 @@ public class SeqPanel extends JPanel implements MouseListener,
   // avcontroller or viewModel
 
   /**
-   * Set status message in alignment panel
+   * Sets the status message in alignment panel, showing the sequence number
+   * (index) and id, and residue and residue position if not at a gap, for the
+   * given sequence and column position. Returns the residue position returned
+   * by Sequence.findPosition. Note this may be for the nearest adjacent residue
+   * if at a gapped position.
    * 
    * @param sequence
    *          aligned sequence object
-   * @param res
+   * @param column
    *          alignment column
    * @param seq
    *          index of sequence in alignment
-   * @return position of res in sequence
+   * @return sequence position of residue at column, or adjacent residue if at a
+   *         gap
    */
-  int setStatusMessage(SequenceI sequence, int res, int seq)
+  int setStatusMessage(SequenceI sequence, final int column, int seq)
   {
     StringBuilder text = new StringBuilder(32);
 
@@ -872,36 +899,34 @@ public class SeqPanel extends JPanel implements MouseListener,
             .append(sequence.getName());
 
     String residue = null;
+
     /*
      * Try to translate the display character to residue name (null for gap).
      */
-    final String displayChar = String.valueOf(sequence.getCharAt(res));
-    if (av.getAlignment().isNucleotide())
+    final String displayChar = String.valueOf(sequence.getCharAt(column));
+    boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+    int pos = sequence.findPosition(column);
+
+    if (!isGapped)
     {
-      residue = ResidueProperties.nucleotideName.get(displayChar);
-      if (residue != null)
+      boolean nucleotide = av.getAlignment().isNucleotide();
+      if (nucleotide)
       {
-        text.append(" Nucleotide: ").append(residue);
+        residue = ResidueProperties.nucleotideName.get(displayChar);
       }
-    }
-    else
-    {
-      residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
-              .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
-              .get(displayChar));
-      if (residue != null)
+      else
       {
-        text.append(" Residue: ").append(residue);
+        residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
+                .equals(displayChar) ? "STOP"
+                : ResidueProperties.aa2Triplet.get(displayChar));
       }
-    }
+      text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
+              .append(": ").append(residue == null ? displayChar : residue);
 
-    int pos = -1;
-    if (residue != null)
-    {
-      pos = sequence.findPosition(res);
       text.append(" (").append(Integer.toString(pos)).append(")");
     }
     ap.alignFrame.statusBar.setText(text.toString());
+
     return pos;
   }
 
@@ -942,30 +967,36 @@ public class SeqPanel extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param evt
-   *          DOCUMENT ME!
+   * {@inheritDoc}
    */
   @Override
   public void mouseDragged(MouseEvent evt)
   {
     if (mouseWheelPressed)
     {
+      boolean inSplitFrame = ap.av.getCodingComplement() != null;
+      boolean copyChanges = inSplitFrame && av.isProteinFontAsCdna();
+
       int oldWidth = av.getCharWidth();
 
       // Which is bigger, left-right or up-down?
       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
               .getX() - lastMousePress.getX()))
       {
+        /*
+         * on drag up or down, decrement or increment font size
+         */
         int fontSize = av.font.getSize();
+        boolean fontChanged = false;
 
         if (evt.getY() < lastMousePress.getY())
         {
+          fontChanged = true;
           fontSize--;
         }
         else if (evt.getY() > lastMousePress.getY())
         {
+          fontChanged = true;
           fontSize++;
         }
 
@@ -974,24 +1005,56 @@ public class SeqPanel extends JPanel implements MouseListener,
           fontSize = 1;
         }
 
-        av.setFont(
-                new Font(av.font.getName(), av.font.getStyle(), fontSize),
-                true);
-        av.setCharWidth(oldWidth);
-        ap.fontChanged();
+        if (fontChanged)
+        {
+          Font newFont = new Font(av.font.getName(), av.font.getStyle(),
+                  fontSize);
+          av.setFont(newFont, true);
+          av.setCharWidth(oldWidth);
+          ap.fontChanged();
+          if (copyChanges)
+          {
+            ap.av.getCodingComplement().setFont(newFont, true);
+            SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+                    .getSplitViewContainer();
+            splitFrame.adjustLayout();
+            splitFrame.repaint();
+          }
+        }
       }
       else
       {
+        /*
+         * on drag left or right, decrement or increment character width
+         */
+        int newWidth = 0;
         if (evt.getX() < lastMousePress.getX() && av.getCharWidth() > 1)
         {
-          av.setCharWidth(av.getCharWidth() - 1);
+          newWidth = av.getCharWidth() - 1;
+          av.setCharWidth(newWidth);
         }
         else if (evt.getX() > lastMousePress.getX())
         {
-          av.setCharWidth(av.getCharWidth() + 1);
+          newWidth = av.getCharWidth() + 1;
+          av.setCharWidth(newWidth);
+        }
+        if (newWidth > 0)
+        {
+          ap.paintAlignment(false);
+          if (copyChanges)
+          {
+            /*
+             * need to ensure newWidth is set on cdna, regardless of which
+             * panel the mouse drag happened in; protein will compute its 
+             * character width as 1:1 or 3:1
+             */
+            av.getCodingComplement().setCharWidth(newWidth);
+            SplitFrame splitFrame = (SplitFrame) ap.alignFrame
+                    .getSplitViewContainer();
+            splitFrame.adjustLayout();
+            splitFrame.repaint();
+          }
         }
-
-        ap.paintAlignment(false);
       }
 
       FontMetrics fm = getFontMetrics(av.getFont());
@@ -1008,7 +1071,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       return;
     }
 
-    int res = findRes(evt);
+    int res = findColumn(evt);
 
     if (res < 0)
     {
@@ -1141,8 +1204,10 @@ public class SeqPanel extends JPanel implements MouseListener,
     if (av.hasHiddenColumns())
     {
       fixedColumns = true;
-      int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
-      int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
+      int y1 = av.getAlignment().getHiddenColumns()
+              .getHiddenBoundaryLeft(startres);
+      int y2 = av.getAlignment().getHiddenColumns()
+              .getHiddenBoundaryRight(startres);
 
       if ((insertGap && startres > y1 && lastres < y1)
               || (!insertGap && startres < y2 && lastres > y2))
@@ -1217,8 +1282,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         {
           if (sg.getSize() == av.getAlignment().getHeight())
           {
-            if ((av.hasHiddenColumns() && startres < av
-                    .getColumnSelection().getHiddenBoundaryRight(startres)))
+            if ((av.hasHiddenColumns() && startres < av.getAlignment()
+                    .getHiddenColumns().getHiddenBoundaryRight(startres)))
             {
               endEditing();
               return;
@@ -1482,6 +1547,11 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
   }
 
+  /**
+   * Handler for double-click on a position with one or more sequence features.
+   * Opens the Amend Features dialog to allow feature details to be amended, or
+   * the feature deleted.
+   */
   @Override
   public void mouseClicked(MouseEvent evt)
   {
@@ -1496,24 +1566,38 @@ public class SeqPanel extends JPanel implements MouseListener,
         av.setSelectionGroup(null);
       }
 
+      int column = findColumn(evt);
+      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+
+      /*
+       * find features at the position (if not gapped), or straddling
+       * the position (if at a gap)
+       */
       List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
               .findFeaturesAtRes(sequence.getDatasetSequence(),
-                      sequence.findPosition(findRes(evt)));
+                      sequence.findPosition(column));
+      if (isGapped)
+      {
+        removeAdjacentFeatures(features, column, sequence);
+      }
 
-      if (features != null && features.size() > 0)
+      if (!features.isEmpty())
       {
+        /*
+         * highlight the first feature at the position on the alignment
+         */
         SearchResultsI highlight = new SearchResults();
         highlight.addResult(sequence, features.get(0).getBegin(), features
                 .get(0).getEnd());
         seqCanvas.highlightSearchResults(highlight);
-      }
-      if (features != null && features.size() > 0)
-      {
-        seqCanvas.getFeatureRenderer().amendFeatures(
-                new SequenceI[] { sequence },
-                features.toArray(new SequenceFeature[features.size()]),
-                false, ap);
 
+        /*
+         * open the Amend Features dialog; clear highlighting afterwards,
+         * whether changes were made or not
+         */
+        List<SequenceI> seqs = Collections.singletonList(sequence);
+        seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
+                ap);
         seqCanvas.highlightSearchResults(null);
       }
     }
@@ -1527,23 +1611,23 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       if (e.isShiftDown())
       {
-        ap.scrollRight(true);
+        av.getRanges().scrollRight(true);
 
       }
       else
       {
-        ap.scrollUp(false);
+        av.getRanges().scrollUp(false);
       }
     }
     else
     {
       if (e.isShiftDown())
       {
-        ap.scrollRight(false);
+        av.getRanges().scrollRight(false);
       }
       else
       {
-        ap.scrollUp(true);
+        av.getRanges().scrollUp(true);
       }
     }
     // TODO Update tooltip for new position.
@@ -1557,7 +1641,7 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   public void doMousePressedDefineMode(MouseEvent evt)
   {
-    final int res = findRes(evt);
+    final int res = findColumn(evt);
     final int seq = findSeq(evt);
     oldSeq = seq;
     needOverviewUpdate = false;
@@ -1587,34 +1671,15 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     stretchGroup = av.getSelectionGroup();
 
-    if (stretchGroup == null)
+    if (stretchGroup == null || !stretchGroup.contains(sequence, res))
     {
       stretchGroup = av.getAlignment().findGroup(sequence, res);
-      av.setSelectionGroup(stretchGroup);
-    }
-    if (stretchGroup == null
-            || !stretchGroup.getSequences(null).contains(sequence)
-            || (stretchGroup.getStartRes() > res)
-            || (stretchGroup.getEndRes() < res))
-    {
-      stretchGroup = null;
-
-      SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
-
-      if (allGroups != null)
+      if (stretchGroup != null)
       {
-        for (int i = 0; i < allGroups.length; i++)
-        {
-          if ((allGroups[i].getStartRes() <= res)
-                  && (allGroups[i].getEndRes() >= res))
-          {
-            stretchGroup = allGroups[i];
-            break;
-          }
-        }
+        // only update the current selection if the popup menu has a group to
+        // focus on
+        av.setSelectionGroup(stretchGroup);
       }
-
-      av.setSelectionGroup(stretchGroup);
     }
 
     if (evt.isPopupTrigger()) // Mac: mousePressed
@@ -1635,7 +1700,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (av.cursorMode)
     {
-      seqCanvas.cursorX = findRes(evt);
+      seqCanvas.cursorX = findColumn(evt);
       seqCanvas.cursorY = findSeq(evt);
       seqCanvas.repaint();
       return;
@@ -1664,6 +1729,7 @@ public class SeqPanel extends JPanel implements MouseListener,
         SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
                 ap.getViewName());
       }
+      // TODO: stretchGroup will always be not null. Is this a merge error ?
       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
       {
         // Edit end res position of selected group
@@ -1686,17 +1752,17 @@ public class SeqPanel extends JPanel implements MouseListener,
    * 
    * @param evt
    * @param res
-   * @param sequence
+   * @param sequences
    */
   void showPopupMenu(MouseEvent evt)
   {
-    final int res = findRes(evt);
+    final int res = findColumn(evt);
     final int seq = findSeq(evt);
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
     List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
             .findFeaturesAtRes(sequence.getDatasetSequence(),
                     sequence.findPosition(res));
-    List<String> links = new ArrayList<String>();
+    List<String> links = new ArrayList<>();
     for (SequenceFeature sf : allFeatures)
     {
       if (sf.links != null)
@@ -1713,12 +1779,15 @@ public class SeqPanel extends JPanel implements MouseListener,
   }
 
   /**
-   * DOCUMENT ME!
+   * Update the display after mouse up on a selection or group
    * 
    * @param evt
-   *          DOCUMENT ME!
+   *          mouse released event details
+   * @param afterDrag
+   *          true if this event is happening after a mouse drag (rather than a
+   *          mouse down)
    */
-  public void doMouseReleasedDefineMode(MouseEvent evt)
+  public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag)
   {
     if (stretchGroup == null)
     {
@@ -1727,7 +1796,8 @@ public class SeqPanel extends JPanel implements MouseListener,
     // always do this - annotation has own state
     // but defer colourscheme update until hidden sequences are passed in
     boolean vischange = stretchGroup.recalcConservation(true);
-    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
+    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup()
+            && afterDrag;
     if (stretchGroup.cs != null)
     {
       stretchGroup.cs.alignmentChanged(stretchGroup,
@@ -1761,7 +1831,7 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   public void doMouseDraggedDefineMode(MouseEvent evt)
   {
-    int res = findRes(evt);
+    int res = findColumn(evt);
     int y = findSeq(evt);
 
     if (wrappedBlock != startWrapBlock)
@@ -1790,9 +1860,9 @@ public class SeqPanel extends JPanel implements MouseListener,
       changeStartRes = true;
     }
 
-    if (res < av.getStartRes())
+    if (res < av.getRanges().getStartRes())
     {
-      res = av.getStartRes();
+      res = av.getRanges().getStartRes();
     }
 
     if (changeEndRes)
@@ -1926,24 +1996,26 @@ public class SeqPanel extends JPanel implements MouseListener,
       {
         if (evt != null)
         {
-          if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
+          if (mouseDragging && (evt.getY() < 0)
+                  && (av.getRanges().getStartSeq() > 0))
           {
-            running = ap.scrollUp(true);
+            running = av.getRanges().scrollUp(true);
           }
 
           if (mouseDragging && (evt.getY() >= getHeight())
-                  && (av.getAlignment().getHeight() > av.getEndSeq()))
+                  && (av.getAlignment().getHeight() > av.getRanges()
+                          .getEndSeq()))
           {
-            running = ap.scrollUp(false);
+            running = av.getRanges().scrollUp(false);
           }
 
           if (mouseDragging && (evt.getX() < 0))
           {
-            running = ap.scrollRight(false);
+            running = av.getRanges().scrollRight(false);
           }
           else if (mouseDragging && (evt.getX() >= getWidth()))
           {
-            running = ap.scrollRight(true);
+            running = av.getRanges().scrollRight(true);
           }
         }
 
@@ -1962,7 +2034,7 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   @Override
   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
+          HiddenColumns hidden, SelectionSource source)
   {
     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
     // handles selection messages...
@@ -1970,7 +2042,20 @@ public class SeqPanel extends JPanel implements MouseListener,
     // shared between viewports.
     boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
             .getSequenceSetId().equals(av.getSequenceSetId())));
-    if (iSentTheSelection || !av.followSelection)
+
+    if (iSentTheSelection)
+    {
+      // respond to our own event by updating dependent dialogs
+      if (ap.getCalculationDialog() != null)
+      {
+        ap.getCalculationDialog().validateCalcTypes();
+      }
+
+      return;
+    }
+
+    // process further ?
+    if (!av.followSelection)
     {
       return;
     }
@@ -1987,7 +2072,7 @@ public class SeqPanel extends JPanel implements MouseListener,
      * Check for selection in a view of which this one is a dna/protein
      * complement.
      */
-    if (selectionFromTranslation(seqsel, colsel, source))
+    if (selectionFromTranslation(seqsel, colsel, hidden, source))
     {
       return;
     }
@@ -2050,7 +2135,8 @@ public class SeqPanel extends JPanel implements MouseListener,
         }
         else
         {
-          av.getColumnSelection().setElementsFrom(colsel);
+          av.getColumnSelection().setElementsFrom(colsel,
+                  av.getAlignment().getHiddenColumns());
         }
       }
       av.isColSelChanged(true);
@@ -2059,8 +2145,8 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (copycolsel
             && av.hasHiddenColumns()
-            && (av.getColumnSelection() == null || av.getColumnSelection()
-                    .getHiddenColumns() == null))
+            && (av.getAlignment().getHiddenColumns() == null || av
+                    .getAlignment().getHiddenColumns().getHiddenRegions() == null))
     {
       System.err.println("Bad things");
     }
@@ -2070,6 +2156,13 @@ public class SeqPanel extends JPanel implements MouseListener,
       PaintRefresher.Refresh(this, av.getSequenceSetId());
       // ap.paintAlignment(false);
     }
+
+    // lastly, update dependent dialogs
+    if (ap.getCalculationDialog() != null)
+    {
+      ap.getCalculationDialog().validateCalcTypes();
+    }
+
   }
 
   /**
@@ -2082,7 +2175,8 @@ public class SeqPanel extends JPanel implements MouseListener,
    * @param source
    */
   protected boolean selectionFromTranslation(SequenceGroup seqsel,
-          ColumnSelection colsel, SelectionSource source)
+          ColumnSelection colsel, HiddenColumns hidden,
+          SelectionSource source)
   {
     if (!(source instanceof AlignViewportI))
     {
@@ -2105,9 +2199,19 @@ public class SeqPanel extends JPanel implements MouseListener,
     /*
      * Map column selection
      */
-    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
-            av);
+    // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
+    // av);
+    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns hs = new HiddenColumns();
+    MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
     av.setColumnSelection(cs);
+    av.getAlignment().setHiddenColumns(hs);
+
+    // lastly, update any dependent dialogs
+    if (ap.getCalculationDialog() != null)
+    {
+      ap.getCalculationDialog().validateCalcTypes();
+    }
 
     PaintRefresher.Refresh(this, av.getSequenceSetId());
 
index 8e1d549..bf0ab70 100755 (executable)
@@ -52,7 +52,6 @@ import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTextArea;
index 95c3261..36825ea 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.api.FeatureRenderer;
+import jalview.api.AlignViewportI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.util.Comparison;
 
 import java.awt.Color;
@@ -34,7 +35,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
 {
   final static int CHAR_TO_UPPER = 'A' - 'a';
 
-  AlignViewport av;
+  AlignViewportI av;
 
   FontMetrics fm;
 
@@ -53,14 +54,13 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
   boolean forOverview = false;
 
   /**
-   * Creates a new SequenceRenderer object.
+   * Creates a new SequenceRenderer object
    * 
-   * @param av
-   *          DOCUMENT ME!
+   * @param viewport
    */
-  public SequenceRenderer(AlignViewport av)
+  public SequenceRenderer(AlignViewportI viewport)
   {
-    this.av = av;
+    this.av = viewport;
   }
 
   /**
@@ -83,8 +83,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     this.renderGaps = renderGaps;
   }
 
-  @Override
-  public Color getResidueBoxColour(SequenceI seq, int i)
+  protected Color getResidueBoxColour(SequenceI seq, int i)
   {
     // rate limiting step when rendering overview for lots of groups
     allGroups = av.getAlignment().findAllGroups(seq);
@@ -111,20 +110,18 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
    * 
    * @param seq
    * @param position
-   * @param fr
+   * @param finder
    * @return
    */
   @Override
   public Color getResidueColour(final SequenceI seq, int position,
-          FeatureRenderer fr)
+          FeatureColourFinder finder)
   {
-    // TODO replace 8 or so code duplications with calls to this method
-    // (refactored as needed)
     Color col = getResidueBoxColour(seq, position);
 
-    if (fr != null)
+    if (finder != null)
     {
-      col = fr.findFeatureColour(col, seq, position);
+      col = finder.findFeatureColour(col, seq, position);
     }
     return col;
   }
@@ -141,7 +138,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
    */
   void getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
   {
-    if (shader != null)
+    if (shader.getColourScheme() != null)
     {
       resBoxColour = shader.findColour(seq.getCharAt(i),
               i, seq);
@@ -185,7 +182,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
 
     drawBoxes(seq, start, end, y1);
 
-    if (av.validCharWidth)
+    if (av.isValidCharWidth())
     {
       drawText(seq, start, end, y1);
     }
index 6c849c3..1d929e6 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.gui;
 
 import jalview.api.SplitContainerI;
-import jalview.api.ViewStyleI;
 import jalview.datamodel.AlignmentI;
 import jalview.jbgui.GAlignFrame;
 import jalview.jbgui.GSplitFrame;
@@ -189,10 +188,8 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
             : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
     if (protein != null && cdna != null)
     {
-      ViewStyleI vs = protein.getViewStyle();
-      int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
-      vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
-      protein.setViewStyle(vs);
+      int scale = protein.isScaleProteinAsCdna() ? 3 : 1;
+      protein.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
     }
   }
 
index ee22ae4..3e516a6 100644 (file)
@@ -53,7 +53,6 @@ import java.util.Vector;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.table.AbstractTableModel;
 
 /**
@@ -542,26 +541,26 @@ public class StructureChooser extends GStructureChooser implements
     if (haveData)
     {
       cmb_filterOption.addItem(new FilterOption("Best Quality",
-              "overall_quality", VIEWS_FILTER));
+              "overall_quality", VIEWS_FILTER, false));
       cmb_filterOption.addItem(new FilterOption("Best Resolution",
-              "resolution", VIEWS_FILTER));
+              "resolution", VIEWS_FILTER, false));
       cmb_filterOption.addItem(new FilterOption("Most Protein Chain",
-              "number_of_protein_chains", VIEWS_FILTER));
+              "number_of_protein_chains", VIEWS_FILTER, false));
       cmb_filterOption.addItem(new FilterOption("Most Bound Molecules",
-              "number_of_bound_molecules", VIEWS_FILTER));
+              "number_of_bound_molecules", VIEWS_FILTER, false));
       cmb_filterOption.addItem(new FilterOption("Most Polymer Residues",
-              "number_of_polymer_residues", VIEWS_FILTER));
+              "number_of_polymer_residues", VIEWS_FILTER, true));
     }
     cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
-            VIEWS_ENTER_ID));
+            VIEWS_ENTER_ID, false));
     cmb_filterOption.addItem(new FilterOption("From File", "-",
-            VIEWS_FROM_FILE));
-    FilterOption cachedOption = new FilterOption("Cached PDB Entries", "-",
-            VIEWS_LOCAL_PDB);
-    cmb_filterOption.addItem(cachedOption);
+            VIEWS_FROM_FILE, false));
 
-    if (/*!haveData &&*/cachedPDBExists)
+    if (cachedPDBExists)
     {
+      FilterOption cachedOption = new FilterOption("Cached PDB Entries",
+              "-", VIEWS_LOCAL_PDB, false);
+      cmb_filterOption.addItem(cachedOption);
       cmb_filterOption.setSelectedItem(cachedOption);
     }
 
diff --git a/src/jalview/gui/StructureChooser.java~ b/src/jalview/gui/StructureChooser.java~
new file mode 100644 (file)
index 0000000..5d6bef1
--- /dev/null
@@ -0,0 +1,1161 @@
+/*
+ * 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.gui;
+
+import jalview.bin.Jalview;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.DBRefSource;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.fts.api.FTSData;
+import jalview.fts.api.FTSDataColumnI;
+import jalview.fts.api.FTSRestClientI;
+import jalview.fts.core.FTSRestRequest;
+import jalview.fts.core.FTSRestResponse;
+import jalview.fts.service.pdb.PDBFTSRestClient;
+import jalview.jbgui.GStructureChooser;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.MessageManager;
+import jalview.ws.DBRefFetcher;
+import jalview.ws.sifts.SiftsSettings;
+
+import java.awt.event.ItemEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Vector;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Provides the behaviors for the Structure chooser Panel
+ * 
+ * @author tcnofoegbu
+ *
+ */
+@SuppressWarnings("serial")
+public class StructureChooser extends GStructureChooser implements
+        IProgressIndicator
+{
+  private boolean structuresDiscovered = false;
+
+  private SequenceI selectedSequence;
+
+  private SequenceI[] selectedSequences;
+
+  private IProgressIndicator progressIndicator;
+
+  private Collection<FTSData> discoveredStructuresSet;
+
+  private FTSRestRequest lastPdbRequest;
+
+  private FTSRestClientI pdbRestCleint;
+
+  private String selectedPdbFileName;
+
+  private boolean isValidPBDEntry;
+
+  public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
+          AlignmentPanel ap)
+  {
+    this.ap = ap;
+    this.selectedSequence = selectedSeq;
+    this.selectedSequences = selectedSeqs;
+    this.progressIndicator = (ap == null) ? null : ap.alignFrame;
+    init();
+  }
+
+  /**
+   * Initializes parameters used by the Structure Chooser Panel
+   */
+  public void init()
+  {
+    if (!Jalview.isHeadlessMode())
+    {
+      progressBar = new ProgressBar(this.statusPanel, this.statusBar);
+    }
+
+    Thread discoverPDBStructuresThread = new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        long startTime = System.currentTimeMillis();
+        updateProgressIndicator(MessageManager
+                .getString("status.loading_cached_pdb_entries"), startTime);
+        loadLocalCachedPDBEntries();
+        updateProgressIndicator(null, startTime);
+        updateProgressIndicator(MessageManager
+                .getString("status.searching_for_pdb_structures"),
+                startTime);
+        fetchStructuresMetaData();
+        populateFilterComboBox();
+        updateProgressIndicator(null, startTime);
+        mainFrame.setVisible(true);
+        updateCurrentView();
+      }
+    });
+    discoverPDBStructuresThread.start();
+  }
+
+  /**
+   * Updates the progress indicator with the specified message
+   * 
+   * @param message
+   *          displayed message for the operation
+   * @param id
+   *          unique handle for this indicator
+   */
+  public void updateProgressIndicator(String message, long id)
+  {
+    if (progressIndicator != null)
+    {
+      progressIndicator.setProgressBar(message, id);
+    }
+  }
+
+  /**
+   * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
+   * selection group
+   */
+  public void fetchStructuresMetaData()
+  {
+    long startTime = System.currentTimeMillis();
+    pdbRestCleint = PDBFTSRestClient.getInstance();
+    Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
+            .getStructureSummaryFields();
+
+    discoveredStructuresSet = new LinkedHashSet<FTSData>();
+    HashSet<String> errors = new HashSet<String>();
+    for (SequenceI seq : selectedSequences)
+    {
+      FTSRestRequest pdbRequest = new FTSRestRequest();
+      pdbRequest.setAllowEmptySeq(false);
+      pdbRequest.setResponseSize(500);
+      pdbRequest.setFieldToSearchBy("(");
+      pdbRequest.setWantedFields(wantedFields);
+      pdbRequest.setSearchTerm(buildQuery(seq) + ")");
+      pdbRequest.setAssociatedSequence(seq);
+      FTSRestResponse resultList;
+      try
+      {
+        resultList = pdbRestCleint.executeRequest(pdbRequest);
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+        errors.add(e.getMessage());
+        continue;
+      }
+      lastPdbRequest = pdbRequest;
+      if (resultList.getSearchSummary() != null
+              && !resultList.getSearchSummary().isEmpty())
+      {
+        discoveredStructuresSet.addAll(resultList.getSearchSummary());
+      }
+    }
+
+    int noOfStructuresFound = 0;
+    String totalTime = (System.currentTimeMillis() - startTime)
+            + " milli secs";
+    if (discoveredStructuresSet != null
+            && !discoveredStructuresSet.isEmpty())
+    {
+      getResultTable().setModel(
+              FTSRestResponse.getTableModel(lastPdbRequest,
+              discoveredStructuresSet));
+      structuresDiscovered = true;
+      noOfStructuresFound = discoveredStructuresSet.size();
+      mainFrame.setTitle(MessageManager.formatMessage(
+              "label.structure_chooser_no_of_structures",
+              noOfStructuresFound, totalTime));
+    }
+    else
+    {
+      mainFrame.setTitle(MessageManager
+              .getString("label.structure_chooser_manual_association"));
+      if (errors.size() > 0)
+      {
+        StringBuilder errorMsg = new StringBuilder();
+        for (String error : errors)
+        {
+          errorMsg.append(error).append("\n");
+        }
+        JOptionPane.showMessageDialog(this, errorMsg.toString(),
+                MessageManager.getString("label.pdb_web-service_error"),
+                JOptionPane.ERROR_MESSAGE);
+      }
+    }
+  }
+
+  public void loadLocalCachedPDBEntries()
+  {
+    ArrayList<CachedPDB> entries = new ArrayList<CachedPDB>();
+    for (SequenceI seq : selectedSequences)
+    {
+      if (seq.getDatasetSequence() != null
+              && seq.getDatasetSequence().getAllPDBEntries() != null)
+      {
+        for (PDBEntry pdbEntry : seq.getDatasetSequence()
+                .getAllPDBEntries())
+        {
+          if (pdbEntry.getFile() != null)
+          {
+            entries.add(new CachedPDB(seq, pdbEntry));
+          }
+        }
+      }
+    }
+
+    PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
+    tbl_local_pdb.setModel(tableModelx);
+  }
+
+  /**
+   * Builds a query string for a given sequences using its DBRef entries
+   * 
+   * @param seq
+   *          the sequences to build a query for
+   * @return the built query string
+   */
+
+  public static String buildQuery(SequenceI seq)
+  {
+    boolean isPDBRefsFound = false;
+    boolean isUniProtRefsFound = false;
+    StringBuilder queryBuilder = new StringBuilder();
+    HashSet<String> seqRefs = new LinkedHashSet<String>();
+
+    if (seq.getAllPDBEntries() != null)
+    {
+      for (PDBEntry entry : seq.getAllPDBEntries())
+      {
+        if (isValidSeqName(entry.getId()))
+        {
+          queryBuilder.append("pdb_id")
+                  .append(":")
+.append(entry.getId().toLowerCase())
+                  .append(" OR ");
+          isPDBRefsFound = true;
+          // seqRefs.add(entry.getId());
+        }
+      }
+    }
+
+    if (seq.getDBRefs() != null && seq.getDBRefs().length != 0)
+    {
+      for (DBRefEntry dbRef : seq.getDBRefs())
+      {
+        if (isValidSeqName(getDBRefId(dbRef)))
+        {
+          if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
+          {
+            queryBuilder
+.append("uniprot_accession").append(":")
+                    .append(getDBRefId(dbRef))
+                    .append(" OR ");
+            queryBuilder
+.append("uniprot_id")
+                    .append(":")
+                    .append(getDBRefId(dbRef)).append(" OR ");
+            isUniProtRefsFound = true;
+          }
+          else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
+          {
+
+            queryBuilder.append("pdb_id")
+                    .append(":").append(getDBRefId(dbRef).toLowerCase())
+                    .append(" OR ");
+            isPDBRefsFound = true;
+          }
+          else
+          {
+            seqRefs.add(getDBRefId(dbRef));
+          }
+        }
+      }
+    }
+
+    if (!isPDBRefsFound && !isUniProtRefsFound)
+    {
+      String seqName = seq.getName();
+      seqName = sanitizeSeqName(seqName);
+      String[] names = seqName.toLowerCase().split("\\|");
+      for (String name : names)
+      {
+        // System.out.println("Found name : " + name);
+        name.trim();
+        if (isValidSeqName(name))
+        {
+          seqRefs.add(name);
+        }
+      }
+
+      for (String seqRef : seqRefs)
+      {
+        queryBuilder.append("text:").append(seqRef).append(" OR ");
+      }
+    }
+
+    int endIndex = queryBuilder.lastIndexOf(" OR ");
+    if (queryBuilder.toString().length() < 6)
+    {
+      return null;
+    }
+    String query = queryBuilder.toString().substring(0, endIndex);
+    return query;
+  }
+
+  /**
+   * Remove the following special characters from input string +, -, &, !, (, ),
+   * {, }, [, ], ^, ", ~, *, ?, :, \
+   * 
+   * @param seqName
+   * @return
+   */
+  static String sanitizeSeqName(String seqName)
+  {
+    Objects.requireNonNull(seqName);
+    return seqName.replaceAll("\\[\\d*\\]", "")
+            .replaceAll("[^\\dA-Za-z|_]", "").replaceAll("\\s+", "+");
+  }
+
+
+  /**
+   * Ensures sequence ref names are not less than 3 characters and does not
+   * contain a database name
+   * 
+   * @param seqName
+   * @return
+   */
+  public static boolean isValidSeqName(String seqName)
+  {
+    // System.out.println("seqName : " + seqName);
+    String ignoreList = "pdb,uniprot,swiss-prot";
+    if (seqName.length() < 3)
+    {
+      return false;
+    }
+    if (seqName.contains(":"))
+    {
+      return false;
+    }
+    seqName = seqName.toLowerCase();
+    for (String ignoredEntry : ignoreList.split(","))
+    {
+      if (seqName.contains(ignoredEntry))
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public static String getDBRefId(DBRefEntry dbRef)
+  {
+    String ref = dbRef.getAccessionId().replaceAll("GO:", "");
+    return ref;
+  }
+
+  /**
+   * Filters a given list of discovered structures based on supplied argument
+   * 
+   * @param fieldToFilterBy
+   *          the field to filter by
+   */
+  public void filterResultSet(final String fieldToFilterBy)
+  {
+    Thread filterThread = new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        long startTime = System.currentTimeMillis();
+        pdbRestCleint = PDBFTSRestClient.getInstance();
+        lbl_loading.setVisible(true);
+        Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
+                .getStructureSummaryFields();
+        Collection<FTSData> filteredResponse = new HashSet<FTSData>();
+        HashSet<String> errors = new HashSet<String>();
+
+        for (SequenceI seq : selectedSequences)
+        {
+          FTSRestRequest pdbRequest = new FTSRestRequest();
+          if (fieldToFilterBy.equalsIgnoreCase("uniprot_coverage"))
+          {
+            pdbRequest.setAllowEmptySeq(false);
+            pdbRequest.setResponseSize(1);
+            pdbRequest.setFieldToSearchBy("(");
+            pdbRequest.setSearchTerm(buildQuery(seq) + ")");
+            pdbRequest.setWantedFields(wantedFields);
+            pdbRequest.setAssociatedSequence(seq);
+            pdbRequest.setFacet(true);
+            pdbRequest.setFacetPivot(fieldToFilterBy + ",entry_entity");
+            pdbRequest.setFacetPivotMinCount(1);
+          }
+          else
+          {
+            pdbRequest.setAllowEmptySeq(false);
+            pdbRequest.setResponseSize(1);
+            pdbRequest.setFieldToSearchBy("(");
+            pdbRequest.setFieldToSortBy(fieldToFilterBy,
+                    !chk_invertFilter.isSelected());
+            pdbRequest.setSearchTerm(buildQuery(seq) + ")");
+            pdbRequest.setWantedFields(wantedFields);
+            pdbRequest.setAssociatedSequence(seq);
+          }
+          FTSRestResponse resultList;
+          try
+          {
+            resultList = pdbRestCleint.executeRequest(pdbRequest);
+          } catch (Exception e)
+          {
+            e.printStackTrace();
+            errors.add(e.getMessage());
+            continue;
+          }
+          lastPdbRequest = pdbRequest;
+          if (resultList.getSearchSummary() != null
+                  && !resultList.getSearchSummary().isEmpty())
+          {
+            filteredResponse.addAll(resultList.getSearchSummary());
+          }
+        }
+
+        String totalTime = (System.currentTimeMillis() - startTime)
+                + " milli secs";
+        if (!filteredResponse.isEmpty())
+        {
+          final int filterResponseCount = filteredResponse.size();
+          Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<FTSData>();
+          reorderedStructuresSet.addAll(filteredResponse);
+          reorderedStructuresSet.addAll(discoveredStructuresSet);
+          getResultTable().setModel(
+                  FTSRestResponse.getTableModel(
+                  lastPdbRequest, reorderedStructuresSet));
+
+          FTSRestResponse.configureTableColumn(getResultTable(),
+                  wantedFields);
+          getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
+          getResultTable().getColumn("Ref Sequence").setMinWidth(100);
+          getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
+          // Update table selection model here
+          getResultTable().addRowSelectionInterval(0,
+                  filterResponseCount - 1);
+          mainFrame.setTitle(MessageManager.formatMessage(
+                  "label.structure_chooser_filter_time", totalTime));
+        }
+        else
+        {
+          mainFrame.setTitle(MessageManager.formatMessage(
+                  "label.structure_chooser_filter_time", totalTime));
+          if (errors.size() > 0)
+          {
+            StringBuilder errorMsg = new StringBuilder();
+            for (String error : errors)
+            {
+              errorMsg.append(error).append("\n");
+            }
+            JOptionPane.showMessageDialog(
+                    null,
+                    errorMsg.toString(),
+                    MessageManager.getString("label.pdb_web-service_error"),
+                    JOptionPane.ERROR_MESSAGE);
+          }
+        }
+
+        lbl_loading.setVisible(false);
+
+        validateSelections();
+      }
+    });
+    filterThread.start();
+  }
+
+  /**
+   * Handles action event for btn_pdbFromFile
+   */
+  @Override
+  public void pdbFromFile_actionPerformed()
+  {
+    jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
+            jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
+    chooser.setFileView(new jalview.io.JalviewFileView());
+    chooser.setDialogTitle(MessageManager.formatMessage(
+            "label.select_pdb_file_for",
+            selectedSequence.getDisplayId(false)));
+    chooser.setToolTipText(MessageManager.formatMessage(
+            "label.load_pdb_file_associate_with_sequence",
+            selectedSequence.getDisplayId(false)));
+
+    int value = chooser.showOpenDialog(null);
+    if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
+    {
+      selectedPdbFileName = chooser.getSelectedFile().getPath();
+      jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
+      validateSelections();
+    }
+  }
+
+  /**
+   * Populates the filter combo-box options dynamically depending on discovered
+   * structures
+   */
+  @Override
+  protected void populateFilterComboBox()
+  {
+    if (isStructuresDiscovered())
+    {
+      cmb_filterOption.addItem(new FilterOption("Best Quality",
+              "overall_quality", VIEWS_FILTER));
+      cmb_filterOption.addItem(new FilterOption("Best Resolution",
+              "resolution", VIEWS_FILTER));
+      cmb_filterOption.addItem(new FilterOption("Most Protein Chain",
+              "number_of_protein_chains", VIEWS_FILTER));
+      cmb_filterOption.addItem(new FilterOption("Most Bound Molecules",
+              "number_of_bound_molecules", VIEWS_FILTER));
+      cmb_filterOption.addItem(new FilterOption("Most Polymer Residues",
+              "number_of_polymer_residues", VIEWS_FILTER));
+    }
+    cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
+            VIEWS_ENTER_ID));
+    cmb_filterOption.addItem(new FilterOption("From File", "-",
+            VIEWS_FROM_FILE));
+    cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-",
+            VIEWS_LOCAL_PDB));
+  }
+
+  /**
+   * Updates the displayed view based on the selected filter option
+   */
+  @Override
+  protected void updateCurrentView()
+  {
+    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
+            .getSelectedItem());
+    layout_switchableViews.show(pnl_switchableViews,
+            selectedFilterOpt.getView());
+    String filterTitle = mainFrame.getTitle();
+    mainFrame.setTitle(frameTitle);
+    chk_invertFilter.setVisible(false);
+    if (selectedFilterOpt.getView() == VIEWS_FILTER)
+    {
+      mainFrame.setTitle(filterTitle);
+      chk_invertFilter.setVisible(true);
+      filterResultSet(selectedFilterOpt.getValue());
+    }
+    else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
+            || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
+    {
+      mainFrame.setTitle(MessageManager
+              .getString("label.structure_chooser_manual_association"));
+      idInputAssSeqPanel.loadCmbAssSeq();
+      fileChooserAssSeqPanel.loadCmbAssSeq();
+    }
+    validateSelections();
+  }
+
+  /**
+   * Validates user selection and activates the view button if all parameters
+   * are correct
+   */
+  @Override
+  public void validateSelections()
+  {
+    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
+            .getSelectedItem());
+    btn_view.setEnabled(false);
+    String currentView = selectedFilterOpt.getView();
+    if (currentView == VIEWS_FILTER)
+    {
+      if (getResultTable().getSelectedRows().length > 0)
+      {
+        btn_view.setEnabled(true);
+      }
+    }
+    else if (currentView == VIEWS_LOCAL_PDB)
+    {
+      if (tbl_local_pdb.getSelectedRows().length > 0)
+      {
+        btn_view.setEnabled(true);
+      }
+    }
+    else if (currentView == VIEWS_ENTER_ID)
+    {
+      validateAssociationEnterPdb();
+    }
+    else if (currentView == VIEWS_FROM_FILE)
+    {
+      validateAssociationFromFile();
+    }
+  }
+
+  /**
+   * Validates inputs from the Manual PDB entry panel
+   */
+  public void validateAssociationEnterPdb()
+  {
+    AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
+            .getCmb_assSeq().getSelectedItem();
+    lbl_pdbManualFetchStatus.setIcon(errorImage);
+    lbl_pdbManualFetchStatus.setToolTipText("");
+    if (txt_search.getText().length() > 0)
+    {
+      lbl_pdbManualFetchStatus
+              .setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager
+                      .formatMessage("info.no_pdb_entry_found_for",
+                              txt_search.getText())));
+    }
+
+    if (errorWarning.length() > 0)
+    {
+      lbl_pdbManualFetchStatus.setIcon(warningImage);
+      lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(
+              true, errorWarning.toString()));
+    }
+
+    if (selectedSequences.length == 1
+            || !assSeqOpt.getName().equalsIgnoreCase(
+                    "-Select Associated Seq-"))
+    {
+      txt_search.setEnabled(true);
+      if (isValidPBDEntry)
+      {
+        btn_view.setEnabled(true);
+        lbl_pdbManualFetchStatus.setToolTipText("");
+        lbl_pdbManualFetchStatus.setIcon(goodImage);
+      }
+    }
+    else
+    {
+      txt_search.setEnabled(false);
+      lbl_pdbManualFetchStatus.setIcon(errorImage);
+    }
+  }
+
+  /**
+   * Validates inputs for the manual PDB file selection options
+   */
+  public void validateAssociationFromFile()
+  {
+    AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
+            .getCmb_assSeq().getSelectedItem();
+    lbl_fromFileStatus.setIcon(errorImage);
+    if (selectedSequences.length == 1
+            || (assSeqOpt != null && !assSeqOpt.getName().equalsIgnoreCase(
+                    "-Select Associated Seq-")))
+    {
+      btn_pdbFromFile.setEnabled(true);
+      if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
+      {
+        btn_view.setEnabled(true);
+        lbl_fromFileStatus.setIcon(goodImage);
+      }
+    }
+    else
+    {
+      btn_pdbFromFile.setEnabled(false);
+      lbl_fromFileStatus.setIcon(errorImage);
+    }
+  }
+
+  @Override
+  public void cmbAssSeqStateChanged()
+  {
+    validateSelections();
+  }
+
+  /**
+   * Handles the state change event for the 'filter' combo-box and 'invert'
+   * check-box
+   */
+  @Override
+  protected void stateChanged(ItemEvent e)
+  {
+    if (e.getSource() instanceof JCheckBox)
+    {
+      updateCurrentView();
+    }
+    else
+    {
+      if (e.getStateChange() == ItemEvent.SELECTED)
+      {
+        updateCurrentView();
+      }
+    }
+
+  }
+
+  /**
+   * Handles action event for btn_ok
+   */
+  @Override
+  public void ok_ActionPerformed()
+  {
+    final StructureSelectionManager ssm = ap.getStructureSelectionManager();
+    new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+    FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
+            .getSelectedItem());
+    String currentView = selectedFilterOpt.getView();
+    if (currentView == VIEWS_FILTER)
+    {
+          int pdbIdColIndex = getResultTable().getColumn("PDB Id")
+                  .getModelIndex();
+          int refSeqColIndex = getResultTable().getColumn("Ref Sequence")
+              .getModelIndex();
+          int[] selectedRows = getResultTable().getSelectedRows();
+      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
+      int count = 0;
+      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
+      for (int row : selectedRows)
+      {
+            String pdbIdStr = getResultTable().getValueAt(row,
+                    pdbIdColIndex)
+                .toString();
+            SequenceI selectedSeq = (SequenceI) getResultTable()
+                    .getValueAt(row,
+                refSeqColIndex);
+        selectedSeqsToView.add(selectedSeq);
+            PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
+            if (pdbEntry == null)
+            {
+              pdbEntry = getFindEntry(pdbIdStr,
+                      selectedSeq.getAllPDBEntries());
+            }
+        if (pdbEntry == null)
+        {
+          pdbEntry = new PDBEntry();
+          pdbEntry.setId(pdbIdStr);
+          pdbEntry.setType(PDBEntry.Type.PDB);
+          selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
+        }
+        pdbEntriesToView[count++] = pdbEntry;
+      }
+      SequenceI[] selectedSeqs = selectedSeqsToView
+              .toArray(new SequenceI[selectedSeqsToView.size()]);
+          launchStructureViewer(ssm, pdbEntriesToView, ap,
+                  selectedSeqs);
+    }
+    else if (currentView == VIEWS_LOCAL_PDB)
+    {
+      int[] selectedRows = tbl_local_pdb.getSelectedRows();
+      PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
+      int count = 0;
+          int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
+                  .getModelIndex();
+      int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
+              .getModelIndex();
+      ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
+      for (int row : selectedRows)
+      {
+        PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
+                pdbIdColIndex);
+        pdbEntriesToView[count++] = pdbEntry;
+        SequenceI selectedSeq = (SequenceI) tbl_local_pdb.getValueAt(row,
+                refSeqColIndex);
+        selectedSeqsToView.add(selectedSeq);
+      }
+      SequenceI[] selectedSeqs = selectedSeqsToView
+              .toArray(new SequenceI[selectedSeqsToView.size()]);
+          launchStructureViewer(ssm, pdbEntriesToView, ap,
+                  selectedSeqs);
+    }
+    else if (currentView == VIEWS_ENTER_ID)
+    {
+      SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
+              .getCmb_assSeq().getSelectedItem()).getSequence();
+      if (userSelectedSeq != null)
+      {
+        selectedSequence = userSelectedSeq;
+      }
+
+      String pdbIdStr = txt_search.getText();
+      PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
+      if (pdbEntry == null)
+      {
+        pdbEntry = new PDBEntry();
+            if (pdbIdStr.split(":").length > 1)
+            {
+              pdbEntry.setChainCode(pdbIdStr.split(":")[1]);
+            }
+        pdbEntry.setId(pdbIdStr);
+        pdbEntry.setType(PDBEntry.Type.PDB);
+        selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
+      }
+
+      PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
+          launchStructureViewer(ssm, pdbEntriesToView, ap,
+                  new SequenceI[] { selectedSequence });
+    }
+    else if (currentView == VIEWS_FROM_FILE)
+    {
+      SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
+              .getCmb_assSeq().getSelectedItem()).getSequence();
+      if (userSelectedSeq != null)
+      {
+        selectedSequence = userSelectedSeq;
+      }
+      PDBEntry fileEntry = new AssociatePdbFileWithSeq()
+              .associatePdbWithSeq(selectedPdbFileName,
+                      jalview.io.AppletFormatAdapter.FILE,
+                      selectedSequence, true, Desktop.instance);
+
+          launchStructureViewer(ssm,
+                  new PDBEntry[] { fileEntry }, ap,
+                  new SequenceI[] { selectedSequence });
+    }
+    mainFrame.dispose();
+      }
+    }).start();
+  }
+
+  private PDBEntry getFindEntry(String id, Vector<PDBEntry> pdbEntries)
+  {
+    Objects.requireNonNull(id);
+    Objects.requireNonNull(pdbEntries);
+    PDBEntry foundEntry = null;
+    for (PDBEntry entry : pdbEntries)
+    {
+      if (entry.getId().equalsIgnoreCase(id))
+      {
+        return entry;
+      }
+    }
+    return foundEntry;
+  }
+
+  private void launchStructureViewer(StructureSelectionManager ssm,
+          final PDBEntry[] pdbEntriesToView,
+          final AlignmentPanel alignPanel, SequenceI[] sequences)
+  {
+    long progressId = sequences.hashCode();
+    setProgressBar(MessageManager
+            .getString("status.launching_3d_structure_viewer"), progressId);
+    final StructureViewer sViewer = new StructureViewer(ssm);
+    setProgressBar(null, progressId);
+
+    if (SiftsSettings.isMapWithSifts())
+    {
+      // TODO: prompt user if there are lots of sequences without dbrefs.
+      // It can take a long time if we need to fetch all dbrefs for all
+      // sequences!
+      ArrayList<SequenceI> seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
+      for (SequenceI seq : sequences)
+      {
+        if (seq.getSourceDBRef() == null && seq.getDBRefs() == null)
+        {
+            seqsWithoutSourceDBRef.add(seq);
+            continue;
+          }
+      }
+      if (!seqsWithoutSourceDBRef.isEmpty())
+      {
+        int y = seqsWithoutSourceDBRef.size();
+        setProgressBar(MessageManager.formatMessage(
+                "status.fetching_dbrefs_for_sequences_without_valid_refs",
+                y,
+                progressId);
+        SequenceI[] seqWithoutSrcDBRef = new SequenceI[y];
+        int x = 0;
+        for (SequenceI fSeq : seqsWithoutSourceDBRef)
+        {
+          seqWithoutSrcDBRef[x++] = fSeq;
+        }
+        new DBRefFetcher(seqWithoutSrcDBRef).fetchDBRefs(true);
+        setProgressBar("Fetch complete.", progressId); // todo i18n
+      }
+    }
+    if (pdbEntriesToView.length > 1)
+    {
+      ArrayList<SequenceI[]> seqsMap = new ArrayList<SequenceI[]>();
+      for (SequenceI seq : sequences)
+      {
+        seqsMap.add(new SequenceI[] { seq });
+      }
+      SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
+<<<<<<< Updated upstream
+      ssm.setProgressBar(null);
+      ssm.setProgressBar(MessageManager
+              .getString("status.fetching_3d_structures_for_selected_entries"));
+=======
+      setProgressBar("Fetching structure data", progressId);
+>>>>>>> Stashed changes
+      sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
+    }
+    else
+    {
+<<<<<<< Updated upstream
+      ssm.setProgressBar(null);
+      ssm.setProgressBar(MessageManager.formatMessage(
+              "status.fetching_3d_structures_for",
+              pdbEntriesToView[0].getId()));
+=======
+      setProgressBar(
+              "Fetching structure for " + pdbEntriesToView[0].getId(),
+              progressId);
+>>>>>>> Stashed changes
+      sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
+    }
+    setProgressBar(null, progressId);
+  }
+
+  /**
+   * Populates the combo-box used in associating manually fetched structures to
+   * a unique sequence when more than one sequence selection is made.
+   */
+  @Override
+  public void populateCmbAssociateSeqOptions(
+          JComboBox<AssociateSeqOptions> cmb_assSeq, JLabel lbl_associateSeq)
+  {
+    cmb_assSeq.removeAllItems();
+    cmb_assSeq.addItem(new AssociateSeqOptions("-Select Associated Seq-",
+            null));
+    lbl_associateSeq.setVisible(false);
+    if (selectedSequences.length > 1)
+    {
+      for (SequenceI seq : selectedSequences)
+      {
+        cmb_assSeq.addItem(new AssociateSeqOptions(seq));
+      }
+    }
+    else
+    {
+      String seqName = selectedSequence.getDisplayId(false);
+      seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
+      lbl_associateSeq.setText(seqName);
+      lbl_associateSeq.setVisible(true);
+      cmb_assSeq.setVisible(false);
+    }
+  }
+
+  public boolean isStructuresDiscovered()
+  {
+    return structuresDiscovered;
+  }
+
+  public void setStructuresDiscovered(boolean structuresDiscovered)
+  {
+    this.structuresDiscovered = structuresDiscovered;
+  }
+
+  public Collection<FTSData> getDiscoveredStructuresSet()
+  {
+    return discoveredStructuresSet;
+  }
+
+  @Override
+  protected void txt_search_ActionPerformed()
+  {
+    new Thread()
+    {
+      @Override
+      public void run()
+      {
+        errorWarning.setLength(0);
+        isValidPBDEntry = false;
+        if (txt_search.getText().length() > 0)
+        {
+          String searchTerm = txt_search.getText().toLowerCase();
+          searchTerm = searchTerm.split(":")[0];
+          // System.out.println(">>>>> search term : " + searchTerm);
+          List<FTSDataColumnI> wantedFields = new ArrayList<FTSDataColumnI>();
+          FTSRestRequest pdbRequest = new FTSRestRequest();
+          pdbRequest.setAllowEmptySeq(false);
+          pdbRequest.setResponseSize(1);
+          pdbRequest.setFieldToSearchBy("(pdb_id:");
+          pdbRequest.setWantedFields(wantedFields);
+          pdbRequest
+.setSearchTerm(searchTerm + ")");
+          pdbRequest.setAssociatedSequence(selectedSequence);
+          pdbRestCleint = PDBFTSRestClient.getInstance();
+          wantedFields.add(pdbRestCleint.getPrimaryKeyColumn());
+          FTSRestResponse resultList;
+          try
+          {
+            resultList = pdbRestCleint.executeRequest(pdbRequest);
+          } catch (Exception e)
+          {
+            errorWarning.append(e.getMessage());
+            return;
+          } finally
+          {
+            validateSelections();
+          }
+          if (resultList.getSearchSummary() != null
+                  && resultList.getSearchSummary().size() > 0)
+          {
+            isValidPBDEntry = true;
+          }
+        }
+        validateSelections();
+      }
+    }.start();
+  }
+
+  @Override
+  public void tabRefresh()
+  {
+    if (selectedSequences != null)
+    {
+      Thread refreshThread = new Thread(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          fetchStructuresMetaData();
+          filterResultSet(((FilterOption) cmb_filterOption
+                  .getSelectedItem()).getValue());
+        }
+      });
+      refreshThread.start();
+    }
+  }
+
+  public class PDBEntryTableModel extends AbstractTableModel
+  {
+    String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type", "File" };
+
+    private List<CachedPDB> pdbEntries;
+
+    public PDBEntryTableModel(List<CachedPDB> pdbEntries)
+    {
+      this.pdbEntries = new ArrayList<CachedPDB>(pdbEntries);
+    }
+
+    @Override
+    public String getColumnName(int columnIndex)
+    {
+      return columns[columnIndex];
+    }
+
+    @Override
+    public int getRowCount()
+    {
+      return pdbEntries.size();
+    }
+
+    @Override
+    public int getColumnCount()
+    {
+      return columns.length;
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column)
+    {
+      return false;
+    }
+
+    @Override
+    public Object getValueAt(int rowIndex, int columnIndex)
+    {
+      Object value = "??";
+      CachedPDB entry = pdbEntries.get(rowIndex);
+      switch (columnIndex)
+      {
+      case 0:
+        value = entry.getSequence();
+        break;
+      case 1:
+        value = entry.getPdbEntry();
+        break;
+      case 2:
+        value = entry.getPdbEntry().getChainCode() == null ? "_" : entry
+                .getPdbEntry().getChainCode();
+        break;
+      case 3:
+        value = entry.getPdbEntry().getType();
+        break;
+      case 4:
+        value = entry.getPdbEntry().getFile();
+        break;
+      }
+      return value;
+    }
+
+    @Override
+    public Class<?> getColumnClass(int columnIndex)
+    {
+      return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
+    }
+
+    public CachedPDB getPDBEntryAt(int row)
+    {
+      return pdbEntries.get(row);
+    }
+
+  }
+
+  private class CachedPDB
+  {
+    private SequenceI sequence;
+
+    private PDBEntry pdbEntry;
+
+    public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
+    {
+      this.sequence = sequence;
+      this.pdbEntry = pdbEntry;
+    }
+
+    public SequenceI getSequence()
+    {
+      return sequence;
+    }
+
+    public PDBEntry getPdbEntry()
+    {
+      return pdbEntry;
+    }
+
+  }
+
+  private IProgressIndicator progressBar;
+
+  @Override
+  public void setProgressBar(String message, long id)
+  {
+    progressBar.setProgressBar(message, id);
+  }
+
+  @Override
+  public void registerHandler(long id, IProgressIndicatorHandler handler)
+  {
+    progressBar.registerHandler(id, handler);
+  }
+
+  @Override
+  public boolean operationInProgress()
+  {
+    return progressBar.operationInProgress();
+  }
+}
index 34ad659..e73ce07 100644 (file)
@@ -23,7 +23,7 @@ package jalview.gui;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.gui.StructureViewer.ViewerType;
@@ -110,6 +110,8 @@ public abstract class StructureViewerBase extends GStructureViewer
 
   protected boolean allChainsSelected = false;
 
+  protected JMenu viewSelectionMenu;
+
   /**
    * Default constructor
    */
@@ -744,17 +746,17 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void itemStateChanged(ItemEvent e)
       {
-        alignStructs.setEnabled(_alignwith.size() > 0);
+        alignStructs.setEnabled(!_alignwith.isEmpty());
         alignStructs.setToolTipText(MessageManager.formatMessage(
                 "label.align_structures_using_linked_alignment_views",
-                new String[] { String.valueOf(_alignwith.size()) }));
+                _alignwith.size()));
       }
     };
-    JMenu alpanels = new ViewSelectionMenu(
+    viewSelectionMenu = new ViewSelectionMenu(
             MessageManager.getString("label.superpose_with"), this,
             _alignwith, handler);
     handler.itemStateChanged(null);
-    viewerActionMenu.add(alpanels);
+    viewerActionMenu.add(viewSelectionMenu, 0);
     viewerActionMenu.addMenuListener(new MenuListener()
     {
       @Override
@@ -781,16 +783,24 @@ public abstract class StructureViewerBase extends GStructureViewer
   public void setJalviewColourScheme(ColourSchemeI cs) {
     getBinding().setJalviewColourScheme(cs);
   }
+
+  /**
+   * Sends commands to the structure viewer to superimpose structures based on
+   * currently associated alignments. May optionally return an error message for
+   * the operation.
+   */
   @Override
-  protected void alignStructs_actionPerformed(ActionEvent actionEvent)
+  protected String alignStructs_actionPerformed(
+          ActionEvent actionEvent)
   {
-    alignStructs_withAllAlignPanels();
+    return alignStructs_withAllAlignPanels();
   }
-  protected void alignStructs_withAllAlignPanels()
+
+  protected String alignStructs_withAllAlignPanels()
   {
     if (getAlignmentPanel() == null)
     {
-      return;
+      return null;
     }
   
     if (_alignwith.size() == 0)
@@ -798,10 +808,11 @@ public abstract class StructureViewerBase extends GStructureViewer
       _alignwith.add(getAlignmentPanel());
     }
   
+    String reply = null;
     try
     {
       AlignmentI[] als = new Alignment[_alignwith.size()];
-      ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
+      HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
       int[] alm = new int[_alignwith.size()];
       int a = 0;
   
@@ -809,9 +820,15 @@ public abstract class StructureViewerBase extends GStructureViewer
       {
         als[a] = ap.av.getAlignment();
         alm[a] = -1;
-        alc[a++] = ap.av.getColumnSelection();
+        alc[a++] = ap.av.getAlignment().getHiddenColumns();
+      }
+      reply = getBinding().superposeStructures(als, alm, alc);
+      if (reply != null)
+      {
+        String text = MessageManager.formatMessage(
+                "error.superposition_failed", reply);
+        statusBar.setText(text);
       }
-      getBinding().superposeStructures(als, alm, alc);
     } catch (Exception e)
     {
       StringBuffer sp = new StringBuffer();
@@ -822,7 +839,9 @@ public abstract class StructureViewerBase extends GStructureViewer
       Cache.log.info("Couldn't align structures with the " + sp.toString()
               + "associated alignment panels.", e);
     }
+    return reply;
   }
+
   @Override
   public void background_actionPerformed(ActionEvent actionEvent)
   {
@@ -900,7 +919,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       {
         // TODO: cope with multiple PDB files in view
         in = new BufferedReader(
-                new FileReader(getBinding().getPdbFile()[0]));
+                new FileReader(getBinding().getStructureFiles()[0]));
         File outFile = chooser.getSelectedFile();
   
         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
@@ -952,6 +971,10 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   protected abstract String getViewerName();
+
+  /**
+   * Configures the title and menu items of the viewer panel.
+   */
   public void updateTitleAndMenus()
   {
     AAStructureBindingModel binding = getBinding();
@@ -963,10 +986,30 @@ public abstract class StructureViewerBase extends GStructureViewer
     setChainMenuItems(binding.getChainNames());
   
     this.setTitle(binding.getViewerTitle(getViewerName(), true));
-    if (binding.getPdbFile().length > 1 && binding.getSequence().length > 1)
+
+    /*
+     * enable 'Superpose with' if more than one mapped structure
+     */
+    viewSelectionMenu.setEnabled(false);
+    if (getBinding().getStructureFiles().length > 1
+            && getBinding().getSequence().length > 1)
     {
-      viewerActionMenu.setVisible(true);
+      viewSelectionMenu.setEnabled(true);
     }
+
+    /*
+     * Show action menu if it has any enabled items
+     */
+    viewerActionMenu.setVisible(false);
+    for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
+    {
+      if (viewerActionMenu.getItem(i).isEnabled())
+      {
+        viewerActionMenu.setVisible(true);
+        break;
+      }
+    }
+
     if (!binding.isLoadingFromArchive())
     {
       seqColour_actionPerformed(null);
index 49fdaf7..91e05c6 100644 (file)
@@ -28,11 +28,12 @@ import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.swing.BorderFactory;
 import javax.swing.JColorChooser;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JSlider;
 import javax.swing.event.ChangeEvent;
@@ -44,36 +45,43 @@ public class TextColourChooser
 
   SequenceGroup sg;
 
-  public void chooseColour(AlignmentPanel ap, SequenceGroup sg)
+  Color original1, original2;
+
+  int originalThreshold;
+
+  Map<SequenceGroup, Color> groupColour1;
+
+  Map<SequenceGroup, Color> groupColour2;
+
+  Map<SequenceGroup, Integer> groupThreshold;
+
+  /**
+   * Show a dialogue which allows the user to select two text colours and adjust
+   * a slider for the cross-over point
+   * 
+   * @param alignPanel
+   *          the AlignmentPanel context
+   * @param sequenceGroup
+   *          the SequenceGroup context (only for group pop-menu option)
+   */
+  public void chooseColour(AlignmentPanel alignPanel, SequenceGroup sequenceGroup)
   {
-    this.ap = ap;
-    this.sg = sg;
+    this.ap = alignPanel;
+    this.sg = sequenceGroup;
 
-    int original1, original2, originalThreshold;
-    if (sg == null)
-    {
-      original1 = ap.av.getTextColour().getRGB();
-      original2 = ap.av.getTextColour2().getRGB();
-      originalThreshold = ap.av.getThresholdTextColour();
-    }
-    else
-    {
-      original1 = sg.textColour.getRGB();
-      original2 = sg.textColour2.getRGB();
-      originalThreshold = sg.thresholdTextColour;
-    }
+    saveInitialSettings();
 
     final JSlider slider = new JSlider(0, 750, originalThreshold);
     final JPanel col1 = new JPanel();
     col1.setPreferredSize(new Dimension(40, 20));
     col1.setBorder(BorderFactory.createEtchedBorder());
     col1.setToolTipText(MessageManager.getString("label.dark_colour"));
-    col1.setBackground(new Color(original1));
+    col1.setBackground(original1);
     final JPanel col2 = new JPanel();
     col2.setPreferredSize(new Dimension(40, 20));
     col2.setBorder(BorderFactory.createEtchedBorder());
     col2.setToolTipText(MessageManager.getString("label.light_colour"));
-    col2.setBackground(new Color(original2));
+    col2.setBackground(original2);
     final JPanel bigpanel = new JPanel(new BorderLayout());
     JPanel panel = new JPanel();
     bigpanel.add(panel, BorderLayout.CENTER);
@@ -130,7 +138,7 @@ public class TextColourChooser
 
     int reply = JvOptionPane
             .showInternalOptionDialog(
-                    ap,
+                    alignPanel,
                     bigpanel,
                     MessageManager
                             .getString("label.adjunst_foreground_text_colour_threshold"),
@@ -139,19 +147,81 @@ public class TextColourChooser
 
     if (reply == JvOptionPane.CANCEL_OPTION)
     {
-      if (sg == null)
-      {
-        ap.av.setTextColour(new Color(original1));
-        ap.av.setTextColour2(new Color(original2));
-        ap.av.setThresholdTextColour(originalThreshold);
-      }
-      else
+      restoreInitialSettings();
+    }
+  }
+
+  /**
+   * Restore initial settings on Cancel
+   */
+  protected void restoreInitialSettings()
+  {
+    if (sg == null)
+    {
+      ap.av.setTextColour(original1);
+      ap.av.setTextColour2(original2);
+      ap.av.setThresholdTextColour(originalThreshold);
+    }
+    else
+    {
+      sg.textColour = original1;
+      sg.textColour2 = original2;
+      sg.thresholdTextColour = originalThreshold;
+    }
+
+    /*
+     * if 'Apply To All Groups' was in force, there will be 
+     * group-specific settings to restore as well
+     */
+    for (SequenceGroup group : this.groupColour1.keySet())
+    {
+      group.textColour = groupColour1.get(group);
+      group.textColour2 = groupColour2.get(group);
+      group.thresholdTextColour = groupThreshold.get(group);
+    }
+  }
+
+  /**
+   * Save settings on entry, for restore on Cancel
+   */
+  protected void saveInitialSettings()
+  {
+    groupColour1 = new HashMap<SequenceGroup, Color>();
+    groupColour2 = new HashMap<SequenceGroup, Color>();
+    groupThreshold = new HashMap<SequenceGroup, Integer>();
+
+    if (sg == null)
+    {
+      /*
+       * alignment scope
+       */
+      original1 = ap.av.getTextColour();
+      original2 = ap.av.getTextColour2();
+      originalThreshold = ap.av.getThresholdTextColour();
+      if (ap.av.getColourAppliesToAllGroups()
+              && ap.av.getAlignment().getGroups() != null)
       {
-        sg.textColour = new Color(original1);
-        sg.textColour2 = new Color(original2);
-        sg.thresholdTextColour = originalThreshold;
+        /*
+         * if applying changes to all groups, need to be able to 
+         * restore group settings as well
+         */
+        for (SequenceGroup group : ap.av.getAlignment().getGroups())
+        {
+          groupColour1.put(group, group.textColour);
+          groupColour2.put(group, group.textColour2);
+          groupThreshold.put(group, group.thresholdTextColour);
+        }
       }
     }
+    else
+    {
+      /*
+       * Sequence group scope
+       */
+      original1 = sg.textColour;
+      original2 = sg.textColour2;
+      originalThreshold = sg.thresholdTextColour;
+    }
   }
 
   void colour1Changed(Color col)
@@ -215,11 +285,11 @@ public class TextColourChooser
       return;
     }
 
-    for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+    for (SequenceGroup group : ap.av.getAlignment().getGroups())
     {
-      sg.textColour = ap.av.getTextColour();
-      sg.textColour2 = ap.av.getTextColour2();
-      sg.thresholdTextColour = ap.av.getThresholdTextColour();
+      group.textColour = ap.av.getTextColour();
+      group.textColour2 = ap.av.getTextColour2();
+      group.thresholdTextColour = ap.av.getThresholdTextColour();
     }
   }
 
index 54eed1a..e60ac8e 100755 (executable)
@@ -21,7 +21,7 @@
 package jalview.gui;
 
 import jalview.analysis.Conservation;
-import jalview.analysis.NJTree;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
@@ -53,6 +53,7 @@ import java.awt.print.PrinterException;
 import java.awt.print.PrinterJob;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Vector;
 
 import javax.swing.JColorChooser;
@@ -73,7 +74,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
   /** DOCUMENT ME!! */
   public static final String PLACEHOLDER = " * ";
 
-  NJTree tree;
+  TreeModel tree;
 
   JScrollPane scrollPane;
 
@@ -168,7 +169,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    * @param tree
    *          DOCUMENT ME!
    */
-  public void setTree(NJTree tree)
+  public void setTree(TreeModel tree)
   {
     this.tree = tree;
     tree.findHeight(tree.getTopNode());
@@ -207,7 +208,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    *          DOCUMENT ME!
    * @param chunk
    *          DOCUMENT ME!
-   * @param scale
+   * @param wscale
    *          DOCUMENT ME!
    * @param width
    *          DOCUMENT ME!
@@ -217,7 +218,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    *          DOCUMENT ME!
    */
   public void drawNode(Graphics g, SequenceNode node, float chunk,
-          float scale, int width, int offx, int offy)
+          double wscale, int width, int offx, int offy)
   {
     if (node == null)
     {
@@ -227,11 +228,11 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     if ((node.left() == null) && (node.right() == null))
     {
       // Drawing leaf node
-      float height = node.height;
-      float dist = node.dist;
+      double height = node.height;
+      double dist = node.dist;
 
-      int xstart = (int) ((height - dist) * scale) + offx;
-      int xend = (int) (height * scale) + offx;
+      int xstart = (int) ((height - dist) * wscale) + offx;
+      int xend = (int) (height * wscale) + offx;
 
       int ypos = (int) (node.ycount * chunk) + offy;
 
@@ -306,16 +307,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
+      drawNode(g, (SequenceNode) node.left(), chunk, wscale, width, offx,
               offy);
-      drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
+      drawNode(g, (SequenceNode) node.right(), chunk, wscale, width, offx,
               offy);
 
-      float height = node.height;
-      float dist = node.dist;
+      double height = node.height;
+      double dist = node.dist;
 
-      int xstart = (int) ((height - dist) * scale) + offx;
-      int xend = (int) (height * scale) + offx;
+      int xstart = (int) ((height - dist) * wscale) + offx;
+      int xend = (int) (height * wscale) + offx;
       int ypos = (int) (node.ycount * chunk) + offy;
 
       g.setColor(node.color.darker());
@@ -331,16 +332,17 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         g.fillRect(xend - 2, ypos - 2, 4, 4);
       }
 
-      int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
-              + offy;
-      int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
+      int ystart = (node.left() == null ? 0 : (int) (((SequenceNode) node
+              .left()).ycount * chunk)) + offy;
+      int yend = (node.right() == null ? 0 : (int) (((SequenceNode) node
+              .right()).ycount * chunk))
               + offy;
 
       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
       nodeHash.put(node, pos);
 
-      g.drawLine((int) (height * scale) + offx, ystart,
-              (int) (height * scale) + offx, yend);
+      g.drawLine((int) (height * wscale) + offx, ystart,
+              (int) (height * wscale) + offx, yend);
 
       String nodeLabel = "";
 
@@ -422,7 +424,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
     SequenceNode top = tree.getTopNode();
 
-    float wscale = (float) ((width * .8) - (offx * 2))
+    double wscale = ((width * .8) - (offx * 2))
             / tree.getMaxHeight();
 
     if (top.count == 0)
@@ -445,7 +447,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    *          DOCUMENT ME!
    * @param chunk
    *          DOCUMENT ME!
-   * @param scale
+   * @param wscale
    *          DOCUMENT ME!
    * @param width
    *          DOCUMENT ME!
@@ -455,7 +457,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    *          DOCUMENT ME!
    */
   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
-          float scale, int width, int offx, int offy)
+          double wscale, int width, int offx, int offy)
   {
     if (node == null)
     {
@@ -464,11 +466,11 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
     if ((node.left() == null) && (node.right() == null))
     {
-      float height = node.height;
-      float dist = node.dist;
+      double height = node.height;
+      double dist = node.dist;
 
-      int xstart = (int) ((height - dist) * scale) + offx;
-      int xend = (int) (height * scale) + offx;
+      int xstart = (int) ((height - dist) * wscale) + offx;
+      int xend = (int) (height * wscale) + offx;
 
       int ypos = (int) (node.ycount * chunk) + offy;
 
@@ -488,9 +490,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
+      pickNode(pickBox, (SequenceNode) node.left(), chunk, wscale, width,
               offx, offy);
-      pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
+      pickNode(pickBox, (SequenceNode) node.right(), chunk, wscale, width,
               offx, offy);
     }
   }
@@ -549,7 +551,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
   public void run()
   {
     PrinterJob printJob = PrinterJob.getPrinterJob();
-    PageFormat pf = printJob.pageDialog(printJob.defaultPage());
+    PageFormat defaultPage = printJob.defaultPage();
+    PageFormat pf = printJob.pageDialog(defaultPage);
+
+    if (defaultPage == pf)
+    {
+      /*
+       * user cancelled
+       */
+      return;
+    }
 
     printJob.setPrintable(this, pf);
 
@@ -715,7 +726,8 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
     labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
 
-    float wscale = (width - labelLength - (offx * 2)) / tree.getMaxHeight();
+    double wscale = (width - labelLength - (offx * 2))
+            / tree.getMaxHeight();
 
     SequenceNode top = tree.getTopNode();
 
@@ -928,8 +940,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         threshold = (float) (x - offx)
                 / (float) (getWidth() - labelLength - (2 * offx));
 
-        tree.getGroups().removeAllElements();
-        tree.groupNodes(tree.getTopNode(), threshold);
+        List<SequenceNode> groups = tree.groupNodes(threshold);
         setColor(tree.getTopNode(), Color.black);
 
         AlignmentPanel[] aps = getAssociatedPanels();
@@ -949,7 +960,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
             aps[a].av.getCodingComplement().clearSequenceColours();
           }
         }
-        colourGroups();
+        colourGroups(groups);
       }
 
       PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
@@ -958,17 +969,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
   }
 
-  void colourGroups()
+  void colourGroups(List<SequenceNode> groups)
   {
     AlignmentPanel[] aps = getAssociatedPanels();
-    for (int i = 0; i < tree.getGroups().size(); i++)
+    for (int i = 0; i < groups.size(); i++)
     {
       Color col = new Color((int) (Math.random() * 255),
               (int) (Math.random() * 255), (int) (Math.random() * 255));
-      setColor(tree.getGroups().elementAt(i), col.brighter());
+      setColor(groups.get(i), col.brighter());
 
-      Vector<SequenceNode> l = tree.findLeaves(tree.getGroups()
-              .elementAt(i));
+      Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
 
       Vector<SequenceI> sequences = new Vector<SequenceI>();
 
diff --git a/src/jalview/gui/TreeCanvas.java~ b/src/jalview/gui/TreeCanvas.java~
new file mode 100755 (executable)
index 0000000..90c74be
--- /dev/null
@@ -0,0 +1,1080 @@
+/*
+ * 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.gui;
+
+import jalview.analysis.Conservation;
+import jalview.analysis.NJTree;
+import jalview.api.AlignViewportI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.schemes.ResidueProperties;
+import jalview.schemes.UserColourScheme;
+import jalview.structure.SelectionSource;
+import jalview.util.Format;
+import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JColorChooser;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+
+/**
+ * DOCUMENT ME!
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class TreeCanvas extends JPanel implements MouseListener, Runnable,
+        Printable, MouseMotionListener, SelectionSource
+{
+  /** DOCUMENT ME!! */
+  public static final String PLACEHOLDER = " * ";
+
+  NJTree tree;
+
+  JScrollPane scrollPane;
+
+  TreePanel tp;
+
+  AlignViewport av;
+
+  AlignmentPanel ap;
+
+  Font font;
+
+  FontMetrics fm;
+
+  boolean fitToWindow = true;
+
+  boolean showDistances = false;
+
+  boolean showBootstrap = false;
+
+  boolean markPlaceholders = false;
+
+  int offx = 20;
+
+  int offy;
+
+  float threshold;
+
+  String longestName;
+
+  int labelLength = -1;
+
+  Hashtable nameHash = new Hashtable();
+
+  Hashtable nodeHash = new Hashtable();
+
+  SequenceNode highlightNode;
+
+  boolean applyToAllViews = false;
+
+  /**
+   * Creates a new TreeCanvas object.
+   * 
+   * @param av
+   *          DOCUMENT ME!
+   * @param tree
+   *          DOCUMENT ME!
+   * @param scroller
+   *          DOCUMENT ME!
+   * @param label
+   *          DOCUMENT ME!
+   */
+  public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
+  {
+    this.tp = tp;
+    this.av = ap.av;
+    this.ap = ap;
+    font = av.getFont();
+    scrollPane = scroller;
+    addMouseListener(this);
+    addMouseMotionListener(this);
+    ToolTipManager.sharedInstance().registerComponent(this);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param sequence
+   *          DOCUMENT ME!
+   */
+  public void treeSelectionChanged(SequenceI sequence)
+  {
+    AlignmentPanel[] aps = getAssociatedPanels();
+
+    for (int a = 0; a < aps.length; a++)
+    {
+      SequenceGroup selected = aps[a].av.getSelectionGroup();
+
+      if (selected == null)
+      {
+        selected = new SequenceGroup();
+        aps[a].av.setSelectionGroup(selected);
+      }
+
+      selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
+      selected.addOrRemove(sequence, true);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param tree
+   *          DOCUMENT ME!
+   */
+  public void setTree(NJTree tree)
+  {
+    this.tree = tree;
+    tree.findHeight(tree.getTopNode());
+
+    // Now have to calculate longest name based on the leaves
+    Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
+    boolean has_placeholders = false;
+    longestName = "";
+
+    for (int i = 0; i < leaves.size(); i++)
+    {
+      SequenceNode lf = (SequenceNode) leaves.elementAt(i);
+
+      if (lf.isPlaceholder())
+      {
+        has_placeholders = true;
+      }
+
+      if (longestName.length() < ((Sequence) lf.element()).getName()
+              .length())
+      {
+        longestName = TreeCanvas.PLACEHOLDER
+                + ((Sequence) lf.element()).getName();
+      }
+    }
+
+    setMarkPlaceholders(has_placeholders);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param node
+   *          DOCUMENT ME!
+   * @param chunk
+   *          DOCUMENT ME!
+   * @param scale
+   *          DOCUMENT ME!
+   * @param width
+   *          DOCUMENT ME!
+   * @param offx
+   *          DOCUMENT ME!
+   * @param offy
+   *          DOCUMENT ME!
+   */
+  public void drawNode(Graphics g, SequenceNode node, float chunk,
+          float scale, int width, int offx, int offy)
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    if ((node.left() == null) && (node.right() == null))
+    {
+      // Drawing leaf node
+      float height = node.height;
+      float dist = node.dist;
+
+      int xstart = (int) ((height - dist) * scale) + offx;
+      int xend = (int) (height * scale) + offx;
+
+      int ypos = (int) (node.ycount * chunk) + offy;
+
+      if (node.element() instanceof SequenceI)
+      {
+        SequenceI seq = (SequenceI) node.element();
+
+        if (av.getSequenceColour(seq) == Color.white)
+        {
+          g.setColor(Color.black);
+        }
+        else
+        {
+          g.setColor(av.getSequenceColour(seq).darker());
+        }
+      }
+      else
+      {
+        g.setColor(Color.black);
+      }
+
+      // Draw horizontal line
+      g.drawLine(xstart, ypos, xend, ypos);
+
+      String nodeLabel = "";
+
+      if (showDistances && (node.dist > 0))
+      {
+        nodeLabel = new Format("%-.2f").form(node.dist);
+      }
+
+      if (showBootstrap && node.bootstrap > -1)
+      {
+        if (showDistances)
+        {
+          nodeLabel = nodeLabel + " : ";
+        }
+
+        nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
+      }
+
+      if (!nodeLabel.equals(""))
+      {
+        g.drawString(nodeLabel, xstart + 2, ypos - 2);
+      }
+
+      String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
+              .getName()) : node.getName();
+
+      int charWidth = fm.stringWidth(name) + 3;
+      int charHeight = font.getSize();
+
+      Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
+              charWidth, charHeight);
+
+      nameHash.put(node.element(), rect);
+
+      // Colour selected leaves differently
+      SequenceGroup selected = av.getSelectionGroup();
+
+      if ((selected != null)
+              && selected.getSequences(null).contains(node.element()))
+      {
+        g.setColor(Color.gray);
+
+        g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
+        g.setColor(Color.white);
+      }
+
+      g.drawString(name, xend + 10, ypos + fm.getDescent());
+      g.setColor(Color.black);
+    }
+    else
+    {
+      drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
+              offy);
+      drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
+              offy);
+
+      float height = node.height;
+      float dist = node.dist;
+
+      int xstart = (int) ((height - dist) * scale) + offx;
+      int xend = (int) (height * scale) + offx;
+      int ypos = (int) (node.ycount * chunk) + offy;
+
+      g.setColor(node.color.darker());
+
+      // Draw horizontal line
+      g.drawLine(xstart, ypos, xend, ypos);
+      if (node == highlightNode)
+      {
+        g.fillRect(xend - 3, ypos - 3, 6, 6);
+      }
+      else
+      {
+        g.fillRect(xend - 2, ypos - 2, 4, 4);
+      }
+
+      int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
+              + offy;
+      int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
+              + offy;
+
+      Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
+      nodeHash.put(node, pos);
+
+      g.drawLine((int) (height * scale) + offx, ystart,
+              (int) (height * scale) + offx, yend);
+
+      String nodeLabel = "";
+
+      if (showDistances && (node.dist > 0))
+      {
+        nodeLabel = new Format("%-.2f").form(node.dist);
+      }
+
+      if (showBootstrap && node.bootstrap > -1)
+      {
+        if (showDistances)
+        {
+          nodeLabel = nodeLabel + " : ";
+        }
+
+        nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
+      }
+
+      if (!nodeLabel.equals(""))
+      {
+        g.drawString(nodeLabel, xstart + 2, ypos - 2);
+      }
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param x
+   *          DOCUMENT ME!
+   * @param y
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public Object findElement(int x, int y)
+  {
+    Enumeration keys = nameHash.keys();
+
+    while (keys.hasMoreElements())
+    {
+      Object ob = keys.nextElement();
+      Rectangle rect = (Rectangle) nameHash.get(ob);
+
+      if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
+              && (y <= (rect.y + rect.height)))
+      {
+        return ob;
+      }
+    }
+
+    keys = nodeHash.keys();
+
+    while (keys.hasMoreElements())
+    {
+      Object ob = keys.nextElement();
+      Rectangle rect = (Rectangle) nodeHash.get(ob);
+
+      if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
+              && (y <= (rect.y + rect.height)))
+      {
+        return ob;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param pickBox
+   *          DOCUMENT ME!
+   */
+  public void pickNodes(Rectangle pickBox)
+  {
+    int width = getWidth();
+    int height = getHeight();
+
+    SequenceNode top = tree.getTopNode();
+
+    float wscale = (float) ((width * .8) - (offx * 2))
+            / tree.getMaxHeight();
+
+    if (top.count == 0)
+    {
+      top.count = ((SequenceNode) top.left()).count
+              + ((SequenceNode) top.right()).count;
+    }
+
+    float chunk = (float) (height - (offy)) / top.count;
+
+    pickNode(pickBox, top, chunk, wscale, width, offx, offy);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param pickBox
+   *          DOCUMENT ME!
+   * @param node
+   *          DOCUMENT ME!
+   * @param chunk
+   *          DOCUMENT ME!
+   * @param scale
+   *          DOCUMENT ME!
+   * @param width
+   *          DOCUMENT ME!
+   * @param offx
+   *          DOCUMENT ME!
+   * @param offy
+   *          DOCUMENT ME!
+   */
+  public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
+          float scale, int width, int offx, int offy)
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    if ((node.left() == null) && (node.right() == null))
+    {
+      float height = node.height;
+      float dist = node.dist;
+
+      int xstart = (int) ((height - dist) * scale) + offx;
+      int xend = (int) (height * scale) + offx;
+
+      int ypos = (int) (node.ycount * chunk) + offy;
+
+      if (pickBox.contains(new Point(xend, ypos)))
+      {
+        if (node.element() instanceof SequenceI)
+        {
+          SequenceI seq = (SequenceI) node.element();
+          SequenceGroup sg = av.getSelectionGroup();
+
+          if (sg != null)
+          {
+            sg.addOrRemove(seq, true);
+          }
+        }
+      }
+    }
+    else
+    {
+      pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
+              offx, offy);
+      pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
+              offx, offy);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param node
+   *          DOCUMENT ME!
+   * @param c
+   *          DOCUMENT ME!
+   */
+  public void setColor(SequenceNode node, Color c)
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    if ((node.left() == null) && (node.right() == null)) // TODO: internal node
+    {
+      node.color = c;
+
+      if (node.element() instanceof SequenceI)
+      {
+        AlignmentPanel[] aps = getAssociatedPanels();
+        if (aps != null)
+        {
+          for (int a = 0; a < aps.length; a++)
+          {
+            final SequenceI seq = (SequenceI) node.element();
+            aps[a].av.setSequenceColour(seq, c);
+          }
+        }
+      }
+    }
+    else
+    {
+      node.color = c;
+      setColor((SequenceNode) node.left(), c);
+      setColor((SequenceNode) node.right(), c);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   */
+  void startPrinting()
+  {
+    Thread thread = new Thread(this);
+    thread.start();
+  }
+
+  // put printing in a thread to avoid painting problems
+  @Override
+  public void run()
+  {
+    PrinterJob printJob = PrinterJob.getPrinterJob();
+    PageFormat pf = printJob.pageDialog(printJob.defaultPage());
+
+    printJob.setPrintable(this, pf);
+
+    if (printJob.printDialog())
+    {
+      try
+      {
+        printJob.print();
+      } catch (Exception PrintException)
+      {
+        PrintException.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param pg
+   *          DOCUMENT ME!
+   * @param pf
+   *          DOCUMENT ME!
+   * @param pi
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   * 
+   * @throws PrinterException
+   *           DOCUMENT ME!
+   */
+  @Override
+  public int print(Graphics pg, PageFormat pf, int pi)
+          throws PrinterException
+  {
+    pg.setFont(font);
+    pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
+
+    int pwidth = (int) pf.getImageableWidth();
+    int pheight = (int) pf.getImageableHeight();
+
+    int noPages = getHeight() / pheight;
+
+    if (pi > noPages)
+    {
+      return Printable.NO_SUCH_PAGE;
+    }
+
+    if (pwidth > getWidth())
+    {
+      pwidth = getWidth();
+    }
+
+    if (fitToWindow)
+    {
+      if (pheight > getHeight())
+      {
+        pheight = getHeight();
+      }
+
+      noPages = 0;
+    }
+    else
+    {
+      FontMetrics fm = pg.getFontMetrics(font);
+      int height = fm.getHeight() * nameHash.size();
+      pg.translate(0, -pi * pheight);
+      pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
+
+      // translate number of pages,
+      // height is screen size as this is the
+      // non overlapping text size
+      pheight = height;
+    }
+
+    draw(pg, pwidth, pheight);
+
+    return Printable.PAGE_EXISTS;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void paintComponent(Graphics g)
+  {
+    super.paintComponent(g);
+    g.setFont(font);
+
+    if (tree == null)
+    {
+      g.drawString(MessageManager.getString("label.calculating_tree")
+              + "....", 20, getHeight() / 2);
+    }
+    else
+    {
+      fm = g.getFontMetrics(font);
+
+      if (nameHash.size() == 0)
+      {
+        repaint();
+      }
+
+      if (fitToWindow
+              || (!fitToWindow && (scrollPane.getHeight() > ((fm
+                      .getHeight() * nameHash.size()) + offy))))
+      {
+        draw(g, scrollPane.getWidth(), scrollPane.getHeight());
+        setPreferredSize(null);
+      }
+      else
+      {
+        setPreferredSize(new Dimension(scrollPane.getWidth(),
+                fm.getHeight() * nameHash.size()));
+        draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
+      }
+
+      scrollPane.revalidate();
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param fontSize
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void setFont(Font font)
+  {
+    this.font = font;
+    repaint();
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g1
+   *          DOCUMENT ME!
+   * @param width
+   *          DOCUMENT ME!
+   * @param height
+   *          DOCUMENT ME!
+   */
+  public void draw(Graphics g1, int width, int height)
+  {
+    Graphics2D g2 = (Graphics2D) g1;
+    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+            RenderingHints.VALUE_ANTIALIAS_ON);
+    g2.setColor(Color.white);
+    g2.fillRect(0, 0, width, height);
+    g2.setFont(font);
+
+    if (longestName == null || tree == null)
+    {
+      g2.drawString("Calculating tree.", 20, 20);
+    }
+    offy = font.getSize() + 10;
+
+    fm = g2.getFontMetrics(font);
+
+    labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
+
+    float wscale = (width - labelLength - (offx * 2)) / tree.getMaxHeight();
+
+    SequenceNode top = tree.getTopNode();
+
+    if (top.count == 0)
+    {
+      top.count = ((SequenceNode) top.left()).count
+              + ((SequenceNode) top.right()).count;
+    }
+
+    float chunk = (float) (height - (offy)) / top.count;
+
+    drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
+
+    if (threshold != 0)
+    {
+      if (av.getCurrentTree() == tree)
+      {
+        g2.setColor(Color.red);
+      }
+      else
+      {
+        g2.setColor(Color.gray);
+      }
+
+      int x = (int) ((threshold * (getWidth() - labelLength - (2 * offx))) + offx);
+
+      g2.drawLine(x, 0, x, getHeight());
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void mouseReleased(MouseEvent e)
+  {
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void mouseEntered(MouseEvent e)
+  {
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void mouseExited(MouseEvent e)
+  {
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void mouseClicked(MouseEvent evt)
+  {
+    if (highlightNode != null)
+    {
+      if (SwingUtilities.isRightMouseButton(evt))
+      {
+        Color col = JColorChooser.showDialog(this,
+                MessageManager.getString("label.select_subtree_colour"),
+                highlightNode.color);
+        if (col != null)
+        {
+          setColor(highlightNode, col);
+        }
+      }
+      else if (evt.getClickCount() > 1)
+      {
+        tree.swapNodes(highlightNode);
+        tree.reCount(tree.getTopNode());
+        tree.findHeight(tree.getTopNode());
+      }
+      else
+      {
+        Vector leaves = new Vector();
+        tree.findLeaves(highlightNode, leaves);
+
+        for (int i = 0; i < leaves.size(); i++)
+        {
+          SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
+                  .element();
+          treeSelectionChanged(seq);
+        }
+        av.sendSelection();
+      }
+
+      PaintRefresher.Refresh(tp, av.getSequenceSetId());
+      repaint();
+    }
+  }
+
+  @Override
+  public void mouseMoved(MouseEvent evt)
+  {
+    av.setCurrentTree(tree);
+
+    Object ob = findElement(evt.getX(), evt.getY());
+
+    if (ob instanceof SequenceNode)
+    {
+      highlightNode = (SequenceNode) ob;
+      this.setToolTipText("<html>"
+              + MessageManager.getString("label.highlightnode"));
+      repaint();
+
+    }
+    else
+    {
+      if (highlightNode != null)
+      {
+        highlightNode = null;
+        setToolTipText(null);
+        repaint();
+      }
+    }
+  }
+
+  @Override
+  public void mouseDragged(MouseEvent ect)
+  {
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param e
+   *          DOCUMENT ME!
+   */
+  @Override
+  public void mousePressed(MouseEvent e)
+  {
+    av.setCurrentTree(tree);
+
+    int x = e.getX();
+    int y = e.getY();
+
+    Object ob = findElement(x, y);
+
+    if (ob instanceof SequenceI)
+    {
+      treeSelectionChanged((Sequence) ob);
+      PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
+      repaint();
+      av.sendSelection();
+      return;
+    }
+    else if (!(ob instanceof SequenceNode))
+    {
+      // Find threshold
+      if (tree.getMaxHeight() != 0)
+      {
+        threshold = (float) (x - offx)
+                / (float) (getWidth() - labelLength - (2 * offx));
+
+        tree.getGroups().removeAllElements();
+        tree.groupNodes(tree.getTopNode(), threshold);
+        setColor(tree.getTopNode(), Color.black);
+
+        AlignmentPanel[] aps = getAssociatedPanels();
+
+        // TODO push calls below into a single AlignViewportI method?
+        // see also AlignViewController.deleteGroups
+        for (int a = 0; a < aps.length; a++)
+        {
+          aps[a].av.setSelectionGroup(null);
+          aps[a].av.getAlignment().deleteAllGroups();
+          aps[a].av.clearSequenceColours();
+          if (aps[a].av.getCodingComplement() != null)
+          {
+            aps[a].av.getCodingComplement().setSelectionGroup(null);
+            aps[a].av.getCodingComplement().getAlignment()
+                    .deleteAllGroups();
+            aps[a].av.getCodingComplement().clearSequenceColours();
+          }
+        }
+        colourGroups();
+      }
+
+      PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
+      repaint();
+    }
+
+  }
+
+  void colourGroups()
+  {
+    AlignmentPanel[] aps = getAssociatedPanels();
+    for (int i = 0; i < tree.getGroups().size(); i++)
+    {
+      Color col = new Color((int) (Math.random() * 255),
+              (int) (Math.random() * 255), (int) (Math.random() * 255));
+      setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
+
+      Vector l = tree.findLeaves(
+              (SequenceNode) tree.getGroups().elementAt(i), new Vector());
+
+      Vector sequences = new Vector();
+
+      for (int j = 0; j < l.size(); j++)
+      {
+        SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
+                .element();
+
+        if (!sequences.contains(s1))
+        {
+          sequences.addElement(s1);
+        }
+      }
+
+      ColourSchemeI cs = null;
+      SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
+              false, 0, av.getAlignment().getWidth() - 1);
+
+      if (av.getGlobalColourScheme() != null)
+      {
+        if (av.getGlobalColourScheme() instanceof UserColourScheme)
+        {
+          cs = new UserColourScheme(
+                  ((UserColourScheme) av.getGlobalColourScheme())
+                          .getColours());
+
+        }
+        else
+        {
+          cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
+                  .getColourName(av.getGlobalColourScheme()));
+        }
+        // cs is null if shading is an annotationColourGradient
+        if (cs != null)
+        {
+          cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
+                  av.isIgnoreGapsConsensus());
+        }
+      }
+      sg.cs = cs;
+      // sg.recalcConservation();
+      sg.setName("JTreeGroup:" + sg.hashCode());
+      sg.setIdColour(col);
+
+      for (int a = 0; a < aps.length; a++)
+      {
+        if (aps[a].av.getGlobalColourScheme() != null
+                && aps[a].av.getGlobalColourScheme().conservationApplied())
+        {
+          Conservation c = new Conservation("Group",
+                  ResidueProperties.propHash, 3, sg.getSequences(null),
+                  sg.getStartRes(), sg.getEndRes());
+
+          c.calculate();
+          c.verdict(false, aps[a].av.getConsPercGaps());
+          sg.cs.setConservation(c);
+        }
+
+        aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
+        // TODO can we push all of the below into AlignViewportI?
+        final AlignViewportI codingComplement = aps[a].av
+                .getCodingComplement();
+        if (codingComplement != null)
+        {
+          if (codingComplement != null)
+          {
+            SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
+                    av, codingComplement);
+            if (mappedGroup.getSequences().size() > 0)
+            {
+              codingComplement.getAlignment().addGroup(mappedGroup);
+              for (SequenceI seq : mappedGroup.getSequences())
+              {
+                codingComplement.setSequenceColour(seq, col.brighter());
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // notify the panel(s) to redo any group specific stuff.
+    for (int a = 0; a < aps.length; a++)
+    {
+      aps[a].updateAnnotation();
+      // TODO: JAL-868 - need to ensure view colour change message is broadcast
+      // to any Jmols listening in
+      final AlignViewportI codingComplement = aps[a].av
+              .getCodingComplement();
+      if (codingComplement != null)
+      {
+        ((AlignViewport) codingComplement).getAlignPanel()
+                .updateAnnotation();
+
+      }
+
+    }
+
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param state
+   *          DOCUMENT ME!
+   */
+  public void setShowDistances(boolean state)
+  {
+    this.showDistances = state;
+    repaint();
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param state
+   *          DOCUMENT ME!
+   */
+  public void setShowBootstrap(boolean state)
+  {
+    this.showBootstrap = state;
+    repaint();
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param state
+   *          DOCUMENT ME!
+   */
+  public void setMarkPlaceholders(boolean state)
+  {
+    this.markPlaceholders = state;
+    repaint();
+  }
+
+  AlignmentPanel[] getAssociatedPanels()
+  {
+    if (applyToAllViews)
+    {
+      return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
+    }
+    else
+    {
+      return new AlignmentPanel[] { ap };
+    }
+  }
+}
index 25f4c1b..35998eb 100755 (executable)
 package jalview.gui;
 
 import jalview.analysis.AlignmentSorter;
+import jalview.analysis.AverageDistanceTree;
 import jalview.analysis.NJTree;
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.TreeModel;
+import jalview.analysis.scoremodels.ScoreModels;
 import jalview.api.analysis.ScoreModelI;
-import jalview.api.analysis.ViewBasedAnalysisI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.bin.Cache;
 import jalview.commands.CommandI;
 import jalview.commands.OrderCommand;
@@ -31,8 +35,8 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.BinaryNode;
-import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.NodeTransformI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
@@ -41,7 +45,6 @@ import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.io.NewickFile;
 import jalview.jbgui.GTreePanel;
-import jalview.schemes.ResidueProperties;
 import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
@@ -71,68 +74,46 @@ import org.jibble.epsgraphics.EpsGraphics2D;
  */
 public class TreePanel extends GTreePanel
 {
-  String type;
+  String treeType;
 
-  String pwtype;
+  String scoreModelName; // if tree computed
+
+  String treeTitle; // if tree loaded
+
+  SimilarityParamsI similarityParams;
 
   TreeCanvas treeCanvas;
 
-  NJTree tree;
+  TreeModel tree;
 
   AlignViewport av;
 
   /**
    * Creates a new TreePanel object.
    * 
-   * @param av
-   *          DOCUMENT ME!
-   * @param seqVector
-   *          DOCUMENT ME!
+   * @param ap
    * @param type
-   *          DOCUMENT ME!
-   * @param pwtype
-   *          DOCUMENT ME!
-   * @param s
-   *          DOCUMENT ME!
-   * @param e
-   *          DOCUMENT ME!
+   * @param modelName
+   * @param options
    */
-  public TreePanel(AlignmentPanel ap, String type, String pwtype)
+  public TreePanel(AlignmentPanel ap, String type, String modelName,
+          SimilarityParamsI options)
   {
     super();
-    initTreePanel(ap, type, pwtype, null, null);
+    this.similarityParams = options;
+    initTreePanel(ap, type, modelName, null, null);
 
     // We know this tree has distances. JBPNote TODO: prolly should add this as
     // a userdefined default
     // showDistances(true);
   }
 
-  /**
-   * Creates a new TreePanel object.
-   * 
-   * @param av
-   *          DOCUMENT ME!
-   * @param seqVector
-   *          DOCUMENT ME!
-   * @param newtree
-   *          DOCUMENT ME!
-   * @param type
-   *          DOCUMENT ME!
-   * @param pwtype
-   *          DOCUMENT ME!
-   */
-  public TreePanel(AlignmentPanel ap, String type, String pwtype,
-          NewickFile newtree)
-  {
-    super();
-    initTreePanel(ap, type, pwtype, newtree, null);
-  }
-
-  public TreePanel(AlignmentPanel av, String type, String pwtype,
-          NewickFile newtree, AlignmentView inputData)
+  public TreePanel(AlignmentPanel alignPanel, NewickFile newtree,
+          String theTitle, AlignmentView inputData)
   {
     super();
-    initTreePanel(av, type, pwtype, newtree, inputData);
+    this.treeTitle = theTitle;
+    initTreePanel(alignPanel, null, null, newtree, inputData);
   }
 
   public AlignmentI getAlignment()
@@ -145,13 +126,13 @@ public class TreePanel extends GTreePanel
     return treeCanvas.av;
   }
 
-  void initTreePanel(AlignmentPanel ap, String type, String pwtype,
+  void initTreePanel(AlignmentPanel ap, String type, String modelName,
           NewickFile newTree, AlignmentView inputData)
   {
 
     av = ap.av;
-    this.type = type;
-    this.pwtype = pwtype;
+    this.treeType = type;
+    this.scoreModelName = modelName;
 
     treeCanvas = new TreeCanvas(this, ap, scrollPane);
     scrollPane.setViewportView(treeCanvas);
@@ -181,7 +162,7 @@ public class TreePanel extends GTreePanel
                     .println("new alignment sequences vector value is null");
           }
 
-          tree.UpdatePlaceHolders((List<SequenceI>) evt.getNewValue());
+          tree.updatePlaceHolders((List<SequenceI>) evt.getNewValue());
           treeCanvas.nameHash.clear(); // reset the mapping between canvas
           // rectangles and leafnodes
           repaint();
@@ -189,11 +170,7 @@ public class TreePanel extends GTreePanel
       }
     });
 
-    TreeLoader tl = new TreeLoader(newTree);
-    if (inputData != null)
-    {
-      tl.odata = inputData;
-    }
+    TreeLoader tl = new TreeLoader(newTree, inputData);
     tl.start();
 
   }
@@ -265,19 +242,21 @@ public class TreePanel extends GTreePanel
 
   class TreeLoader extends Thread
   {
-    NewickFile newtree;
+    private NewickFile newtree;
 
-    jalview.datamodel.AlignmentView odata = null;
+    private AlignmentView odata = null;
 
-    public TreeLoader(NewickFile newtree)
+    public TreeLoader(NewickFile newickFile, AlignmentView inputData)
     {
-      this.newtree = newtree;
-      if (newtree != null)
+      this.newtree = newickFile;
+      this.odata = inputData;
+
+      if (newickFile != null)
       {
         // Must be outside run(), as Jalview2XML tries to
         // update distance/bootstrap visibility at the same time
-        showBootstrap(newtree.HasBootstrap());
-        showDistances(newtree.HasDistances());
+        showBootstrap(newickFile.HasBootstrap());
+        showDistances(newickFile.HasDistances());
       }
     }
 
@@ -287,60 +266,21 @@ public class TreePanel extends GTreePanel
 
       if (newtree != null)
       {
-        if (odata == null)
-        {
-          tree = new NJTree(av.getAlignment().getSequencesArray(), newtree);
-        }
-        else
+        tree = new TreeModel(av.getAlignment().getSequencesArray(), odata,
+                newtree);
+        if (tree.getOriginalData() == null)
         {
-          tree = new NJTree(av.getAlignment().getSequencesArray(), odata,
-                  newtree);
-        }
-        if (!tree.hasOriginalSequenceData())
-        {
-          allowOriginalSeqData(false);
+          originalSeqData.setVisible(false);
         }
       }
       else
       {
-        int start, end;
-        SequenceI[] seqs;
-        boolean selview = av.getSelectionGroup() != null
-                && av.getSelectionGroup().getSize() > 1;
-        AlignmentView seqStrings = av.getAlignmentView(selview);
-        if (!selview)
-        {
-          start = 0;
-          end = av.getAlignment().getWidth();
-          seqs = av.getAlignment().getSequencesArray();
-        }
-        else
-        {
-          start = av.getSelectionGroup().getStartRes();
-          end = av.getSelectionGroup().getEndRes() + 1;
-          seqs = av.getSelectionGroup().getSequencesInOrder(
-                  av.getAlignment());
-        }
-        ScoreModelI sm = ResidueProperties.getScoreModel(pwtype);
-        if (sm instanceof ViewBasedAnalysisI)
-        {
-          try
-          {
-            sm = sm.getClass().newInstance();
-            ((ViewBasedAnalysisI) sm)
-                    .configureFromAlignmentView(treeCanvas.ap);
-          } catch (Exception q)
-          {
-            Cache.log.error("Couldn't create a scoremodel instance for "
-                    + sm.getName());
-          }
-          tree = new NJTree(seqs, seqStrings, type, pwtype, sm, start, end);
-        }
-        else
-        {
-          tree = new NJTree(seqs, seqStrings, type, pwtype, null, start,
-                  end);
-        }
+        ScoreModelI sm = ScoreModels.getInstance().getScoreModel(
+                scoreModelName, treeCanvas.ap);
+        TreeBuilder njtree = treeType.equals(TreeBuilder.NEIGHBOUR_JOINING) ? new NJTree(
+                av, sm, similarityParams) : new AverageDistanceTree(av, sm,
+                similarityParams);
+        tree = new TreeModel(njtree);
         showDistances(true);
       }
 
@@ -374,17 +314,12 @@ public class TreePanel extends GTreePanel
     treeCanvas.setMarkPlaceholders(b);
   }
 
-  private void allowOriginalSeqData(boolean b)
-  {
-    originalSeqData.setVisible(b);
-  }
-
   /**
    * DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    */
-  public NJTree getTree()
+  public TreeModel getTree()
   {
     return tree;
   }
@@ -400,33 +335,14 @@ public class TreePanel extends GTreePanel
   {
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
 
-    StringBuffer buffer = new StringBuffer();
+    String newTitle = getPanelTitle();
 
-    if (type.equals("AV"))
-    {
-      buffer.append("Average distance tree using ");
-    }
-    else
-    {
-      buffer.append("Neighbour joining tree using ");
-    }
-
-    if (pwtype.equals("BL"))
-    {
-      buffer.append("BLOSUM62");
-    }
-    else
-    {
-      buffer.append("PID");
-    }
-
-    jalview.io.NewickFile fout = new jalview.io.NewickFile(
-            tree.getTopNode());
+    NewickFile fout = new NewickFile(tree.getTopNode());
     try
     {
-      cap.setText(fout.print(tree.isHasBootstrap(), tree.isHasDistances(),
-              tree.isHasRootDistance()));
-      Desktop.addInternalFrame(cap, buffer.toString(), 500, 100);
+      cap.setText(fout.print(tree.hasBootstrap(), tree.hasDistances(),
+              tree.hasRootDistance()));
+      Desktop.addInternalFrame(cap, newTitle, 500, 100);
     } catch (OutOfMemoryError oom)
     {
       new OOMWarning("generating newick tree file", oom);
@@ -463,8 +379,8 @@ public class TreePanel extends GTreePanel
       {
         jalview.io.NewickFile fout = new jalview.io.NewickFile(
                 tree.getTopNode());
-        String output = fout.print(tree.isHasBootstrap(),
-                tree.isHasDistances(), tree.isHasRootDistance());
+        String output = fout.print(tree.hasBootstrap(),
+                tree.hasDistances(), tree.hasRootDistance());
         java.io.PrintWriter out = new java.io.PrintWriter(
                 new java.io.FileWriter(choice));
         out.println(output);
@@ -492,7 +408,8 @@ public class TreePanel extends GTreePanel
   @Override
   public void originalSeqData_actionPerformed(ActionEvent e)
   {
-    if (!tree.hasOriginalSequenceData())
+    AlignmentView originalData = tree.getOriginalData();
+    if (originalData == null)
     {
       jalview.bin.Cache.log
               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
@@ -514,8 +431,8 @@ public class TreePanel extends GTreePanel
     } catch (Exception ex)
     {
     }
-    ;
-    Object[] alAndColsel = tree.seqData.getAlignmentAndColumnSelection(gc);
+
+    Object[] alAndColsel = originalData.getAlignmentAndHiddenColumns(gc);
 
     if (alAndColsel != null && alAndColsel[0] != null)
     {
@@ -532,8 +449,8 @@ public class TreePanel extends GTreePanel
       if (true)
       {
         // make a new frame!
-        AlignFrame af = new AlignFrame(al,
-                (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
+        AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
+                AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
 
         // >>>This is a fix for the moment, until a better solution is
@@ -632,11 +549,11 @@ public class TreePanel extends GTreePanel
 
   public CommandI sortAlignmentIn(AlignmentPanel ap)
   {
-    AlignmentViewport av = ap.av;
-    SequenceI[] oldOrder = av.getAlignment().getSequencesArray();
-    AlignmentSorter.sortByTree(av.getAlignment(), tree);
+    AlignmentViewport viewport = ap.av;
+    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
+    AlignmentSorter.sortByTree(viewport.getAlignment(), tree);
     CommandI undo;
-    undo = new OrderCommand("Tree Sort", oldOrder, av.getAlignment());
+    undo = new OrderCommand("Tree Sort", oldOrder, viewport.getAlignment());
 
     ap.paintAlignment(true);
     return undo;
@@ -664,11 +581,11 @@ public class TreePanel extends GTreePanel
     return treeCanvas.font;
   }
 
-  public void setTreeFont(Font font)
+  public void setTreeFont(Font f)
   {
     if (treeCanvas != null)
     {
-      treeCanvas.setFont(font);
+      treeCanvas.setFont(f);
     }
   }
 
@@ -891,12 +808,46 @@ public class TreePanel extends GTreePanel
           }
           if (newname != null)
           {
-            String oldname = ((SequenceNode) node).getName();
-            // TODO : save in the undo object for this modification.
+            // String oldname = ((SequenceNode) node).getName();
+            // TODO : save oldname in the undo object for this modification.
             ((SequenceNode) node).setName(newname);
           }
         }
       }
     });
   }
+
+  /**
+   * Formats a localised title for the tree panel, like
+   * <p>
+   * Neighbour Joining Using BLOSUM62
+   * <p>
+   * For a tree loaded from file, just uses the file name
+   * @return
+   */
+  public String getPanelTitle()
+  {
+    if (treeTitle != null)
+    {
+      return treeTitle;
+    }
+
+    /*
+     * i18n description of Neighbour Joining or Average Distance method
+     */
+    String treecalcnm = MessageManager.getString("label.tree_calc_"
+            + treeType.toLowerCase());
+
+    /*
+     * short score model name (long description can be too long)
+     */
+    String smn = scoreModelName;
+
+    /*
+     * put them together as <method> Using <model>
+     */
+    final String ttl = MessageManager.formatMessage("label.treecalc_title",
+            treecalcnm, smn);
+    return ttl;
+  }
 }
index 83a8d24..f75a0a3 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
-import jalview.datamodel.SequenceGroup;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.jbgui.GUserDefinedColours;
 import jalview.schemabinding.version2.Colour;
 import jalview.schemabinding.version2.JalviewUserColours;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
@@ -39,7 +38,6 @@ import jalview.util.MessageManager;
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Insets;
-import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.io.File;
@@ -71,7 +69,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
   private static final String LAST_DIRECTORY = "LAST_DIRECTORY";
 
-  private static final int MY_FRAME_HEIGHT = 420;
+  private static final int MY_FRAME_HEIGHT = 440;
 
   private static final int MY_FRAME_WIDTH = 810;
 
@@ -79,43 +77,38 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
   AlignmentPanel ap;
 
-  SequenceGroup seqGroup;
-
-  List<JButton> selectedButtons;
-
+  /*
+   * the colour scheme when the dialog was opened, or
+   * the scheme last saved to file
+   */
   ColourSchemeI oldColourScheme;
 
-  JInternalFrame frame;
+  /*
+   * flag is true if the colour scheme has been changed since the
+   * dialog was opened, or the changes last saved to file
+   */
+  boolean changed;
 
-  JalviewStructureDisplayI structureViewer;
+  JInternalFrame frame;
 
   List<JButton> upperCaseButtons;
 
   List<JButton> lowerCaseButtons;
 
   /**
-   * Creates a new UserDefinedColours object.
+   * Creates and displays a new UserDefinedColours panel
    * 
-   * @param ap
-   * @param sg
+   * @param alignPanel
    */
-  public UserDefinedColours(AlignmentPanel ap, SequenceGroup sg)
+  public UserDefinedColours(AlignmentPanel alignPanel)
   {
     this();
 
     lcaseColour.setEnabled(false);
 
-    this.ap = ap;
-    seqGroup = sg;
+    this.ap = alignPanel;
 
-    if (seqGroup != null)
-    {
-      oldColourScheme = seqGroup.getColourScheme();
-    }
-    else
-    {
-      oldColourScheme = ap.av.getGlobalColourScheme();
-    }
+    oldColourScheme = alignPanel.av.getGlobalColourScheme();
 
     if (oldColourScheme instanceof UserColourScheme)
     {
@@ -139,29 +132,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
     showFrame();
   }
 
-  public UserDefinedColours(JalviewStructureDisplayI viewer,
-          ColourSchemeI oldcs)
-  {
-    this();
-    this.structureViewer = viewer;
-
-    colorChooser.getSelectionModel().addChangeListener(this);
-
-    oldColourScheme = oldcs;
-
-    if (oldColourScheme instanceof UserColourScheme)
-    {
-      schemeName.setText(((UserColourScheme) oldColourScheme)
-              .getSchemeName());
-    }
-
-    resetButtonPanel(false);
-
-    showFrame();
-
-  }
-
-  public UserDefinedColours()
+  UserDefinedColours()
   {
     super();
     selectedButtons = new ArrayList<JButton>();
@@ -175,11 +146,6 @@ public class UserDefinedColours extends GUserDefinedColours implements
     Desktop.addInternalFrame(frame,
             MessageManager.getString("label.user_defined_colours"),
             MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
-
-    if (seqGroup != null)
-    {
-      frame.setTitle(frame.getTitle() + " (" + seqGroup.getName() + ")");
-    }
   }
 
   /**
@@ -276,14 +242,9 @@ public class UserDefinedColours extends GUserDefinedColours implements
   {
     JButton button = null;
     final Color newColour = colorChooser.getColor();
-    for (int i = 0; i < selectedButtons.size(); i++)
-    {
-      button = selectedButtons.get(i);
-      button.setBackground(newColour);
-      button.setForeground(ColorUtils.brighterThan(newColour));
-    }
     if (lcaseColour.isSelected())
     {
+      selectedButtons.clear();
       for (int i = 0; i < lowerCaseButtons.size(); i++)
       {
         button = lowerCaseButtons.get(i);
@@ -291,6 +252,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
         button.setForeground(ColorUtils.brighterThan(button.getBackground()));
       }
     }
+    for (int i = 0; i < selectedButtons.size(); i++)
+    {
+      button = selectedButtons.get(i);
+      button.setBackground(newColour);
+      button.setForeground(ColorUtils.brighterThan(newColour));
+    }
+
+    changed = true;
   }
 
   /**
@@ -478,8 +447,17 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
     else
     {
+      /*
+       * OK is treated as 'apply colours and close'
+       */
       applyButton_actionPerformed();
 
+      /*
+       * If editing a named colour scheme, warn if changes
+       * have not been saved
+       */
+      warnIfUnsavedChanges();
+
       try
       {
         frame.setClosed(true);
@@ -490,6 +468,57 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
+   * If we have made changes to an existing user defined colour scheme but not
+   * saved them, show a dialog with the option to save. If the user chooses to
+   * save, do so, else clear the colour scheme name to indicate a new colour
+   * scheme.
+   */
+  protected void warnIfUnsavedChanges()
+  {
+    if (!changed)
+    {
+      return;
+    }
+
+    String name = schemeName.getText().trim();
+    if (oldColourScheme != null && !"".equals(name)
+            && name.equals(oldColourScheme.getSchemeName()))
+    {
+      String message = MessageManager.formatMessage("label.scheme_changed",
+              name);
+      String title = MessageManager.getString("label.save_changes");
+      String[] options = new String[] { title,
+          MessageManager.getString("label.dont_save_changes"), };
+      final String question = JvSwingUtils.wrapTooltip(true, message);
+      int response = JvOptionPane.showOptionDialog(Desktop.desktop,
+              question, title, JvOptionPane.DEFAULT_OPTION,
+              JvOptionPane.PLAIN_MESSAGE, null, options, options[0]);
+
+      boolean saved = false;
+      if (response == 0)
+      {
+        /*
+         * prompt to save changes to file
+         */
+        saved = savebutton_actionPerformed();
+      }
+
+      /*
+       * if user chooses not to save (either in this dialog or in the
+       * save as dialogs), treat this as a new user defined colour scheme
+       */
+      if (!saved)
+      {
+        /*
+         * clear scheme name and re-apply as an anonymous scheme
+         */
+        schemeName.setText("");
+        applyButton_actionPerformed();
+      }
+    }
+  }
+
+  /**
    * Returns true if the user has not made any colour selection (including if
    * 'case-sensitive' selected and no lower-case colour chosen).
    * 
@@ -507,8 +536,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * Applies the current colour scheme to the alignment, sequence group or
-   * structure view.
+   * Applies the current colour scheme to the alignment or sequence group
    */
   @Override
   protected void applyButton_actionPerformed()
@@ -523,21 +551,15 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
     UserColourScheme ucs = getSchemeFromButtons();
 
-    if (seqGroup != null)
-    {
-      seqGroup.setColourScheme(ucs);
-      ap.paintAlignment(true);
-    }
-    else if (ap != null)
-    {
-      ap.alignFrame.changeColour(ucs);
-    }
-    else if (structureViewer != null)
-    {
-      structureViewer.setJalviewColourScheme(ucs);
-    }
+    ap.alignFrame.changeColour(ucs);
   }
 
+  /**
+   * Constructs an instance of UserColourScheme with the residue colours
+   * currently set on the buttons on the panel
+   * 
+   * @return
+   */
   UserColourScheme getSchemeFromButtons()
   {
 
@@ -589,22 +611,19 @@ public class UserDefinedColours extends GUserDefinedColours implements
       ucs.setLowerCaseColours(newColours);
     }
 
-    // if (ap != null)
-    // {
-    // ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
-    // }
-
     return ucs;
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Action on clicking Load scheme button.
+   * <ul>
+   * <li>Open a file chooser to browse for files with extension .jc</li>
+   * <li>Load in the colour scheme and transfer it to this panel's buttons</li>
+   * <li>Register the loaded colour scheme</li>
+   * </ul>
    */
   @Override
-  protected void loadbutton_actionPerformed(ActionEvent e)
+  protected void loadbutton_actionPerformed()
   {
     upperCaseButtons = new ArrayList<JButton>();
     lowerCaseButtons = new ArrayList<JButton>();
@@ -625,7 +644,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
     File choice = chooser.getSelectedFile();
     Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 
-    UserColourScheme ucs = ColourSchemes.loadColourScheme(choice
+    UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(choice
             .getAbsolutePath());
     Color[] colors = ucs.getColours();
     schemeName.setText(ucs.getSchemeName());
@@ -674,7 +693,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
       {
         colours = colours.substring(0, colours.indexOf("|"));
       }
-      ret = ColourSchemes.loadColourScheme(colours);
+      ret = ColourSchemeLoader.loadColourScheme(colours);
     }
 
     if (ret == null)
@@ -686,13 +705,23 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * DOCUMENT ME!
+   * Action on pressing the Save button.
+   * <ul>
+   * <li>Check a name has been entered</li>
+   * <li>Warn if the name already exists, remove any existing scheme of the same
+   * name if overwriting</li>
+   * <li>Do the standard file chooser thing to write with extension .jc</li>
+   * <li>If saving changes (possibly not yet applied) to the currently selected
+   * colour scheme, then apply the changes, as it is too late to back out now</li>
+   * <li>Don't apply the changes if the currently selected scheme is different,
+   * to allow a new scheme to be configured and saved but not applied</li>
+   * </ul>
+   * Returns true if the scheme is saved to file, false if it is not
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @return
    */
   @Override
-  protected void savebutton_actionPerformed(ActionEvent e)
+  protected boolean savebutton_actionPerformed()
   {
     String name = schemeName.getText().trim();
     if (name.length() < 1)
@@ -701,7 +730,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
               .getString("label.user_colour_scheme_must_have_name"),
               MessageManager.getString("label.no_name_colour_scheme"),
               JvOptionPane.WARNING_MESSAGE);
-      return;
+      return false;
     }
 
     if (ColourSchemes.getInstance().nameExists(name))
@@ -714,9 +743,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
               JvOptionPane.YES_NO_OPTION);
       if (reply != JvOptionPane.YES_OPTION)
       {
-        return;
+        return false;
       }
-      ColourSchemes.getInstance().removeColourScheme(name);
     }
     JalviewFileChooser chooser = new JalviewFileChooser("jc",
             "Jalview User Colours");
@@ -729,12 +757,28 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
     int value = chooser.showSaveDialog(this);
 
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    if (value != JalviewFileChooser.APPROVE_OPTION)
+    {
+      return false;
+    }
+
+    File file = chooser.getSelectedFile();
+    UserColourScheme updatedScheme = addNewColourScheme(file.getPath());
+    saveToFile(file);
+    changed = false;
+
+    /*
+     * changes saved - apply to alignment if we are changing 
+     * the currently selected colour scheme; also make the updated
+     * colours the 'backout' scheme on Cancel
+     */
+    if (oldColourScheme != null
+            && name.equals(oldColourScheme.getSchemeName()))
     {
-      File file = chooser.getSelectedFile();
-      addNewColourScheme(file.getPath());
-      saveToFile(file);
+      oldColourScheme = updatedScheme;
+      applyButton_actionPerformed();
     }
+    return true;
   }
 
   /**
@@ -744,8 +788,9 @@ public class UserDefinedColours extends GUserDefinedColours implements
    * the colour scheme.
    * 
    * @param filePath
+   * @return
    */
-  protected void addNewColourScheme(String filePath)
+  protected UserColourScheme addNewColourScheme(String filePath)
   {
     /*
      * update the delimited list of user defined colour files in
@@ -776,6 +821,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
     {
       ap.alignFrame.buildColourMenu();
     }
+
+    return ucs;
   }
 
   /**
@@ -790,7 +837,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
      * marshal to file
      */
     JalviewUserColours ucs = new JalviewUserColours();
-    ucs.setSchemeName(schemeName.getText());
+    String name = schemeName.getText();
+    ucs.setSchemeName(name);
     try
     {
       PrintWriter out = new PrintWriter(new OutputStreamWriter(
@@ -813,30 +861,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * On cancel, restores the colour scheme before the dialogue was opened
-   * 
-   * @param e
+   * On cancel, restores the colour scheme that was selected before the dialogue
+   * was opened
    */
   @Override
-  protected void cancelButton_actionPerformed(ActionEvent e)
+  protected void cancelButton_actionPerformed()
   {
-    if (ap != null)
-    {
-      if (seqGroup != null)
-      {
-        seqGroup.setColourScheme(oldColourScheme);
-      }
-      else
-      {
-        ap.alignFrame.changeColour(oldColourScheme);
-      }
-      ap.paintAlignment(true);
-    }
-
-    if (structureViewer != null)
-    {
-      structureViewer.setJalviewColourScheme(oldColourScheme);
-    }
+    ap.alignFrame.changeColour(oldColourScheme);
+    ap.paintAlignment(true);
 
     try
     {
@@ -846,8 +878,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
   }
 
+  /**
+   * Action on selecting or deselecting the Case Sensitive option. When
+   * selected, separate buttons are shown for lower case residues, and the panel
+   * is resized to accommodate them. Also, the checkbox for 'apply colour to all
+   * lower case' is enabled.
+   */
   @Override
-  public void caseSensitive_actionPerformed(ActionEvent e)
+  public void caseSensitive_actionPerformed()
   {
     boolean selected = caseSensitive.isSelected();
     resetButtonPanel(selected);
index 75ddba5..d58cb5a 100644 (file)
@@ -23,6 +23,7 @@ package jalview.gui;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.VamsasAppDatastore;
@@ -43,7 +44,6 @@ import java.util.IdentityHashMap;
 import java.util.Iterator;
 
 import javax.swing.JInternalFrame;
-import javax.swing.JOptionPane;
 
 import uk.ac.vamsas.client.ClientHandle;
 import uk.ac.vamsas.client.IClient;
@@ -821,7 +821,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
               {
                 // TODO: rationalise : can only clear a selection over a
                 // referred to object
-                ssm.sendSelection(null, null, me);
+                ssm.sendSelection(null, null, null, me);
                 return;
               }
               Class type = null;
@@ -955,7 +955,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
               }
               if (send)
               {
-                ssm.sendSelection(jselection, colsel, me);
+                ssm.sendSelection(jselection, colsel, null, me);
               }
               // discard message.
               for (int c = 0; c < jvobjs.length; c++)
@@ -1004,7 +1004,8 @@ public class VamsasApplication implements SelectionSource, VamsasSource
 
           @Override
           public void selection(SequenceGroup seqsel,
-                  ColumnSelection colsel, SelectionSource source)
+                  ColumnSelection colsel, HiddenColumns hidden,
+                  SelectionSource source)
           {
             if (vobj2jv == null)
             {
@@ -1079,7 +1080,9 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   }
                   else
                   {
-                    int[] intervals = colsel.getVisibleContigs(
+                    // int[] intervals = colsel.getVisibleContigs(
+                    // seqsel.getStartRes(), seqsel.getEndRes() + 1);
+                    int[] intervals = hidden.getVisibleContigs(
                             seqsel.getStartRes(), seqsel.getEndRes() + 1);
                     for (int iv = 0; iv < intervals.length; iv += 2)
                     {
index 9a4071d..c3e71da 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.GraphLine;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -109,23 +110,22 @@ public class AnnotationFile
    */
   public class ViewDef
   {
-    public String viewname;
+    // TODO this class is not used - remove?
+    public final String viewname;
 
-    public HiddenSequences hidseqs;
+    public final HiddenSequences hidseqs;
 
-    public ColumnSelection hiddencols;
+    public final HiddenColumns hiddencols;
 
-    public Vector visibleGroups;
+    public final Hashtable hiddenRepSeqs;
 
-    public Hashtable hiddenRepSeqs;
-
-    public ViewDef(String viewname, HiddenSequences hidseqs,
-            ColumnSelection hiddencols, Hashtable hiddenRepSeqs)
+    public ViewDef(String vname, HiddenSequences hseqs,
+            HiddenColumns hcols, Hashtable hRepSeqs)
     {
-      this.viewname = viewname;
-      this.hidseqs = hidseqs;
-      this.hiddencols = hiddencols;
-      this.hiddenRepSeqs = hiddenRepSeqs;
+      this.viewname = vname;
+      this.hidseqs = hseqs;
+      this.hiddencols = hcols;
+      this.hiddenRepSeqs = hRepSeqs;
     }
   }
 
@@ -141,7 +141,8 @@ public class AnnotationFile
    */
   public String printAnnotations(AlignmentAnnotation[] annotations,
           List<SequenceGroup> list, Hashtable properties,
-          ColumnSelection cs, AlignmentI al, ViewDef view)
+ HiddenColumns cs,
+          AlignmentI al, ViewDef view)
   {
     if (view != null)
     {
@@ -151,7 +152,7 @@ public class AnnotationFile
       }
       if (list == null)
       {
-        list = view.visibleGroups;
+        // list = view.visibleGroups;
       }
       if (cs == null)
       {
@@ -170,7 +171,7 @@ public class AnnotationFile
     if (cs != null && cs.hasHiddenColumns())
     {
       text.append("VIEW_HIDECOLS\t");
-      List<int[]> hc = cs.getHiddenColumns();
+      List<int[]> hc = cs.getHiddenRegions();
       boolean comma = false;
       for (int[] r : hc)
       {
@@ -536,7 +537,7 @@ public class AnnotationFile
     return false;
   }
 
-  public void printGroups(List<SequenceGroup> list)
+  protected void printGroups(List<SequenceGroup> list)
   {
     SequenceI seqrep = null;
     for (SequenceGroup sg : list)
@@ -582,7 +583,8 @@ public class AnnotationFile
       if (sg.cs != null)
       {
         text.append("colour=");
-        text.append(sg.cs.toString());
+        text.append(ColourSchemeProperty.getColourName(sg.cs
+                .getColourScheme()));
         text.append("\t");
         if (sg.cs.getThreshold() != 0)
         {
@@ -664,15 +666,21 @@ public class AnnotationFile
           String file, DataSourceType protocol)
   {
     ColumnSelection colSel = viewport.getColumnSelection();
+    HiddenColumns hidden = viewport.getAlignment().getHiddenColumns();
     if (colSel == null)
     {
       colSel = new ColumnSelection();
     }
-    boolean rslt = readAnnotationFile(viewport.getAlignment(), colSel,
+    if (hidden == null)
+    {
+      hidden = new HiddenColumns();
+    }
+    boolean rslt = readAnnotationFile(viewport.getAlignment(), hidden,
             file, protocol);
-    if (rslt && (colSel.hasSelectedColumns() || colSel.hasHiddenColumns()))
+    if (rslt && (colSel.hasSelectedColumns() || hidden.hasHiddenColumns()))
     {
       viewport.setColumnSelection(colSel);
+      viewport.getAlignment().setHiddenColumns(hidden);
     }
 
     return rslt;
@@ -684,7 +692,7 @@ public class AnnotationFile
     return readAnnotationFile(al, null, file, sourceType);
   }
 
-  public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
+  public boolean readAnnotationFile(AlignmentI al, HiddenColumns hidden,
           String file, DataSourceType sourceType)
   {
     BufferedReader in = null;
@@ -713,7 +721,7 @@ public class AnnotationFile
       }
       if (in != null)
       {
-        return parseAnnotationFrom(al, colSel, in);
+        return parseAnnotationFrom(al, hidden, in);
       }
 
     } catch (Exception ex)
@@ -736,7 +744,7 @@ public class AnnotationFile
 
   private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE";
 
-  public boolean parseAnnotationFrom(AlignmentI al, ColumnSelection colSel,
+  public boolean parseAnnotationFrom(AlignmentI al, HiddenColumns hidden,
           BufferedReader in) throws Exception
   {
     nlinesread = 0;
@@ -947,11 +955,11 @@ public class AnnotationFile
         {
           if (st.hasMoreTokens())
           {
-            if (colSel == null)
+            if (hidden == null)
             {
-              colSel = new ColumnSelection();
+              hidden = new HiddenColumns();
             }
-            parseHideCols(colSel, st.nextToken());
+            parseHideCols(hidden, st.nextToken());
           }
           modified = true;
           continue;
@@ -965,7 +973,7 @@ public class AnnotationFile
           }
           if (sr != null)
           {
-            if (colSel == null)
+            if (hidden == null)
             {
               System.err
                       .println("Cannot process HIDE_INSERTIONS without an alignment view: Ignoring line: "
@@ -974,7 +982,7 @@ public class AnnotationFile
             else
             {
               // consider deferring this till after the file has been parsed ?
-              colSel.hideInsertionsFor(sr);
+              hidden.hideInsertionsFor(sr);
             }
           }
           modified = true;
@@ -1180,7 +1188,7 @@ public class AnnotationFile
     return modified;
   }
 
-  private void parseHideCols(ColumnSelection colSel, String nextToken)
+  private void parseHideCols(HiddenColumns hidden, String nextToken)
   {
     StringTokenizer inval = new StringTokenizer(nextToken, ",");
     while (inval.hasMoreTokens())
@@ -1192,7 +1200,7 @@ public class AnnotationFile
         from = to = Integer.parseInt(range);
         if (from >= 0)
         {
-          colSel.hideColumns(from, to);
+          hidden.hideColumns(from, to);
         }
       }
       else
@@ -1208,7 +1216,7 @@ public class AnnotationFile
         }
         if (from > 0 && to >= from)
         {
-          colSel.hideColumns(from, to);
+          hidden.hideColumns(from, to);
         }
       }
     }
@@ -1787,7 +1795,7 @@ public class AnnotationFile
     return printAnnotations(viewport.isShowAnnotation() ? viewport
             .getAlignment().getAlignmentAnnotation() : null, viewport
             .getAlignment().getGroups(), viewport.getAlignment()
-            .getProperties(), viewport.getColumnSelection(),
+            .getProperties(), viewport.getAlignment().getHiddenColumns(),
             viewport.getAlignment(), null);
   }
 
index 1c97900..700c595 100755 (executable)
@@ -410,15 +410,26 @@ public class AppletFormatAdapter
     return null;
   }
 
-  public static DataSourceType checkProtocol(String file)
+  /**
+   * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
+   * data
+   *
+   * @param data
+   * @return the protocol for the input data
+   */
+  public static DataSourceType checkProtocol(String data)
   {
-    DataSourceType protocol = DataSourceType.FILE;
-    String ft = file.toLowerCase().trim();
+    DataSourceType protocol = DataSourceType.PASTE;
+    String ft = data.toLowerCase().trim();
     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
             || ft.indexOf("file:") == 0)
     {
       protocol = DataSourceType.URL;
     }
+    else if (new File(data).exists())
+    {
+      protocol = DataSourceType.FILE;
+    }
     return protocol;
   }
 
index a11147c..3354b88 100644 (file)
@@ -256,6 +256,21 @@ public enum FileFormat implements FileFormatI
       return new FeaturesFile();
     }
   },
+  ScoreMatrix("Substitution matrix", "", false, false)
+  {
+    @Override
+    public AlignmentFileReaderI getReader(FileParse source)
+            throws IOException
+    {
+      return new ScoreMatrixFile(source);
+    }
+
+    @Override
+    public AlignmentFileWriterI getWriter(AlignmentI al)
+    {
+      return null;
+    }
+  },
   PDB("PDB", "pdb,ent", true, false)
   {
     @Override
index 4f83ab1..9d5fd93 100755 (executable)
@@ -27,7 +27,7 @@ import jalview.api.FeaturesSourceI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
@@ -39,7 +39,10 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.schemes.ColourSchemeI;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
+import jalview.ws.utils.UrlDownloadClient;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -205,6 +208,12 @@ public class FileLoader implements Runnable
       // refer to it as.
       return;
     }
+    if (file != null
+            && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
+    {
+      // ignore files loaded from the system's temporary directory
+      return;
+    }
     String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
             : "RECENT_URL";
 
@@ -325,9 +334,27 @@ public class FileLoader implements Runnable
 
             // open a new source and read from it
             FormatAdapter fa = new FormatAdapter();
-            al = fa.readFile(file, protocol, format);
-            source = fa.getAlignFile(); // keep reference for later if
-                                        // necessary.
+            boolean downloadStructureFile = format.isStructureFile()
+                    && protocol.equals(DataSourceType.URL);
+            if (downloadStructureFile)
+            {
+              String structExt = format.getExtensions().split(",")[0];
+              String urlLeafName = file.substring(file.lastIndexOf(System
+                      .getProperty("file.separator")), file
+                      .lastIndexOf("."));
+              String tempStructureFileStr = createNamedJvTempFile(
+                      urlLeafName, structExt);
+              UrlDownloadClient.download(file, tempStructureFileStr);
+              al = fa.readFile(tempStructureFileStr, DataSourceType.FILE,
+                      format);
+              source = fa.getAlignFile();
+            }
+            else
+            {
+              al = fa.readFile(file, protocol, format);
+              source = fa.getAlignFile(); // keep reference for later if
+                                          // necessary.
+            }
           }
         } catch (java.io.IOException ex)
         {
@@ -372,8 +399,8 @@ public class FileLoader implements Runnable
 
             if (source instanceof ComplexAlignFile)
             {
-              ColumnSelection colSel = ((ComplexAlignFile) source)
-                      .getColumnSelection();
+              HiddenColumns colSel = ((ComplexAlignFile) source)
+                      .getHiddenColumns();
               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
                       .getHiddenSequences();
               String colourSchemeName = ((ComplexAlignFile) source)
@@ -556,6 +583,29 @@ public class FileLoader implements Runnable
 
   }
 
+  /**
+   * This method creates the file -
+   * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
+   * file name and extension
+   * 
+   * @param fileName
+   *          the name of the temp file to be created
+   * @param extension
+   *          the extension of the temp file to be created
+   * @return
+   */
+  private static String createNamedJvTempFile(String fileName,
+          String extension) throws IOException
+  {
+    String seprator = System.getProperty("file.separator");
+    String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
+            + seprator + System.currentTimeMillis();
+    File tempStructFile = new File(jvTempDir + seprator + fileName + "."
+            + extension);
+    tempStructFile.mkdirs();
+    return tempStructFile.toString();
+  }
+
   /*
    * (non-Javadoc)
    * 
index d9dd79d..f09e8a0 100755 (executable)
@@ -26,7 +26,7 @@ import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -165,14 +165,14 @@ public class FormatAdapter extends AppletFormatAdapter
   }
 
   public String formatSequences(FileFormatI format, AlignmentI alignment,
-          String[] omitHidden, int[] exportRange, ColumnSelection colSel)
+          String[] omitHidden, int[] exportRange, HiddenColumns hidden)
   {
     return formatSequences(format, alignment, omitHidden, exportRange,
-            getCacheSuffixDefault(format), colSel, null);
+            getCacheSuffixDefault(format), hidden, null);
   }
 
   /**
-   * hack function to replace seuqences with visible sequence strings before
+   * hack function to replace sequences with visible sequence strings before
    * generating a string of the alignment in the given format.
    * 
    * @param format
@@ -185,15 +185,15 @@ public class FormatAdapter extends AppletFormatAdapter
    */
   public String formatSequences(FileFormatI format, AlignmentI alignment,
           String[] omitHidden, int[] exportRange, boolean suffix,
-          ColumnSelection colSel)
+          HiddenColumns hidden)
   {
     return formatSequences(format, alignment, omitHidden, exportRange,
-            suffix, colSel, null);
+            suffix, hidden, null);
   }
 
   public String formatSequences(FileFormatI format, AlignmentI alignment,
           String[] omitHidden, int[] exportRange, boolean suffix,
-          ColumnSelection colSel, SequenceGroup selgp)
+          HiddenColumns hidden, SequenceGroup selgp)
   {
     if (omitHidden != null)
     {
@@ -211,12 +211,12 @@ public class FormatAdapter extends AppletFormatAdapter
           AlignmentAnnotation na = new AlignmentAnnotation(ala[i]);
           if (selgp != null)
           {
-            colSel.makeVisibleAnnotation(selgp.getStartRes(),
+            hidden.makeVisibleAnnotation(selgp.getStartRes(),
                     selgp.getEndRes(), na);
           }
           else
           {
-            colSel.makeVisibleAnnotation(na);
+            hidden.makeVisibleAnnotation(na);
           }
           alv.addAnnotation(na);
         }
index 68f3e2c..77006db 100755 (executable)
@@ -91,8 +91,8 @@ public abstract class HTMLOutput implements Runnable
     String bioJSON = new FormatAdapter(ap, exportData.getSettings())
             .formatSequences(FileFormat.Json, exportData.getAlignment(),
                     exportData.getOmitHidden(), exportData
-                            .getStartEndPostions(), ap.getAlignViewport()
-                            .getColumnSelection());
+.getStartEndPostions(), ap.getAlignViewport()
+                            .getAlignment().getHiddenColumns());
     return bioJSON;
   }
 
index af3fb5d..9256278 100644 (file)
@@ -24,7 +24,7 @@ package jalview.io;
 import jalview.api.ComplexAlignFile;
 import jalview.api.FeatureSettingsModelI;
 import jalview.api.FeaturesDisplayedI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 
 import java.io.IOException;
@@ -44,7 +44,7 @@ public class HtmlFile extends AlignFile implements ComplexAlignFile
 
   private boolean showSeqFeatures;
 
-  private ColumnSelection columnSelection;
+  private HiddenColumns hiddenColumns;
 
   private SequenceI[] hiddenSequences;
 
@@ -111,7 +111,7 @@ public class HtmlFile extends AlignFile implements ComplexAlignFile
       this.showSeqFeatures = jsonFile.isShowSeqFeatures();
       this.globalColourScheme = jsonFile.getGlobalColourScheme();
       this.hiddenSequences = jsonFile.getHiddenSequences();
-      this.columnSelection = jsonFile.getColumnSelection();
+      this.hiddenColumns = jsonFile.getHiddenColumns();
       this.displayedFeatures = jsonFile.getDisplayedFeatures();
     } catch (Exception e)
     {
@@ -149,14 +149,14 @@ public class HtmlFile extends AlignFile implements ComplexAlignFile
   }
 
   @Override
-  public ColumnSelection getColumnSelection()
+  public HiddenColumns getHiddenColumns()
   {
-    return columnSelection;
+    return hiddenColumns;
   }
 
-  public void setColumnSelection(ColumnSelection columnSelection)
+  public void setHiddenColumns(HiddenColumns hidden)
   {
-    this.columnSelection = columnSelection;
+    this.hiddenColumns = hidden;
   }
 
   @Override
index 0556e76..035c1fa 100755 (executable)
@@ -98,12 +98,15 @@ public class IdentifyFile
     boolean lineswereskipped = false;
     boolean isBinary = false; // true if length is non-zero and non-printable
     // characters are encountered
+
     try
     {
       if (!closeSource)
       {
         source.mark();
       }
+      boolean aaIndexHeaderRead = false;
+
       while ((data = source.nextLine()) != null)
       {
         bytesRead += data.length();
@@ -141,6 +144,20 @@ public class IdentifyFile
         }
         data = data.toUpperCase();
 
+        if (data.startsWith(ScoreMatrixFile.SCOREMATRIX))
+        {
+          reply = FileFormat.ScoreMatrix;
+          break;
+        }
+        if (data.startsWith("H ") && !aaIndexHeaderRead)
+        {
+          aaIndexHeaderRead = true;
+        }
+        if (data.startsWith("D ") && aaIndexHeaderRead)
+        {
+          reply = FileFormat.ScoreMatrix;
+          break;
+        }
         if (data.startsWith("##GFF-VERSION"))
         {
           // GFF - possibly embedded in a Jalview features file!
index 27ebe5a..816346a 100644 (file)
@@ -32,7 +32,7 @@ import jalview.bin.BuildDetails;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
@@ -46,6 +46,8 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.json.binding.biojson.v1.SequenceFeaturesPojo;
 import jalview.json.binding.biojson.v1.SequenceGrpPojo;
 import jalview.json.binding.biojson.v1.SequencePojo;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.JalviewColourScheme;
 import jalview.schemes.ResidueColourScheme;
 import jalview.util.ColorUtils;
@@ -82,9 +84,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
 
   private FeatureRenderer fr;
 
-  private List<int[]> hiddenColumns;
-
-  private ColumnSelection columnSelection;
+  private HiddenColumns hiddenColumns;
 
   private List<String> hiddenSeqRefs;
 
@@ -239,8 +239,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
         {
           SequenceGrpPojo seqGrpPojo = new SequenceGrpPojo();
           seqGrpPojo.setGroupName(seqGrp.getName());
-          seqGrpPojo.setColourScheme(seqGrp.getColourScheme()
-                  .getSchemeName());
+          seqGrpPojo.setColourScheme(ColourSchemeProperty
+                  .getColourName(seqGrp.getColourScheme()));
           seqGrpPojo.setColourText(seqGrp.getColourText());
           seqGrpPojo.setDescription(seqGrp.getDescription());
           seqGrpPojo.setDisplayBoxes(seqGrp.getDisplayBoxes());
@@ -279,8 +279,9 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
     // hidden column business
     if (getViewport().hasHiddenColumns())
     {
-      List<int[]> hiddenCols = getViewport().getColumnSelection()
-              .getHiddenColumns();
+      List<int[]> hiddenCols = getViewport().getAlignment()
+              .getHiddenColumns()
+              .getHiddenRegions();
       StringBuilder hiddenColsBuilder = new StringBuilder();
       for (int[] range : hiddenCols)
       {
@@ -328,6 +329,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
       return sequenceFeaturesPojo;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+
     for (SequenceI seq : sqs)
     {
       SequenceI dataSetSequence = seq.getDatasetSequence();
@@ -350,7 +353,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
                   String.valueOf(seq.hashCode()));
 
           String featureColour = (fr == null) ? null : jalview.util.Format
-                  .getHexString(fr.findFeatureColour(Color.white, seq,
+                  .getHexString(finder.findFeatureColour(Color.white, seq,
                           seq.findIndex(sf.getBegin())));
           jsonFeature.setXstart(seq.findIndex(sf.getBegin()) - 1);
           jsonFeature.setXend(seq.findIndex(sf.getEnd()));
@@ -663,12 +666,12 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
     String hiddenCols = (String) jvSettingsJson.get("hiddenCols");
     if (hiddenCols != null && !hiddenCols.isEmpty())
     {
-      columnSelection = new ColumnSelection();
+      hiddenColumns = new HiddenColumns();
       String[] rangeStrings = hiddenCols.split(";");
       for (String rangeString : rangeStrings)
       {
         String[] range = rangeString.split("-");
-        columnSelection.hideColumns(Integer.valueOf(range[0]),
+        hiddenColumns.hideColumns(Integer.valueOf(range[0]),
                 Integer.valueOf(range[1]));
       }
     }
@@ -764,7 +767,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
         }
       }
     }
-    globalColourScheme = viewport.getGlobalColourScheme().getSchemeName();
+    globalColourScheme = ColourSchemeProperty.getColourName(viewport
+            .getGlobalColourScheme());
     setDisplayedFeatures(viewport.getFeaturesDisplayed());
     showSeqFeatures = viewport.isShowSequenceFeatures();
 
@@ -786,20 +790,15 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
     return annotations;
   }
 
-  public List<int[]> getHiddenColumns()
-  {
-    return hiddenColumns;
-  }
-
   @Override
-  public ColumnSelection getColumnSelection()
+  public HiddenColumns getHiddenColumns()
   {
-    return columnSelection;
+    return hiddenColumns;
   }
 
-  public void setColumnSelection(ColumnSelection columnSelection)
+  public void setHiddenColumns(HiddenColumns hidden)
   {
-    this.columnSelection = columnSelection;
+    this.hiddenColumns = hidden;
   }
 
   @Override
index 746c4a7..0f2b0ac 100644 (file)
@@ -26,12 +26,14 @@ import jalview.schemes.FeatureSettingsAdapter;
 
 import java.awt.Color;
 
+import MCview.PDBChain;
+
 public class PDBFeatureSettings extends FeatureSettingsAdapter
 {
   // TODO find one central place to define feature names
   private static final String FEATURE_INSERTION = "INSERTION";
 
-  private static final String FEATURE_RES_NUM = "RESNUM";
+  private static final String FEATURE_RES_NUM = PDBChain.RESNUM_FEATURE;
 
   @Override
   public boolean isFeatureDisplayed(String type)
diff --git a/src/jalview/io/ScoreMatrixFile.java b/src/jalview/io/ScoreMatrixFile.java
new file mode 100644 (file)
index 0000000..6b2f891
--- /dev/null
@@ -0,0 +1,433 @@
+package jalview.io;
+
+import jalview.analysis.scoremodels.ScoreMatrix;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.datamodel.SequenceI;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+
+/**
+ * A class that can parse a file containing a substitution matrix and register
+ * it for use in Jalview
+ * <p>
+ * Accepts 'NCBI' format (e.g.
+ * https://www.ncbi.nlm.nih.gov/Class/FieldGuide/BLOSUM62.txt), with the
+ * addition of a header line to provide a matrix name, e.g.
+ * 
+ * <pre>
+ * ScoreMatrix BLOSUM62
+ * </pre>
+ * 
+ * Also accepts 'AAindex' format (as described at
+ * http://www.genome.jp/aaindex/aaindex_help.html) with the minimum data
+ * required being
+ * 
+ * <pre>
+ * H accession number (used as score matrix identifier in Jalview)
+ * D description (used for tooltip in Jalview)
+ * M rows = symbolList
+ * and the substitution scores
+ * </pre>
+ */
+public class ScoreMatrixFile extends AlignFile implements
+        AlignmentFileReaderI
+{
+  // first non-comment line identifier - also checked in IdentifyFile
+  public static final String SCOREMATRIX = "SCOREMATRIX";
+
+  private static final String DELIMITERS = " ,\t";
+
+  private static final String COMMENT_CHAR = "#";
+
+  private String matrixName;
+
+  /*
+   * aaindex format has scores for diagonal and below only
+   */
+  boolean isLowerDiagonalOnly;
+
+  /*
+   * ncbi format has symbols as first column on score rows
+   */
+  boolean hasGuideColumn;
+
+  /**
+   * Constructor
+   * 
+   * @param source
+   * @throws IOException
+   */
+  public ScoreMatrixFile(FileParse source) throws IOException
+  {
+    super(false, source);
+  }
+
+  @Override
+  public String print(SequenceI[] sqs, boolean jvsuffix)
+  {
+    return null;
+  }
+
+  /**
+   * Parses the score matrix file, and if successful registers the matrix so it
+   * will be shown in Jalview menus. This method is not thread-safe (a separate
+   * instance of this class should be used by each thread).
+   */
+  @Override
+  public void parse() throws IOException
+  {
+    ScoreMatrix sm = parseMatrix();
+
+    ScoreModels.getInstance().registerScoreModel(sm);
+  }
+
+  /**
+   * Parses the score matrix file and constructs a ScoreMatrix object. If an
+   * error is found in parsing, it is thrown as FileFormatException. Any
+   * warnings are written to syserr.
+   * 
+   * @return
+   * @throws IOException
+   */
+  public ScoreMatrix parseMatrix() throws IOException
+  {
+    ScoreMatrix sm = null;
+    int lineNo = 0;
+    String name = null;
+    char[] alphabet = null;
+    float[][] scores = null;
+    int size = 0;
+    int row = 0;
+    String err = null;
+    String data;
+    isLowerDiagonalOnly = false;
+
+    while ((data = nextLine()) != null)
+    {
+      lineNo++;
+      data = data.trim();
+      if (data.startsWith(COMMENT_CHAR) || data.length() == 0)
+      {
+        continue;
+      }
+      if (data.toUpperCase().startsWith(SCOREMATRIX))
+      {
+        /*
+         * Parse name from ScoreMatrix <name>
+         * we allow any delimiter after ScoreMatrix then take the rest of the line
+         */
+        if (name != null)
+        {
+          throw new FileFormatException(
+                  "Error: 'ScoreMatrix' repeated in file at line "
+                          + lineNo);
+        }
+        StringTokenizer nameLine = new StringTokenizer(data, DELIMITERS);
+        if (nameLine.countTokens() < 2)
+        {
+          err = "Format error: expected 'ScoreMatrix <name>', found '"
+                  + data + "' at line " + lineNo;
+          throw new FileFormatException(err);
+        }
+        nameLine.nextToken(); // 'ScoreMatrix'
+        name = nameLine.nextToken(); // next field
+        name = data.substring(1).substring(data.substring(1).indexOf(name));
+        continue;
+      }
+      else if (data.startsWith("H ") && name == null)
+      {
+        /*
+         * AAindex identifier 
+         */
+        return parseAAIndexFormat(lineNo, data);
+      }
+      else if (name == null)
+      {
+        err = "Format error: 'ScoreMatrix <name>' should be the first non-comment line";
+        throw new FileFormatException(err);
+      }
+
+      /*
+       * next non-comment line after ScoreMatrix should be the 
+       * column header line with the alphabet of scored symbols
+       */
+      if (alphabet == null)
+      {
+        StringTokenizer columnHeadings = new StringTokenizer(data,
+                DELIMITERS);
+        size = columnHeadings.countTokens();
+        alphabet = new char[size];
+        int col = 0;
+        while (columnHeadings.hasMoreTokens())
+        {
+          alphabet[col++] = columnHeadings.nextToken().charAt(0);
+        }
+        scores = new float[size][];
+        continue;
+      }
+
+      /*
+       * too much information
+       */
+      if (row >= size)
+      {
+        err = "Unexpected extra input line in score model file: '" + data
+                + "'";
+        throw new FileFormatException(err);
+      }
+
+      parseValues(data, lineNo, scores, row, alphabet);
+      row++;
+    }
+
+    /*
+     * out of data - check we found enough
+     */
+    if (row < size)
+    {
+      err = String
+              .format("Expected %d rows of score data in score matrix but only found %d",
+                      size, row);
+      throw new FileFormatException(err);
+    }
+
+    /*
+     * If we get here, then name, alphabet and scores have been parsed successfully
+     */
+    sm = new ScoreMatrix(name, alphabet, scores);
+    matrixName = name;
+
+    return sm;
+  }
+
+  /**
+   * Parse input as AAIndex format, starting from the header line with the
+   * accession id
+   * 
+   * @param lineNo
+   * @param data
+   * @return
+   * @throws IOException
+   */
+  protected ScoreMatrix parseAAIndexFormat(int lineNo, String data)
+          throws IOException
+  {
+    String name = data.substring(2).trim();
+    String description = null;
+
+    float[][] scores = null;
+    char[] alphabet = null;
+    int row = 0;
+    int size = 0;
+
+    while ((data = nextLine()) != null)
+    {
+      lineNo++;
+      data = data.trim();
+      if (skipAAindexLine(data))
+      {
+        continue;
+      }
+      if (data.startsWith("D "))
+      {
+        description = data.substring(2).trim();
+      }
+      else if (data.startsWith("M "))
+      {
+        alphabet = parseAAindexRowsColumns(lineNo, data);
+        size = alphabet.length;
+        scores = new float[size][size];
+      }
+      else if (scores == null)
+      {
+        throw new FileFormatException(
+                "No alphabet specified in matrix file");
+      }
+      else if (row >= size)
+      {
+        throw new FileFormatException("Too many data rows in matrix file");
+      }
+      else
+      {
+        parseValues(data, lineNo, scores, row, alphabet);
+        row++;
+      }
+    }
+
+    ScoreMatrix sm = new ScoreMatrix(name, description, alphabet, scores);
+    matrixName = name;
+
+    return sm;
+  }
+
+  /**
+   * Parse one row of score values, delimited by whitespace or commas. The line
+   * may optionally include the symbol from which the scores are defined. Values
+   * may be present for all columns, or only up to the diagonal (in which case
+   * upper diagonal values are set symmetrically).
+   * 
+   * @param data
+   *          the line to be parsed
+   * @param lineNo
+   * @param scores
+   *          the score matrix to add data to
+   * @param row
+   *          the row number / alphabet index position
+   * @param alphabet
+   * @return
+   * @throws exception
+   *           if invalid, or too few, or too many values
+   */
+  protected void parseValues(String data, int lineNo, float[][] scores,
+          int row, char[] alphabet) throws FileFormatException
+  {
+    String err;
+    int size = alphabet.length;
+    StringTokenizer scoreLine = new StringTokenizer(data, DELIMITERS);
+
+    int tokenCount = scoreLine.countTokens();
+
+    /*
+     * inspect first row to see if it includes the symbol in the first column,
+     * and to see if it is lower diagonal values only (i.e. just one score)
+     */
+    if (row == 0)
+    {
+      if (data.startsWith(String.valueOf(alphabet[0])))
+      {
+        hasGuideColumn = true;
+      }
+      if (tokenCount == (hasGuideColumn ? 2 : 1))
+      {
+        isLowerDiagonalOnly = true;
+      }
+    }
+
+    if (hasGuideColumn)
+    {
+      /*
+       * check 'guide' symbol is the row'th letter of the alphabet
+       */
+      String symbol = scoreLine.nextToken();
+      if (symbol.length() > 1 || symbol.charAt(0) != alphabet[row])
+      {
+        err = String
+                .format("Error parsing score matrix at line %d, expected '%s' but found '%s'",
+                        lineNo, alphabet[row], symbol);
+        throw new FileFormatException(err);
+      }
+      tokenCount = scoreLine.countTokens(); // excluding guide symbol
+    }
+
+    /*
+     * check the right number of values (lower diagonal or full format)
+     */
+    if (isLowerDiagonalOnly && tokenCount != row + 1)
+    {
+      err = String.format(
+              "Expected %d scores at line %d: '%s' but found %d", row + 1,
+              lineNo, data, tokenCount);
+        throw new FileFormatException(err);
+    }
+
+    if (!isLowerDiagonalOnly && tokenCount != size)
+    {
+      err = String.format(
+              "Expected %d scores at line %d: '%s' but found %d", size,
+              lineNo, data, scoreLine.countTokens());
+      throw new FileFormatException(err);
+    }
+
+    /*
+     * parse and set the values, setting the symmetrical value
+     * as well if lower diagonal format data
+     */
+    scores[row] = new float[size];
+    int col = 0;
+    String value = null;
+    while (scoreLine.hasMoreTokens())
+    {
+      try
+      {
+        value = scoreLine.nextToken();
+        scores[row][col] = Float.valueOf(value);
+        if (isLowerDiagonalOnly)
+        {
+          scores[col][row] = scores[row][col];
+        }
+        col++;
+      } catch (NumberFormatException e)
+      {
+        err = String.format(
+                "Invalid score value '%s' at line %d column %d", value,
+                lineNo, col);
+        throw new FileFormatException(err);
+      }
+    }
+  }
+
+  /**
+   * Parse the line in an aaindex file that looks like
+   * 
+   * <pre>
+   * M rows = ARNDCQEGHILKMFPSTWYV, cols = ARNDCQEGHILKMFPSTWYV
+   * </pre>
+   * 
+   * rejecting it if rows and cols do not match. Returns the string of
+   * characters in the row/cols alphabet.
+   * 
+   * @param lineNo
+   * @param data
+   * @return
+   * @throws FileFormatException
+   */
+  protected char[] parseAAindexRowsColumns(int lineNo, String data)
+          throws FileFormatException
+  {
+    String err = "Unexpected aaIndex score matrix data at line " + lineNo
+            + ": " + data;
+    
+    try
+    {
+      String[] toks = data.split(",");
+      String rowsAlphabet = toks[0].split("=")[1].trim();
+      String colsAlphabet = toks[1].split("=")[1].trim();
+      if (!rowsAlphabet.equals(colsAlphabet))
+      {
+        throw new FileFormatException("rows != cols");
+      }
+      return rowsAlphabet.toCharArray();
+    } catch (Throwable t)
+    {
+      throw new FileFormatException(err + " " + t.getMessage());
+    }
+  }
+
+  /**
+   * Answers true if line is one we are not interested in from AAindex format
+   * file
+   * 
+   * @param data
+   * @return
+   */
+  protected boolean skipAAindexLine(String data)
+  {
+    if (data.startsWith(COMMENT_CHAR) || data.length() == 0)
+    {
+      return true;
+    }
+    if (data.startsWith("*") || data.startsWith("R ")
+            || data.startsWith("A ") || data.startsWith("T ")
+            || data.startsWith("J ") || data.startsWith("//"))
+    {
+      return true;
+    }
+    return false;
+  }
+
+  public String getMatrixName()
+  {
+    return matrixName;
+  }
+}
index 2061f29..c2f3683 100644 (file)
@@ -33,6 +33,7 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ResidueProperties;
+import jalview.util.Comparison;
 import jalview.util.Format;
 import jalview.util.MessageManager;
 
@@ -45,7 +46,6 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 import java.util.Vector;
 
 import com.stevesoft.pat.Regex;
@@ -78,8 +78,8 @@ public class StockholmFile extends AlignFile
 
   private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
 
-  private static final Regex DETECT_BRACKETS = new Regex(
-          "(<|>|\\[|\\]|\\(|\\))");
+  public static final Regex DETECT_BRACKETS = new Regex(
+          "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
 
   StringBuffer out; // output buffer
 
@@ -366,6 +366,11 @@ public class StockholmFile extends AlignFile
 
               // add alignment annotation for this feature
               String key = type2id(type);
+
+              /*
+               * have we added annotation rows for this type ?
+               */
+              boolean annotsAdded = false;
               if (key != null)
               {
                 if (accAnnotations != null
@@ -374,6 +379,7 @@ public class StockholmFile extends AlignFile
                   Vector vv = (Vector) accAnnotations.get(key);
                   for (int ii = 0; ii < vv.size(); ii++)
                   {
+                    annotsAdded = true;
                     AlignmentAnnotation an = (AlignmentAnnotation) vv
                             .elementAt(ii);
                     seqO.addAlignmentAnnotation(an);
@@ -386,6 +392,11 @@ public class StockholmFile extends AlignFile
               while (j.hasMoreElements())
               {
                 String desc = j.nextElement().toString();
+                if ("annotations".equals(desc) && annotsAdded)
+                {
+                  // don't add features if we already added an annotation row
+                  continue;
+                }
                 String ns = content.get(desc).toString();
                 char[] byChar = ns.toCharArray();
                 for (int k = 0; k < byChar.length; k++)
@@ -572,22 +583,11 @@ public class StockholmFile extends AlignFile
           {
             String acc = s.stringMatched(1);
             String type = s.stringMatched(2);
-            String seq = new String(s.stringMatched(3));
-            String description = null;
-            // Check for additional information about the current annotation
-            // We use a simple string tokenizer here for speed
-            StringTokenizer sep = new StringTokenizer(seq, " \t");
-            description = sep.nextToken();
-            if (sep.hasMoreTokens())
-            {
-              seq = sep.nextToken();
-            }
-            else
-            {
-              seq = description;
-              description = new String();
-            }
-            // sequence id with from-to fields
+            String oseq = s.stringMatched(3);
+            /*
+             * copy of annotation field that may be processed into whitespace chunks
+             */
+            String seq = new String(oseq);
 
             Hashtable ann;
             // Get an object with all the annotations for this sequence
@@ -602,8 +602,12 @@ public class StockholmFile extends AlignFile
               ann = new Hashtable();
               seqAnn.put(acc, ann);
             }
+
+            // // start of block for appending annotation lines for wrapped
+            // stokchholm file
             // TODO test structure, call parseAnnotationRow with vector from
             // hashtable for specific sequence
+
             Hashtable features;
             // Get an object with all the content for an annotation
             if (ann.containsKey("features"))
@@ -631,15 +635,18 @@ public class StockholmFile extends AlignFile
               content = new Hashtable();
               features.put(this.id2type(type), content);
             }
-            String ns = (String) content.get(description);
+            String ns = (String) content.get("annotation");
+
             if (ns == null)
             {
               ns = "";
             }
+            // finally, append the annotation line
             ns += seq;
-            content.put(description, ns);
+            content.put("annotation", ns);
+            // // end of wrapped annotation block.
+            // // Now a new row is created with the current set of data
 
-            // if(type.equals("SS")){
             Hashtable strucAnn;
             if (seqAnn.containsKey(acc))
             {
@@ -656,7 +663,8 @@ public class StockholmFile extends AlignFile
             {
               alan.visible = false;
             }
-            // annotations.addAll(newStruc);
+            // new annotation overwrites any existing annotation...
+
             strucAnn.put(type, newStruc);
             seqAnn.put(acc, strucAnn);
           }
@@ -800,9 +808,9 @@ public class StockholmFile extends AlignFile
   {
     String convert1, convert2 = null;
 
-    convert1 = OPEN_PAREN.replaceAll(annots);
-    convert2 = CLOSE_PAREN.replaceAll(convert1);
-    annots = convert2;
+    // convert1 = OPEN_PAREN.replaceAll(annots);
+    // convert2 = CLOSE_PAREN.replaceAll(convert1);
+    // annots = convert2;
 
     String type = label;
     if (label.contains("_cons"))
@@ -810,12 +818,16 @@ public class StockholmFile extends AlignFile
       type = (label.indexOf("_cons") == label.length() - 5) ? label
               .substring(0, label.length() - 5) : label;
     }
-    boolean ss = false;
+    boolean ss = false, posterior = false;
     type = id2type(type);
-    if (type.equals("secondary structure"))
+    if (type.equalsIgnoreCase("secondary structure"))
     {
       ss = true;
     }
+    if (type.equalsIgnoreCase("posterior probability"))
+    {
+      posterior = true;
+    }
     // decide on secondary structure or not.
     Annotation[] els = new Annotation[annots.length()];
     for (int i = 0; i < annots.length(); i++)
@@ -831,12 +843,12 @@ public class StockholmFile extends AlignFile
           if (DETECT_BRACKETS.search(pos))
           {
             ann.secondaryStructure = Rna.getRNASecStrucState(pos).charAt(0);
+            ann.displayCharacter = "" + pos.charAt(0);
           }
           else
           {
             ann.secondaryStructure = ResidueProperties.getDssp3state(pos)
                     .charAt(0);
-          }
 
           if (ann.secondaryStructure == pos.charAt(0))
           {
@@ -846,9 +858,29 @@ public class StockholmFile extends AlignFile
           {
             ann.displayCharacter = " " + ann.displayCharacter;
           }
+          }
         }
 
       }
+      if (posterior && !ann.isWhitespace()
+              && !Comparison.isGap(pos.charAt(0)))
+      {
+        float val = 0;
+        // symbol encodes values - 0..*==0..10
+        if (pos.charAt(0) == '*')
+        {
+          val = 10;
+        }
+        else
+        {
+          val = pos.charAt(0) - '0';
+          if (val > 9)
+          {
+            val = 10;
+          }
+        }
+        ann.value = val;
+      }
 
       els[i] = ann;
     }
@@ -966,43 +998,39 @@ public class StockholmFile extends AlignFile
     // output annotations
     while (i < s.length && s[i] != null)
     {
-      if (s[i].getDatasetSequence() != null)
+      AlignmentAnnotation[] alAnot = s[i].getAnnotation();
+      if (alAnot != null)
       {
-        SequenceI ds = s[i].getDatasetSequence();
-        AlignmentAnnotation[] alAnot;
         Annotation[] ann;
-        Annotation annot;
-        alAnot = s[i].getAnnotation();
-        String feature = "";
-        if (alAnot != null)
+        for (int j = 0; j < alAnot.length; j++)
         {
-          for (int j = 0; j < alAnot.length; j++)
+
+          String key = type2id(alAnot[j].label);
+          boolean isrna = alAnot[j].isValidStruc();
+
+          if (isrna)
+          {
+            // hardwire to secondary structure if there is RNA secondary
+            // structure on the annotation
+            key = "SS";
+          }
+          if (key == null)
           {
-            if (ds.getSequenceFeatures() != null)
-            {
-              feature = ds.getSequenceFeatures()[0].type;
-            }
-            // ?bug - feature may still have previous loop value
-            String key = type2id(feature);
 
-            if (key == null)
-            {
-              continue;
-            }
+            continue;
+          }
 
-            // out.append("#=GR ");
-            out.append(new Format("%-" + maxid + "s").form("#=GR "
-                    + printId(s[i], jvSuffix) + " " + key + " "));
-            ann = alAnot[j].annotations;
-            boolean isrna = alAnot[j].isValidStruc();
-            String seq = "";
-            for (int k = 0; k < ann.length; k++)
-            {
-              seq += outputCharacter(key, k, isrna, ann, s[i]);
-            }
-            out.append(seq);
-            out.append(newline);
+          // out.append("#=GR ");
+          out.append(new Format("%-" + maxid + "s").form("#=GR "
+                  + printId(s[i], jvSuffix) + " " + key + " "));
+          ann = alAnot[j].annotations;
+          String seq = "";
+          for (int k = 0; k < ann.length; k++)
+          {
+            seq += outputCharacter(key, k, isrna, ann, s[i]);
           }
+          out.append(seq);
+          out.append(newline);
         }
       }
 
@@ -1088,8 +1116,8 @@ public class StockholmFile extends AlignFile
     {
       if (annot == null)
       {
-        // sensible gap character if one is available or make one up
-        return sequenceI == null ? '-' : sequenceI.getCharAt(k);
+        // sensible gap character
+        return ' ';
       }
       else
       {
@@ -1135,10 +1163,10 @@ public class StockholmFile extends AlignFile
     if (typeIds == null)
     {
       typeIds = new Hashtable();
-      typeIds.put("SS", "secondary structure");
-      typeIds.put("SA", "surface accessibility");
+      typeIds.put("SS", "Secondary Structure");
+      typeIds.put("SA", "Surface Accessibility");
       typeIds.put("TM", "transmembrane");
-      typeIds.put("PP", "posterior probability");
+      typeIds.put("PP", "Posterior Probability");
       typeIds.put("LI", "ligand binding");
       typeIds.put("AS", "active site");
       typeIds.put("IN", "intron");
@@ -1149,7 +1177,7 @@ public class StockholmFile extends AlignFile
       typeIds.put("DE", "description");
       typeIds.put("DR", "reference");
       typeIds.put("LO", "look");
-      typeIds.put("RF", "reference positions");
+      typeIds.put("RF", "Reference Positions");
 
     }
   }
@@ -1172,7 +1200,7 @@ public class StockholmFile extends AlignFile
     while (e.hasMoreElements())
     {
       Object ll = e.nextElement();
-      if (typeIds.get(ll).toString().equals(type))
+      if (typeIds.get(ll).toString().equalsIgnoreCase(type))
       {
         key = (String) ll;
         break;
index 7fe17c8..ab220f0 100644 (file)
@@ -406,7 +406,8 @@ public abstract class StructureFile extends AlignFile
    * make a friendly ID string.
    * 
    * @param dataName
-   * @return truncated dataName to after last '/'
+   * @return truncated dataName to after last '/' and pruned .extension if
+   *         present
    */
   protected String safeName(String dataName)
   {
@@ -415,6 +416,9 @@ public abstract class StructureFile extends AlignFile
     {
       dataName = dataName.substring(p + 1);
     }
+    if(dataName.indexOf(".") > -1){
+      dataName = dataName.substring(0, dataName.lastIndexOf("."));
+    }
     return dataName;
   }
 
index 2c35547..1cab8ca 100644 (file)
@@ -1942,7 +1942,7 @@ public class VamsasAppDatastore
                 TreePanel tp = null;
                 if (vstree.isValidTree())
                 {
-                  tp = alignFrame.ShowNewickTree(vstree.getNewickTree(),
+                  tp = alignFrame.showNewickTree(vstree.getNewickTree(),
                           vstree.getTitle(), vstree.getInputData(), 600,
                           500, t * 20 + 50, t * 20 + 50);
 
diff --git a/src/jalview/io/cache/AppCache.java b/src/jalview/io/cache/AppCache.java
new file mode 100644 (file)
index 0000000..091d30e
--- /dev/null
@@ -0,0 +1,153 @@
+package jalview.io.cache;
+
+
+import jalview.bin.Cache;
+
+import java.util.Hashtable;
+import java.util.LinkedHashSet;
+
+/**
+ * A singleton class used for querying and persisting cache items.
+ * 
+ * @author tcnofoegbu
+ *
+ */
+public class AppCache
+{
+  public static final String DEFAULT_LIMIT = "99";
+
+  public static final String CACHE_DELIMITER = ";";
+
+  private static AppCache instance = null;
+
+  private static final String DEFAULT_LIMIT_KEY = ".DEFAULT_LIMIT";
+
+
+
+  private Hashtable<String, LinkedHashSet<String>> cacheItems;
+
+  private AppCache()
+  {
+    cacheItems = new Hashtable<String, LinkedHashSet<String>>();
+  }
+
+  /**
+   * Method to obtain all the cache items for a given cache key
+   * 
+   * @param cacheKey
+   * @return
+   */
+  public LinkedHashSet<String> getAllCachedItemsFor(String cacheKey)
+  {
+    LinkedHashSet<String> foundCache = cacheItems.get(cacheKey);
+    if (foundCache == null)
+    {
+      foundCache = new LinkedHashSet<String>();
+      cacheItems.put(cacheKey, foundCache);
+    }
+    return foundCache;
+  }
+
+
+  /**
+   * Returns a singleton instance of AppCache
+   * 
+   * @return
+   */
+  public static AppCache getInstance()
+  {
+    if (instance == null)
+    {
+      instance = new AppCache();
+    }
+    return instance;
+  }
+
+
+
+  /**
+   * Method for persisting cache items for a given cache key
+   * 
+   * @param cacheKey
+   */
+  public void persistCache(String cacheKey)
+  {
+    LinkedHashSet<String> foundCacheItems = getAllCachedItemsFor(cacheKey);
+    StringBuffer delimitedCacheBuf = new StringBuffer();
+    for (String cacheItem : foundCacheItems)
+    {
+      delimitedCacheBuf.append(CACHE_DELIMITER).append(cacheItem);
+    }
+    if (delimitedCacheBuf.length() > 0)
+    {
+      delimitedCacheBuf.deleteCharAt(0);
+    }
+    String delimitedCacheString = delimitedCacheBuf.toString();
+
+    Cache.setProperty(cacheKey, delimitedCacheString);
+  }
+
+  /**
+   * Method for deleting cached items for a given cache key
+   * 
+   * @param cacheKey
+   *          the cache key
+   */
+  public void deleteCacheItems(String cacheKey)
+  {
+    cacheItems.put(cacheKey, new LinkedHashSet<String>());
+    persistCache(cacheKey);
+  }
+
+  /**
+   * Method for obtaining the preset maximum cache limit for a given cache key
+   * 
+   * @param cacheKey
+   *          the cache key
+   * @return the max number of items that could be cached
+   */
+  public String getCacheLimit(String cacheKey)
+  {
+    String uniqueKey = cacheKey + DEFAULT_LIMIT_KEY;
+    return Cache.getDefault(uniqueKey, DEFAULT_LIMIT);
+  }
+
+  /**
+   * Method for updating the preset maximum cache limit for a given cache key
+   * 
+   * @param cacheKey
+   *          the cache key
+   * @param newLimit
+   *          the max number of items that could be cached for the given cache
+   *          key
+   * @return
+   */
+  public int updateCacheLimit(String cacheKey, int newUserLimit)
+  {
+    String newLimit = String.valueOf(newUserLimit);
+    String uniqueKey = cacheKey + DEFAULT_LIMIT_KEY;
+    String formerLimit = getCacheLimit(cacheKey);
+    if (newLimit != null && !newLimit.isEmpty()
+            && !formerLimit.equals(newLimit))
+    {
+      Cache.setProperty(uniqueKey, newLimit);
+      formerLimit = newLimit;
+    }
+    return Integer.valueOf(formerLimit);
+  }
+
+  /**
+   * Method for inserting cache items for given cache key into the cache data
+   * structure
+   * 
+   * @param cacheKey
+   *          the cache key
+   * @param cacheItems
+   *          the items to add to the cache
+   */
+  public void putCache(String cacheKey, LinkedHashSet<String> newCacheItems)
+  {
+    cacheItems.put(cacheKey, newCacheItems);
+  }
+
+}
diff --git a/src/jalview/io/cache/JvCacheableInputBox.java b/src/jalview/io/cache/JvCacheableInputBox.java
new file mode 100644 (file)
index 0000000..444670b
--- /dev/null
@@ -0,0 +1,287 @@
+package jalview.io.cache;
+
+import jalview.bin.Cache;
+import jalview.util.MessageManager;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+public class JvCacheableInputBox<E> extends JComboBox<String>
+{
+
+  private static final long serialVersionUID = 5774610435079326695L;
+
+  private static final int INPUT_LIMIT = 2;
+
+  private static final int LEFT_BOARDER_WIDTH = 16;
+
+  private String cacheKey;
+
+  private AppCache appCache;
+
+  private JPanel pnlDefaultCache = new JPanel();
+
+  private JLabel lblDefaultCacheSize = new JLabel();
+
+  private JTextField txtDefaultCacheSize = new JTextField();
+
+  private JPopupMenu popup = new JPopupMenu();
+
+  private JMenuItem menuItemClearCache = new JMenuItem();
+
+  public JvCacheableInputBox(String newCacheKey)
+  {
+    super();
+    this.cacheKey = newCacheKey;
+    setEditable(true);
+    setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
+    appCache = AppCache.getInstance();
+    initCachePopupMenu();
+    initCache(newCacheKey);
+    updateCache();
+  }
+
+  /**
+   * Method for initialising cache items for a given cache key and populating
+   * the in-memory cache with persisted cache items
+   * 
+   * @param cacheKey
+   */
+  private void initCache(String cacheKey)
+  {
+    // obtain persisted cache items from properties file as a delimited string
+    String delimitedCacheStr = Cache.getProperty(cacheKey);
+    if (delimitedCacheStr == null || delimitedCacheStr.isEmpty())
+    {
+      return;
+    }
+    // convert delimited cache items to a list of strings
+    List<String> persistedCacheItems = Arrays.asList(delimitedCacheStr
+            .split(AppCache.CACHE_DELIMITER));
+
+    LinkedHashSet<String> foundCacheItems = appCache
+            .getAllCachedItemsFor(cacheKey);
+    if (foundCacheItems == null)
+    {
+      foundCacheItems = new LinkedHashSet<String>();
+    }
+    // populate memory cache
+    for (String cacheItem : persistedCacheItems)
+    {
+      foundCacheItems.add(cacheItem);
+    }
+    appCache.putCache(cacheKey, foundCacheItems);
+  }
+
+  /**
+   * Initialise this cache's pop-up menu
+   */
+  private void initCachePopupMenu()
+  {
+    pnlDefaultCache.setBackground(Color.WHITE);
+    // pad panel so as to align with other menu items
+    pnlDefaultCache.setBorder(BorderFactory.createEmptyBorder(0,
+            LEFT_BOARDER_WIDTH, 0, 0));
+    txtDefaultCacheSize.setPreferredSize(new Dimension(45, 20));
+    txtDefaultCacheSize.setFont(new java.awt.Font("Verdana", 0, 12));
+    lblDefaultCacheSize.setText(MessageManager
+            .getString("label.default_cache_size"));
+    lblDefaultCacheSize.setFont(new java.awt.Font("Verdana", 0, 12));
+    // Force input to accept only Integer entries up to length - INPUT_LIMIT
+    txtDefaultCacheSize.setDocument(new PlainDocument()
+    {
+      private static final long serialVersionUID = 1L;
+
+      @Override
+      public void insertString(int offs, String str, AttributeSet a)
+              throws BadLocationException
+      {
+        if (getLength() + str.length() <= INPUT_LIMIT && isInteger(str))
+        {
+          super.insertString(offs, str, a);
+        }
+      }
+    });
+    txtDefaultCacheSize.addKeyListener(new java.awt.event.KeyAdapter()
+    {
+      @Override
+      public void keyPressed(KeyEvent e)
+      {
+        if (e.getKeyCode() == KeyEvent.VK_ENTER)
+        {
+          e.consume();
+          updateCache();
+          closePopup();
+        }
+      }
+    });
+
+    txtDefaultCacheSize.setText(appCache.getCacheLimit(cacheKey));
+    pnlDefaultCache.add(lblDefaultCacheSize);
+    menuItemClearCache.setFont(new java.awt.Font("Verdana", 0, 12));
+    pnlDefaultCache.add(txtDefaultCacheSize);
+    menuItemClearCache.setText(MessageManager
+            .getString("action.clear_cached_items"));
+    menuItemClearCache.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        // System.out.println(">>>>> Clear cache items");
+        setSelectedItem("");
+        appCache.deleteCacheItems(cacheKey);
+        updateCache();
+      }
+    });
+
+    popup.insert(pnlDefaultCache, 0);
+    popup.add(menuItemClearCache);
+    setComponentPopupMenu(popup);
+    add(popup);
+  }
+
+  private void closePopup()
+  {
+    popup.setVisible(false);
+    popup.transferFocus();
+  }
+
+  /**
+   * Answers true if input text is an integer
+   * 
+   * @param text
+   * @return
+   */
+  static boolean isInteger(String text)
+  {
+    try
+    {
+      Integer.parseInt(text);
+      return true;
+    } catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+  /**
+   * Method called to update the cache with the last user input
+   */
+  public void updateCache()
+  {
+    SwingUtilities.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        int userLimit = txtDefaultCacheSize.getText().trim().isEmpty() ? Integer
+                .valueOf(AppCache.DEFAULT_LIMIT) : Integer
+                .valueOf(txtDefaultCacheSize.getText());
+        int cacheLimit = appCache.updateCacheLimit(cacheKey, userLimit);
+        String userInput = getUserInput();
+        if (userInput != null && !userInput.isEmpty())
+        {
+          LinkedHashSet<String> foundCache = appCache
+                  .getAllCachedItemsFor(cacheKey);
+          // remove old cache item so as to place current input at the top of
+          // the result
+          foundCache.remove(userInput);
+          foundCache.add(userInput);
+          appCache.putCache(cacheKey, foundCache);
+        }
+
+        String lastSearch = userInput;
+        if (getItemCount() > 0)
+        {
+          removeAllItems();
+        }
+        Set<String> cacheItems = appCache.getAllCachedItemsFor(cacheKey);
+        List<String> reversedCacheItems = new ArrayList<String>();
+        reversedCacheItems.addAll(cacheItems);
+        cacheItems = null;
+        Collections.reverse(reversedCacheItems);
+        if (lastSearch.isEmpty())
+        {
+          addItem("");
+        }
+
+        if (reversedCacheItems != null && !reversedCacheItems.isEmpty())
+        {
+          LinkedHashSet<String> foundCache = appCache
+                  .getAllCachedItemsFor(cacheKey);
+          boolean prune = reversedCacheItems.size() > cacheLimit;
+          int count = 1;
+          boolean limitExceeded = false;
+          for (String cacheItem : reversedCacheItems)
+          {
+            limitExceeded = (count++ > cacheLimit);
+            if (prune)
+            {
+              if (limitExceeded)
+              {
+                foundCache.remove(cacheItem);
+              }
+              else
+              {
+                addItem(cacheItem);
+              }
+            }
+            else
+            {
+              addItem(cacheItem);
+            }
+          }
+          appCache.putCache(cacheKey, foundCache);
+        }
+        setSelectedItem(lastSearch.isEmpty() ? "" : lastSearch);
+      }
+    });
+  }
+
+
+  /**
+   * This method should be called to persist the in-memory cache when this
+   * components parent frame is closed / exited
+   */
+  public void persistCache()
+  {
+    appCache.persistCache(cacheKey);
+    int userLimit = txtDefaultCacheSize.getText().trim().isEmpty() ? Integer
+            .valueOf(AppCache.DEFAULT_LIMIT) : Integer
+            .valueOf(txtDefaultCacheSize.getText());
+    appCache.updateCacheLimit(cacheKey, userLimit);
+  }
+
+  /**
+   * Method to obtain input text from the cache box
+   * 
+   * @return
+   */
+  public String getUserInput()
+  {
+    return getEditor().getItem() == null ? "" : getEditor().getItem()
+            .toString().trim();
+  }
+
+}
index c1ca1b7..9f84c16 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.io.packed;
 
+import jalview.analysis.TreeModel;
 import jalview.api.FeatureColourI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
@@ -151,8 +152,7 @@ public class JalviewDataset
       {
         // the following works because all trees are already had node/SequenceI
         // associations created.
-        jalview.analysis.NJTree njt = new jalview.analysis.NJTree(
-                al.getSequencesArray(), nf);
+        TreeModel njt = new TreeModel(al.getSequencesArray(), null, nf);
         // this just updates the displayed leaf name on the tree according to
         // the SequenceIs.
         njt.renameAssociatedNodes();
index a3781a7..d800d20 100644 (file)
@@ -20,7 +20,8 @@
  */
 package jalview.io.vamsas;
 
-import jalview.analysis.NJTree;
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.TreeModel;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
@@ -219,15 +220,17 @@ public class Tree extends DatastoreItem
     prov.getEntry(0).setUser(provEntry.getUser());
     prov.getEntry(0).setApp(provEntry.getApp());
     prov.getEntry(0).setDate(provEntry.getDate());
-    if (tp.getTree().hasOriginalSequenceData())
+
+    AlignmentView originalData = tp.getTree().getOriginalData();
+    if (originalData != null)
     {
       Input vInput = new Input();
       // LATER: check to see if tree input data is contained in this alignment -
       // or just correctly resolve the tree's seqData to the correct alignment
       // in
       // the document.
-      Vector alsqrefs = getjv2vObjs(findAlignmentSequences(jal,
-              tp.getTree().seqData.getSequences()));
+      Vector alsqrefs = getjv2vObjs(findAlignmentSequences(jal, tp
+              .getTree().getOriginalData().getSequences()));
       Object[] alsqs = new Object[alsqrefs.size()];
       alsqrefs.copyInto(alsqs);
       vInput.setObjRef(alsqs);
@@ -239,12 +242,13 @@ public class Tree extends DatastoreItem
       prov.getEntry(0).addParam(new Param());
       prov.getEntry(0).getParam(0).setName("treeType");
       prov.getEntry(0).getParam(0).setType("utf8");
-      prov.getEntry(0).getParam(0).setContent("NJ"); // TODO: type of tree is a
-      // general parameter
-      int ranges[] = tp.getTree().seqData.getVisibleContigs();
+      prov.getEntry(0).getParam(0)
+              .setContent(TreeBuilder.NEIGHBOUR_JOINING);
+      // TODO: type of tree is a general parameter
+      int ranges[] = originalData.getVisibleContigs();
       // VisibleContigs are with respect to alignment coordinates. Still need
       // offsets
-      int start = tp.getTree().seqData.getAlignmentOrigin();
+      int start = tp.getTree().getOriginalData().getAlignmentOrigin();
       for (int r = 0; r < ranges.length; r += 2)
       {
         Seg visSeg = new Seg();
@@ -370,13 +374,14 @@ public class Tree extends DatastoreItem
   /**
    * construct treenode mappings for mapped sequences
    * 
-   * @param ntree
+   * @param treeModel
    * @param newick
    * @return
    */
-  public Treenode[] makeTreeNodes(NJTree ntree, Newick newick)
+  public Treenode[] makeTreeNodes(TreeModel treeModel, Newick newick)
   {
-    Vector<SequenceNode> leaves = ntree.findLeaves(ntree.getTopNode());
+    Vector<SequenceNode> leaves = treeModel.findLeaves(treeModel
+            .getTopNode());
     Vector tnv = new Vector();
     Enumeration l = leaves.elements();
     Hashtable nodespecs = new Hashtable();
@@ -496,7 +501,7 @@ public class Tree extends DatastoreItem
     bindjvvobj(tp, tree);
     tree.setTitle(tp.getTitle());
     Newick newick = new Newick();
-    newick.setContent(tp.getTree().toString());
+    newick.setContent(tp.getTree().print());
     newick.setTitle(tp.getTitle());
     tree.addNewick(newick);
     tree.setProvenance(makeTreeProvenance(jal, tp));
index dc08b59..fdf8b58 100644 (file)
@@ -23,6 +23,7 @@ package jalview.javascript;
 import jalview.appletgui.AlignFrame;
 import jalview.bin.JalviewLite;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.structure.SelectionSource;
 
@@ -44,7 +45,7 @@ public class JsSelectionSender extends JSFunctionExec implements
 
   @Override
   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
+          HiddenColumns hidden, SelectionSource source)
   {
     // System.err.println("Testing selection event relay to jsfunction:"+_listener);
     try
index 4f833bc..c390b17 100644 (file)
@@ -126,7 +126,7 @@ public class MouseOverStructureListener extends JSFunctionExec implements
   }
 
   @Override
-  public String[] getPdbFile()
+  public String[] getStructureFiles()
   {
     return modelSet;
   }
@@ -221,8 +221,8 @@ public class MouseOverStructureListener extends JSFunctionExec implements
       ArrayList<String[]> ccomands = new ArrayList<String[]>();
       ArrayList<String> pdbfn = new ArrayList<String>();
       StructureMappingcommandSet[] colcommands = JmolCommands
-              .getColourBySequenceCommand(ssm, modelSet, sequence, sr, fr,
-                      ((AlignmentViewPanel) source).getAlignment());
+              .getColourBySequenceCommand(ssm, modelSet, sequence, sr,
+                      (AlignmentViewPanel) source);
       if (colcommands == null)
       {
         return;
@@ -298,6 +298,7 @@ public class MouseOverStructureListener extends JSFunctionExec implements
     return _listenerfn;
   }
 
+  @Override
   public void finalize() throws Throwable
   {
     jvlite = null;
index b39f4a8..88cc0a8 100755 (executable)
@@ -113,7 +113,7 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenu colourMenu = new JMenu();
 
-  protected JRadioButtonMenuItem textColour;
+  protected JMenuItem textColour;
 
   protected JCheckBoxMenuItem conservationMenuItem;
 
@@ -129,7 +129,7 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenu sort = new JMenu();
 
-  protected JMenu calculateTree = new JMenu();
+  protected JMenuItem calculateTree = new JMenuItem();
 
   protected JCheckBoxMenuItem padGapsMenuitem = new JCheckBoxMenuItem();
 
@@ -523,36 +523,6 @@ public class GAlignFrame extends JInternalFrame
         pairwiseAlignmentMenuItem_actionPerformed(e);
       }
     });
-    JMenuItem PCAMenuItem = new JMenuItem(
-            MessageManager.getString("label.principal_component_analysis"));
-    PCAMenuItem.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        PCAMenuItem_actionPerformed(e);
-      }
-    });
-    JMenuItem averageDistanceTreeMenuItem = new JMenuItem(
-            MessageManager.getString("label.average_distance_identity"));
-    averageDistanceTreeMenuItem.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        averageDistanceTreeMenuItem_actionPerformed(e);
-      }
-    });
-    JMenuItem neighbourTreeMenuItem = new JMenuItem(
-            MessageManager.getString("label.neighbour_joining_identity"));
-    neighbourTreeMenuItem.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        neighbourTreeMenuItem_actionPerformed(e);
-      }
-    });
 
     this.getContentPane().setLayout(new BorderLayout());
     alignFrameMenuBar.setFont(new java.awt.Font("Verdana", 0, 11));
@@ -563,27 +533,6 @@ public class GAlignFrame extends JInternalFrame
     outputTextboxMenu.setText(MessageManager
             .getString("label.out_to_textbox"));
 
-
-    JMenuItem avDistanceTreeBlosumMenuItem = new JMenuItem(
-            MessageManager.getString("label.average_distance_blosum62"));
-    avDistanceTreeBlosumMenuItem.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        avTreeBlosumMenuItem_actionPerformed(e);
-      }
-    });
-    JMenuItem njTreeBlosumMenuItem = new JMenuItem(
-            MessageManager.getString("label.neighbour_blosum62"));
-    njTreeBlosumMenuItem.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        njTreeBlosumMenuItem_actionPerformed(e);
-      }
-    });
     annotationPanelMenuItem.setActionCommand("");
     annotationPanelMenuItem.setText(MessageManager
             .getString("label.show_annotations"));
@@ -975,7 +924,7 @@ public class GAlignFrame extends JInternalFrame
     });
 
     JMenuItem createGroup = new JMenuItem(
-            MessageManager.getString("action.create_groups"));
+            MessageManager.getString("action.create_group"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit
             .getDefaultToolkit().getMenuShortcutKeyMask(), false);
     al = new ActionListener()
@@ -1203,7 +1152,7 @@ public class GAlignFrame extends JInternalFrame
       @Override
       public void menuSelected(MenuEvent e)
       {
-        buildTreeMenu();
+        buildTreeSortMenu();
       }
 
       @Override
@@ -1240,8 +1189,8 @@ public class GAlignFrame extends JInternalFrame
     });
     sortByAnnotScore.setVisible(false);
 
-    calculateTree
-            .setText(MessageManager.getString("action.calculate_tree"));
+    calculateTree.setText(MessageManager
+            .getString("action.calculate_tree_pca"));
 
     padGapsMenuitem.setText(MessageManager.getString("label.pad_gaps"));
     padGapsMenuitem.setState(jalview.bin.Cache
@@ -1653,6 +1602,7 @@ public class GAlignFrame extends JInternalFrame
 
     formatMenu.setText(MessageManager.getString("action.format"));
     JMenu selectMenu = new JMenu(MessageManager.getString("action.select"));
+
     idRightAlign.setText(MessageManager
             .getString("label.right_align_sequence_id"));
     idRightAlign.addActionListener(new ActionListener()
@@ -1842,7 +1792,6 @@ public class GAlignFrame extends JInternalFrame
     calculateMenu.add(calculateTree);
     calculateMenu.addSeparator();
     calculateMenu.add(pairwiseAlignmentMenuItem);
-    calculateMenu.add(PCAMenuItem);
     calculateMenu.addSeparator();
     calculateMenu.add(showTranslation);
     calculateMenu.add(showReverse);
@@ -1921,8 +1870,8 @@ public class GAlignFrame extends JInternalFrame
       }
     });
 
-    textColour = new JRadioButtonMenuItem(
-            MessageManager.getString("action.set_text_colour"));
+    textColour = new JMenuItem(
+            MessageManager.getString("label.text_colour"));
     textColour.addActionListener(new ActionListener()
     {
       @Override
@@ -2323,26 +2272,10 @@ public class GAlignFrame extends JInternalFrame
   {
   }
 
-  protected void PCAMenuItem_actionPerformed(ActionEvent e)
-  {
-  }
-
-  protected void averageDistanceTreeMenuItem_actionPerformed(ActionEvent e)
-  {
-  }
-
   protected void neighbourTreeMenuItem_actionPerformed(ActionEvent e)
   {
   }
 
-  protected void njTreeBlosumMenuItem_actionPerformed(ActionEvent e)
-  {
-  }
-
-  protected void avTreeBlosumMenuItem_actionPerformed(ActionEvent e)
-  {
-  }
-
   protected void conservationMenuItem_actionPerformed(boolean selected)
   {
   }
@@ -2627,7 +2560,7 @@ public class GAlignFrame extends JInternalFrame
 
   }
 
-  public void buildTreeMenu()
+  public void buildTreeSortMenu()
   {
 
   }
index 63ecdaf..3e3691c 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.jbgui;
 import jalview.api.AlignmentViewPanel;
 import jalview.io.FileFormatException;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.awt.FlowLayout;
 import java.awt.Toolkit;
@@ -99,6 +100,10 @@ public class GDesktop extends JFrame
 
   JMenuItem garbageCollect = new JMenuItem();
 
+  protected JMenuItem groovyShell;
+
+  protected JCheckBoxMenuItem experimentalFeatures;
+
   protected JCheckBoxMenuItem showConsole = new JCheckBoxMenuItem();
 
   protected JCheckBoxMenuItem showNews = new JCheckBoxMenuItem();
@@ -119,7 +124,7 @@ public class GDesktop extends JFrame
       e.printStackTrace();
     }
 
-    if (!new jalview.util.Platform().isAMac())
+    if (!Platform.isAMac())
     {
       FileMenu.setMnemonic('F');
       inputLocalFileMenuItem.setMnemonic('L');
@@ -374,6 +379,30 @@ public class GDesktop extends JFrame
         showNews_actionPerformed(e);
       }
     });
+    groovyShell = new JMenuItem();
+    groovyShell.setText(MessageManager.getString("label.groovy_console"));
+    groovyShell.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        groovyShell_actionPerformed();
+      }
+    });
+    experimentalFeatures = new JCheckBoxMenuItem();
+    experimentalFeatures.setText(MessageManager
+            .getString("label.show_experimental"));
+    experimentalFeatures.setToolTipText(MessageManager
+            .getString("label.show_experimental_tip"));
+    experimentalFeatures.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showExperimental_actionPerformed(experimentalFeatures.isSelected());
+      }
+    });
+
     snapShotWindow.setText(MessageManager.getString("label.take_snapshot"));
     snapShotWindow.addActionListener(new ActionListener()
     {
@@ -410,6 +439,8 @@ public class GDesktop extends JFrame
     toolsMenu.add(showConsole);
     toolsMenu.add(showNews);
     toolsMenu.add(garbageCollect);
+    toolsMenu.add(groovyShell);
+    toolsMenu.add(experimentalFeatures);
     // toolsMenu.add(snapShotWindow);
     inputMenu.add(inputLocalFileMenuItem);
     inputMenu.add(inputURLMenuItem);
@@ -421,6 +452,14 @@ public class GDesktop extends JFrame
     // inputMenu.add(vamsasLoad);
   }
 
+  protected void showExperimental_actionPerformed(boolean selected)
+  {
+  }
+
+  protected void groovyShell_actionPerformed()
+  {
+  }
+
   protected void snapShotWindow_actionPerformed(ActionEvent e)
   {
     // TODO Auto-generated method stub
index fef4568..c335b33 100755 (executable)
@@ -24,6 +24,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
+import jalview.io.cache.JvCacheableInputBox;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -38,12 +39,11 @@ import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 import javax.swing.event.CaretEvent;
 import javax.swing.event.CaretListener;
+import javax.swing.text.JTextComponent;
 
 public class GFinder extends JPanel
 {
@@ -57,11 +57,9 @@ public class GFinder extends JPanel
 
   GridLayout gridLayout1 = new GridLayout();
 
-  protected JButton createNewGroup = new JButton();
+  protected JButton createFeatures = new JButton();
 
-  JScrollPane jScrollPane1 = new JScrollPane();
-
-  protected JTextArea textfield = new JTextArea();
+  protected JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<String>(getCacheKey());
 
   BorderLayout mainBorderLayout = new BorderLayout();
 
@@ -81,6 +79,8 @@ public class GFinder extends JPanel
 
   GridLayout optionsGridLayout = new GridLayout();
 
+  private static final String FINDER_CACHE_KEY = "CACHE.FINDER";
+
   public GFinder()
   {
     try
@@ -121,22 +121,21 @@ public class GFinder extends JPanel
     gridLayout1.setHgap(0);
     gridLayout1.setRows(3);
     gridLayout1.setVgap(2);
-    createNewGroup.setEnabled(false);
-    createNewGroup.setFont(new java.awt.Font("Verdana", 0, 12));
-    createNewGroup.setMargin(new Insets(0, 0, 0, 0));
-    createNewGroup.setText(MessageManager.getString("label.new_feature"));
-    createNewGroup.addActionListener(new java.awt.event.ActionListener()
+    createFeatures.setEnabled(false);
+    createFeatures.setFont(new java.awt.Font("Verdana", 0, 12));
+    createFeatures.setMargin(new Insets(0, 0, 0, 0));
+    createFeatures.setText(MessageManager.getString("label.new_feature"));
+    createFeatures.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        createNewGroup_actionPerformed(e);
+        createFeatures_actionPerformed();
       }
     });
-    textfield.setFont(new java.awt.Font("Verdana", Font.PLAIN, 12));
-    textfield.setText("");
-    textfield.setLineWrap(true);
-    textfield.addCaretListener(new CaretListener()
+    searchBox.setFont(new java.awt.Font("Verdana", Font.PLAIN, 12));
+    ((JTextComponent) searchBox.getEditor().getEditorComponent())
+            .addCaretListener(new CaretListener()
     {
       @Override
       public void caretUpdate(CaretEvent e)
@@ -144,15 +143,15 @@ public class GFinder extends JPanel
         textfield_caretUpdate(e);
       }
     });
-    textfield.addKeyListener(new java.awt.event.KeyAdapter()
-    {
-      @Override
-      public void keyPressed(KeyEvent e)
-      {
-        textfield_keyPressed(e);
-      }
-    });
-
+    searchBox.getEditor().getEditorComponent()
+            .addKeyListener(new java.awt.event.KeyAdapter()
+            {
+              @Override
+              public void keyPressed(KeyEvent e)
+              {
+                textfield_keyPressed(e);
+              }
+            });
     mainBorderLayout.setHgap(5);
     mainBorderLayout.setVgap(5);
     jPanel4.setLayout(borderLayout2);
@@ -166,14 +165,13 @@ public class GFinder extends JPanel
 
     actionsPanel.add(findNext, null);
     actionsPanel.add(findAll, null);
-    actionsPanel.add(createNewGroup, null);
+    actionsPanel.add(createFeatures, null);
     this.add(jLabelFind, java.awt.BorderLayout.WEST);
     this.add(actionsPanel, java.awt.BorderLayout.EAST);
     this.add(jPanel2, java.awt.BorderLayout.SOUTH);
     this.add(jPanel3, java.awt.BorderLayout.NORTH);
     this.add(jPanel4, java.awt.BorderLayout.CENTER);
-    jPanel4.add(jScrollPane1, java.awt.BorderLayout.NORTH);
-    jScrollPane1.getViewport().add(textfield);
+    jPanel4.add(searchBox, java.awt.BorderLayout.NORTH);
 
     JPanel optionsPanel = new JPanel();
 
@@ -187,37 +185,41 @@ public class GFinder extends JPanel
     jPanel4.add(optionsPanel, java.awt.BorderLayout.WEST);
   }
 
-  protected void findNext_actionPerformed(ActionEvent e)
+  protected void textfield_keyPressed(KeyEvent e)
   {
+    if (e.getKeyCode() == KeyEvent.VK_ENTER)
+    {
+      if (!searchBox.isPopupVisible())
+      {
+        e.consume();
+        findNext_actionPerformed(null);
+      }
+    }
   }
 
-  protected void findAll_actionPerformed(ActionEvent e)
+  protected void findNext_actionPerformed(ActionEvent e)
   {
   }
 
-  protected void textfield_keyPressed(KeyEvent e)
+  protected void findAll_actionPerformed(ActionEvent e)
   {
-    if (e.getKeyCode() == KeyEvent.VK_ENTER)
-    {
-      e.consume();
-      findNext_actionPerformed(null);
-    }
   }
 
-  public void createNewGroup_actionPerformed(ActionEvent e)
+
+  public void createFeatures_actionPerformed()
   {
   }
 
   public void textfield_caretUpdate(CaretEvent e)
   {
-    if (textfield.getText().indexOf(">") > -1)
+    if (searchBox.getUserInput().indexOf(">") > -1)
     {
       SwingUtilities.invokeLater(new Runnable()
       {
         @Override
         public void run()
         {
-          String str = textfield.getText();
+          String str = searchBox.getUserInput();
           AlignmentI al = null;
           try
           {
@@ -232,10 +234,27 @@ public class GFinder extends JPanel
                     jalview.util.Comparison.GapChars, al.getSequenceAt(0)
                             .getSequenceAsString());
 
-            textfield.setText(str);
           }
         }
       });
     }
   }
+
+
+
+
+
+  /**
+   * Returns unique key used for storing Finder cache items in the cache data
+   * structure
+   * 
+   * @return
+   */
+  public String getCacheKey()
+  {
+    return FINDER_CACHE_KEY;
+  }
+
+
+
 }
index 8c893a2..5c15e80 100755 (executable)
@@ -63,6 +63,8 @@ public class GFontChooser extends JPanel
 
   protected JCheckBox scaleAsCdna = new JCheckBox();
 
+  protected JCheckBox fontAsCdna = new JCheckBox();
+
   /**
    * Creates a new GFontChooser object.
    */
@@ -98,9 +100,10 @@ public class GFontChooser extends JPanel
     fontSize.setPreferredSize(new Dimension(50, 21));
     fontSize.addActionListener(new java.awt.event.ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        fontSize_actionPerformed(e);
+        fontSize_actionPerformed();
       }
     });
 
@@ -109,9 +112,10 @@ public class GFontChooser extends JPanel
     fontStyle.setPreferredSize(new Dimension(90, 21));
     fontStyle.addActionListener(new java.awt.event.ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        fontStyle_actionPerformed(e);
+        fontStyle_actionPerformed();
       }
     });
 
@@ -132,9 +136,10 @@ public class GFontChooser extends JPanel
     fontName.setPreferredSize(new Dimension(180, 21));
     fontName.addActionListener(new java.awt.event.ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        fontName_actionPerformed(e);
+        fontName_actionPerformed();
       }
     });
 
@@ -142,9 +147,10 @@ public class GFontChooser extends JPanel
     ok.setFont(VERDANA_11PT);
     ok.addActionListener(new java.awt.event.ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        ok_actionPerformed(e);
+        ok_actionPerformed();
       }
     });
 
@@ -152,9 +158,10 @@ public class GFontChooser extends JPanel
     cancel.setFont(VERDANA_11PT);
     cancel.addActionListener(new java.awt.event.ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        cancel_actionPerformed(e);
+        cancel_actionPerformed();
       }
     });
 
@@ -162,37 +169,57 @@ public class GFontChooser extends JPanel
     defaultButton.setText(MessageManager.getString("label.set_as_default"));
     defaultButton.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        defaultButton_actionPerformed(e);
+        defaultButton_actionPerformed();
       }
     });
 
     smoothFont.setFont(JvSwingUtils.getLabelFont());
     smoothFont.setOpaque(false);
     smoothFont.setText(MessageManager.getString("label.anti_alias_fonts"));
-    smoothFont.setBounds(new Rectangle(41, 65, 260, 23));
+    smoothFont.setBounds(new Rectangle(1, 65, 300, 23));
     smoothFont.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        smoothFont_actionPerformed(e);
+        smoothFont_actionPerformed();
       }
     });
 
     /*
-     * Scale protein as cDNA is only visible in SplitFrame protein alignment
+     * Scale protein as cDNA is only visible in SplitFrame
      */
     scaleAsCdna.setVisible(false);
     scaleAsCdna.setFont(JvSwingUtils.getLabelFont());
     scaleAsCdna.setOpaque(false);
     scaleAsCdna.setText(MessageManager.getString("label.scale_as_cdna"));
-    scaleAsCdna.setBounds(new Rectangle(41, 85, 260, 23));
+    scaleAsCdna.setBounds(new Rectangle(1, 85, 300, 23));
     scaleAsCdna.addActionListener(new ActionListener()
     {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        scaleAsCdna_actionPerformed();
+      }
+    });
+
+    /*
+     * Same font for cDNA/peptide is only visible in SplitFrame
+     */
+    fontAsCdna.setVisible(false);
+    fontAsCdna.setFont(JvSwingUtils.getLabelFont());
+    fontAsCdna.setOpaque(false);
+    fontAsCdna.setText(MessageManager.getString("label.font_as_cdna"));
+    fontAsCdna.setBounds(new Rectangle(1, 105, 350, 23));
+    fontAsCdna.addActionListener(new ActionListener()
+    {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
-        scaleAsCdna_actionPerformed(e);
+        mirrorFonts_actionPerformed();
       }
     });
 
@@ -239,85 +266,53 @@ public class GFontChooser extends JPanel
      */
     JPanel jPanel4 = new JPanel();
     jPanel4.setOpaque(false);
-    jPanel4.setBounds(new Rectangle(24, 112, 300, 35));
+    jPanel4.setBounds(new Rectangle(24, 132, 300, 35));
     jPanel4.add(defaultButton);
     jPanel4.add(ok);
     jPanel4.add(cancel);
 
     this.add(smoothFont);
     this.add(scaleAsCdna);
+    this.add(fontAsCdna);
     this.add(jPanel3, null);
     this.add(jPanel2, null);
     this.add(jPanel4);
     this.add(jPanel1, null);
   }
 
-  protected void scaleAsCdna_actionPerformed(ActionEvent e)
+  protected void mirrorFonts_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void ok_actionPerformed(ActionEvent e)
+  protected void scaleAsCdna_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void cancel_actionPerformed(ActionEvent e)
+  protected void ok_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void fontName_actionPerformed(ActionEvent e)
+  protected void cancel_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void fontSize_actionPerformed(ActionEvent e)
+  protected void fontName_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void fontStyle_actionPerformed(ActionEvent e)
+  protected void fontSize_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  public void defaultButton_actionPerformed(ActionEvent e)
+  protected void fontStyle_actionPerformed()
   {
   }
 
-  public void smoothFont_actionPerformed(ActionEvent e)
+  public void defaultButton_actionPerformed()
   {
+  }
 
+  protected void smoothFont_actionPerformed()
+  {
   }
 }
index 0bc6cac..3715acc 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.util.MessageManager;
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.FlowLayout;
+import java.awt.Font;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -43,54 +44,20 @@ import javax.swing.event.MenuListener;
 
 public class GPCAPanel extends JInternalFrame
 {
-  JPanel jPanel2 = new JPanel();
+  private static final Font VERDANA_12 = new Font("Verdana", 0, 12);
 
-  JLabel jLabel1 = new JLabel();
+  protected JComboBox<String> xCombobox = new JComboBox<String>();
 
-  JLabel jLabel2 = new JLabel();
+  protected JComboBox<String> yCombobox = new JComboBox<String>();
 
-  JLabel jLabel3 = new JLabel();
+  protected JComboBox<String> zCombobox = new JComboBox<String>();
 
-  protected JComboBox xCombobox = new JComboBox();
-
-  protected JComboBox yCombobox = new JComboBox();
-
-  protected JComboBox zCombobox = new JComboBox();
-
-  protected JButton resetButton = new JButton();
-
-  FlowLayout flowLayout1 = new FlowLayout();
-
-  BorderLayout borderLayout1 = new BorderLayout();
-
-  JMenuBar jMenuBar1 = new JMenuBar();
-
-  JMenu fileMenu = new JMenu();
-
-  JMenu saveMenu = new JMenu();
-
-  protected JMenu scoreMatrixMenu = new JMenu();
-
-  JMenuItem eps = new JMenuItem();
-
-  JMenuItem png = new JMenuItem();
-
-  JMenuItem print = new JMenuItem();
-
-  JMenuItem outputValues = new JMenuItem();
-
-  JMenuItem outputPoints = new JMenuItem();
-
-  JMenuItem outputProjPoints = new JMenuItem();
+  protected JMenu scoreModelMenu = new JMenu();
 
   protected JMenu viewMenu = new JMenu();
 
   protected JCheckBoxMenuItem showLabels = new JCheckBoxMenuItem();
 
-  JMenuItem bgcolour = new JMenuItem();
-
-  JMenuItem originalSeqData = new JMenuItem();
-
   protected JMenu associateViewsMenu = new JMenu();
 
   protected JMenu calcSettings = new JMenu();
@@ -99,12 +66,8 @@ public class GPCAPanel extends JInternalFrame
 
   protected JCheckBoxMenuItem protSetting = new JCheckBoxMenuItem();
 
-  protected JCheckBoxMenuItem jvVersionSetting = new JCheckBoxMenuItem();
-
   protected JLabel statusBar = new JLabel();
 
-  protected GridLayout statusPanelLayout = new GridLayout();
-
   protected JPanel statusPanel = new JPanel();
 
   public GPCAPanel()
@@ -123,49 +86,55 @@ public class GPCAPanel extends JInternalFrame
       yCombobox.addItem("dim " + i);
       zCombobox.addItem("dim " + i);
     }
-
-    setJMenuBar(jMenuBar1);
   }
 
   private void jbInit() throws Exception
   {
-    this.getContentPane().setLayout(borderLayout1);
-    jPanel2.setLayout(flowLayout1);
-    jLabel1.setFont(new java.awt.Font("Verdana", 0, 12));
+    this.getContentPane().setLayout(new BorderLayout());
+    JPanel jPanel2 = new JPanel();
+    jPanel2.setLayout(new FlowLayout());
+    JLabel jLabel1 = new JLabel();
+    jLabel1.setFont(VERDANA_12);
     jLabel1.setText("x=");
-    jLabel2.setFont(new java.awt.Font("Verdana", 0, 12));
+    JLabel jLabel2 = new JLabel();
+    jLabel2.setFont(VERDANA_12);
     jLabel2.setText("y=");
-    jLabel3.setFont(new java.awt.Font("Verdana", 0, 12));
+    JLabel jLabel3 = new JLabel();
+    jLabel3.setFont(VERDANA_12);
     jLabel3.setText("z=");
     jPanel2.setBackground(Color.white);
     jPanel2.setBorder(null);
-    zCombobox.setFont(new java.awt.Font("Verdana", 0, 12));
-    zCombobox.addActionListener(new java.awt.event.ActionListener()
+    zCombobox.setFont(VERDANA_12);
+    zCombobox.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         zCombobox_actionPerformed(e);
       }
     });
-    yCombobox.setFont(new java.awt.Font("Verdana", 0, 12));
-    yCombobox.addActionListener(new java.awt.event.ActionListener()
+    yCombobox.setFont(VERDANA_12);
+    yCombobox.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         yCombobox_actionPerformed(e);
       }
     });
-    xCombobox.setFont(new java.awt.Font("Verdana", 0, 12));
-    xCombobox.addActionListener(new java.awt.event.ActionListener()
+    xCombobox.setFont(VERDANA_12);
+    xCombobox.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         xCombobox_actionPerformed(e);
       }
     });
-    resetButton.setFont(new java.awt.Font("Verdana", 0, 12));
+    JButton resetButton = new JButton();
+    resetButton.setFont(VERDANA_12);
     resetButton.setText(MessageManager.getString("action.reset"));
-    resetButton.addActionListener(new java.awt.event.ActionListener()
+    resetButton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -173,51 +142,64 @@ public class GPCAPanel extends JInternalFrame
         resetButton_actionPerformed(e);
       }
     });
+    JMenu fileMenu = new JMenu();
     fileMenu.setText(MessageManager.getString("action.file"));
+    JMenu saveMenu = new JMenu();
     saveMenu.setText(MessageManager.getString("action.save_as"));
-    eps.setText("EPS");
+    JMenuItem eps = new JMenuItem("EPS");
     eps.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         eps_actionPerformed(e);
       }
     });
-    png.setText("PNG");
+    JMenuItem png = new JMenuItem("PNG");
     png.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         png_actionPerformed(e);
       }
     });
+    JMenuItem outputValues = new JMenuItem();
     outputValues.setText(MessageManager.getString("label.output_values"));
     outputValues.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         outputValues_actionPerformed(e);
       }
     });
+    JMenuItem outputPoints = new JMenuItem();
     outputPoints.setText(MessageManager.getString("label.output_points"));
     outputPoints.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         outputPoints_actionPerformed(e);
       }
     });
+    JMenuItem outputProjPoints = new JMenuItem();
     outputProjPoints.setText(MessageManager
             .getString("label.output_transformed_points"));
     outputProjPoints.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         outputProjPoints_actionPerformed(e);
       }
     });
+    JMenuItem print = new JMenuItem();
+    print.setText(MessageManager.getString("action.print"));
     print.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         print_actionPerformed(e);
@@ -226,32 +208,38 @@ public class GPCAPanel extends JInternalFrame
     viewMenu.setText(MessageManager.getString("action.view"));
     viewMenu.addMenuListener(new MenuListener()
     {
+      @Override
       public void menuSelected(MenuEvent e)
       {
         viewMenu_menuSelected();
       }
 
+      @Override
       public void menuDeselected(MenuEvent e)
       {
       }
 
+      @Override
       public void menuCanceled(MenuEvent e)
       {
       }
     });
-    scoreMatrixMenu.setText(MessageManager
+    scoreModelMenu.setText(MessageManager
             .getString("label.select_score_model"));
-    scoreMatrixMenu.addMenuListener(new MenuListener()
+    scoreModelMenu.addMenuListener(new MenuListener()
     {
+      @Override
       public void menuSelected(MenuEvent e)
       {
-        scoreMatrix_menuSelected();
+        scoreModel_menuSelected();
       }
 
+      @Override
       public void menuDeselected(MenuEvent e)
       {
       }
 
+      @Override
       public void menuCanceled(MenuEvent e)
       {
       }
@@ -259,23 +247,27 @@ public class GPCAPanel extends JInternalFrame
     showLabels.setText(MessageManager.getString("label.show_labels"));
     showLabels.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         showLabels_actionPerformed(e);
       }
     });
-    print.setText(MessageManager.getString("action.print"));
+    JMenuItem bgcolour = new JMenuItem();
     bgcolour.setText(MessageManager.getString("action.background_colour"));
     bgcolour.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         bgcolour_actionPerformed(e);
       }
     });
+    JMenuItem originalSeqData = new JMenuItem();
     originalSeqData.setText(MessageManager.getString("label.input_data"));
     originalSeqData.addActionListener(new ActionListener()
     {
+      @Override
       public void actionPerformed(ActionEvent e)
       {
         originalSeqData_actionPerformed(e);
@@ -305,22 +297,12 @@ public class GPCAPanel extends JInternalFrame
         protSetting_actionPerfomed(arg0);
       }
     });
-    jvVersionSetting.setText(MessageManager
-            .getString("label.jalview_pca_calculation"));
-    jvVersionSetting.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent arg0)
-      {
-        jvVersionSetting_actionPerfomed(arg0);
-      }
-    });
-    calcSettings.add(jvVersionSetting);
+
     calcSettings.add(nuclSetting);
     calcSettings.add(protSetting);
-    calcSettings.add(scoreMatrixMenu);
-    statusPanel.setLayout(statusPanelLayout);
-    statusBar.setFont(new java.awt.Font("Verdana", 0, 12));
+    calcSettings.add(scoreModelMenu);
+    statusPanel.setLayout(new GridLayout());
+    statusBar.setFont(VERDANA_12);
     // statusPanel.setBackground(Color.lightGray);
     // statusBar.setBackground(Color.lightGray);
     // statusPanel.add(statusBar, null);
@@ -335,9 +317,12 @@ public class GPCAPanel extends JInternalFrame
     jPanel2.add(jLabel3, null);
     jPanel2.add(zCombobox, null);
     jPanel2.add(resetButton, null);
+
+    JMenuBar jMenuBar1 = new JMenuBar();
     jMenuBar1.add(fileMenu);
     jMenuBar1.add(viewMenu);
     jMenuBar1.add(calcSettings);
+    setJMenuBar(jMenuBar1);
     fileMenu.add(saveMenu);
     fileMenu.add(outputValues);
     fileMenu.add(print);
@@ -351,7 +336,7 @@ public class GPCAPanel extends JInternalFrame
     viewMenu.add(associateViewsMenu);
   }
 
-  protected void scoreMatrix_menuSelected()
+  protected void scoreModel_menuSelected()
   {
     // TODO Auto-generated method stub
 
@@ -438,10 +423,4 @@ public class GPCAPanel extends JInternalFrame
   {
 
   }
-
-  protected void jvVersionSetting_actionPerfomed(ActionEvent arg0)
-  {
-    // TODO Auto-generated method stub
-
-  }
 }
index dda06b4..1ad95dd 100755 (executable)
@@ -101,6 +101,8 @@ public class GPreferences extends JPanel
 
   protected JComboBox<String> fontNameCB = new JComboBox<String>();
 
+  protected JCheckBox showOccupancy = new JCheckBox();
+
   protected JCheckBox showUnconserved = new JCheckBox();
 
   protected JCheckBox idItalics = new JCheckBox();
@@ -1174,6 +1176,13 @@ public class GPreferences extends JPanel
     identity.setHorizontalTextPosition(SwingConstants.LEFT);
     identity.setSelected(true);
     identity.setText(MessageManager.getString("label.consensus"));
+    showOccupancy.setFont(LABEL_FONT);
+    showOccupancy.setEnabled(false);
+    showOccupancy.setHorizontalAlignment(SwingConstants.RIGHT);
+    showOccupancy.setHorizontalTextPosition(SwingConstants.LEFT);
+    showOccupancy.setSelected(true);
+    showOccupancy.setText(MessageManager.getString("label.occupancy"));
+
     JLabel showGroupbits = new JLabel();
     showGroupbits.setFont(LABEL_FONT);
     showGroupbits.setHorizontalAlignment(SwingConstants.RIGHT);
@@ -1228,10 +1237,10 @@ public class GPreferences extends JPanel
             .getString("label.database_references"));
     annotations.setFont(LABEL_FONT);
     annotations.setHorizontalAlignment(SwingConstants.RIGHT);
-    annotations.setHorizontalTextPosition(SwingConstants.LEADING);
+    annotations.setHorizontalTextPosition(SwingConstants.LEFT);
     annotations.setSelected(true);
     annotations.setText(MessageManager.getString("label.show_annotations"));
-    annotations.setBounds(new Rectangle(169, 12, 200, 23));
+    // annotations.setBounds(new Rectangle(169, 12, 200, 23));
     annotations.addActionListener(new ActionListener()
     {
       @Override
@@ -1358,11 +1367,13 @@ public class GPreferences extends JPanel
     sortAutocalc.setBounds(new Rectangle(290, 285, 165, 21));
 
     JPanel annsettingsPanel = new JPanel();
-    annsettingsPanel.setBounds(new Rectangle(173, 34, 320, 75));
+    annsettingsPanel.setBounds(new Rectangle(173, 13, 320, 96));
     annsettingsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
     annsettingsPanel.setBorder(new EtchedBorder());
     visualTab.add(annsettingsPanel);
     Border jb = new EmptyBorder(1, 1, 4, 5);
+    annotations.setBorder(jb);
+    showOccupancy.setBorder(jb);
     quality.setBorder(jb);
     conservation.setBorder(jb);
     identity.setBorder(jb);
@@ -1374,17 +1385,26 @@ public class GPreferences extends JPanel
     showConsensLogo.setBorder(jb);
 
     JPanel autoAnnotSettings = new JPanel();
-    autoAnnotSettings.setLayout(new GridLayout(3, 3));
     annsettingsPanel.add(autoAnnotSettings);
+    autoAnnotSettings.setLayout(new GridLayout(0, 2));
+    autoAnnotSettings.add(annotations);
     autoAnnotSettings.add(quality);
+    // second row of autoannotation box
+    autoAnnotSettings = new JPanel();
+    annsettingsPanel.add(autoAnnotSettings);
+
+    autoAnnotSettings.setLayout(new GridLayout(0, 3));
     autoAnnotSettings.add(conservation);
     autoAnnotSettings.add(identity);
+    autoAnnotSettings.add(showOccupancy);
     autoAnnotSettings.add(showGroupbits);
     autoAnnotSettings.add(showGroupConservation);
     autoAnnotSettings.add(showGroupConsensus);
     autoAnnotSettings.add(showConsensbits);
     autoAnnotSettings.add(showConsensHistogram);
     autoAnnotSettings.add(showConsensLogo);
+    
+    
 
     JPanel tooltipSettings = new JPanel();
     tooltipSettings.setBorder(new TitledBorder(MessageManager
@@ -1433,7 +1453,6 @@ public class GPreferences extends JPanel
     jPanel2.add(sortAnnLabel);
     jPanel2.add(startupCheckbox);
     visualTab.add(jPanel2);
-    visualTab.add(annotations);
     visualTab.add(startupFileTextfield);
     visualTab.add(sortby);
     visualTab.add(sortAnnBy);
index 3a064d2..041fefd 100644 (file)
@@ -33,6 +33,7 @@ import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
 import java.awt.CardLayout;
+import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.GridLayout;
@@ -54,11 +55,14 @@ import javax.swing.JComboBox;
 import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
+import javax.swing.JList;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
 import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.DocumentEvent;
@@ -488,6 +492,19 @@ public abstract class GStructureChooser extends JPanel implements
     });
 
     cmb_filterOption.addItemListener(this);
+
+    // add CustomComboSeparatorsRenderer to filter option combo-box
+    cmb_filterOption.setRenderer(new CustomComboSeparatorsRenderer(
+            (ListCellRenderer<Object>) cmb_filterOption.getRenderer())
+    {
+      @Override
+      protected boolean addSeparatorAfter(JList list, FilterOption value,
+              int index)
+      {
+        return value.isAddSeparatorAfter();
+      }
+    });
+
     chk_invertFilter.addItemListener(this);
 
     pnl_actions.add(chk_rememberSettings);
@@ -643,11 +660,28 @@ public abstract class GStructureChooser extends JPanel implements
 
     private String view;
 
-    public FilterOption(String name, String value, String view)
+    private boolean addSeparatorAfter;
+
+    /**
+     * Model for structure filter option
+     * 
+     * @param name
+     *          - the name of the Option
+     * @param value
+     *          - the value of the option
+     * @param view
+     *          - the category of the filter option
+     * @param addSeparatorAfter
+     *          - if true, a horizontal separator is rendered immediately after
+     *          this filter option, otherwise
+     */
+    public FilterOption(String name, String value, String view,
+            boolean addSeparatorAfter)
     {
       this.name = name;
       this.value = value;
       this.view = view;
+      this.addSeparatorAfter = addSeparatorAfter;
     }
 
     public String getName()
@@ -685,6 +719,16 @@ public abstract class GStructureChooser extends JPanel implements
     {
       return this.name;
     }
+
+    public boolean isAddSeparatorAfter()
+    {
+      return addSeparatorAfter;
+    }
+
+    public void setAddSeparatorAfter(boolean addSeparatorAfter)
+    {
+      this.addSeparatorAfter = addSeparatorAfter;
+    }
   }
 
   /**
@@ -800,6 +844,54 @@ public abstract class GStructureChooser extends JPanel implements
     return cmb_filterOption;
   }
 
+  /**
+   * Custom ListCellRenderer for adding a separator between different categories
+   * of structure chooser filter option drop-down.
+   * 
+   * @author tcnofoegbu
+   *
+   */
+  public abstract class CustomComboSeparatorsRenderer implements
+          ListCellRenderer<Object>
+  {
+    private ListCellRenderer<Object> regent;
+
+    private JPanel separatorPanel = new JPanel(new BorderLayout());
+
+    private JSeparator jSeparator = new JSeparator();
+
+    public CustomComboSeparatorsRenderer(ListCellRenderer<Object> listCellRenderer)
+    {
+      this.regent = listCellRenderer;
+    }
+
+    @Override
+    public Component getListCellRendererComponent(JList list,
+            Object value,
+            int index, boolean isSelected, boolean cellHasFocus)
+    {
+
+      Component comp = regent.getListCellRendererComponent(list, value,
+              index, isSelected, cellHasFocus);
+      if (index != -1
+              && addSeparatorAfter(list, (FilterOption) value, index))
+      { 
+        separatorPanel.removeAll();
+        separatorPanel.add(comp, BorderLayout.CENTER);
+        separatorPanel.add(jSeparator, BorderLayout.SOUTH);
+        return separatorPanel;
+      }
+      else
+      {
+        return comp;
+      }
+    }
+
+    protected abstract boolean addSeparatorAfter(JList list,
+            FilterOption value,
+            int index);
+  }
+
   protected abstract void stateChanged(ItemEvent e);
 
   protected abstract void ok_ActionPerformed();
@@ -816,4 +908,4 @@ public abstract class GStructureChooser extends JPanel implements
   public abstract void tabRefresh();
 
   public abstract void validateSelections();
-}
+}
\ No newline at end of file
index 6b89ab4..d8f3f61 100644 (file)
@@ -170,8 +170,8 @@ public abstract class GStructureViewer extends JInternalFrame implements
       }
     });
     alignStructs = new JMenuItem();
-    alignStructs
-            .setText(MessageManager.getString("label.align_structures"));
+    alignStructs.setText(MessageManager
+            .getString("label.superpose_structures"));
     alignStructs.addActionListener(new ActionListener()
     {
       @Override
@@ -181,7 +181,7 @@ public abstract class GStructureViewer extends JInternalFrame implements
       }
     });
 
-    viewerActionMenu = new JMenu();
+    viewerActionMenu = new JMenu(); // text set in sub-classes
     viewerActionMenu.setVisible(false);
     viewerActionMenu.add(alignStructs);
     colourMenu = new JMenu();
@@ -219,9 +219,8 @@ public abstract class GStructureViewer extends JInternalFrame implements
   {
   }
 
-  protected void alignStructs_actionPerformed(ActionEvent actionEvent)
-  {
-  }
+  protected abstract String alignStructs_actionPerformed(
+          ActionEvent actionEvent);
 
   public void pdbFile_actionPerformed(ActionEvent actionEvent)
   {
index aa5319c..5384cc0 100755 (executable)
@@ -137,7 +137,7 @@ public class GUserDefinedColours extends JPanel
     gridLayout.setRows(5);
     okButton.setFont(new java.awt.Font("Verdana", 0, 11));
     okButton.setText(MessageManager.getString("action.ok"));
-    okButton.addActionListener(new java.awt.event.ActionListener()
+    okButton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -157,32 +157,32 @@ public class GUserDefinedColours extends JPanel
     });
     loadbutton.setFont(new java.awt.Font("Verdana", 0, 11));
     loadbutton.setText(MessageManager.getString("action.load_scheme"));
-    loadbutton.addActionListener(new java.awt.event.ActionListener()
+    loadbutton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        loadbutton_actionPerformed(e);
+        loadbutton_actionPerformed();
       }
     });
     savebutton.setFont(new java.awt.Font("Verdana", 0, 11));
     savebutton.setText(MessageManager.getString("action.save_scheme"));
-    savebutton.addActionListener(new java.awt.event.ActionListener()
+    savebutton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        savebutton_actionPerformed(e);
+        savebutton_actionPerformed();
       }
     });
     cancelButton.setFont(JvSwingUtils.getLabelFont());
     cancelButton.setText(MessageManager.getString("action.cancel"));
-    cancelButton.addActionListener(new java.awt.event.ActionListener()
+    cancelButton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        cancelButton_actionPerformed(e);
+        cancelButton_actionPerformed();
       }
     });
     this.setBackground(new Color(212, 208, 223));
@@ -218,7 +218,7 @@ public class GUserDefinedColours extends JPanel
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        caseSensitive_actionPerformed(e);
+        caseSensitive_actionPerformed();
       }
     });
     lcaseColour
@@ -282,18 +282,13 @@ public class GUserDefinedColours extends JPanel
    * @param e
    *          DOCUMENT ME!
    */
-  protected void loadbutton_actionPerformed(ActionEvent e)
+  protected void loadbutton_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void savebutton_actionPerformed(ActionEvent e)
+  protected boolean savebutton_actionPerformed()
   {
+    return false;
   }
 
   /**
@@ -302,16 +297,16 @@ public class GUserDefinedColours extends JPanel
    * @param e
    *          DOCUMENT ME!
    */
-  protected void cancelButton_actionPerformed(ActionEvent e)
+  protected void cancelButton_actionPerformed()
   {
   }
 
-  public void caseSensitive_actionPerformed(ActionEvent e)
+  public void caseSensitive_actionPerformed()
   {
 
   }
 
-  public void lcaseColour_actionPerformed(ActionEvent e)
+  public void lcaseColour_actionPerformed()
   {
 
   }
index 647fc3a..b39d3c9 100755 (executable)
@@ -28,37 +28,45 @@ import java.io.PrintStream;
 /**
  * A class to model rectangular matrices of double values and operations on them
  */
-public class Matrix
+public class Matrix implements MatrixI
 {
   /*
    * the cell values in row-major order
    */
-  public double[][] value;
+  private double[][] value;
 
   /*
    * the number of rows
    */
-  public int rows;
+  protected int rows;
 
   /*
    * the number of columns
    */
-  public int cols;
+  protected int cols;
 
-  /** DOCUMENT ME!! */
-  public double[] d; // Diagonal
+  protected double[] d; // Diagonal
 
-  /** DOCUMENT ME!! */
-  public double[] e; // off diagonal
+  protected double[] e; // off diagonal
 
   /**
    * maximum number of iterations for tqli
    * 
    */
-  int maxIter = 45; // fudge - add 15 iterations, just in case
+  private static final int maxIter = 45; // fudge - add 15 iterations, just in
+                                         // case
 
   /**
-   * Creates a new Matrix object. For example
+   * Default constructor
+   */
+  public Matrix()
+  {
+
+  }
+  
+  /**
+   * Creates a new Matrix object containing a copy of the supplied array values.
+   * For example
    * 
    * <pre>
    *   new Matrix(new double[][] {{2, 3, 4}, {5, 6, 7})
@@ -78,15 +86,30 @@ public class Matrix
   {
     this.rows = values.length;
     this.cols = this.rows == 0 ? 0 : values[0].length;
-    this.value = values;
+
+    /*
+     * make a copy of the values array, for immutability
+     */
+    this.value = new double[rows][];
+    int i = 0;
+    for (double[] row : values)
+    {
+      if (row != null)
+      {
+        value[i] = new double[row.length];
+        System.arraycopy(row, 0, value[i], 0, row.length);
+      }
+      i++;
+    }
   }
 
   /**
-   * Returns a new matrix which is the transposes of this one
+   * Returns a new matrix which is the transpose of this one
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  public Matrix transpose()
+  @Override
+  public MatrixI transpose()
   {
     double[][] out = new double[cols][rows];
 
@@ -106,14 +129,16 @@ public class Matrix
    * 
    * @param ps
    *          DOCUMENT ME!
+   * @param format
    */
-  public void print(PrintStream ps)
+  @Override
+  public void print(PrintStream ps, String format)
   {
     for (int i = 0; i < rows; i++)
     {
       for (int j = 0; j < cols; j++)
       {
-        Format.print(ps, "%8.2f", value[i][j]);
+        Format.print(ps, format, getValue(i, j));
       }
 
       ps.println();
@@ -132,24 +157,27 @@ public class Matrix
    *           if the number of columns in the pre-multiplier is not equal to
    *           the number of rows in the multiplicand (this)
    */
-  public Matrix preMultiply(Matrix in)
+  @Override
+  public MatrixI preMultiply(MatrixI in)
   {
-    if (in.cols != this.rows)
+    if (in.width() != rows)
     {
       throw new IllegalArgumentException("Can't pre-multiply " + this.rows
-              + " rows by " + in.cols + " columns");
+              + " rows by " + in.width() + " columns");
     }
-    double[][] tmp = new double[in.rows][this.cols];
+    double[][] tmp = new double[in.height()][this.cols];
 
-    for (int i = 0; i < in.rows; i++)
+    for (int i = 0; i < in.height(); i++)
     {
       for (int j = 0; j < this.cols; j++)
       {
-        tmp[i][j] = 0.0;
-
-        for (int k = 0; k < in.cols; k++)
+        /*
+         * result[i][j] is the vector product of 
+         * in.row[i] and this.column[j]
+         */
+        for (int k = 0; k < in.width(); k++)
         {
-          tmp[i][j] += (in.value[i][k] * this.value[k][j]);
+          tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
         }
       }
     }
@@ -195,12 +223,13 @@ public class Matrix
    *           number of columns in the multiplicand (this)
    * @see #preMultiply(Matrix)
    */
-  public Matrix postMultiply(Matrix in)
+  @Override
+  public MatrixI postMultiply(MatrixI in)
   {
-    if (in.rows != this.cols)
+    if (in.height() != this.cols)
     {
       throw new IllegalArgumentException("Can't post-multiply " + this.cols
-              + " columns by " + in.rows + " rows");
+              + " columns by " + in.height() + " rows");
     }
     return in.preMultiply(this);
   }
@@ -210,7 +239,8 @@ public class Matrix
    * 
    * @return
    */
-  public Matrix copy()
+  @Override
+  public MatrixI copy()
   {
     double[][] newmat = new double[rows][cols];
 
@@ -225,10 +255,10 @@ public class Matrix
   /**
    * DOCUMENT ME!
    */
+  @Override
   public void tred()
   {
     int n = rows;
-    int l;
     int k;
     int j;
     int i;
@@ -244,7 +274,7 @@ public class Matrix
 
     for (i = n; i >= 2; i--)
     {
-      l = i - 1;
+      final int l = i - 1;
       h = 0.0;
       scale = 0.0;
 
@@ -252,22 +282,23 @@ public class Matrix
       {
         for (k = 1; k <= l; k++)
         {
-          scale += Math.abs(value[i - 1][k - 1]);
+          double v = Math.abs(getValue(i - 1, k - 1));
+          scale += v;
         }
 
         if (scale == 0.0)
         {
-          e[i - 1] = value[i - 1][l - 1];
+          e[i - 1] = getValue(i - 1, l - 1);
         }
         else
         {
           for (k = 1; k <= l; k++)
           {
-            value[i - 1][k - 1] /= scale;
-            h += (value[i - 1][k - 1] * value[i - 1][k - 1]);
+            double v = divideValue(i - 1, k - 1, scale);
+            h += v * v;
           }
 
-          f = value[i - 1][l - 1];
+          f = getValue(i - 1, l - 1);
 
           if (f > 0)
           {
@@ -280,46 +311,48 @@ public class Matrix
 
           e[i - 1] = scale * g;
           h -= (f * g);
-          value[i - 1][l - 1] = f - g;
+          setValue(i - 1, l - 1, f - g);
           f = 0.0;
 
           for (j = 1; j <= l; j++)
           {
-            value[j - 1][i - 1] = value[i - 1][j - 1] / h;
+            double val = getValue(i - 1, j - 1) / h;
+            setValue(j - 1, i - 1, val);
             g = 0.0;
 
             for (k = 1; k <= j; k++)
             {
-              g += (value[j - 1][k - 1] * value[i - 1][k - 1]);
+              g += (getValue(j - 1, k - 1) * getValue(i - 1, k - 1));
             }
 
             for (k = j + 1; k <= l; k++)
             {
-              g += (value[k - 1][j - 1] * value[i - 1][k - 1]);
+              g += (getValue(k - 1, j - 1) * getValue(i - 1, k - 1));
             }
 
             e[j - 1] = g / h;
-            f += (e[j - 1] * value[i - 1][j - 1]);
+            f += (e[j - 1] * getValue(i - 1, j - 1));
           }
 
           hh = f / (h + h);
 
           for (j = 1; j <= l; j++)
           {
-            f = value[i - 1][j - 1];
+            f = getValue(i - 1, j - 1);
             g = e[j - 1] - (hh * f);
             e[j - 1] = g;
 
             for (k = 1; k <= j; k++)
             {
-              value[j - 1][k - 1] -= ((f * e[k - 1]) + (g * value[i - 1][k - 1]));
+              double val = (f * e[k - 1]) + (g * getValue(i - 1, k - 1));
+              addValue(j - 1, k - 1, -val);
             }
           }
         }
       }
       else
       {
-        e[i - 1] = value[i - 1][l - 1];
+        e[i - 1] = getValue(i - 1, l - 1);
       }
 
       d[i - 1] = h;
@@ -330,7 +363,7 @@ public class Matrix
 
     for (i = 1; i <= n; i++)
     {
-      l = i - 1;
+      final int l = i - 1;
 
       if (d[i - 1] != 0.0)
       {
@@ -340,30 +373,66 @@ public class Matrix
 
           for (k = 1; k <= l; k++)
           {
-            g += (value[i - 1][k - 1] * value[k - 1][j - 1]);
+            g += (getValue(i - 1, k - 1) * getValue(k - 1, j - 1));
           }
 
           for (k = 1; k <= l; k++)
           {
-            value[k - 1][j - 1] -= (g * value[k - 1][i - 1]);
+            addValue(k - 1, j - 1, -(g * getValue(k - 1, i - 1)));
           }
         }
       }
 
-      d[i - 1] = value[i - 1][i - 1];
-      value[i - 1][i - 1] = 1.0;
+      d[i - 1] = getValue(i - 1, i - 1);
+      setValue(i - 1, i - 1, 1.0);
 
       for (j = 1; j <= l; j++)
       {
-        value[j - 1][i - 1] = 0.0;
-        value[i - 1][j - 1] = 0.0;
+        setValue(j - 1, i - 1, 0.0);
+        setValue(i - 1, j - 1, 0.0);
       }
     }
   }
 
   /**
+   * Adds f to the value at [i, j] and returns the new value
+   * 
+   * @param i
+   * @param j
+   * @param f
+   */
+  protected double addValue(int i, int j, double f)
+  {
+    double v = value[i][j] + f;
+    value[i][j] = v;
+    return v;
+  }
+
+  /**
+   * Divides the value at [i, j] by divisor and returns the new value. If d is
+   * zero, returns the unchanged value.
+   * 
+   * @param i
+   * @param j
+   * @param divisor
+   * @return
+   */
+  protected double divideValue(int i, int j, double divisor)
+  {
+    if (divisor == 0d)
+    {
+      return getValue(i, j);
+    }
+    double v = value[i][j];
+    v = v / divisor;
+    value[i][j] = v;
+    return v;
+  }
+
+  /**
    * DOCUMENT ME!
    */
+  @Override
   public void tqli() throws Exception
   {
     int n = rows;
@@ -376,7 +445,6 @@ public class Matrix
     double s;
     double r;
     double p;
-    ;
 
     double g;
     double f;
@@ -459,9 +527,9 @@ public class Matrix
 
             for (k = 1; k <= n; k++)
             {
-              f = value[k - 1][i];
-              value[k - 1][i] = (s * value[k - 1][i - 1]) + (c * f);
-              value[k - 1][i - 1] = (c * value[k - 1][i - 1]) - (s * f);
+              f = getValue(k - 1, i);
+              setValue(k - 1, i, (s * getValue(k - 1, i - 1)) + (c * f));
+              setValue(k - 1, i - 1, (c * getValue(k - 1, i - 1)) - (s * f));
             }
           }
 
@@ -473,6 +541,18 @@ public class Matrix
     }
   }
 
+  @Override
+  public double getValue(int i, int j)
+  {
+    return value[i][j];
+  }
+
+  @Override
+  public void setValue(int i, int j, double val)
+  {
+    value[i][j] = val;
+  }
+
   /**
    * DOCUMENT ME!
    */
@@ -725,16 +805,14 @@ public class Matrix
   }
 
   /**
-   * DOCUMENT ME!
+   * Answers the first argument with the sign of the second argument
    * 
    * @param a
-   *          DOCUMENT ME!
    * @param b
-   *          DOCUMENT ME!
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  public double sign(double a, double b)
+  static double sign(double a, double b)
   {
     if (b < 0)
     {
@@ -770,12 +848,14 @@ public class Matrix
    * 
    * @param ps
    *          DOCUMENT ME!
+   * @param format
    */
-  public void printD(PrintStream ps)
+  @Override
+  public void printD(PrintStream ps, String format)
   {
     for (int j = 0; j < rows; j++)
     {
-      Format.print(ps, "%15.4e", d[j]);
+      Format.print(ps, format, d[j]);
     }
   }
 
@@ -784,12 +864,132 @@ public class Matrix
    * 
    * @param ps
    *          DOCUMENT ME!
+   * @param format TODO
    */
-  public void printE(PrintStream ps)
+  @Override
+  public void printE(PrintStream ps, String format)
   {
     for (int j = 0; j < rows; j++)
     {
-      Format.print(ps, "%15.4e", e[j]);
+      Format.print(ps, format, e[j]);
+    }
+  }
+
+  @Override
+  public double[] getD()
+  {
+    return d;
+  }
+
+  @Override
+  public double[] getE()
+  {
+    return e;
+  }
+  
+  @Override
+  public int height() {
+    return rows;
+  }
+
+  @Override
+  public int width()
+  {
+    return cols;
+  }
+
+  @Override
+  public double[] getRow(int i)
+  {
+    double[] row = new double[cols];
+    System.arraycopy(value[i], 0, row, 0, cols);
+    return row;
+  }
+
+  /**
+   * Returns a length 2 array of {minValue, maxValue} of all values in the
+   * matrix. Returns null if the matrix is null or empty.
+   * 
+   * @return
+   */
+  double[] findMinMax()
+  {
+    if (value == null)
+    {
+      return null;
+    }
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+    boolean empty = true;
+    for (double[] row : value)
+    {
+      if (row != null)
+      {
+        for (double x : row)
+        {
+          empty = false;
+          if (x > max)
+          {
+            max = x;
+          }
+          if (x < min)
+          {
+            min = x;
+          }
+        }
+      }
+    }
+    return empty ? null : new double[] { min, max };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void reverseRange(boolean maxToZero)
+  {
+    if (value == null)
+    {
+      return;
+    }
+    double[] minMax = findMinMax();
+    if (minMax == null)
+    {
+      return; // empty matrix
+    }
+    double subtractFrom = maxToZero ? minMax[1] : minMax[0] + minMax[1];
+
+    for (double[] row : value)
+    {
+      if (row != null)
+      {
+        int j = 0;
+        for (double x : row)
+        {
+          row[j] = subtractFrom - x;
+          j++;
+        }
+      }
+    }
+  }
+
+  /**
+   * Multiplies every entry in the matrix by the given value.
+   * 
+   * @param
+   */
+  @Override
+  public void multiply(double by)
+  {
+    for (double[] row : value)
+    {
+      if (row != null)
+      {
+        for (int i = 0; i < row.length; i++)
+        {
+          row[i] *= by;
+        }
+      }
     }
   }
 }
diff --git a/src/jalview/math/MatrixI.java b/src/jalview/math/MatrixI.java
new file mode 100644 (file)
index 0000000..94b9333
--- /dev/null
@@ -0,0 +1,97 @@
+package jalview.math;
+
+import java.io.PrintStream;
+
+public interface MatrixI
+{
+  /**
+   * Answers the number of columns
+   * 
+   * @return
+   */
+  int width();
+
+  /**
+   * Answers the number of rows
+   * 
+   * @return
+   */
+  int height();
+
+  /**
+   * Answers the value at row i, column j
+   * 
+   * @param i
+   * @param j
+   * @return
+   */
+  double getValue(int i, int j);
+
+  /**
+   * Sets the value at row i, colum j
+   * 
+   * @param i
+   * @param j
+   * @param d
+   */
+  void setValue(int i, int j, double d);
+
+  /**
+   * Answers a copy of the values in the i'th row
+   * 
+   * @return
+   */
+  double[] getRow(int i);
+  
+  MatrixI copy();
+
+  MatrixI transpose();
+
+  MatrixI preMultiply(MatrixI m);
+
+  MatrixI postMultiply(MatrixI m);
+
+  double[] getD();
+
+  double[] getE();
+
+  void print(PrintStream ps, String format);
+
+  void printD(PrintStream ps, String format);
+
+  void printE(PrintStream ps, String format);
+
+  void tqli() throws Exception;
+
+  void tred();
+
+  /**
+   * Reverses the range of the matrix values, so that the smallest values become
+   * the largest, and the largest become the smallest. This operation supports
+   * using a distance measure as a similarity measure, or vice versa.
+   * <p>
+   * If parameter <code>maxToZero</code> is true, then the maximum value becomes
+   * zero, i.e. all values are subtracted from the maximum. This is consistent
+   * with converting an identity similarity score to a distance score - the most
+   * similar (identity) corresponds to zero distance. However note that the
+   * operation is not reversible (unless the original minimum value is zero).
+   * For example a range of 10-40 would become 30-0, which would reverse a
+   * second time to 0-30. Also note that a general similarity measure (such as
+   * BLOSUM) may give different 'identity' scores for different sequences, so
+   * they cannot all convert to zero distance.
+   * <p>
+   * If parameter <code>maxToZero</code> is false, then the values are reflected
+   * about the average of {min, max} (effectively swapping min and max). This
+   * operation <em>is</em> reversible.
+   * 
+   * @param maxToZero
+   */
+  void reverseRange(boolean maxToZero);
+
+  /**
+   * Multiply all entries by the given value
+   * 
+   * @param d
+   */
+  void multiply(double d);
+}
diff --git a/src/jalview/math/SparseMatrix.java b/src/jalview/math/SparseMatrix.java
new file mode 100644 (file)
index 0000000..72f0963
--- /dev/null
@@ -0,0 +1,218 @@
+package jalview.math;
+
+import jalview.ext.android.SparseDoubleArray;
+
+/**
+ * A variant of Matrix intended for use for sparse (mostly zero) matrices. This
+ * class uses a SparseDoubleArray to hold each row of the matrix. The sparse
+ * array only stores non-zero values. This gives a smaller memory footprint, and
+ * fewer matrix calculation operations, for mostly zero matrices.
+ * 
+ * @author gmcarstairs
+ */
+public class SparseMatrix extends Matrix
+{
+  /*
+   * we choose columns for the sparse arrays as this allows
+   * optimisation of the preMultiply() method used in PCA.run()
+   */
+  SparseDoubleArray[] sparseColumns;
+
+  /**
+   * Constructor given data in [row][column] order
+   * 
+   * @param v
+   */
+  public SparseMatrix(double[][] v)
+  {
+    rows = v.length;
+    if (rows > 0) {
+      cols = v[0].length;
+    }
+    sparseColumns = new SparseDoubleArray[cols];
+
+    /*
+     * transpose v[row][col] into [col][row] order
+     */
+    for (int col = 0; col < cols; col++)
+    {
+      SparseDoubleArray sparseColumn = new SparseDoubleArray();
+      sparseColumns[col] = sparseColumn;
+      for (int row = 0; row < rows; row++)
+      {
+        double value = v[row][col];
+        if (value != 0d)
+        {
+          sparseColumn.put(row, value);
+        }
+      }
+    }
+  }
+
+  /**
+   * Answers the value at row i, column j
+   */
+  @Override
+  public double getValue(int i, int j)
+  {
+    return sparseColumns[j].get(i);
+  }
+
+  /**
+   * Sets the value at row i, column j to val
+   */
+  @Override
+  public void setValue(int i, int j, double val)
+  {
+    if (val == 0d)
+    {
+      sparseColumns[j].delete(i);
+    }
+    else
+    {
+      sparseColumns[j].put(i, val);
+    }
+  }
+
+  @Override
+  public double[] getColumn(int i)
+  {
+    double[] col = new double[height()];
+
+    SparseDoubleArray vals = sparseColumns[i];
+    for (int nonZero = 0; nonZero < vals.size(); nonZero++)
+    {
+      col[vals.keyAt(nonZero)] = vals.valueAt(nonZero);
+    }
+    return col;
+  }
+
+  @Override
+  public MatrixI copy()
+  {
+    double[][] vals = new double[height()][width()];
+    for (int i = 0; i < height(); i++)
+    {
+      vals[i] = getRow(i);
+    }
+    return new SparseMatrix(vals);
+  }
+
+  @Override
+  public MatrixI transpose()
+  {
+    double[][] out = new double[cols][rows];
+
+    /*
+     * for each column...
+     */
+    for (int i = 0; i < cols; i++)
+    {
+      /*
+       * put non-zero values into the corresponding row
+       * of the transposed matrix
+       */
+      SparseDoubleArray vals = sparseColumns[i];
+      for (int nonZero = 0; nonZero < vals.size(); nonZero++)
+      {
+        out[i][vals.keyAt(nonZero)] = vals.valueAt(nonZero);
+      }
+    }
+
+    return new SparseMatrix(out);
+  }
+
+  /**
+   * Answers a new matrix which is the product in.this. If the product contains
+   * less than 20% non-zero values, it is returned as a SparseMatrix, else as a
+   * Matrix.
+   * <p>
+   * This method is optimised for the sparse arrays which store column values
+   * for a SparseMatrix. Note that postMultiply is not so optimised. That would
+   * require redundantly also storing sparse arrays for the rows, which has not
+   * been done. Currently only preMultiply is used in Jalview.
+   */
+  @Override
+  public MatrixI preMultiply(MatrixI in)
+  {
+    if (in.width() != rows)
+    {
+      throw new IllegalArgumentException("Can't pre-multiply " + this.rows
+              + " rows by " + in.width() + " columns");
+    }
+    double[][] tmp = new double[in.height()][this.cols];
+
+    long count = 0L;
+    for (int i = 0; i < in.height(); i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+        /*
+         * result[i][j] is the vector product of 
+         * in.row[i] and this.column[j]
+         * we only need to use non-zero values from the column
+         */
+        SparseDoubleArray vals = sparseColumns[j];
+        boolean added = false;
+        for (int nonZero = 0; nonZero < vals.size(); nonZero++)
+        {
+          int myRow = vals.keyAt(nonZero);
+          double myValue = vals.valueAt(nonZero);
+          tmp[i][j] += (in.getValue(i, myRow) * myValue);
+          added = true;
+        }
+        if (added && tmp[i][j] != 0d)
+        {
+          count++; // non-zero entry in product
+        }
+      }
+    }
+
+    /*
+     * heuristic rule - if product is more than 80% zero
+     * then construct a SparseMatrix, else a Matrix
+     */
+    if (count * 5 < in.height() * cols)
+    {
+      return new SparseMatrix(tmp);
+    }
+    else
+    {
+      return new Matrix(tmp);
+    }
+  }
+
+  @Override
+  protected double divideValue(int i, int j, double divisor)
+  {
+    if (divisor == 0d)
+    {
+      return getValue(i, j);
+    }
+    double v = sparseColumns[j].divide(i, divisor);
+    return v;
+  }
+
+  @Override
+  protected double addValue(int i, int j, double addend)
+  {
+    double v = sparseColumns[j].add(i, addend);
+    return v;
+  }
+
+  /**
+   * Returns the fraction of the whole matrix size that is actually modelled in
+   * sparse arrays (normally, the non-zero values)
+   * 
+   * @return
+   */
+  public float getFillRatio()
+  {
+    long count = 0L;
+    for (SparseDoubleArray col : sparseColumns)
+    {
+      count += col.size();
+    }
+    return count / (float) (height() * width());
+  }
+}
index f5562d7..8f751b6 100644 (file)
@@ -28,6 +28,7 @@ import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.ProfilesI;
 import jalview.renderer.api.AnnotationRendererFactoryI;
 import jalview.renderer.api.AnnotationRowRendererI;
@@ -77,6 +78,8 @@ public class AnnotationRenderer
   ResidueShaderI profcolour = null;
 
   private ColumnSelection columnSelection;
+  
+  private HiddenColumns hiddenColumns;
 
   private ProfilesI hconsensus;
 
@@ -311,7 +314,7 @@ public class AnnotationRenderer
   public void updateFromAlignViewport(AlignViewportI av)
   {
     charWidth = av.getCharWidth();
-    endRes = av.getEndRes();
+    endRes = av.getRanges().getEndRes();
     charHeight = av.getCharHeight();
     hasHiddenColumns = av.hasHiddenColumns();
     validCharWidth = av.isValidCharWidth();
@@ -331,6 +334,7 @@ public class AnnotationRenderer
       profcolour = new ResidueShader(col);
     }
     columnSelection = av.getColumnSelection();
+    hiddenColumns = av.getAlignment().getHiddenColumns();
     hconsensus = av.getSequenceConsensusHash();
     complementConsensus = av.getComplementConsensusHash();
     hStrucConsensus = av.getRnaStructureConsensusHash();
@@ -594,7 +598,7 @@ public class AnnotationRenderer
         {
           if (hasHiddenColumns)
           {
-            column = columnSelection.adjustForHiddenColumns(startRes + x);
+            column = hiddenColumns.adjustForHiddenColumns(startRes + x);
             if (column > row_annotations.length - 1)
             {
               break;
@@ -1052,8 +1056,8 @@ public class AnnotationRenderer
             if (renderer != null)
             {
               renderer.renderRow(g, charWidth, charHeight,
-                      hasHiddenColumns, av, columnSelection, row,
-                      row_annotations, startRes, endRes, row.graphMin,
+                      hasHiddenColumns, av, hiddenColumns, columnSelection,
+                      row, row_annotations, startRes, endRes, row.graphMin,
                       row.graphMax, y);
             }
             if (debugRedraw)
@@ -1262,7 +1266,7 @@ public class AnnotationRenderer
       column = sRes + x;
       if (hasHiddenColumns)
       {
-        column = columnSelection.adjustForHiddenColumns(column);
+        column = hiddenColumns.adjustForHiddenColumns(column);
       }
 
       if (column > aaMax)
@@ -1341,7 +1345,7 @@ public class AnnotationRenderer
       column = sRes + x;
       if (hasHiddenColumns)
       {
-        column = columnSelection.adjustForHiddenColumns(column);
+        column = hiddenColumns.adjustForHiddenColumns(column);
       }
 
       if (column > aaMax)
index 35b73a4..bd59315 100644 (file)
@@ -9,6 +9,7 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
 import jalview.datamodel.ContactRange;
+import jalview.datamodel.HiddenColumns;
 import jalview.renderer.api.AnnotationRowRendererI;
 
 import java.awt.Color;
@@ -23,7 +24,7 @@ public class ContactMapRenderer implements AnnotationRowRendererI
 
   @Override
   public void renderRow(Graphics g, int charWidth, int charHeight,
-          boolean hasHiddenColumns, AlignViewportI viewport,
+          boolean hasHiddenColumns, AlignViewportI viewport, HiddenColumns hiddenColumns,
           ColumnSelection columnSelection, AlignmentAnnotation _aa,
           Annotation[] aa_annotations, int sRes, int eRes, float min,
           float max, int y)
@@ -47,7 +48,7 @@ public class ContactMapRenderer implements AnnotationRowRendererI
       column = sRes + x;
       if (hasHiddenColumns)
       {
-        column = columnSelection.adjustForHiddenColumns(column);
+        column = hiddenColumns.adjustForHiddenColumns(column);
       }
 
       if (column > aaMax)
diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java
new file mode 100644 (file)
index 0000000..9291ca6
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.renderer;
+
+import jalview.api.AlignmentColsCollectionI;
+import jalview.api.AlignmentRowsCollectionI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.renderer.seqfeatures.FeatureRenderer;
+import jalview.viewmodel.OverviewDimensions;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+public class OverviewRenderer
+{
+  private FeatureColourFinder finder;
+
+  private jalview.api.SequenceRenderer sr;
+
+  // image to render on
+  private BufferedImage miniMe;
+
+  // raw number of pixels to allocate to each column
+  private float pixelsPerCol;
+
+  // raw number of pixels to allocate to each row
+  private float pixelsPerSeq;
+
+  public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer,
+          FeatureRenderer fr, OverviewDimensions od)
+  // FeatureColourFinder colfinder, OverviewDimensions od)
+  {
+    sr = seqRenderer;
+    finder = new FeatureColourFinder(fr); // colfinder;
+
+    pixelsPerCol = od.getPixelsPerCol();
+    pixelsPerSeq = od.getPixelsPerSeq();
+    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
+            BufferedImage.TYPE_INT_RGB);
+  }
+
+  /**
+   * Draw alignment rows and columns onto an image
+   * 
+   * @param rit
+   *          Iterator over rows to be drawn
+   * @param cit
+   *          Iterator over columns to be drawn
+   * @return image containing the drawing
+   */
+  public BufferedImage draw(AlignmentRowsCollectionI rows,
+          AlignmentColsCollectionI cols)
+  {
+    int rgbcolor = Color.white.getRGB();
+    int seqIndex = 0;
+    int pixelRow = 0;
+    for (int alignmentRow : rows)
+    {
+      // get details of this alignment row
+      boolean hidden = rows.isHidden(alignmentRow);
+      SequenceI seq = rows.getSequence(alignmentRow);
+
+      // calculate where this row extends to in pixels
+      int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
+              miniMe.getHeight() - 1);
+
+      int colIndex = 0;
+      int pixelCol = 0;
+      for (int alignmentCol : cols)
+      {
+        // calculate where this column extends to in pixels
+        int endCol = Math.min(
+                Math.round((colIndex + 1) * pixelsPerCol) - 1,
+                miniMe.getWidth() - 1);
+
+        // don't do expensive colour determination if we're not going to use it
+        // NB this is important to avoid performance issues in the overview
+        // panel
+        if (pixelCol <= endCol)
+        {
+          // determine the colour based on the sequence and column position
+          rgbcolor = getColumnColourFromSequence(seq,
+                  hidden || cols.isHidden(alignmentCol), alignmentCol,
+                  finder);
+
+          // fill in the appropriate number of pixels
+          for (int row = pixelRow; row <= endRow; ++row)
+          {
+            for (int col = pixelCol; col <= endCol; ++col)
+            {
+              miniMe.setRGB(col, row, rgbcolor);
+            }
+          }
+
+          pixelCol = endCol + 1;
+        }
+        colIndex++;
+      }
+      pixelRow = endRow + 1;
+      seqIndex++;
+    }
+    return miniMe;
+  }
+
+  /*
+   * Find the colour of a sequence at a specified column position
+   */
+  private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
+          boolean isHidden, int lastcol, FeatureColourFinder fcfinder)
+  {
+    Color color = Color.white;
+
+    if ((seq != null) && (seq.getLength() > lastcol))
+    {
+      color = sr.getResidueColour(seq, lastcol, fcfinder);
+    }
+
+    if (isHidden)
+    {
+      color = color.darker().darker();
+    }
+
+    return color.getRGB();
+  }
+
+  /**
+   * Draw the alignment annotation in the overview panel
+   * 
+   * @param g
+   *          the graphics object to draw on
+   * @param anno
+   *          alignment annotation information
+   * @param charWidth
+   *          alignment character width value
+   * @param y
+   *          y-position for the annotation graph
+   * @param cols
+   *          the collection of columns used in the overview panel
+   */
+  public void drawGraph(Graphics g, AlignmentAnnotation anno, int charWidth,
+          int y, AlignmentColsCollectionI cols)
+  {
+    Annotation[] annotations = anno.annotations;
+    g.setColor(Color.white);
+    g.fillRect(0, 0, miniMe.getWidth(), y);
+
+    int height;
+    int colIndex = 0;
+    int pixelCol = 0;
+    for (int alignmentCol : cols)
+    {
+      if (alignmentCol >= annotations.length)
+      {
+        break; // no more annotations to draw here
+      }
+      else
+      {
+        int endCol = Math.min(
+                Math.round((colIndex + 1) * pixelsPerCol) - 1,
+                miniMe.getWidth() - 1);
+
+        if (annotations[alignmentCol] != null)
+        {
+          if (annotations[alignmentCol].colour == null)
+          {
+            g.setColor(Color.black);
+          }
+          else
+          {
+            g.setColor(annotations[alignmentCol].colour);
+          }
+
+          height = (int) ((annotations[alignmentCol].value / anno.graphMax) * y);
+          if (height > y)
+          {
+            height = y;
+          }
+
+          g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
+        }
+        pixelCol = endCol + 1;
+        colIndex++;
+      }
+    }
+  }
+}
index 82536d4..9fec256 100644 (file)
@@ -73,7 +73,7 @@ public class ScaleRenderer
     {
       // find bounds and set origin appopriately
       // locate first visible position for this sequence
-      int[] refbounds = av.getColumnSelection()
+      int[] refbounds = av.getAlignment().getHiddenColumns()
               .locateVisibleBoundsOfSequence(refSeq);
 
       refSp = refbounds[0];
@@ -96,12 +96,14 @@ public class ScaleRenderer
       {
         if (refSeq == null)
         {
-          iadj = av.getColumnSelection().adjustForHiddenColumns(i - 1) + 1;
+          iadj = av.getAlignment().getHiddenColumns()
+                  .adjustForHiddenColumns(i - 1) + 1;
           string = String.valueOf(iadj);
         }
         else
         {
-          iadj = av.getColumnSelection().adjustForHiddenColumns(i - 1);
+          iadj = av.getAlignment().getHiddenColumns()
+                  .adjustForHiddenColumns(i - 1);
           refN = refSeq.findPosition(iadj);
           // TODO show bounds if position is a gap
           // - ie L--R -> "1L|2R" for
index 0f0d851..bedbad5 100644 (file)
@@ -4,6 +4,7 @@ import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 
 import java.awt.Graphics;
 
@@ -12,6 +13,7 @@ public interface AnnotationRowRendererI
 
   void renderRow(Graphics g, int charWidth, int charHeight,
           boolean hasHiddenColumns, AlignViewportI av,
+          HiddenColumns hiddenColumns,
           ColumnSelection columnSelection, AlignmentAnnotation row,
           Annotation[] row_annotations, int startRes, int endRes,
           float graphMin, float graphMax, int y);
diff --git a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java
new file mode 100644 (file)
index 0000000..1db2004
--- /dev/null
@@ -0,0 +1,124 @@
+package jalview.renderer.seqfeatures;
+
+import jalview.api.FeatureRenderer;
+import jalview.api.FeaturesDisplayedI;
+import jalview.datamodel.SequenceI;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+/**
+ * A helper class to find feature colour using an associated FeatureRenderer
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class FeatureColourFinder
+{
+  /*
+   * the class we delegate feature finding to
+   */
+  private FeatureRenderer featureRenderer;
+
+  /*
+   * a 1-pixel image on which features can be drawn, for the case where
+   * transparency allows 'see-through' of multiple feature colours
+   */
+  private BufferedImage offscreenImage;
+
+  /**
+   * Constructor
+   * 
+   * @param fr
+   */
+  public FeatureColourFinder(FeatureRenderer fr)
+  {
+    featureRenderer = fr;
+    offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+  }
+
+  /**
+   * Answers the feature colour to show for the given sequence and column
+   * position. This delegates to the FeatureRenderer to find the colour, which
+   * will depend on feature location, visibility, ordering, colour scheme, and
+   * whether or not transparency is applied. For feature rendering with
+   * transparency, this class provides a dummy 'offscreen' graphics context
+   * where multiple feature colours can be overlaid and the combined colour read
+   * back.
+   * <p>
+   * This method is not thread-safe when transparency is applied, since a shared
+   * BufferedImage would be used by all threads to hold the composite colour at
+   * a position. Each thread should use a separate instance of this class.
+   * 
+   * @param defaultColour
+   * @param seq
+   * @param column
+   *          alignment column position (base zero)
+   * @return
+   */
+  public Color findFeatureColour(Color defaultColour, SequenceI seq,
+          int column)
+  {
+    if (noFeaturesDisplayed())
+    {
+      return defaultColour;
+    }
+
+    Graphics g = null;
+
+    /*
+     * if transparency applies, provide a notional 1x1 graphics context 
+     * that has been primed with the default colour
+     */
+    if (featureRenderer.getTransparency() != 1f)
+    {
+      g = offscreenImage.getGraphics();
+      if (defaultColour != null)
+      {
+        offscreenImage.setRGB(0, 0, defaultColour.getRGB());
+      }
+    }
+
+    Color c = featureRenderer.findFeatureColour(seq, column, g);
+    if (c == null)
+    {
+      return defaultColour;
+    }
+
+    if (g != null)
+    {
+      c = new Color(offscreenImage.getRGB(0, 0));
+    }
+    return c;
+  }
+
+  /**
+   * Answers true if feature display is turned off, or there are no features
+   * configured to be visible
+   * 
+   * @return
+   */
+  boolean noFeaturesDisplayed()
+  {
+    if (featureRenderer == null
+            || !featureRenderer.getViewport().isShowSequenceFeatures())
+    {
+      return true;
+    }
+
+    if (!((FeatureRendererModel) featureRenderer).hasRenderOrder())
+    {
+      return true;
+    }
+
+    FeaturesDisplayedI displayed = featureRenderer.getFeaturesDisplayed();
+    if (displayed == null || displayed.getVisibleFeatureCount() == 0)
+    {
+      return true;
+    }
+
+    return false;
+  }
+}
index 9e0089f..72ac2c8 100644 (file)
@@ -23,6 +23,7 @@ package jalview.renderer.seqfeatures;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
 import java.awt.AlphaComposite;
@@ -30,28 +31,11 @@ import java.awt.Color;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
 
 public class FeatureRenderer extends FeatureRendererModel
 {
-
-  FontMetrics fm;
-
-  int charOffset;
-
-  boolean offscreenRender = false;
-
-  protected SequenceI lastSeq;
-
-  char s;
-
-  int i;
-
-  int av_charHeight, av_charWidth;
-
-  boolean av_validCharWidth, av_isShowSeqFeatureHeight;
-
-  private Integer currentColour;
+  private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
+          .getInstance(AlphaComposite.SRC_OVER, 1.0f);
 
   /**
    * Constructor given a viewport
@@ -63,273 +47,252 @@ public class FeatureRenderer extends FeatureRendererModel
     this.av = viewport;
   }
 
-  protected void updateAvConfig()
+  /**
+   * Renders the sequence using the given feature colour between the given start
+   * and end columns. Returns true if at least one column is drawn, else false
+   * (the feature range does not overlap the start and end positions).
+   * 
+   * @param g
+   * @param seq
+   * @param featureStart
+   * @param featureEnd
+   * @param featureColour
+   * @param start
+   * @param end
+   * @param y1
+   * @param colourOnly
+   * @return
+   */
+  boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
+          int featureEnd, Color featureColour, int start, int end, int y1,
+          boolean colourOnly)
   {
-    av_charHeight = av.getCharHeight();
-    av_charWidth = av.getCharWidth();
-    av_validCharWidth = av.isValidCharWidth();
-    av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
-  }
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    boolean validCharWidth = av.isValidCharWidth();
 
-  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    if (featureStart > end || featureEnd < start)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-
-        g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
-                av_charHeight);
-
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.white);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      return false;
+    }
 
-      }
+    if (featureStart < start)
+    {
+      featureStart = start;
     }
-  }
+    if (featureEnd >= end)
+    {
+      featureEnd = end;
+    }
+    int pady = (y1 + charHeight) - charHeight / 5;
 
-  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1, byte[] bs)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    FontMetrics fm = g.getFontMetrics();
+    for (int i = featureStart; i <= featureEnd; i++)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
+      char s = seq.getCharAt(i);
 
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      int ystrt = 0, yend = av_charHeight;
-      if (bs[0] != 0)
-      {
-        // signed - zero is always middle of residue line.
-        if (bs[1] < 128)
-        {
-          yend = av_charHeight * (128 - bs[1]) / 512;
-          ystrt = av_charHeight - yend / 2;
-        }
-        else
-        {
-          ystrt = av_charHeight / 2;
-          yend = av_charHeight * (bs[1] - 128) / 512;
-        }
-      }
-      else
+      if (Comparison.isGap(s))
       {
-        yend = av_charHeight * bs[1] / 255;
-        ystrt = av_charHeight - yend;
-
+        continue;
       }
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
 
-        g.setColor(featureColour);
-        int x = (i - start) * av_charWidth;
-        g.drawRect(x, y1, av_charWidth, av_charHeight);
-        g.fillRect(x, y1 + ystrt, av_charWidth, yend);
+      g.setColor(featureColour);
 
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
+      g.fillRect((i - start) * charWidth, y1, charWidth,
+              charHeight);
 
-        g.setColor(Color.black);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      if (colourOnly || !validCharWidth)
+      {
+        continue;
       }
-    }
-  }
-
-  BufferedImage offscreenImage;
 
-  @Override
-  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
-  {
-    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
+      g.setColor(Color.white);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s), charOffset
+              + (charWidth * (i - start)), pady);
+    }
+    return true;
   }
 
   /**
-   * This is used by Structure Viewers and the Overview Window to get the
-   * feature colour of the rendered sequence, returned as an RGB value
+   * Renders the sequence using the given SCORE feature colour between the given
+   * start and end columns. Returns true if at least one column is drawn, else
+   * false (the feature range does not overlap the start and end positions).
    * 
-   * @param defaultColour
+   * @param g
    * @param seq
-   * @param column
+   * @param fstart
+   * @param fend
+   * @param featureColour
+   * @param start
+   * @param end
+   * @param y1
+   * @param bs
+   * @param colourOnly
    * @return
    */
-  public synchronized int findFeatureColour(int defaultColour,
-          final SequenceI seq, int column)
+  boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
+          int fend, Color featureColour, int start, int end, int y1,
+          byte[] bs, boolean colourOnly)
   {
-    if (!av.isShowSequenceFeatures())
+    if (fstart > end || fend < start)
     {
-      return defaultColour;
+      return false;
     }
 
-    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
-    if (seq != lastSeq)
+    if (fstart < start)
+    { // fix for if the feature we have starts before the sequence start,
+      fstart = start; // but the feature end is still valid!!
+    }
+
+    if (fend >= end)
+    {
+      fend = end;
+    }
+    int charHeight = av.getCharHeight();
+    int pady = (y1 + charHeight) - charHeight / 5;
+    int ystrt = 0, yend = charHeight;
+    if (bs[0] != 0)
     {
-      lastSeq = seq;
-      lastSequenceFeatures = sequenceFeatures;
-      if (lastSequenceFeatures != null)
+      // signed - zero is always middle of residue line.
+      if (bs[1] < 128)
       {
-        sfSize = lastSequenceFeatures.length;
+        yend = charHeight * (128 - bs[1]) / 512;
+        ystrt = charHeight - yend / 2;
+      }
+      else
+      {
+        ystrt = charHeight / 2;
+        yend = charHeight * (bs[1] - 128) / 512;
       }
     }
     else
     {
-      if (lastSequenceFeatures != sequenceFeatures)
+      yend = charHeight * bs[1] / 255;
+      ystrt = charHeight - yend;
+
+    }
+
+    FontMetrics fm = g.getFontMetrics();
+    int charWidth = av.getCharWidth();
+
+    for (int i = fstart; i <= fend; i++)
+    {
+      char s = seq.getCharAt(i);
+
+      if (Comparison.isGap(s))
       {
-        lastSequenceFeatures = sequenceFeatures;
-        if (lastSequenceFeatures != null)
-        {
-          sfSize = lastSequenceFeatures.length;
-        }
+        continue;
       }
+
+      g.setColor(featureColour);
+      int x = (i - start) * charWidth;
+      g.drawRect(x, y1, charWidth, charHeight);
+      g.fillRect(x, y1 + ystrt, charWidth, yend);
+
+      if (colourOnly || !av.isValidCharWidth())
+      {
+        continue;
+      }
+
+      g.setColor(Color.black);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s), charOffset
+              + (charWidth * (i - start)), pady);
     }
+    return true;
+  }
 
-    if (lastSequenceFeatures == null || sfSize == 0)
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Color findFeatureColour(SequenceI seq, int column, Graphics g)
+  {
+    if (!av.isShowSequenceFeatures())
     {
-      return defaultColour;
+      return null;
     }
 
-    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
+    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
+
+    if (sequenceFeatures == null || sequenceFeatures.length == 0)
     {
-      return Color.white.getRGB();
+      return null;
     }
 
-    // Only bother making an offscreen image if transparency is applied
-    if (transparency != 1.0f && offscreenImage == null)
+    if (Comparison.isGap(seq.getCharAt(column)))
     {
-      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+      return Color.white;
     }
 
-    currentColour = null;
-    // TODO: non-threadsafe - each rendering thread needs its own instance of
-    // the feature renderer - or this should be synchronized.
-    offscreenRender = true;
-
-    if (offscreenImage != null)
+    Color renderedColour = null;
+    if (transparency == 1.0f)
     {
-      offscreenImage.setRGB(0, 0, defaultColour);
-      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
-
-      return offscreenImage.getRGB(0, 0);
+      /*
+       * simple case - just find the topmost rendered visible feature colour
+       */
+      renderedColour = findFeatureColour(seq, seq.findPosition(column));
     }
     else
     {
-      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
-
-      if (currentColour == null)
-      {
-        return defaultColour;
-      }
-      else
-      {
-        return currentColour.intValue();
-      }
+      /*
+       * transparency case - draw all visible features in render order to
+       * build up a composite colour on the graphics context
+       */
+      renderedColour = drawSequence(g, seq, column, column, 0, true);
     }
-
+    return renderedColour;
   }
 
-  private volatile SequenceFeature[] lastSequenceFeatures;
-
-  int sfSize;
-
-  int sfindex;
-
-  int spos;
-
-  int epos;
-
   /**
-   * Draws the sequence on the graphics context, or just determines the colour
-   * that would be drawn (if flag offscreenrender is true).
+   * Draws the sequence features on the graphics context, or just determines the
+   * colour that would be drawn (if flag colourOnly is true). Returns the last
+   * colour drawn (which may not be the effective colour if transparency
+   * applies), or null if no feature is drawn in the range given.
    * 
    * @param g
+   *          the graphics context to draw on (may be null if colourOnly==true)
    * @param seq
    * @param start
-   *          start column (or sequence position in offscreenrender mode)
+   *          start column
    * @param end
-   *          end column (not used in offscreenrender mode)
+   *          end column
    * @param y1
    *          vertical offset at which to draw on the graphics
+   * @param colourOnly
+   *          if true, only do enough to determine the colour for the position,
+   *          do not draw the character
+   * @return
    */
-  public synchronized void drawSequence(Graphics g, final SequenceI seq,
-          int start, int end, int y1)
+  public synchronized Color drawSequence(final Graphics g,
+          final SequenceI seq, int start, int end, int y1,
+          boolean colourOnly)
   {
     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
     if (sequenceFeatures == null || sequenceFeatures.length == 0)
     {
-      return;
-    }
-
-    if (g != null)
-    {
-      fm = g.getFontMetrics();
+      return null;
     }
 
     updateFeatures();
 
-    if (lastSeq == null || seq != lastSeq
-            || sequenceFeatures != lastSequenceFeatures)
-    {
-      lastSeq = seq;
-      lastSequenceFeatures = sequenceFeatures;
-    }
-
-    if (transparency != 1 && g != null)
+    if (transparency != 1f && g != null)
     {
       Graphics2D g2 = (Graphics2D) g;
       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
               transparency));
     }
 
-    if (!offscreenRender)
-    {
-      spos = lastSeq.findPosition(start);
-      epos = lastSeq.findPosition(end);
-    }
+    int startPos = seq.findPosition(start);
+    int endPos = seq.findPosition(end);
+
+    int sfSize = sequenceFeatures.length;
+    Color drawnColour = null;
 
-    sfSize = lastSequenceFeatures.length;
+    /*
+     * iterate over features in ordering of their rendering (last is on top)
+     */
     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
     {
       String type = renderOrder[renderIndex];
@@ -340,27 +303,29 @@ public class FeatureRenderer extends FeatureRendererModel
 
       // loop through all features in sequence to find
       // current feature to render
-      for (sfindex = 0; sfindex < sfSize; sfindex++)
+      for (int sfindex = 0; sfindex < sfSize; sfindex++)
       {
-        final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
+        final SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
         if (!sequenceFeature.type.equals(type))
         {
           continue;
         }
 
+        /*
+         * a feature type may be flagged as shown but the group 
+         * an instance of it belongs to may be hidden
+         */
         if (featureGroupNotShown(sequenceFeature))
         {
           continue;
         }
 
         /*
-         * check feature overlaps the visible part of the alignment, 
-         * unless doing offscreenRender (to the Overview window or a 
-         * structure viewer) which is not limited 
+         * check feature overlaps the target range
+         * TODO: efficient retrieval of features overlapping a range
          */
-        if (!offscreenRender
-                && (sequenceFeature.getBegin() > epos || sequenceFeature
-                        .getEnd() < spos))
+        if (sequenceFeature.getBegin() > endPos
+                || sequenceFeature.getEnd() < startPos)
         {
           continue;
         }
@@ -368,58 +333,46 @@ public class FeatureRenderer extends FeatureRendererModel
         Color featureColour = getColour(sequenceFeature);
         boolean isContactFeature = sequenceFeature.isContactFeature();
 
-        if (offscreenRender && offscreenImage == null)
-        {
-          /*
-           * offscreen mode with no image (image is only needed if transparency 
-           * is applied to feature colours) - just check feature is rendered at 
-           * the requested position (start == sequence position in this mode)
-           */
-          boolean featureIsAtPosition = sequenceFeature.begin <= start
-                  && sequenceFeature.end >= start;
-          if (isContactFeature)
-          {
-            featureIsAtPosition = sequenceFeature.begin == start
-                    || sequenceFeature.end == start;
-          }
-          if (featureIsAtPosition)
-          {
-            // this is passed out to the overview and other sequence renderers
-            // (e.g. molecule viewer) to get displayed colour for rendered
-            // sequence
-            currentColour = new Integer(featureColour.getRGB());
-            // used to be retreived from av.featuresDisplayed
-            // currentColour = av.featuresDisplayed
-            // .get(sequenceFeatures[sfindex].type);
-
-          }
-        }
-        else if (isContactFeature)
+        if (isContactFeature)
         {
-          renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
+          boolean drawn = renderFeature(g, seq,
+                  seq.findIndex(sequenceFeature.begin) - 1,
                   seq.findIndex(sequenceFeature.begin) - 1, featureColour,
-                  start, end, y1);
-          renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
+                  start, end, y1, colourOnly);
+          drawn |= renderFeature(g, seq,
+                  seq.findIndex(sequenceFeature.end) - 1,
                   seq.findIndex(sequenceFeature.end) - 1, featureColour,
-                  start, end, y1);
-
+                  start, end, y1, colourOnly);
+          if (drawn)
+          {
+            drawnColour = featureColour;
+          }
         }
         else if (showFeature(sequenceFeature))
         {
-          if (av_isShowSeqFeatureHeight
+          if (av.isShowSequenceFeaturesHeight()
                   && !Float.isNaN(sequenceFeature.score))
           {
-            renderScoreFeature(g, seq,
+            boolean drawn = renderScoreFeature(g, seq,
                     seq.findIndex(sequenceFeature.begin) - 1,
-                    seq.findIndex(sequenceFeature.end) - 1,
-                    featureColour, start, end, y1,
-                    normaliseScore(sequenceFeature));
+                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
+                    start, end, y1, normaliseScore(sequenceFeature),
+                    colourOnly);
+            if (drawn)
+            {
+              drawnColour = featureColour;
+            }
           }
           else
           {
-            renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
-                    seq.findIndex(sequenceFeature.end) - 1,
-                    featureColour, start, end, y1);
+            boolean drawn = renderFeature(g, seq,
+                    seq.findIndex(sequenceFeature.begin) - 1,
+                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
+                    start, end, y1, colourOnly);
+            if (drawn)
+            {
+              drawnColour = featureColour;
+            }
           }
         }
       }
@@ -427,10 +380,14 @@ public class FeatureRenderer extends FeatureRendererModel
 
     if (transparency != 1.0f && g != null)
     {
+      /*
+       * reset transparency
+       */
       Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              1.0f));
+      g2.setComposite(NO_TRANSPARENCY);
     }
+
+    return drawnColour;
   }
 
   /**
@@ -459,7 +416,78 @@ public class FeatureRenderer extends FeatureRendererModel
   @Override
   public void featuresAdded()
   {
-    lastSeq = null;
     findAllFeatures();
   }
+
+  /**
+   * Returns the sequence feature colour rendered at the given sequence
+   * position, or null if none found. The feature of highest render order (i.e.
+   * on top) is found, subject to both feature type and feature group being
+   * visible, and its colour returned.
+   * 
+   * @param seq
+   * @param pos
+   * @return
+   */
+  Color findFeatureColour(SequenceI seq, int pos)
+  {
+    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
+    if (sequenceFeatures == null || sequenceFeatures.length == 0)
+    {
+      return null;
+    }
+  
+    /*
+     * check for new feature added while processing
+     */
+    updateFeatures();
+
+    /*
+     * inspect features in reverse renderOrder (the last in the array is 
+     * displayed on top) until we find one that is rendered at the position
+     */
+    for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
+    {
+      String type = renderOrder[renderIndex];
+      if (!showFeatureOfType(type))
+      {
+        continue;
+      }
+
+      for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
+      {
+        SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
+        if (!sequenceFeature.type.equals(type))
+        {
+          continue;
+        }
+
+        if (featureGroupNotShown(sequenceFeature))
+        {
+          continue;
+        }
+
+        /*
+         * check the column position is within the feature range
+         * (or is one of the two contact positions for a contact feature)
+         */
+        boolean featureIsAtPosition = sequenceFeature.begin <= pos
+                && sequenceFeature.end >= pos;
+        if (sequenceFeature.isContactFeature())
+        {
+          featureIsAtPosition = sequenceFeature.begin == pos
+                  || sequenceFeature.end == pos;
+        }
+        if (featureIsAtPosition)
+        {
+          return getColour(sequenceFeature);
+        }
+      }
+    }
+  
+    /*
+     * no displayed feature found at position
+     */
+    return null;
+  }
 }
index 1a3e9ef..220d3ab 100755 (executable)
@@ -27,6 +27,8 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.AnnotationRenderer;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.IdentityHashMap;
@@ -40,15 +42,25 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   public static final int ABOVE_THRESHOLD = 1;
 
-  public AlignmentAnnotation annotation;
+  private final AlignmentAnnotation annotation;
 
-  int aboveAnnotationThreshold = -1;
+  private final int aboveAnnotationThreshold;
 
   public boolean thresholdIsMinMax = false;
 
-  GraphLine annotationThreshold;
+  private GraphLine annotationThreshold;
 
-  float r1, g1, b1, rr, gg, bb;
+  private int redMin;
+
+  private int greenMin;
+
+  private int blueMin;
+
+  private int redRange;
+
+  private int greenRange;
+
+  private int blueRange;
 
   private boolean predefinedColours = false;
 
@@ -61,7 +73,7 @@ public class AnnotationColourGradient extends FollowerColourScheme
    */
   private boolean noGradient = false;
 
-  IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
+  private IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
 
   @Override
   public ColourSchemeI getInstance(AnnotatedCollectionI sg,
@@ -72,12 +84,12 @@ public class AnnotationColourGradient extends FollowerColourScheme
     acg.thresholdIsMinMax = thresholdIsMinMax;
     acg.annotationThreshold = (annotationThreshold == null) ? null
             : new GraphLine(annotationThreshold);
-    acg.r1 = r1;
-    acg.g1 = g1;
-    acg.b1 = b1;
-    acg.rr = rr;
-    acg.gg = gg;
-    acg.bb = bb;
+    acg.redMin = redMin;
+    acg.greenMin = greenMin;
+    acg.blueMin = blueMin;
+    acg.redRange = redRange;
+    acg.greenRange = greenRange;
+    acg.blueRange = blueRange;
     acg.predefinedColours = predefinedColours;
     acg.seqAssociated = seqAssociated;
     acg.noGradient = noGradient;
@@ -109,12 +121,12 @@ public class AnnotationColourGradient extends FollowerColourScheme
       annotationThreshold = annotation.threshold;
     }
     // clear values so we don't get weird black bands...
-    r1 = 254;
-    g1 = 254;
-    b1 = 254;
-    rr = 0;
-    gg = 0;
-    bb = 0;
+    redMin = 254;
+    greenMin = 254;
+    blueMin = 254;
+    redRange = 0;
+    greenRange = 0;
+    blueRange = 0;
 
     noGradient = true;
     checkLimits();
@@ -135,13 +147,13 @@ public class AnnotationColourGradient extends FollowerColourScheme
       annotationThreshold = annotation.threshold;
     }
 
-    r1 = minColour.getRed();
-    g1 = minColour.getGreen();
-    b1 = minColour.getBlue();
+    redMin = minColour.getRed();
+    greenMin = minColour.getGreen();
+    blueMin = minColour.getBlue();
 
-    rr = maxColour.getRed() - r1;
-    gg = maxColour.getGreen() - g1;
-    bb = maxColour.getBlue() - b1;
+    redRange = maxColour.getRed() - redMin;
+    greenRange = maxColour.getGreen() - greenMin;
+    blueRange = maxColour.getBlue() - blueMin;
 
     noGradient = false;
     checkLimits();
@@ -211,9 +223,9 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   float aamin = 0f, aamax = 0f;
 
-  public String getAnnotation()
+  public AlignmentAnnotation getAnnotation()
   {
-    return annotation.label;
+    return annotation;
   }
 
   public int getAboveThreshold()
@@ -235,12 +247,13 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   public Color getMinColour()
   {
-    return new Color((int) r1, (int) g1, (int) b1);
+    return new Color(redMin, greenMin, blueMin);
   }
 
   public Color getMaxColour()
   {
-    return new Color((int) (r1 + rr), (int) (g1 + gg), (int) (b1 + bb));
+    return new Color(redMin + redRange, greenMin + greenRange, blueMin
+            + blueRange);
   }
 
   /**
@@ -258,137 +271,157 @@ public class AnnotationColourGradient extends FollowerColourScheme
   }
 
   /**
-   * DOCUMENT ME!
+   * Returns the colour for a given character and position in a sequence
    * 
-   * @param n
-   *          DOCUMENT ME!
+   * @param c
+   *          the residue character
    * @param j
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
+   *          the aligned position
+   * @param seq
+   *          the sequence
+   * @return
    */
   @Override
   public Color findColour(char c, int j, SequenceI seq)
   {
-    Color currentColour = Color.white;
-    AlignmentAnnotation annotation = (seqAssociated && seqannot != null ? seqannot
+    /*
+     * locate the annotation we are configured to colour by
+     */
+    AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot
             .get(seq) : this.annotation);
-    if (annotation == null)
+
+    /*
+     * if gap or no annotation at position, no colour (White)
+     */
+    if (ann == null || ann.annotations == null
+            || j >= ann.annotations.length || ann.annotations[j] == null
+            || Comparison.isGap(c))
     {
-      return currentColour;
+      return Color.white;
     }
-    // if ((threshold == 0) || aboveThreshold(c, j))
-    // {
-    if (annotation.annotations != null && j < annotation.annotations.length
-            && annotation.annotations[j] != null
-            && !jalview.util.Comparison.isGap(c))
+
+    Annotation aj = ann.annotations[j];
+    // 'use original colours' => colourScheme != null
+    // -> look up colour to be used
+    // predefined colours => preconfigured shading
+    // -> only use original colours reference if thresholding enabled &
+    // minmax exists
+    // annotation.hasIcons => null or black colours replaced with glyph
+    // colours
+    // -> reuse original colours if present
+    // -> if thresholding enabled then return colour on non-whitespace glyph
+
+    /*
+     * if threshold applies, and annotation fails the test - no colour (white)
+     */
+    if (annotationThreshold != null)
     {
-      Annotation aj = annotation.annotations[j];
-      // 'use original colours' => colourScheme != null
-      // -> look up colour to be used
-      // predefined colours => preconfigured shading
-      // -> only use original colours reference if thresholding enabled &
-      // minmax exists
-      // annotation.hasIcons => null or black colours replaced with glyph
-      // colours
-      // -> reuse original colours if present
-      // -> if thresholding enabled then return colour on non-whitespace glyph
-
-      if (aboveAnnotationThreshold == NO_THRESHOLD
-              || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value
-                      : aj.value <= annotationThreshold.value)))
+      if ((aboveAnnotationThreshold == ABOVE_THRESHOLD && aj.value < annotationThreshold.value)
+              || (aboveAnnotationThreshold == BELOW_THRESHOLD && aj.value > annotationThreshold.value))
       {
-        if (predefinedColours && aj.colour != null
-                && !aj.colour.equals(Color.black))
-        {
-          currentColour = aj.colour;
-        }
-        else if (annotation.hasIcons
-                && annotation.graph == AlignmentAnnotation.NO_GRAPH)
+        return Color.white;
+      }
+    }
+
+    /*
+     * If 'use original colours' then return the colour of the annotation
+     * at the aligned position - computed using the background colour scheme
+     */
+    if (predefinedColours && aj.colour != null
+            && !aj.colour.equals(Color.black))
+    {
+      return aj.colour;
+    }
+
+    Color result = Color.white;
+    if (ann.hasIcons && ann.graph == AlignmentAnnotation.NO_GRAPH)
+    {
+      /*
+       * secondary structure symbol colouring
+       */
+      if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
+              && aj.secondaryStructure != '-')
+      {
+        if (getColourScheme() != null)
         {
-          if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
-                  && aj.secondaryStructure != '-')
-          {
-            if (getColourScheme() != null)
-            {
-              currentColour = getColourScheme().findColour(c, j, seq, null,
-                      0f);
-            }
-            else
-            {
-              if (annotation.isRNA())
-              {
-                currentColour = ColourSchemeProperty.rnaHelices[(int) aj.value];
-              }
-              else
-              {
-                currentColour = annotation.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR
-                        : annotation.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR
-                                : jalview.renderer.AnnotationRenderer.STEM_COLOUR;
-              }
-            }
-          }
-          else
-          {
-            //
-            return Color.white;
-          }
+          result = getColourScheme().findColour(c, j, seq, null, 0f);
         }
-        else if (noGradient)
+        else
         {
-          if (getColourScheme() != null)
+          if (ann.isRNA())
           {
-            currentColour = getColourScheme().findColour(c, j, seq, null,
-                    0f);
+            result = ColourSchemeProperty.rnaHelices[(int) aj.value];
           }
           else
           {
-            if (aj.colour != null)
-            {
-              currentColour = aj.colour;
-            }
+            result = ann.annotations[j].secondaryStructure == 'H' ? AnnotationRenderer.HELIX_COLOUR
+                    : ann.annotations[j].secondaryStructure == 'E' ? AnnotationRenderer.SHEET_COLOUR
+                            : AnnotationRenderer.STEM_COLOUR;
           }
         }
-        else
+      }
+      else
+      {
+        return Color.white;
+      }
+    }
+    else if (noGradient)
+    {
+      if (getColourScheme() != null)
+      {
+        result = getColourScheme().findColour(c, j, seq, null, 0f);
+      }
+      else
+      {
+        if (aj.colour != null)
         {
-          currentColour = shadeCalculation(annotation, j);
+          result = aj.colour;
         }
       }
-      // if (conservationColouring)
-      // {
-      // currentColour = applyConservation(currentColour, j);
-      // }
     }
-    // }
-    return currentColour;
+    else
+    {
+      result = shadeCalculation(ann, j);
+    }
+
+    return result;
   }
 
-  private Color shadeCalculation(AlignmentAnnotation annotation, int j)
+  /**
+   * Returns a graduated colour for the annotation at the given column. If there
+   * is a threshold value, and it is used as the top/bottom of the colour range,
+   * and the value satisfies the threshold condition, then a colour
+   * proportionate to the range from the threshold is calculated. For all other
+   * cases, a colour proportionate to the annotation's min-max range is
+   * calulated. Note that thresholding is _not_ done here (a colour is computed
+   * even if threshold is not passed).
+   * 
+   * @param ann
+   * @param col
+   * @return
+   */
+  Color shadeCalculation(AlignmentAnnotation ann, int col)
   {
-
-    // calculate a shade
     float range = 1f;
-    if (thresholdIsMinMax
-            && annotation.threshold != null
+    float value = ann.annotations[col].value;
+    if (thresholdIsMinMax && ann.threshold != null
             && aboveAnnotationThreshold == ABOVE_THRESHOLD
-            && annotation.annotations[j].value >= annotation.threshold.value)
+            && value >= ann.threshold.value)
     {
-      range = (annotation.annotations[j].value - annotation.threshold.value)
-              / (annotation.graphMax - annotation.threshold.value);
+      range = (value - ann.threshold.value)
+              / (ann.graphMax - ann.threshold.value);
     }
-    else if (thresholdIsMinMax && annotation.threshold != null
+    else if (thresholdIsMinMax && ann.threshold != null
             && aboveAnnotationThreshold == BELOW_THRESHOLD
-            && annotation.annotations[j].value >= annotation.graphMin)
+            && value <= ann.threshold.value)
     {
-      range = (annotation.annotations[j].value - annotation.graphMin)
-              / (annotation.threshold.value - annotation.graphMin);
+      range = (value - ann.graphMin) / (ann.threshold.value - ann.graphMin);
     }
     else
     {
-      if (annotation.graphMax != annotation.graphMin)
+      if (ann.graphMax != ann.graphMin)
       {
-        range = (annotation.annotations[j].value - annotation.graphMin)
-                / (annotation.graphMax - annotation.graphMin);
+        range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin);
       }
       else
       {
@@ -396,11 +429,11 @@ public class AnnotationColourGradient extends FollowerColourScheme
       }
     }
 
-    int dr = (int) (rr * range + r1), dg = (int) (gg * range + g1), db = (int) (bb
-            * range + b1);
+    int dr = (int) (redRange * range + redMin);
+    int dg = (int) (greenRange * range + greenMin);
+    int db = (int) (blueRange * range + blueMin);
 
     return new Color(dr, dg, db);
-
   }
 
   public boolean isPredefinedColours()
@@ -423,6 +456,16 @@ public class AnnotationColourGradient extends FollowerColourScheme
     seqAssociated = sassoc;
   }
 
+  public boolean isThresholdIsMinMax()
+  {
+    return thresholdIsMinMax;
+  }
+
+  public void setThresholdIsMinMax(boolean minMax)
+  {
+    this.thresholdIsMinMax = minMax;
+  }
+
   @Override
   public String getSchemeName()
   {
index f35b886..70f4910 100755 (executable)
@@ -20,6 +20,8 @@
  */
 package jalview.schemes;
 
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.api.analysis.PairwiseScoreModelI;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
@@ -53,6 +55,8 @@ public class Blosum62ColourScheme extends ResidueColourScheme
   public Color findColour(char res, int j, SequenceI seq,
           String consensusResidue, float pid)
   {
+    PairwiseScoreModelI sm = ScoreModels.getInstance().getBlosum62();
+
     /*
      * compare as upper case; note consensusResidue is 
      * always computed as uppercase
@@ -75,14 +79,14 @@ public class Blosum62ColourScheme extends ResidueColourScheme
     }
     else
     {
-      int c = 0;
+      float score = 0;
 
       for (char consensus : consensusResidue.toCharArray())
       {
-        c += ResidueProperties.getBLOSUM62(consensus, res);
+        score += sm.getPairwiseScore(consensus, res);
       }
 
-      if (c > 0)
+      if (score > 0)
       {
         colour = LIGHT_BLUE;
       }
diff --git a/src/jalview/schemes/ColourSchemeLoader.java b/src/jalview/schemes/ColourSchemeLoader.java
new file mode 100644 (file)
index 0000000..8660f3e
--- /dev/null
@@ -0,0 +1,125 @@
+package jalview.schemes;
+
+import jalview.binding.JalviewUserColours;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import org.exolab.castor.xml.Unmarshaller;
+
+public class ColourSchemeLoader
+{
+
+  /**
+   * Loads a user defined colour scheme from file. The file should contain a
+   * definition of residue colours in XML format as defined in
+   * JalviewUserColours.xsd.
+   * 
+   * @param filePath
+   * 
+   * @return
+   */
+  public static UserColourScheme loadColourScheme(String filePath)
+  {
+    UserColourScheme ucs = null;
+    Color[] newColours = null;
+    File file = new File(filePath);
+    try
+    {
+      InputStreamReader in = new InputStreamReader(
+              new FileInputStream(file), "UTF-8");
+  
+      jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
+  
+      org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
+              jucs);
+      jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
+              .unmarshal(in);
+  
+      /*
+       * non-case-sensitive colours are for 20 amino acid codes,
+       * B, Z, X and Gap
+       * optionally, lower-case alternatives for all except Gap
+       */
+      newColours = new Color[24];
+      Color[] lowerCase = new Color[23];
+      boolean caseSensitive = false;
+  
+      String name;
+      int index;
+      for (int i = 0; i < jucs.getColourCount(); i++)
+      {
+        name = jucs.getColour(i).getName();
+        if (ResidueProperties.aa3Hash.containsKey(name))
+        {
+          index = ResidueProperties.aa3Hash.get(name).intValue();
+        }
+        else
+        {
+          index = ResidueProperties.aaIndex[name.charAt(0)];
+        }
+        if (index == -1)
+        {
+          continue;
+        }
+  
+        Color color = new Color(Integer.parseInt(jucs.getColour(i)
+                .getRGB(), 16));
+        if (name.toLowerCase().equals(name))
+        {
+          caseSensitive = true;
+          lowerCase[index] = color;
+        }
+        else
+        {
+          newColours[index] = color;
+        }
+      }
+  
+      /*
+       * instantiate the colour scheme
+       */
+      ucs = new UserColourScheme(newColours);
+      ucs.setName(jucs.getSchemeName());
+      if (caseSensitive)
+      {
+        ucs.setLowerCaseColours(lowerCase);
+      }
+    } catch (Exception ex)
+    {
+      // Could be old Jalview Archive format
+      try
+      {
+        InputStreamReader in = new InputStreamReader(new FileInputStream(
+                file), "UTF-8");
+  
+        jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
+  
+        jucs = JalviewUserColours.unmarshal(in);
+  
+        newColours = new Color[jucs.getColourCount()];
+  
+        for (int i = 0; i < 24; i++)
+        {
+          newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
+                  .getRGB(), 16));
+        }
+        ucs = new UserColourScheme(newColours);
+        ucs.setName(jucs.getSchemeName());
+      } catch (Exception ex2)
+      {
+        ex2.printStackTrace();
+      }
+  
+      if (newColours == null)
+      {
+        System.out.println("Error loading User ColourFile\n" + ex);
+      }
+    }
+  
+    return ucs;
+  }
+
+}
index 817fb01..269811b 100644 (file)
@@ -1,14 +1,9 @@
 package jalview.schemes;
 
-import jalview.binding.JalviewUserColours;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 
-import java.awt.Color;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -20,7 +15,7 @@ public class ColourSchemes
   private static ColourSchemes instance = new ColourSchemes();
 
   /*
-   * a map from scheme name to an instance of it
+   * a map from scheme name (lower-cased) to an instance of it
    */
   private Map<String, ColourSchemeI> schemes;
 
@@ -99,7 +94,10 @@ public class ColourSchemes
    */
   public void removeColourScheme(String name)
   {
-    schemes.remove(name);
+    if (name != null)
+    {
+      schemes.remove(name.toLowerCase());
+    }
   }
   
   /**
@@ -167,124 +165,6 @@ public class ColourSchemes
     {
       return false;
     }
-    name = name.toLowerCase();
-    for (ColourSchemeI scheme : getColourSchemes())
-    {
-      if (name.equals(scheme.getSchemeName().toLowerCase()))
-      {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Loads a user defined colour scheme from file. The file should contain a
-   * definition of residue colours in XML format as defined in
-   * JalviewUserColours.xsd.
-   * 
-   * @param filePath
-   * 
-   * @return
-   */
-  public static UserColourScheme loadColourScheme(String filePath)
-  {
-    UserColourScheme ucs = null;
-    Color[] newColours = null;
-    File file = new File(filePath);
-    try
-    {
-      InputStreamReader in = new InputStreamReader(
-              new FileInputStream(file), "UTF-8");
-  
-      jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
-  
-      org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
-              jucs);
-      jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
-              .unmarshal(in);
-  
-      /*
-       * non-case-sensitive colours are for 20 amino acid codes,
-       * B, Z, X and Gap
-       * optionally, lower-case alternatives for all except Gap
-       */
-      newColours = new Color[24];
-      Color[] lowerCase = new Color[23];
-      boolean caseSensitive = false;
-  
-      String name;
-      int index;
-      for (int i = 0; i < jucs.getColourCount(); i++)
-      {
-        name = jucs.getColour(i).getName();
-        if (ResidueProperties.aa3Hash.containsKey(name))
-        {
-          index = ResidueProperties.aa3Hash.get(name).intValue();
-        }
-        else
-        {
-          index = ResidueProperties.aaIndex[name.charAt(0)];
-        }
-        if (index == -1)
-        {
-          continue;
-        }
-  
-        Color color = new Color(Integer.parseInt(jucs.getColour(i)
-                .getRGB(), 16));
-        if (name.toLowerCase().equals(name))
-        {
-          caseSensitive = true;
-          lowerCase[index] = color;
-        }
-        else
-        {
-          newColours[index] = color;
-        }
-      }
-  
-      /*
-       * instantiate the colour scheme
-       */
-      ucs = new UserColourScheme(newColours);
-      ucs.setName(jucs.getSchemeName());
-      if (caseSensitive)
-      {
-        ucs.setLowerCaseColours(lowerCase);
-      }
-    } catch (Exception ex)
-    {
-      // Could be old Jalview Archive format
-      try
-      {
-        InputStreamReader in = new InputStreamReader(new FileInputStream(
-                file), "UTF-8");
-  
-        jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
-  
-        jucs = JalviewUserColours.unmarshal(in);
-  
-        newColours = new Color[jucs.getColourCount()];
-  
-        for (int i = 0; i < 24; i++)
-        {
-          newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
-                  .getRGB(), 16));
-        }
-        ucs = new UserColourScheme(newColours);
-        ucs.setName(jucs.getSchemeName());
-      } catch (Exception ex2)
-      {
-        ex2.printStackTrace();
-      }
-  
-      if (newColours == null)
-      {
-        System.out.println("Error loading User ColourFile\n" + ex);
-      }
-    }
-  
-    return ucs;
+    return schemes.containsKey(name.toLowerCase());
   }
 }
index 406814a..751175d 100755 (executable)
  */
 package jalview.schemes;
 
-import jalview.analysis.scoremodels.FeatureScoreModel;
-import jalview.analysis.scoremodels.PIDScoreModel;
-import jalview.api.analysis.ScoreModelI;
-
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,8 +32,6 @@ import java.util.Vector;
 
 public class ResidueProperties
 {
-  public static Hashtable<String, ScoreModelI> scoreMatrices = new Hashtable<String, ScoreModelI>();
-
   // Stores residue codes/names and colours and other things
   public static final int[] aaIndex; // aaHash version 2.1.1 and below
 
@@ -477,105 +471,6 @@ public class ResidueProperties
 
   // public static final double hydmax = 1.38;
   // public static final double hydmin = -2.53;
-  private static final int[][] BLOSUM62 = {
-      { 4, -1, -2, -2, 0, -1, -1, 0, -2, -1, -1, -1, -1, -2, -1, 1, 0, -3,
-          -2, 0, -2, -1, 0, -4 },
-      { -1, 5, 0, -2, -3, 1, 0, -2, 0, -3, -2, 2, -1, -3, -2, -1, -1, -3,
-          -2, -3, -1, 0, -1, -4 },
-      { -2, 0, 6, 1, -3, 0, 0, 0, 1, -3, -3, 0, -2, -3, -2, 1, 0, -4, -2,
-          -3, 3, 0, -1, -4 },
-      { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3, -4, -1, -3, -3, -1, 0, -1, -4,
-          -3, -3, 4, 1, -1, -4 },
-      { 0, 3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1, -1,
-          -2, -2, -1, -3, -3, -2, -4 },
-      { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3, -2, 1, 0, -3, -1, 0, -1, -2, -1,
-          -2, 0, 3, -1, -4 },
-      { -1, 0, 0, 2, -4, 2, 5, -2, 0, -3, -3, 1, -2, -3, -1, 0, -1, -3, -2,
-          -2, 1, 4, -1, -4 },
-      { 0, -2, 0, -1, -3, -2, -2, 6, -2, -4, -4, -2, -3, -3, -2, 0, -2, -2,
-          -3, -3, -1, -2, -1, -4 },
-      { -2, 0, 1, -1, -3, 0, 0, -2, 8, -3, -3, -1, -2, -1, -2, -1, -2, -2,
-          2, -3, 0, 0, -1, -4 },
-      { -1, -3, -3, -3, -1, -3, -3, -4, -3, 4, 2, -3, 1, 0, -3, -2, -1, -3,
-          -1, 3, -3, -3, -1, -4 },
-      { -1, -2, -3, -4, -1, -2, -3, -4, -3, 2, 4, -2, 2, 0, -3, -2, -1, -2,
-          -1, 1, -4, -3, -1, -4 },
-      { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -2, 5, -1, -3, -1, 0, -1, -3,
-          -2, -2, 0, 1, -1, -4 },
-      { -1, -1, -2, -3, -1, 0, -2, -3, -2, 1, 2, -1, 5, 0, -2, -1, -1, -1,
-          -1, 1, -3, -1, -1, -4 },
-      { -2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6, -4, -2, -2, 1,
-          3, -1, -3, -3, -1, -4 },
-      { -1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7, -1, -1,
-          -4, -3, -2, -2, -1, -2, -4 },
-      { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4, 1, -3, -2,
-          -2, 0, 0, 0, -4 },
-      { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 5, -2,
-          -2, 0, -1, -1, 0, -4 },
-      { -3, -3, -4, -4, -2, -2, -3, -2, -2, -3, -2, -3, -1, 1, -4, -3, -2,
-          11, 2, -3, -4, -3, -2, -4 },
-      { -2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2,
-          2, 7, -1, -3, -2, -1, -4 },
-      { 0, -3, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3,
-          -1, 4, -3, -2, -1, -4 },
-      { -2, -1, 3, 4, -3, 0, 1, -1, 0, -3, -4, 0, -3, -3, -2, 0, -1, -4,
-          -3, -3, 4, 1, -1, -4 },
-      { -1, 0, 0, 1, -3, 3, 4, -2, 0, -3, -3, 1, -1, -3, -1, 0, -1, -3, -2,
-          -2, 1, 4, -1, -4 },
-      { 0, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, 0, 0,
-          -2, -1, -1, -1, -1, -1, -4 },
-      { -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4,
-          -4, -4, -4, -4, -4, -4, 1 }, };
-
-  static final int[][] PAM250 = {
-      { 2, -2, 0, 0, -2, 0, 0, 1, -1, -1, -2, -1, -1, -3, 1, 1, 1, -6, -3,
-          0, 0, 0, 0, -8 },
-      { -2, 6, 0, -1, -4, 1, -1, -3, 2, -2, -3, 3, 0, -4, 0, 0, -1, 2, -4,
-          -2, -1, 0, -1, -8 },
-      { 0, 0, 2, 2, -4, 1, 1, 0, 2, -2, -3, 1, -2, -3, 0, 1, 0, -4, -2, -2,
-          2, 1, 0, -8 },
-      { 0, -1, 2, 4, -5, 2, 3, 1, 1, -2, -4, 0, -3, -6, -1, 0, 0, -7, -4,
-          -2, 3, 3, -1, -8 },
-      { -2, -4, -4, -5, 12, -5, -5, -3, -3, -2, -6, -5, -5, -4, -3, 0, -2,
-          -8, 0, -2, -4, -5, -3, -8 },
-      { 0, 1, 1, 2, -5, 4, 2, -1, 3, -2, -2, 1, -1, -5, 0, -1, -1, -5, -4,
-          -2, 1, 3, -1, -8 },
-      { 0, -1, 1, 3, -5, 2, 4, 0, 1, -2, -3, 0, -2, -5, -1, 0, 0, -7, -4,
-          -2, 3, 3, -1, -8 },
-      { 1, -3, 0, 1, -3, -1, 0, 5, -2, -3, -4, -2, -3, -5, 0, 1, 0, -7, -5,
-          -1, 0, 0, -1, -8 },
-      { -1, 2, 2, 1, -3, 3, 1, -2, 6, -2, -2, 0, -2, -2, 0, -1, -1, -3, 0,
-          -2, 1, 2, -1, -8 },
-      { -1, -2, -2, -2, -2, -2, -2, -3, -2, 5, 2, -2, 2, 1, -2, -1, 0, -5,
-          -1, 4, -2, -2, -1, -8 },
-      { -2, -3, -3, -4, -6, -2, -3, -4, -2, 2, 6, -3, 4, 2, -3, -3, -2, -2,
-          -1, 2, -3, -3, -1, -8 },
-      { -1, 3, 1, 0, -5, 1, 0, -2, 0, -2, -3, 5, 0, -5, -1, 0, 0, -3, -4,
-          -2, 1, 0, -1, -8 },
-      { -1, 0, -2, -3, -5, -1, -2, -3, -2, 2, 4, 0, 6, 0, -2, -2, -1, -4,
-          -2, 2, -2, -2, -1, -8 },
-      { -3, -4, -3, -6, -4, -5, -5, -5, -2, 1, 2, -5, 0, 9, -5, -3, -3, 0,
-          7, -1, -4, -5, -2, -8 },
-      { 1, 0, 0, -1, -3, 0, -1, 0, 0, -2, -3, -1, -2, -5, 6, 1, 0, -6, -5,
-          -1, -1, 0, -1, -8 },
-      { 1, 0, 1, 0, 0, -1, 0, 1, -1, -1, -3, 0, -2, -3, 1, 2, 1, -2, -3,
-          -1, 0, 0, 0, -8 },
-      { 1, -1, 0, 0, -2, -1, 0, 0, -1, 0, -2, 0, -1, -3, 0, 1, 3, -5, -3,
-          0, 0, -1, 0, -8 },
-      { -6, 2, -4, -7, -8, -5, -7, -7, -3, -5, -2, -3, -4, 0, -6, -2, -5,
-          17, 0, -6, -5, -6, -4, -8 },
-      { -3, -4, -2, -4, 0, -4, -4, -5, 0, -1, -1, -4, -2, 7, -5, -3, -3, 0,
-          10, -2, -3, -4, -2, -8 },
-      { 0, -2, -2, -2, -2, -2, -2, -1, -2, 4, 2, -2, 2, -1, -1, -1, 0, -6,
-          -2, 4, -2, -2, -1, -8 },
-      { 0, -1, 2, 3, -4, 1, 3, 0, 1, -2, -3, 1, -2, -4, -1, 0, 0, -5, -3,
-          -2, 3, 2, -1, -8 },
-      { 0, 0, 1, 3, -5, 3, 3, 0, 2, -2, -3, 0, -2, -5, 0, 0, -1, -6, -4,
-          -2, 2, 3, -1, -8 },
-      { 0, -1, 0, -1, -3, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, 0, 0, -4,
-          -2, -1, -1, -1, -1, -8 },
-      { -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8,
-          -8, -8, -8, -8, -8, -8, 1 }, };
 
   // not currently used
   // public static final Map<String, Color> ssHash = new Hashtable<String,
@@ -598,33 +493,6 @@ public class ResidueProperties
    * Color.white, // R Color.white, // Y Color.white, // N Color.white, // Gap
    */
 
-  // JBPNote: patch matrix for T/U equivalence when working with DNA or RNA.
-  // Will equate sequences if working with mixed nucleotide sets.
-  // treats T and U identically. R and Y weak equivalence with AG and CTU.
-  // N matches any other base weakly
-  //
-  static final int[][] DNA = { { 10, -8, -8, -8, -8, 1, 1, 1, -8, 1, 1 }, // A
-      { -8, 10, -8, -8, -8, 1, 1, -8, 1, 1, 1 }, // C
-      { -8, -8, 10, -8, -8, 1, 1, 1, -8, 1, 1 }, // G
-      { -8, -8, -8, 10, 10, 1, 1, -8, 1, 1, 1 }, // T
-      { -8, -8, -8, 10, 10, 1, 1, -8, 1, 1, 1 }, // U
-      { 1, 1, 1, 1, 1, 10, 0, 0, 0, 1, 1 }, // I
-      { 1, 1, 1, 1, 1, 0, 10, 0, 0, 1, 1 }, // X
-      { 1, -8, 1, -8, -8, 0, 0, 10, -8, 1, 1 }, // R
-      { -8, 1, -8, 1, 1, 0, 0, -8, 10, 1, 1 }, // Y
-      { 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1 }, // N
-      { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // -
-  };
-  /**
-   * register matrices in list
-   */
-  static
-  {
-    scoreMatrices.put("BLOSUM62", new ScoreMatrix("BLOSUM62", BLOSUM62, 0));
-    scoreMatrices.put("PAM250", new ScoreMatrix("PAM250", PAM250, 0));
-    scoreMatrices.put("DNA", new ScoreMatrix("DNA", DNA, 1));
-  }
-
   public static List<String> STOP = Arrays.asList("TGA", "TAA", "TAG");
 
   public static String START = "ATG";
@@ -1244,15 +1112,6 @@ public class ResidueProperties
       propMatrixPos[i][i] = maxP;
       propMatrixEpos[i][i] = maxEP;
     }
-    // JAL-1512 comment out physicochemical score matrices for 2.8.1 release
-    // scoreMatrices.put("Conservation Pos", new
-    // ScoreMatrix("Conservation Pos",propMatrixPos,0));
-    // scoreMatrices.put("Conservation Both", new
-    // ScoreMatrix("Conservation Both",propMatrixF,0));
-    // scoreMatrices.put("Conservation EnhPos", new
-    // ScoreMatrix("Conservation EnhPos",propMatrixEpos,0));
-    scoreMatrices.put("PID", new PIDScoreModel());
-    scoreMatrices.put("Displayed Features", new FeatureScoreModel());
   }
 
   private ResidueProperties()
@@ -1279,39 +1138,6 @@ public class ResidueProperties
     return aa3Hash;
   }
 
-  public static int[][] getDNA()
-  {
-    return ResidueProperties.DNA;
-  }
-
-  public static int[][] getBLOSUM62()
-  {
-    return ResidueProperties.BLOSUM62;
-  }
-
-  public static int getPAM250(String A1, String A2)
-  {
-    return getPAM250(A1.charAt(0), A2.charAt(0));
-  }
-
-  public static int getBLOSUM62(char c1, char c2)
-  {
-    int pog = 0;
-
-    try
-    {
-      int a = aaIndex[c1];
-      int b = aaIndex[c2];
-
-      pog = ResidueProperties.BLOSUM62[a][b];
-    } catch (Exception e)
-    {
-      // System.out.println("Unknown residue in " + A1 + " " + A2);
-    }
-
-    return pog;
-  }
-
   public static String codonTranslate(String lccodon)
   {
     String cdn = codonHash2.get(lccodon.toUpperCase());
@@ -1322,53 +1148,6 @@ public class ResidueProperties
     return cdn;
   }
 
-  public static int[][] getDefaultPeptideMatrix()
-  {
-    return ResidueProperties.getBLOSUM62();
-  }
-
-  public static int[][] getDefaultDnaMatrix()
-  {
-    return ResidueProperties.getDNA();
-  }
-
-  /**
-   * get a ScoreMatrix based on its string name
-   * 
-   * @param pwtype
-   * @return matrix in scoreMatrices with key pwtype or null
-   */
-  public static ScoreMatrix getScoreMatrix(String pwtype)
-  {
-    Object val = scoreMatrices.get(pwtype);
-    if (val != null && val instanceof ScoreMatrix)
-    {
-      return (ScoreMatrix) val;
-    }
-    return null;
-  }
-
-  /**
-   * get a ScoreModel based on its string name
-   * 
-   * @param pwtype
-   * @return scoremodel of type pwtype or null
-   */
-  public static ScoreModelI getScoreModel(String pwtype)
-  {
-    return scoreMatrices.get(pwtype);
-  }
-
-  public static int getPAM250(char c, char d)
-  {
-    int a = aaIndex[c];
-    int b = aaIndex[d];
-
-    int pog = ResidueProperties.PAM250[a][b];
-
-    return pog;
-  }
-
   public static Hashtable<String, String> toDssp3State;
   static
   {
diff --git a/src/jalview/schemes/ScoreMatrix.java b/src/jalview/schemes/ScoreMatrix.java
deleted file mode 100644 (file)
index 5e5fa8d..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.schemes;
-
-import jalview.analysis.scoremodels.PairwiseSeqScoreModel;
-import jalview.api.analysis.ScoreModelI;
-
-public class ScoreMatrix extends PairwiseSeqScoreModel implements
-        ScoreModelI
-{
-  String name;
-
-  @Override
-  public String getName()
-  {
-    return name;
-  }
-
-  /**
-   * reference to integer score matrix
-   */
-  int[][] matrix;
-
-  /**
-   * 0 for Protein Score matrix. 1 for dna score matrix
-   */
-  int type;
-
-  /**
-   * 
-   * @param name
-   *          Unique, human readable name for the matrix
-   * @param matrix
-   *          Pairwise scores indexed according to appropriate symbol alphabet
-   * @param type
-   *          0 for Protein, 1 for NA
-   */
-  ScoreMatrix(String name, int[][] matrix, int type)
-  {
-    this.matrix = matrix;
-    this.type = type;
-    this.name = name;
-  }
-
-  @Override
-  public boolean isDNA()
-  {
-    return type == 1;
-  }
-
-  @Override
-  public boolean isProtein()
-  {
-    return type == 0;
-  }
-
-  @Override
-  public int[][] getMatrix()
-  {
-    return matrix;
-  }
-
-  /**
-   * 
-   * @param A1
-   * @param A2
-   * @return score for substituting first char in A1 with first char in A2
-   */
-  public int getPairwiseScore(String A1, String A2)
-  {
-    return getPairwiseScore(A1.charAt(0), A2.charAt(0));
-  }
-
-  public int getPairwiseScore(char c, char d)
-  {
-    int pog = 0;
-
-    try
-    {
-      int a = (type == 0) ? ResidueProperties.aaIndex[c]
-              : ResidueProperties.nucleotideIndex[c];
-      int b = (type == 0) ? ResidueProperties.aaIndex[d]
-              : ResidueProperties.nucleotideIndex[d];
-
-      pog = matrix[a][b];
-    } catch (Exception e)
-    {
-      // System.out.println("Unknown residue in " + A1 + " " + A2);
-    }
-
-    return pog;
-  }
-
-  /**
-   * pretty print the matrix
-   */
-  public String toString()
-  {
-    return outputMatrix(false);
-  }
-
-  public String outputMatrix(boolean html)
-  {
-    StringBuffer sb = new StringBuffer();
-    int[] symbols = (type == 0) ? ResidueProperties.aaIndex
-            : ResidueProperties.nucleotideIndex;
-    int symMax = (type == 0) ? ResidueProperties.maxProteinIndex
-            : ResidueProperties.maxNucleotideIndex;
-    boolean header = true;
-    if (html)
-    {
-      sb.append("<table border=\"1\">");
-    }
-    for (char sym = 'A'; sym <= 'Z'; sym++)
-    {
-      if (symbols[sym] >= 0 && symbols[sym] < symMax)
-      {
-        if (header)
-        {
-          sb.append(html ? "<tr><td></td>" : "");
-          for (char sym2 = 'A'; sym2 <= 'Z'; sym2++)
-          {
-            if (symbols[sym2] >= 0 && symbols[sym2] < symMax)
-            {
-              sb.append((html ? "<td>&nbsp;" : "\t") + sym2
-                      + (html ? "&nbsp;</td>" : ""));
-            }
-          }
-          header = false;
-          sb.append(html ? "</tr>\n" : "\n");
-        }
-        if (html)
-        {
-          sb.append("<tr>");
-        }
-        sb.append((html ? "<td>" : "") + sym + (html ? "</td>" : ""));
-        for (char sym2 = 'A'; sym2 <= 'Z'; sym2++)
-        {
-          if (symbols[sym2] >= 0 && symbols[sym2] < symMax)
-          {
-            sb.append((html ? "<td>" : "\t")
-                    + matrix[symbols[sym]][symbols[sym2]]
-                    + (html ? "</td>" : ""));
-          }
-        }
-        sb.append(html ? "</tr>\n" : "\n");
-      }
-    }
-    if (html)
-    {
-      sb.append("</table>");
-    }
-    return sb.toString();
-  }
-}
index a19acef..f20cd31 100644 (file)
@@ -29,9 +29,8 @@ package jalview.structure;
  */
 public class AtomSpec
 {
-  // TODO clarify do we want pdbFile here, or pdbId?
-  // compare highlightAtom in 2.8.2 for JalviewJmolBinding and
-  // javascript.MouseOverStructureListener
+  int modelNo;
+
   private String pdbFile;
 
   private String chain;
@@ -41,6 +40,60 @@ public class AtomSpec
   private int atomIndex;
 
   /**
+   * Parses a Chimera atomspec e.g. #1:12.A to construct an AtomSpec model (with
+   * null pdb file name)
+   * 
+   * @param spec
+   * @return
+   * @throw IllegalArgumentException if the spec cannot be parsed, or represents
+   *        more than one residue
+   */
+  public static AtomSpec fromChimeraAtomspec(String spec)
+  {
+    int colonPos = spec.indexOf(":");
+    if (colonPos == -1)
+    {
+      throw new IllegalArgumentException(spec);
+    }
+
+    int hashPos = spec.indexOf("#");
+    if (hashPos == -1 && colonPos != 0)
+    {
+      // # is missing but something precedes : - reject
+      throw new IllegalArgumentException(spec);
+    }
+
+    String modelSubmodel = spec.substring(hashPos + 1, colonPos);
+    int dotPos = modelSubmodel.indexOf(".");
+    int modelId = 0;
+    try
+    {
+      modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
+              : modelSubmodel.substring(0, dotPos));
+    } catch (NumberFormatException e)
+    {
+      // ignore, default to model 0
+    }
+
+    String residueChain = spec.substring(colonPos + 1);
+    dotPos = residueChain.indexOf(".");
+    int resNum = 0;
+    try
+    {
+      resNum = Integer.parseInt(dotPos == -1 ? residueChain
+              : residueChain.substring(0, dotPos));
+    } catch (NumberFormatException e)
+    {
+      // could be a range e.g. #1:4-7.B
+      throw new IllegalArgumentException(spec);
+    }
+
+    String chainId = dotPos == -1 ? "" : residueChain.substring(dotPos + 1);
+
+    return new AtomSpec(modelId, chainId, resNum, 0);
+  }
+
+  /**
    * Constructor
    * 
    * @param pdbFile
@@ -56,6 +109,22 @@ public class AtomSpec
     this.atomIndex = atomNo;
   }
 
+  /**
+   * Constructor
+   * 
+   * @param modelId
+   * @param chainId
+   * @param resNo
+   * @param atomNo
+   */
+  public AtomSpec(int modelId, String chainId, int resNo, int atomNo)
+  {
+    this.modelNo = modelId;
+    this.chain = chainId;
+    this.pdbResNum = resNo;
+    this.atomIndex = atomNo;
+  }
+
   public String getPdbFile()
   {
     return pdbFile;
@@ -76,6 +145,16 @@ public class AtomSpec
     return atomIndex;
   }
 
+  public int getModelNumber()
+  {
+    return modelNo;
+  }
+
+  public void setPdbFile(String file)
+  {
+    pdbFile = file;
+  }
+
   @Override
   public String toString()
   {
index 2877d46..fe878ce 100644 (file)
@@ -38,5 +38,7 @@ public interface SelectionListener
    *          - source of the selection event
    */
   public void selection(jalview.datamodel.SequenceGroup seqsel,
-          jalview.datamodel.ColumnSelection colsel, SelectionSource source);
+          jalview.datamodel.ColumnSelection colsel,
+          jalview.datamodel.HiddenColumns hidden,
+          SelectionSource source);
 }
index e5c5d04..9fde3f1 100644 (file)
@@ -31,7 +31,7 @@ public interface StructureListener
    * handles messages for, or null if generic listener (only used by
    * removeListener method)
    */
-  public String[] getPdbFile();
+  public String[] getStructureFiles();
 
   /**
    * Called by StructureSelectionManager to inform viewer to highlight given
index 78634e0..40789ed 100644 (file)
@@ -23,7 +23,9 @@ package jalview.structure;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceI;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 public class StructureMapping
 {
@@ -47,6 +49,18 @@ public class StructureMapping
   // and atomNo
   HashMap<Integer, int[]> mapping;
 
+  /**
+   * Constructor
+   * 
+   * @param seq
+   * @param pdbfile
+   * @param pdbid
+   * @param chain
+   * @param mapping
+   *          a map from sequence to two values, { resNo, atomNo } in the
+   *          structure
+   * @param mappingDetails
+   */
   public StructureMapping(SequenceI seq, String pdbfile, String pdbid,
           String chain, HashMap<Integer, int[]> mapping,
           String mappingDetails)
@@ -111,6 +125,70 @@ public class StructureMapping
   }
 
   /**
+   * Returns a (possibly empty) list of [start, end] residue positions in the
+   * mapped structure, corresponding to the given range of sequence positions
+   * 
+   * @param fromSeqPos
+   * @param toSeqPos
+   * @return
+   */
+  public List<int[]> getPDBResNumRanges(int fromSeqPos, int toSeqPos)
+  {
+    List<int[]> result = new ArrayList<int[]>();
+    int startRes = -1;
+    int endRes = -1;
+
+    for (int i = fromSeqPos; i <= toSeqPos; i++)
+    {
+      int resNo = getPDBResNum(i);
+      if (resNo == UNASSIGNED_VALUE)
+      {
+        continue; // no mapping from this sequence position
+      }
+      if (startRes == -1)
+      {
+        startRes = resNo;
+        endRes = resNo;
+      }
+      if (resNo >= startRes && resNo <= endRes)
+      {
+        // within the current range - no change
+        continue;
+      }
+      if (resNo == startRes - 1)
+      {
+        // extend beginning of current range
+        startRes--;
+        continue;
+      }
+      if (resNo == endRes + 1)
+      {
+        // extend end of current range
+        endRes++;
+        continue;
+      }
+
+      /*
+       * resNo is not within or contiguous with last range,
+       * so write out the last range
+       */
+      result.add(new int[] { startRes, endRes });
+      startRes = resNo;
+      endRes = resNo;
+    }
+
+    /*
+     * and add the last range
+     */
+    if (startRes != -1)
+    {
+      result.add(new int[] { startRes, endRes });
+    }
+
+    return result;
+  }
+
+  /**
    * 
    * @param pdbResNum
    * @return -1 or the corresponding sequence position for a pdb residue number
index 65fd5e7..db0b47e 100644 (file)
@@ -29,12 +29,14 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JmolParser;
 import jalview.gui.IProgressIndicator;
+import jalview.io.AppletFormatAdapter;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
 import jalview.util.MappingUtils;
@@ -384,6 +386,7 @@ public class StructureSelectionManager
     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
     try
     {
+      sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
       pdb = new JmolParser(pdbFile, sourceType);
 
       if (pdb.getId() != null && pdb.getId().trim().length() > 0
@@ -454,7 +457,7 @@ public class StructureSelectionManager
        * Attempt pairwise alignment of the sequence with each chain in the PDB,
        * and remember the highest scoring chain
        */
-      int max = -10;
+      float max = -10;
       AlignSeq maxAlignseq = null;
       String maxChainId = " ";
       PDBChain maxChain = null;
@@ -589,11 +592,9 @@ public class StructureSelectionManager
     return pdb;
   }
 
-  private boolean isCIFFile(String filename)
+  public void addStructureMapping(StructureMapping sm)
   {
-    String fileExt = filename.substring(filename.lastIndexOf(".") + 1,
-            filename.length());
-    return "cif".equalsIgnoreCase(fileExt);
+    mappings.add(sm);
   }
 
   /**
@@ -741,7 +742,7 @@ public class StructureSelectionManager
       if (listeners.elementAt(i) instanceof StructureListener)
       {
         sl = (StructureListener) listeners.elementAt(i);
-        for (String pdbfile : sl.getPdbFile())
+        for (String pdbfile : sl.getStructureFiles())
         {
           pdbs.remove(pdbfile);
         }
@@ -806,6 +807,27 @@ public class StructureSelectionManager
       return;
     }
 
+    SearchResultsI results = findAlignmentPositionsForStructurePositions(atoms);
+    for (Object li : listeners)
+    {
+      if (li instanceof SequenceListener)
+      {
+        ((SequenceListener) li).highlightSequence(results);
+      }
+    }
+  }
+
+  /**
+   * Constructs a SearchResults object holding regions (if any) in the Jalview
+   * alignment which have a mapping to the structure viewer positions in the
+   * supplied list
+   * 
+   * @param atoms
+   * @return
+   */
+  public SearchResultsI findAlignmentPositionsForStructurePositions(
+          List<AtomSpec> atoms)
+  {
     SearchResultsI results = new SearchResults();
     for (AtomSpec atom : atoms)
     {
@@ -831,13 +853,7 @@ public class StructureSelectionManager
         }
       }
     }
-    for (Object li : listeners)
-    {
-      if (li instanceof SequenceListener)
-      {
-        ((SequenceListener) li).highlightSequence(results);
-      }
-    }
+    return results;
   }
 
   /**
@@ -1188,13 +1204,14 @@ public class StructureSelectionManager
 
   public synchronized void sendSelection(
           jalview.datamodel.SequenceGroup selection,
-          jalview.datamodel.ColumnSelection colsel, SelectionSource source)
+          jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
+          SelectionSource source)
   {
     for (SelectionListener slis : sel_listeners)
     {
       if (slis != source)
       {
-        slis.selection(selection, colsel, source);
+        slis.selection(selection, colsel, hidden, source);
       }
     }
   }
diff --git a/src/jalview/structure/StructureSelectionManager.java~ b/src/jalview/structure/StructureSelectionManager.java~
new file mode 100644 (file)
index 0000000..5b06097
--- /dev/null
@@ -0,0 +1,1327 @@
+/*
+ * 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.structure;
+
+import jalview.analysis.AlignSeq;
+import jalview.api.StructureSelectionManagerProvider;
+import jalview.commands.CommandI;
+import jalview.commands.EditCommand;
+import jalview.commands.OrderCommand;
+import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SequenceI;
+import jalview.gui.IProgressIndicator;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.StructureFile;
+import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
+import jalview.ws.sifts.SiftsClient;
+import jalview.ws.sifts.SiftsException;
+import jalview.ws.sifts.SiftsSettings;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import MCview.Atom;
+import MCview.PDBChain;
+import MCview.PDBfile;
+
+public class StructureSelectionManager
+{
+  public final static String NEWLINE = System.lineSeparator();
+
+  static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
+
+  private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
+
+  private boolean processSecondaryStructure = false;
+
+  private boolean secStructServices = false;
+
+  private boolean addTempFacAnnot = false;
+
+  private SiftsClient siftsClient = null;
+
+  /*
+   * Set of any registered mappings between (dataset) sequences.
+   */
+  private List<AlignedCodonFrame> seqmappings = new ArrayList<AlignedCodonFrame>();
+
+  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
+
+  private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
+
+  /**
+   * @return true if will try to use external services for processing secondary
+   *         structure
+   */
+  public boolean isSecStructServices()
+  {
+    return secStructServices;
+  }
+
+  /**
+   * control use of external services for processing secondary structure
+   * 
+   * @param secStructServices
+   */
+  public void setSecStructServices(boolean secStructServices)
+  {
+    this.secStructServices = secStructServices;
+  }
+
+  /**
+   * flag controlling addition of any kind of structural annotation
+   * 
+   * @return true if temperature factor annotation will be added
+   */
+  public boolean isAddTempFacAnnot()
+  {
+    return addTempFacAnnot;
+  }
+
+  /**
+   * set flag controlling addition of structural annotation
+   * 
+   * @param addTempFacAnnot
+   */
+  public void setAddTempFacAnnot(boolean addTempFacAnnot)
+  {
+    this.addTempFacAnnot = addTempFacAnnot;
+  }
+
+  /**
+   * 
+   * @return if true, the structure manager will attempt to add secondary
+   *         structure lines for unannotated sequences
+   */
+
+  public boolean isProcessSecondaryStructure()
+  {
+    return processSecondaryStructure;
+  }
+
+  /**
+   * Control whether structure manager will try to annotate mapped sequences
+   * with secondary structure from PDB data.
+   * 
+   * @param enable
+   */
+  public void setProcessSecondaryStructure(boolean enable)
+  {
+    processSecondaryStructure = enable;
+  }
+
+  /**
+   * debug function - write all mappings to stdout
+   */
+  public void reportMapping()
+  {
+    if (mappings.isEmpty())
+    {
+      System.err.println("reportMapping: No PDB/Sequence mappings.");
+    }
+    else
+    {
+      System.err.println("reportMapping: There are " + mappings.size()
+              + " mappings.");
+      int i = 0;
+      for (StructureMapping sm : mappings)
+      {
+        System.err.println("mapping " + i++ + " : " + sm.pdbfile);
+      }
+    }
+  }
+
+  /**
+   * map between the PDB IDs (or structure identifiers) used by Jalview and the
+   * absolute filenames for PDB data that corresponds to it
+   */
+  Map<String, String> pdbIdFileName = new HashMap<String, String>();
+
+  Map<String, String> pdbFileNameId = new HashMap<String, String>();
+
+  public void registerPDBFile(String idForFile, String absoluteFile)
+  {
+    pdbIdFileName.put(idForFile, absoluteFile);
+    pdbFileNameId.put(absoluteFile, idForFile);
+  }
+
+  public String findIdForPDBFile(String idOrFile)
+  {
+    String id = pdbFileNameId.get(idOrFile);
+    return id;
+  }
+
+  public String findFileForPDBId(String idOrFile)
+  {
+    String id = pdbIdFileName.get(idOrFile);
+    return id;
+  }
+
+  public boolean isPDBFileRegistered(String idOrFile)
+  {
+    return pdbFileNameId.containsKey(idOrFile)
+            || pdbIdFileName.containsKey(idOrFile);
+  }
+
+  private static StructureSelectionManager nullProvider = null;
+
+  public static StructureSelectionManager getStructureSelectionManager(
+          StructureSelectionManagerProvider context)
+  {
+    if (context == null)
+    {
+      if (nullProvider == null)
+      {
+        if (instances != null)
+        {
+          throw new Error(
+                  MessageManager
+                          .getString("error.implementation_error_structure_selection_manager_null"),
+                  new NullPointerException(MessageManager
+                          .getString("exception.ssm_context_is_null")));
+        }
+        else
+        {
+          nullProvider = new StructureSelectionManager();
+        }
+        return nullProvider;
+      }
+    }
+    if (instances == null)
+    {
+      instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
+    }
+    StructureSelectionManager instance = instances.get(context);
+    if (instance == null)
+    {
+      if (nullProvider != null)
+      {
+        instance = nullProvider;
+      }
+      else
+      {
+        instance = new StructureSelectionManager();
+      }
+      instances.put(context, instance);
+    }
+    return instance;
+  }
+
+  /**
+   * flag controlling whether SeqMappings are relayed from received sequence
+   * mouse over events to other sequences
+   */
+  boolean relaySeqMappings = true;
+
+  /**
+   * Enable or disable relay of seqMapping events to other sequences. You might
+   * want to do this if there are many sequence mappings and the host computer
+   * is slow
+   * 
+   * @param relay
+   */
+  public void setRelaySeqMappings(boolean relay)
+  {
+    relaySeqMappings = relay;
+  }
+
+  /**
+   * get the state of the relay seqMappings flag.
+   * 
+   * @return true if sequence mouse overs are being relayed to other mapped
+   *         sequences
+   */
+  public boolean isRelaySeqMappingsEnabled()
+  {
+    return relaySeqMappings;
+  }
+
+  Vector listeners = new Vector();
+
+  /**
+   * register a listener for alignment sequence mouseover events
+   * 
+   * @param svl
+   */
+  public void addStructureViewerListener(Object svl)
+  {
+    if (!listeners.contains(svl))
+    {
+      listeners.addElement(svl);
+    }
+  }
+
+  /**
+   * Returns the file name for a mapped PDB id (or null if not mapped).
+   * 
+   * @param pdbid
+   * @return
+   */
+  public String alreadyMappedToFile(String pdbid)
+  {
+    for (StructureMapping sm : mappings)
+    {
+      if (sm.getPdbId().equals(pdbid))
+      {
+        return sm.pdbfile;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Import structure data and register a structure mapping for broadcasting
+   * colouring, mouseovers and selection events (convenience wrapper).
+   * 
+   * @param sequence
+   *          - one or more sequences to be mapped to pdbFile
+   * @param targetChains
+   *          - optional chain specification for mapping each sequence to pdb
+   *          (may be nill, individual elements may be nill)
+   * @param pdbFile
+   *          - structure data resource
+   * @param protocol
+   *          - how to resolve data from resource
+   * @return null or the structure data parsed as a pdb file
+   */
+  synchronized public StructureFile setMapping(SequenceI[] sequence,
+          String[] targetChains, String pdbFile, String protocol,
+          IProgressIndicator progress)
+  {
+    return computeMapping(true, sequence, targetChains, pdbFile, protocol,
+            progress);
+  }
+
+
+  /**
+   * create sequence structure mappings between each sequence and the given
+   * pdbFile (retrieved via the given protocol).
+   * 
+   * @param forStructureView
+   *          when true, record the mapping for use in mouseOvers
+   * 
+   * @param sequenceArray
+   *          - one or more sequences to be mapped to pdbFile
+   * @param targetChainIds
+   *          - optional chain specification for mapping each sequence to pdb
+   *          (may be null, individual elements may be null)
+   * @param pdbFile
+   *          - structure data resource
+   * @param protocol
+   *          - how to resolve data from resource
+   * @return null or the structure data parsed as a pdb file
+   */
+  synchronized public StructureFile setMapping(boolean forStructureView,
+          SequenceI[] sequenceArray, String[] targetChainIds,
+          String pdbFile,
+          String protocol)
+  {
+    return computeMapping(forStructureView, sequenceArray, targetChainIds,
+            pdbFile, protocol, null);
+  }
+
+  synchronized public StructureFile computeMapping(
+          boolean forStructureView, SequenceI[] sequenceArray,
+          String[] targetChainIds, String pdbFile, String protocol,
+          IProgressIndicator progress)
+  {
+    long progressSessionId = System.currentTimeMillis() * 3;
+    /*
+     * There will be better ways of doing this in the future, for now we'll use
+     * the tried and tested MCview pdb mapping
+     */
+    boolean parseSecStr = processSecondaryStructure;
+    if (isPDBFileRegistered(pdbFile))
+    {
+      for (SequenceI sq : sequenceArray)
+      {
+        SequenceI ds = sq;
+        while (ds.getDatasetSequence() != null)
+        {
+          ds = ds.getDatasetSequence();
+        }
+        ;
+        if (ds.getAnnotation() != null)
+        {
+          for (AlignmentAnnotation ala : ds.getAnnotation())
+          {
+            // false if any annotation present from this structure
+            // JBPNote this fails for jmol/chimera view because the *file* is
+            // passed, not the structure data ID -
+            if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
+            {
+              parseSecStr = false;
+            }
+          }
+        }
+      }
+    }
+    StructureFile pdb = null;
+    boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
+    try
+    {
+
+      if (pdbFile != null && isCIFFile(pdbFile))
+      {
+        pdb = new jalview.ext.jmol.JmolParser(addTempFacAnnot, parseSecStr,
+                secStructServices, pdbFile, protocol);
+      }
+      else
+      {
+        pdb = new PDBfile(addTempFacAnnot, parseSecStr, secStructServices,
+                pdbFile, protocol);
+      }
+
+      if (pdb.getId() != null && pdb.getId().trim().length() > 0
+              && AppletFormatAdapter.FILE.equals(protocol))
+      {
+        registerPDBFile(pdb.getId().trim(), pdbFile);
+      }
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+      return null;
+    }
+
+    try
+    {
+      if (isMapUsingSIFTs)
+      {
+        siftsClient = new SiftsClient(pdb);
+      }
+    } catch (SiftsException e)
+    {
+      isMapUsingSIFTs = false;
+      e.printStackTrace();
+    }
+
+    String targetChainId;
+    for (int s = 0; s < sequenceArray.length; s++)
+    {
+      boolean infChain = true;
+      final SequenceI seq = sequenceArray[s];
+      if (targetChainIds != null && targetChainIds[s] != null)
+      {
+        infChain = false;
+        targetChainId = targetChainIds[s];
+      }
+      else if (seq.getName().indexOf("|") > -1)
+      {
+        targetChainId = seq.getName().substring(
+                seq.getName().lastIndexOf("|") + 1);
+        if (targetChainId.length() > 1)
+        {
+          if (targetChainId.trim().length() == 0)
+          {
+            targetChainId = " ";
+          }
+          else
+          {
+            // not a valid chain identifier
+            targetChainId = "";
+          }
+        }
+      }
+      else
+      {
+        targetChainId = "";
+      }
+
+      /*
+       * Attempt pairwise alignment of the sequence with each chain in the PDB,
+       * and remember the highest scoring chain
+       */
+      int max = -10;
+      AlignSeq maxAlignseq = null;
+      String maxChainId = " ";
+      PDBChain maxChain = null;
+      boolean first = true;
+      for (PDBChain chain : pdb.getChains())
+      {
+        if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
+                && !infChain)
+        {
+          continue; // don't try to map chains don't match.
+        }
+        // TODO: correctly determine sequence type for mixed na/peptide
+        // structures
+        final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
+        AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
+                type);
+        // equivalent to:
+        // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
+        // as.calcScoreMatrix();
+        // as.traceAlignment();
+
+        if (first || as.maxscore > max
+                || (as.maxscore == max && chain.id.equals(targetChainId)))
+        {
+          first = false;
+          maxChain = chain;
+          max = as.maxscore;
+          maxAlignseq = as;
+          maxChainId = chain.id;
+        }
+      }
+      if (maxChain == null)
+      {
+        continue;
+      }
+
+      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
+      {
+        pdbFile = "INLINE" + pdb.getId();
+      }
+      ArrayList<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
+      if (isMapUsingSIFTs)
+      {
+<<<<<<< Updated upstream
+        setProgressBar(null);
+        setProgressBar(MessageManager
+                .getString("status.obtaining_mapping_with_sifts"));
+=======
+        if (progress!=null) {
+          progress.setProgressBar("Obtaining mapping with SIFTS",
+                  progressSessionId);
+        }
+>>>>>>> Stashed changes
+        jalview.datamodel.Mapping sqmpping = maxAlignseq
+                .getMappingFromS1(false);
+        if (targetChainId != null && !targetChainId.trim().isEmpty())
+        {
+          StructureMapping siftsMapping;
+          try
+          {
+            siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
+                    pdb, maxChain, sqmpping, maxAlignseq);
+            seqToStrucMapping.add(siftsMapping);
+            maxChain.makeExactMapping(maxAlignseq, seq);
+            maxChain.transferRESNUMFeatures(seq, null);
+            maxChain.transferResidueAnnotation(siftsMapping, sqmpping);
+          } catch (SiftsException e)
+          {
+            // fall back to NW alignment
+            System.err.println(e.getMessage());
+            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
+                    targetChainId, maxChain, pdb, maxAlignseq);
+            seqToStrucMapping.add(nwMapping);
+          }
+        }
+        else
+        {
+          ArrayList<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
+          for (PDBChain chain : pdb.getChains())
+          {
+            try
+            {
+              StructureMapping siftsMapping = getStructureMapping(seq,
+                      pdbFile,
+                      chain.id, pdb, chain, sqmpping, maxAlignseq);
+              foundSiftsMappings.add(siftsMapping);
+            } catch (SiftsException e)
+            {
+              System.err.println(e.getMessage());
+            }
+          }
+          if (!foundSiftsMappings.isEmpty())
+          {
+            seqToStrucMapping.addAll(foundSiftsMappings);
+            maxChain.makeExactMapping(maxAlignseq, seq);
+            maxChain.transferRESNUMFeatures(seq, null);
+            maxChain.transferResidueAnnotation(foundSiftsMappings.get(0),
+                    sqmpping);
+          }
+          else
+          {
+            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
+                    maxChainId, maxChain, pdb, maxAlignseq);
+            seqToStrucMapping.add(nwMapping);
+          }
+        }
+      }
+      else
+      {
+<<<<<<< Updated upstream
+        setProgressBar(null);
+        setProgressBar(MessageManager
+                .getString("status.obtaining_mapping_with_nw_alignment"));
+=======
+        if (progress != null)
+        {
+          progress.setProgressBar("Obtaining mapping with NW alignment",
+                  progressSessionId);
+        }
+>>>>>>> Stashed changes
+        seqToStrucMapping.add(getNWMappings(seq, pdbFile, maxChainId,
+                maxChain, pdb, maxAlignseq));
+      }
+      if (forStructureView)
+      {
+        mappings.addAll(seqToStrucMapping);
+      }
+      if (progress != null)
+      {
+        progress.setProgressBar(null, progressSessionId);
+      }
+    }
+    return pdb;
+  }
+
+  private boolean isCIFFile(String filename)
+  {
+    String fileExt = filename.substring(filename.lastIndexOf(".") + 1,
+            filename.length());
+    return "cif".equalsIgnoreCase(fileExt);
+  }
+
+  private StructureMapping getStructureMapping(SequenceI seq,
+          String pdbFile, String targetChainId, StructureFile pdb,
+          PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
+          AlignSeq maxAlignseq) throws SiftsException
+  {
+      StructureMapping curChainMapping = siftsClient
+              .getSiftsStructureMapping(seq, pdbFile, targetChainId);
+      try
+      {
+      PDBChain chain = pdb.findChain(targetChainId);
+      if (chain != null)
+      {
+        chain.transferResidueAnnotation(curChainMapping, sqmpping);
+      }
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      }
+      return curChainMapping;
+  }
+
+  private StructureMapping getNWMappings(SequenceI seq,
+          String pdbFile,
+          String maxChainId, PDBChain maxChain, StructureFile pdb,
+          AlignSeq maxAlignseq)
+  {
+    final StringBuilder mappingDetails = new StringBuilder(128);
+    mappingDetails.append(NEWLINE).append(
+            "Sequence \u27f7 Structure mapping details");
+    mappingDetails.append(NEWLINE);
+    mappingDetails
+            .append("Method: inferred with Needleman & Wunsch alignment");
+    mappingDetails.append(NEWLINE).append("PDB Sequence is :")
+            .append(NEWLINE).append("Sequence = ")
+            .append(maxChain.sequence.getSequenceAsString());
+    mappingDetails.append(NEWLINE).append("No of residues = ")
+            .append(maxChain.residues.size()).append(NEWLINE)
+            .append(NEWLINE);
+    PrintStream ps = new PrintStream(System.out)
+    {
+      @Override
+      public void print(String x)
+      {
+        mappingDetails.append(x);
+      }
+
+      @Override
+      public void println()
+      {
+        mappingDetails.append(NEWLINE);
+      }
+    };
+
+    maxAlignseq.printAlignment(ps);
+
+    mappingDetails.append(NEWLINE).append("PDB start/end ");
+    mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
+            .append(" ");
+    mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
+    mappingDetails.append(NEWLINE).append("SEQ start/end ");
+    mappingDetails.append(
+            String.valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
+            .append(" ");
+    mappingDetails.append(String.valueOf(maxAlignseq.seq1end
+            + (seq.getStart() - 1)));
+    mappingDetails.append(NEWLINE);
+    maxChain.makeExactMapping(maxAlignseq, seq);
+    jalview.datamodel.Mapping sqmpping = maxAlignseq
+            .getMappingFromS1(false);
+    maxChain.transferRESNUMFeatures(seq, null);
+
+    HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
+    int resNum = -10000;
+    int index = 0;
+    char insCode = ' ';
+
+    do
+    {
+      Atom tmp = maxChain.atoms.elementAt(index);
+      if ((resNum != tmp.resNumber || insCode != tmp.insCode)
+              && tmp.alignmentMapping != -1)
+      {
+        resNum = tmp.resNumber;
+        insCode = tmp.insCode;
+        if (tmp.alignmentMapping >= -1)
+        {
+          mapping.put(tmp.alignmentMapping + 1, new int[] { tmp.resNumber,
+              tmp.atomIndex });
+        }
+      }
+
+      index++;
+    } while (index < maxChain.atoms.size());
+
+    StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
+            pdb.getId(), maxChainId, mapping, mappingDetails.toString());
+    maxChain.transferResidueAnnotation(nwMapping, sqmpping);
+    return nwMapping;
+  }
+
+  public void removeStructureViewerListener(Object svl, String[] pdbfiles)
+  {
+    listeners.removeElement(svl);
+    if (svl instanceof SequenceListener)
+    {
+      for (int i = 0; i < listeners.size(); i++)
+      {
+        if (listeners.elementAt(i) instanceof StructureListener)
+        {
+          ((StructureListener) listeners.elementAt(i))
+                  .releaseReferences(svl);
+        }
+      }
+    }
+
+    if (pdbfiles == null)
+    {
+      return;
+    }
+
+    /*
+     * Remove mappings to the closed listener's PDB files, but first check if
+     * another listener is still interested
+     */
+    List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
+
+    StructureListener sl;
+    for (int i = 0; i < listeners.size(); i++)
+    {
+      if (listeners.elementAt(i) instanceof StructureListener)
+      {
+        sl = (StructureListener) listeners.elementAt(i);
+        for (String pdbfile : sl.getPdbFile())
+        {
+          pdbs.remove(pdbfile);
+        }
+      }
+    }
+
+    /*
+     * Rebuild the mappings set, retaining only those which are for 'other' PDB
+     * files
+     */
+    if (pdbs.size() > 0)
+    {
+      List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+      for (StructureMapping sm : mappings)
+      {
+        if (!pdbs.contains(sm.pdbfile))
+        {
+          tmp.add(sm);
+        }
+      }
+
+      mappings = tmp;
+    }
+  }
+
+  /**
+   * Propagate mouseover of a single position in a structure
+   * 
+   * @param pdbResNum
+   * @param chain
+   * @param pdbfile
+   */
+  public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
+  {
+    AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
+    List<AtomSpec> atoms = Collections.singletonList(atomSpec);
+    mouseOverStructure(atoms);
+  }
+
+  /**
+   * Propagate mouseover or selection of multiple positions in a structure
+   * 
+   * @param atoms
+   */
+  public void mouseOverStructure(List<AtomSpec> atoms)
+  {
+    if (listeners == null)
+    {
+      // old or prematurely sent event
+      return;
+    }
+    boolean hasSequenceListener = false;
+    for (int i = 0; i < listeners.size(); i++)
+    {
+      if (listeners.elementAt(i) instanceof SequenceListener)
+      {
+        hasSequenceListener = true;
+      }
+    }
+    if (!hasSequenceListener)
+    {
+      return;
+    }
+
+    SearchResults results = new SearchResults();
+    for (AtomSpec atom : atoms)
+    {
+      SequenceI lastseq = null;
+      int lastipos = -1;
+      for (StructureMapping sm : mappings)
+      {
+        if (sm.pdbfile.equals(atom.getPdbFile())
+                && sm.pdbchain.equals(atom.getChain()))
+        {
+          int indexpos = sm.getSeqPos(atom.getPdbResNum());
+          if (lastipos != indexpos && lastseq != sm.sequence)
+          {
+            results.addResult(sm.sequence, indexpos, indexpos);
+            lastipos = indexpos;
+            lastseq = sm.sequence;
+            // construct highlighted sequence list
+            for (AlignedCodonFrame acf : seqmappings)
+            {
+              acf.markMappedRegion(sm.sequence, indexpos, results);
+            }
+          }
+        }
+      }
+    }
+    for (Object li : listeners)
+    {
+      if (li instanceof SequenceListener)
+      {
+        ((SequenceListener) li).highlightSequence(results);
+      }
+    }
+  }
+
+  /**
+   * highlight regions associated with a position (indexpos) in seq
+   * 
+   * @param seq
+   *          the sequence that the mouse over occurred on
+   * @param indexpos
+   *          the absolute position being mouseovered in seq (0 to seq.length())
+   * @param seqPos
+   *          the sequence position (if -1, seq.findPosition is called to
+   *          resolve the residue number)
+   */
+  public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
+          VamsasSource source)
+  {
+    boolean hasSequenceListeners = handlingVamsasMo
+            || !seqmappings.isEmpty();
+    SearchResults results = null;
+    if (seqPos == -1)
+    {
+      seqPos = seq.findPosition(indexpos);
+    }
+    for (int i = 0; i < listeners.size(); i++)
+    {
+      Object listener = listeners.elementAt(i);
+      if (listener == source)
+      {
+        // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
+        // Temporary fudge with SequenceListener.getVamsasSource()
+        continue;
+      }
+      if (listener instanceof StructureListener)
+      {
+        highlightStructure((StructureListener) listener, seq, seqPos);
+      }
+      else
+      {
+        if (listener instanceof SequenceListener)
+        {
+          final SequenceListener seqListener = (SequenceListener) listener;
+          if (hasSequenceListeners
+                  && seqListener.getVamsasSource() != source)
+          {
+            if (relaySeqMappings)
+            {
+              if (results == null)
+              {
+                results = MappingUtils.buildSearchResults(seq, seqPos,
+                        seqmappings);
+              }
+              if (handlingVamsasMo)
+              {
+                results.addResult(seq, seqPos, seqPos);
+
+              }
+              if (!results.isEmpty())
+              {
+                seqListener.highlightSequence(results);
+              }
+            }
+          }
+        }
+        else if (listener instanceof VamsasListener && !handlingVamsasMo)
+        {
+          ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
+                  source);
+        }
+        else if (listener instanceof SecondaryStructureListener)
+        {
+          ((SecondaryStructureListener) listener).mouseOverSequence(seq,
+                  indexpos, seqPos);
+        }
+      }
+    }
+  }
+
+  /**
+   * Send suitable messages to a StructureListener to highlight atoms
+   * corresponding to the given sequence position(s)
+   * 
+   * @param sl
+   * @param seq
+   * @param positions
+   */
+  public void highlightStructure(StructureListener sl, SequenceI seq,
+          int... positions)
+  {
+    if (!sl.isListeningFor(seq))
+    {
+      return;
+    }
+    int atomNo;
+    List<AtomSpec> atoms = new ArrayList<AtomSpec>();
+    for (StructureMapping sm : mappings)
+    {
+      if (sm.sequence == seq
+              || sm.sequence == seq.getDatasetSequence()
+              || (sm.sequence.getDatasetSequence() != null && sm.sequence
+                      .getDatasetSequence() == seq.getDatasetSequence()))
+      {
+        for (int index : positions)
+        {
+          atomNo = sm.getAtomNum(index);
+
+          if (atomNo > 0)
+          {
+            atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
+                    .getPDBResNum(index), atomNo));
+          }
+        }
+      }
+    }
+    sl.highlightAtoms(atoms);
+  }
+
+  /**
+   * true if a mouse over event from an external (ie Vamsas) source is being
+   * handled
+   */
+  boolean handlingVamsasMo = false;
+
+  long lastmsg = 0;
+
+  /**
+   * as mouseOverSequence but only route event to SequenceListeners
+   * 
+   * @param sequenceI
+   * @param position
+   *          in an alignment sequence
+   */
+  public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
+          VamsasSource source)
+  {
+    handlingVamsasMo = true;
+    long msg = sequenceI.hashCode() * (1 + position);
+    if (lastmsg != msg)
+    {
+      lastmsg = msg;
+      mouseOverSequence(sequenceI, position, -1, source);
+    }
+    handlingVamsasMo = false;
+  }
+
+  public Annotation[] colourSequenceFromStructure(SequenceI seq,
+          String pdbid)
+  {
+    return null;
+    // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
+    // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
+    /*
+     * Annotation [] annotations = new Annotation[seq.getLength()];
+     * 
+     * StructureListener sl; int atomNo = 0; for (int i = 0; i <
+     * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
+     * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
+     * 
+     * for (int j = 0; j < mappings.length; j++) {
+     * 
+     * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
+     * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
+     * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
+     * "+mappings[j].pdbfile);
+     * 
+     * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
+     * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
+     * 
+     * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
+     * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
+     * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
+     * mappings[j].pdbfile); }
+     * 
+     * annotations[index] = new Annotation("X",null,' ',0,col); } return
+     * annotations; } } } }
+     * 
+     * return annotations;
+     */
+  }
+
+  public void structureSelectionChanged()
+  {
+  }
+
+  public void sequenceSelectionChanged()
+  {
+  }
+
+  public void sequenceColoursChanged(Object source)
+  {
+    StructureListener sl;
+    for (int i = 0; i < listeners.size(); i++)
+    {
+      if (listeners.elementAt(i) instanceof StructureListener)
+      {
+        sl = (StructureListener) listeners.elementAt(i);
+        sl.updateColours(source);
+      }
+    }
+  }
+
+  public StructureMapping[] getMapping(String pdbfile)
+  {
+    List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+    for (StructureMapping sm : mappings)
+    {
+      if (sm.pdbfile.equals(pdbfile))
+      {
+        tmp.add(sm);
+      }
+    }
+    return tmp.toArray(new StructureMapping[tmp.size()]);
+  }
+
+  /**
+   * Returns a readable description of all mappings for the given pdbfile to any
+   * of the given sequences
+   * 
+   * @param pdbfile
+   * @param seqs
+   * @return
+   */
+  public String printMappings(String pdbfile, List<SequenceI> seqs)
+  {
+    if (pdbfile == null || seqs == null || seqs.isEmpty())
+    {
+      return "";
+    }
+
+    StringBuilder sb = new StringBuilder(64);
+    for (StructureMapping sm : mappings)
+    {
+      if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
+      {
+        sb.append(sm.mappingDetails);
+        sb.append(NEWLINE);
+        // separator makes it easier to read multiple mappings
+        sb.append("=====================");
+        sb.append(NEWLINE);
+      }
+    }
+    sb.append(NEWLINE);
+
+    return sb.toString();
+  }
+
+  /**
+   * Remove the given mapping
+   * 
+   * @param acf
+   */
+  public void deregisterMapping(AlignedCodonFrame acf)
+  {
+    if (acf != null)
+    {
+      boolean removed = seqmappings.remove(acf);
+      if (removed && seqmappings.isEmpty())
+      { // debug
+        System.out.println("All mappings removed");
+      }
+    }
+  }
+
+  /**
+   * Add each of the given codonFrames to the stored set, if not aready present.
+   * 
+   * @param mappings
+   */
+  public void registerMappings(List<AlignedCodonFrame> mappings)
+  {
+    if (mappings != null)
+    {
+      for (AlignedCodonFrame acf : mappings)
+      {
+        registerMapping(acf);
+      }
+    }
+  }
+
+  /**
+   * Add the given mapping to the stored set, unless already stored.
+   */
+  public void registerMapping(AlignedCodonFrame acf)
+  {
+    if (acf != null)
+    {
+      if (!seqmappings.contains(acf))
+      {
+        seqmappings.add(acf);
+      }
+    }
+  }
+
+  /**
+   * Resets this object to its initial state by removing all registered
+   * listeners, codon mappings, PDB file mappings
+   */
+  public void resetAll()
+  {
+    if (mappings != null)
+    {
+      mappings.clear();
+    }
+    if (seqmappings != null)
+    {
+      seqmappings.clear();
+    }
+    if (sel_listeners != null)
+    {
+      sel_listeners.clear();
+    }
+    if (listeners != null)
+    {
+      listeners.clear();
+    }
+    if (commandListeners != null)
+    {
+      commandListeners.clear();
+    }
+    if (view_listeners != null)
+    {
+      view_listeners.clear();
+    }
+    if (pdbFileNameId != null)
+    {
+      pdbFileNameId.clear();
+    }
+    if (pdbIdFileName != null)
+    {
+      pdbIdFileName.clear();
+    }
+  }
+
+  public void addSelectionListener(SelectionListener selecter)
+  {
+    if (!sel_listeners.contains(selecter))
+    {
+      sel_listeners.add(selecter);
+    }
+  }
+
+  public void removeSelectionListener(SelectionListener toremove)
+  {
+    if (sel_listeners.contains(toremove))
+    {
+      sel_listeners.remove(toremove);
+    }
+  }
+
+  public synchronized void sendSelection(
+          jalview.datamodel.SequenceGroup selection,
+          jalview.datamodel.ColumnSelection colsel, SelectionSource source)
+  {
+    for (SelectionListener slis : sel_listeners)
+    {
+      if (slis != source)
+      {
+        slis.selection(selection, colsel, source);
+      }
+    }
+  }
+
+  Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
+
+  public synchronized void sendViewPosition(
+          jalview.api.AlignmentViewPanel source, int startRes, int endRes,
+          int startSeq, int endSeq)
+  {
+
+    if (view_listeners != null && view_listeners.size() > 0)
+    {
+      Enumeration<AlignmentViewPanelListener> listeners = view_listeners
+              .elements();
+      while (listeners.hasMoreElements())
+      {
+        AlignmentViewPanelListener slis = listeners.nextElement();
+        if (slis != source)
+        {
+          slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
+        }
+        ;
+      }
+    }
+  }
+
+  /**
+   * release all references associated with this manager provider
+   * 
+   * @param jalviewLite
+   */
+  public static void release(StructureSelectionManagerProvider jalviewLite)
+  {
+    // synchronized (instances)
+    {
+      if (instances == null)
+      {
+        return;
+      }
+      StructureSelectionManager mnger = (instances.get(jalviewLite));
+      if (mnger != null)
+      {
+        instances.remove(jalviewLite);
+        try
+        {
+          mnger.finalize();
+        } catch (Throwable x)
+        {
+        }
+      }
+    }
+  }
+
+  public void registerPDBEntry(PDBEntry pdbentry)
+  {
+    if (pdbentry.getFile() != null
+            && pdbentry.getFile().trim().length() > 0)
+    {
+      registerPDBFile(pdbentry.getId(), pdbentry.getFile());
+    }
+  }
+
+  public void addCommandListener(CommandListener cl)
+  {
+    if (!commandListeners.contains(cl))
+    {
+      commandListeners.add(cl);
+    }
+  }
+
+  public boolean hasCommandListener(CommandListener cl)
+  {
+    return this.commandListeners.contains(cl);
+  }
+
+  public boolean removeCommandListener(CommandListener l)
+  {
+    return commandListeners.remove(l);
+  }
+
+  /**
+   * Forward a command to any command listeners (except for the command's
+   * source).
+   * 
+   * @param command
+   *          the command to be broadcast (in its form after being performed)
+   * @param undo
+   *          if true, the command was being 'undone'
+   * @param source
+   */
+  public void commandPerformed(CommandI command, boolean undo,
+          VamsasSource source)
+  {
+    for (CommandListener listener : commandListeners)
+    {
+      listener.mirrorCommand(command, undo, this, source);
+    }
+  }
+
+  /**
+   * Returns a new CommandI representing the given command as mapped to the
+   * given sequences. If no mapping could be made, or the command is not of a
+   * mappable kind, returns null.
+   * 
+   * @param command
+   * @param undo
+   * @param mapTo
+   * @param gapChar
+   * @return
+   */
+  public CommandI mapCommand(CommandI command, boolean undo,
+          final AlignmentI mapTo, char gapChar)
+  {
+    if (command instanceof EditCommand)
+    {
+      return MappingUtils.mapEditCommand((EditCommand) command, undo,
+              mapTo, gapChar, seqmappings);
+    }
+    else if (command instanceof OrderCommand)
+    {
+      return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
+              mapTo, seqmappings);
+    }
+    return null;
+  }
+
+  public List<AlignedCodonFrame> getSequenceMappings()
+  {
+    return seqmappings;
+  }
+
+}
index fda08fd..1637631 100644 (file)
 package jalview.structures.models;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
@@ -42,6 +41,7 @@ import jalview.util.MessageManager;
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 
 /**
@@ -521,18 +521,18 @@ public abstract class AAStructureBindingModel extends
    *          the sequence alignment which is the basis of structure
    *          superposition
    * @param matched
-   *          an array of booleans, indexed by alignment column, where true
-   *          indicates that every structure has a mapped residue present in the
-   *          column (so the column can participate in structure alignment)
+   *          a BitSet, where bit j is set to indicate that every structure has
+   *          a mapped residue present in column j (so the column can
+   *          participate in structure alignment)
    * @param structures
    *          an array of data beans corresponding to pdb file index
    * @return
    */
   protected int findSuperposableResidues(AlignmentI alignment,
-          boolean[] matched, SuperposeData[] structures)
+          BitSet matched, SuperposeData[] structures)
   {
     int refStructure = -1;
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
     if (files == null)
     {
       return -1;
@@ -559,16 +559,16 @@ public abstract class AAStructureBindingModel extends
             {
               refStructure = pdbfnum;
             }
-            for (int r = 0; r < matched.length; r++)
+            for (int r = 0; r < alignment.getWidth(); r++)
             {
-              if (!matched[r])
+              if (!matched.get(r))
               {
                 continue;
               }
               int pos = getMappedPosition(theSequence, r, mapping);
               if (pos < 1 || pos == lastPos)
               {
-                matched[r] = false;
+                matched.clear(r);
                 continue;
               }
               lastPos = pos;
@@ -700,24 +700,29 @@ public abstract class AAStructureBindingModel extends
 
   public abstract void setJalviewColourScheme(ColourSchemeI cs);
 
-  public abstract void superposeStructures(AlignmentI[] als, int[] alm,
-          ColumnSelection[] alc);
-
-  public abstract void setBackgroundColour(Color col);
-
-  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment);
-
   /**
-   * returns the current featureRenderer that should be used to colour the
-   * structures
-   * 
-   * @param alignment
+   * Constructs and sends a command to align structures against a reference
+   * structure, based on one or more sequence alignments. May optionally return
+   * an error or warning message for the alignment command.
    * 
+   * @param alignments
+   *          an array of alignments to process
+   * @param structureIndices
+   *          an array of corresponding reference structures (index into pdb
+   *          file array); if a negative value is passed, the first PDB file
+   *          mapped to an alignment sequence is used as the reference for
+   *          superposition
+   * @param hiddenCols
+   *          an array of corresponding hidden columns for each alignment
    * @return
    */
-  public abstract FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment);
+  public abstract String superposeStructures(AlignmentI[] alignments,
+          int[] structureIndices, HiddenColumns[] hiddenCols);
+
+  public abstract void setBackgroundColour(Color col);
+
+  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
 
   /**
    * returns the current sequenceRenderer that should be used to colour the
@@ -743,8 +748,6 @@ public abstract class AAStructureBindingModel extends
    */
   public void colourBySequence(AlignmentViewPanel alignmentv)
   {
-    boolean showFeatures = alignmentv.getAlignViewport()
-            .isShowSequenceFeatures();
     if (!colourBySequence || !isLoadingFinished())
     {
       return;
@@ -753,19 +756,12 @@ public abstract class AAStructureBindingModel extends
     {
       return;
     }
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
   
     SequenceRenderer sr = getSequenceRenderer(alignmentv);
   
-    FeatureRenderer fr = null;
-    if (showFeatures)
-    {
-      fr = getFeatureRenderer(alignmentv);
-    }
-    AlignmentI alignment = alignmentv.getAlignment();
-  
     StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
-            files, sr, fr, alignment);
+            files, sr, alignmentv);
     colourBySequence(colourBySequenceCommands);
   }
 
@@ -773,4 +769,7 @@ public abstract class AAStructureBindingModel extends
   {
     return fileLoadingError != null && fileLoadingError.length() > 0;
   }
+
+  public abstract jalview.api.FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment);
 }
index 0eabff7..09566c3 100644 (file)
@@ -36,17 +36,22 @@ import java.util.List;
 
 public class UrlLinkDisplay
 {
-  private String id; // id is not supplied to display, but used to identify
-                     // entries when saved
+  // column positions
+  public static final int DATABASE = 0;
 
-  private boolean isPrimary;
+  public static final int NAME = 1;
 
-  private boolean isSelected;
+  public static final int URL = 2;
 
-  private UrlLink link;
+  public static final int SELECTED = 3;
+
+  public static final int PRIMARY = 4;
+
+  public static final int ID = 5;
 
   // Headers for columns in table
-  private final static List<String> colNames = new ArrayList<String>()
+  @SuppressWarnings("serial")
+  private static final List<String> COLNAMES = new ArrayList<String>()
   {
     {
       add(MessageManager.formatMessage("label.database"));
@@ -58,18 +63,14 @@ public class UrlLinkDisplay
     }
   };
 
-  // column positions
-  public final static int DATABASE = 0;
-
-  public final static int NAME = 1;
-
-  public final static int URL = 2;
+  private String id; // id is not supplied to display, but used to identify
+  // entries when saved
 
-  public final static int SELECTED = 3;
+  private boolean isPrimary;
 
-  public final static int PRIMARY = 4;
+  private boolean isSelected;
 
-  public final static int ID = 5;
+  private UrlLink link;
 
   public UrlLinkDisplay(String rowId, UrlLink rowLink,
           boolean rowSelected, boolean rowDefault)
@@ -176,6 +177,7 @@ public class UrlLinkDisplay
       break;
     case NAME:
       setDescription((String) value);
+      // deliberate fall through
     case DATABASE:
       setDBName((String) value);
       break;
@@ -199,13 +201,9 @@ public class UrlLinkDisplay
       // so only allow editing if it is not
       return (!link.usesDBAccession());
     }
-    else if (index == SELECTED)
-    {
-      return true;
-    }
     else
     {
-      return false;
+      return index == SELECTED;
     }
   }
 
@@ -217,6 +215,6 @@ public class UrlLinkDisplay
   public static List<String> getDisplayColumnNames()
   {
     // Display names between DESCRIPTION and ID (excludes ID)
-    return colNames.subList(DATABASE, ID);
+    return COLNAMES.subList(DATABASE, ID);
   }
 }
index 1326647..22e1ab7 100644 (file)
@@ -34,11 +34,11 @@ public class Comparison
 
   private static final int TO_UPPER_CASE = 'a' - 'A';
 
-  private static final char GAP_SPACE = ' ';
+  public static final char GAP_SPACE = ' ';
 
-  private static final char GAP_DOT = '.';
+  public static final char GAP_DOT = '.';
 
-  private static final char GAP_DASH = '-';
+  public static final char GAP_DASH = '-';
 
   public static final String GapChars = new String(new char[] { GAP_SPACE,
       GAP_DOT, GAP_DASH });
@@ -135,7 +135,9 @@ public class Comparison
    * @param s2
    *          SequenceI
    * @return float
+   * @deprecated use PIDModel.computePID()
    */
+  @Deprecated
   public final static float PID(String seq1, String seq2)
   {
     return PID(seq1, seq2, 0, seq1.length());
@@ -144,6 +146,10 @@ public class Comparison
   static final int caseShift = 'a' - 'A';
 
   // Another pid with region specification
+  /**
+   * @deprecated use PIDModel.computePID()
+   */
+  @Deprecated
   public final static float PID(String seq1, String seq2, int start, int end)
   {
     return PID(seq1, seq2, start, end, true, false);
@@ -165,7 +171,9 @@ public class Comparison
    * @param ungappedOnly
    *          - if true - only count PID over ungapped columns
    * @return
+   * @deprecated use PIDModel.computePID()
    */
+  @Deprecated
   public final static float PID(String seq1, String seq2, int start,
           int end, boolean wcGaps, boolean ungappedOnly)
   {
index bce540f..7f109ec 100644 (file)
@@ -34,7 +34,7 @@ import java.util.LinkedHashMap;
  */
 public class LinkedIdentityHashSet<E> extends AbstractSet<E>
 {
-  LinkedHashMap<IdentityWrapper, IdentityWrapper> set = new LinkedHashMap<IdentityWrapper, IdentityWrapper>();
+  LinkedHashMap<IdentityWrapper, IdentityWrapper> set = new LinkedHashMap<>();
 
   static class IdentityWrapper
   {
@@ -51,7 +51,7 @@ public class LinkedIdentityHashSet<E> extends AbstractSet<E>
     @Override
     public boolean equals(Object obj)
     {
-      return this.obj == obj;
+      return this.obj == ((IdentityWrapper) obj).obj;
     }
 
     @Override
index 2e30132..926ccc7 100644 (file)
@@ -31,6 +31,7 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -508,18 +509,20 @@ public final class MappingUtils
    * @param mapTo
    * @return
    */
-  public static ColumnSelection mapColumnSelection(ColumnSelection colsel,
-          AlignViewportI mapFrom, AlignViewportI mapTo)
+  public static void mapColumnSelection(ColumnSelection colsel,
+          HiddenColumns hiddencols, AlignViewportI mapFrom,
+          AlignViewportI mapTo, ColumnSelection newColSel,
+          HiddenColumns newHidden)
   {
     boolean targetIsNucleotide = mapTo.isNucleotide();
     AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
     List<AlignedCodonFrame> codonFrames = protein.getAlignment()
             .getCodonFrames();
-    ColumnSelection mappedColumns = new ColumnSelection();
+    // ColumnSelection mappedColumns = new ColumnSelection();
 
     if (colsel == null)
     {
-      return mappedColumns;
+      return; // mappedColumns;
     }
 
     char fromGapChar = mapFrom.getAlignment().getGapCharacter();
@@ -533,16 +536,16 @@ public final class MappingUtils
 
     for (Integer sel : colsel.getSelected())
     {
-      mapColumn(sel.intValue(), codonFrames, mappedColumns, fromSequences,
+      mapColumn(sel.intValue(), codonFrames, newColSel, fromSequences,
               toSequences, fromGapChar);
     }
 
-    for (int[] hidden : colsel.getHiddenColumns())
+    for (int[] hidden : hiddencols.getHiddenRegions())
     {
-      mapHiddenColumns(hidden, codonFrames, mappedColumns, fromSequences,
+      mapHiddenColumns(hidden, codonFrames, newHidden, fromSequences,
               toSequences, fromGapChar);
     }
-    return mappedColumns;
+    return; // mappedColumns;
   }
 
   /**
@@ -557,7 +560,7 @@ public final class MappingUtils
    * @param fromGapChar
    */
   protected static void mapHiddenColumns(int[] hidden,
-          List<AlignedCodonFrame> mappings, ColumnSelection mappedColumns,
+          List<AlignedCodonFrame> mappings, HiddenColumns mappedColumns,
           List<SequenceI> fromSequences, List<SequenceI> toSequences,
           char fromGapChar)
   {
diff --git a/src/jalview/util/RangeComparator.java b/src/jalview/util/RangeComparator.java
new file mode 100644 (file)
index 0000000..f911a9b
--- /dev/null
@@ -0,0 +1,25 @@
+package jalview.util;
+
+import java.util.Comparator;
+
+/**
+ * A comparator to order [from, to] ranges into ascending or descending order of
+ * their start position
+ */
+public class RangeComparator implements Comparator<int[]>
+{
+  boolean forwards;
+
+  public RangeComparator(boolean forward)
+  {
+    forwards = forward;
+  }
+
+  @Override
+  public int compare(int[] o1, int[] o2)
+  {
+    int compared = Integer.compare(o1[0], o2[0]);
+    return forwards ? compared : -compared;
+  }
+
+}
\ No newline at end of file
diff --git a/src/jalview/util/SetUtils.java b/src/jalview/util/SetUtils.java
new file mode 100644 (file)
index 0000000..381d9f6
--- /dev/null
@@ -0,0 +1,43 @@
+package jalview.util;
+
+import java.util.Set;
+
+public class SetUtils
+{
+  /**
+   * Returns the count of things that are in one or other of two sets but not in
+   * both. The sets are not modified.
+   * 
+   * @param set1
+   * @param set2
+   * @return
+   */
+  public static int countDisjunction(Set<? extends Object> set1,
+          Set<? extends Object> set2)
+  {
+    if (set1 == null)
+    {
+      return set2 == null ? 0 : set2.size();
+    }
+    if (set2 == null)
+    {
+      return set1.size();
+    }
+
+    int size1 = set1.size();
+    int size2 = set2.size();
+    Set<? extends Object> smallerSet = size1 < size2 ? set1 : set2;
+    Set<? extends Object> largerSet = (smallerSet == set1 ? set2 : set1);
+    int inCommon = 0;
+    for (Object k : smallerSet)
+    {
+      if (largerSet.contains(k))
+      {
+        inCommon++;
+      }
+    }
+
+    int notInCommon = (size1 - inCommon) + (size2 - inCommon);
+    return notInCommon;
+  }
+}
diff --git a/src/jalview/util/SparseCount.java~ b/src/jalview/util/SparseCount.java~
new file mode 100644 (file)
index 0000000..e6b45f2
--- /dev/null
@@ -0,0 +1,154 @@
+package jalview.util;
+
+import jalview.ext.android.SparseIntArray;
+import jalview.ext.android.SparseShortArray;
+
+/**
+ * A class to count occurrences of characters with minimal memory footprint.
+ * Sparse arrays of short values are used to hold the counts, with automatic
+ * promotion to arrays of int if any count exceeds the maximum value for a
+ * short.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class SparseCount
+{
+  private static final int DEFAULT_PROFILE_SIZE = 2;
+
+  /*
+   * array of keys (chars) and values (counts)
+   * held either as shorts or (if shorts overflow) as ints 
+   */
+  private SparseShortArray shortProfile;
+
+  private SparseIntArray intProfile;
+
+  /*
+   * flag is set true after short overflow occurs
+   */
+  private boolean useInts;
+
+  /**
+   * Constructor which initially creates a new sparse array of short values to
+   * hold counts.
+   * 
+   * @param profileSize
+   */
+  public SparseCount(int profileSize)
+  {
+    this.shortProfile = new SparseShortArray(profileSize);
+  }
+
+  /**
+   * Constructor which allocates an initial count array for only two distinct
+   * values (the array will grow if needed)
+   */
+  public SparseCount()
+  {
+    this(DEFAULT_PROFILE_SIZE);
+  }
+
+  /**
+   * Adds the given value for the given key (or sets the initial value), and
+   * returns the new value
+   * 
+   * @param key
+   * @param value
+   */
+  public int add(int key, int value)
+  {
+    int newValue = 0;
+    if (useInts)
+    {
+      newValue = intProfile.add(key, value);
+    }
+    else
+    {
+      try {
+        newValue = shortProfile.add(key, value);
+      } catch (ArithmeticException e) {
+        handleOverflow();
+        newValue = intProfile.add(key, value);
+      }
+    }
+    return newValue;
+  }
+
+  /**
+   * Switch from counting shorts to counting ints
+   */
+  synchronized void handleOverflow()
+  {
+    int size = shortProfile.size();
+    intProfile = new SparseIntArray(size);
+    for (int i = 0; i < size; i++)
+    {
+      short key = shortProfile.keyAt(i);
+      short value = shortProfile.valueAt(i);
+      intProfile.put(key, value);
+    }
+    shortProfile = null;
+    useInts = true;
+  }
+
+  /**
+   * Returns the size of the profile (number of distinct items counted)
+   * 
+   * @return
+   */
+  public int size()
+  {
+    return useInts ? intProfile.size() : shortProfile.size();
+  }
+
+  /**
+   * Returns the value for the key (zero if no such key)
+   * 
+   * @param key
+   * @return
+   */
+  public int get(int key)
+  {
+    return useInts ? intProfile.get(key) : shortProfile.get(key);
+  }
+
+  /**
+   * Sets the value for the given key
+   * 
+   * @param key
+   * @param value
+   */
+  public void put(int key, int value)
+  {
+    if (useInts)
+    {
+      intProfile.put(key, value);
+    }
+    else
+    {
+      shortProfile.put(key, value);
+    }
+  }
+
+  public int keyAt(int k)
+  {
+    return useInts ? intProfile.keyAt(k) : shortProfile.keyAt(k);
+  }
+
+  public int valueAt(int k)
+  {
+    return useInts ? intProfile.valueAt(k) : shortProfile.valueAt(k);
+  }
+
+  /**
+   * Answers true if this object wraps arrays of int values, false if using
+   * short values
+   * 
+   * @return
+   */
+  boolean isUsingInt()
+  {
+    return useInts;
+  }
+}
index cb15a77..8ba72f9 100644 (file)
@@ -36,6 +36,7 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.SearchResultsI;
@@ -52,6 +53,7 @@ import jalview.structure.VamsasSource;
 import jalview.util.Comparison;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
 import jalview.viewmodel.styles.ViewStyle;
 import jalview.workers.AlignCalcManager;
 import jalview.workers.ComplementConsensusThread;
@@ -79,6 +81,8 @@ import java.util.Map;
 public abstract class AlignmentViewport implements AlignViewportI,
         CommandListener, VamsasSource
 {
+  final protected ViewportRanges ranges;
+
   protected ViewStyleI viewStyle = new ViewStyle();
 
   /**
@@ -89,9 +93,20 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   FeaturesDisplayedI featuresDisplayed = null;
 
-  protected Deque<CommandI> historyList = new ArrayDeque<CommandI>();
+  protected Deque<CommandI> historyList = new ArrayDeque<>();
+
+  protected Deque<CommandI> redoList = new ArrayDeque<>();
 
-  protected Deque<CommandI> redoList = new ArrayDeque<CommandI>();
+  /**
+   * alignment displayed in the viewport. Please use get/setter
+   */
+  protected AlignmentI alignment;
+
+  public AlignmentViewport(AlignmentI al)
+  {
+    setAlignment(al);
+    ranges = new ViewportRanges(al);
+  }
 
   /**
    * @param name
@@ -553,10 +568,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
     viewStyle.setSeqNameItalics(default1);
   }
 
-  /**
-   * alignment displayed in the viewport. Please use get/setter
-   */
-  protected AlignmentI alignment;
+
 
   @Override
   public AlignmentI getAlignment()
@@ -598,7 +610,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected boolean ignoreGapsInConsensusCalculation = false;
 
-  protected ResidueShaderI residueShading;
+  protected ResidueShaderI residueShading = new ResidueShader();
 
   @Override
   public void setGlobalColourScheme(ColourSchemeI cs)
@@ -673,6 +685,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected AlignmentAnnotation complementConsensus;
 
+  protected AlignmentAnnotation gapcounts;
+
   protected AlignmentAnnotation strucConsensus;
 
   protected AlignmentAnnotation conservation;
@@ -775,6 +789,12 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
+  public AlignmentAnnotation getAlignmentGapAnnotation()
+  {
+    return gapcounts;
+  }
+
+  @Override
   public AlignmentAnnotation getComplementConsensusAnnotation()
   {
     return complementConsensus;
@@ -1091,9 +1111,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
   }
 
-  public void setHiddenColumns(ColumnSelection colsel)
+  public void setHiddenColumns(HiddenColumns hidden)
   {
-    this.colSel = colsel;
+    this.alignment.setHiddenColumns(hidden);
   }
 
   @Override
@@ -1140,7 +1160,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public boolean hasHiddenColumns()
   {
-    return colSel != null && colSel.hasHiddenColumns();
+    return colSel != null
+            && alignment.getHiddenColumns().hasHiddenColumns();
   }
 
   public void updateHiddenColumns()
@@ -1275,7 +1296,9 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
   protected boolean showConsensus = true;
 
-  private Map<SequenceI, Color> sequenceColours = new HashMap<SequenceI, Color>();
+  protected boolean showOccupancy = true;
+
+  private Map<SequenceI, Color> sequenceColours = new HashMap<>();
 
   protected SequenceAnnotationOrder sortAnnotationsBy = null;
 
@@ -1286,15 +1309,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
    */
   private boolean followHighlight = true;
 
-  // TODO private with getters and setters?
-  public int startRes;
-
-  public int endRes;
-
-  public int startSeq;
-
-  public int endSeq;
-
   /**
    * Property change listener for changes in alignment
    * 
@@ -1344,7 +1358,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
       return;
     }
 
-    colSel.hideSelectedColumns();
+    colSel.hideSelectedColumns(alignment);
     setSelectionGroup(null);
     isColSelChanged(true);
   }
@@ -1353,24 +1367,24 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     if (start == end)
     {
-      colSel.hideColumns(start);
+      colSel.hideSelectedColumns(start, alignment.getHiddenColumns());
     }
     else
     {
-      colSel.hideColumns(start, end);
+      alignment.getHiddenColumns().hideColumns(start, end);
     }
     isColSelChanged(true);
   }
 
   public void showColumn(int col)
   {
-    colSel.revealHiddenColumns(col);
+    alignment.getHiddenColumns().revealHiddenColumns(col, colSel);
     isColSelChanged(true);
   }
 
   public void showAllHiddenColumns()
   {
-    colSel.revealAllHiddenColumns();
+    alignment.getHiddenColumns().revealAllHiddenColumns(colSel);
     isColSelChanged(true);
   }
 
@@ -1514,7 +1528,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
     if (hiddenRepSequences == null)
     {
-      hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+      hiddenRepSequences = new Hashtable<>();
     }
 
     hiddenRepSequences.put(repSequence, sg);
@@ -1592,7 +1606,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public void invertColumnSelection()
   {
-    colSel.invertColumnSelection(0, alignment.getWidth());
+    colSel.invertColumnSelection(0, alignment.getWidth(), alignment);
   }
 
   @Override
@@ -1640,7 +1654,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public CigarArray getViewAsCigars(boolean selectedRegionOnly)
   {
-    return new CigarArray(alignment, colSel,
+    return new CigarArray(alignment, alignment.getHiddenColumns(),
             (selectedRegionOnly ? selectionGroup : null));
   }
 
@@ -1655,8 +1669,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly, boolean markGroups)
   {
-    return new AlignmentView(alignment, colSel, selectionGroup,
-            colSel != null && colSel.hasHiddenColumns(), selectedOnly,
+    return new AlignmentView(alignment, alignment.getHiddenColumns(),
+            selectionGroup, alignment.getHiddenColumns() != null
+                    && alignment.getHiddenColumns().hasHiddenColumns(),
+            selectedOnly,
             markGroups);
   }
 
@@ -1700,9 +1716,11 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
 
     selection = new String[iSize];
-    if (colSel != null && colSel.hasHiddenColumns())
+    if (alignment.getHiddenColumns() != null
+            && alignment.getHiddenColumns().hasHiddenColumns())
     {
-      selection = colSel.getVisibleSequenceStrings(start, end, seqs);
+      selection = alignment.getHiddenColumns().getVisibleSequenceStrings(
+              start, end, seqs);
     }
     else
     {
@@ -1718,20 +1736,21 @@ public abstract class AlignmentViewport implements AlignViewportI,
   @Override
   public List<int[]> getVisibleRegionBoundaries(int min, int max)
   {
-    ArrayList<int[]> regions = new ArrayList<int[]>();
+    ArrayList<int[]> regions = new ArrayList<>();
     int start = min;
     int end = max;
 
     do
     {
-      if (colSel != null && colSel.hasHiddenColumns())
+      HiddenColumns hidden = alignment.getHiddenColumns();
+      if (hidden != null && hidden.hasHiddenColumns())
       {
         if (start == 0)
         {
-          start = colSel.adjustForHiddenColumns(start);
+          start = hidden.adjustForHiddenColumns(start);
         }
 
-        end = colSel.getHiddenBoundaryRight(start);
+        end = hidden.getHiddenBoundaryRight(start);
         if (start == end)
         {
           end = max;
@@ -1744,10 +1763,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
 
       regions.add(new int[] { start, end });
 
-      if (colSel != null && colSel.hasHiddenColumns())
+      if (hidden != null && hidden.hasHiddenColumns())
       {
-        start = colSel.adjustForHiddenColumns(end);
-        start = colSel.getHiddenBoundaryLeft(start) + 1;
+        start = hidden.adjustForHiddenColumns(end);
+        start = hidden.getHiddenBoundaryLeft(start) + 1;
       }
     } while (end < max);
 
@@ -1760,7 +1779,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
           boolean selectedOnly)
   {
-    ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> ala = new ArrayList<>();
     AlignmentAnnotation[] aa;
     if ((aa = alignment.getAlignmentAnnotation()) != null)
     {
@@ -1769,12 +1788,13 @@ public abstract class AlignmentViewport implements AlignViewportI,
         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
         if (selectedOnly && selectionGroup != null)
         {
-          colSel.makeVisibleAnnotation(selectionGroup.getStartRes(),
+          alignment.getHiddenColumns().makeVisibleAnnotation(
+                  selectionGroup.getStartRes(),
                   selectionGroup.getEndRes(), clone);
         }
         else
         {
-          colSel.makeVisibleAnnotation(clone);
+          alignment.getHiddenColumns().makeVisibleAnnotation(clone);
         }
         ala.add(clone);
       }
@@ -1890,19 +1910,21 @@ public abstract class AlignmentViewport implements AlignViewportI,
       {
         initRNAStructure();
       }
-      consensus = new AlignmentAnnotation("Consensus", "PID",
+      consensus = new AlignmentAnnotation("Consensus",
+              MessageManager.getString("label.consensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
       initConsensus(consensus);
+      initGapCounts();
 
       initComplementConsensus();
     }
   }
 
   /**
-   * If this is a protein alignment and there are mappings to cDNA, add the cDNA
-   * consensus annotation.
+   * If this is a protein alignment and there are mappings to cDNA, adds the
+   * cDNA consensus annotation and returns true, else returns false.
    */
-  public void initComplementConsensus()
+  public boolean initComplementConsensus()
   {
     if (!alignment.isNucleotide())
     {
@@ -1926,12 +1948,16 @@ public abstract class AlignmentViewport implements AlignViewportI,
         if (doConsensus)
         {
           complementConsensus = new AlignmentAnnotation("cDNA Consensus",
-                  "PID for cDNA", new Annotation[1], 0f, 100f,
+                  MessageManager
+                          .getString("label.complement_consensus_descr"),
+                  new Annotation[1], 0f, 100f,
                   AlignmentAnnotation.BAR_GRAPH);
           initConsensus(complementConsensus);
+          return true;
         }
       }
     }
+    return false;
   }
 
   private void initConsensus(AlignmentAnnotation aa)
@@ -1945,6 +1971,25 @@ public abstract class AlignmentViewport implements AlignViewportI,
     }
   }
 
+  // these should be extracted from the view model - style and settings for
+  // derived annotation
+  private void initGapCounts()
+  {
+    if (showOccupancy)
+    {
+      gapcounts = new AlignmentAnnotation("Occupancy",
+              MessageManager.getString("label.occupancy_descr"),
+              new Annotation[1], 0f,
+              alignment.getHeight(), AlignmentAnnotation.BAR_GRAPH);
+      gapcounts.hasText = true;
+      gapcounts.autoCalculated = true;
+      gapcounts.scaleColLabel = true;
+      gapcounts.graph = AlignmentAnnotation.BAR_GRAPH;
+
+      alignment.addAnnotation(gapcounts);
+    }
+  }
+
   private void initConservation()
   {
     if (showConservation)
@@ -1952,8 +1997,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
       if (conservation == null)
       {
         conservation = new AlignmentAnnotation("Conservation",
-                "Conservation of total alignment less than "
-                        + getConsPercGaps() + "% gaps", new Annotation[1],
+                MessageManager.formatMessage("label.conservation_descr",
+                        getConsPercGaps()), new Annotation[1],
                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
         conservation.hasText = true;
         conservation.autoCalculated = true;
@@ -1969,7 +2014,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
       if (quality == null)
       {
         quality = new AlignmentAnnotation("Quality",
-                "Alignment Quality based on Blosum62 scores",
+                MessageManager.getString("label.quality_descr"),
                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
         quality.hasText = true;
         quality.autoCalculated = true;
@@ -1982,7 +2027,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
   {
     if (alignment.hasRNAStructure() && strucConsensus == null)
     {
-      strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
+      strucConsensus = new AlignmentAnnotation("StrucConsensus",
+              MessageManager.getString("label.strucconsensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
       strucConsensus.hasText = true;
       strucConsensus.autoCalculated = true;
@@ -2087,7 +2133,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
     // intersect alignment annotation with alignment groups
 
     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
-    List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
+    List<SequenceGroup> oldrfs = new ArrayList<>();
     if (aan != null)
     {
       for (int an = 0; an < aan.length; an++)
@@ -2629,6 +2675,18 @@ public abstract class AlignmentViewport implements AlignViewportI,
     viewStyle.setScaleProteinAsCdna(b);
   }
 
+  @Override
+  public boolean isProteinFontAsCdna()
+  {
+    return viewStyle.isProteinFontAsCdna();
+  }
+
+  @Override
+  public void setProteinFontAsCdna(boolean b)
+  {
+    viewStyle.setProteinFontAsCdna(b);
+  }
+
   /**
    * @return true if view should scroll to show the highlighted region of a
    *         sequence
@@ -2646,63 +2704,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
     this.followHighlight = b;
   }
 
-  public int getStartRes()
-  {
-    return startRes;
-  }
-
   @Override
-  public int getEndRes()
-  {
-    return endRes;
-  }
-
-  public int getStartSeq()
-  {
-    return startSeq;
-  }
-
-  public void setStartRes(int res)
-  {
-    this.startRes = res;
-  }
-
-  public void setStartSeq(int seq)
-  {
-    this.startSeq = seq;
-  }
-
-  public void setEndRes(int res)
+  public ViewportRanges getRanges()
   {
-    if (res > alignment.getWidth() - 1)
-    {
-      // log.System.out.println(" Corrected res from " + res + " to maximum " +
-      // (alignment.getWidth()-1));
-      res = alignment.getWidth() - 1;
-    }
-    if (res < 0)
-    {
-      res = 0;
-    }
-    this.endRes = res;
-  }
-
-  public void setEndSeq(int seq)
-  {
-    if (seq > alignment.getHeight())
-    {
-      seq = alignment.getHeight();
-    }
-    if (seq < 0)
-    {
-      seq = 0;
-    }
-    this.endSeq = seq;
-  }
-
-  public int getEndSeq()
-  {
-    return endSeq;
+    return ranges;
   }
 
   /**
@@ -2742,7 +2747,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
      * locate 'middle' column (true middle if an odd number visible, left of
      * middle if an even number visible)
      */
-    int middleColumn = getStartRes() + (getEndRes() - getStartRes()) / 2;
+    int middleColumn = ranges.getStartRes()
+            + (ranges.getEndRes() - ranges.getStartRes()) / 2;
     final HiddenSequences hiddenSequences = getAlignment()
             .getHiddenSequences();
 
@@ -2752,7 +2758,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
      */
     int lastSeq = alignment.getHeight() - 1;
     List<AlignedCodonFrame> seqMappings = null;
-    for (int seqNo = getStartSeq(); seqNo < lastSeq; seqNo++, seqOffset++)
+    for (int seqNo = ranges.getStartSeq(); seqNo < lastSeq; seqNo++, seqOffset++)
     {
       sequence = getAlignment().getSequenceAt(seqNo);
       if (hiddenSequences != null && hiddenSequences.isHidden(sequence))
@@ -2820,7 +2826,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
    */
   private boolean selectionIsDefinedGroup = false;
 
-
   @Override
   public boolean isSelectionDefinedGroup()
   {
@@ -2841,8 +2846,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
         selectionIsDefinedGroup = gps.contains(selectionGroup);
       }
     }
-    return selectionGroup.getContext() == alignment
-            || selectionIsDefinedGroup;
+    return selectionGroup.isDefined() || selectionIsDefinedGroup;
   }
 
   /**
diff --git a/src/jalview/viewmodel/OverviewDimensions.java b/src/jalview/viewmodel/OverviewDimensions.java
new file mode 100644 (file)
index 0000000..d2912d8
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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.viewmodel;
+
+import jalview.api.AlignmentColsCollectionI;
+import jalview.api.AlignmentRowsCollectionI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenSequences;
+
+import java.awt.Graphics;
+
+public abstract class OverviewDimensions
+{
+  protected static final int MAX_WIDTH = 400;
+
+  protected static final int MIN_WIDTH = 120;
+
+  protected static final int MIN_SEQ_HEIGHT = 40;
+
+  protected static final int MAX_SEQ_HEIGHT = 300;
+
+  private static final int DEFAULT_GRAPH_HEIGHT = 20;
+
+  protected int width;
+
+  protected int sequencesHeight;
+
+  protected int graphHeight = DEFAULT_GRAPH_HEIGHT;
+
+  protected int boxX = -1;
+
+  protected int boxY = -1;
+
+  protected int boxWidth = -1;
+
+  protected int boxHeight = -1;
+
+  protected int alwidth;
+
+  protected int alheight;
+
+  /**
+   * Create an OverviewDimensions object
+   * 
+   * @param ranges
+   *          positional properties of the viewport
+   * @param showAnnotationPanel
+   *          true if the annotation panel is to be shown, false otherwise
+   */
+  public OverviewDimensions(ViewportRanges ranges,
+          boolean showAnnotationPanel)
+  {
+    // scale the initial size of overviewpanel to shape of alignment
+    float initialScale = (float) ranges.getAbsoluteAlignmentWidth()
+            / (float) ranges.getAbsoluteAlignmentHeight();
+
+    if (!showAnnotationPanel)
+    {
+      graphHeight = 0;
+    }
+
+    if (ranges.getAbsoluteAlignmentWidth() > ranges
+            .getAbsoluteAlignmentHeight())
+    {
+      // wider
+      width = MAX_WIDTH;
+      sequencesHeight = Math.round(MAX_WIDTH / initialScale);
+      if (sequencesHeight < MIN_SEQ_HEIGHT)
+      {
+        sequencesHeight = MIN_SEQ_HEIGHT;
+      }
+    }
+    else
+    {
+      // taller
+      width = Math.round(MAX_WIDTH * initialScale);
+      sequencesHeight = MAX_SEQ_HEIGHT;
+
+      if (width < MIN_WIDTH)
+      {
+        width = MIN_WIDTH;
+      }
+    }
+  }
+
+  /**
+   * Draw the overview panel's viewport box on a graphics object
+   * 
+   * @param g
+   *          the graphics object to draw on
+   */
+  public void drawBox(Graphics g)
+  {
+    g.drawRect(boxX, boxY, boxWidth, boxHeight);
+    g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+  }
+
+  public int getBoxX()
+  {
+    return boxX;
+  }
+
+  public int getBoxY()
+  {
+    return boxY;
+  }
+
+  public int getBoxWidth()
+  {
+    return boxWidth;
+  }
+
+  public int getBoxHeight()
+  {
+    return boxHeight;
+  }
+
+  public int getWidth()
+  {
+    return width;
+  }
+
+  public int getHeight()
+  {
+    return sequencesHeight + graphHeight;
+  }
+
+  public int getSequencesHeight()
+  {
+    return sequencesHeight;
+  }
+
+  public int getGraphHeight()
+  {
+    return graphHeight;
+  }
+
+  public float getPixelsPerCol()
+  {
+    resetAlignmentDims();
+    return (float) width / alwidth;
+  }
+
+  public float getPixelsPerSeq()
+  {
+    resetAlignmentDims();
+    return (float) sequencesHeight / alheight;
+  }
+
+  public void setWidth(int w)
+  {
+    width = w;
+  }
+
+  public void setHeight(int h)
+  {
+    sequencesHeight = h - graphHeight;
+  }
+
+  /**
+   * Update the viewport location from a mouse click in the overview panel
+   * 
+   * @param mousex
+   *          x location of mouse
+   * @param mousey
+   *          y location of mouse
+   * @param hiddenSeqs
+   *          the alignment's hidden sequences
+   * @param hiddenCols
+   *          the alignment's hidden columns
+   */
+  public abstract void updateViewportFromMouse(int mousex, int mousey,
+          HiddenSequences hiddenSeqs, HiddenColumns hiddenCols);
+
+  /**
+   * Set the overview panel's box position to match the viewport
+   * 
+   * @param hiddenSeqs
+   *          the alignment's hidden sequences
+   * @param hiddenCols
+   *          the alignment's hidden columns
+   */
+  public abstract void setBoxPosition(HiddenSequences hiddenSeqs,
+          HiddenColumns hiddenCols);
+
+  /**
+   * Get the collection of columns used by this overview dimensions object
+   * 
+   * @param hiddenCols
+   *          the alignment's hidden columns
+   * @return a column collection
+   */
+  public abstract AlignmentColsCollectionI getColumns(AlignmentI al);
+
+  /**
+   * Get the collection of rows used by this overview dimensions object
+   * 
+   * @param al
+   *          the alignment
+   * @return a row collection
+   */
+  public abstract AlignmentRowsCollectionI getRows(AlignmentI al);
+
+  /**
+   * Updates overview dimensions to account for current alignment dimensions
+   */
+  protected abstract void resetAlignmentDims();
+
+  protected void setBoxPosition(int startRes, int startSeq, int vpwidth,
+          int vpheight)
+  {
+    resetAlignmentDims();
+
+    // boxX, boxY is the x,y location equivalent to startRes, startSeq
+    boxX = Math.round((float) startRes * width / alwidth);
+    boxY = Math.round((float) startSeq * sequencesHeight / alheight);
+
+    // boxWidth is the width in residues translated to pixels
+    boxWidth = Math.round((float) vpwidth * width / alwidth);
+
+    // boxHeight is the height in sequences translated to pixels
+    boxHeight = Math.round((float) vpheight * sequencesHeight / alheight);
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/viewmodel/OverviewDimensionsHideHidden.java b/src/jalview/viewmodel/OverviewDimensionsHideHidden.java
new file mode 100644 (file)
index 0000000..4bf7694
--- /dev/null
@@ -0,0 +1,130 @@
+package jalview.viewmodel;
+
+import jalview.api.AlignmentColsCollectionI;
+import jalview.api.AlignmentRowsCollectionI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenSequences;
+import jalview.datamodel.VisibleColsCollection;
+import jalview.datamodel.VisibleRowsCollection;
+
+public class OverviewDimensionsHideHidden extends OverviewDimensions
+{
+  private ViewportRanges ranges;
+
+  public OverviewDimensionsHideHidden(ViewportRanges vpranges,
+          boolean showAnnotationPanel)
+  {
+    super(vpranges, showAnnotationPanel);
+    ranges = vpranges;
+    resetAlignmentDims();
+  }
+
+  @Override
+  public void updateViewportFromMouse(int mousex, int mousey,
+          HiddenSequences hiddenSeqs, HiddenColumns hiddenCols)
+  {
+    resetAlignmentDims();
+
+    int x = mousex;
+    int y = mousey;
+
+    if (x < 0)
+    {
+      x = 0;
+    }
+
+    if (y < 0)
+    {
+      y = 0;
+    }
+
+    //
+    // Convert x value to residue position
+    //
+
+    // need to determine where scrollCol should be, given x
+    // to do this also need to know width of viewport, and some hidden column
+    // correction
+
+    // convert x to residues - this is an absolute position
+    int xAsRes = Math.round((float) x * alwidth / width);
+
+    // get viewport width in residues
+    int vpwidth = ranges.getViewportWidth();
+
+    if (xAsRes + vpwidth > alwidth)
+    {
+      // went past the end of the alignment, adjust backwards
+
+      // if last position was before the end of the alignment, need to update
+      if (ranges.getStartRes() < alwidth)
+      {
+        xAsRes = alwidth - vpwidth;
+      }
+      else
+      {
+        xAsRes = ranges.getStartRes();
+      }
+    }
+
+
+    //
+    // Convert y value to sequence position
+    //
+
+    // convert y to residues
+    int yAsSeq = Math.round((float) y * alheight / sequencesHeight);
+
+    // get viewport height in sequences
+    // add 1 because height includes both endSeq and startSeq
+    int vpheight = ranges.getViewportHeight();
+
+    if (yAsSeq + vpheight > alheight)
+    {
+      // went past the end of the alignment, adjust backwards
+      if (ranges.getEndSeq() < alheight)
+      {
+        yAsSeq = alheight - vpheight;
+      }
+      else
+      {
+        yAsSeq = ranges.getStartSeq();
+      }
+    }
+
+    // update viewport
+    ranges.setStartRes(xAsRes);
+    ranges.setStartSeq(yAsSeq);
+
+  }
+
+  @Override
+  public void setBoxPosition(HiddenSequences hiddenSeqs,
+          HiddenColumns hiddenCols)
+  {
+    setBoxPosition(ranges.getStartRes(), ranges.getStartSeq(),
+            ranges.getViewportWidth(), ranges.getViewportHeight());
+  }
+
+  @Override
+  public AlignmentColsCollectionI getColumns(AlignmentI al)
+  {
+    return new VisibleColsCollection(0,
+            ranges.getAbsoluteAlignmentWidth() - 1, al);
+  }
+
+  @Override
+  public AlignmentRowsCollectionI getRows(AlignmentI al)
+  {
+    return new VisibleRowsCollection(0,
+            ranges.getAbsoluteAlignmentHeight() - 1, al);
+  }
+
+  @Override
+  protected void resetAlignmentDims()
+  {
+    alwidth = ranges.getVisibleAlignmentWidth();
+    alheight = ranges.getVisibleAlignmentHeight();
+  }
+}
diff --git a/src/jalview/viewmodel/OverviewDimensionsShowHidden.java b/src/jalview/viewmodel/OverviewDimensionsShowHidden.java
new file mode 100644 (file)
index 0000000..4b396a6
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * 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.viewmodel;
+
+import jalview.api.AlignmentColsCollectionI;
+import jalview.api.AlignmentRowsCollectionI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AllColsCollection;
+import jalview.datamodel.AllRowsCollection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenSequences;
+
+public class OverviewDimensionsShowHidden extends OverviewDimensions
+{
+  private ViewportRanges ranges;
+
+  /**
+   * Create an OverviewDimensions object
+   * 
+   * @param ranges
+   *          positional properties of the viewport
+   * @param showAnnotationPanel
+   *          true if the annotation panel is to be shown, false otherwise
+   */
+  public OverviewDimensionsShowHidden(ViewportRanges vpranges,
+          boolean showAnnotationPanel)
+  {
+    super(vpranges, showAnnotationPanel);
+    ranges = vpranges;
+    resetAlignmentDims();
+  }
+
+  /**
+   * Check box dimensions and scroll positions and correct if necessary
+   * 
+   * @param mousex
+   *          x position in overview panel
+   * @param mousey
+   *          y position in overview panel
+   * @param hiddenSeqs
+   *          hidden sequences
+   * @param hiddenCols
+   *          hidden columns
+   * @param ranges
+   *          viewport position properties
+   */
+  @Override
+  public void updateViewportFromMouse(int mousex, int mousey,
+          HiddenSequences hiddenSeqs, HiddenColumns hiddenCols)
+  {
+    int x = mousex;
+    int y = mousey;
+
+    resetAlignmentDims();
+
+    if (x < 0)
+    {
+      x = 0;
+    }
+
+    if (y < 0)
+    {
+      y = 0;
+    }
+
+    //
+    // Convert x value to residue position
+    //
+
+    // need to determine where scrollCol should be, given x
+    // to do this also need to know width of viewport, and some hidden column
+    // correction
+
+    // convert x to residues - this is an absolute position
+    int xAsRes = Math.round((float) x * alwidth / width);
+
+    // get viewport width in residues
+    int vpwidth = ranges.getViewportWidth();
+
+    // get where x should be when accounting for hidden cols
+    // if x is in a hidden col region, shift to left - but we still need
+    // absolute position
+    // so convert back after getting visible region position
+    int visXAsRes = hiddenCols.findColumnPosition(xAsRes);
+
+    // check in case we went off the edge of the alignment
+    int visAlignWidth = hiddenCols.findColumnPosition(alwidth - 1);
+    if (visXAsRes + vpwidth - 1 > visAlignWidth)
+    {
+      // went past the end of the alignment, adjust backwards
+
+      // if last position was before the end of the alignment, need to update
+      if (ranges.getEndRes() < visAlignWidth)
+      {
+        visXAsRes = hiddenCols.findColumnPosition(hiddenCols
+                .subtractVisibleColumns(vpwidth - 1, alwidth - 1));
+      }
+      else
+      {
+        visXAsRes = ranges.getStartRes();
+      }
+    }
+
+    //
+    // Convert y value to sequence position
+    //
+
+    // convert y to residues
+    int yAsSeq = Math.round((float) y * alheight / sequencesHeight);
+
+    // get viewport height in sequences
+    int vpheight = ranges.getViewportHeight();
+
+    // get where y should be when accounting for hidden rows
+    // if y is in a hidden row region, shift up - but we still need absolute
+    // position,
+    // so convert back after getting visible region position
+    yAsSeq = hiddenSeqs.adjustForHiddenSeqs(hiddenSeqs
+            .findIndexWithoutHiddenSeqs(yAsSeq));
+
+    // check in case we went off the edge of the alignment
+    int visAlignHeight = hiddenSeqs.findIndexWithoutHiddenSeqs(alheight);
+    int visYAsSeq = hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq);
+    if (visYAsSeq + vpheight - 1 > visAlignHeight)
+    {
+      // went past the end of the alignment, adjust backwards
+      if (ranges.getEndSeq() < visAlignHeight)
+      {
+        visYAsSeq = hiddenSeqs.findIndexWithoutHiddenSeqs(hiddenSeqs
+                .subtractVisibleRows(vpheight - 1, alheight - 1));
+      }
+      else
+      {
+        visYAsSeq = ranges.getStartSeq();
+      }
+    }
+
+    // update viewport
+    ranges.setStartRes(visXAsRes);
+    ranges.setStartSeq(visYAsSeq);
+
+  }
+
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   * @param hiddenSeqs
+   *          hidden sequences
+   * @param hiddenCols
+   *          hidden columns
+   * @param ranges
+   *          viewport position properties
+   */
+  @Override
+  public void setBoxPosition(HiddenSequences hiddenSeqs,
+          HiddenColumns hiddenCols)
+  {
+    // work with absolute values of startRes and endRes
+    int startRes = hiddenCols.adjustForHiddenColumns(ranges.getStartRes());
+    int endRes = hiddenCols.adjustForHiddenColumns(ranges.getEndRes());
+
+    // work with absolute values of startSeq and endSeq
+    int startSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getStartSeq());
+    int endSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getEndSeq());
+
+    setBoxPosition(startRes, startSeq, endRes - startRes + 1, endSeq
+            - startSeq + 1);
+  }
+
+  @Override
+  public AlignmentColsCollectionI getColumns(AlignmentI al)
+  {
+    return new AllColsCollection(0,
+            ranges.getAbsoluteAlignmentWidth() - 1, al);
+  }
+
+  @Override
+  public AlignmentRowsCollectionI getRows(AlignmentI al)
+  {
+    return new AllRowsCollection(0,
+            ranges.getAbsoluteAlignmentHeight() - 1,
+            al);
+  }
+
+  @Override
+  protected void resetAlignmentDims()
+  {
+    alwidth = ranges.getAbsoluteAlignmentWidth();
+    alheight = ranges.getAbsoluteAlignmentHeight();
+  }
+}
index b0af302..928d35e 100644 (file)
@@ -22,6 +22,8 @@ package jalview.viewmodel;
 
 import jalview.analysis.PCA;
 import jalview.api.RotatableCanvasI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequencePoint;
@@ -30,16 +32,6 @@ import java.util.Vector;
 
 public class PCAModel
 {
-
-  public PCAModel(AlignmentView seqstrings2, SequenceI[] seqs2,
-          boolean nucleotide2)
-  {
-    seqstrings = seqstrings2;
-    seqs = seqs2;
-    nucleotide = nucleotide2;
-    score_matrix = nucleotide2 ? "PID" : "BLOSUM62";
-  }
-
   private volatile PCA pca;
 
   int top;
@@ -48,31 +40,41 @@ public class PCAModel
 
   SequenceI[] seqs;
 
-  /**
-   * Score matrix used to calculate PC
+  /*
+   * Name of score model used to calculate PCA
    */
-  String score_matrix;
+  ScoreModelI scoreModel;
 
-  /**
-   * use the identity matrix for calculating similarity between sequences.
-   */
   private boolean nucleotide = false;
 
   private Vector<SequencePoint> points;
 
-  private boolean jvCalcMode = true;
+  private SimilarityParamsI similarityParams;
 
-  public boolean isJvCalcMode()
+  /**
+   * Constructor given sequence data, score model and score calculation
+   * parameter options.
+   * 
+   * @param seqData
+   * @param sqs
+   * @param nuc
+   * @param modelName
+   * @param params
+   */
+  public PCAModel(AlignmentView seqData, SequenceI[] sqs, boolean nuc,
+          ScoreModelI modelName,
+          SimilarityParamsI params)
   {
-    return jvCalcMode;
+    seqstrings = seqData;
+    seqs = sqs;
+    nucleotide = nuc;
+    scoreModel = modelName;
+    similarityParams = params;
   }
 
   public void run()
   {
-
-    pca = new PCA(seqstrings.getSequenceStrings(' '), nucleotide,
-            score_matrix);
-    pca.setJvCalcMode(jvCalcMode);
+    pca = new PCA(seqstrings, scoreModel, similarityParams);
     pca.run();
 
     // Now find the component coordinates
@@ -83,32 +85,23 @@ public class PCAModel
       ii++;
     }
 
-    double[][] comps = new double[ii][ii];
-
-    for (int i = 0; i < ii; i++)
-    {
-      if (pca.getEigenvalue(i) > 1e-4)
-      {
-        comps[i] = pca.component(i);
-      }
-    }
-
-    top = pca.getM().rows - 1;
+    int height = pca.getHeight();
+    // top = pca.getM().height() - 1;
+    top = height - 1;
 
     points = new Vector<SequencePoint>();
     float[][] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
 
-    for (int i = 0; i < pca.getM().rows; i++)
+    for (int i = 0; i < height; i++)
     {
       SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
       points.addElement(sp);
     }
-
   }
 
   public void updateRc(RotatableCanvasI rc)
   {
-    rc.setPoints(points, pca.getM().rows);
+    rc.setPoints(points, pca.getHeight());
   }
 
   public boolean isNucleotide()
@@ -146,9 +139,9 @@ public class PCAModel
     // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
     float[][] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 100);
 
-    for (int i = 0; i < pca.getM().rows; i++)
+    for (int i = 0; i < pca.getHeight(); i++)
     {
-      ((SequencePoint) points.elementAt(i)).coord = scores[i];
+      points.elementAt(i).coord = scores[i];
     }
   }
 
@@ -228,19 +221,14 @@ public class PCAModel
     return pts;
   }
 
-  public void setJvCalcMode(boolean state)
-  {
-    jvCalcMode = state;
-  }
-
-  public String getScore_matrix()
+  public String getScoreModelName()
   {
-    return score_matrix;
+    return scoreModel == null ? "" : scoreModel.getName();
   }
 
-  public void setScore_matrix(String score_matrix)
+  public void setScoreModel(ScoreModelI sm)
   {
-    this.score_matrix = score_matrix;
+    this.scoreModel = sm;
   }
 
 }
diff --git a/src/jalview/viewmodel/ViewportListenerI.java b/src/jalview/viewmodel/ViewportListenerI.java
new file mode 100644 (file)
index 0000000..555089e
--- /dev/null
@@ -0,0 +1,8 @@
+package jalview.viewmodel;
+
+import java.beans.PropertyChangeListener;
+
+public interface ViewportListenerI extends PropertyChangeListener
+{
+
+}
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
-package jalview.api.analysis;
+package jalview.viewmodel;
 
-import jalview.api.AlignmentViewPanel;
+import java.beans.PropertyChangeSupport;
 
-public interface ViewBasedAnalysisI
+public abstract class ViewportProperties
 {
+  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+          this);
 
-  /**
-   * Parameterise the analysis model using the current view
-   * 
-   * @param view
-   * @return true if model is applicable and calculation should proceed
-   */
+  public void addPropertyChangeListener(ViewportListenerI listener)
+  {
+    changeSupport.addPropertyChangeListener(listener);
+  }
 
-  boolean configureFromAlignmentView(AlignmentViewPanel view);
+  public void removePropertyChangeListener(ViewportListenerI listener)
+  {
+    changeSupport.removePropertyChangeListener(listener);
+  }
 
 }
diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java
new file mode 100644 (file)
index 0000000..4eb8c95
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * 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.viewmodel;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+
+/**
+ * Slightly less embryonic class which: Supplies and updates viewport properties
+ * relating to position such as: start and end residues and sequences; ideally
+ * will serve hidden columns/rows too. Intention also to support calculations
+ * for positioning, scrolling etc. such as finding the middle of the viewport,
+ * checking for scrolls off screen
+ */
+public class ViewportRanges extends ViewportProperties
+{
+  // start residue of viewport
+  private int startRes;
+
+  // end residue of viewport
+  private int endRes;
+
+  // start sequence of viewport
+  private int startSeq;
+
+  // end sequence of viewport
+  private int endSeq;
+
+  // alignment
+  private AlignmentI al;
+
+  /**
+   * Constructor
+   * 
+   * @param alignment
+   *          the viewport's alignment
+   */
+  public ViewportRanges(AlignmentI alignment)
+  {
+    // initial values of viewport settings
+    this.startRes = 0;
+    this.endRes = alignment.getWidth() - 1;
+    this.startSeq = 0;
+    this.endSeq = alignment.getHeight() - 1;
+    this.al = alignment;
+  }
+
+  /**
+   * Get alignment width in cols, including hidden cols
+   */
+  public int getAbsoluteAlignmentWidth()
+  {
+    return al.getWidth();
+  }
+
+  /**
+   * Get alignment height in rows, including hidden rows
+   */
+  public int getAbsoluteAlignmentHeight()
+  {
+    return al.getHeight() + al.getHiddenSequences().getSize();
+  }
+
+  /**
+   * Get alignment width in cols, excluding hidden cols
+   */
+  public int getVisibleAlignmentWidth()
+  {
+    return al.getWidth() - al.getHiddenColumns().getSize();
+  }
+
+  /**
+   * Get alignment height in rows, excluding hidden rows
+   */
+  public int getVisibleAlignmentHeight()
+  {
+    return al.getHeight();
+  }
+
+  /**
+   * Set first residue visible in the viewport, and retain the current width.
+   * Fires a property change event.
+   * 
+   * @param res
+   *          residue position
+   */
+  public void setStartRes(int res)
+  {
+    int width = getViewportWidth();
+    setStartEndRes(res, res + width - 1);
+  }
+
+  /**
+   * Set start and end residues at the same time. This method only fires one
+   * event for the two changes, and should be used in preference to separate
+   * calls to setStartRes and setEndRes.
+   * 
+   * @param start
+   *          the start residue
+   * @param end
+   *          the end residue
+   */
+  public void setStartEndRes(int start, int end)
+  {
+    int oldstartres = this.startRes;
+    if (start > getVisibleAlignmentWidth() - 1)
+    {
+      startRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
+    }
+    else if (start < 0)
+    {
+      startRes = 0;
+    }
+    else
+    {
+      startRes = start;
+    }
+
+    int oldendres = this.endRes;
+    if (end < 0)
+    {
+      endRes = 0;
+    }
+    else if (end > getVisibleAlignmentWidth() - 1)
+    {
+      endRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
+    }
+    else
+    {
+      endRes = end;
+    }
+
+    changeSupport.firePropertyChange("startres", oldstartres, startRes);
+    if (oldstartres == startRes)
+    {
+      // event won't be fired if start positions are same
+      // fire an event for the end positions in case they changed
+      changeSupport.firePropertyChange("endres", oldendres, endRes);
+    }
+  }
+
+  /**
+   * Set last residue visible in the viewport. Fires a property change event.
+   * 
+   * @param res
+   *          residue position
+   */
+  public void setEndRes(int res)
+  {
+    int startres = res;
+    int width = getViewportWidth();
+    if (startres + width - 1 > getVisibleAlignmentWidth() - 1)
+    {
+      startres = getVisibleAlignmentWidth() - width;
+    }
+    setStartEndRes(startres - width + 1, startres);
+  }
+
+  /**
+   * Set the first sequence visible in the viewport, maintaining the height. If
+   * the viewport would extend past the last sequence, sets the viewport so it
+   * sits at the bottom of the alignment. Fires a property change event.
+   * 
+   * @param seq
+   *          sequence position
+   */
+  public void setStartSeq(int seq)
+  {
+    int startseq = seq;
+    int height = getViewportHeight();
+    if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
+    {
+      startseq = getVisibleAlignmentHeight() - height;
+    }
+    setStartEndSeq(startseq, startseq + height - 1);
+  }
+
+  /**
+   * Set start and end sequences at the same time. The viewport height may
+   * change. This method only fires one event for the two changes, and should be
+   * used in preference to separate calls to setStartSeq and setEndSeq.
+   * 
+   * @param start
+   *          the start sequence
+   * @param end
+   *          the end sequence
+   */
+  public void setStartEndSeq(int start, int end)
+  {
+    int oldstartseq = this.startSeq;
+    if (start > getVisibleAlignmentHeight() - 1)
+    {
+      startSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
+    }
+    else if (start < 0)
+    {
+      startSeq = 0;
+    }
+    else
+    {
+      startSeq = start;
+    }
+
+    int oldendseq = this.endSeq;
+    if (end >= getVisibleAlignmentHeight())
+    {
+      endSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
+    }
+    else if (end < 0)
+    {
+      endSeq = 0;
+    }
+    else
+    {
+      endSeq = end;
+    }
+
+    changeSupport.firePropertyChange("startseq", oldstartseq, startSeq);
+    if (oldstartseq == startSeq)
+    {
+      // event won't be fired if start positions are the same
+      // fire in case the end positions changed
+      changeSupport.firePropertyChange("endseq", oldendseq, endSeq);
+    }
+  }
+
+  /**
+   * Set the last sequence visible in the viewport. Fires a property change
+   * event.
+   * 
+   * @param seq
+   *          sequence position
+   */
+  public void setEndSeq(int seq)
+  {
+    int height = getViewportHeight();
+    setStartEndSeq(seq - height + 1, seq);
+  }
+
+  /**
+   * Get start residue of viewport
+   */
+  public int getStartRes()
+  {
+    return startRes;
+  }
+
+  /**
+   * Get end residue of viewport
+   */
+  public int getEndRes()
+  {
+    return endRes;
+  }
+
+  /**
+   * Get start sequence of viewport
+   */
+  public int getStartSeq()
+  {
+    return startSeq;
+  }
+
+  /**
+   * Get end sequence of viewport
+   */
+  public int getEndSeq()
+  {
+    return endSeq;
+  }
+
+  /**
+   * Set viewport width in residues, without changing startRes. Use in
+   * preference to calculating endRes from the width, to avoid out by one
+   * errors! Fires a property change event.
+   * 
+   * @param w
+   *          width in residues
+   */
+  public void setViewportWidth(int w)
+  {
+    setStartEndRes(startRes, startRes + w - 1);
+  }
+
+  /**
+   * Set viewport height in residues, without changing startSeq. Use in
+   * preference to calculating endSeq from the height, to avoid out by one
+   * errors! Fires a property change event.
+   * 
+   * @param h
+   *          height in sequences
+   */
+  public void setViewportHeight(int h)
+  {
+    setStartEndSeq(startSeq, startSeq + h - 1);
+  }
+
+  /**
+   * Set viewport horizontal start position and width. Use in preference to
+   * calculating endRes from the width, to avoid out by one errors! Fires a
+   * property change event.
+   * 
+   * @param start
+   *          start residue
+   * @param w
+   *          width in residues
+   */
+  public void setViewportStartAndWidth(int start, int w)
+  {
+    int vpstart = start;
+    if (vpstart < 0)
+    {
+      vpstart = 0;
+    }
+    else if ((w <= getVisibleAlignmentWidth())
+            && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
+    // viewport width is less than the full alignment and we are running off the
+    // RHS edge
+    {
+      vpstart = getVisibleAlignmentWidth() - w;
+    }
+    setStartEndRes(vpstart, vpstart + w - 1);
+  }
+
+  /**
+   * Set viewport vertical start position and height. Use in preference to
+   * calculating endSeq from the height, to avoid out by one errors! Fires a
+   * property change event.
+   * 
+   * @param start
+   *          start sequence
+   * @param h
+   *          height in sequences
+   */
+  public void setViewportStartAndHeight(int start, int h)
+  {
+    int vpstart = start;
+    if (vpstart < 0)
+    {
+      vpstart = 0;
+    }
+    else if ((h <= getVisibleAlignmentHeight())
+            && (vpstart + h - 1 > getVisibleAlignmentHeight() - 1))
+    // viewport height is less than the full alignment and we are running off
+    // the bottom
+    {
+      vpstart = getVisibleAlignmentHeight() - h;
+    }
+    setStartEndSeq(vpstart, vpstart + h - 1);
+  }
+
+  /**
+   * Get width of viewport in residues
+   * 
+   * @return width of viewport
+   */
+  public int getViewportWidth()
+  {
+    return (endRes - startRes + 1);
+  }
+
+  /**
+   * Get height of viewport in residues
+   * 
+   * @return height of viewport
+   */
+  public int getViewportHeight()
+  {
+    return (endSeq - startSeq + 1);
+  }
+
+  /**
+   * Scroll the viewport range vertically. Fires a property change event.
+   * 
+   * @param up
+   *          true if scrolling up, false if down
+   * 
+   * @return true if the scroll is valid
+   */
+  public boolean scrollUp(boolean up)
+  {
+    if (up)
+    {
+      if (startSeq < 1)
+      {
+        return false;
+      }
+
+      setStartSeq(startSeq - 1);
+    }
+    else
+    {
+      if (endSeq >= getVisibleAlignmentHeight() - 1)
+      {
+        return false;
+      }
+
+      setStartSeq(startSeq + 1);
+    }
+    return true;
+  }
+
+  /**
+   * Scroll the viewport range horizontally. Fires a property change event.
+   * 
+   * @param right
+   *          true if scrolling right, false if left
+   * 
+   * @return true if the scroll is valid
+   */
+  public boolean scrollRight(boolean right)
+  {
+    if (!right)
+    {
+      if (startRes < 1)
+      {
+        return false;
+      }
+
+      setStartRes(startRes - 1);
+    }
+    else
+    {
+      if (endRes >= getVisibleAlignmentWidth() - 1)
+      {
+        return false;
+      }
+
+      setStartRes(startRes + 1);
+    }
+
+    return true;
+  }
+
+  /**
+   * Scroll a wrapped alignment so that the specified residue is visible. Fires
+   * a property change event.
+   * 
+   * @param res
+   *          residue position to scroll to
+   */
+  public void scrollToWrappedVisible(int res)
+  {
+    // get the start residue of the wrapped row which res is in
+    // and set that as our start residue
+    int width = getViewportWidth();
+    setStartRes((res / width) * width);
+  }
+
+  /**
+   * Scroll so that (x,y) is visible. Fires a property change event.
+   * 
+   * @param x
+   *          x position in alignment
+   * @param y
+   *          y position in alignment
+   */
+  public void scrollToVisible(int x, int y)
+  {
+    while (y < startSeq)
+    {
+      scrollUp(true);
+    }
+    while (y > endSeq)
+    {
+      scrollUp(false);
+    }
+
+    HiddenColumns hidden = al.getHiddenColumns();
+    while (x < hidden.adjustForHiddenColumns(startRes))
+    {
+      if (!scrollRight(false))
+      {
+        break;
+      }
+    }
+    while (x > hidden.adjustForHiddenColumns(endRes))
+    {
+      if (!scrollRight(true))
+      {
+        break;
+      }
+    }
+  }
+  
+  /**
+   * Adjust sequence position for page up. Fires a property change event.
+   */
+  public void pageUp()
+  {
+    setViewportStartAndHeight(2 * startSeq - endSeq, getViewportHeight());
+  }
+  
+  /**
+   * Adjust sequence position for page down. Fires a property change event.
+   */
+  public void pageDown()
+  {
+    setViewportStartAndHeight(endSeq, getViewportHeight());
+  }
+}
index 8468329..f6addb8 100644 (file)
@@ -36,6 +36,7 @@ import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
@@ -344,6 +345,8 @@ public abstract class FeatureRendererModel implements
     {
       minmax = new Hashtable<String, float[][]>();
     }
+
+    Set<String> oldGroups = new HashSet<String>(featureGroups.keySet());
     AlignmentI alignment = av.getAlignment();
     for (int i = 0; i < alignment.getHeight(); i++)
     {
@@ -358,9 +361,10 @@ public abstract class FeatureRendererModel implements
       int index = 0;
       while (index < features.length)
       {
+        String fgrp = features[index].getFeatureGroup();
+        oldGroups.remove(fgrp);
         if (!featuresDisplayed.isRegistered(features[index].getType()))
         {
-          String fgrp = features[index].getFeatureGroup();
           if (fgrp != null)
           {
             Boolean groupDisplayed = featureGroups.get(fgrp);
@@ -424,6 +428,16 @@ public abstract class FeatureRendererModel implements
         index++;
       }
     }
+
+    /*
+     * oldGroups now consists of groups that no longer 
+     * have any feature in them - remove these
+     */
+    for (String grp : oldGroups)
+    {
+      featureGroups.remove(grp);
+    }
+
     updateRenderOrder(allfeatures);
     findingFeatures = false;
   }
@@ -550,11 +564,13 @@ public abstract class FeatureRendererModel implements
   }
 
   /**
-   * calculate the render colour for a specific feature using current feature
-   * settings.
+   * Returns the configured colour for a particular feature instance. This
+   * includes calculation of 'colour by label', or of a graduated score colour,
+   * if applicable. It does not take into account feature visibility or colour
+   * transparency.
    * 
    * @param feature
-   * @return render colour for the given feature
+   * @return
    */
   public Color getColour(SequenceFeature feature)
   {
@@ -586,11 +602,13 @@ public abstract class FeatureRendererModel implements
     featureColours.put(featureType, col);
   }
 
+  @Override
   public void setTransparency(float value)
   {
     transparency = value;
   }
 
+  @Override
   public float getTransparency()
   {
     return transparency;
@@ -822,7 +840,7 @@ public abstract class FeatureRendererModel implements
    * @return list of groups
    */
   @Override
-  public List getGroups(boolean visible)
+  public List<String> getGroups(boolean visible)
   {
     if (featureGroups != null)
     {
index 14a1bde..f01047d 100644 (file)
@@ -168,6 +168,12 @@ public class ViewStyle implements ViewStyleI
    */
   private boolean scaleProteinAsCdna = true;
 
+  /*
+   * if true, font changes to protein or cDNA are applied to both
+   * sides of a split screen
+   */
+  private boolean proteinFontAsCdna = true;
+
   /**
    * Copy constructor
    * 
@@ -195,6 +201,7 @@ public class ViewStyle implements ViewStyleI
     setScaleAboveWrapped(vs.getScaleAboveWrapped());
     setScaleLeftWrapped(vs.getScaleLeftWrapped());
     setScaleProteinAsCdna(vs.isScaleProteinAsCdna());
+    setProteinFontAsCdna(vs.isProteinFontAsCdna());
     setScaleRightWrapped(vs.getScaleRightWrapped());
     setSeqNameItalics(vs.isSeqNameItalics());
     setShowAnnotation(vs.isShowAnnotation());
@@ -255,6 +262,7 @@ public class ViewStyle implements ViewStyleI
             && getScaleAboveWrapped() == vs.getScaleAboveWrapped()
             && getScaleLeftWrapped() == vs.getScaleLeftWrapped()
             && isScaleProteinAsCdna() == vs.isScaleProteinAsCdna()
+            && isProteinFontAsCdna() == vs.isProteinFontAsCdna()
             && getScaleRightWrapped() == vs.getScaleRightWrapped()
             && isSeqNameItalics() == vs.isSeqNameItalics()
             && isShowAnnotation() == vs.isShowAnnotation()
@@ -1094,4 +1102,16 @@ public class ViewStyle implements ViewStyleI
   {
     this.scaleProteinAsCdna = b;
   }
+
+  @Override
+  public boolean isProteinFontAsCdna()
+  {
+    return proteinFontAsCdna;
+  }
+
+  @Override
+  public void setProteinFontAsCdna(boolean b)
+  {
+    proteinFontAsCdna = b;
+  }
 }
index beee1eb..b0392d4 100644 (file)
@@ -48,35 +48,17 @@ public class AlignmentAnnotationFactory
    * @param counter
    *          provider of feature counts per alignment position
    */
-  public static void newCalculator(FeatureCounterI counter)
+  public static void newCalculator(FeatureSetCounterI counter)
   {
-    // TODO need an interface for AlignFrame by which to access
-    // its AlignViewportI and AlignmentViewPanel
     AlignmentViewPanel currentAlignFrame = Jalview.getCurrentAlignFrame().alignPanel;
-    if (currentAlignFrame != null)
-    {
-      newCalculator(currentAlignFrame.getAlignViewport(),
-              currentAlignFrame, counter);
-    }
-    else
+    if (currentAlignFrame == null)
     {
       System.err
               .println("Can't register calculator as no alignment window has focus");
+      return;
     }
-  }
-
-  /**
-   * Constructs and registers a new alignment annotation worker
-   * 
-   * @param viewport
-   * @param panel
-   * @param counter
-   *          provider of feature counts per alignment position
-   */
-  public static void newCalculator(AlignViewportI viewport,
-          AlignmentViewPanel panel, FeatureCounterI counter)
-  {
-    new ColumnCounterWorker(viewport, panel, counter);
+    new ColumnCounterSetWorker(currentAlignFrame.getAlignViewport(),
+            currentAlignFrame, counter);
   }
 
   /**
@@ -92,8 +74,8 @@ public class AlignmentAnnotationFactory
     AlignFrame currentAlignFrame = Jalview.getCurrentAlignFrame();
     if (currentAlignFrame != null)
     {
-      newCalculator(currentAlignFrame.getViewport(), currentAlignFrame
-              .getAlignPanels().get(0), calculator);
+      new AnnotationWorker(currentAlignFrame.getViewport(),
+              currentAlignFrame.getAlignPanels().get(0), calculator);
     }
     else
     {
@@ -36,16 +36,16 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A class to compute an alignment annotation with column counts of any
- * properties of interest of positions in an alignment. <br>
+ * A class to compute alignment annotations with column counts for a set of
+ * properties of interest on positions in an alignment. <br>
  * This is designed to be extensible, by supplying to the constructor an object
- * that computes a count for each residue position, based on the residue value
- * and any sequence features at that position.
+ * that computes a vector of counts for each residue position, based on the
+ * residue and and sequence features at that position.
  * 
  */
-class ColumnCounterWorker extends AlignCalcWorker
+class ColumnCounterSetWorker extends AlignCalcWorker
 {
-  FeatureCounterI counter;
+  FeatureSetCounterI counter;
 
   /**
    * Constructor registers the annotation for the given alignment frame
@@ -53,8 +53,8 @@ class ColumnCounterWorker extends AlignCalcWorker
    * @param af
    * @param counter
    */
-  public ColumnCounterWorker(AlignViewportI viewport,
-          AlignmentViewPanel panel, FeatureCounterI counter)
+  public ColumnCounterSetWorker(AlignViewportI viewport,
+          AlignmentViewPanel panel, FeatureSetCounterI counter)
   {
     super(viewport, panel);
     ourAnnots = new ArrayList<AlignmentAnnotation>();
@@ -69,6 +69,7 @@ class ColumnCounterWorker extends AlignCalcWorker
   @Override
   public void run()
   {
+    boolean annotationAdded = false;
     try
     {
       calcMan.notifyStart(this);
@@ -93,7 +94,7 @@ class ColumnCounterWorker extends AlignCalcWorker
       {
         try
         {
-          computeAnnotations();
+          annotationAdded = computeAnnotations();
         } catch (IndexOutOfBoundsException x)
         {
           // probable race condition. just finish and return without any fuss.
@@ -111,7 +112,10 @@ class ColumnCounterWorker extends AlignCalcWorker
 
     if (ap != null)
     {
-      ap.adjustAnnotationHeight();
+      if (annotationAdded)
+      {
+        ap.adjustAnnotationHeight();
+      }
       ap.paintAlignment(true);
     }
 
@@ -120,8 +124,10 @@ class ColumnCounterWorker extends AlignCalcWorker
   /**
    * Scan each column of the alignment to calculate a count by feature type. Set
    * the count as the value of the alignment annotation for that feature type.
+   * 
+   * @return
    */
-  void computeAnnotations()
+  boolean computeAnnotations()
   {
     FeatureRenderer fr = new FeatureRenderer(alignViewport);
     // TODO use the commented out code once JAL-2075 is fixed
@@ -130,56 +136,91 @@ class ColumnCounterWorker extends AlignCalcWorker
     // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
     // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
 
-    // int width = alignmentView.getWidth();
+    int rows = counter.getNames().length;
+
     int width = alignment.getWidth();
     int height = alignment.getHeight();
-    int[] counts = new int[width];
-    int max = 0;
+    int[][] counts = new int[width][rows];
+    int max[] = new int[rows];
+    for (int crow = 0; crow < rows; crow++)
+    {
+      max[crow] = 0;
+    }
+
+    int[] minC = counter.getMinColour();
+    int[] maxC = counter.getMaxColour();
+    Color minColour = new Color(minC[0], minC[1], minC[2]);
+    Color maxColour = new Color(maxC[0], maxC[1], maxC[2]);
 
     for (int col = 0; col < width; col++)
     {
-      int count = 0;
+      int[] count = counts[col];
+      for (int crow = 0; crow < rows; crow++)
+      {
+        count[crow] = 0;
+      }
       for (int row = 0; row < height; row++)
       {
-        count += countFeaturesAt(alignment, col, row, fr);
+        int[] colcount = countFeaturesAt(alignment, col, row, fr);
+        if (colcount != null)
+        {
+          for (int crow = 0; crow < rows; crow++)
+          {
+            count[crow] += colcount[crow];
+          }
+        }
       }
       counts[col] = count;
-      max = Math.max(count, max);
+      for (int crow = 0; crow < rows; crow++)
+      {
+        max[crow] = Math.max(count[crow], max[crow]);
+      }
     }
 
-    Annotation[] anns = new Annotation[width];
-    /*
-     * add non-zero counts as annotations
-     */
-    for (int i = 0; i < counts.length; i++)
+    boolean annotationAdded = false;
+
+    for (int anrow = 0; anrow < rows; anrow++)
     {
-      int count = counts[i];
-      if (count > 0)
+      Annotation[] anns = new Annotation[width];
+      long rmax = 0;
+      /*
+       * add counts as annotations. zeros are needed since select-by-annotation ignores empty annotation positions
+       */
+      for (int i = 0; i < counts.length; i++)
       {
-        Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
-                max, Color.blue);
+        int count = counts[i][anrow];
+
+        Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
+                max[anrow], maxColour);
         String str = String.valueOf(count);
         anns[i] = new Annotation(str, str, '0', count, color);
+        rmax = Math.max(count, rmax);
       }
-    }
 
-    /*
-     * construct or update the annotation
-     */
-    AlignmentAnnotation ann = alignViewport.getAlignment()
-            .findOrCreateAnnotation(counter.getName(),
-                    counter.getDescription(), false, null, null);
-    ann.description = counter.getDescription();
-    ann.showAllColLabels = true;
-    ann.scaleColLabel = true;
-    ann.graph = AlignmentAnnotation.BAR_GRAPH;
-    ann.annotations = anns;
-    setGraphMinMax(ann, anns);
-    ann.validateRangeAndDisplay();
-    if (!ourAnnots.contains(ann))
-    {
-      ourAnnots.add(ann);
+      /*
+       * construct or update the annotation
+       */
+      String description = counter.getDescriptions()[anrow];
+      if (!alignment.findAnnotation(description).iterator().hasNext())
+      {
+        annotationAdded = true;
+      }
+      AlignmentAnnotation ann = alignment.findOrCreateAnnotation(
+              counter.getNames()[anrow], description, false, null, null);
+      ann.description = description;
+      ann.showAllColLabels = true;
+      ann.scaleColLabel = true;
+      ann.graph = AlignmentAnnotation.BAR_GRAPH;
+      ann.annotations = anns;
+      ann.graphMin = 0f; // minimum always zero count
+      ann.graphMax = rmax; // maximum count from loop over feature columns
+      ann.validateRangeAndDisplay();
+      if (!ourAnnots.contains(ann))
+      {
+        ourAnnots.add(ann);
+      }
     }
+    return annotationAdded;
   }
 
   /**
@@ -191,22 +232,22 @@ class ColumnCounterWorker extends AlignCalcWorker
    * @param row
    * @param fr
    */
-  int countFeaturesAt(AlignmentI alignment, int col, int row,
+  int[] countFeaturesAt(AlignmentI alignment, int col, int row,
           FeatureRenderer fr)
   {
     SequenceI seq = alignment.getSequenceAt(row);
     if (seq == null)
     {
-      return 0;
+      return null;
     }
     if (col >= seq.getLength())
     {
-      return 0;// sequence doesn't extend this far
+      return null;// sequence doesn't extend this far
     }
     char res = seq.getCharAt(col);
     if (Comparison.isGap(res))
     {
-      return 0;
+      return null;
     }
     int pos = seq.findPosition(col);
 
@@ -216,7 +257,7 @@ class ColumnCounterWorker extends AlignCalcWorker
     // NB have to adjust pos if using AlignmentView.getVisibleAlignment
     // see JAL-2075
     List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
-    int count = this.counter.count(String.valueOf(res), features);
+    int[] count = this.counter.count(String.valueOf(res), features);
     return count;
   }
 
index 0c7e69a..284f1a1 100644 (file)
@@ -50,7 +50,8 @@ public class ConsensusThread extends AlignCalcWorker
     try
     {
       AlignmentAnnotation consensus = getConsensusAnnotation();
-      if (consensus == null || calcMan.isPending(this))
+      AlignmentAnnotation gap = getGapAnnotation();
+      if ((consensus == null && gap == null) || calcMan.isPending(this))
       {
         calcMan.workerComplete(this);
         return;
@@ -117,6 +118,11 @@ public class ConsensusThread extends AlignCalcWorker
   {
     AlignmentAnnotation consensus = getConsensusAnnotation();
     consensus.annotations = new Annotation[aWidth];
+    AlignmentAnnotation gap = getGapAnnotation();
+    if (gap != null)
+    {
+      gap.annotations = new Annotation[aWidth];
+    }
   }
 
   /**
@@ -127,8 +133,8 @@ public class ConsensusThread extends AlignCalcWorker
 
     SequenceI[] aseqs = getSequences();
     int width = alignment.getWidth();
-    ProfilesI hconsensus = AAFrequency.calculate(aseqs, width, 0,
-            width, true);
+    ProfilesI hconsensus = AAFrequency.calculate(aseqs, width, 0, width,
+            true);
 
     alignViewport.setSequenceConsensusHash(hconsensus);
     setColourSchemeConsensus(hconsensus);
@@ -165,6 +171,16 @@ public class ConsensusThread extends AlignCalcWorker
   }
 
   /**
+   * Get the Gap annotation for the alignment
+   * 
+   * @return
+   */
+  protected AlignmentAnnotation getGapAnnotation()
+  {
+    return alignViewport.getAlignmentGapAnnotation();
+  }
+
+  /**
    * update the consensus annotation from the sequence profile data using
    * current visualization settings.
    */
@@ -182,6 +198,11 @@ public class ConsensusThread extends AlignCalcWorker
             && hconsensus != null)
     {
       deriveConsensus(consensus, hconsensus);
+      AlignmentAnnotation gap = getGapAnnotation();
+      if (gap != null)
+      {
+        deriveGap(gap, hconsensus);
+      }
     }
   }
 
@@ -200,13 +221,29 @@ public class ConsensusThread extends AlignCalcWorker
 
     long nseq = getSequences().length;
     AAFrequency.completeConsensus(consensusAnnotation, hconsensus,
-            hconsensus.getStartColumn(),
-            hconsensus.getEndColumn() + 1,
+            hconsensus.getStartColumn(), hconsensus.getEndColumn() + 1,
             alignViewport.isIgnoreGapsConsensus(),
             alignViewport.isShowSequenceLogo(), nseq);
   }
 
   /**
+   * Convert the computed consensus data into a gap annotation row for display.
+   * 
+   * @param gapAnnotation
+   *          the annotation to be populated
+   * @param hconsensus
+   *          the computed consensus data
+   */
+  protected void deriveGap(AlignmentAnnotation gapAnnotation,
+          ProfilesI hconsensus)
+  {
+    long nseq = getSequences().length;
+    AAFrequency.completeGapAnnot(gapAnnotation, hconsensus,
+            hconsensus.getStartColumn(), hconsensus.getEndColumn() + 1,
+            nseq);
+  }
+
+  /**
    * Get the consensus data stored on the viewport.
    * 
    * @return
index e71c4f5..571234c 100644 (file)
@@ -55,7 +55,7 @@ public class ConservationThread extends AlignCalcWorker
     {
       calcMan.notifyStart(this); // updatingConservation = true;
 
-      while (!calcMan.notifyWorking(this))
+      while ((calcMan != null) && (!calcMan.notifyWorking(this)))
       {
         try
         {
@@ -69,7 +69,8 @@ public class ConservationThread extends AlignCalcWorker
           ex.printStackTrace();
         }
       }
-      if (alignViewport.isClosed())
+      if ((alignViewport == null) || (calcMan == null)
+              || (alignViewport.isClosed()))
       {
         abortAndDestroy();
         return;
@@ -114,6 +115,12 @@ public class ConservationThread extends AlignCalcWorker
     }
     calcMan.workerComplete(this);
 
+    if ((alignViewport == null) || (calcMan == null)
+            || (alignViewport.isClosed()))
+    {
+      abortAndDestroy();
+      return;
+    }
     if (ap != null)
     {
       ap.paintAlignment(true);
similarity index 76%
rename from src/jalview/workers/FeatureCounterI.java
rename to src/jalview/workers/FeatureSetCounterI.java
index 3a080ec..e14952f 100644 (file)
@@ -18,6 +18,7 @@
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
+
 package jalview.workers;
 
 import jalview.datamodel.SequenceFeature;
@@ -25,15 +26,16 @@ import jalview.datamodel.SequenceFeature;
 import java.util.List;
 
 /**
- * An interface for a type that returns counts of any value of interest at a
- * sequence position that can be determined from the sequence character and any
- * features present at that position
+ * An interface for a type that returns counts (per computed annotation type) of
+ * any value of interest at a sequence position that can be determined from the
+ * sequence character and any features present at that position
  * 
  */
-public interface FeatureCounterI
+public interface FeatureSetCounterI
 {
   /**
-   * Returns a count of some property of interest, for example
+   * Returns counts (per annotation type) of some properties of interest, for
+   * example
    * <ul>
    * <li>the number of variant features at the position</li>
    * <li>the number of Cath features of status 'True Positive'</li>
@@ -46,22 +48,22 @@ public interface FeatureCounterI
    * @param a
    *          list of any sequence features which include the position
    */
-  int count(String residue, List<SequenceFeature> features);
+  int[] count(String residue, List<SequenceFeature> features);
 
   /**
-   * Returns a name for the annotation that this is counting, for use as the
-   * displayed label
+   * Returns names for the annotations that this is counting, for use as the
+   * displayed labels
    * 
    * @return
    */
-  String getName();
+  String[] getNames();
 
   /**
-   * Returns a description for the annotation, for display as a tooltip
+   * Returns descriptions for the annotations, for display as tooltips
    * 
    * @return
    */
-  String getDescription();
+  String[] getDescriptions();
 
   /**
    * Returns the colour (as [red, green, blue] values in the range 0-255) to use
index 676a4b6..4d3dd2f 100644 (file)
@@ -617,8 +617,8 @@ public class DasSequenceFeatureFetcher
     }
     af.getFeatureRenderer().featuresAdded();
 
-    int start = af.getViewport().getStartSeq();
-    int end = af.getViewport().getEndSeq();
+    int start = af.getViewport().getRanges().getStartSeq();
+    int end = af.getViewport().getRanges().getEndSeq();
     int index;
     for (index = start; index < end; index++)
     {
diff --git a/src/jalview/ws/dbsources/Pdb.java~ b/src/jalview/ws/dbsources/Pdb.java~
new file mode 100644 (file)
index 0000000..6c2f70a
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ws.dbsources;
+
+import jalview.api.FeatureSettingsModelI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.DBRefSource;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.io.FormatAdapter;
+import jalview.io.PDBFeatureSettings;
+import jalview.util.MessageManager;
+import jalview.ws.ebi.EBIFetchClient;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import com.stevesoft.pat.Regex;
+
+/**
+ * @author JimP
+ * 
+ */
+public class Pdb extends EbiFileRetrievedProxy
+{
+  public Pdb()
+  {
+    super();
+  }
+
+  public static final String FEATURE_INSERTION = "INSERTION";
+
+  public static final String FEATURE_RES_NUM = "RESNUM";
+
+  private static String currentDefaultFormat = DBRefSource.PDB;
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
+   */
+  @Override
+  public String getAccessionSeparator()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#getAccessionValidator()
+   */
+  @Override
+  public Regex getAccessionValidator()
+  {
+    return new Regex("([1-9][0-9A-Za-z]{3}):?([ _A-Za-z0-9]?)");
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#getDbSource()
+   */
+  @Override
+  public String getDbSource()
+  {
+    return DBRefSource.PDB;
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#getDbVersion()
+   */
+  @Override
+  public String getDbVersion()
+  {
+    return "0";
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
+   */
+  @Override
+  public AlignmentI getSequenceRecords(String queries) throws Exception
+  {
+    AlignmentI pdbAlignment = null;
+    Vector result = new Vector();
+    String chain = null;
+    String id = null;
+    if (queries.indexOf(":") > -1)
+    {
+      chain = queries.substring(queries.indexOf(":") + 1);
+      id = queries.substring(0, queries.indexOf(":"));
+    }
+    else
+    {
+      id = queries;
+    }
+    if (queries.length() > 4 && chain == null)
+    {
+      chain = queries.substring(4, 5);
+      id = queries.substring(0, 4);
+    }
+    if (!isValidReference(id))
+    {
+      System.err.println("Ignoring invalid pdb query: '" + id + "'");
+      stopQuery();
+      return null;
+    }
+    String ext = getCurrentDefaultFormat().equalsIgnoreCase("mmcif") ? ".cif"
+            : ".xml";
+    EBIFetchClient ebi = new EBIFetchClient();
+    file = ebi.fetchDataAsFile("pdb:" + id,
+<<<<<<< HEAD
+            getCurrentDefaultFomart().toLowerCase(), ext)
+=======
+            getCurrentDefaultFormat().toLowerCase(), "raw", ext)
+>>>>>>> develop
+            .getAbsolutePath();
+    stopQuery();
+    if (file == null)
+    {
+      return null;
+    }
+    try
+    {
+
+      pdbAlignment = new FormatAdapter().readFile(file,
+              jalview.io.AppletFormatAdapter.FILE,
+              getCurrentDefaultFormat());
+      if (pdbAlignment != null)
+      {
+        List<SequenceI> toremove = new ArrayList<SequenceI>();
+        for (SequenceI pdbcs : pdbAlignment.getSequences())
+        {
+          String chid = null;
+          // Mapping map=null;
+          for (PDBEntry pid : pdbcs.getAllPDBEntries())
+          {
+            if (pid.getFile() == file)
+            {
+              chid = pid.getChainCode();
+
+            }
+            ;
+
+          }
+          if (chain == null
+                  || (chid != null && (chid.equals(chain)
+                          || chid.trim().equals(chain.trim()) || (chain
+                          .trim().length() == 0 && chid.equals("_")))))
+          {
+            pdbcs.setName(jalview.datamodel.DBRefSource.PDB + "|" + id
+                    + "|" + pdbcs.getName());
+            // Might need to add more metadata to the PDBEntry object
+            // like below
+            /*
+             * PDBEntry entry = new PDBEntry(); // Construct the PDBEntry
+             * entry.setId(id); if (entry.getProperty() == null)
+             * entry.setProperty(new Hashtable());
+             * entry.getProperty().put("chains", pdbchain.id + "=" +
+             * sq.getStart() + "-" + sq.getEnd());
+             * sq.getDatasetSequence().addPDBId(entry);
+             */
+            // Add PDB DB Refs
+            // We make a DBRefEtntry because we have obtained the PDB file from
+            // a
+            // verifiable source
+            // JBPNote - PDB DBRefEntry should also carry the chain and mapping
+            // information
+            DBRefEntry dbentry = new DBRefEntry(getDbSource(),
+                    getDbVersion(), (chid == null ? id : id + chid));
+            // dbentry.setMap()
+            pdbcs.addDBRef(dbentry);
+          }
+          else
+          {
+            // mark this sequence to be removed from the alignment
+            // - since it's not from the right chain
+            toremove.add(pdbcs);
+          }
+        }
+        // now remove marked sequences
+        for (SequenceI pdbcs : toremove)
+        {
+          pdbAlignment.deleteSequence(pdbcs);
+          if (pdbcs.getAnnotation() != null)
+          {
+            for (AlignmentAnnotation aa : pdbcs.getAnnotation())
+            {
+              pdbAlignment.deleteAnnotation(aa);
+            }
+          }
+        }
+      }
+
+      if (pdbAlignment == null || pdbAlignment.getHeight() < 1)
+      {
+        throw new Exception(MessageManager.formatMessage(
+                "exception.no_pdb_records_for_chain", new String[] { id,
+                    ((chain == null) ? "' '" : chain) }));
+      }
+
+    } catch (Exception ex) // Problem parsing PDB file
+    {
+      stopQuery();
+      throw (ex);
+    }
+    return pdbAlignment;
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
+   */
+  @Override
+  public boolean isValidReference(String accession)
+  {
+    Regex r = getAccessionValidator();
+    return r.search(accession.trim());
+  }
+
+  /**
+   * obtain human glyoxalase chain A sequence
+   */
+  @Override
+  public String getTestQuery()
+  {
+    return "1QIPA";
+  }
+
+  @Override
+  public String getDbName()
+  {
+    return "PDB"; // getDbSource();
+  }
+
+  @Override
+  public int getTier()
+  {
+    return 0;
+  }
+
+  public static String getCurrentDefaultFormat()
+  {
+    return currentDefaultFormat;
+  }
+
+  public static void setCurrentDefaultFormat(String currentDefaultFomart)
+  {
+    Pdb.currentDefaultFormat = currentDefaultFomart;
+  }
+
+  /**
+   * Returns a descriptor for suitable feature display settings with
+   * <ul>
+   * <li>ResNums or insertions features visible</li>
+   * <li>insertions features coloured red</li>
+   * <li>ResNum features coloured by label</li>
+   * <li>Insertions displayed above (on top of) ResNums</li>
+   * </ul>
+   */
+  @Override
+  public FeatureSettingsModelI getFeatureColourScheme()
+  {
+    return new PDBFeatureSettings();
+  }
+}
index b184ff2..e0f7f70 100644 (file)
@@ -69,6 +69,7 @@ public class DasSourceRegistry implements DasSourceRegistryI,
     return loadingDasSources;
   }
 
+  @Override
   public String getDasRegistryURL()
   {
     String registry = jalview.bin.Cache.getDefault("DAS_REGISTRY_URL",
@@ -150,9 +151,8 @@ public class DasSourceRegistry implements DasSourceRegistryI,
       return dsrc;
     } catch (Exception ex)
     {
-      System.err.println("Failed to contact DAS1 registry at "
-              + registryURL);
-      ex.printStackTrace();
+      System.out.println("DAS1 registry at " + registryURL
+              + " no longer exists");
       return new ArrayList<jalviewSourceI>();
     }
   }
index e685d00..b0210d8 100644 (file)
@@ -27,7 +27,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -114,7 +114,7 @@ class JPredThread extends JWS1Thread implements WSClientI
         return null;
       }
       AlignmentI al = null;
-      ColumnSelection alcsel = null;
+      HiddenColumns alhidden = null;
       int FirstSeq = -1; // the position of the query sequence in Alignment al
 
       JpredResult result = (JpredResult) this.result;
@@ -141,10 +141,10 @@ class JPredThread extends JWS1Thread implements WSClientI
           if (predMap != null)
           {
             Object[] alandcolsel = input
-                    .getAlignmentAndColumnSelection(getGapChar());
+                    .getAlignmentAndHiddenColumns(getGapChar());
             sqs = (SequenceI[]) alandcolsel[0];
             al = new Alignment(sqs);
-            alcsel = (ColumnSelection) alandcolsel[1];
+            alhidden = (HiddenColumns) alandcolsel[1];
           }
           else
           {
@@ -192,7 +192,7 @@ class JPredThread extends JWS1Thread implements WSClientI
         {
           char gc = getGapChar();
           SequenceI[] sqs = (SequenceI[]) input
-                  .getAlignmentAndColumnSelection(gc)[0];
+                  .getAlignmentAndHiddenColumns(gc)[0];
           if (this.msaIndex >= sqs.length)
           {
             throw new Error(
@@ -237,7 +237,7 @@ class JPredThread extends JWS1Thread implements WSClientI
           {
             // Adjust input view for gaps
             // propagate insertions into profile
-            alcsel = ColumnSelection.propagateInsertions(profileseq, al,
+            alhidden = HiddenColumns.propagateInsertions(profileseq, al,
                     input);
           }
         }
@@ -252,7 +252,7 @@ class JPredThread extends JWS1Thread implements WSClientI
                   alant.sequenceRef);
         }
       }
-      return new Object[] { al, alcsel }; // , FirstSeq, noMsa};
+      return new Object[] { al, alhidden }; // , FirstSeq, noMsa};
     }
 
     /**
@@ -625,7 +625,7 @@ class JPredThread extends JWS1Thread implements WSClientI
             if (res[1] != null)
             {
               af = new AlignFrame((Alignment) res[0],
-                      (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
+                      (HiddenColumns) res[1], AlignFrame.DEFAULT_WIDTH,
                       AlignFrame.DEFAULT_HEIGHT);
             }
             else
@@ -651,7 +651,8 @@ class JPredThread extends JWS1Thread implements WSClientI
              */
 
             af = new AlignFrame((Alignment) res[0],
-                    (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
+ (HiddenColumns) res[1],
+                    AlignFrame.DEFAULT_WIDTH,
                     AlignFrame.DEFAULT_HEIGHT);
           }
           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,
index e4247f7..72d41c9 100644 (file)
@@ -26,7 +26,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -632,7 +632,7 @@ class MsaWSThread extends JWS1Thread implements WSClientI
       orders[j] = null;
     }
     SequenceI[] alignment = (SequenceI[]) newview[0];
-    ColumnSelection columnselection = (ColumnSelection) newview[1];
+    HiddenColumns hidden = (HiddenColumns) newview[1];
     Alignment al = new Alignment(alignment);
     // TODO: add 'provenance' property to alignment from the method notes
     // accompanying each subjob
@@ -646,7 +646,7 @@ class MsaWSThread extends JWS1Thread implements WSClientI
 
     if (newFrame)
     {
-      AlignFrame af = new AlignFrame(al, columnselection,
+      AlignFrame af = new AlignFrame(al, hidden,
               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 
       // initialise with same renderer settings as in parent alignframe.
index b14917e..edc9ae8 100644 (file)
@@ -666,7 +666,7 @@ class SeqSearchWSThread extends JWS1Thread implements WSClientI
               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
       if (nf != null)
       {
-        af.ShowNewickTree(nf, MessageManager.formatMessage(
+        af.showNewickTree(nf, MessageManager.formatMessage(
                 "label.tree_from", new String[] { this.alTitle }));
       }
       // initialise with same renderer settings as in parent alignframe.
diff --git a/src/jalview/ws/jws2/JPred301Client.java b/src/jalview/ws/jws2/JPred301Client.java
deleted file mode 100644 (file)
index c15f256..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.ws.jws2;
-
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.gui.AlignFrame;
-import jalview.ws.jws2.jabaws2.Jws2Instance;
-import jalview.ws.params.ArgumentI;
-import jalview.ws.params.OptionI;
-import jalview.ws.params.WsParamSetI;
-import jalview.ws.uimodel.AlignAnalysisUIText;
-
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import compbio.data.sequence.FastaSequence;
-import compbio.data.sequence.JpredAlignment;
-import compbio.metadata.Argument;
-
-public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
-{
-  /**
-   * 
-   * @return default args for this service when run as dynamic web service
-   */
-  public List<Argument> selectDefaultArgs()
-  {
-    List<ArgumentI> rgs = new ArrayList<ArgumentI>();
-    for (ArgumentI argi : service.getParamStore().getServiceParameters())
-    {
-      if (argi instanceof OptionI)
-      {
-        List<String> o = ((OptionI) argi).getPossibleValues();
-        if (o.contains("-pred-nohits"))
-        {
-          OptionI cpy = ((OptionI) argi).copy();
-          cpy.setValue("-pred-nohits");
-          rgs.add(cpy);
-        }
-      }
-    }
-    return JabaParamStore.getJabafromJwsArgs(rgs);
-  }
-
-  public JPred301Client(Jws2Instance service, AlignFrame alignFrame,
-          WsParamSetI preset, List<Argument> paramset)
-  {
-    super(service, alignFrame, preset, paramset);
-    submitGaps = true;
-    alignedSeqs = true;
-    nucleotidesAllowed = false;
-    proteinAllowed = true;
-    gapMap = new boolean[0];
-    updateParameters(null, selectDefaultArgs());
-  }
-
-  @Override
-  boolean checkValidInputSeqs(boolean dynamic, List<FastaSequence> seqs)
-  {
-    return (seqs.size() > 1);
-  }
-
-  @Override
-  public String getServiceActionText()
-  {
-    return "calculating consensus secondary structure prediction using JPred service";
-  }
-
-  private static Map<String, String[]> jpredRowLabels = new HashMap<String, String[]>();
-
-  private static final Set<String> jpredRes_graph;
-
-  private static final Set<String> jpredRes_ssonly;
-  static
-  {
-    jpredRes_ssonly = new HashSet<String>();
-    jpredRes_ssonly.add("jnetpred".toLowerCase());
-    jpredRes_ssonly.add("jnetpssm".toLowerCase());
-    jpredRes_ssonly.add("jnethmm".toLowerCase());
-    jpredRes_graph = new HashSet<String>();
-    jpredRes_graph.add("jnetconf".toLowerCase());
-    jpredRes_graph.add("jnet burial".toLowerCase());
-  }
-
-  /**
-   * update the consensus annotation from the sequence profile data using
-   * current visualization settings.
-   */
-  @Override
-  public void updateResultAnnotation(boolean immediate)
-  {
-    if (immediate || !calcMan.isWorking(this) && msascoreset != null)
-    {
-      if (msascoreset instanceof compbio.data.sequence.JpredAlignment)
-      {
-        JpredAlignment jpres = (JpredAlignment) msascoreset;
-        int alWidth = alignViewport.getAlignment().getWidth();
-        ArrayList<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
-        char[] sol = new char[jpres.getJpredSequences().get(0).getLength()];
-        boolean firstsol = true;
-        for (FastaSequence fsq : jpres.getJpredSequences())
-        {
-          String[] k = jpredRowLabels.get(fsq.getId());
-          if (k == null)
-          {
-            k = new String[] { fsq.getId(), "JNet Output" };
-          }
-          if (fsq.getId().startsWith("JNETSOL"))
-          {
-            char amnt = (fsq.getId().endsWith("25") ? "3" : fsq.getId()
-                    .endsWith("5") ? "6" : "9").charAt(0);
-            char[] vseq = fsq.getSequence().toCharArray();
-            for (int spos = 0, sposL = fsq.getLength(); spos < sposL; spos++)
-            {
-              if (firstsol)
-              {
-                sol[spos] = '0';
-              }
-              if (vseq[spos] == 'B'
-                      && (sol[spos] == '0' || sol[spos] < amnt))
-              {
-                sol[spos] = amnt;
-              }
-            }
-            firstsol = false;
-          }
-          else
-          {
-            createAnnotationRowFromString(
-                    ourAnnot,
-                    getCalcId(),
-                    alWidth,
-                    k[0],
-                    k[1],
-                    jpredRes_graph.contains(fsq.getId()) ? AlignmentAnnotation.BAR_GRAPH
-                            : AlignmentAnnotation.NO_GRAPH, 0f, 9f,
-                    fsq.getSequence());
-          }
-
-        }
-        createAnnotationRowFromString(
-                ourAnnot,
-                getCalcId(),
-                alWidth,
-                "Jnet Burial",
-                "<html>Prediction of Solvent Accessibility<br/>levels are<ul><li>0 - Exposed</li><li>3 - 25% or more S.A. accessible</li><li>6 - 5% or more S.A. accessible</li><li>9 - Buried (<5% exposed)</li></ul>",
-                AlignmentAnnotation.BAR_GRAPH, 0f, 9f, new String(sol));
-        for (FastaSequence fsq : jpres.getSequences())
-        {
-          if (fsq.getId().equalsIgnoreCase("QUERY"))
-          {
-            createAnnotationRowFromString(ourAnnot, getCalcId(), alWidth,
-                    "Query", "JPred Reference Sequence",
-                    AlignmentAnnotation.NO_GRAPH, 0f, 0f, fsq.getSequence());
-          }
-        }
-        if (ourAnnot.size() > 0)
-        {
-          updateOurAnnots(ourAnnot);
-        }
-      }
-    }
-  }
-
-  private void createAnnotationRowFromString(
-          ArrayList<AlignmentAnnotation> ourAnnot, String calcId,
-          int alWidth, String label, String descr, int rowType, float min,
-          float max, String jpredPrediction)
-  {
-    // simple annotation row
-    AlignmentAnnotation annotation = alignViewport.getAlignment()
-            .findOrCreateAnnotation(label, calcId, true, null, null);
-    if (alWidth == gapMap.length) // scr.getScores().size())
-    {
-      annotation.label = new String(label);
-      annotation.description = new String(descr);
-      annotation.graph = rowType;
-      annotation.graphMin = min;
-      annotation.graphMax = max;
-      if (constructAnnotationFromString(annotation, jpredPrediction,
-              alWidth, rowType))
-      {
-        // created a valid annotation from the data
-        ourAnnot.add(annotation);
-        // annotation.validateRangeAndDisplay();
-      }
-    }
-  }
-
-  private boolean constructAnnotationFromString(
-          AlignmentAnnotation annotation, String sourceData, int alWidth,
-          int rowType)
-  {
-    if (sourceData.length() == 0 && alWidth > 0)
-    {
-      return false;
-    }
-    Annotation[] elm = new Annotation[alWidth];
-    boolean ssOnly = jpredRes_ssonly.contains(annotation.label
-            .toLowerCase());
-    boolean graphOnly = rowType != AlignmentAnnotation.NO_GRAPH;
-    if (!ssOnly && !graphOnly)
-    {
-      // for burial 'B'
-      annotation.showAllColLabels = true;
-    }
-
-    for (int i = 0, iSize = sourceData.length(); i < iSize; i++)
-    {
-      char annot = sourceData.charAt(i);
-      // if we're at a gapped column then skip to next ungapped position
-      if (gapMap != null && gapMap.length > 0)
-      {
-        while (!gapMap[i])
-        {
-          elm[i++] = new Annotation("", "", ' ', Float.NaN);
-        }
-      }
-      switch (rowType)
-      {
-      case AlignmentAnnotation.NO_GRAPH:
-        elm[i] = ssOnly ? new Annotation("", "", annot, Float.NaN,
-                colourSS(annot)) : new Annotation("" + annot, "" + annot,
-                '\0', Float.NaN);
-        break;
-      default:
-        try
-        {
-          elm[i] = new Annotation("" + annot, "" + annot, annot,
-                  Integer.valueOf("" + annot));
-        } catch (Exception x)
-        {
-          System.err.println("Expected numeric value in character '"
-                  + annot + "'");
-        }
-      }
-    }
-
-    annotation.annotations = elm;
-    annotation.belowAlignment = true;
-    annotation.validateRangeAndDisplay();
-    return true;
-  }
-
-  private Color colourSS(char annot)
-  {
-    switch (annot)
-    {
-    case 'H':
-      return jalview.renderer.AnnotationRenderer.HELIX_COLOUR;
-    case 'E':
-      return jalview.renderer.AnnotationRenderer.SHEET_COLOUR;
-    }
-    return jalview.renderer.AnnotationRenderer.GLYPHLINE_COLOR;
-  }
-
-  @Override
-  public String getCalcId()
-  {
-    return CALC_ID;
-  }
-
-  private static String CALC_ID = "jabaws21.JPred3Cons";
-
-  public static AlignAnalysisUIText getAlignAnalysisUITest()
-  {
-    return new AlignAnalysisUIText(
-            compbio.ws.client.Services.JpredWS.toString(),
-            jalview.ws.jws2.JPred301Client.class, CALC_ID, false, true,
-            true, "JPred Consensus",
-            "When checked, JPred consensus is updated automatically.",
-            "Change JPred Settings...",
-            "Modify settings for JPred calculations.");
-  }
-}
index 1b2c708..12a08a0 100644 (file)
@@ -67,7 +67,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   /*
    * the .jalview_properties entry for JWS2 URLS
    */
-  final static String JWS2HOSTURLS = "JWS2HOSTURLS";
+  private final static String JWS2HOSTURLS = "JWS2HOSTURLS";
 
   /*
    * Singleton instance
@@ -85,12 +85,17 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
           this);
 
-  Vector<String> invalidServiceUrls = null, urlsWithoutServices = null,
-          validServiceUrls = null;
+  private Vector<String> invalidServiceUrls = null;
 
-  boolean running = false, aborted = false;
+  private Vector<String> urlsWithoutServices = null;
 
-  Thread oldthread = null;
+  private Vector<String> validServiceUrls = null;
+
+  private volatile boolean running = false;
+
+  private volatile boolean aborted = false;
+
+  private Thread oldthread = null;
 
   /**
    * holds list of services.
@@ -143,9 +148,9 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   public void setAborted(boolean aborted)
   {
     this.aborted = aborted;
-
   }
 
+  @Override
   public void run()
   {
 
@@ -167,6 +172,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
         {
         }
       }
+      aborted = false;
       Cache.log.debug("Old discovery thread has finished.");
     }
     running = true;
@@ -179,7 +185,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
       ignoredServices.add(ignored);
     }
 
-    changeSupport.firePropertyChange("services", services, new Vector());
+    changeSupport.firePropertyChange("services", services,
+            new Vector<Jws2Instance>());
     oldthread = Thread.currentThread();
     try
     {
@@ -263,7 +270,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     if (!aborted)
     {
       // resort services according to order found in jabaws service list
-      // also ensure servics for each host are ordered in same way.
+      // also ensure services for each host are ordered in same way.
 
       if (services != null && services.size() > 0)
       {
@@ -290,7 +297,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     }
     oldthread = null;
     running = false;
-    changeSupport.firePropertyChange("services", new Vector(), services);
+    changeSupport.firePropertyChange("services",
+            new Vector<Jws2Instance>(), services);
   }
 
   /**
@@ -321,7 +329,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     service.hasParameters();
     if (validServiceUrls == null)
     {
-      validServiceUrls = new Vector();
+      validServiceUrls = new Vector<String>();
     }
     validServiceUrls.add(jwsservers);
   }
@@ -330,6 +338,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * attach all available web services to the appropriate submenu in the given
    * JMenu
    */
+  @Override
   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
   {
     // dynamically regenerate service list.
@@ -348,8 +357,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     {
       return;
     }
-    boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
-            .getDefault("WSMENU_BYTYPE", false);
+
     /**
      * eventually, JWS2 services will appear under the same align/etc submenus.
      * for moment we keep them separate.
@@ -442,27 +450,19 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
             {
               new Thread(new Runnable()
               {
+                @Override
                 public void run()
                 {
                   setPreferredServiceFor(alignFrame, sv.serviceType,
                           sv.action, sv);
                   changeSupport.firePropertyChange("services",
-                          new Vector(), services);
+                          new Vector<Jws2Instance>(), services);
                 };
               }).start();
 
             }
           });
         }
-        /*
-         * hitm.addActionListener(new ActionListener() {
-         * 
-         * @Override public void actionPerformed(ActionEvent arg0) { new
-         * Thread(new Runnable() {
-         * 
-         * @Override public void run() { new SetPreferredServer(alignFrame,
-         * service.serviceType, service.action); } }).start(); } });
-         */
       }
     }
   }
@@ -481,7 +481,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
      * for moment we keep them separate.
      */
     JMenu atpoint;
-    MsaWSClient msacl = new MsaWSClient();
+
     List<String> hostLabels = new ArrayList<String>();
     Hashtable<String, String> lasthostFor = new Hashtable<String, String>();
     Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
@@ -590,6 +590,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
             new PropertyChangeListener()
             {
 
+              @Override
               public void propertyChange(PropertyChangeEvent evt)
               {
                 if (getDiscoverer().services != null)
@@ -765,6 +766,22 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     return true;
   }
 
+  public boolean restart()
+  {
+    synchronized (this)
+    {
+      if (running)
+      {
+        aborted = true;
+      }
+      else
+      {
+        running = true;
+      }
+      return aborted;
+    }
+  }
+
   /**
    * Start a fresh discovery thread and notify the given object when we're
    * finished. Any known existing threads will be killed before this one is
@@ -775,6 +792,16 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    */
   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
   {
+    /*    if (restart())
+        {
+          return;
+        }
+        else
+        {
+          Thread thr = new Thread(this);
+          thr.start();
+        }
+       */
     if (isRunning())
     {
       setAborted(true);
index e425624..2187f46 100644 (file)
@@ -26,7 +26,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
@@ -961,7 +961,7 @@ class MsaWSThread extends AWS2Thread implements WSClientI
       orders[j] = null;
     }
     SequenceI[] alignment = (SequenceI[]) newview[0];
-    ColumnSelection columnselection = (ColumnSelection) newview[1];
+    HiddenColumns hidden = (HiddenColumns) newview[1];
     Alignment al = new Alignment(alignment);
     // TODO: add 'provenance' property to alignment from the method notes
     if (lastProgram != null)
@@ -979,7 +979,7 @@ class MsaWSThread extends AWS2Thread implements WSClientI
 
     if (newFrame)
     {
-      displayInNewFrame(al, alorders, columnselection);
+      displayInNewFrame(al, alorders, hidden);
 
     }
     else
@@ -1000,9 +1000,9 @@ class MsaWSThread extends AWS2Thread implements WSClientI
    * @param columnselection
    */
   protected void displayInNewFrame(AlignmentI al,
-          List<AlignmentOrder> alorders, ColumnSelection columnselection)
+          List<AlignmentOrder> alorders, HiddenColumns hidden)
   {
-    AlignFrame af = new AlignFrame(al, columnselection,
+    AlignFrame af = new AlignFrame(al, hidden,
             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 
     // initialise with same renderer settings as in parent alignframe.
index 7c8395f..671b5f1 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.ws.jws2.jabaws2;
 
 import jalview.ws.jws2.AAConClient;
-import jalview.ws.jws2.JPred301Client;
 import jalview.ws.jws2.RNAalifoldClient;
 import jalview.ws.uimodel.AlignAnalysisUIText;
 
@@ -51,11 +50,8 @@ public class Jws2InstanceFactory
               AAConClient.getAlignAnalysisUITest());
       aaConGUI.put(compbio.ws.client.Services.RNAalifoldWS.toString(),
               RNAalifoldClient.getAlignAnalysisUITest());
-      // disable the JPred301 client in jalview ...
+      // ignore list for JABAWS services not supported in jalview ...
       ignoreGUI = new HashSet<String>();
-      ignoreGUI.add(compbio.ws.client.Services.JpredWS.toString());
-      aaConGUI.put(compbio.ws.client.Services.JpredWS.toString(),
-              JPred301Client.getAlignAnalysisUITest());
     }
   }
 
index 7fbae89..27f5271 100644 (file)
@@ -259,7 +259,8 @@ public class RestClient extends WSClient implements WSClientI,
         {
           // intersect groups with selected region
           _input = new AlignmentView(av.getAlignment(),
-                  av.getColumnSelection(), av.getSelectionGroup(),
+ av.getAlignment()
+                  .getHiddenColumns(), av.getSelectionGroup(),
                   av.hasHiddenColumns(), true, true);
           viewTitle = MessageManager.formatMessage(
                   "label.select_visible_region_of",
@@ -272,7 +273,8 @@ public class RestClient extends WSClient implements WSClientI,
         {
           // use selected region to partition alignment
           _input = new AlignmentView(av.getAlignment(),
-                  av.getColumnSelection(), av.getSelectionGroup(),
+ av.getAlignment()
+                  .getHiddenColumns(), av.getSelectionGroup(),
                   av.hasHiddenColumns(), false, true);
         }
         viewTitle = MessageManager.formatMessage(
@@ -286,7 +288,8 @@ public class RestClient extends WSClient implements WSClientI,
       {
         // just take selected region intersection
         _input = new AlignmentView(av.getAlignment(),
-                av.getColumnSelection(), av.getSelectionGroup(),
+ av.getAlignment()
+                .getHiddenColumns(), av.getSelectionGroup(),
                 av.hasHiddenColumns(), true, true);
         viewTitle = MessageManager.formatMessage(
                 "label.select_visible_region_of",
@@ -300,7 +303,8 @@ public class RestClient extends WSClient implements WSClientI,
     {
       // standard alignment view without selection present
       _input = new AlignmentView(av.getAlignment(),
-              av.getColumnSelection(), null, av.hasHiddenColumns(), false,
+ av.getAlignment()
+              .getHiddenColumns(), null, av.hasHiddenColumns(), false,
               true);
       viewTitle = ""
               + (av.hasHiddenColumns() ? (new StringBuffer(" ")
index 75d2cd4..0592426 100644 (file)
@@ -26,7 +26,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
@@ -173,9 +173,13 @@ public class RestJobThread extends AWSThread
   private String getStage(Stage stg)
   {
     if (stg == Stage.SUBMIT)
+    {
       return "submitting ";
+    }
     if (stg == Stage.POLL)
+    {
       return "checking status of ";
+    }
 
     return (" being confused about ");
   }
@@ -609,7 +613,7 @@ public class RestJobThread extends AWSThread
     // total number of distinct alignment sets generated by job set.
     int numAlSets = 0, als = 0;
     List<AlignmentI> destAls = new ArrayList<AlignmentI>();
-    List<jalview.datamodel.ColumnSelection> destColsel = new ArrayList<jalview.datamodel.ColumnSelection>();
+    List<jalview.datamodel.HiddenColumns> destColsel = new ArrayList<jalview.datamodel.HiddenColumns>();
     List<List<NewickFile>> trees = new ArrayList<List<NewickFile>>();
 
     do
@@ -715,7 +719,7 @@ public class RestJobThread extends AWSThread
           RestJob rj = (RestJob) jobs[nrj];
           int contigs[] = input.getVisibleContigs();
           AlignmentI destAl = null;
-          jalview.datamodel.ColumnSelection destCs = null;
+          jalview.datamodel.HiddenColumns destHCs = null;
           // Resolve destAl for this data.
           if (als == 0 && rj.isInputContextModified())
           {
@@ -731,7 +735,7 @@ public class RestJobThread extends AWSThread
               if (!restClient.isAlignmentModified() && merge)
               {
                 destAl = restClient.av.getAlignment();
-                destCs = restClient.av.getColumnSelection();
+                destHCs = restClient.av.getAlignment().getHiddenColumns();
                 resultDest
                         .add(restClient.isShowResultsInNewView() ? AddDataTo.newView
                                 : AddDataTo.currentView);
@@ -742,15 +746,15 @@ public class RestJobThread extends AWSThread
                 newAlignment = true;
                 // recreate the input alignment data
                 Object[] idat = input
-                        .getAlignmentAndColumnSelection(gapCharacter);
+                        .getAlignmentAndHiddenColumns(gapCharacter);
                 destAl = new Alignment((SequenceI[]) idat[0]);
-                destCs = (ColumnSelection) idat[1];
+                destHCs = (HiddenColumns) idat[1];
                 resultDest.add(AddDataTo.newAlignment);
                 // but do not add to the alignment panel list - since we need to
                 // create a whole new alignFrame set.
               }
               destAls.add(destAl);
-              destColsel.add(destCs);
+              destColsel.add(destHCs);
             }
           }
           else
@@ -769,7 +773,7 @@ public class RestJobThread extends AWSThread
               // recover reference to last alignment created for this rest frame
               // ready for extension
               destAl = destAls.get(als);
-              destCs = destColsel.get(als);
+              destHCs = destColsel.get(als);
             }
             else
             {
@@ -798,12 +802,12 @@ public class RestJobThread extends AWSThread
                 newview = input.getUpdatedView(rseqs, orders, gapCharacter);
               }
               destAl = new Alignment((SequenceI[]) newview[0]);
-              destCs = (ColumnSelection) newview[1];
+              destHCs = (HiddenColumns) newview[1];
               newAlignment = true;
               // TODO create alignment from result data with propagated
               // references.
               destAls.add(destAl);
-              destColsel.add(destCs);
+              destColsel.add(destHCs);
               resultDest.add(AddDataTo.newAlignment);
               throw new Error(
                       MessageManager
@@ -1043,7 +1047,7 @@ public class RestJobThread extends AWSThread
     for (AddDataTo action : resultDest)
     {
       AlignmentI destal;
-      ColumnSelection destcs;
+      HiddenColumns destcs;
       String alTitle = MessageManager.formatMessage(
               "label.webservice_job_title_on", new String[] {
                   restClient.service.details.Action,
index 8bae5ba..2ff4a8b 100644 (file)
@@ -21,6 +21,8 @@
 package jalview.ws.sifts;
 
 import jalview.analysis.AlignSeq;
+import jalview.analysis.scoremodels.ScoreMatrix;
+import jalview.analysis.scoremodels.ScoreModels;
 import jalview.api.DBRefEntryI;
 import jalview.api.SiftsClientI;
 import jalview.datamodel.DBRefEntry;
@@ -74,6 +76,12 @@ import MCview.PDBChain;
 
 public class SiftsClient implements SiftsClientI
 {
+  /*
+   * for use in mocking out file fetch for tests only
+   * - reset to null after testing!
+   */
+  private static File mockSiftsFile;
+
   private Entry siftsEntry;
 
   private StructureFile pdb;
@@ -187,6 +195,14 @@ public class SiftsClient implements SiftsClientI
    */
   public static File getSiftsFile(String pdbId) throws SiftsException
   {
+    /*
+     * return mocked file if it has been set
+     */
+    if (mockSiftsFile != null)
+    {
+      return mockSiftsFile;
+    }
+
     String siftsFileName = SiftsSettings.getSiftDownloadDirectory()
             + pdbId.toLowerCase() + ".xml.gz";
     File siftsFile = new File(siftsFileName);
@@ -561,18 +577,8 @@ public class SiftsClient implements SiftsClientI
                   .equalsIgnoreCase(seqCoordSys.getName())
                   && isAccessionMatched(cRefDb.getDbAccessionId()))
           {
-            String resNumIndexString = cRefDb.getDbResNum()
-                    .equalsIgnoreCase("None") ? String.valueOf(UNASSIGNED)
-                    : cRefDb.getDbResNum();
-            try
-            {
-              currSeqIndex = Integer.valueOf(resNumIndexString);
-            } catch (NumberFormatException nfe)
-            {
-              currSeqIndex = Integer.valueOf(resNumIndexString
-                      .split("[a-zA-Z]")[0]);
-              continue;
-            }
+            currSeqIndex = getLeadingIntegerValue(
+                    cRefDb.getDbResNum(), UNASSIGNED);
             if (pdbRefDb != null)
             {
               break;// exit loop if pdb and uniprot are already found
@@ -585,19 +591,11 @@ public class SiftsClient implements SiftsClientI
         }
         if (currSeqIndex >= seq.getStart() && currSeqIndex <= seq.getEnd())
         {
-          int resNum;
-          try
-          {
-            resNum = (pdbRefDb == null) ? Integer.valueOf(residue
-                    .getDbResNum()) : Integer.valueOf(pdbRefDb
-                    .getDbResNum());
-          } catch (NumberFormatException nfe)
-          {
-            resNum = (pdbRefDb == null) ? Integer.valueOf(residue
-                    .getDbResNum()) : Integer.valueOf(pdbRefDb
-                    .getDbResNum().split("[a-zA-Z]")[0]);
-            continue;
-          }
+
+          int resNum = (pdbRefDb == null) ? getLeadingIntegerValue(
+                  residue.getDbResNum(), UNASSIGNED)
+                  : getLeadingIntegerValue(pdbRefDb.getDbResNum(),
+                          UNASSIGNED);
 
           if (isResidueObserved(residue)
                   || seqCoordSys == CoordinateSys.UNIPROT)
@@ -620,6 +618,30 @@ public class SiftsClient implements SiftsClientI
   }
 
   /**
+   * Get the leading integer part of a string that begins with an integer.
+   * 
+   * @param input
+   *          - the string input to process
+   * @param failValue
+   *          - value returned if unsuccessful
+   * @return
+   */
+  static int getLeadingIntegerValue(String input, int failValue)
+  {
+    if (input == null)
+    {
+      return failValue;
+    }
+    String[] parts = input.split("(?=\\D)(?<=\\d)");
+    if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
+    {
+      return Integer.valueOf(parts[0]);
+    }
+    return failValue;
+  }
+
+
+  /**
    * 
    * @param chainId
    *          Target chain to populate mapping of its atom positions.
@@ -943,7 +965,7 @@ public class SiftsClient implements SiftsClientI
   }
 
   @Override
-  public StringBuffer getMappingOutput(MappingOutputPojo mp)
+  public StringBuilder getMappingOutput(MappingOutputPojo mp)
           throws SiftsException
   {
     String seqRes = mp.getSeqResidue();
@@ -965,7 +987,7 @@ public class SiftsClient implements SiftsClientI
     int nochunks = ((seqRes.length()) / len)
             + ((seqRes.length()) % len > 0 ? 1 : 0);
     // output mappings
-    StringBuffer output = new StringBuffer();
+    StringBuilder output = new StringBuilder(512);
     output.append(NEWLINE);
     output.append("Sequence \u27f7 Structure mapping details").append(
             NEWLINE);
@@ -986,6 +1008,7 @@ public class SiftsClient implements SiftsClientI
     output.append(String.valueOf(pdbEnd));
     output.append(NEWLINE).append(NEWLINE);
 
+    ScoreMatrix pam250 = ScoreModels.getInstance().getPam250();
     int matchedSeqCount = 0;
     for (int j = 0; j < nochunks; j++)
     {
@@ -1004,27 +1027,29 @@ public class SiftsClient implements SiftsClientI
       output.append(NEWLINE);
       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
 
-      // Print out the matching chars
+      /*
+       * Print out the match symbols:
+       * | for exact match (ignoring case)
+       * . if PAM250 score is positive
+       * else a space
+       */
       for (int i = 0; i < len; i++)
       {
         try
         {
           if ((i + (j * len)) < seqRes.length())
           {
-            boolean sameChar = Comparison.isSameResidue(
-                    seqRes.charAt(i + (j * len)),
-                    strRes.charAt(i + (j * len)), false);
-            if (sameChar
-                    && !jalview.util.Comparison.isGap(seqRes.charAt(i
-                            + (j * len))))
+            char c1 = seqRes.charAt(i + (j * len));
+            char c2 = strRes.charAt(i + (j * len));
+            boolean sameChar = Comparison.isSameResidue(c1, c2, false);
+            if (sameChar && !Comparison.isGap(c1))
             {
               matchedSeqCount++;
               output.append("|");
             }
             else if (type.equals("pep"))
             {
-              if (ResidueProperties.getPAM250(seqRes.charAt(i + (j * len)),
-                      strRes.charAt(i + (j * len))) > 0)
+              if (pam250.getPairwiseScore(c1, c2) > 0)
               {
                 output.append(".");
               }
@@ -1097,4 +1122,9 @@ public class SiftsClient implements SiftsClientI
     return siftsEntry.getDbVersion();
   }
 
+  public static void setMockSiftsFile(File file)
+  {
+    mockSiftsFile = file;
+  }
+
 }
index 86e3f76..dcd861a 100644 (file)
@@ -47,7 +47,8 @@ public class UrlDownloadClient
    *          the name of file to save the URLs to
    * @throws IOException
    */
-  public void download(String urlstring, String outfile) throws IOException
+  public static void download(String urlstring, String outfile)
+          throws IOException
   {
     FileOutputStream fos = null;
     ReadableByteChannel rbc = null;
index 4cb5329..837e970 100644 (file)
  */
 package jalview.analysis;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
+import jalview.datamodel.Sequence;
 import jalview.gui.JvOptionPane;
 
 import org.testng.annotations.BeforeClass;
@@ -45,10 +47,32 @@ public class AlignSeqTest
     assertNull(AlignSeq.extractGaps(null, "ACG"));
     assertNull(AlignSeq.extractGaps("-. ", null));
 
-    assertEquals(" AC-G.T", AlignSeq.extractGaps("", " AC-G.T"));
-    assertEquals("AC-G.T", AlignSeq.extractGaps(" ", " AC-G.T"));
-    assertEquals("ACG.T", AlignSeq.extractGaps(" -", " AC-G.T"));
-    assertEquals("ACGT", AlignSeq.extractGaps(" -.", " AC-G.T ."));
-    assertEquals(" ACG.T", AlignSeq.extractGaps("-", " AC-G.T"));
+    assertEquals(AlignSeq.extractGaps("", " AC-G.T"), " AC-G.T");
+    assertEquals(AlignSeq.extractGaps(" ", " AC-G.T"), "AC-G.T");
+    assertEquals(AlignSeq.extractGaps(" -", " AC-G.T"), "ACG.T");
+    assertEquals(AlignSeq.extractGaps(" -.", " AC-G.T ."), "ACGT");
+    assertEquals(AlignSeq.extractGaps("-", " AC-G.T"), " ACG.T");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testIndexEncode_nucleotide()
+  {
+    AlignSeq as = new AlignSeq(new Sequence("s1", "TTAG"), new Sequence(
+            "s2", "ACGT"), AlignSeq.DNA);
+    int[] expected = new int[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+        7, 7, 8, 8, 9, 9, -1, -1, 10, -1 };
+    String s = "aAcCgGtTuUiIxXrRyYnN .-?";
+    assertArrayEquals(expected, as.indexEncode(s));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testIndexEncode_peptide()
+  {
+    AlignSeq as = new AlignSeq(new Sequence("s1", "PFY"), new Sequence(
+            "s2", "RQW"), AlignSeq.PEP);
+    int[] expected = new int[] { 0, 0, 1, 1, 2, 2, 21, 21, 22, 22, -1, 23,
+        -1, -1, -1 };
+    String s = "aArRnNzZxX *.-?";
+    assertArrayEquals(expected, as.indexEncode(s));
   }
 }
diff --git a/test/jalview/analysis/AlignmentSorterTest.java b/test/jalview/analysis/AlignmentSorterTest.java
new file mode 100644 (file)
index 0000000..088611e
--- /dev/null
@@ -0,0 +1,132 @@
+package jalview.analysis;
+
+import static org.testng.Assert.assertSame;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+
+import java.util.Arrays;
+import java.util.List;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class AlignmentSorterTest
+{
+  @Test(groups = "Functional")
+  public void testSortByFeature_score()
+  {
+    SequenceI seq1 = new Sequence("Seq1", "ABC--D-EFGHIJ");
+    SequenceI seq2 = new Sequence("Seq2", "ABCDEFGHIJ");
+    SequenceI seq3 = new Sequence("Seq3", "ABCDE-FGHIJ");
+    SequenceI seq4 = new Sequence("Seq4", "ABCDEFGHIJ");
+    SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
+    AlignmentI al = new Alignment(seqs);
+    al.setDataset(null);
+
+    /*
+     * sort with no score features does nothing
+     */
+    PA.setValue(AlignmentSorter.class, "lastSortByFeatureScore", null);
+
+    AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
+            al,
+            AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(0), seq1);
+    assertSame(al.getSequenceAt(1), seq2);
+    assertSame(al.getSequenceAt(2), seq3);
+    assertSame(al.getSequenceAt(3), seq4);
+
+    /*
+     * add score and non-score features
+     * seq1 Cath(2.0) Pfam(4.0) average 3.0
+     * seq2 Cath(2.5) Metal(NaN) average 2.5
+     * seq3 KD(-4), KD(3.0) average -0.5
+     * seq4 Helix(NaN) - should sort as if largest score
+     */
+    seq1.addSequenceFeature(new SequenceFeature("Cath", "", 2, 3, 2.0f,
+            "g1"));
+    seq1.addSequenceFeature(new SequenceFeature("Pfam", "", 4, 5, 4.0f,
+            "g2"));
+    seq2.addSequenceFeature(new SequenceFeature("Cath", "", 2, 3, 2.5f,
+            "g3"));
+    seq2.addSequenceFeature(new SequenceFeature("Metal", "", 2, 3,
+            Float.NaN, "g4"));
+    seq3.addSequenceFeature(new SequenceFeature("kD", "", 2, 3, -4f, "g5"));
+    seq3.addSequenceFeature(new SequenceFeature("kD", "", 5, 6, 3.0f, "g6"));
+    seq4.addSequenceFeature(new SequenceFeature("Helix", "", 2, 3,
+            Float.NaN, "g7"));
+
+    /*
+     * sort by ascending score, no filter on feature type or group
+     * NB sort order for the same feature set (none) gets toggled, so descending
+     */
+    PA.setValue(AlignmentSorter.class, "sortByFeatureScoreAscending", true);
+    AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
+            al, AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(3), seq3); // -0.5
+    assertSame(al.getSequenceAt(2), seq2); // 2.5
+    assertSame(al.getSequenceAt(1), seq1); // 3.0
+    assertSame(al.getSequenceAt(0), seq4); // maximum 'score'
+
+    /*
+     * repeat sort toggles order - now ascending
+     */
+    AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
+            al, AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(0), seq3); // -0.5
+    assertSame(al.getSequenceAt(1), seq2); // 2.5
+    assertSame(al.getSequenceAt(2), seq1); // 3.0
+    assertSame(al.getSequenceAt(3), seq4);
+
+    /*
+     * specify features, excluding Pfam
+     * seq1 average is now 2.0
+     * next sort is ascending (not toggled) as for a different feature set
+     */
+    List<String> types = Arrays.asList(new String[] { "Cath", "kD" });
+    AlignmentSorter.sortByFeature(types, null, 0, al.getWidth(), al,
+            AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(0), seq3); // -0.5
+    assertSame(al.getSequenceAt(1), seq1); // 2.0
+    assertSame(al.getSequenceAt(2), seq2); // 2.5
+    assertSame(al.getSequenceAt(3), seq4);
+
+    /*
+     * specify groups, excluding g5 (kD -4 score)
+     * seq3 average is now 3.0
+     * next sort is ascending (not toggled) as for a different group spec
+     */
+    List<String> groups = Arrays.asList(new String[] { "g1", "g2", "g3",
+        "g6" });
+    AlignmentSorter.sortByFeature(types, groups, 0, al.getWidth(), al,
+            AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(0), seq1); // 2.0
+    assertSame(al.getSequenceAt(1), seq2); // 2.5
+    assertSame(al.getSequenceAt(2), seq3); // 3.0
+    assertSame(al.getSequenceAt(3), seq4);
+
+    /*
+     * limit to columns 0-4, excluding 2nd feature of seq1 and seq3
+     * seq1 is now 2.0, seq3 is now -4
+     */
+    // fails because seq1.findPosition(4) returns 4
+    // although residue 4 is in column 5! - JAL-2544
+    AlignmentSorter.sortByFeature((String) null, null, 0, 4, al,
+            AlignmentSorter.FEATURE_SCORE);
+    assertSame(al.getSequenceAt(0), seq3); // -4
+    assertSame(al.getSequenceAt(1), seq1); // 2.0
+    assertSame(al.getSequenceAt(2), seq2); // 2.5
+    assertSame(al.getSequenceAt(3), seq4);
+  }
+
+  @Test(groups = "Functional")
+  public void testSortByFeature_density()
+  {
+    // TODO
+  }
+}
index 2e21d9c..d2fa99a 100644 (file)
@@ -28,7 +28,7 @@ import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignedCodon;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignViewport;
@@ -133,7 +133,7 @@ public class DnaTest
     AlignmentI alf = new FormatAdapter().readFile(
             JAL_1312_example_align_fasta, DataSourceType.PASTE,
             FileFormat.Fasta);
-    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
     Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
     AlignmentI translated = dna.translateCdna();
@@ -157,7 +157,7 @@ public class DnaTest
     int vwidth = 15;
     for (int ipos = 0; ipos + vwidth < alf.getWidth(); ipos += vwidth)
     {
-      ColumnSelection cs = new ColumnSelection();
+      HiddenColumns cs = new HiddenColumns();
       if (ipos > 0)
       {
         cs.hideColumns(0, ipos - 1);
@@ -188,7 +188,7 @@ public class DnaTest
   {
     AlignmentI alf = new FormatAdapter().readFile(fasta,
             DataSourceType.PASTE, FileFormat.Fasta);
-    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
     Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
     AlignmentI translated = dna.translateCdna();
@@ -208,7 +208,7 @@ public class DnaTest
   {
     AlignmentI alf = new FormatAdapter().readFile(fasta,
             DataSourceType.PASTE, FileFormat.Fasta);
-    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(6, 14); // hide codons 3/4/5
     cs.hideColumns(24, 35); // hide codons 9-12
     cs.hideColumns(177, 191); // hide codons 60-64
@@ -296,7 +296,7 @@ public class DnaTest
      */
     AlignmentI cdna = new AlignmentGenerator(true)
             .generate(12, 8, 97, 5, 5);
-    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(cdna, cs);
     Dna dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 });
     AlignmentI translated = dna.translateCdna();
@@ -542,7 +542,7 @@ public class DnaTest
     assertEquals(seqDs, al.getSequenceAt(0).getDatasetSequence()
             .getSequenceAsString());
 
-    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(al, cs);
     Dna testee = new Dna(av, new int[] { 0, al.getWidth() - 1 });
     AlignmentI reversed = testee.reverseCdna(false);
index 9fc88ea..70e59c5 100644 (file)
@@ -125,7 +125,7 @@ public class TestAlignSeq
     };
 
     as.printAlignment(ps);
-    String expected = "Score = 320\nLength of alignment = 10\nSequence Seq1 :  3 - 18 (Sequence length = 14)\nSequence Seq1 :  1 - 10 (Sequence length = 10)\n\n"
+    String expected = "Score = 320.0\nLength of alignment = 10\nSequence Seq1 :  3 - 18 (Sequence length = 14)\nSequence Seq1 :  1 - 10 (Sequence length = 10)\n\n"
             + "Seq1 SDFAQQQRRR\n"
             + "     |||||||   \n"
             + "Seq1 SDFAQQQSSS\n\n" + "Percentage ID = 70.00\n";
  */
 package jalview.analysis.scoremodels;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
+import jalview.math.MatrixI;
 
 import java.util.Arrays;
 
@@ -34,7 +43,7 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-public class FeatureScoreModelTest
+public class FeatureDistanceModelTest
 {
 
   @BeforeClass(alwaysRun = true)
@@ -52,6 +61,17 @@ public class FeatureScoreModelTest
 
   int[] sf3 = new int[] { -1, -1, -1, -1, -1, -1, 76, 77 };
 
+  /**
+   * <pre>
+   * Load test alignment and add features to sequences: 
+   *      FER1_MESCR FER1_SPIOL FER3_RAPSA FER1_MAIZE 
+   *  sf1     X          X          X  
+   *  sf2                X                     X 
+   *  sf3                                      X
+   * </pre>
+   * 
+   * @return
+   */
   public AlignFrame getTestAlignmentFrame()
   {
     AlignFrame alf = new FileLoader(false).LoadFileWaitTillLoaded(
@@ -85,7 +105,7 @@ public class FeatureScoreModelTest
     alf.getFeatureRenderer().findAllFeatures(true);
     Assert.assertEquals(alf.getFeatureRenderer().getDisplayedFeatureTypes()
             .size(), 3, "Number of feature types");
-    Assert.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+    assertTrue(alf.getCurrentView().areFeaturesDisplayed());
     return alf;
   }
 
@@ -93,15 +113,17 @@ public class FeatureScoreModelTest
   public void testFeatureScoreModel() throws Exception
   {
     AlignFrame alf = getTestAlignmentFrame();
-    FeatureScoreModel fsm = new FeatureScoreModel();
-    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+    FeatureDistanceModel fsm = new FeatureDistanceModel();
+    assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
             .getAlignPanel()));
     alf.selectAllSequenceMenuItem_actionPerformed(null);
-    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
-            true));
-    Assert.assertTrue(dm[0][2] == 0f,
+
+    MatrixI dm = fsm.findDistances(
+            alf.getViewport().getAlignmentView(true),
+            SimilarityParams.Jalview);
+    assertEquals(dm.getValue(0, 2), 0d,
             "FER1_MESCR (0) should be identical with RAPSA (2)");
-    Assert.assertTrue(dm[0][1] > dm[0][2],
+    assertTrue(dm.getValue(0, 1) > dm.getValue(0, 2),
             "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
   }
 
@@ -111,15 +133,16 @@ public class FeatureScoreModelTest
     AlignFrame alf = getTestAlignmentFrame();
     // hiding first two columns shouldn't affect the tree
     alf.getViewport().hideColumns(0, 1);
-    FeatureScoreModel fsm = new FeatureScoreModel();
-    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+    FeatureDistanceModel fsm = new FeatureDistanceModel();
+    assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
             .getAlignPanel()));
     alf.selectAllSequenceMenuItem_actionPerformed(null);
-    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
-            true));
-    Assert.assertTrue(dm[0][2] == 0f,
+    MatrixI dm = fsm.findDistances(
+            alf.getViewport().getAlignmentView(true),
+            SimilarityParams.Jalview);
+    assertEquals(dm.getValue(0, 2), 0d,
             "FER1_MESCR (0) should be identical with RAPSA (2)");
-    Assert.assertTrue(dm[0][1] > dm[0][2],
+    assertTrue(dm.getValue(0, 1) > dm.getValue(0, 2),
             "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
   }
 
@@ -130,21 +153,24 @@ public class FeatureScoreModelTest
     // hide columns and check tree changes
     alf.getViewport().hideColumns(3, 4);
     alf.getViewport().hideColumns(0, 1);
-    FeatureScoreModel fsm = new FeatureScoreModel();
-    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+    FeatureDistanceModel fsm = new FeatureDistanceModel();
+    assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
             .getAlignPanel()));
     alf.selectAllSequenceMenuItem_actionPerformed(null);
-    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
-            true));
-    Assert.assertTrue(
-            dm[0][2] == 0f,
+    MatrixI dm = fsm.findDistances(
+            alf.getViewport().getAlignmentView(true),
+            SimilarityParams.Jalview);
+    assertEquals(
+            dm.getValue(0, 2),
+            0d,
             "After hiding last two columns FER1_MESCR (0) should still be identical with RAPSA (2)");
-    Assert.assertTrue(
-            dm[0][1] == 0f,
+    assertEquals(
+            dm.getValue(0, 1),
+            0d,
             "After hiding last two columns FER1_MESCR (0) should now also be identical with SPIOL (1)");
     for (int s = 0; s < 3; s++)
     {
-      Assert.assertTrue(dm[s][3] > 0f, "After hiding last two columns "
+      assertTrue(dm.getValue(s, 3) > 0d, "After hiding last two columns "
               + alf.getViewport().getAlignment().getSequenceAt(s).getName()
               + "(" + s + ") should still be distinct from FER1_MAIZE (3)");
     }
@@ -165,7 +191,7 @@ public class FeatureScoreModelTest
     SequenceFeature sf = null;
     sf = new SequenceFeature("disulphide bond", "", 2, 5, Float.NaN, "");
     aseq.addSequenceFeature(sf);
-    Assert.assertTrue(sf.isContactFeature());
+    assertTrue(sf.isContactFeature());
     af.refreshFeatureUI(true);
     af.getFeatureRenderer().setAllVisible(Arrays.asList("disulphide bond"));
     Assert.assertEquals(af.getFeatureRenderer().getDisplayedFeatureTypes()
@@ -190,4 +216,135 @@ public class FeatureScoreModelTest
             .size(), 0);
   }
 
+  @Test(groups = { "Functional" })
+  public void testFindDistances() throws Exception
+  {
+    String seqs = ">s1\nABCDE\n>seq2\nABCDE\n";
+    AlignFrame alf = new FileLoader().LoadFileWaitTillLoaded(seqs,
+            DataSourceType.PASTE);
+    SequenceI s1 = alf.getViewport().getAlignment().getSequenceAt(0);
+    SequenceI s2 = alf.getViewport().getAlignment().getSequenceAt(1);
+
+    /*
+     * set domain and variant features thus:
+     *     ----5
+     *  s1 ddd..
+     *  s1 .vvv.
+     *  s1 ..vvv    
+     *  s2 .ddd. 
+     *  s2 vv..v
+     *  The number of unshared feature types per column is
+     *     20120 (two features of the same type doesn't affect score)
+     *  giving an average (pairwise distance) of 5/5 or 1.0 
+     */
+    s1.addSequenceFeature(new SequenceFeature("domain", null, 1, 3, 0f,
+            null));
+    s1.addSequenceFeature(new SequenceFeature("variant", null, 2, 4, 0f,
+            null));
+    s1.addSequenceFeature(new SequenceFeature("variant", null, 3, 5, 0f,
+            null));
+    s2.addSequenceFeature(new SequenceFeature("domain", null, 2, 4, 0f,
+            null));
+    s2.addSequenceFeature(new SequenceFeature("variant", null, 1, 2, 0f,
+            null));
+    s2.addSequenceFeature(new SequenceFeature("variant", null, 5, 5, 0f,
+            null));
+    alf.setShowSeqFeatures(true);
+    alf.getFeatureRenderer().findAllFeatures(true);
+
+    FeatureDistanceModel fsm = new FeatureDistanceModel();
+    assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+            .getAlignPanel()));
+    alf.selectAllSequenceMenuItem_actionPerformed(null);
+
+    MatrixI distances = fsm.findDistances(alf.getViewport()
+            .getAlignmentView(true), SimilarityParams.Jalview);
+    assertEquals(distances.width(), 2);
+    assertEquals(distances.height(), 2);
+    assertEquals(distances.getValue(0, 0), 0d);
+    assertEquals(distances.getValue(1, 1), 0d);
+
+    assertEquals(distances.getValue(0, 1), 1d,
+            "expected identical pairs. (check normalisation for similarity score)");
+    assertEquals(distances.getValue(1, 0), 1d);
+  }
+
+  /**
+   * Verify computed distances with varying parameter options
+   */
+  @Test(groups = "Functional")
+  public void testFindDistances_withParams()
+  {
+    AlignFrame af = setupAlignmentView();
+    AlignViewport viewport = af.getViewport();
+    AlignmentView view = viewport.getAlignmentView(false);
+
+    FeatureDistanceModel sm = new FeatureDistanceModel();
+    sm.configureFromAlignmentView(af.alignPanel);
+  
+    /*
+     * feature distance model always normalises by region width
+     * gap-gap is always included (but scores zero)
+     * the only variable parameter is 'includeGaps'
+     */
+
+    /*
+     * include gaps
+     * score = 3 + 3 + 0 + 2 + 3 + 2 = 13/6
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true, true);
+    MatrixI distances = sm.findDistances(view, params);
+    assertEquals(distances.getValue(0, 0), 0d);
+    assertEquals(distances.getValue(1, 1), 0d);
+    assertEquals(distances.getValue(0, 1), 13d / 6); // should be 13d/6
+    assertEquals(distances.getValue(1, 0), 13d / 6);
+  
+    /*
+     * exclude gaps
+     * score = 3 + 3 + 0 + 0 + 0 + 0 = 6/6
+     */
+    params = new SimilarityParams(true, true, false, true);
+    distances = sm.findDistances(view, params);
+    assertEquals(distances.getValue(0, 1), 6d / 6);// should be 6d/6
+  }
+
+  /**
+   * <pre>
+   * Set up
+   *   column      1 2 3 4 5 6
+   *        seq s1 F R - K - S
+   *        seq s2 F S - - L
+   *   s1 chain    c c   c   c
+   *   s1 domain   d d   d   d
+   *   s2 chain    c c     c
+   *   s2 metal    m m     m
+   *   s2 Pfam     P P     P
+   *      scores:  3 3 0 2 3 2
+   * </pre>
+   * 
+   * @return
+   */
+  protected AlignFrame setupAlignmentView()
+  {
+    /*
+     * for now, using space for gap to match callers of
+     * AlignmentView.getSequenceStrings()
+     * may change this to '-' (with corresponding change to matrices)
+     */
+    SequenceI s1 = new Sequence("s1", "FR K S");
+    SequenceI s2 = new Sequence("s2", "FS  L");
+
+    s1.addSequenceFeature(new SequenceFeature("chain", null, 1, 4, 0f, null));
+    s1.addSequenceFeature(new SequenceFeature("domain", null, 1, 4, 0f,
+            null));
+    s2.addSequenceFeature(new SequenceFeature("chain", null, 1, 3, 0f, null));
+    s2.addSequenceFeature(new SequenceFeature("metal", null, 1, 3, 0f, null));
+    s2.addSequenceFeature(new SequenceFeature("Pfam", null, 1, 3, 0f, null));
+    AlignmentI al = new Alignment(new SequenceI[] { s1, s2 });
+    AlignFrame af = new AlignFrame(al, 300, 300);
+    af.setShowSeqFeatures(true);
+    af.getFeatureRenderer().findAllFeatures(true);
+    return af;
+  }
+
 }
diff --git a/test/jalview/analysis/scoremodels/PIDModelTest.java b/test/jalview/analysis/scoremodels/PIDModelTest.java
new file mode 100644 (file)
index 0000000..212f825
--- /dev/null
@@ -0,0 +1,176 @@
+package jalview.analysis.scoremodels;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.util.Comparison;
+
+import org.testng.annotations.Test;
+
+public class PIDModelTest
+{
+  private static final double DELTA = 0.00001D;
+
+  @Test(groups = "Functional")
+  public void testGetPairwiseScore()
+  {
+    PIDModel sm = new PIDModel();
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
+    assertEquals(sm.getPairwiseScore('a', 'A'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'B'), 0f);
+    assertEquals(sm.getPairwiseScore('A', ' '), 0f);
+    assertEquals(sm.getPairwiseScore(' ', ' '), 0f);
+    assertEquals(sm.getPairwiseScore('.', '.'), 0f);
+    assertEquals(sm.getPairwiseScore('-', '-'), 0f);
+  }
+
+  /**
+   * Regression test to verify that a (suitably configured) PIDModel computes
+   * the same percentage identities as the Comparison.PID method
+   */
+  @Test(groups = "Functional")
+  public void testComputePID_matchesComparisonPID()
+  {
+    SimilarityParamsI params = new SimilarityParams(true, true, true, true);
+
+    /*
+     * same length, no gaps
+     */
+    String s1 = "ARFNQDWSGI";
+    String s2 = "ARKNQDQSGI";
+
+    new PIDModel();
+    double newScore = PIDModel.computePID(s1, s2, params);
+    double oldScore = Comparison.PID(s1, s2);
+    assertEquals(newScore, oldScore, DELTA);
+
+    /*
+     * same length, with gaps
+     */
+    s1 = "-RFNQDWSGI";
+    s2 = "ARKNQ-QSGI";
+    new PIDModel();
+    newScore = PIDModel.computePID(s1, s2, params);
+    oldScore = Comparison.PID(s1, s2);
+    assertEquals(newScore, oldScore, DELTA);
+
+    /*
+     * s2 longer than s1, with gaps
+     */
+    s1 = "ARK-";
+    s2 = "-RFNQ";
+    new PIDModel();
+    newScore = PIDModel.computePID(s1, s2, params);
+    oldScore = Comparison.PID(s1, s2);
+    assertEquals(newScore, oldScore, DELTA);
+
+    /*
+     * s1 longer than s2, with gaps
+     */
+    s1 = "-RFNQ";
+    s2 = "ARK-";
+    new PIDModel();
+    newScore = PIDModel.computePID(s1, s2, params);
+    oldScore = Comparison.PID(s1, s2);
+    assertEquals(newScore, oldScore, DELTA);
+
+    /*
+     * same but now also with gapped columns
+     */
+    s1 = "-R-F-NQ";
+    s2 = "AR-K--";
+    new PIDModel();
+    newScore = PIDModel.computePID(s1, s2, params);
+    oldScore = Comparison.PID(s1, s2);
+    assertEquals(newScore, oldScore, DELTA);
+  }
+
+  /**
+   * Tests for percentage identity variants where only the shorter length of two
+   * sequences is used
+   */
+  @Test(groups = "Functional")
+  public void testComputePID_matchShortestSequence()
+  {
+    String s1 = "FR-K-S";
+    String s2 = "FS--L";
+
+    /*
+     * match gap-gap and gap-char
+     * PID = 4/5 = 80%
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true, true);
+    assertEquals(PIDModel.computePID(s1, s2, params), 80d);
+
+    /*
+     * match gap-char but not gap-gap
+     * PID = 3/4 = 75%
+     */
+    params = new SimilarityParams(false, true, true, true);
+    assertEquals(PIDModel.computePID(s1, s2, params), 75d);
+
+    /*
+     * include gaps but don't match them
+     * include gap-gap, counted as identity
+     * PID = 2/5 = 40%
+     */
+    params = new SimilarityParams(true, false, true, true);
+    assertEquals(PIDModel.computePID(s1, s2, params), 40d);
+
+    /*
+     * include gaps but don't match them
+     * exclude gap-gap
+     * PID = 1/4 = 25%
+     */
+    params = new SimilarityParams(false, false, true, true);
+    assertEquals(PIDModel.computePID(s1, s2, params), 25d);
+  }
+
+  /**
+   * Tests for percentage identity variants where the longer length of two
+   * sequences is used
+   */
+  @Test(groups = "Functional")
+  public void testComputePID_matchLongestSequence()
+  {
+    String s1 = "FR-K-S";
+    String s2 = "FS--L";
+  
+    /*
+     * match gap-gap and gap-char
+     * shorter sequence treated as if with trailing gaps
+     * PID = 5/6 = 83.333...%
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true, false);
+    assertEquals(PIDModel.computePID(s1, s2, params), 500d / 6);
+  
+    /*
+     * match gap-char but not gap-gap
+     * PID = 4/5 = 80%
+     */
+    params = new SimilarityParams(false, true, true, false);
+    assertEquals(PIDModel.computePID(s1, s2, params), 80d);
+  
+    /*
+     * include gaps but don't match them
+     * include gap-gap, counted as identity
+     * PID = 2/6 = 33.333...%
+     */
+    params = new SimilarityParams(true, false, true, false);
+    assertEquals(PIDModel.computePID(s1, s2, params), 100d / 3);
+  
+    /*
+     * include gaps but don't match them
+     * exclude gap-gap
+     * PID = 1/5 = 25%
+     */
+    params = new SimilarityParams(false, false, true, false);
+    assertEquals(PIDModel.computePID(s1, s2, params), 20d);
+
+    /*
+     * no tests for matchGaps=true, includeGaps=false
+     * as it don't make sense
+     */
+  }
+}
diff --git a/test/jalview/analysis/scoremodels/ScoreMatrixTest.java b/test/jalview/analysis/scoremodels/ScoreMatrixTest.java
new file mode 100644 (file)
index 0000000..1a5d43c
--- /dev/null
@@ -0,0 +1,588 @@
+package jalview.analysis.scoremodels;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
+
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.io.DataSourceType;
+import jalview.io.FileParse;
+import jalview.io.ScoreMatrixFile;
+import jalview.math.MatrixI;
+import jalview.schemes.ResidueProperties;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Arrays;
+
+import org.testng.annotations.Test;
+
+public class ScoreMatrixTest
+{
+  @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    // note score matrix does not have to be symmetric (though it should be!)
+    float[][] scores = new float[3][];
+    scores[0] = new float[] { 1f, 2f, 3f };
+    scores[1] = new float[] { -4f, 5f, 6f };
+    scores[2] = new float[] { 7f, 8f, 9f };
+    ScoreMatrix sm = new ScoreMatrix("Test", "ABC".toCharArray(), scores);
+    assertEquals(sm.getSize(), 3);
+    assertArrayEquals(scores, sm.getMatrix());
+    assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
+    assertEquals(sm.getPairwiseScore('b', 'c'), 6f);
+    assertEquals(sm.getPairwiseScore('c', 'b'), 8f);
+    assertEquals(sm.getMatrixIndex('c'), 2);
+    assertEquals(sm.getMatrixIndex(' '), -1);
+
+    // substitution to or from unknown symbol gets minimum score
+    assertEquals(sm.getPairwiseScore('A', 'D'), -4f);
+    assertEquals(sm.getPairwiseScore('D', 'A'), -4f);
+    // unknown-to-self gets a score of 1
+    assertEquals(sm.getPairwiseScore('D', 'D'), 1f);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testConstructor_matrixTooSmall()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 3f, 4f };
+    new ScoreMatrix("Test", "ABC".toCharArray(), scores);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testConstructor_matrixTooBig()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 3f, 4f };
+    new ScoreMatrix("Test", "A".toCharArray(), scores);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testConstructor_matrixNotSquare()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 3f };
+    new ScoreMatrix("Test", "AB".toCharArray(), scores);
+  }
+
+  @Test(groups = "Functional")
+  public void testBuildSymbolIndex()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 3f, 4f };
+    ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '.' },
+            scores);
+    short[] index = sm.buildSymbolIndex("AX-yxYp".toCharArray());
+
+    assertEquals(index.length, 128); // ASCII character set size
+
+    assertEquals(index['A'], 0);
+    assertEquals(index['a'], 0); // lower-case mapping added
+    assertEquals(index['X'], 1);
+    assertEquals(index['-'], 2);
+    assertEquals(index['y'], 3); // lower-case override
+    assertEquals(index['x'], 4); // lower-case override
+    assertEquals(index['Y'], 5);
+    assertEquals(index['p'], 6);
+    assertEquals(index['P'], -1); // lower-case doesn't map upper-case
+
+    /*
+     * check all unmapped symbols have index for unmapped
+     */
+    for (int c = 0; c < index.length; c++)
+    {
+      if (!"AaXx-. Yyp".contains(String.valueOf((char) c)))
+      {
+        assertEquals(index[c], -1);
+      }
+    }
+  }
+
+  /**
+   * check that characters not in the basic ASCII set are simply ignored
+   */
+  @Test(groups = "Functional")
+  public void testBuildSymbolIndex_nonAscii()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 3f, 4f };
+    ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '.' },
+            scores);
+    char[] weird = new char[] { 128, 245, 'P' };
+    short[] index = sm.buildSymbolIndex(weird);
+    assertEquals(index.length, 128);
+    assertEquals(index['P'], 2);
+    assertEquals(index['p'], 2);
+    for (int c = 0; c < index.length; c++)
+    {
+      if (c != 'P' && c != 'p')
+      {
+        assertEquals(index[c], -1);
+      }
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMatrix()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    float[][] m = sm.getMatrix();
+    assertEquals(m.length, sm.getSize());
+    assertEquals(m[2][4], -3f);
+    // verify a defensive copy is returned
+    float[][] m2 = sm.getMatrix();
+    assertNotSame(m, m2);
+    assertTrue(Arrays.deepEquals(m, m2));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMatrixIndex()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMatrixIndex('A'), 0);
+    assertEquals(sm.getMatrixIndex('R'), 1);
+    assertEquals(sm.getMatrixIndex('r'), 1);
+    assertEquals(sm.getMatrixIndex('N'), 2);
+    assertEquals(sm.getMatrixIndex('D'), 3);
+    assertEquals(sm.getMatrixIndex('X'), 22);
+    assertEquals(sm.getMatrixIndex('x'), 22);
+    assertEquals(sm.getMatrixIndex('-'), -1);
+    assertEquals(sm.getMatrixIndex('*'), 23);
+    assertEquals(sm.getMatrixIndex('.'), -1);
+    assertEquals(sm.getMatrixIndex(' '), -1);
+    assertEquals(sm.getMatrixIndex('?'), -1);
+    assertEquals(sm.getMatrixIndex((char) 128), -1);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetSize()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMatrix().length, sm.getSize());
+  }
+
+  @Test(groups = "Functional")
+  public void testComputePairwiseScores()
+  {
+    /*
+     * NB score matrix expects '-' for gap
+     */
+    String[] seqs = new String[] { "FKL", "R-D", "QIA", "GWC" };
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+
+    MatrixI pairwise = sm.findSimilarities(seqs, SimilarityParams.Jalview);
+
+    /*
+     * should be NxN where N = number of sequences
+     */
+    assertEquals(pairwise.height(), 4);
+    assertEquals(pairwise.width(), 4);
+
+    /*
+     * should be symmetrical (because BLOSUM62 is)
+     */
+    for (int i = 0; i < pairwise.height(); i++)
+    {
+      for (int j = i + 1; j < pairwise.width(); j++)
+      {
+        assertEquals(pairwise.getValue(i, j), pairwise.getValue(j, i),
+                String.format("Not symmetric at [%d, %d]", i, j));
+      }
+    }
+    /*
+     * verify expected BLOSUM dot product scores
+     */
+    // F.F + K.K + L.L = 6 + 5 + 4 = 15
+    assertEquals(pairwise.getValue(0, 0), 15d);
+    // R.R + -.- + D.D = 5 + 1 + 6 = 12
+    assertEquals(pairwise.getValue(1, 1), 12d);
+    // Q.Q + I.I + A.A = 5 + 4 + 4 = 13
+    assertEquals(pairwise.getValue(2, 2), 13d);
+    // G.G + W.W + C.C = 6 + 11 + 9 = 26
+    assertEquals(pairwise.getValue(3, 3), 26d);
+    // F.R + K.- + L.D = -3 + -4 + -4 = -11
+    assertEquals(pairwise.getValue(0, 1), -11d);
+    // F.Q + K.I + L.A = -3 + -3 + -1 = -7
+    assertEquals(pairwise.getValue(0, 2), -7d);
+    // F.G + K.W + L.C = -3 + -3 + -1 = -7
+    assertEquals(pairwise.getValue(0, 3), -7d);
+    // R.Q + -.I + D.A = 1 + -4 + -2 = -5
+    assertEquals(pairwise.getValue(1, 2), -5d);
+    // R.G + -.W + D.C = -2 + -4 + -3 = -9
+    assertEquals(pairwise.getValue(1, 3), -9d);
+    // Q.G + I.W + A.C = -2 + -3 + 0 = -5
+    assertEquals(pairwise.getValue(2, 3), -5d);
+  }
+
+  /**
+   * Test that the result of outputMatrix can be reparsed to give an identical
+   * ScoreMatrix
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testOutputMatrix_roundTrip() throws MalformedURLException,
+          IOException
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    String output = sm.outputMatrix(false);
+    FileParse fp = new FileParse(output, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    ScoreMatrix sm2 = parser.parseMatrix();
+    assertNotNull(sm2);
+    assertTrue(sm2.equals(sm));
+  }
+
+  @Test(groups = "Functional")
+  public void testEqualsAndHashCode()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    ScoreMatrix sm2 = new ScoreMatrix(sm.getName(), sm.getSymbols()
+            .toCharArray(), sm.getMatrix());
+    assertTrue(sm.equals(sm2));
+    assertEquals(sm.hashCode(), sm2.hashCode());
+
+    sm2 = ScoreModels.getInstance().getPam250();
+    assertFalse(sm.equals(sm2));
+    assertNotEquals(sm.hashCode(), sm2.hashCode());
+
+    assertFalse(sm.equals("hello"));
+  }
+
+  /**
+   * Tests for scoring options where the longer length of two sequences is used
+   */
+  @Test(groups = "Functional")
+  public void testcomputeSimilarity_matchLongestSequence()
+  {
+    /*
+     * ScoreMatrix expects '-' for gaps
+     */
+    String s1 = "FR-K-S";
+    String s2 = "FS--L";
+    ScoreMatrix blosum = ScoreModels.getInstance().getBlosum62();
+  
+    /*
+     * score gap-gap and gap-char
+     * shorter sequence treated as if with trailing gaps
+     * score = F^F + R^S + -^- + K^- + -^L + S^-
+     * = 6 + -1 + 1 + -4 + -4 + -4 = -6
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(true, false, true, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
+  
+    /*
+     * score gap-char but not gap-gap
+     * score = F^F + R^S + 0 + K^- + -^L + S^-
+     * = 6 + -1 + 0 + -4 + -4 + -4 = -7
+     */
+    params = new SimilarityParams(false, true, true, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -7d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(false, false, true, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -7d);
+  
+    /*
+     * score gap-gap but not gap-char
+     * score = F^F + R^S + -^- + 0 + 0 + 0
+     * = 6 + -1 + 1 = 6
+     */
+    params = new SimilarityParams(true, false, false, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(true, true, false, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
+  
+    /*
+     * score neither gap-gap nor gap-char
+     * score = F^F + R^S + 0 + 0 + 0 + 0
+     * = 6 + -1  = 5
+     */
+    params = new SimilarityParams(false, false, false, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(false, true, false, false);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
+  }
+
+  /**
+   * Tests for scoring options where only the shorter length of two sequences is
+   * used
+   */
+  @Test(groups = "Functional")
+  public void testcomputeSimilarity_matchShortestSequence()
+  {
+    /*
+     * ScoreMatrix expects '-' for gaps
+     */
+    String s1 = "FR-K-S";
+    String s2 = "FS--L";
+    ScoreMatrix blosum = ScoreModels.getInstance().getBlosum62();
+
+    /*
+     * score gap-gap and gap-char
+     * match shorter sequence only
+     * score = F^F + R^S + -^- + K^- + -^L
+     * = 6 + -1 + 1 + -4 + -4 = -2
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -2d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(true, false, true, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -2d);
+  
+    /*
+     * score gap-char but not gap-gap
+     * score = F^F + R^S + 0 + K^- + -^L
+     * = 6 + -1 + 0 + -4 + -4 = -3
+     */
+    params = new SimilarityParams(false, true, true, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -3d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(false, false, true, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), -3d);
+  
+    /*
+     * score gap-gap but not gap-char
+     * score = F^F + R^S + -^- + 0 + 0
+     * = 6 + -1 + 1 = 6
+     */
+    params = new SimilarityParams(true, false, false, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(true, true, false, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
+  
+    /*
+     * score neither gap-gap nor gap-char
+     * score = F^F + R^S + 0 + 0 + 0
+     * = 6 + -1  = 5
+     */
+    params = new SimilarityParams(false, false, false, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
+    // matchGap (arg2) is ignored:
+    params = new SimilarityParams(false, true, false, true);
+    assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
+  }
+
+  @Test(groups = "Functional")
+  public void testSymmetric()
+  {
+    verifySymmetric(ScoreModels.getInstance().getBlosum62());
+    verifySymmetric(ScoreModels.getInstance().getPam250());
+    verifySymmetric(ScoreModels.getInstance().getDefaultModel(false)); // dna
+  }
+
+  private void verifySymmetric(ScoreMatrix sm)
+  {
+    float[][] m = sm.getMatrix();
+    int rows = m.length;
+    for (int row = 0; row < rows; row++)
+    {
+      assertEquals(m[row].length, rows);
+      for (int col = 0; col < rows; col++)
+      {
+        assertEquals(m[row][col], m[col][row], String.format("%s [%s, %s]",
+                sm.getName(), ResidueProperties.aa[row],
+                ResidueProperties.aa[col]));
+      }
+    }
+  }
+
+  /**
+   * A test that just asserts the expected values in the Blosum62 score matrix
+   */
+  @Test(groups = "Functional")
+  public void testBlosum62_values()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+
+    assertTrue(sm.isProtein());
+    assertFalse(sm.isDNA());
+    assertNull(sm.getDescription());
+
+    /*
+     * verify expected scores against ARNDCQEGHILKMFPSTWYVBZX
+     * scraped from https://www.ncbi.nlm.nih.gov/Class/FieldGuide/BLOSUM62.txt
+     */
+    verifyValues(sm, 'A', new float[] { 4, -1, -2, -2, 0, -1, -1, 0, -2,
+        -1,
+        -1, -1, -1, -2, -1, 1, 0, -3, -2, 0, -2, -1, 0 });
+    verifyValues(sm, 'R', new float[] { -1, 5, 0, -2, -3, 1, 0, -2, 0, -3,
+        -2, 2, -1, -3, -2, -1, -1, -3, -2, -3, -1, 0, -1 });
+    verifyValues(sm, 'N', new float[] { -2, 0, 6, 1, -3, 0, 0, 0, 1, -3,
+        -3,
+        0, -2, -3, -2, 1, 0, -4, -2, -3, 3, 0, -1 });
+    verifyValues(sm, 'D', new float[] { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3,
+        -4, -1, -3, -3, -1, 0, -1, -4, -3, -3, 4, 1, -1 });
+    verifyValues(sm, 'C', new float[] { 0, -3, -3, -3, 9, -3, -4, -3, -3,
+        -1,
+        -1, -3, -1, -2, -3, -1, -1, -2, -2, -1, -3, -3, -2 });
+    verifyValues(sm, 'Q', new float[] { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3,
+        -2,
+        1, 0, -3, -1, 0, -1, -2, -1, -2, 0, 3, -1 });
+    verifyValues(sm, 'E', new float[] { -1, 0, 0, 2, -4, 2, 5, -2, 0, -3,
+        -3,
+        1, -2, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
+    verifyValues(sm, 'G', new float[] { 0, -2, 0, -1, -3, -2, -2, 6, -2,
+        -4,
+        -4, -2, -3, -3, -2, 0, -2, -2, -3, -3, -1, -2, -1 });
+    verifyValues(sm, 'H', new float[] { -2, 0, 1, -1, -3, 0, 0, -2, 8, -3,
+        -3, -1, -2, -1, -2, -1, -2, -2, 2, -3, 0, 0, -1 });
+    verifyValues(sm, 'I', new float[] { -1, -3, -3, -3, -1, -3, -3, -4, -3,
+        4, 2, -3, 1, 0, -3, -2, -1, -3, -1, 3, -3, -3, -1 });
+    verifyValues(sm, 'L', new float[] { -1, -2, -3, -4, -1, -2, -3, -4, -3,
+        2, 4, -2, 2, 0, -3, -2, -1, -2, -1, 1, -4, -3, -1 });
+    verifyValues(sm, 'K', new float[] { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3,
+        -2, 5, -1, -3, -1, 0, -1, -3, -2, -2, 0, 1, -1 });
+    verifyValues(sm, 'M', new float[] { -1, -1, -2, -3, -1, 0, -2, -3, -2,
+        1,
+        2, -1, 5, 0, -2, -1, -1, -1, -1, 1, -3, -1, -1 });
+    verifyValues(sm, 'F', new float[] { -2, -3, -3, -3, -2, -3, -3, -3, -1,
+        0, 0, -3, 0, 6, -4, -2, -2, 1, 3, -1, -3, -3, -1 });
+    verifyValues(sm, 'P', new float[] { -1, -2, -2, -1, -3, -1, -1, -2, -2,
+        -3, -3, -1, -2, -4, 7, -1, -1, -4, -3, -2, -2, -1, -2 });
+    verifyValues(sm, 'S', new float[] { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2,
+        -2,
+        0, -1, -2, -1, 4, 1, -3, -2, -2, 0, 0, 0 });
+    verifyValues(sm, 'T', new float[] { 0, -1, 0, -1, -1, -1, -1, -2, -2,
+        -1,
+        -1, -1, -1, -2, -1, 1, 5, -2, -2, 0, -1, -1, 0 });
+    verifyValues(sm, 'W', new float[] { -3, -3, -4, -4, -2, -2, -3, -2, -2,
+        -3, -2, -3, -1, 1, -4, -3, -2, 11, 2, -3, -4, -3, -2 });
+    verifyValues(sm, 'Y', new float[] { -2, -2, -2, -3, -2, -1, -2, -3, 2,
+        -1, -1, -2, -1, 3, -3, -2, -2, 2, 7, -1, -3, -2, -1 });
+    verifyValues(sm, 'V', new float[] { 0, -3, -3, -3, -1, -2, -2, -3, -3,
+        3,
+        1, -2, 1, -1, -2, -2, 0, -3, -1, 4, -3, -2, -1 });
+    verifyValues(sm, 'B', new float[] { -2, -1, 3, 4, -3, 0, 1, -1, 0, -3,
+        -4, 0, -3, -3, -2, 0, -1, -4, -3, -3, 4, 1, -1 });
+    verifyValues(sm, 'Z', new float[] { -1, 0, 0, 1, -3, 3, 4, -2, 0, -3,
+        -3,
+        1, -1, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
+    verifyValues(sm, 'X', new float[] { 0, -1, -1, -1, -2, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -2, 0, 0, -2, -1, -1, -1, -1, -1 });
+  }
+
+  /**
+   * Helper method to check pairwise scores for one residue
+   * 
+   * @param sm
+   * @param res
+   * @param expected
+   *          score values against 'res', in ResidueProperties.aaIndex order
+   */
+  private void verifyValues(ScoreMatrix sm, char res, float[] expected)
+  {
+    for (int j = 0; j < expected.length; j++)
+    {
+      char c2 = ResidueProperties.aa[j].charAt(0);
+      assertEquals(sm.getPairwiseScore(res, c2), expected[j],
+              String.format("%s->%s", res, c2));
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testConstructor_gapDash()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 4f, 5f };
+    ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '-' },
+            scores);
+    assertEquals(sm.getSize(), 2);
+    assertArrayEquals(scores, sm.getMatrix());
+    assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
+    assertEquals(sm.getPairwiseScore('a', '-'), 2f);
+    assertEquals(sm.getPairwiseScore('-', 'A'), 4f);
+    assertEquals(sm.getMatrixIndex('a'), 0);
+    assertEquals(sm.getMatrixIndex('A'), 0);
+    assertEquals(sm.getMatrixIndex('-'), 1);
+    assertEquals(sm.getMatrixIndex(' '), -1);
+    assertEquals(sm.getMatrixIndex('.'), -1);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetPairwiseScore()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { -4f, 5f };
+    ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', 'B' },
+            scores);
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'B'), 2f);
+    assertEquals(sm.getPairwiseScore('b', 'a'), -4f);
+    assertEquals(sm.getPairwiseScore('B', 'b'), 5f);
+
+    /*
+     * unknown symbols currently score minimum score
+     * or 1 for identity with self
+     */
+    assertEquals(sm.getPairwiseScore('A', '-'), -4f);
+    assertEquals(sm.getPairwiseScore('-', 'A'), -4f);
+    assertEquals(sm.getPairwiseScore('-', '-'), 1f);
+    assertEquals(sm.getPairwiseScore('Q', 'W'), -4f);
+    assertEquals(sm.getPairwiseScore('Q', 'Q'), 1f);
+
+    /*
+     * symbols not in basic ASCII set score zero
+     */
+    char c = (char) 200;
+    assertEquals(sm.getPairwiseScore('Q', c), 0f);
+    assertEquals(sm.getPairwiseScore(c, 'Q'), 0f);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMinimumScore()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMinimumScore(), -4f);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMaximumScore()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMaximumScore(), 11f);
+  }
+
+  @Test(groups = "Functional")
+  public void testOutputMatrix_html()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 4f, -5.3E-10f };
+    ScoreMatrix sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
+    String html = sm.outputMatrix(true);
+    String expected = "<table border=\"1\"><tr><th></th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th></tr>\n"
+            + "<tr><td>A</td><td>1.0</td><td>2.0</td></tr>\n"
+            + "<tr><td>B</td><td>4.0</td><td>-5.3E-10</td></tr>\n"
+            + "</table>";
+    assertEquals(html, expected);
+  }
+}
diff --git a/test/jalview/analysis/scoremodels/ScoreModelsTest.java b/test/jalview/analysis/scoremodels/ScoreModelsTest.java
new file mode 100644 (file)
index 0000000..ffcd1a8
--- /dev/null
@@ -0,0 +1,105 @@
+package jalview.analysis.scoremodels;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.analysis.PairwiseScoreModelI;
+import jalview.api.analysis.ScoreModelI;
+
+import java.util.Iterator;
+
+import org.testng.annotations.Test;
+
+public class ScoreModelsTest
+{
+  /**
+   * Verify that the singleton constructor successfully loads Jalview's built-in
+   * score models
+   */
+  @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    Iterator<ScoreModelI> models = ScoreModels.getInstance().getModels()
+            .iterator();
+    assertTrue(models.hasNext());
+
+    /*
+     * models are served in order of addition
+     */
+    ScoreModelI sm = models.next();
+    assertTrue(sm instanceof SimilarityScoreModel);
+    assertTrue(sm instanceof PairwiseScoreModelI);
+    assertFalse(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "BLOSUM62");
+    assertEquals(((PairwiseScoreModelI) sm).getPairwiseScore('I', 'R'), -3f);
+
+    sm = models.next();
+    assertTrue(sm instanceof SimilarityScoreModel);
+    assertTrue(sm instanceof PairwiseScoreModelI);
+    assertFalse(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "PAM250");
+    assertEquals(((PairwiseScoreModelI) sm).getPairwiseScore('R', 'C'), -4f);
+
+    sm = models.next();
+    assertTrue(sm instanceof SimilarityScoreModel);
+    assertTrue(sm instanceof PairwiseScoreModelI);
+    assertFalse(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "PID");
+    assertEquals(((PairwiseScoreModelI) sm).getPairwiseScore('R', 'C'), 0f);
+    assertEquals(((PairwiseScoreModelI) sm).getPairwiseScore('R', 'r'), 1f);
+
+    sm = models.next();
+    assertTrue(sm instanceof SimilarityScoreModel);
+    assertTrue(sm instanceof PairwiseScoreModelI);
+    assertFalse(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "DNA");
+    assertEquals(((PairwiseScoreModelI) sm).getPairwiseScore('c', 'x'), 1f);
+
+    sm = models.next();
+    assertFalse(sm instanceof SimilarityScoreModel);
+    assertFalse(sm instanceof PairwiseScoreModelI);
+    assertTrue(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "Sequence Feature Similarity");
+  }
+
+  /**
+   * 'Test' that prints out score matrices in tab-delimited format. This test is
+   * intentionally not assigned to any group so would not be run as part of a
+   * suite. It makes no assertions and is just provided as a utility method for
+   * printing out matrices. Relocated here from ScoreMatrixPrinter.
+   */
+  @Test(groups = "none")
+  public void printAllMatrices_tabDelimited()
+  {
+    printAllMatrices(false);
+  }
+
+  /**
+   * 'Test' that prints out score matrices in html format. This test is
+   * intentionally not assigned to any group so would not be run as part of a
+   * suite. It makes no assertions and is just provided as a utility method for
+   * printing out matrices. Relocated here from ScoreMatrixPrinter.
+   */
+  @Test(groups = "none")
+  public void printAllMatrices_asHtml()
+  {
+    printAllMatrices(true);
+  }
+
+  /**
+   * Print all registered ScoreMatrix as plain or html tables
+   * 
+   * @param asHtml
+   */
+  protected void printAllMatrices(boolean asHtml)
+  {
+    for (ScoreModelI sm : ScoreModels.getInstance().getModels())
+    {
+      if (sm instanceof ScoreMatrix)
+      {
+        System.out.println(((ScoreMatrix) sm).outputMatrix(asHtml));
+      }
+    }
+  }
+}
index a2c6256..e47e9d6 100644 (file)
@@ -28,6 +28,7 @@ import jalview.gui.JvOptionPane;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.FileFormat;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -293,4 +294,45 @@ public class AlignmentAnnotationTests
               ann.getDefaultRnaHelixSymbol(i));
     }
   }
+
+  public static Annotation newAnnotation(String ann)
+  {
+    float val = 0f;
+    try
+    {
+      val = Float.parseFloat(ann);
+    } catch (NumberFormatException q)
+    {
+    }
+    ;
+    return new Annotation(ann, ann, '\0', val);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testIsQuantitative()
+  {
+    AlignmentAnnotation ann = null;
+
+    ann = new AlignmentAnnotation("an", "some an", null);
+    Assert.assertFalse(ann.isQuantitative(),
+            "Empty annotation set should not be quantitative.");
+
+    ann = new AlignmentAnnotation("an", "some an", new Annotation[] {
+        newAnnotation("4"), newAnnotation("1"), newAnnotation("1"),
+        newAnnotation("0.1"), newAnnotation("0.3") });
+    Assert.assertTrue(ann.isQuantitative(),
+            "All numbers annotation set should be quantitative.");
+
+    ann = new AlignmentAnnotation("an", "some an", new Annotation[] {
+        newAnnotation("E"), newAnnotation("E"), newAnnotation("E"),
+        newAnnotation("E"), newAnnotation("E") });
+    Assert.assertFalse(ann.isQuantitative(),
+            "All 'E' annotation set should not be quantitative.");
+
+    ann = new AlignmentAnnotation("an", "some an", new Annotation[] {
+        newAnnotation("E"), newAnnotation("1"), newAnnotation("2"),
+        newAnnotation("3"), newAnnotation("E") });
+    Assert.assertTrue(ann.isQuantitative(),
+            "Mixed 'E' annotation set should be quantitative.");
+  }
 }
index 7af77f5..d6e09fd 100644 (file)
@@ -27,6 +27,7 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.analysis.AlignmentGenerator;
 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
@@ -619,6 +620,67 @@ public class AlignmentTest
     assertFalse(iter.hasNext());
   }
 
+  /**
+   * Test method that returns annotations that match on reference sequence,
+   * label, or calcId.
+   */
+  @Test(groups = { "Functional" })
+  public void testFindAnnotations_bySeqLabelandorCalcId()
+  {
+    // TODO: finish testFindAnnotations_bySeqLabelandorCalcId test
+    /* Note - this is an incomplete test - need to check null or
+     * non-null [ matches, not matches ] behaviour for each of the three
+     * parameters..*/
+
+    // search for a single, unique calcId with wildcards on other params
+    Iterable<AlignmentAnnotation> anns = al.findAnnotations(null,
+            "CalcIdForD.melanogaster.2", null);
+    Iterator<AlignmentAnnotation> iter = anns.iterator();
+    assertTrue(iter.hasNext());
+    AlignmentAnnotation ann = iter.next();
+    assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
+    assertFalse(iter.hasNext());
+
+    // save reference to test sequence reference parameter
+    SequenceI rseq = ann.sequenceRef;
+
+    // search for annotation associated with a single sequence
+    anns = al.findAnnotations(rseq, null, null);
+    iter = anns.iterator();
+    assertTrue(iter.hasNext());
+    ann = iter.next();
+    assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
+    assertFalse(iter.hasNext());
+
+    // search for annotation with a non-existant calcId
+    anns = al.findAnnotations(null, "CalcIdForD.melanogaster.?", null);
+    iter = anns.iterator();
+    assertFalse(iter.hasNext());
+
+    // search for annotation with a particular label - expect three
+    anns = al.findAnnotations(null, null, "Secondary Structure");
+    iter = anns.iterator();
+    assertTrue(iter.hasNext());
+    iter.next();
+    assertTrue(iter.hasNext());
+    iter.next();
+    assertTrue(iter.hasNext());
+    iter.next();
+    // third found.. so
+    assertFalse(iter.hasNext());
+
+    // null on all parameters == find all annotations
+    anns = al.findAnnotations(null, null, null);
+    iter = anns.iterator();
+    int n = al.getAlignmentAnnotation().length;
+    while (iter.hasNext())
+    {
+      n--;
+      iter.next();
+    }
+    assertTrue("Found " + n + " fewer annotations from search.", n == 0);
+  }
+
   @Test(groups = { "Functional" })
   public void testDeleteAllAnnotations_includingAutocalculated()
   {
@@ -1196,4 +1258,76 @@ public class AlignmentTest
     assertNull(a.findGroup(seq2, 8));
   }
 
+  @Test(groups = { "Functional" })
+  public void testDeleteSequenceByIndex()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete sequence 10, alignment reduced by 1
+    int height = a.getAbsoluteHeight();
+    a.deleteSequence(10);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete -ve index, nothing happens
+    a.deleteSequence(-1);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete beyond end of alignment, nothing happens
+    a.deleteSequence(14);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testDeleteSequenceBySeq()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete sequence 10, alignment reduced by 1
+    int height = a.getAbsoluteHeight();
+    SequenceI seq = a.getSequenceAt(10);
+    a.deleteSequence(seq);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete non-existent sequence, nothing happens
+    seq = new Sequence("cds", "GCCTCGGAT");
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testDeleteHiddenSequence()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete a sequence which is hidden, check it is NOT removed from hidden
+    // sequences
+    int height = a.getAbsoluteHeight();
+    SequenceI seq = a.getSequenceAt(2);
+    a.getHiddenSequences().hideSequence(seq);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+    a.deleteSequence(2);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+
+    // delete a sequence which is not hidden, check hiddenSequences are not
+    // affected
+    a.deleteSequence(10);
+    assertEquals(a.getAbsoluteHeight(), height - 2);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testSetDataset_selfReference()
+  {
+    SequenceI seq = new Sequence("a", "a");
+    AlignmentI alignment = new Alignment(new SequenceI[] { seq });
+    alignment.setDataset(alignment);
+  }
 }
index 594d6e6..b201c7e 100644 (file)
@@ -22,7 +22,11 @@ package jalview.datamodel;
 
 import static org.testng.Assert.assertEquals;
 
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
 import jalview.gui.JvOptionPane;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -50,4 +54,69 @@ public class AlignmentViewTest
     assertEquals(av.getVisibleAlignment('$').getSequenceAt(0)
             .getSequenceAsString(), "A$$CDE");
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetVisibleContigs()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            ">s1\n0123456789\n", DataSourceType.PASTE);
+    AlignViewport av = af.getViewport();
+    AlignmentView view = av.getAlignmentView(true);
+
+    /*
+     * verify getVisibleContigs returns inclusive [start, end] ranges
+     * 
+     * no columns hidden
+     */
+    int[] contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 0, 9 });
+
+    /*
+     * hide 3 internal columns
+     */
+    av.hideColumns(5, 7);
+    // the old AlignmentView is now stale!
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 0, 9 });
+    // get a fresh AlignmentView
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 0, 4, 8, 9 });
+
+    // hide first 2 columns
+    av.hideColumns(0, 1);
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 2, 4, 8, 9 });
+
+    // hide last column
+    av.hideColumns(9, 9);
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 2, 4, 8, 8 });
+
+    // unhide columns 5-7
+    av.showColumn(5);
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 2, 8 });
+
+    // hide columns 2-7
+    av.hideColumns(2, 7);
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 8, 8 });
+
+    // hide column 8
+    av.hideColumns(8, 8);
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] {});
+
+    // unhide all
+    av.showAllHiddenColumns();
+    view = av.getAlignmentView(true);
+    contigs = view.getVisibleContigs();
+    assertEquals(contigs, new int[] { 0, 9 });
+  }
 }
diff --git a/test/jalview/datamodel/AllColsIteratorTest.java b/test/jalview/datamodel/AllColsIteratorTest.java
new file mode 100644 (file)
index 0000000..3942f0b
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.datamodel;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.NoSuchElementException;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AllColsIteratorTest
+{
+  HiddenColumns hiddenCols;
+  
+  @BeforeClass
+  public void setup()
+  {
+    hiddenCols = new HiddenColumns();
+   hiddenCols.hideColumns(2,4);
+  }
+  
+
+  /*
+   * Test iterator iterates through collection correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNext()
+  {
+    AllColsIterator it = new AllColsIterator(0, 3, hiddenCols);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator throws NoSuchElementException at end of iteration
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNext() throws NoSuchElementException
+  {
+    AllColsIterator it = new AllColsIterator(0, 3, hiddenCols);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator throws UnsupportedOperationException on call to remove
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { UnsupportedOperationException.class })
+  public void testRemove() throws UnsupportedOperationException
+  {
+    AllColsIterator it = new AllColsIterator(0, 3, hiddenCols);
+    it.remove();
+  }
+
+  /*
+   * Test iterator behaves correctly when there is only one element in the collection
+   */
+  @Test(groups = { "Functional" })
+  public void testOneElement()
+  {
+    HiddenColumns hidden = new HiddenColumns();
+    AllColsIterator it = new AllColsIterator(0, 0, hidden);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 1, "hasNext() is false after 1 iteration");
+  }
+}
diff --git a/test/jalview/datamodel/AllRowsIteratorTest.java b/test/jalview/datamodel/AllRowsIteratorTest.java
new file mode 100644 (file)
index 0000000..aeff71d
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.datamodel;
+
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AllRowsIteratorTest
+{
+  AlignmentI al;
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<>();
+
+  @BeforeClass
+  public void setup()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    al = gen.generate(20, 15, 123, 5, 5);
+    if (!hiddenRepSequences.isEmpty())
+    {
+      al.getHiddenSequences().showAll(hiddenRepSequences);
+    }
+    hideSequences(2, 4);
+  }
+
+  /*
+   * Test iterator iterates through collection correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNext()
+  {
+    AllRowsIterator it = new AllRowsIterator(0, 3, al);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator throws NoSuchElementException at end of iteration
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNext() throws NoSuchElementException
+  {
+    AllRowsIterator it = new AllRowsIterator(0, 3, al);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator throws UnsupportedOperationException on call to remove
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { UnsupportedOperationException.class })
+  public void testRemove() throws UnsupportedOperationException
+  {
+    AllRowsIterator it = new AllRowsIterator(0, 3, al);
+    it.remove();
+  }
+
+
+  /*
+   * Hide sequences between start and end
+   */
+  private void hideSequences(int start, int end)
+  {
+    SequenceI[] allseqs = al.getSequencesArray();
+    SequenceGroup theseSeqs = new SequenceGroup();
+
+    for (int i = start; i <= end; i++)
+    {
+      theseSeqs.addSequence(allseqs[i], false);
+      al.getHiddenSequences().hideSequence(allseqs[i]);
+    }
+
+    hiddenRepSequences.put(allseqs[start], theseSeqs);
+  }
+
+  /*
+   * Test iterator behaves correctly when there is only one element in the collection
+   */
+  @Test(groups = { "Functional" })
+  public void testOneElement()
+  {
+    AllRowsIterator it = new AllRowsIterator(0, 0, al);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 1, "hasNext() is false after 1 iteration");
+  }
+
+}
index 1d819c9..7237e63 100644 (file)
@@ -22,10 +22,10 @@ package jalview.datamodel;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.AssertJUnit.fail;
 
+import jalview.analysis.AlignmentGenerator;
 import jalview.gui.JvOptionPane;
 
 import java.util.Arrays;
@@ -59,6 +59,30 @@ public class ColumnSelectionTest
     assertEquals("[2, 5, 3]", sel.toString());
   }
 
+  @Test(groups = { "Functional" })
+  public void testSetElementsFrom()
+  {
+    ColumnSelection fromcs = new ColumnSelection();
+    ColumnSelection tocs = new ColumnSelection();
+    HiddenColumns hidden = new HiddenColumns();
+
+    fromcs.addElement(2);
+    fromcs.addElement(3);
+    fromcs.addElement(5);
+
+    tocs.setElementsFrom(fromcs, hidden);
+    assertTrue(tocs.equals(fromcs));
+
+    hidden.hideColumns(4, 6);
+    tocs.setElementsFrom(fromcs, hidden);
+
+    // expect cols 2 and 3 to be selected but not 5
+    ColumnSelection expectcs = new ColumnSelection();
+    expectcs.addElement(2);
+    expectcs.addElement(3);
+    assertTrue(tocs.equals(expectcs));
+  }
+
   /**
    * Test the remove method - in particular to verify that remove(int i) removes
    * the element whose value is i, _NOT_ the i'th element.
@@ -88,176 +112,6 @@ public class ColumnSelectionTest
   }
 
   /**
-   * Test the method that finds the visible column position of an alignment
-   * column, allowing for hidden columns.
-   */
-  @Test(groups = { "Functional" })
-  public void testFindColumnPosition()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    assertEquals(5, cs.findColumnPosition(5));
-
-    // hiding column 6 makes no difference
-    cs.hideColumns(6, 6);
-    assertEquals(5, cs.findColumnPosition(5));
-
-    // hiding column 4 moves column 5 to column 4
-    cs.hideColumns(4, 4);
-    assertEquals(4, cs.findColumnPosition(5));
-
-    // hiding columns 1 and 2 moves column 5 to column 2
-    cs.hideColumns(1, 2);
-    assertEquals(2, cs.findColumnPosition(5));
-  }
-
-  /**
-   * Test the code used to locate the reference sequence ruler origin
-   */
-  @Test(groups = { "Functional" })
-  public void testLocateVisibleBoundsofSequence()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
-    assertEquals(2, seq.findIndex(seq.getStart()));
-
-    // no hidden columns
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-    // hidden column on gap after end of sequence - should not affect bounds
-    cs.hideColumns(13);
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-    cs.revealAllHiddenColumns();
-    // hidden column on gap before beginning of sequence - should vis bounds by
-    // one
-    cs.hideColumns(0);
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
-                seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-    cs.revealAllHiddenColumns();
-    // hide columns around most of sequence - leave one residue remaining
-    cs.hideColumns(1, 3);
-    cs.hideColumns(6, 11);
-    assertEquals("-D",
-            cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
-    assertEquals(
-            Arrays.toString(new int[] { 1, 1, 3, 3,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-    cs.revealAllHiddenColumns();
-
-    // hide whole sequence - should just get location of hidden region
-    // containing sequence
-    cs.hideColumns(1, 11);
-    assertEquals(
-            Arrays.toString(new int[] { 0, 1, 0, 0,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-  }
-
-  @Test(groups = { "Functional" })
-  public void testLocateVisibleBoundsPathologicals()
-  {
-    // test some pathological cases we missed
-    AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
-            "refseqGaptest", "KTDVTI----------NFI-----G----L") });
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideInsertionsFor(al.getSequenceAt(0));
-    assertEquals(
-            "G",
-            ""
-                    + al.getSequenceAt(0).getCharAt(
-                            cs.adjustForHiddenColumns(9)));
-
-  }
-
-  @Test(groups = { "Functional" })
-  public void testHideColumns()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(5);
-    List<int[]> hidden = cs.getHiddenColumns();
-    assertEquals(1, hidden.size());
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
-
-    cs.hideColumns(3);
-    assertEquals(2, hidden.size());
-    // two hidden ranges, in order:
-    assertSame(hidden, cs.getHiddenColumns());
-    assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
-
-    // hiding column 4 expands [3, 3] to [3, 4]
-    // and merges to [5, 5] to make [3, 5]
-    cs.hideColumns(4);
-    hidden = cs.getHiddenColumns();
-    assertEquals(1, hidden.size());
-    assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
-
-    // clear hidden columns (note they are added to selected)
-    cs.revealAllHiddenColumns();
-    // it is now actually null but getter returns an empty list
-    assertTrue(cs.getHiddenColumns().isEmpty());
-
-    cs.hideColumns(3, 6);
-    hidden = cs.getHiddenColumns();
-    int[] firstHiddenRange = hidden.get(0);
-    assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
-
-    // adding a subrange of already hidden should do nothing
-    cs.hideColumns(4, 5);
-    assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
-    cs.hideColumns(3, 5);
-    assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
-    cs.hideColumns(4, 6);
-    assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
-    cs.hideColumns(3, 6);
-    assertEquals(1, hidden.size());
-    assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
-
-    cs.revealAllHiddenColumns();
-    cs.hideColumns(2, 4);
-    hidden = cs.getHiddenColumns();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
-
-    // extend contiguous with 2 positions overlap
-    cs.hideColumns(3, 5);
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
-
-    // extend contiguous with 1 position overlap
-    cs.hideColumns(5, 6);
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
-
-    // extend contiguous with overlap both ends:
-    cs.hideColumns(1, 7);
-    assertEquals(1, hidden.size());
-    assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
-  }
-
-  /**
    * Test the method that hides a specified column including any adjacent
    * selected columns. This is a convenience method for the case where multiple
    * column regions are selected and then hidden using menu option View | Hide |
@@ -266,48 +120,55 @@ public class ColumnSelectionTest
   @Test(groups = { "Functional" })
   public void testHideColumns_withSelection()
   {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
     ColumnSelection cs = new ColumnSelection();
     // select columns 4-6
     cs.addElement(4);
     cs.addElement(5);
     cs.addElement(6);
     // hide column 5 (and adjacent):
-    cs.hideColumns(5);
+    cs.hideSelectedColumns(5, al.getHiddenColumns());
     // 4,5,6 now hidden:
-    List<int[]> hidden = cs.getHiddenColumns();
+    List<int[]> hidden = al.getHiddenColumns().getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     // none now selected:
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, hiding column 4 (5 and 6)
+    al = gen.generate(50, 20, 123, 5, 5);
     cs = new ColumnSelection();
     cs.addElement(4);
     cs.addElement(5);
     cs.addElement(6);
-    cs.hideColumns(4);
-    hidden = cs.getHiddenColumns();
+    cs.hideSelectedColumns(4, al.getHiddenColumns());
+    hidden = al.getHiddenColumns().getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, hiding column (4, 5 and) 6
+    al = gen.generate(50, 20, 123, 5, 5);
     cs = new ColumnSelection();
     cs.addElement(4);
     cs.addElement(5);
     cs.addElement(6);
-    cs.hideColumns(6);
-    hidden = cs.getHiddenColumns();
+    cs.hideSelectedColumns(6, al.getHiddenColumns());
+    hidden = al.getHiddenColumns().getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, with _only_ adjacent columns selected
+    al = gen.generate(50, 20, 123, 5, 5);
     cs = new ColumnSelection();
     cs.addElement(4);
     cs.addElement(6);
-    cs.hideColumns(5);
-    hidden = cs.getHiddenColumns();
+    cs.hideSelectedColumns(5, al.getHiddenColumns());
+    hidden = al.getHiddenColumns().getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
     assertTrue(cs.getSelected().isEmpty());
@@ -319,17 +180,23 @@ public class ColumnSelectionTest
   @Test(groups = { "Functional" })
   public void testHideSelectedColumns()
   {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
     ColumnSelection cs = new ColumnSelection();
     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
     for (int col : sel)
     {
       cs.addElement(col);
     }
-    cs.hideColumns(15, 18);
 
-    cs.hideSelectedColumns();
+    HiddenColumns cols = al.getHiddenColumns();
+    cols.hideColumns(15, 18);
+
+    cs.hideSelectedColumns(al);
     assertTrue(cs.getSelected().isEmpty());
-    List<int[]> hidden = cs.getHiddenColumns();
+    List<int[]> hidden = cols.getHiddenRegions();
     assertEquals(4, hidden.size());
     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
@@ -367,104 +234,27 @@ public class ColumnSelectionTest
     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
   }
 
-  /**
-   * Test the method that reveals a range of hidden columns given the start
-   * column of the range
-   */
-  @Test(groups = { "Functional" })
-  public void testRevealHiddenColumns()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(5, 8);
-    cs.addElement(10);
-    cs.revealHiddenColumns(5);
-    // hidden columns list now null but getter returns empty list:
-    assertTrue(cs.getHiddenColumns().isEmpty());
-    // revealed columns are marked as selected (added to selection):
-    assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString());
-
-    // calling with a column other than the range start does nothing:
-    cs = new ColumnSelection();
-    cs.hideColumns(5, 8);
-    List<int[]> hidden = cs.getHiddenColumns();
-    cs.revealHiddenColumns(6);
-    assertSame(hidden, cs.getHiddenColumns());
-    assertTrue(cs.getSelected().isEmpty());
-  }
-
-  @Test(groups = { "Functional" })
-  public void testRevealAllHiddenColumns()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(5, 8);
-    cs.hideColumns(2, 3);
-    cs.addElement(11);
-    cs.addElement(1);
-    cs.revealAllHiddenColumns();
-
-    /*
-     * revealing hidden columns adds them (in order) to the (unordered)
-     * selection list
-     */
-    assertTrue(cs.getHiddenColumns().isEmpty());
-    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString());
-  }
-
-  @Test(groups = { "Functional" })
-  public void testIsVisible()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(2, 4);
-    cs.hideColumns(6, 7);
-    assertTrue(cs.isVisible(0));
-    assertTrue(cs.isVisible(-99));
-    assertTrue(cs.isVisible(1));
-    assertFalse(cs.isVisible(2));
-    assertFalse(cs.isVisible(3));
-    assertFalse(cs.isVisible(4));
-    assertTrue(cs.isVisible(5));
-    assertFalse(cs.isVisible(6));
-    assertFalse(cs.isVisible(7));
-  }
-
-  @Test(groups = { "Functional" })
-  public void testGetVisibleContigs()
-  {
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(3, 6);
-    cs.hideColumns(8, 9);
-    cs.hideColumns(12, 12);
-
-    // start position is inclusive, end position exclusive:
-    int[] visible = cs.getVisibleContigs(1, 13);
-    assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 14);
-    assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(3, 10);
-    assertEquals("[7, 7]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 6);
-    assertEquals("[]", Arrays.toString(visible));
-  }
-
   @Test(groups = { "Functional" })
   public void testInvertColumnSelection()
   {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
     ColumnSelection cs = new ColumnSelection();
     cs.addElement(4);
     cs.addElement(6);
     cs.addElement(8);
-    cs.hideColumns(3, 3);
-    cs.hideColumns(6, 6);
+
+    HiddenColumns cols = al.getHiddenColumns();
+    cols.hideColumns(3, 3);
+    cols.hideColumns(6, 6);
 
     // invert selection from start (inclusive) to end (exclusive)
-    // hidden columns are _not_ changed
-    cs.invertColumnSelection(2, 9);
+    cs.invertColumnSelection(2, 9, al);
     assertEquals("[2, 5, 7]", cs.getSelected().toString());
 
-    cs.invertColumnSelection(1, 9);
+    cs.invertColumnSelection(1, 9, al);
     assertEquals("[1, 4, 8]", cs.getSelected().toString());
   }
 
@@ -507,9 +297,6 @@ public class ColumnSelectionTest
     cs.addElement(0);
     cs.addElement(513);
     cs.addElement(1);
-    cs.hideColumns(3);
-    cs.hideColumns(7);
-    cs.hideColumns(5, 9);
 
     // same selections added in a different order
     ColumnSelection cs2 = new ColumnSelection();
@@ -517,15 +304,6 @@ public class ColumnSelectionTest
     cs2.addElement(513);
     cs2.addElement(0);
 
-    // with no hidden columns
-    assertFalse(cs.equals(cs2));
-    assertFalse(cs2.equals(cs));
-
-    // with hidden columns added in a different order
-    cs2.hideColumns(6, 9);
-    cs2.hideColumns(5, 8);
-    cs2.hideColumns(3);
-
     assertTrue(cs.equals(cs2));
     assertTrue(cs.equals(cs));
     assertTrue(cs2.equals(cs));
@@ -537,18 +315,20 @@ public class ColumnSelectionTest
 
     cs2.removeElement(12);
     assertTrue(cs.equals(cs2));
-
-    cs2.hideColumns(88);
-    assertFalse(cs.equals(cs2));
-    /*
-     * unhiding a column adds it to selection!
-     */
-    cs2.revealHiddenColumns(88);
-    assertFalse(cs.equals(cs2));
-    cs.addElement(88);
-    assertTrue(cs.equals(cs2));
   }
 
+  /*
+      cs2.hideSelectedColumns(88);
+      assertFalse(cs.equals(cs2));
+      /*
+       * unhiding a column adds it to selection!
+       */
+  /*    cs2.revealHiddenColumns(88);
+      assertFalse(cs.equals(cs2));
+      cs.addElement(88);
+      assertTrue(cs.equals(cs2));
+    */
+
   /**
    * Test the method that returns selected columns, in the order in which they
    * were added
@@ -746,75 +526,14 @@ public class ColumnSelectionTest
     ColumnSelection cs = new ColumnSelection();
     cs.addElement(3);
     cs.addElement(1);
-    cs.hideColumns(10, 11);
-    cs.hideColumns(5, 7);
-    assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0)));
 
     ColumnSelection cs2 = new ColumnSelection(cs);
     assertTrue(cs2.hasSelectedColumns());
-    assertTrue(cs2.hasHiddenColumns());
+
     // order of column selection is preserved
     assertEquals("[3, 1]", cs2.getSelected().toString());
-    assertEquals(2, cs2.getHiddenColumns().size());
-    // hidden columns are held in column order
-    assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0)));
-    assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1)));
   }
 
-  /**
-   * Test for the case when a hidden range encloses more one already hidden
-   * range
-   */
-  @Test(groups = { "Functional" })
-  public void testHideColumns_subsumingHidden()
-  {
-    /*
-     * JAL-2370 bug scenario:
-     * two hidden ranges subsumed by a third
-     */
-    ColumnSelection cs = new ColumnSelection();
-    cs.hideColumns(49, 59);
-    cs.hideColumns(69, 79);
-    List<int[]> hidden = cs.getHiddenColumns();
-    assertEquals(2, hidden.size());
-    assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
-    assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
-  
-    cs.hideColumns(48, 80);
-    hidden = cs.getHiddenColumns();
-    assertEquals(1, hidden.size());
-    assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
-
-    /*
-     * another...joining hidden ranges
-     */
-    cs = new ColumnSelection();
-    cs.hideColumns(10, 20);
-    cs.hideColumns(30, 40);
-    cs.hideColumns(50, 60);
-    // hiding 21-49 should merge to one range
-    cs.hideColumns(21, 49);
-    hidden = cs.getHiddenColumns();
-    assertEquals(1, hidden.size());
-    assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
-
-    /*
-     * another...lef overlap, subsumption, right overlap,
-     * no overlap of existing hidden ranges
-     */
-    cs = new ColumnSelection();
-    cs.hideColumns(10, 20);
-    cs.hideColumns(10, 20);
-    cs.hideColumns(30, 35);
-    cs.hideColumns(40, 50);
-    cs.hideColumns(60, 70);
-
-    cs.hideColumns(15, 45);
-    hidden = cs.getHiddenColumns();
-    assertEquals(2, hidden.size());
-    assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
-    assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
-  }
 
   @Test(groups = { "Functional" })
   public void testStretchGroup_expand()
diff --git a/test/jalview/datamodel/HiddenColumnsTest.java b/test/jalview/datamodel/HiddenColumnsTest.java
new file mode 100644 (file)
index 0000000..10808d6
--- /dev/null
@@ -0,0 +1,588 @@
+/*
+ * 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.datamodel;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertSame;
+import static org.testng.AssertJUnit.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.gui.JvOptionPane;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Random;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class HiddenColumnsTest
+{
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  /**
+   * Test the method which counts the number of hidden columns
+   */
+  @Test(groups = { "Functional" })
+  public void testGetSize()
+  {
+    HiddenColumns hidden = new HiddenColumns();
+    assertEquals(0, hidden.getSize());
+
+    hidden.hideColumns(3, 5);
+    assertEquals(3, hidden.getSize());
+
+    hidden.hideColumns(8, 8);
+    assertEquals(4, hidden.getSize());
+
+    hidden.hideColumns(9, 14);
+    assertEquals(10, hidden.getSize());
+
+    ColumnSelection cs = new ColumnSelection();
+    hidden.revealAllHiddenColumns(cs);
+    assertEquals(0, hidden.getSize());
+  }
+
+  /**
+   * Test the method that finds the visible column position of an alignment
+   * column, allowing for hidden columns.
+   */
+  @Test(groups = { "Functional" })
+  public void testFindColumnPosition()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    assertEquals(5, cs.findColumnPosition(5));
+
+    // hiding column 6 makes no difference
+    cs.hideColumns(6, 6);
+    assertEquals(5, cs.findColumnPosition(5));
+
+    // hiding column 4 moves column 5 to column 4
+    cs.hideColumns(4, 4);
+    assertEquals(4, cs.findColumnPosition(5));
+
+    // hiding column 4 moves column 4 to position 3
+    assertEquals(3, cs.findColumnPosition(4));
+
+    // hiding columns 1 and 2 moves column 5 to column 2
+    cs.hideColumns(1, 2);
+    assertEquals(2, cs.findColumnPosition(5));
+
+    // check with > 1 hidden column regions
+    // where some columns are in the hidden regions
+    HiddenColumns cs2 = new HiddenColumns();
+    cs2.hideColumns(5, 10);
+    cs2.hideColumns(20, 27);
+    cs2.hideColumns(40, 44);
+
+    // hiding columns 5-10 and 20-27 moves column 8 to column 4
+    assertEquals(4, cs2.findColumnPosition(8));
+
+    // and moves column 24 to 13
+    assertEquals(13, cs2.findColumnPosition(24));
+
+    // and moves column 28 to 14
+    assertEquals(14, cs2.findColumnPosition(28));
+
+    // and moves column 40 to 25
+    assertEquals(25, cs2.findColumnPosition(40));
+
+    // check when hidden columns start at 0 that the visible column
+    // is returned as 0
+    HiddenColumns cs3 = new HiddenColumns();
+    cs3.hideColumns(0, 4);
+    assertEquals(0, cs3.findColumnPosition(2));
+
+  }
+
+  /**
+   * Test the method that finds the visible column position a given distance
+   * before another column
+   */
+  @Test(groups = { "Functional" })
+  public void testFindColumnNToLeft()
+  {
+    HiddenColumns cs = new HiddenColumns();
+
+    // test that without hidden columns, findColumnNToLeft returns
+    // position n to left of provided position
+    int pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to left returns negative number
+    pos = cs.subtractVisibleColumns(3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden columns to left of result column
+    // behaviour is the same as above
+    cs.hideColumns(1, 3);
+
+    // position n to left of provided position
+    pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    ColumnSelection colsel = new ColumnSelection();
+    cs.revealAllHiddenColumns(colsel);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetVisibleContigs()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(3, 6);
+    cs.hideColumns(8, 9);
+    cs.hideColumns(12, 12);
+
+    // start position is inclusive, end position exclusive:
+    int[] visible = cs.getVisibleContigs(1, 13);
+    assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
+
+    visible = cs.getVisibleContigs(4, 14);
+    assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
+
+    visible = cs.getVisibleContigs(3, 10);
+    assertEquals("[7, 7]", Arrays.toString(visible));
+
+    visible = cs.getVisibleContigs(4, 6);
+    assertEquals("[]", Arrays.toString(visible));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testEquals()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(5, 9);
+
+    // a different set of hidden columns
+    HiddenColumns cs2 = new HiddenColumns();
+
+    // with no hidden columns
+    assertFalse(cs.equals(cs2));
+    assertFalse(cs2.equals(cs));
+
+    // with hidden columns added in a different order
+    cs2.hideColumns(6, 9);
+    cs2.hideColumns(5, 8);
+
+    assertTrue(cs.equals(cs2));
+    assertTrue(cs.equals(cs));
+    assertTrue(cs2.equals(cs));
+    assertTrue(cs2.equals(cs2));
+  }
+
+  @Test(groups = "Functional")
+  public void testCopyConstructor()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(10, 11);
+    cs.hideColumns(5, 7);
+    assertEquals("[5, 7]", Arrays.toString(cs.getHiddenRegions().get(0)));
+
+    HiddenColumns cs2 = new HiddenColumns(cs);
+    assertTrue(cs2.hasHiddenColumns());
+    assertEquals(2, cs2.getHiddenRegions().size());
+    // hidden columns are held in column order
+    assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenRegions().get(0)));
+    assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenRegions().get(1)));
+  }
+
+  /**
+   * Test the code used to locate the reference sequence ruler origin
+   */
+  @Test(groups = { "Functional" })
+  public void testLocateVisibleBoundsofSequence()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
+    HiddenColumns cs = al.getHiddenColumns();
+    ColumnSelection colsel = new ColumnSelection();
+
+    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
+    assertEquals(2, seq.findIndex(seq.getStart()));
+
+    // no hidden columns
+    assertEquals(
+            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
+                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1 }),
+            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+
+    // hidden column on gap after end of sequence - should not affect bounds
+    colsel.hideSelectedColumns(13, al.getHiddenColumns());
+    assertEquals(
+            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
+                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1 }),
+            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+
+    cs.revealAllHiddenColumns(colsel);
+    // hidden column on gap before beginning of sequence - should vis bounds by
+    // one
+    colsel.hideSelectedColumns(0, al.getHiddenColumns());
+    assertEquals(
+            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
+                seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
+                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1 }),
+            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+
+    cs.revealAllHiddenColumns(colsel);
+    // hide columns around most of sequence - leave one residue remaining
+    cs.hideColumns(1, 3);
+    cs.hideColumns(6, 11);
+    assertEquals("-D",
+            cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
+    assertEquals(
+            Arrays.toString(new int[] { 1, 1, 3, 3,
+                seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1 }),
+            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    cs.revealAllHiddenColumns(colsel);
+
+    // hide whole sequence - should just get location of hidden region
+    // containing sequence
+    cs.hideColumns(1, 11);
+    assertEquals(
+            Arrays.toString(new int[] { 0, 1, 0, 0,
+                seq.findIndex(seq.getStart()) - 1,
+                seq.findIndex(seq.getEnd()) - 1 }),
+            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+
+  }
+
+  @Test(groups = { "Functional" })
+  public void testLocateVisibleBoundsPathologicals()
+  {
+    // test some pathological cases we missed
+    AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
+            "refseqGaptest", "KTDVTI----------NFI-----G----L") });
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideInsertionsFor(al.getSequenceAt(0));
+    assertEquals(
+            "G",
+            ""
+                    + al.getSequenceAt(0).getCharAt(
+                            cs.adjustForHiddenColumns(9)));
+
+  }
+
+  @Test(groups = { "Functional" })
+  public void testHideColumns()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
+    ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns cs = al.getHiddenColumns();
+    colsel.hideSelectedColumns(5, al.getHiddenColumns());
+    List<int[]> hidden = cs.getHiddenRegions();
+    assertEquals(1, hidden.size());
+    assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
+
+    colsel.hideSelectedColumns(3, al.getHiddenColumns());
+    assertEquals(2, hidden.size());
+    // two hidden ranges, in order:
+    assertSame(hidden, cs.getHiddenRegions());
+    assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
+    assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
+
+    // hiding column 4 expands [3, 3] to [3, 4]
+    // and merges to [5, 5] to make [3, 5]
+    colsel.hideSelectedColumns(4, al.getHiddenColumns());
+    hidden = cs.getHiddenRegions();
+    assertEquals(1, hidden.size());
+    assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
+
+    // clear hidden columns (note they are added to selected)
+    cs.revealAllHiddenColumns(colsel);
+    // it is now actually null but getter returns an empty list
+    assertTrue(cs.getHiddenRegions().isEmpty());
+
+    cs.hideColumns(3, 6);
+    hidden = cs.getHiddenRegions();
+    int[] firstHiddenRange = hidden.get(0);
+    assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
+
+    // adding a subrange of already hidden should do nothing
+    cs.hideColumns(4, 5);
+    assertEquals(1, hidden.size());
+    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    cs.hideColumns(3, 5);
+    assertEquals(1, hidden.size());
+    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    cs.hideColumns(4, 6);
+    assertEquals(1, hidden.size());
+    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+    cs.hideColumns(3, 6);
+    assertEquals(1, hidden.size());
+    assertSame(firstHiddenRange, cs.getHiddenRegions().get(0));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(2, 4);
+    hidden = cs.getHiddenRegions();
+    assertEquals(1, hidden.size());
+    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
+
+    // extend contiguous with 2 positions overlap
+    cs.hideColumns(3, 5);
+    assertEquals(1, hidden.size());
+    assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
+
+    // extend contiguous with 1 position overlap
+    cs.hideColumns(5, 6);
+    assertEquals(1, hidden.size());
+    assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
+
+    // extend contiguous with overlap both ends:
+    cs.hideColumns(1, 7);
+    assertEquals(1, hidden.size());
+    assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
+  }
+
+  /**
+   * Test the method that reveals a range of hidden columns given the start
+   * column of the range
+   */
+  @Test(groups = { "Functional" })
+  public void testRevealHiddenColumns()
+  {
+    ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(5, 8);
+    colsel.addElement(10);
+    cs.revealHiddenColumns(5, colsel);
+    // hidden columns list now null but getter returns empty list:
+    assertTrue(cs.getHiddenRegions().isEmpty());
+    // revealed columns are marked as selected (added to selection):
+    assertEquals("[10, 5, 6, 7, 8]", colsel.getSelected().toString());
+
+    // calling with a column other than the range start does nothing:
+    colsel = new ColumnSelection();
+    cs = new HiddenColumns();
+    cs.hideColumns(5, 8);
+    List<int[]> hidden = cs.getHiddenRegions();
+    cs.revealHiddenColumns(6, colsel);
+    assertSame(hidden, cs.getHiddenRegions());
+    assertTrue(colsel.getSelected().isEmpty());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testRevealAllHiddenColumns()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    ColumnSelection colsel = new ColumnSelection();
+    cs.hideColumns(5, 8);
+    cs.hideColumns(2, 3);
+    colsel.addElement(11);
+    colsel.addElement(1);
+    cs.revealAllHiddenColumns(colsel);
+
+    /*
+     * revealing hidden columns adds them (in order) to the (unordered)
+     * selection list
+     */
+    assertTrue(cs.getHiddenRegions().isEmpty());
+    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", colsel.getSelected()
+            .toString());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testIsVisible()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(2, 4);
+    cs.hideColumns(6, 7);
+    assertTrue(cs.isVisible(0));
+    assertTrue(cs.isVisible(-99));
+    assertTrue(cs.isVisible(1));
+    assertFalse(cs.isVisible(2));
+    assertFalse(cs.isVisible(3));
+    assertFalse(cs.isVisible(4));
+    assertTrue(cs.isVisible(5));
+    assertFalse(cs.isVisible(6));
+    assertFalse(cs.isVisible(7));
+  }
+
+  /**
+   * Test for the case when a hidden range encloses more one already hidden
+   * range
+   */
+  @Test(groups = { "Functional" })
+  public void testHideColumns_subsumingHidden()
+  {
+    /*
+     * JAL-2370 bug scenario:
+     * two hidden ranges subsumed by a third
+     */
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(49, 59);
+    cs.hideColumns(69, 79);
+    List<int[]> hidden = cs.getHiddenRegions();
+    assertEquals(2, hidden.size());
+    assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
+    assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
+
+    cs.hideColumns(48, 80);
+    hidden = cs.getHiddenRegions();
+    assertEquals(1, hidden.size());
+    assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
+
+    /*
+     * another...joining hidden ranges
+     */
+    cs = new HiddenColumns();
+    cs.hideColumns(10, 20);
+    cs.hideColumns(30, 40);
+    cs.hideColumns(50, 60);
+    // hiding 21-49 should merge to one range
+    cs.hideColumns(21, 49);
+    hidden = cs.getHiddenRegions();
+    assertEquals(1, hidden.size());
+    assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
+
+    /*
+     * another...left overlap, subsumption, right overlap,
+     * no overlap of existing hidden ranges
+     */
+    cs = new HiddenColumns();
+    cs.hideColumns(10, 20);
+    cs.hideColumns(10, 20);
+    cs.hideColumns(30, 35);
+    cs.hideColumns(40, 50);
+    cs.hideColumns(60, 70);
+
+    cs.hideColumns(15, 45);
+    hidden = cs.getHiddenRegions();
+    assertEquals(2, hidden.size());
+    assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
+    assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testHideBitset()
+  {
+    HiddenColumns cs;
+
+    BitSet one = new BitSet();
+
+    // one hidden range
+    one.set(1);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    one.set(2);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    one.set(3);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    // split
+    one.clear(2);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(2, cs.getHiddenRegions().size());
+
+    assertEquals(0, cs.adjustForHiddenColumns(0));
+    assertEquals(2, cs.adjustForHiddenColumns(1));
+    assertEquals(4, cs.adjustForHiddenColumns(2));
+
+    // one again
+    one.clear(1);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    assertEquals(0, cs.adjustForHiddenColumns(0));
+    assertEquals(1, cs.adjustForHiddenColumns(1));
+    assertEquals(2, cs.adjustForHiddenColumns(2));
+    assertEquals(4, cs.adjustForHiddenColumns(3));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetBitset()
+  {
+    BitSet toMark, fromMark;
+    long seed = -3241532;
+    Random number = new Random(seed);
+    for (int n = 0; n < 1000; n++)
+    {
+      // create a random bitfield
+      toMark = BitSet.valueOf(new long[] { number.nextLong(),
+          number.nextLong(), number.nextLong() });
+      toMark.set(n * number.nextInt(10), n * (25 + number.nextInt(25)));
+      HiddenColumns hc = new HiddenColumns();
+      hc.hideMarkedBits(toMark);
+
+      // see if we can recover bitfield
+      hc.markHiddenRegions(fromMark = new BitSet());
+      assertEquals(toMark, fromMark);
+    }
+  }
+}
index cae3536..7795988 100644 (file)
@@ -49,7 +49,7 @@ public class HiddenSequencesTest
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  static int SEQ_COUNT = 10;
+  static int SEQ_COUNT = 25;
 
   SequenceI[] seqs;
 
@@ -62,8 +62,9 @@ public class HiddenSequencesTest
     seqs = new SequenceI[SEQ_COUNT];
     for (int i = 0; i < SEQ_COUNT; i++)
     {
-      // sequence lengths are 1, 2, ... 10
-      seqs[i] = new Sequence("Seq" + i, "abcdefghijk".substring(0, i + 1));
+      // sequence lengths are 1, 2, ... 25
+      seqs[i] = new Sequence("Seq" + i,
+              "abcdefghijklmnopqrstuvwxy".substring(0, i + 1));
     }
   }
 
@@ -89,7 +90,7 @@ public class HiddenSequencesTest
     /*
      * alignment is now seq0/2/3/4/7/8/9
      */
-    assertEquals(7, al.getHeight());
+    assertEquals(SEQ_COUNT - 3, al.getHeight());
     assertEquals(0, hs.adjustForHiddenSeqs(0));
     assertEquals(2, hs.adjustForHiddenSeqs(1));
     assertEquals(3, hs.adjustForHiddenSeqs(2));
@@ -193,7 +194,7 @@ public class HiddenSequencesTest
     /*
      * alignment is now seq0/2/3/4/7/8/9
      */
-    assertEquals(7, al.getHeight());
+    assertEquals(SEQ_COUNT - 3, al.getHeight());
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(0));
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(1));
     assertEquals(1, hs.findIndexWithoutHiddenSeqs(2));
@@ -207,6 +208,76 @@ public class HiddenSequencesTest
   }
 
   /**
+   * Test the method that finds the visible row position a given distance before
+   * another row
+   */
+  @Test(groups = { "Functional" })
+  public void testFindIndexNFromRow()
+  {
+    AlignmentI al = new Alignment(seqs);
+    HiddenSequences hs = new HiddenSequences(al);
+
+    // test that without hidden rows, findIndexNFromRow returns
+    // position n above provided position
+    int pos = hs.subtractVisibleRows(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = hs.subtractVisibleRows(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to top returns negative number
+    pos = hs.subtractVisibleRows(3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden rows above result row
+    // behaviour is the same as above
+    hs.hideSequence(seqs[1]);
+    hs.hideSequence(seqs[2]);
+    hs.hideSequence(seqs[3]);
+
+    // position n above provided position
+    pos = hs.subtractVisibleRows(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = hs.subtractVisibleRows(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden rows between start and required position
+    hs.hideSequence(seqs[12]);
+    hs.hideSequence(seqs[13]);
+    hs.hideSequence(seqs[14]);
+    hs.hideSequence(seqs[15]);
+    pos = hs.subtractVisibleRows(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden rows between start and required position
+    hs.hideSequence(seqs[20]);
+    hs.hideSequence(seqs[21]);
+    pos = hs.subtractVisibleRows(8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    hs.showAll(null);
+
+    // test with one set of hidden rows between start and required position
+    hs.hideSequence(seqs[12]);
+    hs.hideSequence(seqs[13]);
+    hs.hideSequence(seqs[14]);
+    hs.hideSequence(seqs[15]);
+    pos = hs.subtractVisibleRows(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden rows between start and required position
+    hs.hideSequence(seqs[20]);
+    hs.hideSequence(seqs[21]);
+    pos = hs.subtractVisibleRows(8, 23);
+    assertEquals(9, pos);
+
+  }
+
+  /**
    * Test the method that reconstructs (sort of) the full alignment including
    * hidden sequences
    */
@@ -289,7 +360,7 @@ public class HiddenSequencesTest
     assertTrue(al.getSequences().contains(seqs[1]));
     HiddenSequences hs = al.getHiddenSequences();
     assertEquals(0, hs.getSize());
-    assertEquals(10, al.getHeight());
+    assertEquals(SEQ_COUNT, al.getHeight());
 
     /*
      * hide the second sequence in the alignment
@@ -299,7 +370,7 @@ public class HiddenSequencesTest
     assertTrue(hs.isHidden(seqs[1]));
     assertFalse(al.getSequences().contains(seqs[1]));
     assertEquals(1, hs.getSize());
-    assertEquals(9, al.getHeight());
+    assertEquals(SEQ_COUNT - 1, al.getHeight());
     assertSame(seqs[2], al.getSequenceAt(1));
 
     /*
@@ -312,7 +383,7 @@ public class HiddenSequencesTest
     assertFalse(al.getSequences().contains(seqs[1]));
     assertFalse(al.getSequences().contains(seqs[2]));
     assertEquals(2, hs.getSize());
-    assertEquals(8, al.getHeight());
+    assertEquals(SEQ_COUNT - 2, al.getHeight());
 
     /*
      * perform 'reveal' on what is now the second sequence in the alignment
@@ -323,7 +394,54 @@ public class HiddenSequencesTest
     assertTrue(revealed.contains(seqs[1]));
     assertTrue(revealed.contains(seqs[2]));
     assertEquals(0, hs.getSize());
-    assertEquals(10, al.getHeight());
+    assertEquals(SEQ_COUNT, al.getHeight());
+  }
+
+  /**
+   * Test the method that adds a sequence to the hidden sequences and deletes it
+   * from the alignment, and its converse, where the first hidden sequences are
+   * at the bottom of the alignment (JAL-2437)
+   */
+  @Test(groups = "Functional")
+  public void testHideShowLastSequences()
+  {
+    AlignmentI al = new Alignment(seqs);
+    assertTrue(al.getSequences().contains(seqs[1]));
+    HiddenSequences hs = al.getHiddenSequences();
+    assertEquals(0, hs.getSize());
+    assertEquals(SEQ_COUNT, al.getHeight());
+
+    /*
+     * hide the last sequence in the alignment
+     */
+    hs.hideSequence(seqs[SEQ_COUNT - 1]);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 2]));
+    assertTrue(hs.isHidden(seqs[SEQ_COUNT - 1]));
+    assertFalse(al.getSequences().contains(seqs[SEQ_COUNT - 1]));
+    assertEquals(1, hs.getSize());
+    assertEquals(SEQ_COUNT - 1, al.getHeight());
+
+    /*
+     * hide the third last sequence in the alignment
+     */
+    hs.hideSequence(seqs[SEQ_COUNT - 3]);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 2]));
+    assertTrue(hs.isHidden(seqs[SEQ_COUNT - 3]));
+    assertFalse(al.getSequences().contains(seqs[SEQ_COUNT - 3]));
+    assertEquals(2, hs.getSize());
+    assertEquals(SEQ_COUNT - 2, al.getHeight());
+
+    /*
+     * reveal all the sequences, which should be reinstated in the same order as they started in
+     */
+    hs.showAll(null);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 3]));
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 1]));
+    assertEquals(seqs[SEQ_COUNT - 3], al.getSequences().get(SEQ_COUNT - 3));
+    assertEquals(seqs[SEQ_COUNT - 2], al.getSequences().get(SEQ_COUNT - 2));
+    assertEquals(seqs[SEQ_COUNT - 1], al.getSequences().get(SEQ_COUNT - 1));
+    assertEquals(0, hs.getSize());
+    assertEquals(SEQ_COUNT, al.getHeight());
   }
 
   @Test(groups = "Functional")
index 243bd53..f6d4028 100644 (file)
@@ -3,18 +3,24 @@ package jalview.datamodel;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 import jalview.schemes.NucleotideColourScheme;
+import jalview.schemes.PIDColourScheme;
+
+import java.awt.Color;
+
+import junit.extensions.PA;
 
 import org.testng.annotations.Test;
 
 public class SequenceGroupTest
 {
-  @Test
+  @Test(groups={"Functional"})
   public void testAddSequence()
   {
     SequenceGroup sg = new SequenceGroup();
@@ -42,7 +48,7 @@ public class SequenceGroupTest
     assertTrue(sg.getSequences().contains(seq3));
   }
 
-  @Test
+  @Test(groups={"Functional"})
   public void testAddOrRemove()
   {
     SequenceGroup sg = new SequenceGroup();
@@ -66,7 +72,7 @@ public class SequenceGroupTest
     assertFalse(sg.getSequences().contains(seq1));
   }
 
-  @Test
+  @Test(groups={"Functional"})
   public void testGetColourScheme()
   {
     SequenceGroup sg = new SequenceGroup();
@@ -81,7 +87,7 @@ public class SequenceGroupTest
     assertSame(scheme, sg.getColourScheme());
   }
 
-  @Test
+  @Test(groups={"Functional"})
   public void testSetContext()
   {
     SequenceGroup sg1 = new SequenceGroup();
@@ -111,5 +117,177 @@ public class SequenceGroupTest
       // expected
       assertNull(sg3.getContext());
     }
+
+    /*
+     * use PrivilegedAccessor to 'force' a SequenceGroup with
+     * a circular context reference
+     */
+    PA.setValue(sg2, "context", sg2);
+    try
+    {
+      sg3.setContext(sg2, false); // circular reference in sg2
+      fail("Expected exception");
+    } catch (IllegalArgumentException e)
+    {
+      // expected
+      assertNull(sg3.getContext());
+    }
+
+    // test isDefined setting behaviour
+    sg2 = new SequenceGroup();
+    sg1.setContext(null, false);
+    assertFalse(sg1.isDefined());
+
+    sg1.setContext(sg2, false);
+    assertFalse(sg1.isDefined());
+
+    sg1.setContext(sg2, true);
+    assertTrue(sg1.isDefined());
+
+    // setContext without defined parameter does not change isDefined
+    sg1.setContext(null);
+    assertTrue(sg1.isDefined());
+
+    sg1.setContext(null, false);
+    sg1.setContext(sg2);
+    assertFalse(sg1.isDefined());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testContains()
+  {
+    /* 
+     * essentially the same tests as AlignmentI.findGroup 
+     * but from a particular group's perspective
+     */
+
+    SequenceI seq1 = new Sequence("seq1", "ABCDEF---GHI");
+    SequenceI seq2 = new Sequence("seq2", "---JKLMNO---");
+    AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2 });
+    /*
+     * add a group consisting of just "DEF"
+     */
+    SequenceGroup sg1 = new SequenceGroup();
+    sg1.addSequence(seq1, false);
+    sg1.setStartRes(3);
+    sg1.setEndRes(5);
+
+    /*
+     * test sequence membership
+     */
+    assertTrue(sg1.contains(seq1));
+    assertFalse(sg1.contains(seq2));
+
+    /*
+     * test sequence+position
+     */
+
+    assertFalse(sg1.contains(seq1, 2)); // position not in group
+    assertFalse(sg1.contains(seq1, 6)); // position not in group
+    assertFalse(sg1.contains(seq2, 5)); // sequence not in group
+    assertTrue(sg1.contains(seq1, 3)); // yes
+    assertTrue(sg1.contains(seq1, 4));
+    assertTrue(sg1.contains(seq1, 5));
+
+    /*
+     * add a group consisting of 
+     * EF--
+     * KLMN
+     */
+    SequenceGroup sg2 = new SequenceGroup();
+    sg2.addSequence(seq1, false);
+    sg2.addSequence(seq2, false);
+    sg2.setStartRes(4);
+    sg2.setEndRes(7);
+    a.addGroup(sg2);
+
+    /*
+     * if a residue is in more than one group, method returns
+     * the first found (in order groups were added)
+     */
+    assertTrue(sg2.contains(seq1, 4));
+    assertTrue(sg2.contains(seq1, 5));
+
+    /*
+     * seq2 only belongs to the second group
+     */
+    assertTrue(sg2.contains(seq2, 4));
+    assertTrue(sg2.contains(seq2, 5));
+    assertTrue(sg2.contains(seq2, 6));
+    assertTrue(sg2.contains(seq2, 7));
+    assertFalse(sg2.contains(seq2, 3));
+    assertFalse(sg2.contains(seq2, 8));
+    sg2.setEndRes(8);
+    assertTrue(sg2.contains(seq2, 8));
+    sg2.deleteSequence(seq2, false);
+    assertFalse(sg2.contains(seq2));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testCopyConstructor()
+  {
+    SequenceI seq = new Sequence("seq", "ABC");
+    SequenceGroup sg = new SequenceGroup();
+    sg.addSequence(seq, false);
+    sg.setName("g1");
+    sg.setDescription("desc");
+    sg.setColourScheme(new PIDColourScheme());
+    sg.setDisplayBoxes(false);
+    sg.setDisplayText(false);
+    sg.setColourText(true);
+    sg.isDefined = true;
+    sg.setShowNonconserved(true);
+    sg.setOutlineColour(Color.red);
+    sg.setIdColour(Color.blue);
+    sg.thresholdTextColour = 1;
+    sg.textColour = Color.orange;
+    sg.textColour2 = Color.yellow;
+    sg.setIgnoreGapsConsensus(false);
+    sg.setshowSequenceLogo(true);
+    sg.setNormaliseSequenceLogo(true);
+    sg.setHidereps(true);
+    sg.setHideCols(true);
+    sg.setShowConsensusHistogram(true);
+    sg.setContext(new SequenceGroup());
+
+    SequenceGroup sg2 = new SequenceGroup(sg);
+    assertEquals(sg2.getName(), sg.getName());
+    assertEquals(sg2.getDescription(), sg.getDescription());
+    assertNotSame(sg2.getGroupColourScheme(), sg.getGroupColourScheme());
+    assertSame(sg2.getColourScheme(), sg.getColourScheme());
+    assertEquals(sg2.getDisplayBoxes(), sg.getDisplayBoxes());
+    assertEquals(sg2.getDisplayText(), sg.getDisplayText());
+    assertEquals(sg2.getColourText(), sg.getColourText());
+    assertEquals(sg2.getShowNonconserved(), sg.getShowNonconserved());
+    assertEquals(sg2.getOutlineColour(), sg.getOutlineColour());
+    assertEquals(sg2.getIdColour(), sg.getIdColour());
+    assertEquals(sg2.thresholdTextColour, sg.thresholdTextColour);
+    assertEquals(sg2.textColour, sg.textColour);
+    assertEquals(sg2.textColour2, sg.textColour2);
+    assertEquals(sg2.getIgnoreGapsConsensus(), sg.getIgnoreGapsConsensus());
+    assertEquals(sg2.isShowSequenceLogo(), sg.isShowSequenceLogo());
+    assertEquals(sg2.isNormaliseSequenceLogo(),
+            sg.isNormaliseSequenceLogo());
+    assertEquals(sg2.isHidereps(), sg.isHidereps());
+    assertEquals(sg2.isHideCols(), sg.isHideCols());
+    assertEquals(sg2.isShowConsensusHistogram(),
+            sg.isShowConsensusHistogram());
+
+    /*
+     * copy of sequences
+     */
+    assertNotSame(sg2.getSequences(), sg.getSequences());
+    assertEquals(sg2.getSequences(), sg.getSequences());
+
+    /*
+     * isDefined should only be set true when a new group is added to
+     * an alignment, not in the copy constructor
+     */
+    assertFalse(sg2.isDefined());
+
+    /*
+     * context should be set explicitly, not by copy
+     */
+    assertNull(sg2.getContext());
   }
 }
index 08e6f7d..a52f0a2 100644 (file)
@@ -35,6 +35,7 @@ import jalview.util.MapList;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 import java.util.Vector;
 
@@ -74,6 +75,18 @@ public class SequenceTest
     assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
     assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
     assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
+
+    BitSet gapfield = aseq.getInsertionsAsBits();
+    BitSet expectedgaps = new BitSet();
+    expectedgaps.set(2, 5);
+    expectedgaps.set(6, 9);
+
+    assertEquals(6, expectedgaps.cardinality());
+
+    assertEquals("getInsertionsAsBits didn't mark expected number of gaps",
+            6, gapfield.cardinality());
+
+    assertEquals("getInsertionsAsBits not correct.", expectedgaps, gapfield);
   }
 
   @Test(groups = ("Functional"))
diff --git a/test/jalview/datamodel/VisibleColsIteratorTest.java b/test/jalview/datamodel/VisibleColsIteratorTest.java
new file mode 100644 (file)
index 0000000..b2d747b
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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.datamodel;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.NoSuchElementException;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class VisibleColsIteratorTest
+{
+  HiddenColumns hiddenCols;
+
+  HiddenColumns hiddenColsAtStart;
+
+  @BeforeClass(groups = { "Functional" })
+  public void setup()
+  {
+    hiddenCols = new HiddenColumns();
+    hiddenCols.hideColumns(2, 4);
+
+    hiddenColsAtStart = new HiddenColumns();
+    hiddenColsAtStart.hideColumns(0, 2);
+  }
+
+  /*
+   * Test iterator iterates correctly through the columns
+   * when alignment has hidden cols
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextWithHidden()
+  {
+    VisibleColsIterator it = new VisibleColsIterator(0, 6, hiddenCols);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the columns
+   * when alignment has no hidden cols
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextNoHidden()
+  {
+    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
+            new HiddenColumns());
+    int count = 0;
+    while (it2.hasNext())
+    {
+      it2.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the columns
+   * when alignment has hidden cols at start
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextStartHidden()
+  {
+    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
+            hiddenColsAtStart);
+    int count = 0;
+    while (it3.hasNext())
+    {
+      it3.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the columns
+   * when alignment has hidden cols at end
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextEndHidden()
+  {
+    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    int count = 0;
+    while (it4.hasNext())
+    {
+      it4.next();
+      count++;
+    }
+    assertTrue(count == 2, "hasNext() is false after 2 iterations");
+
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden cols
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextWithHidden() throws NoSuchElementException
+  {
+    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has no hidden cols
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextNoHidden() throws NoSuchElementException
+  {
+    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
+            new HiddenColumns());
+    while (it2.hasNext())
+    {
+      it2.next();
+    }
+    it2.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden cols at start
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextStartHidden() throws NoSuchElementException
+  {
+    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
+            hiddenColsAtStart);
+    while (it3.hasNext())
+    {
+      it3.next();
+    }
+    it3.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden cols at end
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextEndHidden() throws NoSuchElementException
+  {
+    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    while (it4.hasNext())
+    {
+      it4.next();
+    }
+    it4.next();
+  }
+
+  /*
+   * Test calls to remove throw UnsupportedOperationException
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { UnsupportedOperationException.class })
+  public void testRemove() throws UnsupportedOperationException
+  {
+    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    it.remove();
+  }
+}
diff --git a/test/jalview/datamodel/VisibleRowsIteratorTest.java b/test/jalview/datamodel/VisibleRowsIteratorTest.java
new file mode 100644 (file)
index 0000000..4a021c5
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * 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.datamodel;
+
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class VisibleRowsIteratorTest
+{
+  AlignmentI al;
+
+  AlignmentI al2;
+
+  AlignmentI al3;
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences2 = new Hashtable<SequenceI, SequenceCollectionI>();
+
+  @BeforeClass(groups = { "Functional" })
+  public void setup()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    al = gen.generate(20, 15, 123, 5, 5);
+    if (!hiddenRepSequences.isEmpty())
+    {
+      al.getHiddenSequences().showAll(hiddenRepSequences);
+    }
+    hideSequences(al, hiddenRepSequences, 2, 4);
+
+    al2 = gen.generate(20, 15, 123, 5, 5);
+    if (!hiddenRepSequences2.isEmpty())
+    {
+      al2.getHiddenSequences().showAll(hiddenRepSequences2);
+    }
+    hideSequences(al2, hiddenRepSequences2, 0, 2);
+
+    al3 = gen.generate(20, 15, 123, 5, 5);
+  }
+
+  /*
+   * Test iterator iterates correctly through the rows
+   * when alignment has hidden rows
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextWithHidden()
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 6, al);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the rows
+   * when alignment has no hidden rows
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextNoHidden()
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 3, al3);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the rows
+   * when alignment has hidden rows at start
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextStartHidden()
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 6, al2);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 4, "hasNext() is false after 4 iterations");
+  }
+
+  /*
+   * Test iterator iterates correctly through the rows
+   * when alignment has hidden rows at end
+   */
+  @Test(groups = { "Functional" })
+  public void testHasNextAndNextEndHidden()
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 4, al);
+    int count = 0;
+    while (it.hasNext())
+    {
+      it.next();
+      count++;
+    }
+    assertTrue(count == 2, "hasNext() is false after 2 iterations");
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden rows
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextWithHidden() throws NoSuchElementException
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 3, al);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has no hidden rows
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextNoHidden() throws NoSuchElementException
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 3, al3);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden rows at start
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextStartHidden() throws NoSuchElementException
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 3, al2);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test iterator always throws NoSuchElementException at end of iteration
+   * when alignment has hidden rows at end
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { NoSuchElementException.class })
+  public void testLastNextEndHidden() throws NoSuchElementException
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 4, al);
+    while (it.hasNext())
+    {
+      it.next();
+    }
+    it.next();
+  }
+
+  /*
+   * Test calls to remove throw UnsupportedOperationException
+   */
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { UnsupportedOperationException.class })
+  public void testRemove() throws UnsupportedOperationException
+  {
+    VisibleRowsIterator it = new VisibleRowsIterator(0, 3, al);
+    it.remove();
+  }
+
+  /*
+   * Hide sequences between start and end
+   */
+  private void hideSequences(AlignmentI alignment,
+          Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences,
+          int start, int end)
+  {
+    SequenceI[] allseqs = alignment.getSequencesArray();
+    SequenceGroup theseSeqs = new SequenceGroup();
+
+    for (int i = start; i <= end; i++)
+    {
+      theseSeqs.addSequence(allseqs[i], false);
+      alignment.getHiddenSequences().hideSequence(allseqs[i]);
+    }
+
+    hiddenRepSequences.put(allseqs[start], theseSeqs);
+  }
+}
diff --git a/test/jalview/ext/android/SparseDoubleArrayTest.java b/test/jalview/ext/android/SparseDoubleArrayTest.java
new file mode 100644 (file)
index 0000000..7d64a28
--- /dev/null
@@ -0,0 +1,53 @@
+package jalview.ext.android;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+public class SparseDoubleArrayTest
+{
+
+  @Test
+  public void testConstructor()
+  {
+    double[] d = new double[] { 0d, 0d, 1.2d, 0d, 0d, 3.4d };
+    SparseDoubleArray s = new SparseDoubleArray(d);
+    for (int i = 0; i < d.length; i++)
+    {
+      assertEquals(s.get(i), d[i], "At [" + i + "]");
+    }
+  }
+
+  @Test
+  public void testAdd()
+  {
+    double[] d = new double[] { 0d, 0d, 1.2d, 0d, 0d, 3.4d };
+    SparseDoubleArray s = new SparseDoubleArray(d);
+    // add to zero (absent)
+    s.add(0, 3.2d);
+    assertEquals(s.get(0), 3.2d);
+    // add to non-zero
+    s.add(0, 2.5d);
+    assertEquals(s.get(0), 5.7d);
+    // add negative value
+    s.add(2, -5.3d);
+    assertEquals(s.get(2), -4.1d);
+    // add to unset value
+    s.add(12, 9.8d);
+    assertEquals(s.get(12), 9.8d);
+  }
+
+  @Test
+  public void testDivide()
+  {
+    double delta = 1.0e-10;
+    double[] d = new double[] { 0d, 2.4d, 1.2d, 0d, -4.8d, -3.6d };
+    SparseDoubleArray s = new SparseDoubleArray(d);
+    assertEquals(s.divide(0, 1d), 0d); // no such entry
+    assertEquals(s.divide(2, 0d), 0d); // zero divisor
+    assertEquals(s.divide(1, 2d), 1.2d, delta); // + / +
+    assertEquals(s.divide(2, -2d), -0.6d, delta); // + / -
+    assertEquals(s.divide(4, 3d), -1.6d, delta); // - / +
+    assertEquals(s.divide(5, -3d), 1.2d, delta); // - / -
+  }
+}
index 439e188..3309adf 100644 (file)
  */
 package jalview.ext.jmol;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
+import jalview.schemes.JalviewColourScheme;
+import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
+import java.util.HashMap;
+
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -58,6 +66,77 @@ public class JmolCommandsTest
     // need some mappings!
 
     StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, null, al);
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetColourBySequenceCommands_hiddenColumns()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    StructureSelectionManager ssm = new StructureSelectionManager();
+  
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+  
+    StructureMappingcommandSet[] commands = JmolCommands
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+    assertEquals(commands.length, 2);
+    assertEquals(commands[0].commands.length, 1);
+
+    String chainACommand = commands[0].commands[0];
+    // M colour is #82827d == (130, 130, 125) (see strand.html help page)
+    assertTrue(chainACommand
+            .contains(";select 21:A/1.1;color[130,130,125]"));
+    // H colour is #60609f == (96, 96, 159)
+    assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(chainACommand
+            .contains(";select 23-25:A/1.1;color[128,128,128]"));
+    // S and G are both coloured #4949b6 == (73, 73, 182)
+    assertTrue(chainACommand
+            .contains(";select 26-30:A/1.1;color[73,73,182]"));
+
+    String chainBCommand = commands[1].commands[0];
+    // M colour is #82827d == (130, 130, 125)
+    assertTrue(chainBCommand
+            .contains(";select 21:B/2.1;color[130,130,125]"));
+    // V colour is #ffff00 == (255, 255, 0)
+    assertTrue(chainBCommand
+.contains(";select 22:B/2.1;color[255,255,0]"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(chainBCommand
+            .contains(";select 23-25:B/2.1;color[128,128,128]"));
+    // S and G are both coloured #4949b6 == (73, 73, 182)
+    assertTrue(chainBCommand
+            .contains(";select 26-30:B/2.1;color[73,73,182]"));
   }
 }
index 131ef41..36e9b20 100644 (file)
@@ -272,7 +272,7 @@ public class JmolParserTest
      * local structure files should yield a false ID based on the filename
      */
     assertNotNull(structureData.getId());
-    assertEquals(structureData.getId(), "localstruct.pdb");
+    assertEquals(structureData.getId(), "localstruct");
     assertNotNull(structureData.getSeqs());
     /*
      * the ID is also the group for features derived from structure data 
@@ -280,7 +280,7 @@ public class JmolParserTest
     assertNotNull(structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup);
     assertEquals(
             structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup,
-            "localstruct.pdb");
+            "localstruct");
 
   }
 }
index 959ecab..792f7ad 100644 (file)
@@ -24,6 +24,7 @@ import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
+import jalview.bin.Jalview;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
@@ -53,8 +54,7 @@ public class JmolViewerTest
   @BeforeClass(alwaysRun = true)
   public static void setUpBeforeClass() throws Exception
   {
-    jalview.bin.Jalview.main(new String[] {
-        "-noquestionnaire -nonews -props",
+    Jalview.main(new String[] { "-noquestionnaire", "-nonews", "-props",
         "test/jalview/ext/rbvi/chimera/testProps.jvprops" });
   }
 
@@ -115,4 +115,6 @@ public class JmolViewerTest
       }
     }
   }
+
+
 }
diff --git a/test/jalview/ext/rbvi/chimera/4zho.pdb b/test/jalview/ext/rbvi/chimera/4zho.pdb
new file mode 100644 (file)
index 0000000..e4ee6df
--- /dev/null
@@ -0,0 +1,3526 @@
+HEADER    ELECTRON TRANSPORT                      26-APR-15   4ZHO              
+TITLE     THE CRYSTAL STRUCTURE OF ARABIDOPSIS FERREDOXIN 2 WITH 2FE-2S CLUSTER 
+COMPND    MOL_ID: 1;                                                            
+COMPND   2 MOLECULE: FERREDOXIN-2, CHLOROPLASTIC;                               
+COMPND   3 CHAIN: A, B;                                                         
+COMPND   4 SYNONYM: ATFD2;                                                      
+COMPND   5 ENGINEERED: YES                                                      
+SOURCE    MOL_ID: 1;                                                            
+SOURCE   2 ORGANISM_SCIENTIFIC: ARABIDOPSIS THALIANA;                           
+SOURCE   3 ORGANISM_COMMON: MOUSE-EAR CRESS;                                    
+SOURCE   4 ORGANISM_TAXID: 3702;                                                
+SOURCE   5 GENE: FD2, PETF, PETF1, AT1G60950, T7P1.9;                           
+SOURCE   6 EXPRESSION_SYSTEM: ESCHERICHIA COLI BL21(DE3);                       
+SOURCE   7 EXPRESSION_SYSTEM_TAXID: 469008                                      
+KEYWDS    FERREDOXIN 2FE-2S CLUSTER ELECTRON TRANSFER CHLOROPLAST, ELECTRON     
+KEYWDS   2 TRANSPORT                                                            
+EXPDTA    X-RAY DIFFRACTION                                                     
+AUTHOR    R.GRINTER,I.JOSTS,A.W.ROSZAK,R.J.COGDELL,D.WALKER                     
+REVDAT   2   09-NOV-16 4ZHO    1       JRNL                                     
+REVDAT   1   31-AUG-16 4ZHO    0                                                
+JRNL        AUTH   R.GRINTER,I.JOSTS,K.MOSBAHI,A.W.ROSZAK,R.J.COGDELL,          
+JRNL        AUTH 2 A.M.BONVIN,J.J.MILNER,S.M.KELLY,O.BYRON,B.O.SMITH,D.WALKER   
+JRNL        TITL   STRUCTURE OF THE BACTERIAL PLANT-FERREDOXIN RECEPTOR FUSA.   
+JRNL        REF    NAT COMMUN                    V.   7 13308 2016              
+JRNL        REFN                   ESSN 2041-1723                               
+JRNL        PMID   27796364                                                     
+JRNL        DOI    10.1038/NCOMMS13308                                          
+REMARK   2                                                                      
+REMARK   2 RESOLUTION.    2.34 ANGSTROMS.                                       
+REMARK   3                                                                      
+REMARK   3 REFINEMENT.                                                          
+REMARK   3   PROGRAM     : REFMAC 5.8.0049                                      
+REMARK   3   AUTHORS     : MURSHUDOV,VAGIN,DODSON                               
+REMARK   3                                                                      
+REMARK   3    REFINEMENT TARGET : MAXIMUM LIKELIHOOD                            
+REMARK   3                                                                      
+REMARK   3  DATA USED IN REFINEMENT.                                            
+REMARK   3   RESOLUTION RANGE HIGH (ANGSTROMS) : 2.34                           
+REMARK   3   RESOLUTION RANGE LOW  (ANGSTROMS) : 60.73                          
+REMARK   3   DATA CUTOFF            (SIGMA(F)) : NULL                           
+REMARK   3   COMPLETENESS FOR RANGE        (%) : 99.5                           
+REMARK   3   NUMBER OF REFLECTIONS             : 12221                          
+REMARK   3                                                                      
+REMARK   3  FIT TO DATA USED IN REFINEMENT.                                     
+REMARK   3   CROSS-VALIDATION METHOD          : THROUGHOUT                      
+REMARK   3   FREE R VALUE TEST SET SELECTION  : RANDOM                          
+REMARK   3   R VALUE     (WORKING + TEST SET) : 0.198                           
+REMARK   3   R VALUE            (WORKING SET) : 0.197                           
+REMARK   3   FREE R VALUE                     : 0.216                           
+REMARK   3   FREE R VALUE TEST SET SIZE   (%) : 4.900                           
+REMARK   3   FREE R VALUE TEST SET COUNT      : 627                             
+REMARK   3                                                                      
+REMARK   3  FIT IN THE HIGHEST RESOLUTION BIN.                                  
+REMARK   3   TOTAL NUMBER OF BINS USED           : 20                           
+REMARK   3   BIN RESOLUTION RANGE HIGH       (A) : 2.34                         
+REMARK   3   BIN RESOLUTION RANGE LOW        (A) : 2.40                         
+REMARK   3   REFLECTION IN BIN     (WORKING SET) : 864                          
+REMARK   3   BIN COMPLETENESS (WORKING+TEST) (%) : 98.58                        
+REMARK   3   BIN R VALUE           (WORKING SET) : 0.2390                       
+REMARK   3   BIN FREE R VALUE SET COUNT          : 39                           
+REMARK   3   BIN FREE R VALUE                    : 0.3090                       
+REMARK   3                                                                      
+REMARK   3  NUMBER OF NON-HYDROGEN ATOMS USED IN REFINEMENT.                    
+REMARK   3   PROTEIN ATOMS            : 1440                                    
+REMARK   3   NUCLEIC ACID ATOMS       : 0                                       
+REMARK   3   HETEROGEN ATOMS          : 10                                      
+REMARK   3   SOLVENT ATOMS            : 38                                      
+REMARK   3                                                                      
+REMARK   3  B VALUES.                                                           
+REMARK   3   FROM WILSON PLOT           (A**2) : NULL                           
+REMARK   3   MEAN B VALUE      (OVERALL, A**2) : 59.55                          
+REMARK   3   OVERALL ANISOTROPIC B VALUE.                                       
+REMARK   3    B11 (A**2) : 2.75000                                              
+REMARK   3    B22 (A**2) : 2.75000                                              
+REMARK   3    B33 (A**2) : -5.50000                                             
+REMARK   3    B12 (A**2) : 0.00000                                              
+REMARK   3    B13 (A**2) : 0.00000                                              
+REMARK   3    B23 (A**2) : 0.00000                                              
+REMARK   3                                                                      
+REMARK   3  ESTIMATED OVERALL COORDINATE ERROR.                                 
+REMARK   3   ESU BASED ON R VALUE                            (A): 0.229         
+REMARK   3   ESU BASED ON FREE R VALUE                       (A): 0.180         
+REMARK   3   ESU BASED ON MAXIMUM LIKELIHOOD                 (A): 0.136         
+REMARK   3   ESU FOR B VALUES BASED ON MAXIMUM LIKELIHOOD (A**2): 12.808        
+REMARK   3                                                                      
+REMARK   3 CORRELATION COEFFICIENTS.                                            
+REMARK   3   CORRELATION COEFFICIENT FO-FC      : 0.949                         
+REMARK   3   CORRELATION COEFFICIENT FO-FC FREE : 0.952                         
+REMARK   3                                                                      
+REMARK   3  RMS DEVIATIONS FROM IDEAL VALUES        COUNT    RMS    WEIGHT      
+REMARK   3   BOND LENGTHS REFINED ATOMS        (A):  1468 ; 0.017 ; 0.019       
+REMARK   3   BOND LENGTHS OTHERS               (A):  1296 ; 0.001 ; 0.020       
+REMARK   3   BOND ANGLES REFINED ATOMS   (DEGREES):  1990 ; 1.933 ; 1.975       
+REMARK   3   BOND ANGLES OTHERS          (DEGREES):  3026 ; 0.912 ; 3.000       
+REMARK   3   TORSION ANGLES, PERIOD 1    (DEGREES):   192 ; 7.288 ; 5.000       
+REMARK   3   TORSION ANGLES, PERIOD 2    (DEGREES):    66 ;34.298 ;26.970       
+REMARK   3   TORSION ANGLES, PERIOD 3    (DEGREES):   234 ;14.918 ;15.000       
+REMARK   3   TORSION ANGLES, PERIOD 4    (DEGREES):     2 ; 5.922 ;15.000       
+REMARK   3   CHIRAL-CENTER RESTRAINTS       (A**3):   230 ; 0.115 ; 0.200       
+REMARK   3   GENERAL PLANES REFINED ATOMS      (A):  1682 ; 0.007 ; 0.020       
+REMARK   3   GENERAL PLANES OTHERS             (A):   276 ; 0.001 ; 0.020       
+REMARK   3   NON-BONDED CONTACTS REFINED ATOMS (A):  NULL ;  NULL ;  NULL       
+REMARK   3   NON-BONDED CONTACTS OTHERS        (A):  NULL ;  NULL ;  NULL       
+REMARK   3   NON-BONDED TORSION REFINED ATOMS  (A):  NULL ;  NULL ;  NULL       
+REMARK   3   NON-BONDED TORSION OTHERS         (A):  NULL ;  NULL ;  NULL       
+REMARK   3   H-BOND (X...Y) REFINED ATOMS      (A):  NULL ;  NULL ;  NULL       
+REMARK   3   H-BOND (X...Y) OTHERS             (A):  NULL ;  NULL ;  NULL       
+REMARK   3   POTENTIAL METAL-ION REFINED ATOMS (A):  NULL ;  NULL ;  NULL       
+REMARK   3   POTENTIAL METAL-ION OTHERS        (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY VDW REFINED ATOMS        (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY VDW OTHERS               (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY H-BOND REFINED ATOMS     (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY H-BOND OTHERS            (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY METAL-ION REFINED ATOMS  (A):  NULL ;  NULL ;  NULL       
+REMARK   3   SYMMETRY METAL-ION OTHERS         (A):  NULL ;  NULL ;  NULL       
+REMARK   3                                                                      
+REMARK   3  ISOTROPIC THERMAL FACTOR RESTRAINTS.     COUNT   RMS    WEIGHT      
+REMARK   3   MAIN-CHAIN BOND REFINED ATOMS  (A**2):   774 ; 3.602 ; 4.642       
+REMARK   3   MAIN-CHAIN BOND OTHER ATOMS    (A**2):   773 ; 3.584 ; 4.636       
+REMARK   3   MAIN-CHAIN ANGLE REFINED ATOMS (A**2):   964 ; 4.741 ; 6.949       
+REMARK   3   MAIN-CHAIN ANGLE OTHER ATOMS   (A**2):   965 ; 4.738 ; 6.957       
+REMARK   3   SIDE-CHAIN BOND REFINED ATOMS  (A**2):   694 ; 5.685 ; 5.263       
+REMARK   3   SIDE-CHAIN BOND OTHER ATOMS    (A**2):   691 ; 5.624 ; 5.277       
+REMARK   3   SIDE-CHAIN ANGLE REFINED ATOMS (A**2):  NULL ;  NULL ;  NULL       
+REMARK   3   SIDE-CHAIN ANGLE OTHER ATOMS   (A**2):  1021 ; 8.298 ; 7.648       
+REMARK   3   LONG RANGE B REFINED ATOMS     (A**2):  1582 ; 9.824 ;37.488       
+REMARK   3   LONG RANGE B OTHER ATOMS       (A**2):  1580 ; 9.825 ;37.492       
+REMARK   3                                                                      
+REMARK   3 ANISOTROPIC THERMAL FACTOR RESTRAINTS.    COUNT   RMS   WEIGHT       
+REMARK   3   RIGID-BOND RESTRAINTS          (A**2):  NULL ;  NULL ;  NULL       
+REMARK   3   SPHERICITY; FREE ATOMS         (A**2):  NULL ;  NULL ;  NULL       
+REMARK   3   SPHERICITY; BONDED ATOMS       (A**2):  NULL ;  NULL ;  NULL       
+REMARK   3                                                                      
+REMARK   3  NCS RESTRAINTS STATISTICS                                           
+REMARK   3   NUMBER OF DIFFERENT NCS GROUPS : NULL                              
+REMARK   3                                                                      
+REMARK   3  TLS DETAILS                                                         
+REMARK   3   NUMBER OF TLS GROUPS  : 2                                          
+REMARK   3                                                                      
+REMARK   3   TLS GROUP : 1                                                      
+REMARK   3    NUMBER OF COMPONENTS GROUP : 1                                    
+REMARK   3    COMPONENTS        C SSSEQI   TO  C SSSEQI                         
+REMARK   3    RESIDUE RANGE :   A     2        A   118                          
+REMARK   3    ORIGIN FOR THE GROUP (A): -11.9068  -7.9134 -35.1151              
+REMARK   3    T TENSOR                                                          
+REMARK   3      T11:   0.0724 T22:   0.0486                                     
+REMARK   3      T33:   0.0466 T12:  -0.0255                                     
+REMARK   3      T13:   0.0362 T23:  -0.0257                                     
+REMARK   3    L TENSOR                                                          
+REMARK   3      L11:   3.9618 L22:   2.5662                                     
+REMARK   3      L33:   3.5301 L12:  -0.2237                                     
+REMARK   3      L13:  -2.4244 L23:  -0.7777                                     
+REMARK   3    S TENSOR                                                          
+REMARK   3      S11:  -0.1452 S12:   0.2312 S13:   0.0678                       
+REMARK   3      S21:   0.1082 S22:   0.0201 S23:   0.1702                       
+REMARK   3      S31:   0.1983 S32:  -0.3971 S33:   0.1251                       
+REMARK   3                                                                      
+REMARK   3   TLS GROUP : 2                                                      
+REMARK   3    NUMBER OF COMPONENTS GROUP : 1                                    
+REMARK   3    COMPONENTS        C SSSEQI   TO  C SSSEQI                         
+REMARK   3    RESIDUE RANGE :   B     2        B   122                          
+REMARK   3    ORIGIN FOR THE GROUP (A): -27.4515 -16.6414 -12.3927              
+REMARK   3    T TENSOR                                                          
+REMARK   3      T11:   0.0153 T22:   0.0305                                     
+REMARK   3      T33:   0.0110 T12:   0.0014                                     
+REMARK   3      T13:   0.0124 T23:  -0.0038                                     
+REMARK   3    L TENSOR                                                          
+REMARK   3      L11:   2.2921 L22:   2.7795                                     
+REMARK   3      L33:   6.4597 L12:   0.0122                                     
+REMARK   3      L13:   0.2226 L23:  -0.5396                                     
+REMARK   3    S TENSOR                                                          
+REMARK   3      S11:   0.0678 S12:  -0.2191 S13:   0.0982                       
+REMARK   3      S21:   0.1361 S22:   0.0490 S23:   0.0984                       
+REMARK   3      S31:  -0.1717 S32:  -0.0459 S33:  -0.1168                       
+REMARK   3                                                                      
+REMARK   3  BULK SOLVENT MODELLING.                                             
+REMARK   3   METHOD USED : MASK                                                 
+REMARK   3   PARAMETERS FOR MASK CALCULATION                                    
+REMARK   3   VDW PROBE RADIUS   : 1.20                                          
+REMARK   3   ION PROBE RADIUS   : 0.80                                          
+REMARK   3   SHRINKAGE RADIUS   : 0.80                                          
+REMARK   3                                                                      
+REMARK   3  OTHER REFINEMENT REMARKS: HYDROGENS HAVE BEEN ADDED IN THE RIDING   
+REMARK   3  POSITIONS                                                           
+REMARK   4                                                                      
+REMARK   4 4ZHO COMPLIES WITH FORMAT V. 3.30, 13-JUL-11                         
+REMARK 100                                                                      
+REMARK 100 THIS ENTRY HAS BEEN PROCESSED BY PDBE ON 27-APR-15.                  
+REMARK 100 THE DEPOSITION ID IS D_1000209256.                                   
+REMARK 200                                                                      
+REMARK 200 EXPERIMENTAL DETAILS                                                 
+REMARK 200  EXPERIMENT TYPE                : X-RAY DIFFRACTION                  
+REMARK 200  DATE OF DATA COLLECTION        : 21-JUL-14                          
+REMARK 200  TEMPERATURE           (KELVIN) : 100                                
+REMARK 200  PH                             : 8.5                                
+REMARK 200  NUMBER OF CRYSTALS USED        : NULL                               
+REMARK 200                                                                      
+REMARK 200  SYNCHROTRON              (Y/N) : Y                                  
+REMARK 200  RADIATION SOURCE               : DIAMOND                            
+REMARK 200  BEAMLINE                       : I02                                
+REMARK 200  X-RAY GENERATOR MODEL          : NULL                               
+REMARK 200  MONOCHROMATIC OR LAUE    (M/L) : M                                  
+REMARK 200  WAVELENGTH OR RANGE        (A) : 1.74                               
+REMARK 200  MONOCHROMATOR                  : SILICON CRYSTAL                    
+REMARK 200  OPTICS                         : NULL                               
+REMARK 200                                                                      
+REMARK 200  DETECTOR TYPE                  : PIXEL                              
+REMARK 200  DETECTOR MANUFACTURER          : PSI PILATUS 6M                     
+REMARK 200  INTENSITY-INTEGRATION SOFTWARE : XDS                                
+REMARK 200  DATA SCALING SOFTWARE          : AIMLESS                            
+REMARK 200                                                                      
+REMARK 200  NUMBER OF UNIQUE REFLECTIONS   : 12894                              
+REMARK 200  RESOLUTION RANGE HIGH      (A) : 2.340                              
+REMARK 200  RESOLUTION RANGE LOW       (A) : 60.730                             
+REMARK 200  REJECTION CRITERIA  (SIGMA(I)) : NULL                               
+REMARK 200                                                                      
+REMARK 200 OVERALL.                                                             
+REMARK 200  COMPLETENESS FOR RANGE     (%) : 99.4                               
+REMARK 200  DATA REDUNDANCY                : 12.30                              
+REMARK 200  R MERGE                    (I) : 0.06600                            
+REMARK 200  R SYM                      (I) : NULL                               
+REMARK 200  <I/SIGMA(I)> FOR THE DATA SET  : 23.2000                            
+REMARK 200                                                                      
+REMARK 200 IN THE HIGHEST RESOLUTION SHELL.                                     
+REMARK 200  HIGHEST RESOLUTION SHELL, RANGE HIGH (A) : 2.34                     
+REMARK 200  HIGHEST RESOLUTION SHELL, RANGE LOW  (A) : 2.40                     
+REMARK 200  COMPLETENESS FOR SHELL     (%) : 98.4                               
+REMARK 200  DATA REDUNDANCY IN SHELL       : 11.80                              
+REMARK 200  R MERGE FOR SHELL          (I) : 0.59800                            
+REMARK 200  R SYM FOR SHELL            (I) : NULL                               
+REMARK 200  <I/SIGMA(I)> FOR SHELL         : 4.200                              
+REMARK 200                                                                      
+REMARK 200 DIFFRACTION PROTOCOL: SINGLE WAVELENGTH                              
+REMARK 200 METHOD USED TO DETERMINE THE STRUCTURE: NULL                         
+REMARK 200 SOFTWARE USED: PHASER                                                
+REMARK 200 STARTING MODEL: NULL                                                 
+REMARK 200                                                                      
+REMARK 200 REMARK: THIN PLATES, DEEP RED BROWN COLOUR DUE TO 2FE-2S IRON        
+REMARK 200  SULPHUR CLUSTER                                                     
+REMARK 280                                                                      
+REMARK 280 CRYSTAL                                                              
+REMARK 280 SOLVENT CONTENT, VS   (%): 64.33                                     
+REMARK 280 MATTHEWS COEFFICIENT, VM (ANGSTROMS**3/DA): 3.45                     
+REMARK 280                                                                      
+REMARK 280 CRYSTALLIZATION CONDITIONS: 0.2 M MGCL2, 0.1 M TRIS, 20 % PEG        
+REMARK 280  8000, PH 8.5, VAPOR DIFFUSION, SITTING DROP, TEMPERATURE 294K       
+REMARK 290                                                                      
+REMARK 290 CRYSTALLOGRAPHIC SYMMETRY                                            
+REMARK 290 SYMMETRY OPERATORS FOR SPACE GROUP: P 42 21 2                        
+REMARK 290                                                                      
+REMARK 290      SYMOP   SYMMETRY                                                
+REMARK 290     NNNMMM   OPERATOR                                                
+REMARK 290       1555   X,Y,Z                                                   
+REMARK 290       2555   -X,-Y,Z                                                 
+REMARK 290       3555   -Y+1/2,X+1/2,Z+1/2                                      
+REMARK 290       4555   Y+1/2,-X+1/2,Z+1/2                                      
+REMARK 290       5555   -X+1/2,Y+1/2,-Z+1/2                                     
+REMARK 290       6555   X+1/2,-Y+1/2,-Z+1/2                                     
+REMARK 290       7555   Y,X,-Z                                                  
+REMARK 290       8555   -Y,-X,-Z                                                
+REMARK 290                                                                      
+REMARK 290     WHERE NNN -> OPERATOR NUMBER                                     
+REMARK 290           MMM -> TRANSLATION VECTOR                                  
+REMARK 290                                                                      
+REMARK 290 CRYSTALLOGRAPHIC SYMMETRY TRANSFORMATIONS                            
+REMARK 290 THE FOLLOWING TRANSFORMATIONS OPERATE ON THE ATOM/HETATM             
+REMARK 290 RECORDS IN THIS ENTRY TO PRODUCE CRYSTALLOGRAPHICALLY                
+REMARK 290 RELATED MOLECULES.                                                   
+REMARK 290   SMTRY1   1  1.000000  0.000000  0.000000        0.00000            
+REMARK 290   SMTRY2   1  0.000000  1.000000  0.000000        0.00000            
+REMARK 290   SMTRY3   1  0.000000  0.000000  1.000000        0.00000            
+REMARK 290   SMTRY1   2 -1.000000  0.000000  0.000000        0.00000            
+REMARK 290   SMTRY2   2  0.000000 -1.000000  0.000000        0.00000            
+REMARK 290   SMTRY3   2  0.000000  0.000000  1.000000        0.00000            
+REMARK 290   SMTRY1   3  0.000000 -1.000000  0.000000       30.36500            
+REMARK 290   SMTRY2   3  1.000000  0.000000  0.000000       30.36500            
+REMARK 290   SMTRY3   3  0.000000  0.000000  1.000000       77.36500            
+REMARK 290   SMTRY1   4  0.000000  1.000000  0.000000       30.36500            
+REMARK 290   SMTRY2   4 -1.000000  0.000000  0.000000       30.36500            
+REMARK 290   SMTRY3   4  0.000000  0.000000  1.000000       77.36500            
+REMARK 290   SMTRY1   5 -1.000000  0.000000  0.000000       30.36500            
+REMARK 290   SMTRY2   5  0.000000  1.000000  0.000000       30.36500            
+REMARK 290   SMTRY3   5  0.000000  0.000000 -1.000000       77.36500            
+REMARK 290   SMTRY1   6  1.000000  0.000000  0.000000       30.36500            
+REMARK 290   SMTRY2   6  0.000000 -1.000000  0.000000       30.36500            
+REMARK 290   SMTRY3   6  0.000000  0.000000 -1.000000       77.36500            
+REMARK 290   SMTRY1   7  0.000000  1.000000  0.000000        0.00000            
+REMARK 290   SMTRY2   7  1.000000  0.000000  0.000000        0.00000            
+REMARK 290   SMTRY3   7  0.000000  0.000000 -1.000000        0.00000            
+REMARK 290   SMTRY1   8  0.000000 -1.000000  0.000000        0.00000            
+REMARK 290   SMTRY2   8 -1.000000  0.000000  0.000000        0.00000            
+REMARK 290   SMTRY3   8  0.000000  0.000000 -1.000000        0.00000            
+REMARK 290                                                                      
+REMARK 290 REMARK: NULL                                                         
+REMARK 300                                                                      
+REMARK 300 BIOMOLECULE: 1, 2                                                    
+REMARK 300 SEE REMARK 350 FOR THE AUTHOR PROVIDED AND/OR PROGRAM                
+REMARK 300 GENERATED ASSEMBLY INFORMATION FOR THE STRUCTURE IN                  
+REMARK 300 THIS ENTRY. THE REMARK MAY ALSO PROVIDE INFORMATION ON               
+REMARK 300 BURIED SURFACE AREA.                                                 
+REMARK 350                                                                      
+REMARK 350 COORDINATES FOR A COMPLETE MULTIMER REPRESENTING THE KNOWN           
+REMARK 350 BIOLOGICALLY SIGNIFICANT OLIGOMERIZATION STATE OF THE                
+REMARK 350 MOLECULE CAN BE GENERATED BY APPLYING BIOMT TRANSFORMATIONS          
+REMARK 350 GIVEN BELOW.  BOTH NON-CRYSTALLOGRAPHIC AND                          
+REMARK 350 CRYSTALLOGRAPHIC OPERATIONS ARE GIVEN.                               
+REMARK 350                                                                      
+REMARK 350 BIOMOLECULE: 1                                                       
+REMARK 350 AUTHOR DETERMINED BIOLOGICAL UNIT: MONOMERIC                         
+REMARK 350 APPLY THE FOLLOWING TO CHAINS: A                                     
+REMARK 350   BIOMT1   1  1.000000  0.000000  0.000000        0.00000            
+REMARK 350   BIOMT2   1  0.000000  1.000000  0.000000        0.00000            
+REMARK 350   BIOMT3   1  0.000000  0.000000  1.000000        0.00000            
+REMARK 350                                                                      
+REMARK 350 BIOMOLECULE: 2                                                       
+REMARK 350 AUTHOR DETERMINED BIOLOGICAL UNIT: MONOMERIC                         
+REMARK 350 APPLY THE FOLLOWING TO CHAINS: B                                     
+REMARK 350   BIOMT1   1  1.000000  0.000000  0.000000        0.00000            
+REMARK 350   BIOMT2   1  0.000000  1.000000  0.000000        0.00000            
+REMARK 350   BIOMT3   1  0.000000  0.000000  1.000000        0.00000            
+REMARK 375                                                                      
+REMARK 375 SPECIAL POSITION                                                     
+REMARK 375 THE FOLLOWING ATOMS ARE FOUND TO BE WITHIN 0.15 ANGSTROMS            
+REMARK 375 OF A SYMMETRY RELATED ATOM AND ARE ASSUMED TO BE ON SPECIAL          
+REMARK 375 POSITIONS.                                                           
+REMARK 375                                                                      
+REMARK 375 ATOM RES CSSEQI                                                      
+REMARK 375      HOH B 319  LIES ON A SPECIAL POSITION.                          
+REMARK 465                                                                      
+REMARK 465 MISSING RESIDUES                                                     
+REMARK 465 THE FOLLOWING RESIDUES WERE NOT LOCATED IN THE                       
+REMARK 465 EXPERIMENT. (M=MODEL NUMBER; RES=RESIDUE NAME; C=CHAIN               
+REMARK 465 IDENTIFIER; SSSEQ=SEQUENCE NUMBER; I=INSERTION CODE.)                
+REMARK 465                                                                      
+REMARK 465   M RES C SSSEQI                                                     
+REMARK 465     MET A     1                                                      
+REMARK 465     GLU A    99                                                      
+REMARK 465     HIS A   100                                                      
+REMARK 465     HIS A   101                                                      
+REMARK 465     HIS A   102                                                      
+REMARK 465     HIS A   103                                                      
+REMARK 465     HIS A   104                                                      
+REMARK 465     HIS A   105                                                      
+REMARK 465     MET B     1                                                      
+REMARK 465     GLU B    99                                                      
+REMARK 465     HIS B   100                                                      
+REMARK 465     HIS B   101                                                      
+REMARK 465     HIS B   102                                                      
+REMARK 465     HIS B   103                                                      
+REMARK 465     HIS B   104                                                      
+REMARK 465     HIS B   105                                                      
+REMARK 470                                                                      
+REMARK 470 MISSING ATOM                                                         
+REMARK 470 THE FOLLOWING RESIDUES HAVE MISSING ATOMS (M=MODEL NUMBER;           
+REMARK 470 RES=RESIDUE NAME; C=CHAIN IDENTIFIER; SSEQ=SEQUENCE NUMBER;          
+REMARK 470 I=INSERTION CODE):                                                   
+REMARK 470   M RES CSSEQI  ATOMS                                                
+REMARK 470     LEU A  98    CG   CD1  CD2                                       
+REMARK 470     LEU B  98    CG   CD1  CD2                                       
+REMARK 500                                                                      
+REMARK 500 GEOMETRY AND STEREOCHEMISTRY                                         
+REMARK 500 SUBTOPIC: TORSION ANGLES                                             
+REMARK 500                                                                      
+REMARK 500 TORSION ANGLES OUTSIDE THE EXPECTED RAMACHANDRAN REGIONS:            
+REMARK 500 (M=MODEL NUMBER; RES=RESIDUE NAME; C=CHAIN IDENTIFIER;               
+REMARK 500 SSEQ=SEQUENCE NUMBER; I=INSERTION CODE).                             
+REMARK 500                                                                      
+REMARK 500 STANDARD TABLE:                                                      
+REMARK 500 FORMAT:(10X,I3,1X,A3,1X,A1,I4,A1,4X,F7.2,3X,F7.2)                    
+REMARK 500                                                                      
+REMARK 500 EXPECTED VALUES: GJ KLEYWEGT AND TA JONES (1996). PHI/PSI-           
+REMARK 500 CHOLOGY: RAMACHANDRAN REVISITED. STRUCTURE 4, 1395 - 1400            
+REMARK 500                                                                      
+REMARK 500  M RES CSSEQI        PSI       PHI                                   
+REMARK 500    SER A  39      -64.23   -146.55                                   
+REMARK 500    MET A  97      -67.17    -99.01                                   
+REMARK 500    SER B  39      -78.49   -140.11                                   
+REMARK 500    SER B  63      -12.80   -148.10                                   
+REMARK 500                                                                      
+REMARK 500 REMARK: NULL                                                         
+REMARK 620                                                                      
+REMARK 620 METAL COORDINATION                                                   
+REMARK 620 (M=MODEL NUMBER; RES=RESIDUE NAME; C=CHAIN IDENTIFIER;               
+REMARK 620 SSEQ=SEQUENCE NUMBER; I=INSERTION CODE):                             
+REMARK 620                                                                      
+REMARK 620 COORDINATION ANGLES FOR:  M RES CSSEQI METAL                         
+REMARK 620                             FES A 201  FE1                           
+REMARK 620 N RES CSSEQI ATOM                                                    
+REMARK 620 1 CYS A  40   SG                                                     
+REMARK 620 2 FES A 201   S1  124.8                                              
+REMARK 620 3 FES A 201   S2  100.1  99.4                                        
+REMARK 620 4 CYS A  45   SG  105.3 110.0 117.8                                  
+REMARK 620 N                    1     2     3                                   
+REMARK 620                                                                      
+REMARK 620 COORDINATION ANGLES FOR:  M RES CSSEQI METAL                         
+REMARK 620                             FES A 201  FE2                           
+REMARK 620 N RES CSSEQI ATOM                                                    
+REMARK 620 1 CYS A  48   SG                                                     
+REMARK 620 2 FES A 201   S1  114.4                                              
+REMARK 620 3 FES A 201   S2  108.9 101.7                                        
+REMARK 620 4 CYS A  78   SG  105.9 122.0 102.7                                  
+REMARK 620 N                    1     2     3                                   
+REMARK 620                                                                      
+REMARK 620 COORDINATION ANGLES FOR:  M RES CSSEQI METAL                         
+REMARK 620                             FES B 201  FE1                           
+REMARK 620 N RES CSSEQI ATOM                                                    
+REMARK 620 1 CYS B  40   SG                                                     
+REMARK 620 2 FES B 201   S1  119.9                                              
+REMARK 620 3 FES B 201   S2  102.0  97.8                                        
+REMARK 620 4 CYS B  45   SG  109.5 108.1 119.7                                  
+REMARK 620 N                    1     2     3                                   
+REMARK 620                                                                      
+REMARK 620 COORDINATION ANGLES FOR:  M RES CSSEQI METAL                         
+REMARK 620                             FES B 201  FE2                           
+REMARK 620 N RES CSSEQI ATOM                                                    
+REMARK 620 1 CYS B  48   SG                                                     
+REMARK 620 2 FES B 201   S1  111.1                                              
+REMARK 620 3 FES B 201   S2  115.5  99.1                                        
+REMARK 620 4 CYS B  78   SG  110.9 117.2 102.5                                  
+REMARK 620 N                    1     2     3                                   
+REMARK 800                                                                      
+REMARK 800 SITE                                                                 
+REMARK 800 SITE_IDENTIFIER: AC1                                                 
+REMARK 800 EVIDENCE_CODE: SOFTWARE                                              
+REMARK 800 SITE_DESCRIPTION: binding site for residue FES A 201                 
+REMARK 800                                                                      
+REMARK 800 SITE_IDENTIFIER: AC2                                                 
+REMARK 800 EVIDENCE_CODE: SOFTWARE                                              
+REMARK 800 SITE_DESCRIPTION: binding site for residue CL A 202                  
+REMARK 800                                                                      
+REMARK 800 SITE_IDENTIFIER: AC3                                                 
+REMARK 800 EVIDENCE_CODE: SOFTWARE                                              
+REMARK 800 SITE_DESCRIPTION: binding site for residue FES B 201                 
+REMARK 800                                                                      
+REMARK 800 SITE_IDENTIFIER: AC4                                                 
+REMARK 800 EVIDENCE_CODE: SOFTWARE                                              
+REMARK 800 SITE_DESCRIPTION: binding site for residue CL B 202                  
+DBREF  4ZHO A    1    94  UNP    P16972   FER2_ARATH      52    145             
+DBREF  4ZHO B    1    94  UNP    P16972   FER2_ARATH      52    145             
+SEQADV 4ZHO ALA A   95  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO ILE A   96  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO MET A   97  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO LEU A   98  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO GLU A   99  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  100  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  101  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  102  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  103  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  104  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS A  105  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO ALA B   95  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO ILE B   96  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO MET B   97  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO LEU B   98  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO GLU B   99  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  100  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  101  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  102  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  103  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  104  UNP  P16972              EXPRESSION TAG                 
+SEQADV 4ZHO HIS B  105  UNP  P16972              EXPRESSION TAG                 
+SEQRES   1 A  105  MET ALA THR TYR LYS VAL LYS PHE ILE THR PRO GLU GLY          
+SEQRES   2 A  105  GLU LEU GLU VAL GLU CYS ASP ASP ASP VAL TYR VAL LEU          
+SEQRES   3 A  105  ASP ALA ALA GLU GLU ALA GLY ILE ASP LEU PRO TYR SER          
+SEQRES   4 A  105  CYS ARG ALA GLY SER CYS SER SER CYS ALA GLY LYS VAL          
+SEQRES   5 A  105  VAL SER GLY SER VAL ASP GLN SER ASP GLN SER PHE LEU          
+SEQRES   6 A  105  ASP ASP GLU GLN ILE GLY GLU GLY PHE VAL LEU THR CYS          
+SEQRES   7 A  105  ALA ALA TYR PRO THR SER ASP VAL THR ILE GLU THR HIS          
+SEQRES   8 A  105  LYS GLU GLU ALA ILE MET LEU GLU HIS HIS HIS HIS HIS          
+SEQRES   9 A  105  HIS                                                          
+SEQRES   1 B  105  MET ALA THR TYR LYS VAL LYS PHE ILE THR PRO GLU GLY          
+SEQRES   2 B  105  GLU LEU GLU VAL GLU CYS ASP ASP ASP VAL TYR VAL LEU          
+SEQRES   3 B  105  ASP ALA ALA GLU GLU ALA GLY ILE ASP LEU PRO TYR SER          
+SEQRES   4 B  105  CYS ARG ALA GLY SER CYS SER SER CYS ALA GLY LYS VAL          
+SEQRES   5 B  105  VAL SER GLY SER VAL ASP GLN SER ASP GLN SER PHE LEU          
+SEQRES   6 B  105  ASP ASP GLU GLN ILE GLY GLU GLY PHE VAL LEU THR CYS          
+SEQRES   7 B  105  ALA ALA TYR PRO THR SER ASP VAL THR ILE GLU THR HIS          
+SEQRES   8 B  105  LYS GLU GLU ALA ILE MET LEU GLU HIS HIS HIS HIS HIS          
+SEQRES   9 B  105  HIS                                                          
+HET    FES  A 201       4                                                       
+HET     CL  A 202       1                                                       
+HET    FES  B 201       4                                                       
+HET     CL  B 202       1                                                       
+HETNAM     FES FE2/S2 (INORGANIC) CLUSTER                                       
+HETNAM      CL CHLORIDE ION                                                     
+FORMUL   3  FES    2(FE2 S2)                                                    
+FORMUL   4   CL    2(CL 1-)                                                     
+FORMUL   7  HOH   *38(H2 O)                                                     
+HELIX    1 AA1 TYR A   24  ALA A   32  1                                   9    
+HELIX    2 AA2 ASP A   66  GLU A   72  1                                   7    
+HELIX    3 AA3 CYS A   78  ALA A   80  5                                   3    
+HELIX    4 AA5 TYR B   24  ALA B   32  1                                   9    
+HELIX    5 AA6 ASP B   66  GLU B   72  1                                   7    
+HELIX    6 AA7 CYS B   78  ALA B   80  5                                   3    
+HELIX    7 AA8 LYS B   92  MET B   97  5                                   6    
+SHEET    1 AA1 5 GLU A  14  ASP A  20  0                                        
+SHEET    2 AA1 5 THR A   3  ILE A   9 -1  N  PHE A   8   O  LEU A  15           
+SHEET    3 AA1 5 VAL A  86  GLU A  89  1  O  ILE A  88   N  LYS A   7           
+SHEET    4 AA1 5 ALA A  49  SER A  54 -1  N  SER A  54   O  THR A  87           
+SHEET    5 AA1 5 PHE A  74  LEU A  76 -1  O  VAL A  75   N  GLY A  50           
+SHEET    1 AA2 2 VAL A  57  ASP A  58  0                                        
+SHEET    2 AA2 2 TYR A  81  PRO A  82 -1  O  TYR A  81   N  ASP A  58           
+SHEET    1 AA3 5 GLY B  13  ASP B  20  0                                        
+SHEET    2 AA3 5 THR B   3  THR B  10 -1  N  PHE B   8   O  LEU B  15           
+SHEET    3 AA3 5 VAL B  86  GLU B  89  1  O  ILE B  88   N  LYS B   7           
+SHEET    4 AA3 5 ALA B  49  SER B  54 -1  N  LYS B  51   O  GLU B  89           
+SHEET    5 AA3 5 PHE B  74  LEU B  76 -1  O  VAL B  75   N  GLY B  50           
+SHEET    1 AA4 2 VAL B  57  ASP B  58  0                                        
+SHEET    2 AA4 2 TYR B  81  PRO B  82 -1  O  TYR B  81   N  ASP B  58           
+LINK         SG  CYS A  40                FE1  FES A 201     1555   1555  2.33  
+LINK         SG  CYS A  45                FE1  FES A 201     1555   1555  2.28  
+LINK         SG  CYS A  48                FE2  FES A 201     1555   1555  2.28  
+LINK         SG  CYS A  78                FE2  FES A 201     1555   1555  2.34  
+LINK         SG  CYS B  40                FE1  FES B 201     1555   1555  2.22  
+LINK         SG  CYS B  45                FE1  FES B 201     1555   1555  2.25  
+LINK         SG  CYS B  48                FE2  FES B 201     1555   1555  2.18  
+LINK         SG  CYS B  78                FE2  FES B 201     1555   1555  2.36  
+SITE     1 AC1  8 SER A  39  CYS A  40  ARG A  41  GLY A  43                    
+SITE     2 AC1  8 SER A  44  CYS A  45  CYS A  48  CYS A  78                    
+SITE     1 AC2  2 SER A  84  ASP A  85                                          
+SITE     1 AC3  8 SER B  39  CYS B  40  ARG B  41  GLY B  43                    
+SITE     2 AC3  8 SER B  44  CYS B  45  CYS B  48  CYS B  78                    
+SITE     1 AC4  2 SER B  84  ASP B  85                                          
+CRYST1   60.730   60.730  154.730  90.00  90.00  90.00 P 42 21 2    16          
+ORIGX1      1.000000  0.000000  0.000000        0.00000                         
+ORIGX2      0.000000  1.000000  0.000000        0.00000                         
+ORIGX3      0.000000  0.000000  1.000000        0.00000                         
+SCALE1      0.016466  0.000000  0.000000        0.00000                         
+SCALE2      0.000000  0.016466  0.000000        0.00000                         
+SCALE3      0.000000  0.000000  0.006463        0.00000                         
+ATOM      1  N   ALA A   2      -1.257 -15.807 -48.124  1.00 50.77           N  
+ANISOU    1  N   ALA A   2     7787   5804   5698   -219   1819   -481       N  
+ATOM      2  CA  ALA A   2      -1.523 -14.339 -48.197  1.00 50.70           C  
+ANISOU    2  CA  ALA A   2     7568   6034   5660   -183   1548   -344       C  
+ATOM      3  C   ALA A   2      -1.438 -13.722 -46.830  1.00 45.70           C  
+ANISOU    3  C   ALA A   2     6785   5347   5230    -97   1396   -189       C  
+ATOM      4  O   ALA A   2      -1.809 -14.345 -45.825  1.00 44.98           O  
+ANISOU    4  O   ALA A   2     6732   5134   5221   -136   1414   -240       O  
+ATOM      5  CB  ALA A   2      -2.903 -14.040 -48.848  1.00 48.96           C  
+ANISOU    5  CB  ALA A   2     7316   6077   5207   -370   1390   -508       C  
+ATOM      6  N   THR A   3      -1.069 -12.455 -46.833  1.00 45.54           N  
+ANISOU    6  N   THR A   3     6600   5431   5271     -1   1249    -16       N  
+ATOM      7  CA  THR A   3      -0.949 -11.641 -45.631  1.00 47.35           C  
+ANISOU    7  CA  THR A   3     6681   5636   5674     60   1092    109       C  
+ATOM      8  C   THR A   3      -1.689 -10.384 -45.941  1.00 43.11           C  
+ANISOU    8  C   THR A   3     6022   5274   5083     31    915    145       C  
+ATOM      9  O   THR A   3      -1.531  -9.803 -47.012  1.00 41.92           O  
+ANISOU    9  O   THR A   3     5843   5236   4848     63    924    210       O  
+ATOM     10  CB  THR A   3       0.543 -11.321 -45.328  1.00 50.05           C  
+ANISOU   10  CB  THR A   3     6935   5888   6195    216   1141    301       C  
+ATOM     11  OG1 THR A   3       1.229 -12.551 -45.036  1.00 50.95           O  
+ANISOU   11  OG1 THR A   3     7151   5846   6362    283   1328    306       O  
+ATOM     12  CG2 THR A   3       0.673 -10.438 -44.115  1.00 54.46           C  
+ANISOU   12  CG2 THR A   3     7341   6448   6902    242    968    390       C  
+ATOM     13  N   TYR A   4      -2.484  -9.944 -44.989  1.00 46.99           N  
+ANISOU   13  N   TYR A   4     6443   5784   5625     -9    776    123       N  
+ATOM     14  CA  TYR A   4      -3.347  -8.768 -45.177  1.00 48.43           C  
+ANISOU   14  CA  TYR A   4     6510   6116   5775    -19    632    167       C  
+ATOM     15  C   TYR A   4      -3.023  -7.694 -44.144  1.00 47.22           C  
+ANISOU   15  C   TYR A   4     6246   5881   5813     46    540    272       C  
+ATOM     16  O   TYR A   4      -2.521  -7.988 -43.069  1.00 48.33           O  
+ANISOU   16  O   TYR A   4     6396   5904   6063     58    537    263       O  
+ATOM     17  CB  TYR A   4      -4.801  -9.196 -45.059  1.00 47.01           C  
+ANISOU   17  CB  TYR A   4     6341   6049   5468   -146    570     20       C  
+ATOM     18  CG  TYR A   4      -5.208 -10.134 -46.157  1.00 43.84           C  
+ANISOU   18  CG  TYR A   4     6035   5763   4857   -258    645   -122       C  
+ATOM     19  CD1 TYR A   4      -5.280  -9.700 -47.461  1.00 48.21           C  
+ANISOU   19  CD1 TYR A   4     6559   6515   5243   -243    630    -80       C  
+ATOM     20  CD2 TYR A   4      -5.473 -11.470 -45.898  1.00 48.38           C  
+ANISOU   20  CD2 TYR A   4     6743   6241   5397   -383    752   -303       C  
+ATOM     21  CE1 TYR A   4      -5.629 -10.561 -48.480  1.00 48.93           C  
+ANISOU   21  CE1 TYR A   4     6746   6739   5104   -368    694   -244       C  
+ATOM     22  CE2 TYR A   4      -5.802 -12.362 -46.923  1.00 48.52           C  
+ANISOU   22  CE2 TYR A   4     6868   6342   5221   -522    844   -480       C  
+ATOM     23  CZ  TYR A   4      -5.914 -11.892 -48.199  1.00 48.26           C  
+ANISOU   23  CZ  TYR A   4     6798   6543   4992   -526    799   -466       C  
+ATOM     24  OH  TYR A   4      -6.226 -12.778 -49.220  1.00 52.35           O  
+ANISOU   24  OH  TYR A   4     7434   7172   5284   -686    885   -675       O  
+ATOM     25  N   LYS A   5      -3.307  -6.453 -44.489  1.00 47.90           N  
+ANISOU   25  N   LYS A   5     6234   6036   5930     90    478    373       N  
+ATOM     26  CA  LYS A   5      -3.126  -5.320 -43.582  1.00 49.46           C  
+ANISOU   26  CA  LYS A   5     6343   6138   6309    126    414    439       C  
+ATOM     27  C   LYS A   5      -4.415  -5.114 -42.819  1.00 44.72           C  
+ANISOU   27  C   LYS A   5     5725   5576   5688     87    334    362       C  
+ATOM     28  O   LYS A   5      -5.464  -4.904 -43.423  1.00 45.76           O  
+ANISOU   28  O   LYS A   5     5820   5852   5714     87    308    374       O  
+ATOM     29  CB  LYS A   5      -2.776  -4.038 -44.357  1.00 51.93           C  
+ANISOU   29  CB  LYS A   5     6573   6454   6702    205    443    602       C  
+ATOM     30  CG  LYS A   5      -1.497  -4.126 -45.113  1.00 62.94           C  
+ANISOU   30  CG  LYS A   5     7967   7811   8137    249    542    698       C  
+ATOM     31  CD  LYS A   5      -0.351  -4.665 -44.240  1.00 71.74           C  
+ANISOU   31  CD  LYS A   5     9078   8803   9377    228    553    662       C  
+ATOM     32  CE  LYS A   5      -0.083  -3.798 -43.025  1.00 78.38           C  
+ANISOU   32  CE  LYS A   5     9836   9541  10403    192    473    648       C  
+ATOM     33  NZ  LYS A   5       0.491  -2.513 -43.496  1.00 87.36           N  
+ANISOU   33  NZ  LYS A   5    10881  10609  11701    215    524    774       N  
+ATOM     34  N   VAL A   6      -4.356  -5.236 -41.505  1.00 40.33           N  
+ANISOU   34  N   VAL A   6     5186   4922   5213     58    297    290       N  
+ATOM     35  CA  VAL A   6      -5.506  -4.978 -40.686  1.00 43.48           C  
+ANISOU   35  CA  VAL A   6     5572   5340   5608     33    246    224       C  
+ATOM     36  C   VAL A   6      -5.286  -3.624 -40.015  1.00 44.26           C  
+ANISOU   36  C   VAL A   6     5616   5334   5865     73    225    265       C  
+ATOM     37  O   VAL A   6      -4.317  -3.413 -39.286  1.00 45.54           O  
+ANISOU   37  O   VAL A   6     5782   5395   6125     57    211    245       O  
+ATOM     38  CB  VAL A   6      -5.704  -6.072 -39.614  1.00 47.03           C  
+ANISOU   38  CB  VAL A   6     6100   5753   6016    -24    246    110       C  
+ATOM     39  CG1 VAL A   6      -6.982  -5.808 -38.849  1.00 45.34           C  
+ANISOU   39  CG1 VAL A   6     5866   5571   5789    -46    216     50       C  
+ATOM     40  CG2 VAL A   6      -5.764  -7.451 -40.254  1.00 46.02           C  
+ANISOU   40  CG2 VAL A   6     6052   5664   5769    -78    315     53       C  
+ATOM     41  N   LYS A   7      -6.163  -2.682 -40.330  1.00 45.38           N  
+ANISOU   41  N   LYS A   7     5700   5508   6034    123    235    327       N  
+ATOM     42  CA  LYS A   7      -6.143  -1.373 -39.740  1.00 45.98           C  
+ANISOU   42  CA  LYS A   7     5748   5450   6273    160    263    351       C  
+ATOM     43  C   LYS A   7      -7.143  -1.363 -38.592  1.00 47.81           C  
+ANISOU   43  C   LYS A   7     6003   5668   6491    143    248    245       C  
+ATOM     44  O   LYS A   7      -8.347  -1.536 -38.787  1.00 51.78           O  
+ANISOU   44  O   LYS A   7     6468   6287   6918    173    249    263       O  
+ATOM     45  CB  LYS A   7      -6.506  -0.347 -40.788  1.00 48.29           C  
+ANISOU   45  CB  LYS A   7     5968   5763   6617    264    329    520       C  
+ATOM     46  CG  LYS A   7      -6.637   1.070 -40.268  1.00 58.39           C  
+ANISOU   46  CG  LYS A   7     7234   6859   8090    316    413    556       C  
+ATOM     47  CD  LYS A   7      -6.631   2.026 -41.447  1.00 68.64           C  
+ANISOU   47  CD  LYS A   7     8467   8148   9461    441    515    776       C  
+ATOM     48  CE  LYS A   7      -6.930   3.448 -41.018  1.00 82.52           C  
+ANISOU   48  CE  LYS A   7    10225   9693  11435    515    651    833       C  
+ATOM     49  NZ  LYS A   7      -6.213   4.399 -41.926  1.00 90.34           N  
+ANISOU   49  NZ  LYS A   7    11188  10561  12575    587    785   1022       N  
+ATOM     50  N   PHE A   8      -6.640  -1.148 -37.392  1.00 43.94           N  
+ANISOU   50  N   PHE A   8     5567   5061   6068     93    233    136       N  
+ATOM     51  CA  PHE A   8      -7.513  -1.002 -36.238  1.00 50.30           C  
+ANISOU   51  CA  PHE A   8     6412   5840   6859     87    244     34       C  
+ATOM     52  C   PHE A   8      -7.808   0.440 -35.906  1.00 44.05           C  
+ANISOU   52  C   PHE A   8     5618   4900   6218    131    331     33       C  
+ATOM     53  O   PHE A   8      -6.905   1.228 -35.663  1.00 48.85           O  
+ANISOU   53  O   PHE A   8     6245   5366   6948     88    353     -5       O  
+ATOM     54  CB  PHE A   8      -6.843  -1.646 -35.018  1.00 45.89           C  
+ANISOU   54  CB  PHE A   8     5927   5267   6239     10    184    -93       C  
+ATOM     55  CG  PHE A   8      -6.679  -3.122 -35.144  1.00 46.40           C  
+ANISOU   55  CG  PHE A   8     6019   5437   6174    -10    148    -85       C  
+ATOM     56  CD1 PHE A   8      -7.769  -3.967 -35.038  1.00 48.26           C  
+ANISOU   56  CD1 PHE A   8     6277   5749   6311    -14    173   -106       C  
+ATOM     57  CD2 PHE A   8      -5.438  -3.668 -35.354  1.00 44.91           C  
+ANISOU   57  CD2 PHE A   8     5828   5253   5981    -27    113    -54       C  
+ATOM     58  CE1 PHE A   8      -7.601  -5.339 -35.131  1.00 47.89           C  
+ANISOU   58  CE1 PHE A   8     6276   5748   6170    -46    182   -111       C  
+ATOM     59  CE2 PHE A   8      -5.279  -5.027 -35.445  1.00 45.32           C  
+ANISOU   59  CE2 PHE A   8     5923   5360   5935    -28    125    -39       C  
+ATOM     60  CZ  PHE A   8      -6.356  -5.857 -35.342  1.00 47.01           C  
+ANISOU   60  CZ  PHE A   8     6184   5615   6059    -43    168    -74       C  
+ATOM     61  N   ILE A   9      -9.069   0.763 -35.795  1.00 45.19           N  
+ANISOU   61  N   ILE A   9     5739   5067   6363    208    395     63       N  
+ATOM     62  CA  ILE A   9      -9.459   2.100 -35.309  1.00 51.68           C  
+ANISOU   62  CA  ILE A   9     6584   5712   7339    270    525     51       C  
+ATOM     63  C   ILE A   9      -9.826   1.880 -33.879  1.00 50.92           C  
+ANISOU   63  C   ILE A   9     6580   5587   7179    217    529   -126       C  
+ATOM     64  O   ILE A   9     -10.824   1.229 -33.599  1.00 53.51           O  
+ANISOU   64  O   ILE A   9     6889   6036   7407    248    527   -124       O  
+ATOM     65  CB  ILE A   9     -10.635   2.697 -36.124  1.00 55.53           C  
+ANISOU   65  CB  ILE A   9     6967   6250   7879    433    624    240       C  
+ATOM     66  CG1 ILE A   9     -10.204   2.918 -37.592  1.00 55.29           C  
+ANISOU   66  CG1 ILE A   9     6852   6282   7873    497    619    438       C  
+ATOM     67  CG2 ILE A   9     -11.137   4.018 -35.502  1.00 55.96           C  
+ANISOU   67  CG2 ILE A   9     7065   6086   8109    523    808    232       C  
+ATOM     68  CD1 ILE A   9     -11.385   2.981 -38.527  1.00 58.25           C  
+ANISOU   68  CD1 ILE A   9     7085   6859   8188    645    639    641       C  
+ATOM     69  N   THR A  10      -8.967   2.329 -32.980  1.00 50.67           N  
+ANISOU   69  N   THR A  10     6641   5425   7186    121    526   -284       N  
+ATOM     70  CA  THR A  10      -9.185   2.107 -31.561  1.00 51.69           C  
+ANISOU   70  CA  THR A  10     6873   5558   7208     65    520   -464       C  
+ATOM     71  C   THR A  10      -9.612   3.432 -30.915  1.00 50.65           C  
+ANISOU   71  C   THR A  10     6822   5218   7204     89    689   -568       C  
+ATOM     72  O   THR A  10      -9.544   4.466 -31.525  1.00 57.26           O  
+ANISOU   72  O   THR A  10     7639   5887   8228    134    806   -501       O  
+ATOM     73  CB  THR A  10      -7.930   1.531 -30.864  1.00 50.80           C  
+ANISOU   73  CB  THR A  10     6804   5512   6986    -68    378   -588       C  
+ATOM     74  OG1 THR A  10      -6.968   2.554 -30.671  1.00 53.43           O  
+ANISOU   74  OG1 THR A  10     7157   5704   7440   -168    390   -696       O  
+ATOM     75  CG2 THR A  10      -7.278   0.462 -31.693  1.00 50.38           C  
+ANISOU   75  CG2 THR A  10     6674   5593   6874    -73    264   -466       C  
+ATOM     76  N   PRO A  11     -10.106   3.393 -29.679  1.00 56.03           N  
+ANISOU   76  N   PRO A  11     7607   5897   7785     72    733   -722       N  
+ATOM     77  CA  PRO A  11     -10.427   4.652 -28.979  1.00 58.93           C  
+ANISOU   77  CA  PRO A  11     8085   6039   8264     79    922   -863       C  
+ATOM     78  C   PRO A  11      -9.259   5.590 -28.772  1.00 63.22           C  
+ANISOU   78  C   PRO A  11     8697   6403   8918    -73    935  -1031       C  
+ATOM     79  O   PRO A  11      -9.508   6.756 -28.521  1.00 69.60           O  
+ANISOU   79  O   PRO A  11     9594   6966   9884    -65   1137  -1123       O  
+ATOM     80  CB  PRO A  11     -10.886   4.178 -27.593  1.00 59.38           C  
+ANISOU   80  CB  PRO A  11     8256   6185   8120     51    924  -1030       C  
+ATOM     81  CG  PRO A  11     -11.306   2.750 -27.777  1.00 55.35           C  
+ANISOU   81  CG  PRO A  11     7663   5915   7451     96    802   -896       C  
+ATOM     82  CD  PRO A  11     -10.443   2.207 -28.868  1.00 52.67           C  
+ANISOU   82  CD  PRO A  11     7215   5657   7140     56    649   -769       C  
+ATOM     83  N   GLU A  12      -8.030   5.041 -28.776  1.00 70.08           N  
+ANISOU   83  N   GLU A  12     9526   7398   9704   -217    738  -1080       N  
+ATOM     84  CA  GLU A  12      -6.732   5.771 -28.699  1.00 69.59           C  
+ANISOU   84  CA  GLU A  12     9470   7225   9747   -402    705  -1227       C  
+ATOM     85  C   GLU A  12      -6.252   6.249 -30.096  1.00 71.32           C  
+ANISOU   85  C   GLU A  12     9578   7319  10199   -369    757  -1033       C  
+ATOM     86  O   GLU A  12      -5.575   7.279 -30.200  1.00 82.47           O  
+ANISOU   86  O   GLU A  12    11009   8520  11805   -484    854  -1123       O  
+ATOM     87  CB  GLU A  12      -5.618   4.877 -28.105  1.00 80.51           C  
+ANISOU   87  CB  GLU A  12    10809   8855  10925   -546    460  -1320       C  
+ATOM     88  CG  GLU A  12      -5.569   4.624 -26.578  1.00 89.15           C  
+ANISOU   88  CG  GLU A  12    12010  10093  11769   -643    381  -1555       C  
+ATOM     89  CD  GLU A  12      -4.271   3.871 -26.137  1.00 96.47           C  
+ANISOU   89  CD  GLU A  12    12846  11289  12519   -771    133  -1597       C  
+ATOM     90  OE1 GLU A  12      -3.440   4.458 -25.401  1.00 86.42           O  
+ANISOU   90  OE1 GLU A  12    11584  10049  11202   -968     64  -1827       O  
+ATOM     91  OE2 GLU A  12      -4.044   2.692 -26.542  1.00 87.89           O  
+ANISOU   91  OE2 GLU A  12    11665  10388  11340   -678     12  -1398       O  
+ATOM     92  N   GLY A  13      -6.566   5.500 -31.160  1.00 63.49           N  
+ANISOU   92  N   GLY A  13     8479   6460   9184   -227    703   -780       N  
+ATOM     93  CA  GLY A  13      -6.219   5.913 -32.504  1.00 59.26           C  
+ANISOU   93  CA  GLY A  13     7850   5841   8826   -167    764   -575       C  
+ATOM     94  C   GLY A  13      -6.111   4.770 -33.479  1.00 56.21           C  
+ANISOU   94  C   GLY A  13     7353   5681   8322    -92    628   -378       C  
+ATOM     95  O   GLY A  13      -6.534   3.652 -33.197  1.00 53.58           O  
+ANISOU   95  O   GLY A  13     7018   5544   7795    -65    517   -379       O  
+ATOM     96  N   GLU A  14      -5.519   5.057 -34.631  1.00 54.33           N  
+ANISOU   96  N   GLU A  14     7036   5399   8206    -65    661   -215       N  
+ATOM     97  CA  GLU A  14      -5.492   4.115 -35.751  1.00 60.06           C  
+ANISOU   97  CA  GLU A  14     7674   6319   8826     19    577    -23       C  
+ATOM     98  C   GLU A  14      -4.126   3.540 -35.940  1.00 51.22           C  
+ANISOU   98  C   GLU A  14     6504   5271   7684    -88    464    -38       C  
+ATOM     99  O   GLU A  14      -3.198   4.277 -36.050  1.00 52.79           O  
+ANISOU   99  O   GLU A  14     6674   5338   8045   -172    512    -55       O  
+ATOM    100  CB  GLU A  14      -5.953   4.814 -37.053  1.00 67.15           C  
+ANISOU  100  CB  GLU A  14     8513   7162   9837    174    715    218       C  
+ATOM    101  CG  GLU A  14      -7.367   5.360 -36.842  1.00 82.24           C  
+ANISOU  101  CG  GLU A  14    10442   9031  11772    314    834    268       C  
+ATOM    102  CD  GLU A  14      -7.938   6.250 -37.944  1.00 92.51           C  
+ANISOU  102  CD  GLU A  14    11677  10275  13196    506    998    534       C  
+ATOM    103  OE1 GLU A  14      -7.274   6.424 -38.992  1.00104.38           O  
+ANISOU  103  OE1 GLU A  14    13129  11783  14748    536   1022    696       O  
+ATOM    104  OE2 GLU A  14      -9.080   6.772 -37.742  1.00 86.19           O  
+ANISOU  104  OE2 GLU A  14    10870   9437  12438    645   1116    601       O  
+ATOM    105  N   LEU A  15      -4.066   2.220 -36.076  1.00 51.18           N  
+ANISOU  105  N   LEU A  15     6481   5467   7497    -72    343    -11       N  
+ATOM    106  CA  LEU A  15      -2.856   1.402 -36.095  1.00 54.47           C  
+ANISOU  106  CA  LEU A  15     6850   5982   7861   -141    239    -15       C  
+ATOM    107  C   LEU A  15      -3.099   0.276 -37.078  1.00 53.30           C  
+ANISOU  107  C   LEU A  15     6690   5980   7581    -52    221    115       C  
+ATOM    108  O   LEU A  15      -4.183  -0.290 -37.098  1.00 48.80           O  
+ANISOU  108  O   LEU A  15     6160   5493   6889      1    217    115       O  
+ATOM    109  CB  LEU A  15      -2.708   0.632 -34.773  1.00 53.82           C  
+ANISOU  109  CB  LEU A  15     6808   6004   7634   -208    122   -164       C  
+ATOM    110  CG  LEU A  15      -2.570   1.415 -33.509  1.00 64.22           C  
+ANISOU  110  CG  LEU A  15     8165   7251   8981   -319    102   -355       C  
+ATOM    111  CD1 LEU A  15      -2.755   0.471 -32.348  1.00 68.91           C  
+ANISOU  111  CD1 LEU A  15     8813   8000   9367   -328      0   -448       C  
+ATOM    112  CD2 LEU A  15      -1.203   2.057 -33.474  1.00 66.88           C  
+ANISOU  112  CD2 LEU A  15     8412   7542   9455   -454     71   -403       C  
+ATOM    113  N   GLU A  16      -2.062  -0.144 -37.776  1.00 49.83           N  
+ANISOU  113  N   GLU A  16     6195   5579   7157    -55    214    200       N  
+ATOM    114  CA  GLU A  16      -2.191  -1.180 -38.784  1.00 52.00           C  
+ANISOU  114  CA  GLU A  16     6480   5971   7306     14    227    299       C  
+ATOM    115  C   GLU A  16      -1.165  -2.260 -38.528  1.00 44.99           C  
+ANISOU  115  C   GLU A  16     5581   5146   6365     -5    183    291       C  
+ATOM    116  O   GLU A  16      -0.027  -1.953 -38.247  1.00 41.15           O  
+ANISOU  116  O   GLU A  16     5017   4632   5983    -49    162    300       O  
+ATOM    117  CB  GLU A  16      -1.924  -0.519 -40.111  1.00 56.47           C  
+ANISOU  117  CB  GLU A  16     6996   6506   7952     73    320    454       C  
+ATOM    118  CG  GLU A  16      -2.598  -1.111 -41.299  1.00 64.79           C  
+ANISOU  118  CG  GLU A  16     8072   7693   8851    152    352    546       C  
+ATOM    119  CD  GLU A  16      -2.299  -0.276 -42.542  1.00 77.28           C  
+ANISOU  119  CD  GLU A  16     9604   9258  10500    229    453    725       C  
+ATOM    120  OE1 GLU A  16      -1.932  -0.864 -43.609  1.00 78.06           O  
+ANISOU  120  OE1 GLU A  16     9708   9456  10491    269    492    807       O  
+ATOM    121  OE2 GLU A  16      -2.392   0.977 -42.427  1.00 67.17           O  
+ANISOU  121  OE2 GLU A  16     8290   7846   9385    251    517    784       O  
+ATOM    122  N   VAL A  17      -1.538  -3.520 -38.672  1.00 44.95           N  
+ANISOU  122  N   VAL A  17     5642   5223   6212     27    187    284       N  
+ATOM    123  CA  VAL A  17      -0.578  -4.609 -38.466  1.00 43.94           C  
+ANISOU  123  CA  VAL A  17     5513   5132   6048     46    189    310       C  
+ATOM    124  C   VAL A  17      -0.754  -5.509 -39.643  1.00 44.55           C  
+ANISOU  124  C   VAL A  17     5652   5239   6033     92    285    360       C  
+ATOM    125  O   VAL A  17      -1.732  -5.387 -40.374  1.00 43.66           O  
+ANISOU  125  O   VAL A  17     5577   5162   5847     87    308    346       O  
+ATOM    126  CB  VAL A  17      -0.860  -5.372 -37.157  1.00 48.00           C  
+ANISOU  126  CB  VAL A  17     6081   5682   6472     37    134    232       C  
+ATOM    127  CG1 VAL A  17      -0.799  -4.395 -35.985  1.00 48.79           C  
+ANISOU  127  CG1 VAL A  17     6138   5776   6622    -25     37    146       C  
+ATOM    128  CG2 VAL A  17      -2.229  -6.014 -37.217  1.00 46.07           C  
+ANISOU  128  CG2 VAL A  17     5942   5449   6111     34    172    170       C  
+ATOM    129  N   GLU A  18       0.203  -6.379 -39.858  1.00 39.17           N  
+ANISOU  129  N   GLU A  18     4974   4558   5351    139    347    420       N  
+ATOM    130  CA  GLU A  18       0.027  -7.446 -40.834  1.00 45.65           C  
+ANISOU  130  CA  GLU A  18     5895   5385   6065    169    468    423       C  
+ATOM    131  C   GLU A  18      -0.549  -8.660 -40.120  1.00 42.91           C  
+ANISOU  131  C   GLU A  18     5658   5017   5628    157    500    342       C  
+ATOM    132  O   GLU A  18      -0.225  -8.908 -38.983  1.00 50.61           O  
+ANISOU  132  O   GLU A  18     6614   5981   6632    182    459    356       O  
+ATOM    133  CB  GLU A  18       1.373  -7.869 -41.404  1.00 49.05           C  
+ANISOU  133  CB  GLU A  18     6290   5791   6555    246    574    536       C  
+ATOM    134  CG  GLU A  18       1.849  -7.008 -42.535  1.00 63.96           C  
+ANISOU  134  CG  GLU A  18     8110   7691   8497    264    618    626       C  
+ATOM    135  CD  GLU A  18       3.316  -7.261 -42.867  1.00 75.06           C  
+ANISOU  135  CD  GLU A  18     9436   9075  10006    344    719    757       C  
+ATOM    136  OE1 GLU A  18       3.833  -8.337 -42.508  1.00 68.87           O  
+ANISOU  136  OE1 GLU A  18     8682   8270   9214    408    790    779       O  
+ATOM    137  OE2 GLU A  18       3.953  -6.347 -43.437  1.00 86.26           O  
+ANISOU  137  OE2 GLU A  18    10749  10492  11532    351    741    855       O  
+ATOM    138  N   CYS A  19      -1.403  -9.397 -40.824  1.00 42.18           N  
+ANISOU  138  N   CYS A  19     5675   4930   5420    110    580    258       N  
+ATOM    139  CA  CYS A  19      -2.100 -10.522 -40.319  1.00 42.40           C  
+ANISOU  139  CA  CYS A  19     5816   4913   5379     67    646    167       C  
+ATOM    140  C   CYS A  19      -2.196 -11.521 -41.439  1.00 43.87           C  
+ANISOU  140  C   CYS A  19     6123   5068   5477     29    804     99       C  
+ATOM    141  O   CYS A  19      -2.743 -11.212 -42.516  1.00 43.45           O  
+ANISOU  141  O   CYS A  19     6070   5112   5326    -37    790     39       O  
+ATOM    142  CB  CYS A  19      -3.524 -10.140 -39.826  1.00 45.74           C  
+ANISOU  142  CB  CYS A  19     6229   5397   5751    -23    551     67       C  
+ATOM    143  SG  CYS A  19      -4.342 -11.539 -39.028  1.00 48.02           S  
+ANISOU  143  SG  CYS A  19     6646   5607   5990    -86    658    -31       S  
+ATOM    144  N   ASP A  20      -1.642 -12.721 -41.204  1.00 45.23           N  
+ANISOU  144  N   ASP A  20     6401   5109   5674     79    970    112       N  
+ATOM    145  CA  ASP A  20      -1.719 -13.795 -42.164  1.00 47.07           C  
+ANISOU  145  CA  ASP A  20     6786   5262   5833     28   1168     13       C  
+ATOM    146  C   ASP A  20      -3.181 -14.220 -42.331  1.00 49.54           C  
+ANISOU  146  C   ASP A  20     7175   5613   6035   -157   1166   -185       C  
+ATOM    147  O   ASP A  20      -4.027 -14.046 -41.400  1.00 46.05           O  
+ANISOU  147  O   ASP A  20     6690   5197   5609   -209   1069   -212       O  
+ATOM    148  CB  ASP A  20      -0.895 -14.979 -41.694  1.00 49.71           C  
+ANISOU  148  CB  ASP A  20     7224   5411   6252    141   1380     87       C  
+ATOM    149  CG  ASP A  20       0.620 -14.723 -41.766  1.00 64.33           C  
+ANISOU  149  CG  ASP A  20     8980   7254   8207    325   1414    288       C  
+ATOM    150  OD1 ASP A  20       1.122 -14.017 -42.690  1.00 70.08           O  
+ANISOU  150  OD1 ASP A  20     9639   8062   8926    341   1382    324       O  
+ATOM    151  OD2 ASP A  20       1.349 -15.271 -40.906  1.00 70.59           O  
+ANISOU  151  OD2 ASP A  20     9755   7971   9092    465   1489    432       O  
+ATOM    152  N   ASP A  21      -3.481 -14.806 -43.487  1.00 48.95           N  
+ANISOU  152  N   ASP A  21     7206   5550   5840   -269   1280   -332       N  
+ATOM    153  CA  ASP A  21      -4.886 -15.213 -43.792  1.00 52.07           C  
+ANISOU  153  CA  ASP A  21     7641   6029   6111   -489   1264   -547       C  
+ATOM    154  C   ASP A  21      -5.466 -16.364 -42.961  1.00 52.45           C  
+ANISOU  154  C   ASP A  21     7805   5898   6224   -586   1412   -652       C  
+ATOM    155  O   ASP A  21      -6.664 -16.621 -43.076  1.00 52.58           O  
+ANISOU  155  O   ASP A  21     7815   5996   6165   -785   1384   -823       O  
+ATOM    156  CB  ASP A  21      -5.080 -15.591 -45.251  1.00 52.20           C  
+ANISOU  156  CB  ASP A  21     7744   6142   5948   -620   1342   -716       C  
+ATOM    157  CG  ASP A  21      -4.192 -16.744 -45.675  1.00 54.79           C  
+ANISOU  157  CG  ASP A  21     8277   6240   6300   -591   1630   -772       C  
+ATOM    158  OD1 ASP A  21      -3.555 -17.399 -44.833  1.00 60.00           O  
+ANISOU  158  OD1 ASP A  21     9012   6660   7124   -475   1787   -678       O  
+ATOM    159  OD2 ASP A  21      -4.087 -16.978 -46.873  1.00 66.22           O  
+ANISOU  159  OD2 ASP A  21     9812   7754   7595   -664   1713   -895       O  
+ATOM    160  N   ASP A  22      -4.630 -17.059 -42.198  1.00 51.20           N  
+ANISOU  160  N   ASP A  22     7738   5513   6200   -447   1580   -538       N  
+ATOM    161  CA  ASP A  22      -5.098 -18.073 -41.256  1.00 62.00           C  
+ANISOU  161  CA  ASP A  22     9213   6691   7651   -492   1741   -573       C  
+ATOM    162  C   ASP A  22      -4.625 -17.744 -39.835  1.00 59.08           C  
+ANISOU  162  C   ASP A  22     8764   6294   7388   -296   1668   -346       C  
+ATOM    163  O   ASP A  22      -4.377 -18.629 -39.062  1.00 56.04           O  
+ANISOU  163  O   ASP A  22     8478   5724   7088   -214   1851   -264       O  
+ATOM    164  CB  ASP A  22      -4.624 -19.475 -41.714  1.00 71.33           C  
+ANISOU  164  CB  ASP A  22    10621   7602   8879   -509   2084   -652       C  
+ATOM    165  CG  ASP A  22      -3.093 -19.576 -41.838  1.00 78.00           C  
+ANISOU  165  CG  ASP A  22    11493   8340   9802   -254   2204   -448       C  
+ATOM    166  OD1 ASP A  22      -2.396 -18.536 -41.681  1.00 74.86           O  
+ANISOU  166  OD1 ASP A  22    10926   8102   9414   -102   2002   -272       O  
+ATOM    167  OD2 ASP A  22      -2.585 -20.697 -42.122  1.00 91.53           O  
+ANISOU  167  OD2 ASP A  22    13392   9804  11580   -212   2520   -469       O  
+ATOM    168  N   VAL A  23      -4.476 -16.441 -39.535  1.00 58.45           N  
+ANISOU  168  N   VAL A  23     8509   6406   7294   -222   1411   -246       N  
+ATOM    169  CA  VAL A  23      -4.282 -15.937 -38.191  1.00 51.17           C  
+ANISOU  169  CA  VAL A  23     7497   5521   6421    -98   1292    -96       C  
+ATOM    170  C   VAL A  23      -5.446 -15.027 -37.852  1.00 51.90           C  
+ANISOU  170  C   VAL A  23     7486   5767   6465   -205   1102   -177       C  
+ATOM    171  O   VAL A  23      -5.900 -14.243 -38.695  1.00 52.44           O  
+ANISOU  171  O   VAL A  23     7471   5973   6479   -287    979   -252       O  
+ATOM    172  CB  VAL A  23      -2.914 -15.295 -38.008  1.00 53.75           C  
+ANISOU  172  CB  VAL A  23     7721   5902   6798     84   1203     86       C  
+ATOM    173  CG1 VAL A  23      -2.782 -14.588 -36.643  1.00 51.64           C  
+ANISOU  173  CG1 VAL A  23     7345   5733   6542    168   1034    192       C  
+ATOM    174  CG2 VAL A  23      -1.872 -16.402 -38.166  1.00 52.89           C  
+ANISOU  174  CG2 VAL A  23     7712   5629   6754    219   1438    196       C  
+ATOM    175  N   TYR A  24      -6.020 -15.271 -36.669  1.00 49.92           N  
+ANISOU  175  N   TYR A  24     7252   5485   6229   -201   1118   -157       N  
+ATOM    176  CA  TYR A  24      -7.105 -14.448 -36.120  1.00 49.53           C  
+ANISOU  176  CA  TYR A  24     7107   5561   6149   -269    973   -210       C  
+ATOM    177  C   TYR A  24      -6.598 -13.062 -35.859  1.00 47.90           C  
+ANISOU  177  C   TYR A  24     6775   5477   5946   -174    771   -133       C  
+ATOM    178  O   TYR A  24      -5.530 -12.835 -35.330  1.00 50.41           O  
+ANISOU  178  O   TYR A  24     7074   5788   6289    -48    730    -19       O  
+ATOM    179  CB  TYR A  24      -7.664 -14.995 -34.792  1.00 53.15           C  
+ANISOU  179  CB  TYR A  24     7622   5953   6617   -252   1058   -177       C  
+ATOM    180  CG  TYR A  24      -8.368 -16.349 -34.905  1.00 54.34           C  
+ANISOU  180  CG  TYR A  24     7898   5950   6796   -378   1291   -263       C  
+ATOM    181  CD1 TYR A  24      -9.369 -16.572 -35.863  1.00 61.64           C  
+ANISOU  181  CD1 TYR A  24     8804   6911   7703   -595   1316   -450       C  
+ATOM    182  CD2 TYR A  24      -8.040 -17.385 -34.061  1.00 58.06           C  
+ANISOU  182  CD2 TYR A  24     8497   6251   7312   -288   1491   -157       C  
+ATOM    183  CE1 TYR A  24     -10.013 -17.808 -35.976  1.00 67.82           C  
+ANISOU  183  CE1 TYR A  24     9699   7540   8526   -757   1542   -565       C  
+ATOM    184  CE2 TYR A  24      -8.691 -18.634 -34.147  1.00 67.21           C  
+ANISOU  184  CE2 TYR A  24     9786   7220   8531   -418   1749   -239       C  
+ATOM    185  CZ  TYR A  24      -9.672 -18.847 -35.099  1.00 67.30           C  
+ANISOU  185  CZ  TYR A  24     9783   7246   8539   -670   1777   -462       C  
+ATOM    186  OH  TYR A  24     -10.304 -20.070 -35.167  1.00 63.80           O  
+ANISOU  186  OH  TYR A  24     9466   6605   8170   -838   2041   -574       O  
+ATOM    187  N   VAL A  25      -7.413 -12.132 -36.241  1.00 46.10           N  
+ANISOU  187  N   VAL A  25     6451   5365   5699   -242    655   -197       N  
+ATOM    188  CA  VAL A  25      -7.117 -10.720 -36.104  1.00 50.13           C  
+ANISOU  188  CA  VAL A  25     6853   5957   6237   -176    500   -147       C  
+ATOM    189  C   VAL A  25      -6.622 -10.284 -34.725  1.00 49.08           C  
+ANISOU  189  C   VAL A  25     6716   5810   6120    -88    441    -91       C  
+ATOM    190  O   VAL A  25      -5.602  -9.548 -34.601  1.00 48.80           O  
+ANISOU  190  O   VAL A  25     6626   5791   6124    -25    355    -35       O  
+ATOM    191  CB  VAL A  25      -8.407 -10.000 -36.511  1.00 51.95           C  
+ANISOU  191  CB  VAL A  25     6992   6297   6447   -248    438   -209       C  
+ATOM    192  CG1 VAL A  25      -8.482  -8.654 -35.889  1.00 65.37           C  
+ANISOU  192  CG1 VAL A  25     8616   8025   8196   -180    340   -173       C  
+ATOM    193  CG2 VAL A  25      -8.427  -9.935 -38.033  1.00 52.86           C  
+ANISOU  193  CG2 VAL A  25     7066   6496   6519   -296    427   -223       C  
+ATOM    194  N   LEU A  26      -7.301 -10.753 -33.681  1.00 50.45           N  
+ANISOU  194  N   LEU A  26     6944   5968   6256    -94    493   -112       N  
+ATOM    195  CA  LEU A  26      -6.902 -10.450 -32.290  1.00 47.33           C  
+ANISOU  195  CA  LEU A  26     6562   5597   5822    -14    443    -70       C  
+ATOM    196  C   LEU A  26      -5.498 -10.894 -32.035  1.00 47.06           C  
+ANISOU  196  C   LEU A  26     6537   5561   5781     82    436     39       C  
+ATOM    197  O   LEU A  26      -4.721 -10.194 -31.377  1.00 55.96           O  
+ANISOU  197  O   LEU A  26     7607   6768   6888    128    318     66       O  
+ATOM    198  CB  LEU A  26      -7.807 -11.200 -31.306  1.00 53.55           C  
+ANISOU  198  CB  LEU A  26     7430   6361   6552    -20    551    -79       C  
+ATOM    199  CG  LEU A  26      -7.510 -11.161 -29.801  1.00 55.13           C  
+ANISOU  199  CG  LEU A  26     7674   6613   6658     70    531    -26       C  
+ATOM    200  CD1 LEU A  26      -7.326  -9.720 -29.325  1.00 55.80           C  
+ANISOU  200  CD1 LEU A  26     7695   6785   6722     71    376    -95       C  
+ATOM    201  CD2 LEU A  26      -8.666 -11.852 -29.032  1.00 61.83           C  
+ANISOU  201  CD2 LEU A  26     8600   7428   7463     52    674    -33       C  
+ATOM    202  N   ASP A  27      -5.136 -12.069 -32.524  1.00 48.10           N  
+ANISOU  202  N   ASP A  27     6734   5609   5931    111    571    103       N  
+ATOM    203  CA  ASP A  27      -3.762 -12.587 -32.305  1.00 48.67           C  
+ANISOU  203  CA  ASP A  27     6797   5686   6010    241    594    251       C  
+ATOM    204  C   ASP A  27      -2.701 -11.814 -33.061  1.00 52.00           C  
+ANISOU  204  C   ASP A  27     7102   6157   6495    255    488    280       C  
+ATOM    205  O   ASP A  27      -1.619 -11.648 -32.546  1.00 53.84           O  
+ANISOU  205  O   ASP A  27     7252   6477   6725    342    414    382       O  
+ATOM    206  CB  ASP A  27      -3.670 -14.036 -32.649  1.00 52.61           C  
+ANISOU  206  CB  ASP A  27     7412   6040   6537    286    816    319       C  
+ATOM    207  CG  ASP A  27      -4.541 -14.887 -31.735  1.00 63.74           C  
+ANISOU  207  CG  ASP A  27     8934   7380   7902    289    954    332       C  
+ATOM    208  OD1 ASP A  27      -4.580 -14.630 -30.517  1.00 77.44           O  
+ANISOU  208  OD1 ASP A  27    10653   9216   9555    357    885    392       O  
+ATOM    209  OD2 ASP A  27      -5.228 -15.780 -32.232  1.00 66.17           O  
+ANISOU  209  OD2 ASP A  27     9347   7539   8253    206   1139    268       O  
+ATOM    210  N   ALA A  28      -3.001 -11.313 -34.254  1.00 45.81           N  
+ANISOU  210  N   ALA A  28     6297   5346   5763    172    479    200       N  
+ATOM    211  CA  ALA A  28      -2.052 -10.414 -34.921  1.00 45.27           C  
+ANISOU  211  CA  ALA A  28     6113   5321   5765    182    389    237       C  
+ATOM    212  C   ALA A  28      -1.874  -9.098 -34.113  1.00 46.76           C  
+ANISOU  212  C   ALA A  28     6200   5596   5968    152    219    201       C  
+ATOM    213  O   ALA A  28      -0.782  -8.604 -33.981  1.00 48.55           O  
+ANISOU  213  O   ALA A  28     6321   5880   6246    174    142    256       O  
+ATOM    214  CB  ALA A  28      -2.542 -10.096 -36.327  1.00 48.41           C  
+ANISOU  214  CB  ALA A  28     6517   5691   6185    111    423    175       C  
+ATOM    215  N   ALA A  29      -2.956  -8.547 -33.566  1.00 46.59           N  
+ANISOU  215  N   ALA A  29     6211   5582   5908     92    177     97       N  
+ATOM    216  CA  ALA A  29      -2.882  -7.367 -32.726  1.00 45.37           C  
+ANISOU  216  CA  ALA A  29     6001   5476   5760     54     58     28       C  
+ATOM    217  C   ALA A  29      -2.026  -7.590 -31.506  1.00 45.22           C  
+ANISOU  217  C   ALA A  29     5953   5566   5662     94    -18     63       C  
+ATOM    218  O   ALA A  29      -1.157  -6.760 -31.212  1.00 50.70           O  
+ANISOU  218  O   ALA A  29     6546   6327   6390     54   -129     39       O  
+ATOM    219  CB  ALA A  29      -4.268  -6.959 -32.299  1.00 45.68           C  
+ANISOU  219  CB  ALA A  29     6100   5492   5763     13     76    -74       C  
+ATOM    220  N   GLU A  30      -2.244  -8.704 -30.827  1.00 46.60           N  
+ANISOU  220  N   GLU A  30     6205   5769   5730    169     45    128       N  
+ATOM    221  CA  GLU A  30      -1.476  -9.042 -29.636  1.00 54.76           C  
+ANISOU  221  CA  GLU A  30     7206   6952   6646    243    -23    204       C  
+ATOM    222  C   GLU A  30      -0.050  -9.331 -29.955  1.00 54.21           C  
+ANISOU  222  C   GLU A  30     7010   6960   6626    313    -60    346       C  
+ATOM    223  O   GLU A  30       0.832  -8.823 -29.288  1.00 59.88           O  
+ANISOU  223  O   GLU A  30     7604   7849   7298    302   -205    354       O  
+ATOM    224  CB  GLU A  30      -2.122 -10.183 -28.852  1.00 62.74           C  
+ANISOU  224  CB  GLU A  30     8340   7960   7538    332     92    277       C  
+ATOM    225  CG  GLU A  30      -3.470  -9.711 -28.236  1.00 72.73           C  
+ANISOU  225  CG  GLU A  30     9697   9198   8739    260    106    134       C  
+ATOM    226  CD  GLU A  30      -4.256 -10.812 -27.471  1.00 84.77           C  
+ANISOU  226  CD  GLU A  30    11347  10699  10163    332    254    206       C  
+ATOM    227  OE1 GLU A  30      -4.358 -12.001 -27.950  1.00 80.99           O  
+ANISOU  227  OE1 GLU A  30    10932  10104   9737    384    419    311       O  
+ATOM    228  OE2 GLU A  30      -4.785 -10.463 -26.379  1.00 83.98           O  
+ANISOU  228  OE2 GLU A  30    11293  10681   9934    331    227    148       O  
+ATOM    229  N   GLU A  31       0.205 -10.073 -31.022  1.00 59.74           N  
+ANISOU  229  N   GLU A  31     7727   7548   7422    373     72    443       N  
+ATOM    230  CA  GLU A  31       1.574 -10.229 -31.535  1.00 56.65           C  
+ANISOU  230  CA  GLU A  31     7199   7210   7113    445     65    582       C  
+ATOM    231  C   GLU A  31       2.251  -8.862 -31.829  1.00 60.42           C  
+ANISOU  231  C   GLU A  31     7517   7754   7685    328    -85    505       C  
+ATOM    232  O   GLU A  31       3.462  -8.797 -31.776  1.00 66.23           O  
+ANISOU  232  O   GLU A  31     8087   8613   8463    365   -148    610       O  
+ATOM    233  CB  GLU A  31       1.536 -11.120 -32.771  1.00 64.23           C  
+ANISOU  233  CB  GLU A  31     8243   7999   8160    503    266    648       C  
+ATOM    234  CG  GLU A  31       2.861 -11.443 -33.471  1.00 83.36           C  
+ANISOU  234  CG  GLU A  31    10552  10436  10682    606    328    807       C  
+ATOM    235  CD  GLU A  31       3.790 -12.366 -32.669  1.00102.53           C  
+ANISOU  235  CD  GLU A  31    12908  12976  13073    795    367   1027       C  
+ATOM    236  OE1 GLU A  31       3.385 -12.888 -31.594  1.00113.60           O  
+ANISOU  236  OE1 GLU A  31    14374  14434  14355    859    366   1071       O  
+ATOM    237  OE2 GLU A  31       4.946 -12.569 -33.121  1.00102.50           O  
+ANISOU  237  OE2 GLU A  31    12770  13016  13158    898    412   1182       O  
+ATOM    238  N   ALA A  32       1.502  -7.778 -32.127  1.00 57.02           N  
+ANISOU  238  N   ALA A  32     7120   7240   7301    193   -126    339       N  
+ATOM    239  CA  ALA A  32       2.120  -6.424 -32.410  1.00 59.13           C  
+ANISOU  239  CA  ALA A  32     7255   7519   7692     72   -224    265       C  
+ATOM    240  C   ALA A  32       2.224  -5.516 -31.195  1.00 56.89           C  
+ANISOU  240  C   ALA A  32     6920   7349   7346    -41   -378    124       C  
+ATOM    241  O   ALA A  32       2.754  -4.418 -31.279  1.00 65.02           O  
+ANISOU  241  O   ALA A  32     7847   8373   8484   -167   -445     38       O  
+ATOM    242  CB  ALA A  32       1.377  -5.676 -33.506  1.00 53.40           C  
+ANISOU  242  CB  ALA A  32     6588   6626   7076     11   -149    198       C  
+ATOM    243  N   GLY A  33       1.719  -5.987 -30.077  1.00 55.56           N  
+ANISOU  243  N   GLY A  33     6832   7274   7000     -3   -414     93       N  
+ATOM    244  CA  GLY A  33       1.778  -5.257 -28.849  1.00 58.15           C  
+ANISOU  244  CA  GLY A  33     7139   7738   7215   -106   -550    -56       C  
+ATOM    245  C   GLY A  33       0.546  -4.451 -28.568  1.00 60.01           C  
+ANISOU  245  C   GLY A  33     7520   7837   7440   -190   -508   -246       C  
+ATOM    246  O   GLY A  33       0.620  -3.573 -27.741  1.00 69.82           O  
+ANISOU  246  O   GLY A  33     8758   9139   8629   -310   -593   -418       O  
+ATOM    247  N   ILE A  34      -0.586  -4.759 -29.224  1.00 61.19           N  
+ANISOU  247  N   ILE A  34     7792   7820   7635   -130   -370   -221       N  
+ATOM    248  CA  ILE A  34      -1.840  -4.017 -29.038  1.00 58.99           C  
+ANISOU  248  CA  ILE A  34     7627   7418   7368   -177   -306   -363       C  
+ATOM    249  C   ILE A  34      -2.765  -4.786 -28.094  1.00 62.15           C  
+ANISOU  249  C   ILE A  34     8149   7876   7589   -107   -261   -367       C  
+ATOM    250  O   ILE A  34      -3.056  -5.947 -28.343  1.00 63.50           O  
+ANISOU  250  O   ILE A  34     8359   8045   7721    -13   -186   -239       O  
+ATOM    251  CB  ILE A  34      -2.577  -3.788 -30.390  1.00 61.05           C  
+ANISOU  251  CB  ILE A  34     7905   7506   7785   -156   -191   -318       C  
+ATOM    252  CG1 ILE A  34      -1.701  -2.983 -31.385  1.00 65.68           C  
+ANISOU  252  CG1 ILE A  34     8381   8020   8552   -209   -201   -287       C  
+ATOM    253  CG2 ILE A  34      -3.890  -3.029 -30.184  1.00 63.01           C  
+ANISOU  253  CG2 ILE A  34     8237   7650   8053   -172   -114   -425       C  
+ATOM    254  CD1 ILE A  34      -2.244  -3.016 -32.822  1.00 63.49           C  
+ANISOU  254  CD1 ILE A  34     8108   7640   8375   -155    -98   -187       C  
+ATOM    255  N   ASP A  35      -3.244  -4.141 -27.030  1.00 63.70           N  
+ANISOU  255  N   ASP A  35     8416   8102   7682   -158   -279   -520       N  
+ATOM    256  CA  ASP A  35      -4.088  -4.826 -26.025  1.00 71.88           C  
+ANISOU  256  CA  ASP A  35     9569   9207   8534    -87   -222   -516       C  
+ATOM    257  C   ASP A  35      -5.567  -4.529 -26.307  1.00 64.56           C  
+ANISOU  257  C   ASP A  35     8725   8121   7682    -77    -78   -569       C  
+ATOM    258  O   ASP A  35      -6.130  -3.476 -25.980  1.00 66.69           O  
+ANISOU  258  O   ASP A  35     9040   8315   7983   -127    -40   -715       O  
+ATOM    259  CB  ASP A  35      -3.667  -4.503 -24.557  1.00 81.85           C  
+ANISOU  259  CB  ASP A  35    10862  10660   9575   -127   -325   -633       C  
+ATOM    260  CG  ASP A  35      -2.375  -5.278 -24.107  1.00 93.06           C  
+ANISOU  260  CG  ASP A  35    12178  12327  10851    -75   -463   -497       C  
+ATOM    261  OD1 ASP A  35      -2.081  -6.430 -24.567  1.00 87.57           O  
+ANISOU  261  OD1 ASP A  35    11448  11645  10179     50   -418   -279       O  
+ATOM    262  OD2 ASP A  35      -1.644  -4.712 -23.270  1.00100.07           O  
+ANISOU  262  OD2 ASP A  35    13013  13406  11600   -161   -608   -613       O  
+ATOM    263  N   LEU A  36      -6.157  -5.471 -27.010  1.00 61.66           N  
+ANISOU  263  N   LEU A  36     8363   7702   7362    -15     12   -444       N  
+ATOM    264  CA  LEU A  36      -7.533  -5.410 -27.353  1.00 56.54           C  
+ANISOU  264  CA  LEU A  36     7746   6959   6776     -6    133   -460       C  
+ATOM    265  C   LEU A  36      -8.274  -6.365 -26.418  1.00 58.83           C  
+ANISOU  265  C   LEU A  36     8125   7301   6925     45    227   -424       C  
+ATOM    266  O   LEU A  36      -7.725  -7.349 -25.938  1.00 59.91           O  
+ANISOU  266  O   LEU A  36     8295   7514   6951     96    222   -330       O  
+ATOM    267  CB  LEU A  36      -7.736  -5.829 -28.796  1.00 54.45           C  
+ANISOU  267  CB  LEU A  36     7414   6631   6642     -9    169   -370       C  
+ATOM    268  CG  LEU A  36      -7.150  -4.979 -29.924  1.00 47.43           C  
+ANISOU  268  CG  LEU A  36     6437   5685   5896    -39    115   -362       C  
+ATOM    269  CD1 LEU A  36      -7.661  -5.559 -31.235  1.00 51.84           C  
+ANISOU  269  CD1 LEU A  36     6953   6228   6515    -36    168   -282       C  
+ATOM    270  CD2 LEU A  36      -7.550  -3.538 -29.754  1.00 48.45           C  
+ANISOU  270  CD2 LEU A  36     6559   5739   6109    -60    130   -458       C  
+ATOM    271  N   PRO A  37      -9.531  -6.066 -26.158  1.00 52.73           N  
+ANISOU  271  N   PRO A  37     7383   6483   6166     48    333   -477       N  
+ATOM    272  CA  PRO A  37     -10.189  -6.803 -25.130  1.00 48.70           C  
+ANISOU  272  CA  PRO A  37     6960   6021   5521     94    437   -451       C  
+ATOM    273  C   PRO A  37     -10.579  -8.210 -25.630  1.00 53.45           C  
+ANISOU  273  C   PRO A  37     7558   6591   6158    100    538   -324       C  
+ATOM    274  O   PRO A  37     -10.785  -8.477 -26.837  1.00 49.32           O  
+ANISOU  274  O   PRO A  37     6961   6008   5767     49    548   -299       O  
+ATOM    275  CB  PRO A  37     -11.382  -5.907 -24.791  1.00 51.49           C  
+ANISOU  275  CB  PRO A  37     7326   6326   5910     95    535   -549       C  
+ATOM    276  CG  PRO A  37     -11.712  -5.243 -26.095  1.00 57.15           C  
+ANISOU  276  CG  PRO A  37     7927   6960   6825     64    521   -547       C  
+ATOM    277  CD  PRO A  37     -10.372  -5.018 -26.760  1.00 53.71           C  
+ANISOU  277  CD  PRO A  37     7454   6520   6432     30    379   -538       C  
+ATOM    278  N   TYR A  38     -10.593  -9.140 -24.698  1.00 52.25           N  
+ANISOU  278  N   TYR A  38     7494   6481   5875    159    621   -244       N  
+ATOM    279  CA  TYR A  38     -11.096 -10.464 -24.975  1.00 58.41           C  
+ANISOU  279  CA  TYR A  38     8299   7189   6702    152    774   -141       C  
+ATOM    280  C   TYR A  38     -11.545 -11.071 -23.664  1.00 53.69           C  
+ANISOU  280  C   TYR A  38     7809   6629   5962    227    913    -70       C  
+ATOM    281  O   TYR A  38     -11.164 -10.607 -22.581  1.00 52.44           O  
+ANISOU  281  O   TYR A  38     7709   6589   5625    301    856    -82       O  
+ATOM    282  CB  TYR A  38      -9.996 -11.359 -25.594  1.00 54.60           C  
+ANISOU  282  CB  TYR A  38     7821   6673   6252    178    755    -32       C  
+ATOM    283  CG  TYR A  38      -8.783 -11.536 -24.706  1.00 57.28           C  
+ANISOU  283  CG  TYR A  38     8199   7128   6435    299    678     70       C  
+ATOM    284  CD1 TYR A  38      -7.743 -10.603 -24.717  1.00 57.17           C  
+ANISOU  284  CD1 TYR A  38     8115   7222   6383    300    480     19       C  
+ATOM    285  CD2 TYR A  38      -8.679 -12.620 -23.830  1.00 58.87           C  
+ANISOU  285  CD2 TYR A  38     8492   7349   6525    412    809    232       C  
+ATOM    286  CE1 TYR A  38      -6.629 -10.752 -23.885  1.00 58.64           C  
+ANISOU  286  CE1 TYR A  38     8295   7574   6409    396    383    113       C  
+ATOM    287  CE2 TYR A  38      -7.565 -12.768 -22.985  1.00 62.26           C  
+ANISOU  287  CE2 TYR A  38     8928   7946   6781    545    722    358       C  
+ATOM    288  CZ  TYR A  38      -6.548 -11.837 -23.032  1.00 65.34           C  
+ANISOU  288  CZ  TYR A  38     9221   8482   7122    529    494    291       C  
+ATOM    289  OH  TYR A  38      -5.464 -11.963 -22.208  1.00 72.76           O  
+ANISOU  289  OH  TYR A  38    10126   9640   7877    642    383    410       O  
+ATOM    290  N   SER A  39     -12.264 -12.176 -23.765  1.00 51.04           N  
+ANISOU  290  N   SER A  39     7503   6198   5691    202   1102      6       N  
+ATOM    291  CA  SER A  39     -12.527 -12.977 -22.556  1.00 52.70           C  
+ANISOU  291  CA  SER A  39     7828   6422   5770    296   1272    129       C  
+ATOM    292  C   SER A  39     -12.586 -14.475 -22.933  1.00 54.61           C  
+ANISOU  292  C   SER A  39     8121   6509   6118    281   1473    260       C  
+ATOM    293  O   SER A  39     -11.697 -15.196 -22.537  1.00 53.05           O  
+ANISOU  293  O   SER A  39     8000   6314   5842    404   1512    419       O  
+ATOM    294  CB  SER A  39     -13.752 -12.480 -21.786  1.00 50.63           C  
+ANISOU  294  CB  SER A  39     7578   6193   5466    287   1376     64       C  
+ATOM    295  OG  SER A  39     -14.134 -13.447 -20.804  1.00 57.92           O  
+ANISOU  295  OG  SER A  39     8608   7099   6297    364   1590    207       O  
+ATOM    296  N   CYS A  40     -13.549 -14.932 -23.748  1.00 56.78           N  
+ANISOU  296  N   CYS A  40     8345   6653   6573    127   1599    192       N  
+ATOM    297  CA  CYS A  40     -13.634 -16.380 -24.053  1.00 57.29           C  
+ANISOU  297  CA  CYS A  40     8484   6532   6749     80   1830    281       C  
+ATOM    298  C   CYS A  40     -12.645 -16.882 -25.075  1.00 62.77           C  
+ANISOU  298  C   CYS A  40     9192   7133   7523     67   1795    293       C  
+ATOM    299  O   CYS A  40     -12.278 -18.037 -25.022  1.00 51.88           O  
+ANISOU  299  O   CYS A  40     7920   5599   6191    114   1993    416       O  
+ATOM    300  CB  CYS A  40     -15.040 -16.831 -24.469  1.00 62.61           C  
+ANISOU  300  CB  CYS A  40     9100   7107   7580   -117   2004    185       C  
+ATOM    301  SG  CYS A  40     -15.521 -16.463 -26.163  1.00 58.56           S  
+ANISOU  301  SG  CYS A  40     8420   6610   7220   -350   1871    -25       S  
+ATOM    302  N   ARG A  41     -12.233 -16.028 -26.016  1.00 58.00           N  
+ANISOU  302  N   ARG A  41     8486   6605   6943     14   1575    175       N  
+ATOM    303  CA  ARG A  41     -11.317 -16.429 -27.087  1.00 58.21           C  
+ANISOU  303  CA  ARG A  41     8521   6552   7044     -1   1548    175       C  
+ATOM    304  C   ARG A  41     -11.821 -17.585 -27.916  1.00 57.79           C  
+ANISOU  304  C   ARG A  41     8520   6302   7133   -157   1766    115       C  
+ATOM    305  O   ARG A  41     -10.986 -18.264 -28.554  1.00 62.26           O  
+ANISOU  305  O   ARG A  41     9155   6747   7753   -132   1841    152       O  
+ATOM    306  CB  ARG A  41      -9.896 -16.763 -26.569  1.00 63.76           C  
+ANISOU  306  CB  ARG A  41     9289   7271   7664    206   1533    363       C  
+ATOM    307  CG  ARG A  41      -9.186 -15.605 -25.856  1.00 71.18           C  
+ANISOU  307  CG  ARG A  41    10160   8433   8450    322   1286    387       C  
+ATOM    308  CD  ARG A  41      -7.710 -15.932 -25.568  1.00 80.04           C  
+ANISOU  308  CD  ARG A  41    11287   9622   9501    504   1238    570       C  
+ATOM    309  NE  ARG A  41      -6.866 -15.741 -26.762  1.00 74.82           N  
+ANISOU  309  NE  ARG A  41    10554   8929   8944    472   1145    532       N  
+ATOM    310  CZ  ARG A  41      -6.356 -14.582 -27.159  1.00 78.00           C  
+ANISOU  310  CZ  ARG A  41    10841   9455   9338    432    915    438       C  
+ATOM    311  NH1 ARG A  41      -6.552 -13.467 -26.465  1.00 81.90           N  
+ANISOU  311  NH1 ARG A  41    11285  10102   9732    410    749    350       N  
+ATOM    312  NH2 ARG A  41      -5.644 -14.532 -28.265  1.00 86.15           N  
+ANISOU  312  NH2 ARG A  41    11819  10442  10471    411    877    426       N  
+ATOM    313  N   ALA A  42     -13.150 -17.769 -27.971  1.00 49.81           N  
+ANISOU  313  N   ALA A  42     7468   5266   6189   -330   1868      7       N  
+ATOM    314  CA  ALA A  42     -13.768 -18.969 -28.558  1.00 57.96           C  
+ANISOU  314  CA  ALA A  42     8557   6104   7358   -524   2112    -75       C  
+ATOM    315  C   ALA A  42     -14.982 -18.639 -29.396  1.00 57.37           C  
+ANISOU  315  C   ALA A  42     8326   6129   7343   -777   2047   -282       C  
+ATOM    316  O   ALA A  42     -15.740 -19.526 -29.769  1.00 64.20           O  
+ANISOU  316  O   ALA A  42     9200   6877   8312   -990   2234   -389       O  
+ATOM    317  CB  ALA A  42     -14.200 -19.960 -27.458  1.00 56.47           C  
+ANISOU  317  CB  ALA A  42     8492   5754   7210   -482   2407     58       C  
+ATOM    318  N   GLY A  43     -15.231 -17.380 -29.635  1.00 52.54           N  
+ANISOU  318  N   GLY A  43     7557   5735   6668   -759   1799   -332       N  
+ATOM    319  CA  GLY A  43     -16.309 -17.040 -30.539  1.00 57.00           C  
+ANISOU  319  CA  GLY A  43     7941   6441   7273   -969   1718   -492       C  
+ATOM    320  C   GLY A  43     -17.684 -17.010 -29.934  1.00 58.47           C  
+ANISOU  320  C   GLY A  43     8011   6695   7510  -1063   1807   -511       C  
+ATOM    321  O   GLY A  43     -18.693 -16.858 -30.667  1.00 64.87           O  
+ANISOU  321  O   GLY A  43     8633   7651   8361  -1255   1754   -633       O  
+ATOM    322  N   SER A  44     -17.753 -17.088 -28.605  1.00 57.15           N  
+ANISOU  322  N   SER A  44     7930   6459   7323   -922   1931   -379       N  
+ATOM    323  CA  SER A  44     -19.057 -17.205 -27.927  1.00 59.58           C  
+ANISOU  323  CA  SER A  44     8144   6800   7692  -1003   2076   -376       C  
+ATOM    324  C   SER A  44     -19.354 -16.159 -26.851  1.00 51.98           C  
+ANISOU  324  C   SER A  44     7145   5959   6646   -810   2021   -281       C  
+ATOM    325  O   SER A  44     -20.144 -16.385 -25.952  1.00 58.60           O  
+ANISOU  325  O   SER A  44     7978   6778   7507   -803   2197   -224       O  
+ATOM    326  CB  SER A  44     -19.181 -18.630 -27.398  1.00 63.12           C  
+ANISOU  326  CB  SER A  44     8744   7008   8228  -1080   2393   -327       C  
+ATOM    327  OG  SER A  44     -17.989 -18.942 -26.691  1.00 75.47           O  
+ANISOU  327  OG  SER A  44    10522   8441   9708   -852   2451   -164       O  
+ATOM    328  N   CYS A  45     -18.785 -14.973 -26.981  1.00 51.65           N  
+ANISOU  328  N   CYS A  45     7072   6035   6515   -668   1796   -279       N  
+ATOM    329  CA  CYS A  45     -19.057 -13.886 -26.059  1.00 48.70           C  
+ANISOU  329  CA  CYS A  45     6677   5758   6066   -505   1753   -233       C  
+ATOM    330  C   CYS A  45     -18.893 -12.526 -26.780  1.00 51.12           C  
+ANISOU  330  C   CYS A  45     6860   6197   6366   -453   1521   -288       C  
+ATOM    331  O   CYS A  45     -18.542 -12.469 -27.980  1.00 50.80           O  
+ANISOU  331  O   CYS A  45     6749   6191   6360   -532   1388   -341       O  
+ATOM    332  CB  CYS A  45     -18.171 -13.989 -24.805  1.00 50.90           C  
+ANISOU  332  CB  CYS A  45     7166   5974   6198   -316   1803   -126       C  
+ATOM    333  SG  CYS A  45     -16.566 -13.180 -24.911  1.00 53.21           S  
+ANISOU  333  SG  CYS A  45     7542   6307   6366   -170   1556   -124       S  
+ATOM    334  N   SER A  46     -19.167 -11.439 -26.041  1.00 49.49           N  
+ANISOU  334  N   SER A  46     6640   6051   6114   -315   1503   -272       N  
+ATOM    335  CA  SER A  46     -19.261 -10.117 -26.614  1.00 49.31           C  
+ANISOU  335  CA  SER A  46     6494   6118   6122   -255   1356   -304       C  
+ATOM    336  C   SER A  46     -17.941  -9.369 -26.532  1.00 51.44           C  
+ANISOU  336  C   SER A  46     6888   6345   6311   -146   1208   -320       C  
+ATOM    337  O   SER A  46     -17.854  -8.268 -27.057  1.00 46.10           O  
+ANISOU  337  O   SER A  46     6135   5703   5678    -96   1103   -340       O  
+ATOM    338  CB  SER A  46     -20.298  -9.307 -25.871  1.00 50.14           C  
+ANISOU  338  CB  SER A  46     6525   6274   6249   -163   1462   -287       C  
+ATOM    339  OG  SER A  46     -19.973  -9.287 -24.483  1.00 49.91           O  
+ANISOU  339  OG  SER A  46     6691   6179   6094    -53   1563   -278       O  
+ATOM    340  N   SER A  47     -16.939  -9.950 -25.880  1.00 47.00           N  
+ANISOU  340  N   SER A  47     6498   5716   5641   -106   1210   -297       N  
+ATOM    341  CA  SER A  47     -15.839  -9.148 -25.341  1.00 48.40           C  
+ANISOU  341  CA  SER A  47     6782   5891   5713     -1   1093   -321       C  
+ATOM    342  C   SER A  47     -14.884  -8.548 -26.378  1.00 50.48           C  
+ANISOU  342  C   SER A  47     6998   6152   6030    -15    913   -348       C  
+ATOM    343  O   SER A  47     -14.433  -7.391 -26.220  1.00 42.86           O  
+ANISOU  343  O   SER A  47     6043   5186   5055     37    827   -403       O  
+ATOM    344  CB  SER A  47     -15.052  -9.964 -24.335  1.00 48.69           C  
+ANISOU  344  CB  SER A  47     6981   5919   5599     58   1134   -259       C  
+ATOM    345  OG  SER A  47     -14.042  -9.148 -23.822  1.00 52.57           O  
+ANISOU  345  OG  SER A  47     7538   6457   5976    130    999   -303       O  
+ATOM    346  N   CYS A  48     -14.586  -9.312 -27.432  1.00 46.30           N  
+ANISOU  346  N   CYS A  48     6426   5606   5560    -94    881   -320       N  
+ATOM    347  CA  CYS A  48     -13.615  -8.899 -28.445  1.00 43.55           C  
+ANISOU  347  CA  CYS A  48     6042   5255   5251   -102    736   -325       C  
+ATOM    348  C   CYS A  48     -14.282  -8.264 -29.667  1.00 48.19           C  
+ANISOU  348  C   CYS A  48     6472   5897   5938   -148    687   -339       C  
+ATOM    349  O   CYS A  48     -13.664  -8.163 -30.727  1.00 52.68           O  
+ANISOU  349  O   CYS A  48     7001   6475   6538   -171    598   -328       O  
+ATOM    350  CB  CYS A  48     -12.855 -10.140 -28.938  1.00 45.20           C  
+ANISOU  350  CB  CYS A  48     6309   5413   5450   -145    755   -280       C  
+ATOM    351  SG  CYS A  48     -13.936 -11.252 -29.869  1.00 49.74           S  
+ANISOU  351  SG  CYS A  48     6821   5979   6098   -304    875   -312       S  
+ATOM    352  N   ALA A  49     -15.536  -7.842 -29.543  1.00 49.80           N  
+ANISOU  352  N   ALA A  49     6575   6158   6186   -145    751   -344       N  
+ATOM    353  CA  ALA A  49     -16.294  -7.449 -30.708  1.00 48.00           C  
+ANISOU  353  CA  ALA A  49     6167   6039   6032   -181    710   -321       C  
+ATOM    354  C   ALA A  49     -15.698  -6.209 -31.365  1.00 47.62           C  
+ANISOU  354  C   ALA A  49     6079   5983   6030    -94    608   -287       C  
+ATOM    355  O   ALA A  49     -15.183  -5.313 -30.693  1.00 45.45           O  
+ANISOU  355  O   ALA A  49     5885   5614   5769     -6    608   -304       O  
+ATOM    356  CB  ALA A  49     -17.720  -7.170 -30.327  1.00 44.89           C  
+ANISOU  356  CB  ALA A  49     5646   5725   5684   -164    808   -302       C  
+ATOM    357  N   GLY A  50     -15.818  -6.151 -32.678  1.00 48.69           N  
+ANISOU  357  N   GLY A  50     6091   6224   6184   -128    536   -244       N  
+ATOM    358  CA  GLY A  50     -15.449  -4.952 -33.442  1.00 49.68           C  
+ANISOU  358  CA  GLY A  50     6154   6355   6367    -31    471   -170       C  
+ATOM    359  C   GLY A  50     -16.413  -4.732 -34.592  1.00 50.54           C  
+ANISOU  359  C   GLY A  50     6054   6667   6481    -27    438    -81       C  
+ATOM    360  O   GLY A  50     -17.402  -5.446 -34.760  1.00 51.08           O  
+ANISOU  360  O   GLY A  50     6008   6888   6512   -119    451    -99       O  
+ATOM    361  N   LYS A  51     -16.125  -3.748 -35.412  1.00 53.55           N  
+ANISOU  361  N   LYS A  51     6371   7070   6904     74    397     23       N  
+ATOM    362  CA  LYS A  51     -16.949  -3.502 -36.568  1.00 53.87           C  
+ANISOU  362  CA  LYS A  51     6201   7352   6915    104    349    142       C  
+ATOM    363  C   LYS A  51     -16.099  -3.324 -37.812  1.00 52.01           C  
+ANISOU  363  C   LYS A  51     5967   7166   6626    116    266    208       C  
+ATOM    364  O   LYS A  51     -15.259  -2.429 -37.884  1.00 49.83           O  
+ANISOU  364  O   LYS A  51     5763   6736   6431    222    288    275       O  
+ATOM    365  CB  LYS A  51     -17.819  -2.272 -36.354  1.00 55.11           C  
+ANISOU  365  CB  LYS A  51     6228   7529   7182    289    432    285       C  
+ATOM    366  CG  LYS A  51     -19.060  -2.302 -37.241  1.00 69.25           C  
+ANISOU  366  CG  LYS A  51     7743   9649   8918    311    386    414       C  
+ATOM    367  CD  LYS A  51     -19.994  -1.116 -37.036  1.00 78.14           C  
+ANISOU  367  CD  LYS A  51     8713  10807  10169    534    496    599       C  
+ATOM    368  CE  LYS A  51     -19.697  -0.098 -38.130  1.00 91.14           C  
+ANISOU  368  CE  LYS A  51    10279  12512  11835    709    478    813       C  
+ATOM    369  NZ  LYS A  51     -20.322   1.238 -37.924  1.00 98.12           N  
+ANISOU  369  NZ  LYS A  51    11067  13328  12884    974    639   1022       N  
+ATOM    370  N   VAL A  52     -16.371  -4.149 -38.805  1.00 56.50           N  
+ANISOU  370  N   VAL A  52     6451   7958   7057     -1    184    184       N  
+ATOM    371  CA  VAL A  52     -15.743  -4.044 -40.096  1.00 53.02           C  
+ANISOU  371  CA  VAL A  52     5995   7623   6524     10    113    251       C  
+ATOM    372  C   VAL A  52     -16.271  -2.823 -40.812  1.00 55.66           C  
+ANISOU  372  C   VAL A  52     6153   8116   6878    198    104    478       C  
+ATOM    373  O   VAL A  52     -17.446  -2.763 -41.107  1.00 57.54           O  
+ANISOU  373  O   VAL A  52     6184   8614   7061    218     71    553       O  
+ATOM    374  CB  VAL A  52     -16.050  -5.263 -40.957  1.00 52.46           C  
+ANISOU  374  CB  VAL A  52     5882   7779   6271   -184     41    135       C  
+ATOM    375  CG1 VAL A  52     -15.519  -5.033 -42.382  1.00 54.82           C  
+ANISOU  375  CG1 VAL A  52     6153   8239   6436   -149    -26    221       C  
+ATOM    376  CG2 VAL A  52     -15.383  -6.462 -40.332  1.00 50.36           C  
+ANISOU  376  CG2 VAL A  52     5817   7306   6010   -339     95    -55       C  
+ATOM    377  N   VAL A  53     -15.394  -1.848 -41.054  1.00 54.66           N  
+ANISOU  377  N   VAL A  53     6097   7828   6843    341    150    599       N  
+ATOM    378  CA  VAL A  53     -15.763  -0.629 -41.772  1.00 62.36           C  
+ANISOU  378  CA  VAL A  53     6930   8905   7859    554    184    855       C  
+ATOM    379  C   VAL A  53     -15.275  -0.645 -43.240  1.00 58.73           C  
+ANISOU  379  C   VAL A  53     6429   8643   7241    577    116    972       C  
+ATOM    380  O   VAL A  53     -15.785   0.085 -44.048  1.00 58.59           O  
+ANISOU  380  O   VAL A  53     6253   8828   7179    741    116   1203       O  
+ATOM    381  CB  VAL A  53     -15.351   0.680 -41.005  1.00 65.49           C  
+ANISOU  381  CB  VAL A  53     7415   8968   8500    719    338    940       C  
+ATOM    382  CG1 VAL A  53     -15.576   0.504 -39.522  1.00 72.86           C  
+ANISOU  382  CG1 VAL A  53     8446   9701   9533    655    398    764       C  
+ATOM    383  CG2 VAL A  53     -13.917   1.090 -41.248  1.00 68.64           C  
+ANISOU  383  CG2 VAL A  53     7968   9134   8977    721    376    943       C  
+ATOM    384  N   SER A  54     -14.348  -1.525 -43.591  1.00 53.74           N  
+ANISOU  384  N   SER A  54     5935   7975   6509    426     68    825       N  
+ATOM    385  CA  SER A  54     -13.884  -1.675 -44.972  1.00 55.87           C  
+ANISOU  385  CA  SER A  54     6190   8441   6597    431     18    904       C  
+ATOM    386  C   SER A  54     -13.173  -3.046 -45.081  1.00 56.25           C  
+ANISOU  386  C   SER A  54     6394   8450   6527    213    -15    660       C  
+ATOM    387  O   SER A  54     -12.486  -3.512 -44.129  1.00 49.66           O  
+ANISOU  387  O   SER A  54     5714   7335   5817    133     33    511       O  
+ATOM    388  CB  SER A  54     -12.991  -0.455 -45.367  1.00 56.61           C  
+ANISOU  388  CB  SER A  54     6326   8357   6824    625    121   1123       C  
+ATOM    389  OG  SER A  54     -12.138  -0.710 -46.445  1.00 63.73           O  
+ANISOU  389  OG  SER A  54     7288   9336   7589    611    112   1159       O  
+ATOM    390  N   GLY A  55     -13.389  -3.710 -46.217  1.00 53.71           N  
+ANISOU  390  N   GLY A  55     6028   8423   5956    121    -89    620       N  
+ATOM    391  CA  GLY A  55     -12.890  -5.055 -46.464  1.00 54.03           C  
+ANISOU  391  CA  GLY A  55     6214   8444   5869    -87    -89    381       C  
+ATOM    392  C   GLY A  55     -13.780  -6.179 -45.936  1.00 56.10           C  
+ANISOU  392  C   GLY A  55     6459   8774   6082   -306   -121    151       C  
+ATOM    393  O   GLY A  55     -14.982  -5.983 -45.681  1.00 60.55           O  
+ANISOU  393  O   GLY A  55     6840   9527   6637   -318   -183    180       O  
+ATOM    394  N   SER A  56     -13.181  -7.362 -45.781  1.00 52.30           N  
+ANISOU  394  N   SER A  56     6161   8128   5583   -472    -54    -60       N  
+ATOM    395  CA  SER A  56     -13.909  -8.555 -45.414  1.00 56.19           C  
+ANISOU  395  CA  SER A  56     6669   8647   6032   -704    -41   -291       C  
+ATOM    396  C   SER A  56     -13.104  -9.443 -44.480  1.00 53.82           C  
+ANISOU  396  C   SER A  56     6589   7981   5876   -764     94   -420       C  
+ATOM    397  O   SER A  56     -11.877  -9.347 -44.370  1.00 54.78           O  
+ANISOU  397  O   SER A  56     6852   7882   6078   -658    162   -364       O  
+ATOM    398  CB  SER A  56     -14.330  -9.330 -46.692  1.00 60.27           C  
+ANISOU  398  CB  SER A  56     7153   9473   6272   -904    -92   -449       C  
+ATOM    399  OG  SER A  56     -13.185  -9.640 -47.492  1.00 64.49           O  
+ANISOU  399  OG  SER A  56     7867   9923   6713   -890    -21   -484       O  
+ATOM    400  N   VAL A  57     -13.818 -10.317 -43.788  1.00 57.62           N  
+ANISOU  400  N   VAL A  57     7081   8413   6396   -929    142   -574       N  
+ATOM    401  CA  VAL A  57     -13.221 -11.309 -42.892  1.00 53.60           C  
+ANISOU  401  CA  VAL A  57     6773   7581   6008   -986    293   -680       C  
+ATOM    402  C   VAL A  57     -13.972 -12.587 -43.112  1.00 55.21           C  
+ANISOU  402  C   VAL A  57     7008   7830   6138  -1256    368   -912       C  
+ATOM    403  O   VAL A  57     -15.069 -12.588 -43.684  1.00 53.95           O  
+ANISOU  403  O   VAL A  57     6676   7964   5858  -1404    275   -989       O  
+ATOM    404  CB  VAL A  57     -13.373 -10.956 -41.381  1.00 57.18           C  
+ANISOU  404  CB  VAL A  57     7224   7857   6644   -880    320   -596       C  
+ATOM    405  CG1 VAL A  57     -12.463  -9.803 -41.015  1.00 52.19           C  
+ANISOU  405  CG1 VAL A  57     6602   7118   6107   -653    277   -417       C  
+ATOM    406  CG2 VAL A  57     -14.834 -10.627 -41.047  1.00 53.61           C  
+ANISOU  406  CG2 VAL A  57     6573   7599   6197   -936    258   -594       C  
+ATOM    407  N   ASP A  58     -13.366 -13.666 -42.641  1.00 55.82           N  
+ANISOU  407  N   ASP A  58     7294   7617   6296  -1318    547  -1013       N  
+ATOM    408  CA  ASP A  58     -13.996 -14.957 -42.561  1.00 58.46           C  
+ANISOU  408  CA  ASP A  58     7701   7878   6631  -1574    687  -1233       C  
+ATOM    409  C   ASP A  58     -14.136 -15.223 -41.085  1.00 55.65           C  
+ANISOU  409  C   ASP A  58     7395   7287   6461  -1515    794  -1166       C  
+ATOM    410  O   ASP A  58     -13.156 -15.457 -40.418  1.00 55.05           O  
+ANISOU  410  O   ASP A  58     7478   6953   6484  -1367    903  -1074       O  
+ATOM    411  CB  ASP A  58     -13.103 -16.016 -43.188  1.00 65.76           C  
+ANISOU  411  CB  ASP A  58     8862   8605   7518  -1656    869  -1374       C  
+ATOM    412  CG  ASP A  58     -13.609 -17.451 -42.970  1.00 72.39           C  
+ANISOU  412  CG  ASP A  58     9833   9259   8413  -1919   1088  -1606       C  
+ATOM    413  OD1 ASP A  58     -14.654 -17.719 -42.304  1.00 72.82           O  
+ANISOU  413  OD1 ASP A  58     9792   9332   8542  -2058   1107  -1663       O  
+ATOM    414  OD2 ASP A  58     -12.932 -18.339 -43.523  1.00 80.41           O  
+ANISOU  414  OD2 ASP A  58    11056  10090   9406  -1990   1274  -1737       O  
+ATOM    415  N   GLN A  59     -15.358 -15.182 -40.586  1.00 59.22           N  
+ANISOU  415  N   GLN A  59     7695   7858   6947  -1623    763  -1198       N  
+ATOM    416  CA  GLN A  59     -15.612 -15.422 -39.174  1.00 62.73           C  
+ANISOU  416  CA  GLN A  59     8181   8107   7544  -1569    878  -1130       C  
+ATOM    417  C   GLN A  59     -16.694 -16.457 -38.993  1.00 65.10           C  
+ANISOU  417  C   GLN A  59     8454   8390   7889  -1842   1015  -1303       C  
+ATOM    418  O   GLN A  59     -17.576 -16.295 -38.154  1.00 66.15           O  
+ANISOU  418  O   GLN A  59     8471   8562   8101  -1853   1028  -1256       O  
+ATOM    419  CB  GLN A  59     -16.011 -14.137 -38.463  1.00 57.25           C  
+ANISOU  419  CB  GLN A  59     7325   7538   6889  -1378    738   -953       C  
+ATOM    420  CG  GLN A  59     -17.246 -13.434 -39.016  1.00 55.97           C  
+ANISOU  420  CG  GLN A  59     6886   7717   6661  -1449    585   -955       C  
+ATOM    421  CD  GLN A  59     -17.652 -12.296 -38.096  1.00 62.04           C  
+ANISOU  421  CD  GLN A  59     7537   8522   7511  -1245    528   -780       C  
+ATOM    422  OE1 GLN A  59     -18.210 -11.269 -38.534  1.00 66.81           O  
+ANISOU  422  OE1 GLN A  59     7938   9366   8079  -1159    394   -682       O  
+ATOM    423  NE2 GLN A  59     -17.359 -12.461 -36.800  1.00 56.51           N  
+ANISOU  423  NE2 GLN A  59     6972   7583   6913  -1150    646   -731       N  
+ATOM    424  N   SER A  60     -16.585 -17.512 -39.786  1.00 64.01           N  
+ANISOU  424  N   SER A  60     8432   8180   7707  -2067   1137  -1510       N  
+ATOM    425  CA  SER A  60     -17.358 -18.738 -39.660  1.00 71.88           C  
+ANISOU  425  CA  SER A  60     9472   9060   8776  -2367   1339  -1715       C  
+ATOM    426  C   SER A  60     -17.042 -19.592 -38.399  1.00 69.57           C  
+ANISOU  426  C   SER A  60     9386   8371   8673  -2297   1620  -1638       C  
+ATOM    427  O   SER A  60     -17.817 -20.492 -38.073  1.00 71.72           O  
+ANISOU  427  O   SER A  60     9674   8528   9048  -2526   1809  -1764       O  
+ATOM    428  CB  SER A  60     -17.104 -19.609 -40.916  1.00 74.82           C  
+ANISOU  428  CB  SER A  60     9970   9417   9042  -2615   1424  -1979       C  
+ATOM    429  OG  SER A  60     -15.720 -19.921 -40.981  1.00 69.57           O  
+ANISOU  429  OG  SER A  60     9563   8467   8400  -2430   1560  -1910       O  
+ATOM    430  N   ASP A  61     -15.919 -19.358 -37.720  1.00 66.51           N  
+ANISOU  430  N   ASP A  61     9149   7790   8330  -1996   1657  -1429       N  
+ATOM    431  CA  ASP A  61     -15.690 -19.995 -36.412  1.00 71.53           C  
+ANISOU  431  CA  ASP A  61     9937   8131   9109  -1878   1886  -1294       C  
+ATOM    432  C   ASP A  61     -16.401 -19.243 -35.270  1.00 70.72           C  
+ANISOU  432  C   ASP A  61     9687   8145   9037  -1762   1797  -1144       C  
+ATOM    433  O   ASP A  61     -16.415 -19.738 -34.176  1.00 76.22           O  
+ANISOU  433  O   ASP A  61    10486   8652   9822  -1683   1980  -1036       O  
+ATOM    434  CB  ASP A  61     -14.215 -20.094 -36.049  1.00 71.97           C  
+ANISOU  434  CB  ASP A  61    10185   7978   9182  -1596   1957  -1113       C  
+ATOM    435  CG  ASP A  61     -13.386 -20.780 -37.089  1.00 73.12           C  
+ANISOU  435  CG  ASP A  61    10490   7983   9309  -1651   2076  -1223       C  
+ATOM    436  OD1 ASP A  61     -13.929 -21.468 -37.992  1.00 83.53           O  
+ANISOU  436  OD1 ASP A  61    11838   9288  10611  -1938   2174  -1474       O  
+ATOM    437  OD2 ASP A  61     -12.154 -20.643 -36.976  1.00 81.02           O  
+ANISOU  437  OD2 ASP A  61    11586   8889  10308  -1407   2081  -1062       O  
+ATOM    438  N   GLN A  62     -16.979 -18.058 -35.515  1.00 72.21           N  
+ANISOU  438  N   GLN A  62     9647   8639   9149  -1733   1544  -1123       N  
+ATOM    439  CA  GLN A  62     -17.756 -17.359 -34.476  1.00 67.75           C  
+ANISOU  439  CA  GLN A  62     8946   8173   8619  -1633   1498  -1004       C  
+ATOM    440  C   GLN A  62     -19.164 -17.942 -34.377  1.00 73.09           C  
+ANISOU  440  C   GLN A  62     9484   8914   9371  -1894   1611  -1123       C  
+ATOM    441  O   GLN A  62     -19.705 -18.425 -35.366  1.00 70.89           O  
+ANISOU  441  O   GLN A  62     9116   8743   9073  -2166   1602  -1315       O  
+ATOM    442  CB  GLN A  62     -17.867 -15.838 -34.717  1.00 66.80           C  
+ANISOU  442  CB  GLN A  62     8639   8319   8422  -1477   1233   -915       C  
+ATOM    443  CG  GLN A  62     -19.034 -15.338 -35.614  1.00 67.21           C  
+ANISOU  443  CG  GLN A  62     8412   8696   8428  -1635   1083   -998       C  
+ATOM    444  CD  GLN A  62     -20.329 -14.964 -34.874  1.00 64.99           C  
+ANISOU  444  CD  GLN A  62     7928   8548   8214  -1649   1104   -947       C  
+ATOM    445  OE1 GLN A  62     -20.304 -14.340 -33.806  1.00 71.34           O  
+ANISOU  445  OE1 GLN A  62     8759   9285   9059  -1444   1131   -808       O  
+ATOM    446  NE2 GLN A  62     -21.471 -15.281 -35.476  1.00 63.47           N  
+ANISOU  446  NE2 GLN A  62     7516   8576   8024  -1892   1085  -1063       N  
+ATOM    447  N   SER A  63     -19.742 -17.884 -33.174  1.00 72.66           N  
+ANISOU  447  N   SER A  63     9406   8808   9392  -1819   1719  -1013       N  
+ATOM    448  CA  SER A  63     -21.142 -18.244 -32.958  1.00 72.40           C  
+ANISOU  448  CA  SER A  63     9193   8866   9449  -2039   1821  -1088       C  
+ATOM    449  C   SER A  63     -21.984 -17.121 -32.341  1.00 66.69           C  
+ANISOU  449  C   SER A  63     8251   8365   8723  -1899   1711   -963       C  
+ATOM    450  O   SER A  63     -23.178 -17.067 -32.599  1.00 77.64           O  
+ANISOU  450  O   SER A  63     9385   9960  10154  -2074   1693  -1026       O  
+ATOM    451  CB  SER A  63     -21.277 -19.527 -32.118  1.00 70.17           C  
+ANISOU  451  CB  SER A  63     9088   8272   9301  -2138   2157  -1089       C  
+ATOM    452  OG  SER A  63     -20.352 -19.523 -31.051  1.00 76.40           O  
+ANISOU  452  OG  SER A  63    10101   8853  10075  -1847   2253   -890       O  
+ATOM    453  N   PHE A  64     -21.393 -16.238 -31.540  1.00 58.38           N  
+ANISOU  453  N   PHE A  64     7282   7276   7622  -1597   1650   -796       N  
+ATOM    454  CA  PHE A  64     -22.194 -15.361 -30.713  1.00 54.39           C  
+ANISOU  454  CA  PHE A  64     6633   6895   7136  -1461   1643   -686       C  
+ATOM    455  C   PHE A  64     -23.087 -14.381 -31.501  1.00 58.70           C  
+ANISOU  455  C   PHE A  64     6865   7765   7673  -1486   1455   -693       C  
+ATOM    456  O   PHE A  64     -24.182 -14.086 -31.039  1.00 57.54           O  
+ANISOU  456  O   PHE A  64     6527   7742   7591  -1488   1516   -644       O  
+ATOM    457  CB  PHE A  64     -21.262 -14.595 -29.755  1.00 55.93           C  
+ANISOU  457  CB  PHE A  64     7010   6983   7257  -1156   1611   -549       C  
+ATOM    458  CG  PHE A  64     -21.972 -13.594 -28.871  1.00 56.33           C  
+ANISOU  458  CG  PHE A  64     6960   7132   7311   -994   1624   -455       C  
+ATOM    459  CD1 PHE A  64     -22.478 -13.970 -27.623  1.00 51.74           C  
+ANISOU  459  CD1 PHE A  64     6445   6457   6754   -951   1839   -390       C  
+ATOM    460  CD2 PHE A  64     -22.149 -12.275 -29.292  1.00 59.73           C  
+ANISOU  460  CD2 PHE A  64     7237   7734   7723   -872   1452   -423       C  
+ATOM    461  CE1 PHE A  64     -23.090 -13.031 -26.810  1.00 57.92           C  
+ANISOU  461  CE1 PHE A  64     7159   7319   7527   -791   1874   -316       C  
+ATOM    462  CE2 PHE A  64     -22.810 -11.337 -28.484  1.00 60.57           C  
+ANISOU  462  CE2 PHE A  64     7268   7898   7848   -710   1505   -344       C  
+ATOM    463  CZ  PHE A  64     -23.289 -11.715 -27.242  1.00 54.75           C  
+ANISOU  463  CZ  PHE A  64     6607   7070   7122   -674   1715   -302       C  
+ATOM    464  N   LEU A  65     -22.629 -13.842 -32.646  1.00 59.31           N  
+ANISOU  464  N   LEU A  65     6879   7987   7667  -1474   1243   -724       N  
+ATOM    465  CA  LEU A  65     -23.379 -12.775 -33.374  1.00 63.23           C  
+ANISOU  465  CA  LEU A  65     7079   8808   8137  -1426   1064   -668       C  
+ATOM    466  C   LEU A  65     -24.616 -13.283 -34.188  1.00 65.16           C  
+ANISOU  466  C   LEU A  65     7020   9342   8393  -1708   1030   -770       C  
+ATOM    467  O   LEU A  65     -24.516 -14.329 -34.834  1.00 64.66           O  
+ANISOU  467  O   LEU A  65     7003   9259   8302  -1976   1052   -946       O  
+ATOM    468  CB  LEU A  65     -22.421 -11.920 -34.276  1.00 63.03           C  
+ANISOU  468  CB  LEU A  65     7095   8842   8010  -1279    864   -623       C  
+ATOM    469  CG  LEU A  65     -21.294 -11.083 -33.600  1.00 62.01           C  
+ANISOU  469  CG  LEU A  65     7179   8506   7874  -1004    852   -518       C  
+ATOM    470  CD1 LEU A  65     -20.274 -10.486 -34.575  1.00 60.47           C  
+ANISOU  470  CD1 LEU A  65     7035   8338   7604   -917    691   -493       C  
+ATOM    471  CD2 LEU A  65     -21.842  -9.965 -32.731  1.00 60.62           C  
+ANISOU  471  CD2 LEU A  65     6923   8352   7758   -795    888   -391       C  
+ATOM    472  N   ASP A  66     -25.764 -12.554 -34.051  1.00 69.31           N  
+ANISOU  472  N   ASP A  66     7243  10123   8968  -1644    999   -661       N  
+ATOM    473  CA  ASP A  66     -27.047 -12.615 -34.848  1.00 72.82           C  
+ANISOU  473  CA  ASP A  66     7291  10968   9408  -1843    905   -692       C  
+ATOM    474  C   ASP A  66     -26.639 -12.271 -36.317  1.00 77.46           C  
+ANISOU  474  C   ASP A  66     7794  11812   9825  -1871    656   -723       C  
+ATOM    475  O   ASP A  66     -25.716 -11.438 -36.541  1.00 65.69           O  
+ANISOU  475  O   ASP A  66     6447  10241   8269  -1624    566   -617       O  
+ATOM    476  CB  ASP A  66     -28.090 -11.511 -34.364  1.00 77.64           C  
+ANISOU  476  CB  ASP A  66     7609  11789  10101  -1619    914   -476       C  
+ATOM    477  CG  ASP A  66     -29.226 -12.030 -33.407  1.00 93.62           C  
+ANISOU  477  CG  ASP A  66     9481  13808  12279  -1733   1126   -471       C  
+ATOM    478  OD1 ASP A  66     -29.080 -13.108 -32.793  1.00106.10           O  
+ANISOU  478  OD1 ASP A  66    11255  15134  13923  -1914   1308   -596       O  
+ATOM    479  OD2 ASP A  66     -30.299 -11.343 -33.266  1.00 93.76           O  
+ANISOU  479  OD2 ASP A  66     9173  14081  12368  -1624   1134   -319       O  
+ATOM    480  N   ASP A  67     -27.292 -12.872 -37.323  1.00 79.83           N  
+ANISOU  480  N   ASP A  67     7864  12428  10039  -2173    549   -870       N  
+ATOM    481  CA  ASP A  67     -26.991 -12.525 -38.711  1.00 83.48           C  
+ANISOU  481  CA  ASP A  67     8232  13188  10299  -2190    314   -888       C  
+ATOM    482  C   ASP A  67     -27.305 -11.060 -38.958  1.00 81.61           C  
+ANISOU  482  C   ASP A  67     7760  13217  10028  -1857    174   -603       C  
+ATOM    483  O   ASP A  67     -26.634 -10.428 -39.777  1.00 81.48           O  
+ANISOU  483  O   ASP A  67     7791  13288   9878  -1705     34   -521       O  
+ATOM    484  CB  ASP A  67     -27.780 -13.356 -39.743  1.00 99.82           C  
+ANISOU  484  CB  ASP A  67    10050  15632  12243  -2589    204  -1107       C  
+ATOM    485  CG  ASP A  67     -27.453 -14.842 -39.701  1.00102.82           C  
+ANISOU  485  CG  ASP A  67    10677  15732  12655  -2952    367  -1422       C  
+ATOM    486  OD1 ASP A  67     -26.299 -15.215 -39.353  1.00102.93           O  
+ANISOU  486  OD1 ASP A  67    11082  15321  12706  -2872    499  -1465       O  
+ATOM    487  OD2 ASP A  67     -28.380 -15.622 -40.029  1.00 94.69           O  
+ANISOU  487  OD2 ASP A  67     9430  14922  11623  -3321    370  -1619       O  
+ATOM    488  N   GLU A  68     -28.313 -10.508 -38.287  1.00 76.33           N  
+ANISOU  488  N   GLU A  68     6842  12669   9490  -1729    236   -436       N  
+ATOM    489  CA  GLU A  68     -28.488  -9.053 -38.331  1.00 87.60           C  
+ANISOU  489  CA  GLU A  68     8114  14233  10935  -1350    179   -137       C  
+ATOM    490  C   GLU A  68     -27.140  -8.357 -37.951  1.00 89.20           C  
+ANISOU  490  C   GLU A  68     8695  14033  11161  -1073    233    -65       C  
+ATOM    491  O   GLU A  68     -26.669  -7.443 -38.628  1.00 88.13           O  
+ANISOU  491  O   GLU A  68     8554  13978  10954   -862    128     85       O  
+ATOM    492  CB  GLU A  68     -29.608  -8.602 -37.395  1.00 95.60           C  
+ANISOU  492  CB  GLU A  68     8892  15305  12126  -1215    319     23       C  
+ATOM    493  CG  GLU A  68     -29.735  -7.087 -37.282  1.00104.83           C  
+ANISOU  493  CG  GLU A  68     9958  16515  13354   -793    334    332       C  
+ATOM    494  CD  GLU A  68     -30.926  -6.636 -36.439  1.00115.12           C  
+ANISOU  494  CD  GLU A  68    11001  17904  14832   -647    495    501       C  
+ATOM    495  OE1 GLU A  68     -31.956  -7.348 -36.393  1.00115.20           O  
+ANISOU  495  OE1 GLU A  68    10727  18166  14878   -878    505    440       O  
+ATOM    496  OE2 GLU A  68     -30.828  -5.546 -35.827  1.00115.96           O  
+ANISOU  496  OE2 GLU A  68    11189  17821  15050   -303    630    690       O  
+ATOM    497  N   GLN A  69     -26.519  -8.821 -36.871  1.00 84.99           N  
+ANISOU  497  N   GLN A  69     8479  13084  10727  -1087    401   -169       N  
+ATOM    498  CA  GLN A  69     -25.278  -8.246 -36.383  1.00 74.41           C  
+ANISOU  498  CA  GLN A  69     7470  11389   9412   -867    450   -126       C  
+ATOM    499  C   GLN A  69     -24.113  -8.417 -37.351  1.00 73.31           C  
+ANISOU  499  C   GLN A  69     7506  11209   9139   -912    323   -199       C  
+ATOM    500  O   GLN A  69     -23.352  -7.485 -37.533  1.00 68.06           O  
+ANISOU  500  O   GLN A  69     6940  10450   8469   -692    283    -80       O  
+ATOM    501  CB  GLN A  69     -24.929  -8.815 -34.993  1.00 71.55           C  
+ANISOU  501  CB  GLN A  69     7378  10660   9148   -886    645   -216       C  
+ATOM    502  CG  GLN A  69     -25.865  -8.310 -33.913  1.00 72.95           C  
+ANISOU  502  CG  GLN A  69     7442  10823   9452   -746    799   -105       C  
+ATOM    503  CD  GLN A  69     -25.579  -8.891 -32.535  1.00 72.43           C  
+ANISOU  503  CD  GLN A  69     7636  10439   9442   -758    996   -178       C  
+ATOM    504  OE1 GLN A  69     -25.227 -10.079 -32.369  1.00 64.31           O  
+ANISOU  504  OE1 GLN A  69     6765   9275   8394   -961   1053   -316       O  
+ATOM    505  NE2 GLN A  69     -25.737  -8.050 -31.530  1.00 72.76           N  
+ANISOU  505  NE2 GLN A  69     7734  10360   9548   -530   1119    -79       N  
+ATOM    506  N   ILE A  70     -23.969  -9.589 -37.964  1.00 78.29           N  
+ANISOU  506  N   ILE A  70     8181  11890   9673  -1199    285   -397       N  
+ATOM    507  CA  ILE A  70     -22.959  -9.783 -39.017  1.00 75.81           C  
+ANISOU  507  CA  ILE A  70     8009  11579   9215  -1247    178   -469       C  
+ATOM    508  C   ILE A  70     -23.247  -8.950 -40.279  1.00 80.31           C  
+ANISOU  508  C   ILE A  70     8337  12536   9640  -1154     -9   -332       C  
+ATOM    509  O   ILE A  70     -22.334  -8.336 -40.829  1.00 79.77           O  
+ANISOU  509  O   ILE A  70     8384  12414   9511   -993    -67   -242       O  
+ATOM    510  CB  ILE A  70     -22.818 -11.250 -39.409  1.00 73.74           C  
+ANISOU  510  CB  ILE A  70     7859  11274   8884  -1583    218   -732       C  
+ATOM    511  CG1 ILE A  70     -22.302 -12.031 -38.193  1.00 70.17           C  
+ANISOU  511  CG1 ILE A  70     7687  10403   8571  -1612    426   -812       C  
+ATOM    512  CG2 ILE A  70     -21.890 -11.368 -40.629  1.00 69.89           C  
+ANISOU  512  CG2 ILE A  70     7488  10843   8223  -1620    113   -800       C  
+ATOM    513  CD1 ILE A  70     -22.178 -13.529 -38.421  1.00 73.86           C  
+ANISOU  513  CD1 ILE A  70     8298  10742   9024  -1925    541  -1055       C  
+ATOM    514  N   GLY A  71     -24.501  -8.934 -40.720  1.00 80.29           N  
+ANISOU  514  N   GLY A  71     7989  12934   9582  -1249    -96   -298       N  
+ATOM    515  CA  GLY A  71     -24.945  -8.065 -41.782  1.00 84.25           C  
+ANISOU  515  CA  GLY A  71     8211  13855   9944  -1113   -267   -106       C  
+ATOM    516  C   GLY A  71     -24.627  -6.587 -41.543  1.00 90.55           C  
+ANISOU  516  C   GLY A  71     9018  14542  10843   -709   -231    194       C  
+ATOM    517  O   GLY A  71     -24.287  -5.858 -42.499  1.00 81.85           O  
+ANISOU  517  O   GLY A  71     7861  13613   9622   -547   -332    358       O  
+ATOM    518  N   GLU A  72     -24.732  -6.122 -40.290  1.00 85.84           N  
+ANISOU  518  N   GLU A  72     8502  13652  10461   -547    -67    267       N  
+ATOM    519  CA  GLU A  72     -24.363  -4.720 -39.956  1.00 84.90           C  
+ANISOU  519  CA  GLU A  72     8443  13349  10466   -189     11    507       C  
+ATOM    520  C   GLU A  72     -22.834  -4.496 -39.871  1.00 78.13           C  
+ANISOU  520  C   GLU A  72     7942  12117   9627   -119     45    456       C  
+ATOM    521  O   GLU A  72     -22.372  -3.347 -39.798  1.00 75.56           O  
+ANISOU  521  O   GLU A  72     7680  11635   9392    134    104    627       O  
+ATOM    522  CB  GLU A  72     -25.091  -4.227 -38.684  1.00 90.04           C  
+ANISOU  522  CB  GLU A  72     9044  13847  11319    -41    188    589       C  
+ATOM    523  CG  GLU A  72     -26.571  -3.883 -38.951  1.00104.52           C  
+ANISOU  523  CG  GLU A  72    10456  16088  13168     32    166    776       C  
+ATOM    524  CD  GLU A  72     -27.484  -3.868 -37.708  1.00115.41           C  
+ANISOU  524  CD  GLU A  72    11754  17371  14724     75    350    790       C  
+ATOM    525  OE1 GLU A  72     -27.080  -4.369 -36.632  1.00119.16           O  
+ANISOU  525  OE1 GLU A  72    12496  17503  15276    -13    480    617       O  
+ATOM    526  OE2 GLU A  72     -28.638  -3.366 -37.807  1.00117.96           O  
+ANISOU  526  OE2 GLU A  72    11730  17986  15102    210    373    993       O  
+ATOM    527  N   GLY A  73     -22.058  -5.585 -39.875  1.00 67.33           N  
+ANISOU  527  N   GLY A  73     6793  10600   8189   -345     28    226       N  
+ATOM    528  CA  GLY A  73     -20.599  -5.498 -39.918  1.00 67.28           C  
+ANISOU  528  CA  GLY A  73     7077  10299   8185   -298     44    183       C  
+ATOM    529  C   GLY A  73     -19.821  -5.908 -38.667  1.00 62.16           C  
+ANISOU  529  C   GLY A  73     6708   9260   7648   -326    164     53       C  
+ATOM    530  O   GLY A  73     -18.585  -5.771 -38.638  1.00 63.30           O  
+ANISOU  530  O   GLY A  73     7062   9181   7806   -276    171     35       O  
+ATOM    531  N   PHE A  74     -20.506  -6.407 -37.635  1.00 54.21           N  
+ANISOU  531  N   PHE A  74     5695   8188   6714   -400    259    -21       N  
+ATOM    532  CA  PHE A  74     -19.813  -6.733 -36.379  1.00 50.47           C  
+ANISOU  532  CA  PHE A  74     5476   7384   6317   -394    373   -110       C  
+ATOM    533  C   PHE A  74     -19.017  -7.976 -36.610  1.00 48.37           C  
+ANISOU  533  C   PHE A  74     5385   7015   5978   -577    371   -263       C  
+ATOM    534  O   PHE A  74     -19.389  -8.786 -37.447  1.00 48.97           O  
+ANISOU  534  O   PHE A  74     5379   7259   5968   -764    327   -353       O  
+ATOM    535  CB  PHE A  74     -20.762  -6.849 -35.179  1.00 51.93           C  
+ANISOU  535  CB  PHE A  74     5616   7527   6587   -388    503   -119       C  
+ATOM    536  CG  PHE A  74     -21.382  -5.561 -34.825  1.00 54.65           C  
+ANISOU  536  CG  PHE A  74     5836   7903   7023   -170    553     31       C  
+ATOM    537  CD1 PHE A  74     -20.611  -4.556 -34.263  1.00 59.00           C  
+ANISOU  537  CD1 PHE A  74     6552   8226   7638     12    602     74       C  
+ATOM    538  CD2 PHE A  74     -22.705  -5.309 -35.097  1.00 62.03           C  
+ANISOU  538  CD2 PHE A  74     6482   9097   7988   -145    562    133       C  
+ATOM    539  CE1 PHE A  74     -21.141  -3.312 -33.970  1.00 61.30           C  
+ANISOU  539  CE1 PHE A  74     6759   8497   8034    221    689    205       C  
+ATOM    540  CE2 PHE A  74     -23.278  -4.074 -34.762  1.00 65.97           C  
+ANISOU  540  CE2 PHE A  74     6870   9599   8593     96    649    300       C  
+ATOM    541  CZ  PHE A  74     -22.481  -3.064 -34.213  1.00 69.36           C  
+ANISOU  541  CZ  PHE A  74     7503   9751   9097    283    726    331       C  
+ATOM    542  N   VAL A  75     -17.889  -8.074 -35.901  1.00 48.27           N  
+ANISOU  542  N   VAL A  75     5608   6737   5995   -517    420   -290       N  
+ATOM    543  CA  VAL A  75     -16.947  -9.203 -35.999  1.00 47.38           C  
+ANISOU  543  CA  VAL A  75     5685   6480   5837   -632    453   -395       C  
+ATOM    544  C   VAL A  75     -16.430  -9.520 -34.615  1.00 49.15           C  
+ANISOU  544  C   VAL A  75     6091   6473   6108   -575    558   -404       C  
+ATOM    545  O   VAL A  75     -16.248  -8.629 -33.798  1.00 47.13           O  
+ANISOU  545  O   VAL A  75     5866   6148   5892   -428    559   -343       O  
+ATOM    546  CB  VAL A  75     -15.775  -8.834 -36.956  1.00 48.33           C  
+ANISOU  546  CB  VAL A  75     5867   6583   5911   -571    365   -359       C  
+ATOM    547  CG1 VAL A  75     -15.079  -7.549 -36.507  1.00 49.98           C  
+ANISOU  547  CG1 VAL A  75     6112   6686   6192   -372    333   -248       C  
+ATOM    548  CG2 VAL A  75     -14.782  -9.929 -37.088  1.00 49.25           C  
+ANISOU  548  CG2 VAL A  75     6167   6549   5995   -653    422   -442       C  
+ATOM    549  N   LEU A  76     -16.184 -10.794 -34.355  1.00 49.57           N  
+ANISOU  549  N   LEU A  76     6273   6411   6149   -688    660   -479       N  
+ATOM    550  CA  LEU A  76     -15.521 -11.198 -33.153  1.00 47.93           C  
+ANISOU  550  CA  LEU A  76     6243   6013   5953   -611    755   -452       C  
+ATOM    551  C   LEU A  76     -14.098 -11.431 -33.507  1.00 45.41           C  
+ANISOU  551  C   LEU A  76     6054   5590   5609   -559    721   -432       C  
+ATOM    552  O   LEU A  76     -13.751 -12.422 -34.211  1.00 46.04           O  
+ANISOU  552  O   LEU A  76     6200   5615   5676   -659    783   -486       O  
+ATOM    553  CB  LEU A  76     -16.177 -12.472 -32.551  1.00 49.85           C  
+ANISOU  553  CB  LEU A  76     6545   6174   6220   -737    935   -501       C  
+ATOM    554  CG  LEU A  76     -17.619 -12.228 -32.105  1.00 52.45           C  
+ANISOU  554  CG  LEU A  76     6721   6616   6591   -787    985   -507       C  
+ATOM    555  CD1 LEU A  76     -18.258 -13.483 -31.561  1.00 56.78           C  
+ANISOU  555  CD1 LEU A  76     7318   7068   7185   -930   1185   -551       C  
+ATOM    556  CD2 LEU A  76     -17.640 -11.190 -31.039  1.00 52.12           C  
+ANISOU  556  CD2 LEU A  76     6691   6565   6545   -599    972   -426       C  
+ATOM    557  N   THR A  77     -13.248 -10.565 -32.967  1.00 46.19           N  
+ANISOU  557  N   THR A  77     6193   5650   5705   -409    646   -365       N  
+ATOM    558  CA  THR A  77     -11.875 -10.505 -33.442  1.00 42.15           C  
+ANISOU  558  CA  THR A  77     5743   5084   5186   -350    586   -327       C  
+ATOM    559  C   THR A  77     -11.098 -11.716 -33.100  1.00 45.26           C  
+ANISOU  559  C   THR A  77     6277   5354   5565   -343    692   -303       C  
+ATOM    560  O   THR A  77     -10.095 -11.997 -33.772  1.00 50.56           O  
+ANISOU  560  O   THR A  77     6987   5982   6241   -320    686   -277       O  
+ATOM    561  CB  THR A  77     -11.139  -9.239 -32.928  1.00 47.57           C  
+ANISOU  561  CB  THR A  77     6417   5768   5889   -225    479   -279       C  
+ATOM    562  OG1 THR A  77     -11.277  -9.041 -31.498  1.00 46.78           O  
+ANISOU  562  OG1 THR A  77     6374   5641   5757   -165    507   -279       O  
+ATOM    563  CG2 THR A  77     -11.677  -8.033 -33.637  1.00 44.69           C  
+ANISOU  563  CG2 THR A  77     5923   5486   5568   -210    405   -272       C  
+ATOM    564  N   CYS A  78     -11.496 -12.435 -32.033  1.00 50.28           N  
+ANISOU  564  N   CYS A  78     6990   5924   6188   -338    814   -286       N  
+ATOM    565  CA  CYS A  78     -10.739 -13.646 -31.587  1.00 46.96           C  
+ANISOU  565  CA  CYS A  78     6711   5367   5761   -289    955   -214       C  
+ATOM    566  C   CYS A  78     -11.053 -14.787 -32.559  1.00 49.46           C  
+ANISOU  566  C   CYS A  78     7079   5587   6126   -435   1104   -293       C  
+ATOM    567  O   CYS A  78     -10.411 -15.828 -32.559  1.00 51.48           O  
+ANISOU  567  O   CYS A  78     7458   5693   6407   -405   1257   -244       O  
+ATOM    568  CB  CYS A  78     -11.178 -14.092 -30.182  1.00 53.40           C  
+ANISOU  568  CB  CYS A  78     7600   6145   6541   -234   1071   -151       C  
+ATOM    569  SG  CYS A  78     -12.881 -14.758 -30.242  1.00 51.94           S  
+ANISOU  569  SG  CYS A  78     7387   5928   6417   -420   1238   -249       S  
+ATOM    570  N   ALA A  79     -12.058 -14.598 -33.405  1.00 53.34           N  
+ANISOU  570  N   ALA A  79     7469   6170   6626   -598   1068   -418       N  
+ATOM    571  CA  ALA A  79     -12.515 -15.685 -34.236  1.00 55.83           C  
+ANISOU  571  CA  ALA A  79     7829   6416   6967   -787   1210   -543       C  
+ATOM    572  C   ALA A  79     -12.550 -15.316 -35.744  1.00 59.35           C  
+ANISOU  572  C   ALA A  79     8188   7002   7360   -894   1092   -650       C  
+ATOM    573  O   ALA A  79     -13.139 -16.061 -36.535  1.00 55.41           O  
+ANISOU  573  O   ALA A  79     7693   6513   6847  -1099   1172   -802       O  
+ATOM    574  CB  ALA A  79     -13.912 -16.109 -33.759  1.00 59.21           C  
+ANISOU  574  CB  ALA A  79     8205   6858   7433   -940   1313   -616       C  
+ATOM    575  N   ALA A  80     -11.913 -14.208 -36.131  1.00 51.54           N  
+ANISOU  575  N   ALA A  80     7126   6121   6335   -767    917   -576       N  
+ATOM    576  CA  ALA A  80     -12.025 -13.689 -37.496  1.00 52.60           C  
+ANISOU  576  CA  ALA A  80     7161   6426   6398   -833    799   -635       C  
+ATOM    577  C   ALA A  80     -10.641 -13.659 -38.187  1.00 52.00           C  
+ANISOU  577  C   ALA A  80     7168   6282   6305   -734    797   -581       C  
+ATOM    578  O   ALA A  80      -9.618 -13.161 -37.620  1.00 49.22           O  
+ANISOU  578  O   ALA A  80     6840   5858   6002   -561    760   -450       O  
+ATOM    579  CB  ALA A  80     -12.672 -12.314 -37.499  1.00 53.13           C  
+ANISOU  579  CB  ALA A  80     7044   6690   6453   -769    627   -571       C  
+ATOM    580  N   TYR A  81     -10.617 -14.269 -39.382  1.00 50.25           N  
+ANISOU  580  N   TYR A  81     6991   6091   6011   -860    853   -698       N  
+ATOM    581  CA  TYR A  81      -9.473 -14.232 -40.301  1.00 47.17           C  
+ANISOU  581  CA  TYR A  81     6664   5673   5582   -786    868   -665       C  
+ATOM    582  C   TYR A  81      -9.695 -13.111 -41.287  1.00 50.29           C  
+ANISOU  582  C   TYR A  81     6913   6311   5882   -771    694   -634       C  
+ATOM    583  O   TYR A  81     -10.787 -12.986 -41.832  1.00 51.07           O  
+ANISOU  583  O   TYR A  81     6906   6613   5885   -903    620   -722       O  
+ATOM    584  CB  TYR A  81      -9.426 -15.493 -41.146  1.00 51.08           C  
+ANISOU  584  CB  TYR A  81     7306   6080   6021   -943   1048   -833       C  
+ATOM    585  CG  TYR A  81      -9.189 -16.735 -40.354  1.00 58.36           C  
+ANISOU  585  CG  TYR A  81     8398   6724   7050   -954   1282   -854       C  
+ATOM    586  CD1 TYR A  81      -8.089 -16.833 -39.537  1.00 55.81           C  
+ANISOU  586  CD1 TYR A  81     8143   6231   6829   -740   1353   -672       C  
+ATOM    587  CD2 TYR A  81     -10.088 -17.800 -40.389  1.00 63.94           C  
+ANISOU  587  CD2 TYR A  81     9183   7347   7763  -1179   1441  -1042       C  
+ATOM    588  CE1 TYR A  81      -7.853 -17.958 -38.801  1.00 58.92           C  
+ANISOU  588  CE1 TYR A  81     8687   6381   7317   -708   1583   -641       C  
+ATOM    589  CE2 TYR A  81      -9.854 -18.945 -39.642  1.00 66.35           C  
+ANISOU  589  CE2 TYR A  81     9659   7359   8190  -1168   1700  -1029       C  
+ATOM    590  CZ  TYR A  81      -8.713 -19.009 -38.869  1.00 60.72           C  
+ANISOU  590  CZ  TYR A  81     9018   6485   7566   -910   1772   -810       C  
+ATOM    591  OH  TYR A  81      -8.409 -20.106 -38.119  1.00 67.42           O  
+ANISOU  591  OH  TYR A  81    10026   7058   8530   -846   2038   -740       O  
+ATOM    592  N   PRO A  82      -8.665 -12.297 -41.547  1.00 49.08           N  
+ANISOU  592  N   PRO A  82     6740   6152   5756   -609    636   -494       N  
+ATOM    593  CA  PRO A  82      -8.810 -11.345 -42.651  1.00 47.57           C  
+ANISOU  593  CA  PRO A  82     6433   6172   5466   -585    518   -444       C  
+ATOM    594  C   PRO A  82      -8.910 -12.091 -43.994  1.00 51.19           C  
+ANISOU  594  C   PRO A  82     6956   6741   5751   -719    580   -581       C  
+ATOM    595  O   PRO A  82      -8.241 -13.098 -44.143  1.00 50.40           O  
+ANISOU  595  O   PRO A  82     7018   6474   5657   -755    739   -662       O  
+ATOM    596  CB  PRO A  82      -7.518 -10.563 -42.588  1.00 45.77           C  
+ANISOU  596  CB  PRO A  82     6205   5851   5335   -407    504   -279       C  
+ATOM    597  CG  PRO A  82      -6.511 -11.495 -41.974  1.00 47.02           C  
+ANISOU  597  CG  PRO A  82     6497   5788   5579   -368    638   -280       C  
+ATOM    598  CD  PRO A  82      -7.288 -12.383 -41.033  1.00 48.95           C  
+ANISOU  598  CD  PRO A  82     6803   5951   5844   -460    702   -380       C  
+ATOM    599  N   THR A  83      -9.757 -11.627 -44.924  1.00 48.49           N  
+ANISOU  599  N   THR A  83     6489   6685   5247   -788    466   -606       N  
+ATOM    600  CA  THR A  83      -9.762 -12.153 -46.307  1.00 51.50           C  
+ANISOU  600  CA  THR A  83     6925   7231   5411   -907    499   -734       C  
+ATOM    601  C   THR A  83      -9.429 -11.095 -47.342  1.00 49.00           C  
+ANISOU  601  C   THR A  83     6517   7124   4975   -771    406   -571       C  
+ATOM    602  O   THR A  83      -9.557 -11.323 -48.533  1.00 50.94           O  
+ANISOU  602  O   THR A  83     6777   7584   4992   -851    400   -650       O  
+ATOM    603  CB  THR A  83     -11.157 -12.749 -46.647  1.00 56.46           C  
+ANISOU  603  CB  THR A  83     7475   8093   5884  -1158    444   -940       C  
+ATOM    604  OG1 THR A  83     -12.178 -11.809 -46.291  1.00 57.08           O  
+ANISOU  604  OG1 THR A  83     7322   8384   5979  -1111    275   -821       O  
+ATOM    605  CG2 THR A  83     -11.377 -14.007 -45.846  1.00 57.41           C  
+ANISOU  605  CG2 THR A  83     7729   7967   6115  -1324    602  -1126       C  
+ATOM    606  N   SER A  84      -9.123  -9.892 -46.868  1.00 49.80           N  
+ANISOU  606  N   SER A  84     6516   7183   5220   -575    335   -345       N  
+ATOM    607  CA  SER A  84      -8.642  -8.809 -47.689  1.00 51.09           C  
+ANISOU  607  CA  SER A  84     6609   7464   5339   -413    295   -145       C  
+ATOM    608  C   SER A  84      -8.017  -7.870 -46.668  1.00 49.54           C  
+ANISOU  608  C   SER A  84     6376   7040   5405   -251    294     24       C  
+ATOM    609  O   SER A  84      -8.128  -8.093 -45.490  1.00 47.99           O  
+ANISOU  609  O   SER A  84     6201   6679   5354   -276    298    -28       O  
+ATOM    610  CB  SER A  84      -9.795  -8.069 -48.389  1.00 56.08           C  
+ANISOU  610  CB  SER A  84     7050   8454   5800   -400    154    -60       C  
+ATOM    611  OG  SER A  84     -10.589  -7.330 -47.413  1.00 52.75           O  
+ANISOU  611  OG  SER A  84     6487   8017   5536   -337     76     32       O  
+ATOM    612  N   ASP A  85      -7.382  -6.806 -47.126  1.00 46.38           N  
+ANISOU  612  N   ASP A  85     5924   6639   5057    -96    299    222       N  
+ATOM    613  CA  ASP A  85      -6.983  -5.755 -46.253  1.00 49.05           C  
+ANISOU  613  CA  ASP A  85     6206   6797   5631     21    291    358       C  
+ATOM    614  C   ASP A  85      -8.286  -5.304 -45.638  1.00 50.62           C  
+ANISOU  614  C   ASP A  85     6295   7088   5849     14    202    357       C  
+ATOM    615  O   ASP A  85      -9.334  -5.212 -46.322  1.00 54.44           O  
+ANISOU  615  O   ASP A  85     6675   7839   6170      0    136    381       O  
+ATOM    616  CB  ASP A  85      -6.341  -4.609 -47.058  1.00 47.60           C  
+ANISOU  616  CB  ASP A  85     5967   6628   5489    170    330    579       C  
+ATOM    617  CG  ASP A  85      -4.900  -4.925 -47.559  1.00 52.73           C  
+ANISOU  617  CG  ASP A  85     6707   7157   6169    197    442    609       C  
+ATOM    618  OD1 ASP A  85      -4.277  -5.936 -47.150  1.00 52.35           O  
+ANISOU  618  OD1 ASP A  85     6760   6983   6148    128    494    481       O  
+ATOM    619  OD2 ASP A  85      -4.368  -4.108 -48.369  1.00 56.01           O  
+ANISOU  619  OD2 ASP A  85     7082   7598   6598    307    501    791       O  
+ATOM    620  N   VAL A  86      -8.255  -4.968 -44.377  1.00 45.98           N  
+ANISOU  620  N   VAL A  86     5710   6311   5446     32    200    343       N  
+ATOM    621  CA  VAL A  86      -9.522  -4.737 -43.682  1.00 45.59           C  
+ANISOU  621  CA  VAL A  86     5573   6331   5415     18    147    316       C  
+ATOM    622  C   VAL A  86      -9.338  -3.725 -42.597  1.00 43.07           C  
+ANISOU  622  C   VAL A  86     5245   5812   5305    107    169    375       C  
+ATOM    623  O   VAL A  86      -8.275  -3.619 -42.020  1.00 44.22           O  
+ANISOU  623  O   VAL A  86     5470   5760   5572    112    201    355       O  
+ATOM    624  CB  VAL A  86     -10.078  -6.072 -43.148  1.00 48.01           C  
+ANISOU  624  CB  VAL A  86     5939   6654   5647   -143    144    117       C  
+ATOM    625  CG1 VAL A  86      -9.082  -6.710 -42.219  1.00 49.95           C  
+ANISOU  625  CG1 VAL A  86     6328   6652   5997   -170    206     38       C  
+ATOM    626  CG2 VAL A  86     -11.442  -5.939 -42.465  1.00 50.17           C  
+ANISOU  626  CG2 VAL A  86     6107   7020   5936   -171    105     89       C  
+ATOM    627  N   THR A  87     -10.371  -2.925 -42.367  1.00 45.19           N  
+ANISOU  627  N   THR A  87     5406   6148   5615    181    159    451       N  
+ATOM    628  CA  THR A  87     -10.350  -1.923 -41.352  1.00 45.50           C  
+ANISOU  628  CA  THR A  87     5452   5991   5845    259    209    481       C  
+ATOM    629  C   THR A  87     -11.421  -2.288 -40.351  1.00 47.74           C  
+ANISOU  629  C   THR A  87     5716   6302   6122    213    199    378       C  
+ATOM    630  O   THR A  87     -12.526  -2.582 -40.730  1.00 47.10           O  
+ANISOU  630  O   THR A  87     5523   6431   5940    201    166    397       O  
+ATOM    631  CB  THR A  87     -10.656  -0.544 -41.917  1.00 50.80           C  
+ANISOU  631  CB  THR A  87     6025   6671   6605    425    268    690       C  
+ATOM    632  OG1 THR A  87      -9.617  -0.209 -42.839  1.00 51.07           O  
+ANISOU  632  OG1 THR A  87     6082   6667   6653    467    301    799       O  
+ATOM    633  CG2 THR A  87     -10.675   0.502 -40.782  1.00 50.21           C  
+ANISOU  633  CG2 THR A  87     5986   6346   6744    486    359    679       C  
+ATOM    634  N   ILE A  88     -11.062  -2.273 -39.075  1.00 47.12           N  
+ANISOU  634  N   ILE A  88     5735   6028   6137    183    227    271       N  
+ATOM    635  CA  ILE A  88     -11.915  -2.724 -38.016  1.00 47.67           C  
+ANISOU  635  CA  ILE A  88     5818   6102   6192    138    240    168       C  
+ATOM    636  C   ILE A  88     -11.927  -1.719 -36.867  1.00 42.11           C  
+ANISOU  636  C   ILE A  88     5161   5214   5622    206    309    145       C  
+ATOM    637  O   ILE A  88     -10.907  -1.494 -36.286  1.00 43.07           O  
+ANISOU  637  O   ILE A  88     5383   5181   5801    181    309     80       O  
+ATOM    638  CB  ILE A  88     -11.395  -4.068 -37.512  1.00 49.90           C  
+ANISOU  638  CB  ILE A  88     6214   6349   6396     13    220     34       C  
+ATOM    639  CG1 ILE A  88     -11.587  -5.112 -38.628  1.00 49.65           C  
+ANISOU  639  CG1 ILE A  88     6153   6480   6231    -76    191     16       C  
+ATOM    640  CG2 ILE A  88     -12.108  -4.473 -36.212  1.00 51.61           C  
+ANISOU  640  CG2 ILE A  88     6470   6528   6608    -19    261    -58       C  
+ATOM    641  CD1 ILE A  88     -10.896  -6.432 -38.386  1.00 49.71           C  
+ANISOU  641  CD1 ILE A  88     6291   6410   6186   -176    218    -87       C  
+ATOM    642  N   GLU A  89     -13.099  -1.193 -36.519  1.00 43.90           N  
+ANISOU  642  N   GLU A  89     5312   5478   5890    282    372    184       N  
+ATOM    643  CA  GLU A  89     -13.278  -0.421 -35.290  1.00 47.43           C  
+ANISOU  643  CA  GLU A  89     5831   5749   6437    331    467    118       C  
+ATOM    644  C   GLU A  89     -13.369  -1.368 -34.118  1.00 48.10           C  
+ANISOU  644  C   GLU A  89     6014   5828   6434    235    456    -33       C  
+ATOM    645  O   GLU A  89     -14.208  -2.238 -34.131  1.00 46.09           O  
+ANISOU  645  O   GLU A  89     5699   5711   6099    192    450    -38       O  
+ATOM    646  CB  GLU A  89     -14.570   0.378 -35.316  1.00 52.55           C  
+ANISOU  646  CB  GLU A  89     6361   6442   7161    470    572    230       C  
+ATOM    647  CG  GLU A  89     -14.633   1.492 -36.347  1.00 63.41           C  
+ANISOU  647  CG  GLU A  89     7637   7813   8643    621    629    431       C  
+ATOM    648  CD  GLU A  89     -15.842   2.407 -36.147  1.00 73.77           C  
+ANISOU  648  CD  GLU A  89     8843   9123  10063    800    777    562       C  
+ATOM    649  OE1 GLU A  89     -16.733   2.070 -35.336  1.00 80.53           O  
+ANISOU  649  OE1 GLU A  89     9673  10027  10895    795    819    497       O  
+ATOM    650  OE2 GLU A  89     -15.887   3.472 -36.793  1.00 76.86           O  
+ANISOU  650  OE2 GLU A  89     9175   9453  10575    962    875    747       O  
+ATOM    651  N   THR A  90     -12.531  -1.166 -33.102  1.00 44.70           N  
+ANISOU  651  N   THR A  90     5722   5248   6013    199    462   -150       N  
+ATOM    652  CA  THR A  90     -12.427  -2.084 -32.002  1.00 43.34           C  
+ANISOU  652  CA  THR A  90     5650   5087   5727    129    448   -262       C  
+ATOM    653  C   THR A  90     -13.337  -1.652 -30.827  1.00 47.39           C  
+ANISOU  653  C   THR A  90     6210   5556   6240    178    561   -329       C  
+ATOM    654  O   THR A  90     -13.940  -0.586 -30.855  1.00 46.89           O  
+ANISOU  654  O   THR A  90     6109   5423   6283    268    659   -299       O  
+ATOM    655  CB  THR A  90     -10.937  -2.167 -31.551  1.00 42.54           C  
+ANISOU  655  CB  THR A  90     5651   4917   5596     69    368   -337       C  
+ATOM    656  OG1 THR A  90     -10.480  -0.884 -31.081  1.00 46.16           O  
+ANISOU  656  OG1 THR A  90     6155   5238   6145     78    400   -410       O  
+ATOM    657  CG2 THR A  90     -10.008  -2.601 -32.693  1.00 42.77           C  
+ANISOU  657  CG2 THR A  90     5633   4980   5636     38    287   -260       C  
+ATOM    658  N   HIS A  91     -13.394  -2.452 -29.757  1.00 47.53           N  
+ANISOU  658  N   HIS A  91     6321   5601   6138    136    573   -409       N  
+ATOM    659  CA  HIS A  91     -14.121  -2.027 -28.523  1.00 50.54           C  
+ANISOU  659  CA  HIS A  91     6775   5936   6488    184    696   -488       C  
+ATOM    660  C   HIS A  91     -15.577  -1.689 -28.823  1.00 48.63           C  
+ANISOU  660  C   HIS A  91     6407   5732   6336    269    818   -403       C  
+ATOM    661  O   HIS A  91     -16.087  -0.683 -28.370  1.00 47.55           O  
+ANISOU  661  O   HIS A  91     6289   5503   6273    358    945   -426       O  
+ATOM    662  CB  HIS A  91     -13.449  -0.819 -27.839  1.00 53.09           C  
+ANISOU  662  CB  HIS A  91     7212   6116   6841    192    723   -613       C  
+ATOM    663  CG  HIS A  91     -12.037  -1.062 -27.377  1.00 53.84           C  
+ANISOU  663  CG  HIS A  91     7400   6222   6835     99    592   -706       C  
+ATOM    664  ND1 HIS A  91     -10.998  -1.282 -28.245  1.00 52.95           N  
+ANISOU  664  ND1 HIS A  91     7230   6124   6762     50    467   -650       N  
+ATOM    665  CD2 HIS A  91     -11.494  -1.106 -26.132  1.00 56.62           C  
+ANISOU  665  CD2 HIS A  91     7877   6601   7033     54    565   -841       C  
+ATOM    666  CE1 HIS A  91      -9.869  -1.434 -27.568  1.00 52.38           C  
+ANISOU  666  CE1 HIS A  91     7223   6088   6589    -18    367   -735       C  
+ATOM    667  NE2 HIS A  91     -10.149  -1.348 -26.282  1.00 55.87           N  
+ANISOU  667  NE2 HIS A  91     7774   6556   6899    -20    411   -852       N  
+ATOM    668  N   LYS A  92     -16.228  -2.521 -29.624  1.00 49.04           N  
+ANISOU  668  N   LYS A  92     6318   5927   6386    236    787   -308       N  
+ATOM    669  CA  LYS A  92     -17.650  -2.390 -29.844  1.00 50.58           C  
+ANISOU  669  CA  LYS A  92     6348   6224   6644    297    884   -219       C  
+ATOM    670  C   LYS A  92     -18.507  -3.326 -28.988  1.00 47.54           C  
+ANISOU  670  C   LYS A  92     5964   5904   6193    250    976   -250       C  
+ATOM    671  O   LYS A  92     -19.654  -3.503 -29.328  1.00 46.77           O  
+ANISOU  671  O   LYS A  92     5686   5937   6144    257   1030   -172       O  
+ATOM    672  CB  LYS A  92     -17.947  -2.701 -31.306  1.00 53.04           C  
+ANISOU  672  CB  LYS A  92     6468   6705   6979    263    785   -107       C  
+ATOM    673  CG  LYS A  92     -17.183  -1.817 -32.271  1.00 56.58           C  
+ANISOU  673  CG  LYS A  92     6898   7107   7489    325    715    -36       C  
+ATOM    674  CD  LYS A  92     -17.486  -0.352 -32.061  1.00 58.78           C  
+ANISOU  674  CD  LYS A  92     7167   7262   7905    494    841     28       C  
+ATOM    675  CE  LYS A  92     -18.585   0.113 -32.975  1.00 67.73           C  
+ANISOU  675  CE  LYS A  92     8061   8564   9107    618    876    223       C  
+ATOM    676  NZ  LYS A  92     -18.423   1.591 -33.141  1.00 70.69           N  
+ANISOU  676  NZ  LYS A  92     8453   8766   9637    795    997    324       N  
+ATOM    677  N   GLU A  93     -17.977  -3.940 -27.920  1.00 48.14           N  
+ANISOU  677  N   GLU A  93     6220   5912   6155    204    998   -342       N  
+ATOM    678  CA  GLU A  93     -18.834  -4.744 -26.990  1.00 51.58           C  
+ANISOU  678  CA  GLU A  93     6674   6388   6535    183   1133   -348       C  
+ATOM    679  C   GLU A  93     -20.133  -4.043 -26.630  1.00 53.52           C  
+ANISOU  679  C   GLU A  93     6808   6660   6864    287   1296   -305       C  
+ATOM    680  O   GLU A  93     -21.165  -4.650 -26.637  1.00 58.70           O  
+ANISOU  680  O   GLU A  93     7330   7419   7554    250   1380   -249       O  
+ATOM    681  CB  GLU A  93     -18.112  -5.078 -25.684  1.00 53.98           C  
+ANISOU  681  CB  GLU A  93     7203   6619   6686    189   1166   -429       C  
+ATOM    682  CG  GLU A  93     -18.999  -5.656 -24.586  1.00 58.34           C  
+ANISOU  682  CG  GLU A  93     7798   7194   7171    208   1345   -418       C  
+ATOM    683  CD  GLU A  93     -18.194  -6.346 -23.498  1.00 59.78           C  
+ANISOU  683  CD  GLU A  93     8187   7360   7166    207   1352   -449       C  
+ATOM    684  OE1 GLU A  93     -17.556  -5.652 -22.668  1.00 64.37           O  
+ANISOU  684  OE1 GLU A  93     8915   7917   7626    267   1330   -538       O  
+ATOM    685  OE2 GLU A  93     -18.184  -7.597 -23.528  1.00 52.88           O  
+ANISOU  685  OE2 GLU A  93     7321   6504   6265    142   1381   -382       O  
+ATOM    686  N   GLU A  94     -20.084  -2.768 -26.311  1.00 59.72           N  
+ANISOU  686  N   GLU A  94     7647   7342   7699    413   1362   -331       N  
+ATOM    687  CA  GLU A  94     -21.265  -2.097 -25.790  1.00 66.89           C  
+ANISOU  687  CA  GLU A  94     8483   8242   8687    544   1565   -289       C  
+ATOM    688  C   GLU A  94     -22.389  -2.061 -26.822  1.00 70.98           C  
+ANISOU  688  C   GLU A  94     8697   8926   9344    581   1575   -124       C  
+ATOM    689  O   GLU A  94     -23.572  -2.215 -26.481  1.00 76.40           O  
+ANISOU  689  O   GLU A  94     9243   9704  10081    626   1721    -54       O  
+ATOM    690  CB  GLU A  94     -20.887  -0.700 -25.315  1.00 74.24           C  
+ANISOU  690  CB  GLU A  94     9560   8986   9660    665   1660   -370       C  
+ATOM    691  CG  GLU A  94     -20.302  -0.701 -23.891  1.00 88.53           C  
+ANISOU  691  CG  GLU A  94    11642  10696  11297    640   1723   -551       C  
+ATOM    692  CD  GLU A  94     -21.356  -0.505 -22.760  1.00104.83           C  
+ANISOU  692  CD  GLU A  94    13759  12740  13331    745   1979   -573       C  
+ATOM    693  OE1 GLU A  94     -20.982   0.024 -21.666  1.00102.89           O  
+ANISOU  693  OE1 GLU A  94    13744  12388  12962    767   2076   -740       O  
+ATOM    694  OE2 GLU A  94     -22.562  -0.867 -22.947  1.00106.29           O  
+ANISOU  694  OE2 GLU A  94    13749  13029  13605    797   2092   -434       O  
+ATOM    695  N   ALA A  95     -22.007  -1.909 -28.091  1.00 68.41           N  
+ANISOU  695  N   ALA A  95     8255   8672   9065    556   1414    -53       N  
+ATOM    696  CA  ALA A  95     -22.956  -1.709 -29.182  1.00 66.09           C  
+ANISOU  696  CA  ALA A  95     7658   8583   8869    608   1388    116       C  
+ATOM    697  C   ALA A  95     -23.662  -2.996 -29.528  1.00 66.44           C  
+ANISOU  697  C   ALA A  95     7523   8851   8867    435   1328    127       C  
+ATOM    698  O   ALA A  95     -24.779  -2.940 -30.017  1.00 70.13           O  
+ANISOU  698  O   ALA A  95     7710   9533   9402    465   1352    251       O  
+ATOM    699  CB  ALA A  95     -22.245  -1.163 -30.422  1.00 66.10           C  
+ANISOU  699  CB  ALA A  95     7613   8606   8896    636   1238    189       C  
+ATOM    700  N   ILE A  96     -23.008  -4.142 -29.324  1.00 64.35           N  
+ANISOU  700  N   ILE A  96     7407   8544   8498    253   1257      4       N  
+ATOM    701  CA  ILE A  96     -23.650  -5.436 -29.582  1.00 69.19           C  
+ANISOU  701  CA  ILE A  96     7886   9314   9088     56   1244    -17       C  
+ATOM    702  C   ILE A  96     -24.290  -6.032 -28.311  1.00 75.00           C  
+ANISOU  702  C   ILE A  96     8682   9987   9825     30   1440    -50       C  
+ATOM    703  O   ILE A  96     -24.815  -7.127 -28.332  1.00 68.54           O  
+ANISOU  703  O   ILE A  96     7782   9246   9011   -142   1479    -77       O  
+ATOM    704  CB  ILE A  96     -22.743  -6.492 -30.279  1.00 69.26           C  
+ANISOU  704  CB  ILE A  96     7991   9314   9009   -136   1099   -110       C  
+ATOM    705  CG1 ILE A  96     -21.443  -6.740 -29.528  1.00 72.23           C  
+ANISOU  705  CG1 ILE A  96     8677   9458   9305   -123   1101   -195       C  
+ATOM    706  CG2 ILE A  96     -22.463  -6.154 -31.716  1.00 67.05           C  
+ANISOU  706  CG2 ILE A  96     7581   9181   8715   -151    922    -66       C  
+ATOM    707  CD1 ILE A  96     -21.463  -8.031 -28.757  1.00 71.63           C  
+ANISOU  707  CD1 ILE A  96     8723   9309   9184   -245   1210   -253       C  
+ATOM    708  N   MET A  97     -24.258  -5.310 -27.201  1.00 82.61           N  
+ANISOU  708  N   MET A  97     9796  10805  10784    194   1584    -55       N  
+ATOM    709  CA  MET A  97     -25.147  -5.636 -26.099  1.00 87.11           C  
+ANISOU  709  CA  MET A  97    10364  11365  11366    215   1801    -42       C  
+ATOM    710  C   MET A  97     -26.325  -4.668 -26.205  1.00 95.61           C  
+ANISOU  710  C   MET A  97    11196  12551  12577    376   1919     80       C  
+ATOM    711  O   MET A  97     -27.410  -5.101 -26.587  1.00 94.92           O  
+ANISOU  711  O   MET A  97    10826  12664  12575    303   1954    163       O  
+ATOM    712  CB  MET A  97     -24.435  -5.516 -24.761  1.00 78.92           C  
+ANISOU  712  CB  MET A  97     9648  10134  10203    296   1904   -129       C  
+ATOM    713  CG  MET A  97     -23.264  -6.468 -24.547  1.00 69.75           C  
+ANISOU  713  CG  MET A  97     8709   8888   8904    181   1804   -207       C  
+ATOM    714  SD  MET A  97     -22.432  -5.914 -23.038  1.00 73.23           S  
+ANISOU  714  SD  MET A  97     9478   9183   9162    316   1882   -298       S  
+ATOM    715  CE  MET A  97     -21.484  -7.324 -22.587  1.00 70.81           C  
+ANISOU  715  CE  MET A  97     9359   8848   8698    213   1836   -305       C  
+ATOM    716  N   LEU A  98     -26.060  -3.361 -25.977  1.00101.64           N  
+ANISOU  716  N   LEU A  98    12055  13191  13371    583   1974     95       N  
+ATOM    717  CA  LEU A  98     -27.090  -2.306 -25.727  1.00104.44           C  
+ANISOU  717  CA  LEU A  98    12260  13563  13858    803   2175    216       C  
+ATOM    718  C   LEU A  98     -27.843  -1.768 -26.965  1.00 99.32           C  
+ANISOU  718  C   LEU A  98    11253  13135  13349    890   2107    411       C  
+ATOM    719  O   LEU A  98     -27.308  -1.714 -28.071  1.00 91.78           O  
+ANISOU  719  O   LEU A  98    10232  12263  12378    839   1898    441       O  
+ATOM    720  CB  LEU A  98     -26.469  -1.140 -24.941  1.00 98.56           C  
+ANISOU  720  CB  LEU A  98    11797  12554  13097    977   2303    129       C  
+TER     721      LEU A  98                                                      
+ATOM    722  N   ALA B   2     -32.420 -24.500 -28.343  1.00 55.40           N  
+ANISOU  722  N   ALA B   2     8165   6777   6106   -670  -1057   -125       N  
+ATOM    723  CA  ALA B   2     -32.280 -23.018 -28.309  1.00 49.74           C  
+ANISOU  723  CA  ALA B   2     7221   6215   5463   -512   -965    -28       C  
+ATOM    724  C   ALA B   2     -30.968 -22.594 -27.656  1.00 49.10           C  
+ANISOU  724  C   ALA B   2     7123   6073   5460   -324   -756    -44       C  
+ATOM    725  O   ALA B   2     -30.475 -23.240 -26.699  1.00 48.76           O  
+ANISOU  725  O   ALA B   2     7101   5932   5490   -308   -687    -81       O  
+ATOM    726  CB  ALA B   2     -33.452 -22.398 -27.562  1.00 46.79           C  
+ANISOU  726  CB  ALA B   2     6548   6005   5225   -580  -1026     92       C  
+ATOM    727  N   THR B   3     -30.475 -21.450 -28.119  1.00 46.70           N  
+ANISOU  727  N   THR B   3     6765   5842   5136   -203   -668      1       N  
+ATOM    728  CA  THR B   3     -29.337 -20.783 -27.525  1.00 45.09           C  
+ANISOU  728  CA  THR B   3     6493   5632   5006    -79   -488      8       C  
+ATOM    729  C   THR B   3     -29.711 -19.332 -27.227  1.00 42.65           C  
+ANISOU  729  C   THR B   3     6022   5395   4786    -46   -461    119       C  
+ATOM    730  O   THR B   3     -30.163 -18.603 -28.102  1.00 49.78           O  
+ANISOU  730  O   THR B   3     6948   6349   5617    -31   -515    186       O  
+ATOM    731  CB  THR B   3     -28.152 -20.886 -28.525  1.00 48.51           C  
+ANISOU  731  CB  THR B   3     7083   6061   5285     14   -383    -53       C  
+ATOM    732  OG1 THR B   3     -27.960 -22.272 -28.848  1.00 53.59           O  
+ANISOU  732  OG1 THR B   3     7925   6609   5827     21   -421   -170       O  
+ATOM    733  CG2 THR B   3     -26.865 -20.283 -27.959  1.00 46.16           C  
+ANISOU  733  CG2 THR B   3     6683   5804   5049    105   -200    -45       C  
+ATOM    734  N   TYR B   4     -29.515 -18.935 -25.980  1.00 41.02           N  
+ANISOU  734  N   TYR B   4     5685   5178   4720    -22   -381    136       N  
+ATOM    735  CA  TYR B   4     -29.849 -17.616 -25.484  1.00 41.59           C  
+ANISOU  735  CA  TYR B   4     5649   5273   4878     26   -345    216       C  
+ATOM    736  C   TYR B   4     -28.595 -16.874 -25.182  1.00 37.08           C  
+ANISOU  736  C   TYR B   4     5099   4664   4323     55   -200    205       C  
+ATOM    737  O   TYR B   4     -27.552 -17.444 -25.084  1.00 42.67           O  
+ANISOU  737  O   TYR B   4     5829   5376   5007     49   -128    142       O  
+ATOM    738  CB  TYR B   4     -30.686 -17.729 -24.208  1.00 37.93           C  
+ANISOU  738  CB  TYR B   4     5032   4839   4538     12   -366    231       C  
+ATOM    739  CG  TYR B   4     -31.986 -18.470 -24.482  1.00 40.65           C  
+ANISOU  739  CG  TYR B   4     5310   5268   4864    -68   -514    257       C  
+ATOM    740  CD1 TYR B   4     -32.953 -17.916 -25.304  1.00 41.09           C  
+ANISOU  740  CD1 TYR B   4     5314   5421   4875    -38   -621    333       C  
+ATOM    741  CD2 TYR B   4     -32.253 -19.691 -23.893  1.00 39.18           C  
+ANISOU  741  CD2 TYR B   4     5113   5074   4699   -188   -554    219       C  
+ATOM    742  CE1 TYR B   4     -34.139 -18.580 -25.546  1.00 44.88           C  
+ANISOU  742  CE1 TYR B   4     5690   6029   5332   -146   -773    362       C  
+ATOM    743  CE2 TYR B   4     -33.429 -20.383 -24.132  1.00 44.73           C  
+ANISOU  743  CE2 TYR B   4     5750   5865   5377   -328   -695    249       C  
+ATOM    744  CZ  TYR B   4     -34.369 -19.827 -24.951  1.00 49.31           C  
+ANISOU  744  CZ  TYR B   4     6235   6582   5915   -317   -807    316       C  
+ATOM    745  OH  TYR B   4     -35.506 -20.526 -25.188  1.00 53.31           O  
+ANISOU  745  OH  TYR B   4     6643   7219   6390   -490   -962    347       O  
+ATOM    746  N   LYS B   5     -28.735 -15.589 -25.005  1.00 42.61           N  
+ANISOU  746  N   LYS B   5     5794   5333   5063     87   -164    270       N  
+ATOM    747  CA  LYS B   5     -27.647 -14.685 -24.656  1.00 46.72           C  
+ANISOU  747  CA  LYS B   5     6346   5805   5598     57    -42    270       C  
+ATOM    748  C   LYS B   5     -27.871 -14.311 -23.218  1.00 44.65           C  
+ANISOU  748  C   LYS B   5     6004   5508   5453     71    -18    246       C  
+ATOM    749  O   LYS B   5     -28.949 -13.910 -22.869  1.00 45.52           O  
+ANISOU  749  O   LYS B   5     6085   5600   5610    146    -64    281       O  
+ATOM    750  CB  LYS B   5     -27.735 -13.411 -25.451  1.00 51.37           C  
+ANISOU  750  CB  LYS B   5     7063   6323   6130     68    -27    364       C  
+ATOM    751  CG  LYS B   5     -27.674 -13.596 -26.941  1.00 58.63           C  
+ANISOU  751  CG  LYS B   5     8089   7285   6902     63    -56    410       C  
+ATOM    752  CD  LYS B   5     -26.289 -13.986 -27.387  1.00 70.28           C  
+ANISOU  752  CD  LYS B   5     9582   8829   8291    -17     60    365       C  
+ATOM    753  CE  LYS B   5     -26.281 -14.277 -28.899  1.00 79.59           C  
+ANISOU  753  CE  LYS B   5    10887  10066   9288     -6     42    396       C  
+ATOM    754  NZ  LYS B   5     -26.107 -13.052 -29.737  1.00 78.80           N  
+ANISOU  754  NZ  LYS B   5    10935   9919   9086    -50     88    523       N  
+ATOM    755  N   VAL B   6     -26.848 -14.482 -22.400  1.00 44.73           N  
+ANISOU  755  N   VAL B   6     5968   5536   5490     12     52    185       N  
+ATOM    756  CA  VAL B   6     -26.898 -14.160 -21.017  1.00 43.45           C  
+ANISOU  756  CA  VAL B   6     5755   5350   5403      9     75    149       C  
+ATOM    757  C   VAL B   6     -25.875 -13.071 -20.817  1.00 41.71           C  
+ANISOU  757  C   VAL B   6     5600   5078   5168    -88    150    141       C  
+ATOM    758  O   VAL B   6     -24.703 -13.264 -21.100  1.00 40.03           O  
+ANISOU  758  O   VAL B   6     5351   4944   4913   -178    196    125       O  
+ATOM    759  CB  VAL B   6     -26.496 -15.357 -20.132  1.00 43.61           C  
+ANISOU  759  CB  VAL B   6     5675   5449   5445      0     68     89       C  
+ATOM    760  CG1 VAL B   6     -26.579 -14.975 -18.662  1.00 45.36           C  
+ANISOU  760  CG1 VAL B   6     5860   5659   5714     -5     90     55       C  
+ATOM    761  CG2 VAL B   6     -27.382 -16.547 -20.422  1.00 42.80           C  
+ANISOU  761  CG2 VAL B   6     5551   5370   5340     37     -7     99       C  
+ATOM    762  N   LYS B   7     -26.334 -11.940 -20.314  1.00 40.23           N  
+ANISOU  762  N   LYS B   7     5512   4765   5007    -71    162    150       N  
+ATOM    763  CA  LYS B   7     -25.489 -10.806 -20.042  1.00 45.32           C  
+ANISOU  763  CA  LYS B   7     6277   5311   5631   -205    217    138       C  
+ATOM    764  C   LYS B   7     -25.268 -10.732 -18.545  1.00 45.32           C  
+ANISOU  764  C   LYS B   7     6243   5315   5660   -241    224     48       C  
+ATOM    765  O   LYS B   7     -26.230 -10.649 -17.762  1.00 42.09           O  
+ANISOU  765  O   LYS B   7     5848   4860   5282   -112    213     21       O  
+ATOM    766  CB  LYS B   7     -26.179  -9.567 -20.522  1.00 45.61           C  
+ANISOU  766  CB  LYS B   7     6527   5150   5652   -141    218    203       C  
+ATOM    767  CG  LYS B   7     -25.518  -8.255 -20.177  1.00 54.48           C  
+ANISOU  767  CG  LYS B   7     7863   6090   6747   -291    265    191       C  
+ATOM    768  CD  LYS B   7     -26.348  -7.148 -20.886  1.00 64.52           C  
+ANISOU  768  CD  LYS B   7     9392   7132   7990   -162    255    284       C  
+ATOM    769  CE  LYS B   7     -25.977  -5.744 -20.437  1.00 79.55           C  
+ANISOU  769  CE  LYS B   7    11603   8759   9861   -274    292    266       C  
+ATOM    770  NZ  LYS B   7     -26.138  -4.714 -21.507  1.00 86.71           N  
+ANISOU  770  NZ  LYS B   7    12802   9439  10702   -264    295    396       N  
+ATOM    771  N   PHE B   8     -24.001 -10.796 -18.153  1.00 44.26           N  
+ANISOU  771  N   PHE B   8     6044   5273   5500   -411    242      4       N  
+ATOM    772  CA  PHE B   8     -23.636 -10.739 -16.757  1.00 48.27           C  
+ANISOU  772  CA  PHE B   8     6521   5811   6006   -472    227    -80       C  
+ATOM    773  C   PHE B   8     -23.203  -9.366 -16.431  1.00 50.61           C  
+ANISOU  773  C   PHE B   8     7012   5952   6265   -641    245   -114       C  
+ATOM    774  O   PHE B   8     -22.332  -8.836 -17.115  1.00 51.85           O  
+ANISOU  774  O   PHE B   8     7205   6107   6388   -833    269    -77       O  
+ATOM    775  CB  PHE B   8     -22.495 -11.714 -16.458  1.00 46.54           C  
+ANISOU  775  CB  PHE B   8     6090   5823   5767   -542    208   -104       C  
+ATOM    776  CG  PHE B   8     -22.919 -13.133 -16.573  1.00 43.20           C  
+ANISOU  776  CG  PHE B   8     5543   5501   5370   -371    182    -85       C  
+ATOM    777  CD1 PHE B   8     -23.753 -13.684 -15.611  1.00 45.99           C  
+ANISOU  777  CD1 PHE B   8     5889   5839   5743   -264    152   -107       C  
+ATOM    778  CD2 PHE B   8     -22.561 -13.888 -17.670  1.00 43.53           C  
+ANISOU  778  CD2 PHE B   8     5511   5629   5397   -326    196    -45       C  
+ATOM    779  CE1 PHE B   8     -24.172 -15.008 -15.719  1.00 45.51           C  
+ANISOU  779  CE1 PHE B   8     5756   5836   5697   -151    123    -79       C  
+ATOM    780  CE2 PHE B   8     -22.977 -15.203 -17.786  1.00 47.96           C  
+ANISOU  780  CE2 PHE B   8     6024   6230   5968   -183    163    -39       C  
+ATOM    781  CZ  PHE B   8     -23.809 -15.751 -16.817  1.00 42.32           C  
+ANISOU  781  CZ  PHE B   8     5316   5479   5284   -113    120    -50       C  
+ATOM    782  N   ILE B   9     -23.818  -8.777 -15.407  1.00 50.12           N  
+ANISOU  782  N   ILE B   9     7095   5752   6194   -582    242   -186       N  
+ATOM    783  CA  ILE B   9     -23.293  -7.532 -14.841  1.00 52.49           C  
+ANISOU  783  CA  ILE B   9     7627   5881   6436   -773    243   -255       C  
+ATOM    784  C   ILE B   9     -22.569  -7.908 -13.581  1.00 53.51           C  
+ANISOU  784  C   ILE B   9     7645   6168   6518   -894    195   -352       C  
+ATOM    785  O   ILE B   9     -23.199  -8.276 -12.624  1.00 53.62           O  
+ANISOU  785  O   ILE B   9     7645   6206   6520   -744    190   -409       O  
+ATOM    786  CB  ILE B   9     -24.432  -6.583 -14.587  1.00 57.52           C  
+ANISOU  786  CB  ILE B   9     8544   6244   7065   -590    275   -283       C  
+ATOM    787  CG1 ILE B   9     -25.188  -6.426 -15.903  1.00 59.96           C  
+ANISOU  787  CG1 ILE B   9     8902   6461   7416   -426    296   -159       C  
+ATOM    788  CG2 ILE B   9     -23.932  -5.267 -13.966  1.00 60.99           C  
+ANISOU  788  CG2 ILE B   9     9309   6437   7425   -785    272   -375       C  
+ATOM    789  CD1 ILE B   9     -26.172  -5.282 -15.905  1.00 63.59           C  
+ANISOU  789  CD1 ILE B   9     9670   6630   7859   -227    324   -158       C  
+ATOM    790  N   THR B  10     -21.243  -7.928 -13.622  1.00 58.51           N  
+ANISOU  790  N   THR B  10     8157   6958   7116  -1157    160   -355       N  
+ATOM    791  CA  THR B  10     -20.440  -8.325 -12.483  1.00 58.08           C  
+ANISOU  791  CA  THR B  10     7958   7106   7001  -1275     86   -431       C  
+ATOM    792  C   THR B  10     -19.973  -7.044 -11.813  1.00 70.23           C  
+ANISOU  792  C   THR B  10     9748   8484   8451  -1550     48   -529       C  
+ATOM    793  O   THR B  10     -20.144  -5.958 -12.368  1.00 71.35           O  
+ANISOU  793  O   THR B  10    10161   8360   8588  -1655     87   -519       O  
+ATOM    794  CB  THR B  10     -19.189  -9.135 -12.876  1.00 58.06           C  
+ANISOU  794  CB  THR B  10     7627   7434   6998  -1387     54   -376       C  
+ATOM    795  OG1 THR B  10     -18.233  -8.281 -13.503  1.00 67.21           O  
+ANISOU  795  OG1 THR B  10     8801   8610   8125  -1697     67   -349       O  
+ATOM    796  CG2 THR B  10     -19.528 -10.276 -13.818  1.00 59.69           C  
+ANISOU  796  CG2 THR B  10     7661   7742   7275  -1147    101   -286       C  
+ATOM    797  N   PRO B  11     -19.376  -7.160 -10.620  1.00 72.85           N  
+ANISOU  797  N   PRO B  11    10022   8962   8697  -1676    -39   -622       N  
+ATOM    798  CA  PRO B  11     -18.837  -5.933 -10.022  1.00 82.51           C  
+ANISOU  798  CA  PRO B  11    11506  10030   9814  -1997    -96   -729       C  
+ATOM    799  C   PRO B  11     -17.740  -5.357 -10.913  1.00 82.13           C  
+ANISOU  799  C   PRO B  11    11402  10036   9765  -2359   -105   -662       C  
+ATOM    800  O   PRO B  11     -17.601  -4.166 -11.043  1.00 81.04           O  
+ANISOU  800  O   PRO B  11    11578   9634   9578  -2614   -104   -697       O  
+ATOM    801  CB  PRO B  11     -18.263  -6.417  -8.685  1.00 79.55           C  
+ANISOU  801  CB  PRO B  11    10990   9902   9333  -2070   -213   -819       C  
+ATOM    802  CG  PRO B  11     -19.024  -7.665  -8.373  1.00 77.41           C  
+ANISOU  802  CG  PRO B  11    10544   9762   9107  -1699   -182   -776       C  
+ATOM    803  CD  PRO B  11     -19.316  -8.308  -9.700  1.00 70.96           C  
+ANISOU  803  CD  PRO B  11     9555   8976   8429  -1526    -97   -639       C  
+ATOM    804  N   GLU B  12     -17.008  -6.230 -11.575  1.00 88.50           N  
+ANISOU  804  N   GLU B  12    11824  11180  10621  -2365    -98   -558       N  
+ATOM    805  CA  GLU B  12     -15.873  -5.804 -12.367  1.00 99.06           C  
+ANISOU  805  CA  GLU B  12    13027  12669  11940  -2715    -89   -484       C  
+ATOM    806  C   GLU B  12     -16.299  -5.351 -13.766  1.00 93.10           C  
+ANISOU  806  C   GLU B  12    12432  11697  11242  -2687     35   -371       C  
+ATOM    807  O   GLU B  12     -15.498  -4.776 -14.476  1.00 97.19           O  
+ANISOU  807  O   GLU B  12    12930  12268  11727  -3008     67   -299       O  
+ATOM    808  CB  GLU B  12     -14.832  -6.940 -12.446  1.00112.44           C  
+ANISOU  808  CB  GLU B  12    14216  14866  13638  -2694   -123   -425       C  
+ATOM    809  CG  GLU B  12     -14.678  -7.719 -11.130  1.00124.13           C  
+ANISOU  809  CG  GLU B  12    15529  16561  15070  -2562   -247   -503       C  
+ATOM    810  CD  GLU B  12     -13.339  -8.441 -10.973  1.00129.71           C  
+ANISOU  810  CD  GLU B  12    15773  17775  15735  -2649   -327   -459       C  
+ATOM    811  OE1 GLU B  12     -12.591  -8.120 -10.015  1.00122.40           O  
+ANISOU  811  OE1 GLU B  12    14767  17032  14705  -2906   -469   -526       O  
+ATOM    812  OE2 GLU B  12     -13.042  -9.343 -11.788  1.00121.66           O  
+ANISOU  812  OE2 GLU B  12    14470  16981  14772  -2441   -255   -362       O  
+ATOM    813  N   GLY B  13     -17.543  -5.608 -14.166  1.00 81.49           N  
+ANISOU  813  N   GLY B  13    11109  10009   9842  -2323     99   -344       N  
+ATOM    814  CA  GLY B  13     -18.037  -5.147 -15.451  1.00 73.28           C  
+ANISOU  814  CA  GLY B  13    10250   8758   8835  -2271    193   -234       C  
+ATOM    815  C   GLY B  13     -18.997  -6.122 -16.098  1.00 65.13           C  
+ANISOU  815  C   GLY B  13     9104   7761   7882  -1867    241   -172       C  
+ATOM    816  O   GLY B  13     -19.539  -6.995 -15.467  1.00 67.40           O  
+ANISOU  816  O   GLY B  13     9263   8138   8207  -1617    210   -222       O  
+ATOM    817  N   GLU B  14     -19.185  -5.957 -17.384  1.00 61.80           N  
+ANISOU  817  N   GLU B  14     8743   7270   7466  -1838    311    -57       N  
+ATOM    818  CA  GLU B  14     -20.256  -6.598 -18.125  1.00 63.32           C  
+ANISOU  818  CA  GLU B  14     8924   7423   7712  -1496    338      2       C  
+ATOM    819  C   GLU B  14     -19.671  -7.497 -19.189  1.00 56.31           C  
+ANISOU  819  C   GLU B  14     7774   6809   6810  -1480    388     83       C  
+ATOM    820  O   GLU B  14     -18.634  -7.194 -19.774  1.00 62.63           O  
+ANISOU  820  O   GLU B  14     8502   7742   7551  -1731    443    140       O  
+ATOM    821  CB  GLU B  14     -21.129  -5.544 -18.792  1.00 71.21           C  
+ANISOU  821  CB  GLU B  14    10284   8074   8699  -1428    362     71       C  
+ATOM    822  CG  GLU B  14     -22.416  -5.260 -18.043  1.00 86.78           C  
+ANISOU  822  CG  GLU B  14    12442   9819  10709  -1152    330      6       C  
+ATOM    823  CD  GLU B  14     -22.957  -3.842 -18.244  1.00101.03           C  
+ANISOU  823  CD  GLU B  14    14681  11229  12476  -1139    343     36       C  
+ATOM    824  OE1 GLU B  14     -22.373  -2.917 -17.631  1.00107.53           O  
+ANISOU  824  OE1 GLU B  14    15741  11869  13245  -1388    335    -29       O  
+ATOM    825  OE2 GLU B  14     -23.973  -3.658 -18.983  1.00100.60           O  
+ANISOU  825  OE2 GLU B  14    14745  11041  12435   -874    348    124       O  
+ATOM    826  N   LEU B  15     -20.365  -8.587 -19.454  1.00 56.17           N  
+ANISOU  826  N   LEU B  15     7631   6874   6835  -1191    377     86       N  
+ATOM    827  CA  LEU B  15     -20.023  -9.507 -20.528  1.00 55.27           C  
+ANISOU  827  CA  LEU B  15     7340   6967   6692  -1109    425    143       C  
+ATOM    828  C   LEU B  15     -21.290 -10.308 -20.860  1.00 53.66           C  
+ANISOU  828  C   LEU B  15     7166   6692   6529   -812    384    148       C  
+ATOM    829  O   LEU B  15     -22.039 -10.720 -19.940  1.00 50.69           O  
+ANISOU  829  O   LEU B  15     6774   6269   6216   -671    324     88       O  
+ATOM    830  CB  LEU B  15     -18.948 -10.454 -20.044  1.00 57.82           C  
+ANISOU  830  CB  LEU B  15     7356   7605   7007  -1128    426     93       C  
+ATOM    831  CG  LEU B  15     -18.440 -11.420 -21.099  1.00 63.30           C  
+ANISOU  831  CG  LEU B  15     7878   8524   7650  -1015    495    130       C  
+ATOM    832  CD1 LEU B  15     -17.756 -10.666 -22.216  1.00 63.58           C  
+ANISOU  832  CD1 LEU B  15     7943   8622   7594  -1222    602    221       C  
+ATOM    833  CD2 LEU B  15     -17.443 -12.372 -20.490  1.00 65.97           C  
+ANISOU  833  CD2 LEU B  15     7922   9161   7982   -958    487     79       C  
+ATOM    834  N   GLU B  16     -21.532 -10.510 -22.157  1.00 47.63           N  
+ANISOU  834  N   GLU B  16     6447   5937   5713   -745    415    221       N  
+ATOM    835  CA  GLU B  16     -22.664 -11.304 -22.654  1.00 45.67           C  
+ANISOU  835  CA  GLU B  16     6218   5656   5479   -515    358    230       C  
+ATOM    836  C   GLU B  16     -22.099 -12.584 -23.254  1.00 48.61           C  
+ANISOU  836  C   GLU B  16     6436   6232   5799   -443    384    205       C  
+ATOM    837  O   GLU B  16     -21.055 -12.553 -23.930  1.00 46.18           O  
+ANISOU  837  O   GLU B  16     6067   6070   5407   -536    474    229       O  
+ATOM    838  CB  GLU B  16     -23.451 -10.553 -23.698  1.00 48.11           C  
+ANISOU  838  CB  GLU B  16     6731   5809   5738   -476    346    328       C  
+ATOM    839  CG  GLU B  16     -24.832 -11.113 -24.032  1.00 53.37           C  
+ANISOU  839  CG  GLU B  16     7413   6439   6424   -265    251    343       C  
+ATOM    840  CD  GLU B  16     -25.747 -10.126 -24.829  1.00 57.72           C  
+ANISOU  840  CD  GLU B  16     8169   6829   6932   -188    209    453       C  
+ATOM    841  OE1 GLU B  16     -26.936  -9.937 -24.446  1.00 69.98           O  
+ANISOU  841  OE1 GLU B  16     9737   8308   8544    -25    134    464       O  
+ATOM    842  OE2 GLU B  16     -25.279  -9.506 -25.809  1.00 71.51           O  
+ANISOU  842  OE2 GLU B  16    10058   8536   8577   -278    255    541       O  
+ATOM    843  N   VAL B  17     -22.770 -13.706 -22.978  1.00 44.60           N  
+ANISOU  843  N   VAL B  17     5878   5736   5329   -281    315    156       N  
+ATOM    844  CA  VAL B  17     -22.304 -14.976 -23.436  1.00 46.75           C  
+ANISOU  844  CA  VAL B  17     6070   6143   5548   -182    330    114       C  
+ATOM    845  C   VAL B  17     -23.463 -15.746 -23.995  1.00 45.90           C  
+ANISOU  845  C   VAL B  17     6061   5952   5427    -66    243    110       C  
+ATOM    846  O   VAL B  17     -24.614 -15.485 -23.683  1.00 43.95           O  
+ANISOU  846  O   VAL B  17     5860   5598   5240    -49    164    134       O  
+ATOM    847  CB  VAL B  17     -21.605 -15.796 -22.302  1.00 47.60           C  
+ANISOU  847  CB  VAL B  17     6021   6360   5702   -132    323     46       C  
+ATOM    848  CG1 VAL B  17     -20.698 -14.897 -21.492  1.00 51.12           C  
+ANISOU  848  CG1 VAL B  17     6363   6888   6173   -288    361     47       C  
+ATOM    849  CG2 VAL B  17     -22.618 -16.422 -21.353  1.00 49.09           C  
+ANISOU  849  CG2 VAL B  17     6236   6442   5970    -53    227     18       C  
+ATOM    850  N   GLU B  18     -23.109 -16.745 -24.776  1.00 46.07           N  
+ANISOU  850  N   GLU B  18     6102   6043   5357     16    260     72       N  
+ATOM    851  CA  GLU B  18     -24.020 -17.745 -25.270  1.00 49.63           C  
+ANISOU  851  CA  GLU B  18     6660   6424   5772     95    165     41       C  
+ATOM    852  C   GLU B  18     -24.215 -18.851 -24.218  1.00 44.35           C  
+ANISOU  852  C   GLU B  18     5956   5714   5177    154    106    -15       C  
+ATOM    853  O   GLU B  18     -23.267 -19.284 -23.560  1.00 51.13           O  
+ANISOU  853  O   GLU B  18     6734   6641   6052    214    157    -53       O  
+ATOM    854  CB  GLU B  18     -23.469 -18.355 -26.555  1.00 50.78           C  
+ANISOU  854  CB  GLU B  18     6901   6634   5756    163    218      4       C  
+ATOM    855  CG  GLU B  18     -23.996 -17.686 -27.802  1.00 68.77           C  
+ANISOU  855  CG  GLU B  18     9305   8898   7926    113    200     68       C  
+ATOM    856  CD  GLU B  18     -23.835 -18.551 -29.075  1.00 88.05           C  
+ANISOU  856  CD  GLU B  18    11901  11375  10178    191    210      8       C  
+ATOM    857  OE1 GLU B  18     -22.896 -19.410 -29.126  1.00 81.31           O  
+ANISOU  857  OE1 GLU B  18    11040  10592   9262    305    301    -77       O  
+ATOM    858  OE2 GLU B  18     -24.664 -18.355 -30.020  1.00 95.29           O  
+ANISOU  858  OE2 GLU B  18    12955  12254  10995    158    122     45       O  
+ATOM    859  N   CYS B  19     -25.445 -19.302 -24.075  1.00 41.64           N  
+ANISOU  859  N   CYS B  19     5672   5279   4869    130     -6     -8       N  
+ATOM    860  CA  CYS B  19     -25.763 -20.344 -23.133  1.00 45.79           C  
+ANISOU  860  CA  CYS B  19     6202   5746   5449    144    -63    -39       C  
+ATOM    861  C   CYS B  19     -26.882 -21.159 -23.766  1.00 46.84           C  
+ANISOU  861  C   CYS B  19     6462   5798   5535     89   -183    -46       C  
+ATOM    862  O   CYS B  19     -27.984 -20.631 -23.986  1.00 45.31           O  
+ANISOU  862  O   CYS B  19     6227   5622   5365     10   -256      7       O  
+ATOM    863  CB  CYS B  19     -26.236 -19.742 -21.802  1.00 44.81           C  
+ANISOU  863  CB  CYS B  19     5955   5628   5442     93    -68      2       C  
+ATOM    864  SG  CYS B  19     -26.514 -21.029 -20.541  1.00 44.98           S  
+ANISOU  864  SG  CYS B  19     5996   5591   5503     93   -120     -8       S  
+ATOM    865  N   ASP B  20     -26.627 -22.429 -24.061  1.00 46.35           N  
+ANISOU  865  N   ASP B  20     6561   5651   5398    132   -213   -114       N  
+ATOM    866  CA  ASP B  20     -27.710 -23.303 -24.531  1.00 48.89           C  
+ANISOU  866  CA  ASP B  20     7032   5874   5668     19   -349   -130       C  
+ATOM    867  C   ASP B  20     -28.747 -23.474 -23.465  1.00 47.10           C  
+ANISOU  867  C   ASP B  20     6711   5635   5547   -116   -420    -68       C  
+ATOM    868  O   ASP B  20     -28.472 -23.214 -22.296  1.00 47.38           O  
+ANISOU  868  O   ASP B  20     6628   5699   5673    -83   -357    -35       O  
+ATOM    869  CB  ASP B  20     -27.195 -24.650 -25.016  1.00 49.61           C  
+ANISOU  869  CB  ASP B  20     7385   5821   5642     92   -366   -229       C  
+ATOM    870  CG  ASP B  20     -26.366 -24.538 -26.278  1.00 56.74           C  
+ANISOU  870  CG  ASP B  20     8391   6766   6398    224   -291   -299       C  
+ATOM    871  OD1 ASP B  20     -26.482 -23.537 -27.079  1.00 61.65           O  
+ANISOU  871  OD1 ASP B  20     8935   7508   6978    194   -270   -261       O  
+ATOM    872  OD2 ASP B  20     -25.587 -25.487 -26.498  1.00 64.45           O  
+ANISOU  872  OD2 ASP B  20     9551   7653   7283    379   -246   -389       O  
+ATOM    873  N   ASP B  21     -29.960 -23.866 -23.882  1.00 51.35           N  
+ANISOU  873  N   ASP B  21     7285   6165   6058   -283   -552    -48       N  
+ATOM    874  CA  ASP B  21     -31.093 -24.022 -22.958  1.00 47.36           C  
+ANISOU  874  CA  ASP B  21     6647   5708   5637   -444   -612     27       C  
+ATOM    875  C   ASP B  21     -30.985 -25.153 -21.926  1.00 47.41           C  
+ANISOU  875  C   ASP B  21     6767   5582   5664   -512   -610     28       C  
+ATOM    876  O   ASP B  21     -31.828 -25.247 -21.025  1.00 45.27           O  
+ANISOU  876  O   ASP B  21     6372   5373   5454   -650   -626    105       O  
+ATOM    877  CB  ASP B  21     -32.458 -24.057 -23.695  1.00 51.66           C  
+ANISOU  877  CB  ASP B  21     7134   6351   6143   -629   -764     67       C  
+ATOM    878  CG  ASP B  21     -32.657 -25.259 -24.632  1.00 61.29           C  
+ANISOU  878  CG  ASP B  21     8623   7436   7229   -779   -899     -7       C  
+ATOM    879  OD1 ASP B  21     -31.869 -26.233 -24.690  1.00 60.99           O  
+ANISOU  879  OD1 ASP B  21     8860   7188   7124   -738   -879    -93       O  
+ATOM    880  OD2 ASP B  21     -33.671 -25.198 -25.362  1.00 76.44           O  
+ANISOU  880  OD2 ASP B  21    10482   9466   9093   -935  -1042     19       O  
+ATOM    881  N   ASP B  22     -29.986 -26.019 -22.094  1.00 45.76           N  
+ANISOU  881  N   ASP B  22     6801   5197   5386   -402   -588    -47       N  
+ATOM    882  CA  ASP B  22     -29.698 -27.103 -21.184  1.00 49.94           C  
+ANISOU  882  CA  ASP B  22     7499   5560   5913   -408   -587    -39       C  
+ATOM    883  C   ASP B  22     -28.367 -26.870 -20.478  1.00 51.81           C  
+ANISOU  883  C   ASP B  22     7692   5815   6179   -156   -468    -51       C  
+ATOM    884  O   ASP B  22     -27.852 -27.770 -19.904  1.00 47.85           O  
+ANISOU  884  O   ASP B  22     7360   5171   5649    -76   -467    -52       O  
+ATOM    885  CB  ASP B  22     -29.681 -28.442 -21.915  1.00 54.09           C  
+ANISOU  885  CB  ASP B  22     8395   5838   6318   -461   -680   -116       C  
+ATOM    886  CG  ASP B  22     -28.657 -28.512 -23.084  1.00 63.91           C  
+ANISOU  886  CG  ASP B  22     9803   7027   7451   -230   -637   -239       C  
+ATOM    887  OD1 ASP B  22     -28.251 -27.429 -23.607  1.00 56.77           O  
+ANISOU  887  OD1 ASP B  22     8699   6311   6557   -118   -561   -247       O  
+ATOM    888  OD2 ASP B  22     -28.283 -29.685 -23.476  1.00 60.29           O  
+ANISOU  888  OD2 ASP B  22     9703   6324   6879   -163   -672   -325       O  
+ATOM    889  N   VAL B  23     -27.833 -25.644 -20.499  1.00 44.07           N  
+ANISOU  889  N   VAL B  23     6485   5013   5246    -45   -380    -50       N  
+ATOM    890  CA  VAL B  23     -26.611 -25.310 -19.757  1.00 47.28           C  
+ANISOU  890  CA  VAL B  23     6793   5491   5677    138   -286    -54       C  
+ATOM    891  C   VAL B  23     -26.941 -24.305 -18.663  1.00 46.19           C  
+ANISOU  891  C   VAL B  23     6430   5488   5630     71   -247      9       C  
+ATOM    892  O   VAL B  23     -27.644 -23.285 -18.881  1.00 39.27           O  
+ANISOU  892  O   VAL B  23     5415   4705   4798    -11   -238     32       O  
+ATOM    893  CB  VAL B  23     -25.523 -24.795 -20.707  1.00 50.38           C  
+ANISOU  893  CB  VAL B  23     7146   5975   6020    297   -208   -117       C  
+ATOM    894  CG1 VAL B  23     -24.342 -24.208 -19.967  1.00 46.76           C  
+ANISOU  894  CG1 VAL B  23     6507   5665   5593    423   -122   -109       C  
+ATOM    895  CG2 VAL B  23     -25.132 -25.955 -21.653  1.00 50.32           C  
+ANISOU  895  CG2 VAL B  23     7401   5823   5893    416   -228   -198       C  
+ATOM    896  N   TYR B  24     -26.524 -24.659 -17.451  1.00 44.23           N  
+ANISOU  896  N   TYR B  24     6178   5236   5390    119   -233     41       N  
+ATOM    897  CA  TYR B  24     -26.635 -23.726 -16.362  1.00 42.32           C  
+ANISOU  897  CA  TYR B  24     5758   5120   5199     83   -187     78       C  
+ATOM    898  C   TYR B  24     -25.823 -22.471 -16.675  1.00 41.44           C  
+ANISOU  898  C   TYR B  24     5501   5135   5109    147   -123     34       C  
+ATOM    899  O   TYR B  24     -24.735 -22.521 -17.198  1.00 44.52           O  
+ANISOU  899  O   TYR B  24     5887   5563   5465    255    -98     -5       O  
+ATOM    900  CB  TYR B  24     -26.178 -24.336 -15.052  1.00 44.23           C  
+ANISOU  900  CB  TYR B  24     6041   5350   5413    135   -194    117       C  
+ATOM    901  CG  TYR B  24     -27.087 -25.431 -14.497  1.00 46.96           C  
+ANISOU  901  CG  TYR B  24     6541   5568   5732     16   -244    192       C  
+ATOM    902  CD1 TYR B  24     -28.468 -25.241 -14.364  1.00 44.56           C  
+ANISOU  902  CD1 TYR B  24     6173   5299   5459   -181   -243    243       C  
+ATOM    903  CD2 TYR B  24     -26.538 -26.649 -14.079  1.00 51.68           C  
+ANISOU  903  CD2 TYR B  24     7347   6023   6266    106   -288    224       C  
+ATOM    904  CE1 TYR B  24     -29.285 -26.242 -13.871  1.00 49.24           C  
+ANISOU  904  CE1 TYR B  24     6889   5801   6017   -343   -281    325       C  
+ATOM    905  CE2 TYR B  24     -27.342 -27.663 -13.564  1.00 49.44           C  
+ANISOU  905  CE2 TYR B  24     7249   5591   5944    -40   -332    309       C  
+ATOM    906  CZ  TYR B  24     -28.691 -27.448 -13.430  1.00 51.30           C  
+ANISOU  906  CZ  TYR B  24     7400   5881   6209   -289   -324    362       C  
+ATOM    907  OH  TYR B  24     -29.429 -28.459 -12.887  1.00 52.12           O  
+ANISOU  907  OH  TYR B  24     7677   5862   6265   -476   -359    461       O  
+ATOM    908  N   VAL B  25     -26.397 -21.353 -16.303  1.00 42.58           N  
+ANISOU  908  N   VAL B  25     5534   5344   5298     74    -91     48       N  
+ATOM    909  CA  VAL B  25     -25.870 -19.977 -16.515  1.00 43.51           C  
+ANISOU  909  CA  VAL B  25     5561   5536   5435     77    -35     18       C  
+ATOM    910  C   VAL B  25     -24.479 -19.883 -15.914  1.00 45.47           C  
+ANISOU  910  C   VAL B  25     5750   5871   5655    125    -13    -11       C  
+ATOM    911  O   VAL B  25     -23.583 -19.234 -16.483  1.00 42.02           O  
+ANISOU  911  O   VAL B  25     5255   5505   5205    121     24    -37       O  
+ATOM    912  CB  VAL B  25     -27.014 -19.053 -15.974  1.00 47.07           C  
+ANISOU  912  CB  VAL B  25     5964   5993   5928     22    -14     42       C  
+ATOM    913  CG1 VAL B  25     -26.658 -17.985 -14.979  1.00 51.34           C  
+ANISOU  913  CG1 VAL B  25     6467   6569   6468     14     35     10       C  
+ATOM    914  CG2 VAL B  25     -27.951 -18.612 -17.124  1.00 50.07           C  
+ANISOU  914  CG2 VAL B  25     6347   6348   6328     11    -32     68       C  
+ATOM    915  N   LEU B  26     -24.262 -20.562 -14.781  1.00 42.91           N  
+ANISOU  915  N   LEU B  26     5432   5567   5305    159    -44      3       N  
+ATOM    916  CA  LEU B  26     -22.937 -20.508 -14.120  1.00 44.21           C  
+ANISOU  916  CA  LEU B  26     5508   5860   5427    214    -52    -16       C  
+ATOM    917  C   LEU B  26     -21.873 -21.156 -15.044  1.00 43.16           C  
+ANISOU  917  C   LEU B  26     5348   5786   5263    347    -46    -31       C  
+ATOM    918  O   LEU B  26     -20.782 -20.644 -15.204  1.00 41.77           O  
+ANISOU  918  O   LEU B  26     5032   5771   5066    355    -19    -54       O  
+ATOM    919  CB  LEU B  26     -22.973 -21.227 -12.757  1.00 43.87           C  
+ANISOU  919  CB  LEU B  26     5503   5824   5338    252   -103     21       C  
+ATOM    920  CG  LEU B  26     -21.633 -21.303 -12.009  1.00 51.12           C  
+ANISOU  920  CG  LEU B  26     6319   6909   6194    330   -147     15       C  
+ATOM    921  CD1 LEU B  26     -20.989 -19.928 -11.865  1.00 47.47           C  
+ANISOU  921  CD1 LEU B  26     5717   6587   5732    203   -127    -41       C  
+ATOM    922  CD2 LEU B  26     -21.802 -21.989 -10.650  1.00 53.81           C  
+ANISOU  922  CD2 LEU B  26     6734   7244   6465    368   -206     70       C  
+ATOM    923  N   ASP B  27     -22.221 -22.283 -15.633  1.00 44.33           N  
+ANISOU  923  N   ASP B  27     5638   5809   5394    443    -66    -21       N  
+ATOM    924  CA  ASP B  27     -21.332 -23.028 -16.510  1.00 46.01           C  
+ANISOU  924  CA  ASP B  27     5876   6049   5554    621    -47    -49       C  
+ATOM    925  C   ASP B  27     -20.956 -22.226 -17.766  1.00 49.22           C  
+ANISOU  925  C   ASP B  27     6200   6549   5953    583     32    -86       C  
+ATOM    926  O   ASP B  27     -19.802 -22.149 -18.106  1.00 50.44           O  
+ANISOU  926  O   ASP B  27     6223   6878   6063    680     86   -104       O  
+ATOM    927  CB  ASP B  27     -21.974 -24.337 -16.843  1.00 48.69           C  
+ANISOU  927  CB  ASP B  27     6461   6173   5864    695    -93    -45       C  
+ATOM    928  CG  ASP B  27     -22.021 -25.293 -15.605  1.00 61.91           C  
+ANISOU  928  CG  ASP B  27     8247   7761   7515    764   -163     12       C  
+ATOM    929  OD1 ASP B  27     -21.027 -25.395 -14.870  1.00 72.00           O  
+ANISOU  929  OD1 ASP B  27     9424   9171   8760    907   -177     30       O  
+ATOM    930  OD2 ASP B  27     -23.034 -25.974 -15.376  1.00 65.76           O  
+ANISOU  930  OD2 ASP B  27     8922   8059   8004    668   -211     51       O  
+ATOM    931  N   ALA B  28     -21.929 -21.573 -18.394  1.00 48.34           N  
+ANISOU  931  N   ALA B  28     6146   6346   5874    438     40    -82       N  
+ATOM    932  CA  ALA B  28     -21.708 -20.761 -19.556  1.00 48.04           C  
+ANISOU  932  CA  ALA B  28     6072   6368   5811    384    108    -93       C  
+ATOM    933  C   ALA B  28     -20.772 -19.641 -19.163  1.00 49.27           C  
+ANISOU  933  C   ALA B  28     6043   6698   5979    293    162    -84       C  
+ATOM    934  O   ALA B  28     -19.857 -19.319 -19.908  1.00 48.19           O  
+ANISOU  934  O   ALA B  28     5813   6707   5789    295    240    -88       O  
+ATOM    935  CB  ALA B  28     -23.039 -20.183 -20.056  1.00 52.12           C  
+ANISOU  935  CB  ALA B  28     6682   6758   6362    258     77    -68       C  
+ATOM    936  N   ALA B  29     -20.986 -19.058 -17.979  1.00 46.38           N  
+ANISOU  936  N   ALA B  29     5631   6327   5664    194    124    -74       N  
+ATOM    937  CA  ALA B  29     -20.076 -18.026 -17.489  1.00 48.20           C  
+ANISOU  937  CA  ALA B  29     5715   6706   5890     65    150    -79       C  
+ATOM    938  C   ALA B  29     -18.630 -18.516 -17.392  1.00 50.01           C  
+ANISOU  938  C   ALA B  29     5752   7184   6064    154    166    -86       C  
+ATOM    939  O   ALA B  29     -17.691 -17.860 -17.830  1.00 49.30           O  
+ANISOU  939  O   ALA B  29     5514   7276   5941     54    227    -80       O  
+ATOM    940  CB  ALA B  29     -20.518 -17.475 -16.122  1.00 48.24           C  
+ANISOU  940  CB  ALA B  29     5740   6659   5928    -29     96    -90       C  
+ATOM    941  N   GLU B  30     -18.455 -19.668 -16.787  1.00 50.74           N  
+ANISOU  941  N   GLU B  30     5842   7297   6140    344    111    -87       N  
+ATOM    942  CA  GLU B  30     -17.125 -20.211 -16.573  1.00 53.52           C  
+ANISOU  942  CA  GLU B  30     5996   7905   6434    495    109    -84       C  
+ATOM    943  C   GLU B  30     -16.487 -20.582 -17.882  1.00 55.58           C  
+ANISOU  943  C   GLU B  30     6200   8279   6639    634    213    -95       C  
+ATOM    944  O   GLU B  30     -15.310 -20.345 -18.062  1.00 53.30           O  
+ANISOU  944  O   GLU B  30     5662   8284   6302    650    267    -87       O  
+ATOM    945  CB  GLU B  30     -17.245 -21.433 -15.659  1.00 55.76           C  
+ANISOU  945  CB  GLU B  30     6364   8121   6701    708     18    -66       C  
+ATOM    946  CG  GLU B  30     -17.599 -20.966 -14.244  1.00 60.54           C  
+ANISOU  946  CG  GLU B  30     6973   8707   7322    564    -69    -52       C  
+ATOM    947  CD  GLU B  30     -17.847 -22.100 -13.266  1.00 67.44           C  
+ANISOU  947  CD  GLU B  30     7969   9491   8161    734   -157     -9       C  
+ATOM    948  OE1 GLU B  30     -18.209 -23.248 -13.665  1.00 66.53           O  
+ANISOU  948  OE1 GLU B  30     8035   9206   8037    915   -159      8       O  
+ATOM    949  OE2 GLU B  30     -17.669 -21.807 -12.069  1.00 71.86           O  
+ANISOU  949  OE2 GLU B  30     8471  10144   8689    666   -231      5       O  
+ATOM    950  N   GLU B  31     -17.279 -21.156 -18.796  1.00 55.65           N  
+ANISOU  950  N   GLU B  31     6432   8073   6637    723    241   -115       N  
+ATOM    951  CA  GLU B  31     -16.811 -21.477 -20.146  1.00 59.63           C  
+ANISOU  951  CA  GLU B  31     6942   8655   7058    853    350   -141       C  
+ATOM    952  C   GLU B  31     -16.291 -20.194 -20.865  1.00 55.72           C  
+ANISOU  952  C   GLU B  31     6283   8348   6540    632    457   -115       C  
+ATOM    953  O   GLU B  31     -15.355 -20.248 -21.599  1.00 58.65           O  
+ANISOU  953  O   GLU B  31     6505   8951   6826    714    569   -117       O  
+ATOM    954  CB  GLU B  31     -17.945 -22.145 -20.947  1.00 70.50           C  
+ANISOU  954  CB  GLU B  31     8629   9739   8417    905    329   -174       C  
+ATOM    955  CG  GLU B  31     -17.483 -23.157 -21.980  1.00 89.10           C  
+ANISOU  955  CG  GLU B  31    11093  12105  10654   1169    401   -233       C  
+ATOM    956  CD  GLU B  31     -16.540 -24.196 -21.373  1.00108.88           C  
+ANISOU  956  CD  GLU B  31    13534  14713  13120   1482    395   -248       C  
+ATOM    957  OE1 GLU B  31     -16.868 -24.715 -20.275  1.00122.63           O  
+ANISOU  957  OE1 GLU B  31    15352  16322  14918   1517    281   -221       O  
+ATOM    958  OE2 GLU B  31     -15.461 -24.475 -21.968  1.00106.25           O  
+ANISOU  958  OE2 GLU B  31    13067  14613  12689   1708    509   -276       O  
+ATOM    959  N   ALA B  32     -16.902 -19.037 -20.607  1.00 54.88           N  
+ANISOU  959  N   ALA B  32     6217   8134   6498    356    427    -85       N  
+ATOM    960  CA  ALA B  32     -16.492 -17.745 -21.179  1.00 59.73           C  
+ANISOU  960  CA  ALA B  32     6747   8857   7089    106    511    -44       C  
+ATOM    961  C   ALA B  32     -15.343 -17.016 -20.467  1.00 63.02           C  
+ANISOU  961  C   ALA B  32     6892   9546   7504    -75    520    -22       C  
+ATOM    962  O   ALA B  32     -14.998 -15.927 -20.905  1.00 63.67           O  
+ANISOU  962  O   ALA B  32     6934   9694   7561   -332    587     19       O  
+ATOM    963  CB  ALA B  32     -17.705 -16.806 -21.263  1.00 56.13           C  
+ANISOU  963  CB  ALA B  32     6511   8123   6691    -78    469    -20       C  
+ATOM    964  N   GLY B  33     -14.771 -17.574 -19.387  1.00 61.55           N  
+ANISOU  964  N   GLY B  33     6539   9514   7332     31    442    -42       N  
+ATOM    965  CA  GLY B  33     -13.600 -16.980 -18.707  1.00 63.73           C  
+ANISOU  965  CA  GLY B  33     6518  10110   7586   -146    423    -23       C  
+ATOM    966  C   GLY B  33     -13.895 -16.058 -17.525  1.00 66.50           C  
+ANISOU  966  C   GLY B  33     6927  10352   7985   -414    308    -38       C  
+ATOM    967  O   GLY B  33     -13.016 -15.338 -17.073  1.00 78.05           O  
+ANISOU  967  O   GLY B  33     8192  12044   9417   -656    282    -30       O  
+ATOM    968  N   ILE B  34     -15.129 -16.076 -17.028  1.00 67.68           N  
+ANISOU  968  N   ILE B  34     7348  10169   8195   -380    241    -66       N  
+ATOM    969  CA  ILE B  34     -15.538 -15.285 -15.866  1.00 69.74           C  
+ANISOU  969  CA  ILE B  34     7714  10302   8481   -573    147    -99       C  
+ATOM    970  C   ILE B  34     -15.518 -16.184 -14.636  1.00 64.98           C  
+ANISOU  970  C   ILE B  34     7061   9759   7866   -396     34   -118       C  
+ATOM    971  O   ILE B  34     -15.991 -17.333 -14.669  1.00 60.93           O  
+ANISOU  971  O   ILE B  34     6620   9161   7366   -127     24   -105       O  
+ATOM    972  CB  ILE B  34     -17.001 -14.806 -15.975  1.00 71.51           C  
+ANISOU  972  CB  ILE B  34     8249  10155   8763   -593    156   -112       C  
+ATOM    973  CG1 ILE B  34     -17.270 -14.074 -17.281  1.00 80.00           C  
+ANISOU  973  CG1 ILE B  34     9439  11114   9843   -696    254    -73       C  
+ATOM    974  CG2 ILE B  34     -17.370 -13.876 -14.827  1.00 74.50           C  
+ANISOU  974  CG2 ILE B  34     8756  10405   9144   -770     88   -161       C  
+ATOM    975  CD1 ILE B  34     -18.763 -13.913 -17.553  1.00 74.26           C  
+ANISOU  975  CD1 ILE B  34     8970  10076   9167   -610    251    -68       C  
+ATOM    976  N   ASP B  35     -15.021 -15.649 -13.537  1.00 61.68           N  
+ANISOU  976  N   ASP B  35     6562   9464   7408   -566    -59   -146       N  
+ATOM    977  CA  ASP B  35     -15.019 -16.414 -12.290  1.00 75.53           C  
+ANISOU  977  CA  ASP B  35     8296  11276   9122   -414   -177   -152       C  
+ATOM    978  C   ASP B  35     -16.098 -15.810 -11.437  1.00 67.96           C  
+ANISOU  978  C   ASP B  35     7599  10051   8171   -525   -210   -201       C  
+ATOM    979  O   ASP B  35     -16.004 -14.664 -11.028  1.00 71.36           O  
+ANISOU  979  O   ASP B  35     8091  10448   8572   -786   -232   -255       O  
+ATOM    980  CB  ASP B  35     -13.639 -16.434 -11.587  1.00 82.96           C  
+ANISOU  980  CB  ASP B  35     8931  12609   9978   -475   -282   -144       C  
+ATOM    981  CG  ASP B  35     -12.583 -17.205 -12.400  1.00 94.51           C  
+ANISOU  981  CG  ASP B  35    10096  14387  11425   -270   -232    -89       C  
+ATOM    982  OD1 ASP B  35     -12.806 -18.403 -12.759  1.00 96.53           O  
+ANISOU  982  OD1 ASP B  35    10403  14579  11694     79   -201    -57       O  
+ATOM    983  OD2 ASP B  35     -11.544 -16.586 -12.714  1.00 99.59           O  
+ANISOU  983  OD2 ASP B  35    10468  15337  12033   -469   -215    -78       O  
+ATOM    984  N   LEU B  36     -17.154 -16.583 -11.237  1.00 61.63           N  
+ANISOU  984  N   LEU B  36     6966   9051   7399   -328   -200   -183       N  
+ATOM    985  CA  LEU B  36     -18.261 -16.213 -10.383  1.00 56.78           C  
+ANISOU  985  CA  LEU B  36     6568   8229   6777   -371   -208   -218       C  
+ATOM    986  C   LEU B  36     -18.245 -17.129  -9.153  1.00 52.62           C  
+ANISOU  986  C   LEU B  36     6041   7780   6171   -238   -300   -192       C  
+ATOM    987  O   LEU B  36     -17.788 -18.261  -9.215  1.00 51.28           O  
+ANISOU  987  O   LEU B  36     5782   7714   5987    -45   -339   -130       O  
+ATOM    988  CB  LEU B  36     -19.602 -16.402 -11.121  1.00 60.32           C  
+ANISOU  988  CB  LEU B  36     7183   8425   7309   -276   -120   -195       C  
+ATOM    989  CG  LEU B  36     -19.852 -15.603 -12.408  1.00 57.54           C  
+ANISOU  989  CG  LEU B  36     6878   7960   7022   -360    -36   -197       C  
+ATOM    990  CD1 LEU B  36     -21.213 -16.023 -13.015  1.00 57.02           C  
+ANISOU  990  CD1 LEU B  36     6941   7702   7020   -235     11   -162       C  
+ATOM    991  CD2 LEU B  36     -19.778 -14.120 -12.106  1.00 59.01           C  
+ANISOU  991  CD2 LEU B  36     7159   8073   7187   -580    -26   -256       C  
+ATOM    992  N   PRO B  37     -18.782 -16.647  -8.045  1.00 49.77           N  
+ANISOU  992  N   PRO B  37     5814   7352   5742   -321   -326   -238       N  
+ATOM    993  CA  PRO B  37     -18.701 -17.404  -6.819  1.00 52.69           C  
+ANISOU  993  CA  PRO B  37     6200   7815   6003   -225   -415   -204       C  
+ATOM    994  C   PRO B  37     -19.633 -18.609  -6.877  1.00 53.75           C  
+ANISOU  994  C   PRO B  37     6445   7807   6169    -30   -373   -116       C  
+ATOM    995  O   PRO B  37     -20.659 -18.578  -7.575  1.00 52.82           O  
+ANISOU  995  O   PRO B  37     6420   7504   6142    -22   -275   -109       O  
+ATOM    996  CB  PRO B  37     -19.214 -16.410  -5.772  1.00 55.23           C  
+ANISOU  996  CB  PRO B  37     6682   8069   6232   -380   -415   -294       C  
+ATOM    997  CG  PRO B  37     -20.178 -15.544  -6.538  1.00 56.99           C  
+ANISOU  997  CG  PRO B  37     7035   8061   6556   -438   -287   -341       C  
+ATOM    998  CD  PRO B  37     -19.480 -15.359  -7.863  1.00 55.95           C  
+ANISOU  998  CD  PRO B  37     6768   7965   6526   -486   -269   -323       C  
+ATOM    999  N   TYR B  38     -19.264 -19.661  -6.164  1.00 51.10           N  
+ANISOU  999  N   TYR B  38     6105   7561   5748    112   -458    -39       N  
+ATOM   1000  CA  TYR B  38     -20.137 -20.809  -5.974  1.00 53.20           C  
+ANISOU 1000  CA  TYR B  38     6529   7673   6010    243   -434     55       C  
+ATOM   1001  C   TYR B  38     -19.656 -21.588  -4.747  1.00 54.18           C  
+ANISOU 1001  C   TYR B  38     6693   7909   5982    349   -548    134       C  
+ATOM   1002  O   TYR B  38     -18.582 -21.362  -4.322  1.00 49.82           O  
+ANISOU 1002  O   TYR B  38     6003   7570   5353    362   -654    116       O  
+ATOM   1003  CB  TYR B  38     -20.086 -21.734  -7.179  1.00 49.99           C  
+ANISOU 1003  CB  TYR B  38     6125   7167   5701    391   -407    104       C  
+ATOM   1004  CG  TYR B  38     -18.715 -22.275  -7.467  1.00 51.86           C  
+ANISOU 1004  CG  TYR B  38     6213   7579   5912    569   -486    127       C  
+ATOM   1005  CD1 TYR B  38     -17.850 -21.585  -8.312  1.00 55.55           C  
+ANISOU 1005  CD1 TYR B  38     6471   8204   6429    528   -464     64       C  
+ATOM   1006  CD2 TYR B  38     -18.269 -23.505  -6.904  1.00 61.75           C  
+ANISOU 1006  CD2 TYR B  38     7532   8850   7078    796   -577    225       C  
+ATOM   1007  CE1 TYR B  38     -16.594 -22.086  -8.614  1.00 58.14           C  
+ANISOU 1007  CE1 TYR B  38     6611   8752   6728    714   -515     89       C  
+ATOM   1008  CE2 TYR B  38     -16.989 -24.012  -7.186  1.00 59.46           C  
+ANISOU 1008  CE2 TYR B  38     7079   8753   6757   1025   -645    249       C  
+ATOM   1009  CZ  TYR B  38     -16.163 -23.294  -8.041  1.00 63.46           C  
+ANISOU 1009  CZ  TYR B  38     7329   9462   7319    985   -607    177       C  
+ATOM   1010  OH  TYR B  38     -14.891 -23.736  -8.343  1.00 72.30           O  
+ANISOU 1010  OH  TYR B  38     8229  10837   8402   1217   -652    201       O  
+ATOM   1011  N   SER B  39     -20.429 -22.569  -4.273  1.00 53.67           N  
+ANISOU 1011  N   SER B  39     6815   7706   5871    420   -534    237       N  
+ATOM   1012  CA  SER B  39     -19.980 -23.484  -3.243  1.00 53.24           C  
+ANISOU 1012  CA  SER B  39     6845   7718   5665    553   -645    347       C  
+ATOM   1013  C   SER B  39     -20.482 -24.911  -3.576  1.00 53.99           C  
+ANISOU 1013  C   SER B  39     7143   7585   5782    691   -631    477       C  
+ATOM   1014  O   SER B  39     -19.746 -25.715  -4.122  1.00 60.00           O  
+ANISOU 1014  O   SER B  39     7905   8323   6567    901   -692    520       O  
+ATOM   1015  CB  SER B  39     -20.438 -22.974  -1.880  1.00 54.47           C  
+ANISOU 1015  CB  SER B  39     7088   7945   5660    422   -647    339       C  
+ATOM   1016  OG  SER B  39     -19.961 -23.809  -0.856  1.00 58.10           O  
+ANISOU 1016  OG  SER B  39     7640   8485   5947    547   -768    457       O  
+ATOM   1017  N   CYS B  40     -21.745 -25.188  -3.303  1.00 53.52           N  
+ANISOU 1017  N   CYS B  40     7259   7360   5714    563   -542    532       N  
+ATOM   1018  CA  CYS B  40     -22.341 -26.502  -3.494  1.00 52.54           C  
+ANISOU 1018  CA  CYS B  40     7371   6999   5591    607   -533    661       C  
+ATOM   1019  C   CYS B  40     -22.554 -26.894  -4.990  1.00 54.29           C  
+ANISOU 1019  C   CYS B  40     7615   7039   5972    636   -494    621       C  
+ATOM   1020  O   CYS B  40     -22.599 -28.079  -5.354  1.00 56.18           O  
+ANISOU 1020  O   CYS B  40     8070   7067   6206    733   -528    702       O  
+ATOM   1021  CB  CYS B  40     -23.706 -26.493  -2.758  1.00 53.96           C  
+ANISOU 1021  CB  CYS B  40     7670   7122   5708    390   -431    727       C  
+ATOM   1022  SG  CYS B  40     -25.014 -25.506  -3.570  1.00 54.05           S  
+ANISOU 1022  SG  CYS B  40     7540   7122   5874    175   -270    621       S  
+ATOM   1023  N   ARG B  41     -22.745 -25.881  -5.830  1.00 55.21           N  
+ANISOU 1023  N   ARG B  41     7548   7218   6210    539   -423    498       N  
+ATOM   1024  CA  ARG B  41     -23.100 -26.036  -7.243  1.00 57.45           C  
+ANISOU 1024  CA  ARG B  41     7842   7361   6623    525   -377    449       C  
+ATOM   1025  C   ARG B  41     -24.289 -26.944  -7.426  1.00 53.39           C  
+ANISOU 1025  C   ARG B  41     7544   6624   6118    400   -351    531       C  
+ATOM   1026  O   ARG B  41     -24.325 -27.732  -8.363  1.00 57.10           O  
+ANISOU 1026  O   ARG B  41     8153   6914   6628    449   -373    531       O  
+ATOM   1027  CB  ARG B  41     -21.926 -26.568  -8.054  1.00 57.25           C  
+ANISOU 1027  CB  ARG B  41     7805   7331   6614    765   -433    421       C  
+ATOM   1028  CG  ARG B  41     -20.730 -25.656  -8.128  1.00 63.18           C  
+ANISOU 1028  CG  ARG B  41     8288   8346   7369    845   -452    341       C  
+ATOM   1029  CD  ARG B  41     -19.678 -26.364  -8.976  1.00 72.83           C  
+ANISOU 1029  CD  ARG B  41     9491   9584   8595   1112   -481    331       C  
+ATOM   1030  NE  ARG B  41     -20.005 -26.268 -10.416  1.00 66.24           N  
+ANISOU 1030  NE  ARG B  41     8672   8642   7853   1080   -399    256       N  
+ATOM   1031  CZ  ARG B  41     -19.501 -25.337 -11.206  1.00 60.12           C  
+ANISOU 1031  CZ  ARG B  41     7679   8033   7131   1040   -342    173       C  
+ATOM   1032  NH1 ARG B  41     -18.683 -24.440 -10.693  1.00 58.08           N  
+ANISOU 1032  NH1 ARG B  41     7175   8038   6852    995   -361    150       N  
+ATOM   1033  NH2 ARG B  41     -19.820 -25.271 -12.489  1.00 59.63           N  
+ANISOU 1033  NH2 ARG B  41     7655   7877   7124   1014   -273    118       N  
+ATOM   1034  N   ALA B  42     -25.254 -26.842  -6.528  1.00 50.75           N  
+ANISOU 1034  N   ALA B  42     7240   6313   5731    225   -301    598       N  
+ATOM   1035  CA  ALA B  42     -26.322 -27.854  -6.466  1.00 55.82           C  
+ANISOU 1035  CA  ALA B  42     8087   6773   6348     63   -286    714       C  
+ATOM   1036  C   ALA B  42     -27.689 -27.296  -6.092  1.00 53.85           C  
+ANISOU 1036  C   ALA B  42     7723   6630   6105   -178   -176    738       C  
+ATOM   1037  O   ALA B  42     -28.646 -28.060  -5.838  1.00 55.78           O  
+ANISOU 1037  O   ALA B  42     8091   6791   6308   -370   -150    854       O  
+ATOM   1038  CB  ALA B  42     -25.919 -28.950  -5.457  1.00 58.15           C  
+ANISOU 1038  CB  ALA B  42     8635   6960   6496    141   -354    856       C  
+ATOM   1039  N   GLY B  43     -27.766 -25.976  -6.007  1.00 52.94           N  
+ANISOU 1039  N   GLY B  43     7380   6706   6028   -168   -108    636       N  
+ATOM   1040  CA  GLY B  43     -29.019 -25.311  -5.704  1.00 57.58           C  
+ANISOU 1040  CA  GLY B  43     7828   7426   6621   -321      9    641       C  
+ATOM   1041  C   GLY B  43     -29.393 -25.157  -4.247  1.00 56.47           C  
+ANISOU 1041  C   GLY B  43     7699   7426   6330   -371     90    705       C  
+ATOM   1042  O   GLY B  43     -30.490 -24.676  -3.919  1.00 61.40           O  
+ANISOU 1042  O   GLY B  43     8200   8190   6936   -473    214    718       O  
+ATOM   1043  N   SER B  44     -28.507 -25.552  -3.359  1.00 57.15           N  
+ANISOU 1043  N   SER B  44     7922   7502   6288   -282     25    748       N  
+ATOM   1044  CA  SER B  44     -28.864 -25.573  -1.947  1.00 60.58           C  
+ANISOU 1044  CA  SER B  44     8415   8061   6539   -340     95    828       C  
+ATOM   1045  C   SER B  44     -28.070 -24.590  -1.083  1.00 55.02           C  
+ANISOU 1045  C   SER B  44     7667   7512   5724   -218     81    721       C  
+ATOM   1046  O   SER B  44     -27.939 -24.770   0.117  1.00 56.43           O  
+ANISOU 1046  O   SER B  44     7952   7776   5712   -219     84    785       O  
+ATOM   1047  CB  SER B  44     -28.724 -26.997  -1.455  1.00 64.11           C  
+ANISOU 1047  CB  SER B  44     9118   8363   6875   -384     27   1009       C  
+ATOM   1048  OG  SER B  44     -27.369 -27.403  -1.576  1.00 72.90           O  
+ANISOU 1048  OG  SER B  44    10341   9378   7978   -177   -128    997       O  
+ATOM   1049  N   CYS B  45     -27.538 -23.526  -1.680  1.00 50.30           N  
+ANISOU 1049  N   CYS B  45     6932   6949   5228   -137     60    559       N  
+ATOM   1050  CA  CYS B  45     -26.911 -22.490  -0.877  1.00 49.85           C  
+ANISOU 1050  CA  CYS B  45     6852   7028   5060    -85     47    441       C  
+ATOM   1051  C   CYS B  45     -27.172 -21.136  -1.520  1.00 51.33           C  
+ANISOU 1051  C   CYS B  45     6911   7228   5361    -83    119    279       C  
+ATOM   1052  O   CYS B  45     -27.967 -21.005  -2.440  1.00 55.12           O  
+ANISOU 1052  O   CYS B  45     7307   7650   5984   -106    191    278       O  
+ATOM   1053  CB  CYS B  45     -25.390 -22.791  -0.653  1.00 48.86           C  
+ANISOU 1053  CB  CYS B  45     6762   6928   4874     20   -134    439       C  
+ATOM   1054  SG  CYS B  45     -24.160 -22.368  -1.931  1.00 53.35           S  
+ANISOU 1054  SG  CYS B  45     7176   7476   5616    105   -244    330       S  
+ATOM   1055  N   SER B  46     -26.488 -20.125  -1.034  1.00 50.33           N  
+ANISOU 1055  N   SER B  46     6793   7170   5158    -63     86    148       N  
+ATOM   1056  CA  SER B  46     -26.687 -18.804  -1.525  1.00 53.49           C  
+ANISOU 1056  CA  SER B  46     7146   7540   5638    -62    151      0       C  
+ATOM   1057  C   SER B  46     -25.436 -18.250  -2.234  1.00 51.56           C  
+ANISOU 1057  C   SER B  46     6851   7262   5478    -77     28    -89       C  
+ATOM   1058  O   SER B  46     -25.397 -17.049  -2.580  1.00 51.57           O  
+ANISOU 1058  O   SER B  46     6861   7210   5522   -105     60   -215       O  
+ATOM   1059  CB  SER B  46     -27.077 -17.908  -0.321  1.00 54.94           C  
+ANISOU 1059  CB  SER B  46     7434   7804   5634    -60    242   -100       C  
+ATOM   1060  OG  SER B  46     -25.958 -17.693   0.515  1.00 59.45           O  
+ANISOU 1060  OG  SER B  46     8091   8449   6049    -97    114   -164       O  
+ATOM   1061  N   SER B  47     -24.417 -19.082  -2.465  1.00 50.22           N  
+ANISOU 1061  N   SER B  47     6631   7124   5322    -52   -104    -22       N  
+ATOM   1062  CA  SER B  47     -23.104 -18.545  -2.910  1.00 53.80           C  
+ANISOU 1062  CA  SER B  47     6993   7634   5811    -82   -216   -100       C  
+ATOM   1063  C   SER B  47     -23.102 -17.974  -4.299  1.00 49.57           C  
+ANISOU 1063  C   SER B  47     6378   7002   5454   -106   -173   -147       C  
+ATOM   1064  O   SER B  47     -22.409 -17.004  -4.556  1.00 47.90           O  
+ANISOU 1064  O   SER B  47     6131   6812   5254   -198   -204   -242       O  
+ATOM   1065  CB  SER B  47     -21.985 -19.587  -2.863  1.00 55.00           C  
+ANISOU 1065  CB  SER B  47     7072   7893   5931      4   -358    -10       C  
+ATOM   1066  OG  SER B  47     -21.799 -19.986  -1.539  1.00 67.17           O  
+ANISOU 1066  OG  SER B  47     8697   9542   7281     24   -428     35       O  
+ATOM   1067  N   CYS B  48     -23.836 -18.597  -5.210  1.00 47.83           N  
+ANISOU 1067  N   CYS B  48     6142   6677   5355    -48   -112    -75       N  
+ATOM   1068  CA  CYS B  48     -23.825 -18.180  -6.626  1.00 44.63           C  
+ANISOU 1068  CA  CYS B  48     5671   6187   5098    -57    -82   -102       C  
+ATOM   1069  C   CYS B  48     -25.021 -17.249  -6.959  1.00 50.82           C  
+ANISOU 1069  C   CYS B  48     6502   6866   5939    -77     30   -146       C  
+ATOM   1070  O   CYS B  48     -25.428 -17.128  -8.127  1.00 51.63           O  
+ANISOU 1070  O   CYS B  48     6573   6888   6154    -61     61   -130       O  
+ATOM   1071  CB  CYS B  48     -23.944 -19.426  -7.480  1.00 41.68           C  
+ANISOU 1071  CB  CYS B  48     5279   5758   4800     24   -100     -7       C  
+ATOM   1072  SG  CYS B  48     -25.514 -20.250  -7.174  1.00 47.58           S  
+ANISOU 1072  SG  CYS B  48     6110   6423   5543     17    -32     87       S  
+ATOM   1073  N   ALA B  49     -25.604 -16.621  -5.943  1.00 49.42           N  
+ANISOU 1073  N   ALA B  49     6406   6702   5668    -83     90   -197       N  
+ATOM   1074  CA  ALA B  49     -26.803 -15.852  -6.145  1.00 47.37           C  
+ANISOU 1074  CA  ALA B  49     6180   6371   5446    -34    205   -227       C  
+ATOM   1075  C   ALA B  49     -26.501 -14.656  -7.025  1.00 46.14           C  
+ANISOU 1075  C   ALA B  49     6068   6096   5364    -55    208   -304       C  
+ATOM   1076  O   ALA B  49     -25.486 -13.952  -6.837  1.00 47.19           O  
+ANISOU 1076  O   ALA B  49     6266   6210   5452   -154    153   -386       O  
+ATOM   1077  CB  ALA B  49     -27.369 -15.388  -4.811  1.00 48.90           C  
+ANISOU 1077  CB  ALA B  49     6469   6616   5493     -1    286   -284       C  
+ATOM   1078  N   GLY B  50     -27.414 -14.412  -7.959  1.00 46.35           N  
+ANISOU 1078  N   GLY B  50     6066   6052   5491     20    266   -269       N  
+ATOM   1079  CA  GLY B  50     -27.486 -13.112  -8.644  1.00 47.72           C  
+ANISOU 1079  CA  GLY B  50     6340   6080   5709     42    296   -328       C  
+ATOM   1080  C   GLY B  50     -28.933 -12.665  -8.683  1.00 52.51           C  
+ANISOU 1080  C   GLY B  50     6951   6666   6333    212    397   -314       C  
+ATOM   1081  O   GLY B  50     -29.796 -13.183  -7.949  1.00 51.98           O  
+ANISOU 1081  O   GLY B  50     6805   6723   6220    284    461   -282       O  
+ATOM   1082  N   LYS B  51     -29.194 -11.690  -9.542  1.00 56.73           N  
+ANISOU 1082  N   LYS B  51     7570   7061   6924    284    413   -325       N  
+ATOM   1083  CA  LYS B  51     -30.512 -11.104  -9.670  1.00 58.00           C  
+ANISOU 1083  CA  LYS B  51     7730   7209   7099    498    500   -309       C  
+ATOM   1084  C   LYS B  51     -30.801 -10.888 -11.160  1.00 51.95           C  
+ANISOU 1084  C   LYS B  51     6929   6374   6435    547    454   -226       C  
+ATOM   1085  O   LYS B  51     -30.004 -10.302 -11.879  1.00 52.34           O  
+ANISOU 1085  O   LYS B  51     7113   6268   6503    462    408   -238       O  
+ATOM   1086  CB  LYS B  51     -30.496  -9.813  -8.890  1.00 62.10           C  
+ANISOU 1086  CB  LYS B  51     8498   7573   7524    591    570   -437       C  
+ATOM   1087  CG  LYS B  51     -31.824  -9.123  -8.706  1.00 71.92           C  
+ANISOU 1087  CG  LYS B  51     9764   8816   8744    885    686   -447       C  
+ATOM   1088  CD  LYS B  51     -31.618  -8.015  -7.667  1.00 84.43           C  
+ANISOU 1088  CD  LYS B  51    11656  10230  10191    959    758   -609       C  
+ATOM   1089  CE  LYS B  51     -32.841  -7.727  -6.792  1.00 94.36           C  
+ANISOU 1089  CE  LYS B  51    12897  11600  11354   1256    917   -653       C  
+ATOM   1090  NZ  LYS B  51     -33.550  -6.484  -7.218  1.00 96.90           N  
+ANISOU 1090  NZ  LYS B  51    13408  11730  11677   1566    985   -687       N  
+ATOM   1091  N   VAL B  52     -31.914 -11.423 -11.620  1.00 50.92           N  
+ANISOU 1091  N   VAL B  52     6608   6386   6353    653    460   -133       N  
+ATOM   1092  CA  VAL B  52     -32.367 -11.250 -12.978  1.00 51.68           C  
+ANISOU 1092  CA  VAL B  52     6658   6458   6518    721    403    -47       C  
+ATOM   1093  C   VAL B  52     -32.869  -9.817 -13.125  1.00 55.73           C  
+ANISOU 1093  C   VAL B  52     7340   6819   7014    951    455    -71       C  
+ATOM   1094  O   VAL B  52     -33.750  -9.385 -12.411  1.00 58.68           O  
+ANISOU 1094  O   VAL B  52     7692   7253   7351   1160    545    -97       O  
+ATOM   1095  CB  VAL B  52     -33.490 -12.237 -13.328  1.00 54.96           C  
+ANISOU 1095  CB  VAL B  52     6806   7103   6972    744    375     55       C  
+ATOM   1096  CG1 VAL B  52     -34.003 -12.011 -14.775  1.00 52.68           C  
+ANISOU 1096  CG1 VAL B  52     6472   6811   6730    819    290    142       C  
+ATOM   1097  CG2 VAL B  52     -33.016 -13.669 -13.135  1.00 50.23           C  
+ANISOU 1097  CG2 VAL B  52     6115   6592   6375    524    325     77       C  
+ATOM   1098  N   VAL B  53     -32.237  -9.071 -14.009  1.00 58.89           N  
+ANISOU 1098  N   VAL B  53     7936   7012   7426    915    408    -62       N  
+ATOM   1099  CA  VAL B  53     -32.688  -7.736 -14.395  1.00 62.70           C  
+ANISOU 1099  CA  VAL B  53     8634   7295   7891   1137    434    -54       C  
+ATOM   1100  C   VAL B  53     -33.762  -7.758 -15.502  1.00 61.16           C  
+ANISOU 1100  C   VAL B  53     8295   7206   7737   1336    378     79       C  
+ATOM   1101  O   VAL B  53     -34.656  -6.932 -15.513  1.00 69.08           O  
+ANISOU 1101  O   VAL B  53     9355   8170   8723   1636    413    102       O  
+ATOM   1102  CB  VAL B  53     -31.492  -6.995 -14.961  1.00 67.39           C  
+ANISOU 1102  CB  VAL B  53     9512   7623   8467    954    398    -72       C  
+ATOM   1103  CG1 VAL B  53     -31.901  -5.593 -15.416  1.00 75.74           C  
+ANISOU 1103  CG1 VAL B  53    10871   8410   9495   1168    416    -46       C  
+ATOM   1104  CG2 VAL B  53     -30.375  -6.982 -13.920  1.00 67.44           C  
+ANISOU 1104  CG2 VAL B  53     9626   7572   8426    722    423   -198       C  
+ATOM   1105  N   SER B  54     -33.617  -8.645 -16.484  1.00 54.51           N  
+ANISOU 1105  N   SER B  54     7294   6485   6931   1182    280    163       N  
+ATOM   1106  CA  SER B  54     -34.604  -8.785 -17.528  1.00 57.42           C  
+ANISOU 1106  CA  SER B  54     7508   6990   7316   1325    196    285       C  
+ATOM   1107  C   SER B  54     -34.480 -10.128 -18.163  1.00 49.00           C  
+ANISOU 1107  C   SER B  54     6241   6104   6271   1103    103    329       C  
+ATOM   1108  O   SER B  54     -33.420 -10.732 -18.126  1.00 48.61           O  
+ANISOU 1108  O   SER B  54     6245   6001   6221    872    101    279       O  
+ATOM   1109  CB  SER B  54     -34.457  -7.676 -18.615  1.00 67.92           C  
+ANISOU 1109  CB  SER B  54     9097   8093   8615   1441    149    357       C  
+ATOM   1110  OG  SER B  54     -33.212  -7.732 -19.310  1.00 68.84           O  
+ANISOU 1110  OG  SER B  54     9383   8061   8712   1181    119    358       O  
+ATOM   1111  N   GLY B  55     -35.568 -10.582 -18.760  1.00 49.29           N  
+ANISOU 1111  N   GLY B  55     6054   6357   6315   1182     19    420       N  
+ATOM   1112  CA  GLY B  55     -35.709 -11.925 -19.288  1.00 50.10           C  
+ANISOU 1112  CA  GLY B  55     5974   6638   6423    971    -80    453       C  
+ATOM   1113  C   GLY B  55     -36.015 -12.954 -18.190  1.00 54.88           C  
+ANISOU 1113  C   GLY B  55     6384   7412   7052    843    -31    418       C  
+ATOM   1114  O   GLY B  55     -36.357 -12.606 -17.055  1.00 51.85           O  
+ANISOU 1114  O   GLY B  55     5948   7078   6673    954     81    384       O  
+ATOM   1115  N   SER B  56     -35.910 -14.235 -18.540  1.00 52.05           N  
+ANISOU 1115  N   SER B  56     5952   7132   6691    610   -114    429       N  
+ATOM   1116  CA  SER B  56     -36.309 -15.287 -17.628  1.00 53.81           C  
+ANISOU 1116  CA  SER B  56     6017   7504   6924    461    -86    428       C  
+ATOM   1117  C   SER B  56     -35.313 -16.444 -17.548  1.00 48.18           C  
+ANISOU 1117  C   SER B  56     5432   6672   6199    230   -112    379       C  
+ATOM   1118  O   SER B  56     -34.496 -16.695 -18.438  1.00 51.01           O  
+ANISOU 1118  O   SER B  56     5943   6898   6540    166   -175    352       O  
+ATOM   1119  CB  SER B  56     -37.721 -15.783 -17.988  1.00 54.48           C  
+ANISOU 1119  CB  SER B  56     5818   7872   7006    422   -173    525       C  
+ATOM   1120  OG  SER B  56     -37.776 -16.208 -19.330  1.00 61.00           O  
+ANISOU 1120  OG  SER B  56     6673   8698   7805    316   -335    559       O  
+ATOM   1121  N   VAL B  57     -35.377 -17.125 -16.430  1.00 52.89           N  
+ANISOU 1121  N   VAL B  57     5977   7324   6794    133    -50    372       N  
+ATOM   1122  CA  VAL B  57     -34.589 -18.335 -16.192  1.00 52.55           C  
+ANISOU 1122  CA  VAL B  57     6056   7179   6732    -51    -77    346       C  
+ATOM   1123  C   VAL B  57     -35.550 -19.389 -15.674  1.00 55.14           C  
+ANISOU 1123  C   VAL B  57     6247   7658   7045   -229    -95    418       C  
+ATOM   1124  O   VAL B  57     -36.603 -19.052 -15.133  1.00 53.51           O  
+ANISOU 1124  O   VAL B  57     5830   7660   6841   -192    -35    473       O  
+ATOM   1125  CB  VAL B  57     -33.487 -18.134 -15.162  1.00 52.68           C  
+ANISOU 1125  CB  VAL B  57     6199   7082   6734     -9     13    278       C  
+ATOM   1126  CG1 VAL B  57     -32.429 -17.171 -15.703  1.00 51.20           C  
+ANISOU 1126  CG1 VAL B  57     6147   6751   6553     92     24    213       C  
+ATOM   1127  CG2 VAL B  57     -34.091 -17.723 -13.810  1.00 53.07           C  
+ANISOU 1127  CG2 VAL B  57     6147   7252   6763     53    130    286       C  
+ATOM   1128  N   ASP B  58     -35.198 -20.651 -15.913  1.00 54.40           N  
+ANISOU 1128  N   ASP B  58     6283   7459   6926   -420   -174    422       N  
+ATOM   1129  CA  ASP B  58     -35.847 -21.810 -15.319  1.00 52.99           C  
+ANISOU 1129  CA  ASP B  58     6069   7345   6718   -650   -190    493       C  
+ATOM   1130  C   ASP B  58     -34.894 -22.245 -14.184  1.00 50.66           C  
+ANISOU 1130  C   ASP B  58     5942   6913   6390   -637   -113    472       C  
+ATOM   1131  O   ASP B  58     -33.754 -22.666 -14.424  1.00 47.29           O  
+ANISOU 1131  O   ASP B  58     5729   6287   5951   -598   -150    415       O  
+ATOM   1132  CB  ASP B  58     -36.003 -22.916 -16.375  1.00 56.52           C  
+ANISOU 1132  CB  ASP B  58     6638   7699   7137   -861   -345    501       C  
+ATOM   1133  CG  ASP B  58     -36.384 -24.259 -15.771  1.00 65.37           C  
+ANISOU 1133  CG  ASP B  58     7842   8781   8214  -1139   -373    568       C  
+ATOM   1134  OD1 ASP B  58     -36.846 -24.290 -14.603  1.00 66.65           O  
+ANISOU 1134  OD1 ASP B  58     7883   9073   8365  -1194   -270    640       O  
+ATOM   1135  OD2 ASP B  58     -36.259 -25.280 -16.483  1.00 70.20           O  
+ANISOU 1135  OD2 ASP B  58     8664   9224   8785  -1310   -494    551       O  
+ATOM   1136  N   GLN B  59     -35.324 -22.089 -12.942  1.00 54.05           N  
+ANISOU 1136  N   GLN B  59     6267   7475   6792   -640     -1    517       N  
+ATOM   1137  CA  GLN B  59     -34.515 -22.555 -11.829  1.00 56.53           C  
+ANISOU 1137  CA  GLN B  59     6742   7686   7049   -640     51    516       C  
+ATOM   1138  C   GLN B  59     -35.268 -23.486 -10.889  1.00 59.14           C  
+ANISOU 1138  C   GLN B  59     7050   8107   7313   -851     94    632       C  
+ATOM   1139  O   GLN B  59     -35.188 -23.390  -9.661  1.00 65.41           O  
+ANISOU 1139  O   GLN B  59     7848   8966   8035   -822    198    658       O  
+ATOM   1140  CB  GLN B  59     -33.848 -21.372 -11.086  1.00 52.52           C  
+ANISOU 1140  CB  GLN B  59     6227   7196   6529   -422    151    436       C  
+ATOM   1141  CG  GLN B  59     -34.803 -20.396 -10.480  1.00 52.17           C  
+ANISOU 1141  CG  GLN B  59     5988   7361   6471   -326    274    442       C  
+ATOM   1142  CD  GLN B  59     -34.109 -19.292  -9.719  1.00 53.41           C  
+ANISOU 1142  CD  GLN B  59     6218   7483   6592   -139    359    343       C  
+ATOM   1143  OE1 GLN B  59     -33.067 -19.483  -9.016  1.00 53.43           O  
+ANISOU 1143  OE1 GLN B  59     6375   7387   6535   -140    352    304       O  
+ATOM   1144  NE2 GLN B  59     -34.697 -18.133  -9.795  1.00 52.42           N  
+ANISOU 1144  NE2 GLN B  59     5992   7441   6483     26    432    304       N  
+ATOM   1145  N   SER B  60     -35.956 -24.435 -11.476  1.00 61.58           N  
+ANISOU 1145  N   SER B  60     7365   8407   7623  -1092      6    703       N  
+ATOM   1146  CA  SER B  60     -36.740 -25.363 -10.706  1.00 72.30           C  
+ANISOU 1146  CA  SER B  60     8709   9847   8911  -1360     39    834       C  
+ATOM   1147  C   SER B  60     -35.825 -26.402 -10.017  1.00 74.05           C  
+ANISOU 1147  C   SER B  60     9269   9808   9057  -1412     17    868       C  
+ATOM   1148  O   SER B  60     -36.221 -27.047  -9.059  1.00 79.84           O  
+ANISOU 1148  O   SER B  60    10049  10581   9705  -1588     76    985       O  
+ATOM   1149  CB  SER B  60     -37.836 -25.999 -11.579  1.00 70.49           C  
+ANISOU 1149  CB  SER B  60     8372   9709   8700  -1655    -64    902       C  
+ATOM   1150  OG  SER B  60     -37.380 -26.382 -12.862  1.00 66.63           O  
+ANISOU 1150  OG  SER B  60     8069   9001   8245  -1672   -224    825       O  
+ATOM   1151  N   ASP B  61     -34.586 -26.497 -10.469  1.00 73.32           N  
+ANISOU 1151  N   ASP B  61     9398   9476   8984  -1231    -57    775       N  
+ATOM   1152  CA  ASP B  61     -33.572 -27.336  -9.811  1.00 80.97           C  
+ANISOU 1152  CA  ASP B  61    10666  10220   9879  -1177    -83    802       C  
+ATOM   1153  C   ASP B  61     -32.954 -26.686  -8.541  1.00 78.11           C  
+ANISOU 1153  C   ASP B  61    10262   9960   9455   -993     15    795       C  
+ATOM   1154  O   ASP B  61     -32.067 -27.263  -7.927  1.00 77.14           O  
+ANISOU 1154  O   ASP B  61    10352   9697   9259   -910    -16    823       O  
+ATOM   1155  CB  ASP B  61     -32.442 -27.683 -10.824  1.00 80.12           C  
+ANISOU 1155  CB  ASP B  61    10771   9865   9803  -1012   -194    700       C  
+ATOM   1156  CG  ASP B  61     -32.957 -28.443 -12.071  1.00 80.27           C  
+ANISOU 1156  CG  ASP B  61    10914   9741   9842  -1194   -306    687       C  
+ATOM   1157  OD1 ASP B  61     -34.142 -28.856 -12.081  1.00 92.44           O  
+ANISOU 1157  OD1 ASP B  61    12396  11355  11371  -1491   -323    772       O  
+ATOM   1158  OD2 ASP B  61     -32.182 -28.623 -13.047  1.00 76.90           O  
+ANISOU 1158  OD2 ASP B  61    10638   9149   9429  -1052   -377    588       O  
+ATOM   1159  N   GLN B  62     -33.390 -25.476  -8.178  1.00 79.30           N  
+ANISOU 1159  N   GLN B  62    10161  10347   9623   -909    123    751       N  
+ATOM   1160  CA  GLN B  62     -32.867 -24.772  -7.012  1.00 68.13           C  
+ANISOU 1160  CA  GLN B  62     8730   9027   8129   -755    210    719       C  
+ATOM   1161  C   GLN B  62     -33.726 -25.126  -5.824  1.00 70.39           C  
+ANISOU 1161  C   GLN B  62     8983   9467   8294   -906    322    843       C  
+ATOM   1162  O   GLN B  62     -34.807 -25.613  -6.012  1.00 70.22           O  
+ANISOU 1162  O   GLN B  62     8868   9532   8279  -1119    351    940       O  
+ATOM   1163  CB  GLN B  62     -32.896 -23.248  -7.236  1.00 67.38           C  
+ANISOU 1163  CB  GLN B  62     8445   9063   8093   -577    276    593       C  
+ATOM   1164  CG  GLN B  62     -34.246 -22.561  -6.972  1.00 69.55           C  
+ANISOU 1164  CG  GLN B  62     8479   9582   8363   -600    408    617       C  
+ATOM   1165  CD  GLN B  62     -34.463 -22.057  -5.534  1.00 62.14           C  
+ANISOU 1165  CD  GLN B  62     7513   8808   7289   -536    557    618       C  
+ATOM   1166  OE1 GLN B  62     -33.521 -21.731  -4.823  1.00 54.90           O  
+ANISOU 1166  OE1 GLN B  62     6736   7827   6294   -429    553    551       O  
+ATOM   1167  NE2 GLN B  62     -35.725 -21.958  -5.128  1.00 63.75           N  
+ANISOU 1167  NE2 GLN B  62     7519   9253   7449   -599    689    690       N  
+ATOM   1168  N   SER B  63     -33.270 -24.812  -4.615  1.00 70.67           N  
+ANISOU 1168  N   SER B  63     9076   9569   8205   -808    389    839       N  
+ATOM   1169  CA  SER B  63     -34.035 -25.114  -3.403  1.00 75.26           C  
+ANISOU 1169  CA  SER B  63     9643  10319   8633   -940    519    961       C  
+ATOM   1170  C   SER B  63     -33.862 -24.131  -2.248  1.00 70.31           C  
+ANISOU 1170  C   SER B  63     8977   9863   7874   -780    639    888       C  
+ATOM   1171  O   SER B  63     -34.598 -24.210  -1.268  1.00 78.27           O  
+ANISOU 1171  O   SER B  63     9939  11060   8737   -863    783    973       O  
+ATOM   1172  CB  SER B  63     -33.622 -26.492  -2.865  1.00 78.05           C  
+ANISOU 1172  CB  SER B  63    10283  10493   8878  -1077    445   1110       C  
+ATOM   1173  OG  SER B  63     -32.682 -26.352  -1.790  1.00 90.02           O  
+ANISOU 1173  OG  SER B  63    11950  12004  10249   -923    435   1099       O  
+ATOM   1174  N   PHE B  64     -32.871 -23.255  -2.316  1.00 65.41           N  
+ANISOU 1174  N   PHE B  64     8394   9180   7277   -573    583    734       N  
+ATOM   1175  CA  PHE B  64     -32.513 -22.466  -1.160  1.00 66.55           C  
+ANISOU 1175  CA  PHE B  64     8587   9435   7264   -453    656    655       C  
+ATOM   1176  C   PHE B  64     -33.316 -21.199  -1.122  1.00 68.15           C  
+ANISOU 1176  C   PHE B  64     8618   9800   7476   -340    809    543       C  
+ATOM   1177  O   PHE B  64     -33.653 -20.718  -0.042  1.00 74.02           O  
+ANISOU 1177  O   PHE B  64     9375  10700   8049   -284    945    514       O  
+ATOM   1178  CB  PHE B  64     -31.016 -22.107  -1.193  1.00 68.74           C  
+ANISOU 1178  CB  PHE B  64     8990   9582   7543   -323    508    542       C  
+ATOM   1179  CG  PHE B  64     -30.513 -21.437   0.066  1.00 66.26           C  
+ANISOU 1179  CG  PHE B  64     8771   9370   7032   -245    537    461       C  
+ATOM   1180  CD1 PHE B  64     -30.170 -22.204   1.194  1.00 72.14           C  
+ANISOU 1180  CD1 PHE B  64     9675  10158   7576   -288    506    569       C  
+ATOM   1181  CD2 PHE B  64     -30.367 -20.052   0.126  1.00 68.69           C  
+ANISOU 1181  CD2 PHE B  64     9050   9711   7337   -137    581    279       C  
+ATOM   1182  CE1 PHE B  64     -29.710 -21.588   2.347  1.00 71.63           C  
+ANISOU 1182  CE1 PHE B  64     9709  10202   7302   -228    515    487       C  
+ATOM   1183  CE2 PHE B  64     -29.898 -19.430   1.275  1.00 69.45           C  
+ANISOU 1183  CE2 PHE B  64     9273   9885   7231    -91    592    184       C  
+ATOM   1184  CZ  PHE B  64     -29.571 -20.192   2.381  1.00 72.58           C  
+ANISOU 1184  CZ  PHE B  64     9799  10357   7420   -139    555    283       C  
+ATOM   1185  N   LEU B  65     -33.564 -20.610  -2.286  1.00 63.83           N  
+ANISOU 1185  N   LEU B  65     7937   9206   7106   -274    785    473       N  
+ATOM   1186  CA  LEU B  65     -34.355 -19.387  -2.350  1.00 66.16           C  
+ANISOU 1186  CA  LEU B  65     8089   9630   7419   -115    921    375       C  
+ATOM   1187  C   LEU B  65     -35.835 -19.702  -2.078  1.00 70.47           C  
+ANISOU 1187  C   LEU B  65     8411  10440   7921   -180   1083    494       C  
+ATOM   1188  O   LEU B  65     -36.350 -20.690  -2.636  1.00 62.63           O  
+ANISOU 1188  O   LEU B  65     7317   9477   7002   -379   1035    634       O  
+ATOM   1189  CB  LEU B  65     -34.235 -18.733  -3.729  1.00 59.60           C  
+ANISOU 1189  CB  LEU B  65     7191   8674   6779    -25    837    299       C  
+ATOM   1190  CG  LEU B  65     -32.861 -18.246  -4.188  1.00 57.76           C  
+ANISOU 1190  CG  LEU B  65     7120   8221   6602     27    700    183       C  
+ATOM   1191  CD1 LEU B  65     -32.917 -17.582  -5.576  1.00 57.81           C  
+ANISOU 1191  CD1 LEU B  65     7061   8127   6775    102    646    135       C  
+ATOM   1192  CD2 LEU B  65     -32.327 -17.275  -3.169  1.00 54.39           C  
+ANISOU 1192  CD2 LEU B  65     6839   7788   6036    131    752     49       C  
+ATOM   1193  N   ASP B  66     -36.487 -18.860  -1.244  1.00 74.89           N  
+ANISOU 1193  N   ASP B  66     8903  11198   8353    -19   1274    433       N  
+ATOM   1194  CA  ASP B  66     -37.951 -18.868  -1.031  1.00 83.97           C  
+ANISOU 1194  CA  ASP B  66     9768  12675   9461    -11   1464    525       C  
+ATOM   1195  C   ASP B  66     -38.674 -18.077  -2.147  1.00 89.05           C  
+ANISOU 1195  C   ASP B  66    10182  13382  10272    163   1465    482       C  
+ATOM   1196  O   ASP B  66     -38.020 -17.512  -3.028  1.00 82.08           O  
+ANISOU 1196  O   ASP B  66     9403  12261   9520    266   1329    383       O  
+ATOM   1197  CB  ASP B  66     -38.358 -18.435   0.427  1.00 96.74           C  
+ANISOU 1197  CB  ASP B  66    11411  14515  10828    108   1694    490       C  
+ATOM   1198  CG  ASP B  66     -37.950 -16.966   0.829  1.00101.11           C  
+ANISOU 1198  CG  ASP B  66    12141  14972  11304    434   1759    259       C  
+ATOM   1199  OD1 ASP B  66     -37.822 -16.062  -0.014  1.00103.38           O  
+ANISOU 1199  OD1 ASP B  66    12437  15111  11732    617   1698    145       O  
+ATOM   1200  OD2 ASP B  66     -37.801 -16.698   2.048  1.00107.31           O  
+ANISOU 1200  OD2 ASP B  66    13086  15829  11857    502   1880    192       O  
+ATOM   1201  N   ASP B  67     -40.005 -18.040  -2.158  1.00 86.95           N  
+ANISOU 1201  N   ASP B  67     9589  13450   9995    197   1609    569       N  
+ATOM   1202  CA  ASP B  67     -40.677 -17.431  -3.312  1.00 90.53           C  
+ANISOU 1202  CA  ASP B  67     9811  13977  10608    358   1567    559       C  
+ATOM   1203  C   ASP B  67     -40.717 -15.891  -3.254  1.00 86.24           C  
+ANISOU 1203  C   ASP B  67     9333  13386  10046    784   1662    390       C  
+ATOM   1204  O   ASP B  67     -40.642 -15.233  -4.281  1.00 81.53           O  
+ANISOU 1204  O   ASP B  67     8742  12650   9585    940   1557    340       O  
+ATOM   1205  CB  ASP B  67     -42.050 -18.071  -3.552  1.00102.24           C  
+ANISOU 1205  CB  ASP B  67    10878  15858  12109    201   1632    735       C  
+ATOM   1206  CG  ASP B  67     -41.961 -19.594  -3.777  1.00110.12           C  
+ANISOU 1206  CG  ASP B  67    11890  16814  13135   -260   1502    895       C  
+ATOM   1207  OD1 ASP B  67     -41.291 -20.027  -4.737  1.00 98.84           O  
+ANISOU 1207  OD1 ASP B  67    10618  15101  11835   -390   1286    884       O  
+ATOM   1208  OD2 ASP B  67     -42.569 -20.364  -2.992  1.00123.31           O  
+ANISOU 1208  OD2 ASP B  67    13438  18728  14684   -494   1624   1033       O  
+ATOM   1209  N   GLU B  68     -40.822 -15.309  -2.066  1.00 91.45           N  
+ANISOU 1209  N   GLU B  68    10086  14137  10521    974   1859    301       N  
+ATOM   1210  CA  GLU B  68     -40.562 -13.877  -1.887  1.00 92.79           C  
+ANISOU 1210  CA  GLU B  68    10476  14135  10644   1350   1926    105       C  
+ATOM   1211  C   GLU B  68     -39.357 -13.490  -2.763  1.00 88.95           C  
+ANISOU 1211  C   GLU B  68    10277  13221  10296   1308   1700     12       C  
+ATOM   1212  O   GLU B  68     -39.451 -12.586  -3.592  1.00 86.77           O  
+ANISOU 1212  O   GLU B  68    10035  12811  10122   1529   1655    -46       O  
+ATOM   1213  CB  GLU B  68     -40.252 -13.581  -0.409  1.00103.28           C  
+ANISOU 1213  CB  GLU B  68    12042  15476  11725   1425   2090     -9       C  
+ATOM   1214  CG  GLU B  68     -39.658 -12.210  -0.110  1.00110.78           C  
+ANISOU 1214  CG  GLU B  68    13356  16139  12594   1717   2114   -242       C  
+ATOM   1215  CD  GLU B  68     -40.711 -11.114  -0.156  1.00127.27           C  
+ANISOU 1215  CD  GLU B  68    15344  18364  14648   2164   2303   -314       C  
+ATOM   1216  OE1 GLU B  68     -41.594 -11.091   0.736  1.00128.34           O  
+ANISOU 1216  OE1 GLU B  68    15322  18834  14608   2332   2547   -306       O  
+ATOM   1217  OE2 GLU B  68     -40.658 -10.276  -1.086  1.00135.31           O  
+ANISOU 1217  OE2 GLU B  68    16444  19164  15804   2367   2214   -371       O  
+ATOM   1218  N   GLN B  69     -38.246 -14.206  -2.567  1.00 72.49           N  
+ANISOU 1218  N   GLN B  69     8389  10950   8204   1029   1565     15       N  
+ATOM   1219  CA  GLN B  69     -36.950 -13.911  -3.200  1.00 73.21           C  
+ANISOU 1219  CA  GLN B  69     8740  10685   8389    958   1373    -72       C  
+ATOM   1220  C   GLN B  69     -36.830 -14.132  -4.727  1.00 65.48           C  
+ANISOU 1220  C   GLN B  69     7666   9593   7621    882   1203     -2       C  
+ATOM   1221  O   GLN B  69     -36.176 -13.383  -5.439  1.00 63.34           O  
+ANISOU 1221  O   GLN B  69     7557   9080   7428    951   1109    -83       O  
+ATOM   1222  CB  GLN B  69     -35.889 -14.744  -2.517  1.00 75.50           C  
+ANISOU 1222  CB  GLN B  69     9196  10892   8596    710   1285    -63       C  
+ATOM   1223  CG  GLN B  69     -35.602 -14.267  -1.103  1.00 78.45           C  
+ANISOU 1223  CG  GLN B  69     9777  11287   8741    786   1396   -180       C  
+ATOM   1224  CD  GLN B  69     -34.680 -15.215  -0.356  1.00 80.20           C  
+ANISOU 1224  CD  GLN B  69    10125  11490   8855    555   1301   -133       C  
+ATOM   1225  OE1 GLN B  69     -34.695 -16.439  -0.569  1.00 76.67           O  
+ANISOU 1225  OE1 GLN B  69     9569  11099   8460    360   1233     26       O  
+ATOM   1226  NE2 GLN B  69     -33.869 -14.655   0.531  1.00 79.87           N  
+ANISOU 1226  NE2 GLN B  69    10337  11359   8650    578   1283   -273       N  
+ATOM   1227  N   ILE B  70     -37.462 -15.169  -5.220  1.00 61.39           N  
+ANISOU 1227  N   ILE B  70     6901   9247   7174    715   1163    150       N  
+ATOM   1228  CA  ILE B  70     -37.598 -15.345  -6.623  1.00 62.15           C  
+ANISOU 1228  CA  ILE B  70     6889   9291   7432    668   1022    213       C  
+ATOM   1229  C   ILE B  70     -38.394 -14.186  -7.226  1.00 73.71           C  
+ANISOU 1229  C   ILE B  70     8249  10811   8945    974   1070    184       C  
+ATOM   1230  O   ILE B  70     -37.990 -13.601  -8.254  1.00 73.86           O  
+ANISOU 1230  O   ILE B  70     8375  10629   9058   1051    959    151       O  
+ATOM   1231  CB  ILE B  70     -38.275 -16.689  -6.902  1.00 63.18           C  
+ANISOU 1231  CB  ILE B  70     6790   9618   7596    406    976    375       C  
+ATOM   1232  CG1 ILE B  70     -37.310 -17.798  -6.413  1.00 66.78           C  
+ANISOU 1232  CG1 ILE B  70     7440   9927   8005    147    902    402       C  
+ATOM   1233  CG2 ILE B  70     -38.638 -16.782  -8.387  1.00 57.25           C  
+ANISOU 1233  CG2 ILE B  70     5905   8860   6984    377    830    431       C  
+ATOM   1234  CD1 ILE B  70     -37.651 -19.227  -6.791  1.00 70.61           C  
+ANISOU 1234  CD1 ILE B  70     7840  10465   8522   -155    814    547       C  
+ATOM   1235  N   GLY B  71     -39.521 -13.852  -6.588  1.00 75.76           N  
+ANISOU 1235  N   GLY B  71     8304  11355   9126   1166   1244    206       N  
+ATOM   1236  CA  GLY B  71     -40.376 -12.761  -7.036  1.00 72.95           C  
+ANISOU 1236  CA  GLY B  71     7834  11088   8796   1528   1307    188       C  
+ATOM   1237  C   GLY B  71     -39.624 -11.447  -7.147  1.00 76.38           C  
+ANISOU 1237  C   GLY B  71     8635  11163   9222   1770   1297     33       C  
+ATOM   1238  O   GLY B  71     -39.883 -10.642  -8.039  1.00 79.14           O  
+ANISOU 1238  O   GLY B  71     9008  11417   9641   1993   1244     36       O  
+ATOM   1239  N   GLU B  72     -38.660 -11.230  -6.267  1.00 77.34           N  
+ANISOU 1239  N   GLU B  72     9065  11073   9246   1700   1332    -94       N  
+ATOM   1240  CA  GLU B  72     -37.776 -10.071  -6.409  1.00 82.72           C  
+ANISOU 1240  CA  GLU B  72    10132  11378   9918   1818   1290   -240       C  
+ATOM   1241  C   GLU B  72     -36.680 -10.326  -7.459  1.00 73.65           C  
+ANISOU 1241  C   GLU B  72     9106   9983   8893   1567   1086   -215       C  
+ATOM   1242  O   GLU B  72     -35.725  -9.562  -7.537  1.00 70.35           O  
+ANISOU 1242  O   GLU B  72     8999   9266   8462   1543   1035   -321       O  
+ATOM   1243  CB  GLU B  72     -37.154  -9.705  -5.053  1.00 94.77           C  
+ANISOU 1243  CB  GLU B  72    11940  12798  11270   1810   1386   -397       C  
+ATOM   1244  CG  GLU B  72     -38.118  -9.911  -3.884  1.00110.12           C  
+ANISOU 1244  CG  GLU B  72    13720  15060  13058   1957   1598   -397       C  
+ATOM   1245  CD  GLU B  72     -37.932  -8.936  -2.746  1.00125.11           C  
+ANISOU 1245  CD  GLU B  72    15946  16831  14757   2160   1737   -591       C  
+ATOM   1246  OE1 GLU B  72     -38.918  -8.231  -2.409  1.00134.61           O  
+ANISOU 1246  OE1 GLU B  72    17112  18158  15874   2531   1921   -638       O  
+ATOM   1247  OE2 GLU B  72     -36.813  -8.896  -2.185  1.00125.40           O  
+ANISOU 1247  OE2 GLU B  72    16271  16663  14709   1956   1661   -698       O  
+ATOM   1248  N   GLY B  73     -36.799 -11.410  -8.233  1.00 65.49           N  
+ANISOU 1248  N   GLY B  73     7839   9083   7962   1362    977    -81       N  
+ATOM   1249  CA  GLY B  73     -35.815 -11.747  -9.280  1.00 62.64           C  
+ANISOU 1249  CA  GLY B  73     7570   8528   7700   1152    805    -56       C  
+ATOM   1250  C   GLY B  73     -34.511 -12.432  -8.913  1.00 64.69           C  
+ANISOU 1250  C   GLY B  73     7964   8676   7938    882    732    -95       C  
+ATOM   1251  O   GLY B  73     -33.621 -12.552  -9.759  1.00 61.13           O  
+ANISOU 1251  O   GLY B  73     7598   8075   7554    753    615    -91       O  
+ATOM   1252  N   PHE B  74     -34.380 -12.937  -7.687  1.00 63.75           N  
+ANISOU 1252  N   PHE B  74     7851   8656   7716    805    798   -121       N  
+ATOM   1253  CA  PHE B  74     -33.146 -13.627  -7.305  1.00 53.01           C  
+ANISOU 1253  CA  PHE B  74     6598   7217   6322    587    711   -142       C  
+ATOM   1254  C   PHE B  74     -32.988 -14.972  -7.975  1.00 47.15           C  
+ANISOU 1254  C   PHE B  74     5729   6525   5658    405    605    -25       C  
+ATOM   1255  O   PHE B  74     -33.925 -15.691  -8.210  1.00 47.19           O  
+ANISOU 1255  O   PHE B  74     5555   6679   5693    369    619     77       O  
+ATOM   1256  CB  PHE B  74     -33.033 -13.757  -5.799  1.00 57.58           C  
+ANISOU 1256  CB  PHE B  74     7252   7883   6742    571    795   -193       C  
+ATOM   1257  CG  PHE B  74     -32.721 -12.455  -5.116  1.00 62.39           C  
+ANISOU 1257  CG  PHE B  74     8094   8364   7244    697    859   -353       C  
+ATOM   1258  CD1 PHE B  74     -31.451 -11.886  -5.228  1.00 64.10           C  
+ANISOU 1258  CD1 PHE B  74     8521   8379   7455    591    755   -452       C  
+ATOM   1259  CD2 PHE B  74     -33.694 -11.780  -4.360  1.00 68.55           C  
+ANISOU 1259  CD2 PHE B  74     8895   9233   7917    917   1026   -411       C  
+ATOM   1260  CE1 PHE B  74     -31.155 -10.674  -4.606  1.00 68.98           C  
+ANISOU 1260  CE1 PHE B  74     9402   8846   7962    659    796   -610       C  
+ATOM   1261  CE2 PHE B  74     -33.400 -10.578  -3.721  1.00 69.76           C  
+ANISOU 1261  CE2 PHE B  74     9331   9224   7950   1040   1082   -581       C  
+ATOM   1262  CZ  PHE B  74     -32.132 -10.015  -3.852  1.00 73.33           C  
+ANISOU 1262  CZ  PHE B  74    10030   9434   8398    891    957   -683       C  
+ATOM   1263  N   VAL B  75     -31.754 -15.313  -8.293  1.00 47.62           N  
+ANISOU 1263  N   VAL B  75     5890   6460   5740    284    497    -46       N  
+ATOM   1264  CA  VAL B  75     -31.467 -16.527  -9.040  1.00 46.67           C  
+ANISOU 1264  CA  VAL B  75     5712   6336   5685    155    395     39       C  
+ATOM   1265  C   VAL B  75     -30.245 -17.245  -8.471  1.00 44.46           C  
+ANISOU 1265  C   VAL B  75     5526   6018   5347     63    327     28       C  
+ATOM   1266  O   VAL B  75     -29.283 -16.611  -8.088  1.00 45.81           O  
+ANISOU 1266  O   VAL B  75     5789   6141   5474     68    309    -55       O  
+ATOM   1267  CB  VAL B  75     -31.345 -16.225 -10.573  1.00 48.40           C  
+ANISOU 1267  CB  VAL B  75     5921   6459   6009    172    323     45       C  
+ATOM   1268  CG1 VAL B  75     -30.229 -15.219 -10.891  1.00 49.82           C  
+ANISOU 1268  CG1 VAL B  75     6236   6497   6194    190    302    -42       C  
+ATOM   1269  CG2 VAL B  75     -31.192 -17.513 -11.372  1.00 52.32           C  
+ANISOU 1269  CG2 VAL B  75     6386   6945   6547     57    228    117       C  
+ATOM   1270  N   LEU B  76     -30.313 -18.572  -8.356  1.00 47.02           N  
+ANISOU 1270  N   LEU B  76     5836   6371   5658    -24    284    120       N  
+ATOM   1271  CA  LEU B  76     -29.121 -19.328  -8.132  1.00 46.01           C  
+ANISOU 1271  CA  LEU B  76     5797   6192   5492    -56    197    127       C  
+ATOM   1272  C   LEU B  76     -28.512 -19.742  -9.476  1.00 40.71           C  
+ANISOU 1272  C   LEU B  76     5131   5424   4910    -54    113    128       C  
+ATOM   1273  O   LEU B  76     -29.048 -20.600 -10.147  1.00 46.45           O  
+ANISOU 1273  O   LEU B  76     5862   6109   5676   -101     81    191       O  
+ATOM   1274  CB  LEU B  76     -29.384 -20.509  -7.221  1.00 47.02           C  
+ANISOU 1274  CB  LEU B  76     5975   6357   5531   -120    195    227       C  
+ATOM   1275  CG  LEU B  76     -30.003 -20.207  -5.836  1.00 48.21           C  
+ANISOU 1275  CG  LEU B  76     6126   6635   5556   -128    299    239       C  
+ATOM   1276  CD1 LEU B  76     -30.102 -21.517  -5.059  1.00 49.81           C  
+ANISOU 1276  CD1 LEU B  76     6416   6849   5659   -217    281    368       C  
+ATOM   1277  CD2 LEU B  76     -29.254 -19.158  -5.030  1.00 49.77           C  
+ANISOU 1277  CD2 LEU B  76     6382   6864   5663    -57    312    121       C  
+ATOM   1278  N   THR B  77     -27.369 -19.136  -9.812  1.00 41.43           N  
+ANISOU 1278  N   THR B  77     5232   5494   5012    -17     79     55       N  
+ATOM   1279  CA  THR B  77     -26.727 -19.284 -11.128  1.00 40.67           C  
+ANISOU 1279  CA  THR B  77     5128   5343   4981      1     33     43       C  
+ATOM   1280  C   THR B  77     -26.271 -20.689 -11.435  1.00 44.46           C  
+ANISOU 1280  C   THR B  77     5663   5781   5447     32    -31     92       C  
+ATOM   1281  O   THR B  77     -26.273 -21.097 -12.621  1.00 45.14           O  
+ANISOU 1281  O   THR B  77     5776   5802   5573     48    -54     93       O  
+ATOM   1282  CB  THR B  77     -25.599 -18.252 -11.327  1.00 45.56           C  
+ANISOU 1282  CB  THR B  77     5722   5989   5598     -3     32    -32       C  
+ATOM   1283  OG1 THR B  77     -24.616 -18.284 -10.248  1.00 43.74           O  
+ANISOU 1283  OG1 THR B  77     5481   5849   5287    -11     -5    -59       O  
+ATOM   1284  CG2 THR B  77     -26.220 -16.863 -11.400  1.00 41.29           C  
+ANISOU 1284  CG2 THR B  77     5203   5403   5080    -24     94    -78       C  
+ATOM   1285  N   CYS B  78     -25.967 -21.485 -10.393  1.00 45.27           N  
+ANISOU 1285  N   CYS B  78     5822   5901   5476     54    -62    136       N  
+ATOM   1286  CA  CYS B  78     -25.538 -22.878 -10.640  1.00 40.58           C  
+ANISOU 1286  CA  CYS B  78     5341   5220   4856    123   -127    189       C  
+ATOM   1287  C   CYS B  78     -26.708 -23.807 -10.971  1.00 41.12           C  
+ANISOU 1287  C   CYS B  78     5522   5162   4939     26   -135    258       C  
+ATOM   1288  O   CYS B  78     -26.504 -24.884 -11.423  1.00 43.05           O  
+ANISOU 1288  O   CYS B  78     5913   5275   5166     63   -188    285       O  
+ATOM   1289  CB  CYS B  78     -24.806 -23.414  -9.415  1.00 46.97           C  
+ANISOU 1289  CB  CYS B  78     6199   6078   5566    196   -175    233       C  
+ATOM   1290  SG  CYS B  78     -25.953 -23.849  -8.071  1.00 49.40           S  
+ANISOU 1290  SG  CYS B  78     6603   6375   5792     77   -143    334       S  
+ATOM   1291  N   ALA B  79     -27.946 -23.393 -10.780  1.00 44.78           N  
+ANISOU 1291  N   ALA B  79     5918   5669   5425   -101    -84    285       N  
+ATOM   1292  CA  ALA B  79     -29.075 -24.281 -11.005  1.00 47.60           C  
+ANISOU 1292  CA  ALA B  79     6343   5956   5784   -250   -101    364       C  
+ATOM   1293  C   ALA B  79     -30.185 -23.636 -11.871  1.00 49.22           C  
+ANISOU 1293  C   ALA B  79     6406   6233   6061   -337    -80    350       C  
+ATOM   1294  O   ALA B  79     -31.381 -24.008 -11.779  1.00 50.46           O  
+ANISOU 1294  O   ALA B  79     6514   6441   6216   -497    -73    423       O  
+ATOM   1295  CB  ALA B  79     -29.642 -24.689  -9.663  1.00 51.35           C  
+ANISOU 1295  CB  ALA B  79     6846   6483   6181   -345    -59    459       C  
+ATOM   1296  N   ALA B  80     -29.766 -22.691 -12.710  1.00 47.73           N  
+ANISOU 1296  N   ALA B  80     6146   6065   5924   -239    -75    270       N  
+ATOM   1297  CA  ALA B  80     -30.656 -21.820 -13.483  1.00 47.79           C  
+ANISOU 1297  CA  ALA B  80     6019   6149   5986   -255    -60    260       C  
+ATOM   1298  C   ALA B  80     -30.291 -21.968 -14.960  1.00 45.38           C  
+ANISOU 1298  C   ALA B  80     5782   5759   5701   -235   -129    220       C  
+ATOM   1299  O   ALA B  80     -29.179 -21.667 -15.325  1.00 42.72           O  
+ANISOU 1299  O   ALA B  80     5492   5381   5359   -133   -121    162       O  
+ATOM   1300  CB  ALA B  80     -30.480 -20.367 -13.042  1.00 44.94           C  
+ANISOU 1300  CB  ALA B  80     5565   5866   5641   -146     20    209       C  
+ATOM   1301  N   TYR B  81     -31.241 -22.453 -15.772  1.00 45.42           N  
+ANISOU 1301  N   TYR B  81     5782   5764   5709   -349   -198    253       N  
+ATOM   1302  CA  TYR B  81     -31.175 -22.431 -17.214  1.00 43.03           C  
+ANISOU 1302  CA  TYR B  81     5534   5415   5397   -340   -266    217       C  
+ATOM   1303  C   TYR B  81     -31.816 -21.180 -17.774  1.00 44.38           C  
+ANISOU 1303  C   TYR B  81     5553   5705   5604   -286   -255    229       C  
+ATOM   1304  O   TYR B  81     -32.869 -20.780 -17.277  1.00 44.18           O  
+ANISOU 1304  O   TYR B  81     5365   5812   5607   -312   -236    284       O  
+ATOM   1305  CB  TYR B  81     -31.969 -23.595 -17.812  1.00 42.89           C  
+ANISOU 1305  CB  TYR B  81     5610   5343   5342   -522   -376    244       C  
+ATOM   1306  CG  TYR B  81     -31.528 -24.936 -17.365  1.00 42.13           C  
+ANISOU 1306  CG  TYR B  81     5733   5076   5198   -585   -404    245       C  
+ATOM   1307  CD1 TYR B  81     -30.314 -25.445 -17.790  1.00 44.66           C  
+ANISOU 1307  CD1 TYR B  81     6255   5239   5473   -442   -407    173       C  
+ATOM   1308  CD2 TYR B  81     -32.339 -25.734 -16.540  1.00 48.75           C  
+ANISOU 1308  CD2 TYR B  81     6588   5909   6023   -781   -423    329       C  
+ATOM   1309  CE1 TYR B  81     -29.894 -26.718 -17.417  1.00 43.35           C  
+ANISOU 1309  CE1 TYR B  81     6335   4883   5252   -447   -440    177       C  
+ATOM   1310  CE2 TYR B  81     -31.956 -27.041 -16.215  1.00 49.19           C  
+ANISOU 1310  CE2 TYR B  81     6919   5750   6019   -844   -464    343       C  
+ATOM   1311  CZ  TYR B  81     -30.702 -27.516 -16.640  1.00 48.71           C  
+ANISOU 1311  CZ  TYR B  81     7087   5502   5916   -648   -475    264       C  
+ATOM   1312  OH  TYR B  81     -30.184 -28.775 -16.265  1.00 52.95           O  
+ANISOU 1312  OH  TYR B  81     7931   5800   6387   -626   -510    278       O  
+ATOM   1313  N   PRO B  82     -31.222 -20.582 -18.851  1.00 42.24           N  
+ANISOU 1313  N   PRO B  82     5338   5394   5317   -199   -264    189       N  
+ATOM   1314  CA  PRO B  82     -31.916 -19.427 -19.426  1.00 43.63           C  
+ANISOU 1314  CA  PRO B  82     5410   5655   5512   -135   -271    223       C  
+ATOM   1315  C   PRO B  82     -33.193 -19.929 -20.110  1.00 42.47           C  
+ANISOU 1315  C   PRO B  82     5186   5607   5344   -243   -390    277       C  
+ATOM   1316  O   PRO B  82     -33.182 -21.018 -20.729  1.00 43.18           O  
+ANISOU 1316  O   PRO B  82     5392   5637   5378   -371   -481    256       O  
+ATOM   1317  CB  PRO B  82     -30.922 -18.885 -20.482  1.00 44.26           C  
+ANISOU 1317  CB  PRO B  82     5608   5658   5550    -62   -258    186       C  
+ATOM   1318  CG  PRO B  82     -29.978 -19.997 -20.799  1.00 44.00           C  
+ANISOU 1318  CG  PRO B  82     5714   5541   5462    -94   -268    127       C  
+ATOM   1319  CD  PRO B  82     -30.010 -20.955 -19.610  1.00 43.22           C  
+ANISOU 1319  CD  PRO B  82     5616   5415   5388   -148   -264    126       C  
+ATOM   1320  N   THR B  83     -34.264 -19.156 -20.049  1.00 43.35           N  
+ANISOU 1320  N   THR B  83     5114   5871   5484   -188   -400    342       N  
+ATOM   1321  CA  THR B  83     -35.417 -19.426 -20.932  1.00 48.62           C  
+ANISOU 1321  CA  THR B  83     5669   6684   6117   -276   -540    401       C  
+ATOM   1322  C   THR B  83     -35.748 -18.249 -21.876  1.00 46.65           C  
+ANISOU 1322  C   THR B  83     5378   6497   5848   -103   -583    444       C  
+ATOM   1323  O   THR B  83     -36.739 -18.269 -22.580  1.00 52.22           O  
+ANISOU 1323  O   THR B  83     5955   7366   6519   -132   -710    507       O  
+ATOM   1324  CB  THR B  83     -36.648 -19.794 -20.074  1.00 51.91           C  
+ANISOU 1324  CB  THR B  83     5841   7312   6568   -382   -542    472       C  
+ATOM   1325  OG1 THR B  83     -36.869 -18.747 -19.119  1.00 51.49           O  
+ANISOU 1325  OG1 THR B  83     5651   7343   6568   -184   -407    493       O  
+ATOM   1326  CG2 THR B  83     -36.374 -21.131 -19.339  1.00 48.25           C  
+ANISOU 1326  CG2 THR B  83     5482   6756   6094   -602   -531    453       C  
+ATOM   1327  N   SER B  84     -34.922 -17.220 -21.872  1.00 49.15           N  
+ANISOU 1327  N   SER B  84     5810   6685   6177     63   -486    421       N  
+ATOM   1328  CA  SER B  84     -34.968 -16.169 -22.893  1.00 47.46           C  
+ANISOU 1328  CA  SER B  84     5660   6452   5920    210   -525    469       C  
+ATOM   1329  C   SER B  84     -33.560 -15.596 -22.849  1.00 44.74           C  
+ANISOU 1329  C   SER B  84     5522   5905   5571    249   -408    413       C  
+ATOM   1330  O   SER B  84     -32.757 -15.991 -21.985  1.00 42.92           O  
+ANISOU 1330  O   SER B  84     5325   5604   5377    186   -319    344       O  
+ATOM   1331  CB  SER B  84     -35.999 -15.072 -22.468  1.00 52.98           C  
+ANISOU 1331  CB  SER B  84     6195   7271   6664    424   -504    546       C  
+ATOM   1332  OG  SER B  84     -35.478 -14.259 -21.372  1.00 56.24           O  
+ANISOU 1332  OG  SER B  84     6673   7559   7133    543   -346    502       O  
+ATOM   1333  N   ASP B  85     -33.263 -14.621 -23.694  1.00 42.09           N  
+ANISOU 1333  N   ASP B  85     5315   5491   5184    344   -407    456       N  
+ATOM   1334  CA  ASP B  85     -32.096 -13.779 -23.472  1.00 44.39           C  
+ANISOU 1334  CA  ASP B  85     5767   5616   5481    363   -284    426       C  
+ATOM   1335  C   ASP B  85     -32.367 -13.068 -22.174  1.00 44.33           C  
+ANISOU 1335  C   ASP B  85     5710   5574   5557    461   -200    408       C  
+ATOM   1336  O   ASP B  85     -33.473 -12.654 -21.914  1.00 44.74           O  
+ANISOU 1336  O   ASP B  85     5658   5704   5635    607   -226    457       O  
+ATOM   1337  CB  ASP B  85     -31.910 -12.741 -24.580  1.00 50.33           C  
+ANISOU 1337  CB  ASP B  85     6690   6279   6153    434   -297    505       C  
+ATOM   1338  CG  ASP B  85     -31.692 -13.380 -25.963  1.00 53.63           C  
+ANISOU 1338  CG  ASP B  85     7180   6749   6447    352   -376    524       C  
+ATOM   1339  OD1 ASP B  85     -31.204 -14.529 -26.022  1.00 47.82           O  
+ANISOU 1339  OD1 ASP B  85     6425   6053   5688    233   -377    444       O  
+ATOM   1340  OD2 ASP B  85     -32.060 -12.736 -26.984  1.00 54.69           O  
+ANISOU 1340  OD2 ASP B  85     7411   6877   6491    428   -443    619       O  
+ATOM   1341  N   VAL B  86     -31.347 -12.922 -21.361  1.00 40.17           N  
+ANISOU 1341  N   VAL B  86     5253   4953   5056    392   -100    335       N  
+ATOM   1342  CA  VAL B  86     -31.545 -12.627 -20.001  1.00 40.95           C  
+ANISOU 1342  CA  VAL B  86     5304   5044   5208    443    -28    288       C  
+ATOM   1343  C   VAL B  86     -30.338 -11.876 -19.512  1.00 40.30           C  
+ANISOU 1343  C   VAL B  86     5380   4811   5117    374     55    227       C  
+ATOM   1344  O   VAL B  86     -29.225 -12.135 -19.945  1.00 43.19           O  
+ANISOU 1344  O   VAL B  86     5796   5158   5455    238     66    208       O  
+ATOM   1345  CB  VAL B  86     -31.795 -13.954 -19.226  1.00 47.21           C  
+ANISOU 1345  CB  VAL B  86     5933   5975   6029    355    -41    254       C  
+ATOM   1346  CG1 VAL B  86     -30.588 -14.864 -19.281  1.00 53.03           C  
+ANISOU 1346  CG1 VAL B  86     6718   6685   6745    209    -38    201       C  
+ATOM   1347  CG2 VAL B  86     -32.149 -13.690 -17.770  1.00 54.18           C  
+ANISOU 1347  CG2 VAL B  86     6757   6889   6939    415     40    215       C  
+ATOM   1348  N   THR B  87     -30.567 -10.923 -18.633  1.00 40.44           N  
+ANISOU 1348  N   THR B  87     5478   4736   5147    466    116    194       N  
+ATOM   1349  CA  THR B  87     -29.526 -10.137 -17.998  1.00 44.61           C  
+ANISOU 1349  CA  THR B  87     6175   5117   5656    367    180    122       C  
+ATOM   1350  C   THR B  87     -29.555 -10.459 -16.518  1.00 45.83           C  
+ANISOU 1350  C   THR B  87     6259   5336   5818    364    223     34       C  
+ATOM   1351  O   THR B  87     -30.627 -10.480 -15.920  1.00 50.57           O  
+ANISOU 1351  O   THR B  87     6782   6002   6428    521    248     34       O  
+ATOM   1352  CB  THR B  87     -29.811  -8.653 -18.240  1.00 48.52           C  
+ANISOU 1352  CB  THR B  87     6913   5392   6126    483    206    148       C  
+ATOM   1353  OG1 THR B  87     -29.766  -8.415 -19.647  1.00 50.14           O  
+ANISOU 1353  OG1 THR B  87     7196   5550   6304    478    160    251       O  
+ATOM   1354  CG2 THR B  87     -28.775  -7.748 -17.578  1.00 51.37           C  
+ANISOU 1354  CG2 THR B  87     7497   5567   6453    330    258     66       C  
+ATOM   1355  N   ILE B  88     -28.383 -10.757 -15.950  1.00 46.15           N  
+ANISOU 1355  N   ILE B  88     6302   5394   5839    188    231    -31       N  
+ATOM   1356  CA  ILE B  88     -28.248 -11.202 -14.561  1.00 48.38           C  
+ANISOU 1356  CA  ILE B  88     6524   5758   6100    163    252   -105       C  
+ATOM   1357  C   ILE B  88     -27.148 -10.356 -13.868  1.00 49.51           C  
+ANISOU 1357  C   ILE B  88     6821   5800   6187     17    267   -195       C  
+ATOM   1358  O   ILE B  88     -26.006 -10.333 -14.336  1.00 50.31           O  
+ANISOU 1358  O   ILE B  88     6922   5912   6279   -155    240   -192       O  
+ATOM   1359  CB  ILE B  88     -27.854 -12.697 -14.495  1.00 47.16           C  
+ANISOU 1359  CB  ILE B  88     6193   5770   5956     88    209    -84       C  
+ATOM   1360  CG1 ILE B  88     -29.028 -13.550 -14.899  1.00 45.49           C  
+ANISOU 1360  CG1 ILE B  88     5857   5647   5780    178    185    -12       C  
+ATOM   1361  CG2 ILE B  88     -27.449 -13.095 -13.075  1.00 50.06           C  
+ANISOU 1361  CG2 ILE B  88     6533   6211   6275     43    217   -145       C  
+ATOM   1362  CD1 ILE B  88     -28.663 -14.921 -15.331  1.00 44.53           C  
+ANISOU 1362  CD1 ILE B  88     5652   5603   5664    105    128     18       C  
+ATOM   1363  N   GLU B  89     -27.473  -9.703 -12.758  1.00 53.87           N  
+ANISOU 1363  N   GLU B  89     7496   6283   6689     72    309   -278       N  
+ATOM   1364  CA  GLU B  89     -26.437  -9.087 -11.897  1.00 55.06           C  
+ANISOU 1364  CA  GLU B  89     7788   6369   6762   -106    296   -383       C  
+ATOM   1365  C   GLU B  89     -25.875 -10.173 -10.991  1.00 50.59           C  
+ANISOU 1365  C   GLU B  89     7047   6015   6159   -183    255   -407       C  
+ATOM   1366  O   GLU B  89     -26.632 -10.952 -10.372  1.00 53.81           O  
+ANISOU 1366  O   GLU B  89     7350   6536   6558    -58    279   -389       O  
+ATOM   1367  CB  GLU B  89     -26.978  -7.962 -10.983  1.00 64.61           C  
+ANISOU 1367  CB  GLU B  89     9255   7398   7894     -8    353   -489       C  
+ATOM   1368  CG  GLU B  89     -27.906  -6.925 -11.597  1.00 75.35           C  
+ANISOU 1368  CG  GLU B  89    10817   8537   9275    195    408   -466       C  
+ATOM   1369  CD  GLU B  89     -28.548  -5.964 -10.562  1.00 82.40           C  
+ANISOU 1369  CD  GLU B  89    11968   9264  10073    373    485   -588       C  
+ATOM   1370  OE1 GLU B  89     -29.068  -6.404  -9.504  1.00 81.15           O  
+ANISOU 1370  OE1 GLU B  89    11720   9254   9857    488    538   -645       O  
+ATOM   1371  OE2 GLU B  89     -28.574  -4.751 -10.836  1.00 78.16           O  
+ANISOU 1371  OE2 GLU B  89    11754   8436   9505    415    502   -623       O  
+ATOM   1372  N   THR B  90     -24.559 -10.217 -10.878  1.00 51.58           N  
+ANISOU 1372  N   THR B  90     7135   6210   6250   -392    193   -434       N  
+ATOM   1373  CA  THR B  90     -23.886 -11.258 -10.104  1.00 50.76           C  
+ANISOU 1373  CA  THR B  90     6862   6321   6103   -439    132   -438       C  
+ATOM   1374  C   THR B  90     -23.428 -10.795  -8.698  1.00 54.51           C  
+ANISOU 1374  C   THR B  90     7442   6820   6450   -544     93   -549       C  
+ATOM   1375  O   THR B  90     -23.592  -9.656  -8.337  1.00 56.29           O  
+ANISOU 1375  O   THR B  90     7894   6876   6618   -596    118   -640       O  
+ATOM   1376  CB  THR B  90     -22.680 -11.779 -10.880  1.00 51.05           C  
+ANISOU 1376  CB  THR B  90     6727   6500   6170   -558     78   -385       C  
+ATOM   1377  OG1 THR B  90     -21.707 -10.743 -11.014  1.00 54.51           O  
+ANISOU 1377  OG1 THR B  90     7235   6905   6570   -789     55   -432       O  
+ATOM   1378  CG2 THR B  90     -23.111 -12.265 -12.264  1.00 50.66           C  
+ANISOU 1378  CG2 THR B  90     6610   6424   6212   -455    115   -293       C  
+ATOM   1379  N   HIS B  91     -22.903 -11.730  -7.904  1.00 57.41           N  
+ANISOU 1379  N   HIS B  91     7670   7387   6755   -556     26   -538       N  
+ATOM   1380  CA  HIS B  91     -22.350 -11.451  -6.562  1.00 53.16           C  
+ANISOU 1380  CA  HIS B  91     7206   6925   6066   -666    -42   -633       C  
+ATOM   1381  C   HIS B  91     -23.379 -10.865  -5.635  1.00 52.82           C  
+ANISOU 1381  C   HIS B  91     7383   6755   5930   -566     36   -720       C  
+ATOM   1382  O   HIS B  91     -23.140  -9.839  -5.055  1.00 57.23           O  
+ANISOU 1382  O   HIS B  91     8152   7207   6385   -681     21   -845       O  
+ATOM   1383  CB  HIS B  91     -21.120 -10.564  -6.646  1.00 52.81           C  
+ANISOU 1383  CB  HIS B  91     7193   6894   5976   -939   -126   -702       C  
+ATOM   1384  CG  HIS B  91     -19.981 -11.185  -7.409  1.00 56.19           C  
+ANISOU 1384  CG  HIS B  91     7349   7534   6464  -1027   -193   -617       C  
+ATOM   1385  ND1 HIS B  91     -20.029 -11.423  -8.768  1.00 58.74           N  
+ANISOU 1385  ND1 HIS B  91     7574   7829   6914   -972   -131   -528       N  
+ATOM   1386  CD2 HIS B  91     -18.759 -11.596  -7.006  1.00 57.11           C  
+ANISOU 1386  CD2 HIS B  91     7263   7919   6514  -1145   -312   -608       C  
+ATOM   1387  CE1 HIS B  91     -18.883 -11.936  -9.173  1.00 56.30           C  
+ANISOU 1387  CE1 HIS B  91     7023   7755   6612  -1044   -186   -477       C  
+ATOM   1388  NE2 HIS B  91     -18.094 -12.046  -8.123  1.00 61.01           N  
+ANISOU 1388  NE2 HIS B  91     7535   8546   7100  -1142   -299   -520       N  
+ATOM   1389  N   LYS B  92     -24.532 -11.528  -5.533  1.00 51.89           N  
+ANISOU 1389  N   LYS B  92     7218   6656   5842   -359    126   -655       N  
+ATOM   1390  CA  LYS B  92     -25.686 -11.045  -4.764  1.00 55.84           C  
+ANISOU 1390  CA  LYS B  92     7874   7083   6259   -208    243   -719       C  
+ATOM   1391  C   LYS B  92     -25.938 -11.835  -3.472  1.00 58.34           C  
+ANISOU 1391  C   LYS B  92     8162   7572   6430   -160    255   -708       C  
+ATOM   1392  O   LYS B  92     -26.959 -11.637  -2.823  1.00 58.98           O  
+ANISOU 1392  O   LYS B  92     8322   7655   6430    -18    378   -739       O  
+ATOM   1393  CB  LYS B  92     -26.964 -11.067  -5.656  1.00 58.46           C  
+ANISOU 1393  CB  LYS B  92     8144   7346   6720    -12    355   -640       C  
+ATOM   1394  CG  LYS B  92     -26.997  -9.991  -6.743  1.00 61.75           C  
+ANISOU 1394  CG  LYS B  92     8682   7550   7230     -3    369   -662       C  
+ATOM   1395  CD  LYS B  92     -26.411  -8.664  -6.237  1.00 67.88           C  
+ANISOU 1395  CD  LYS B  92     9757   8134   7899   -115    352   -813       C  
+ATOM   1396  CE  LYS B  92     -26.933  -7.436  -6.946  1.00 70.75           C  
+ANISOU 1396  CE  LYS B  92    10351   8225   8305    -10    414   -844       C  
+ATOM   1397  NZ  LYS B  92     -28.067  -6.929  -6.119  1.00 81.14           N  
+ANISOU 1397  NZ  LYS B  92    11822   9481   9525    253    540   -928       N  
+ATOM   1398  N   GLU B  93     -25.011 -12.712  -3.102  1.00 57.86           N  
+ANISOU 1398  N   GLU B  93     7989   7669   6322   -262    135   -655       N  
+ATOM   1399  CA  GLU B  93     -25.136 -13.464  -1.883  1.00 65.82           C  
+ANISOU 1399  CA  GLU B  93     9004   8833   7172   -230    128   -625       C  
+ATOM   1400  C   GLU B  93     -25.494 -12.505  -0.726  1.00 69.11           C  
+ANISOU 1400  C   GLU B  93     9655   9212   7388   -218    194   -777       C  
+ATOM   1401  O   GLU B  93     -26.466 -12.735  -0.015  1.00 63.37           O  
+ANISOU 1401  O   GLU B  93     8970   8546   6562    -93    320   -761       O  
+ATOM   1402  CB  GLU B  93     -23.834 -14.222  -1.595  1.00 65.64           C  
+ANISOU 1402  CB  GLU B  93     8876   8968   7096   -333    -46   -573       C  
+ATOM   1403  CG  GLU B  93     -23.788 -14.779  -0.175  1.00 79.13           C  
+ANISOU 1403  CG  GLU B  93    10652  10826   8585   -318    -84   -555       C  
+ATOM   1404  CD  GLU B  93     -22.987 -16.055  -0.039  1.00 76.96           C  
+ANISOU 1404  CD  GLU B  93    10243  10705   8292   -298   -222   -415       C  
+ATOM   1405  OE1 GLU B  93     -21.780 -16.023  -0.371  1.00 79.66           O  
+ANISOU 1405  OE1 GLU B  93    10469  11135   8663   -371   -367   -426       O  
+ATOM   1406  OE2 GLU B  93     -23.601 -17.080   0.373  1.00 67.00           O  
+ANISOU 1406  OE2 GLU B  93     8994   9478   6985   -203   -176   -287       O  
+ATOM   1407  N   GLU B  94     -24.725 -11.432  -0.566  1.00 69.03           N  
+ANISOU 1407  N   GLU B  94     9811   9108   7309   -360    117   -926       N  
+ATOM   1408  CA  GLU B  94     -24.928 -10.483   0.552  1.00 77.95           C  
+ANISOU 1408  CA  GLU B  94    11229  10170   8216   -366    159  -1102       C  
+ATOM   1409  C   GLU B  94     -26.428 -10.119   0.763  1.00 81.07           C  
+ANISOU 1409  C   GLU B  94    11734  10485   8584   -108    385  -1134       C  
+ATOM   1410  O   GLU B  94     -26.904 -10.013   1.908  1.00 85.54           O  
+ANISOU 1410  O   GLU B  94    12445  11120   8935    -23    471  -1211       O  
+ATOM   1411  CB  GLU B  94     -24.036  -9.252   0.313  1.00 87.19           C  
+ANISOU 1411  CB  GLU B  94    12594  11164   9368   -580     56  -1254       C  
+ATOM   1412  CG  GLU B  94     -24.432  -7.953   1.012  1.00106.45           C  
+ANISOU 1412  CG  GLU B  94    15427  13387  11633   -560    129  -1466       C  
+ATOM   1413  CD  GLU B  94     -23.735  -7.773   2.341  1.00110.68           C  
+ANISOU 1413  CD  GLU B  94    16139  14025  11889   -732      8  -1602       C  
+ATOM   1414  OE1 GLU B  94     -22.649  -8.362   2.521  1.00117.37           O  
+ANISOU 1414  OE1 GLU B  94    16802  15085  12706   -936   -180  -1543       O  
+ATOM   1415  OE2 GLU B  94     -24.273  -7.047   3.196  1.00115.53           O  
+ANISOU 1415  OE2 GLU B  94    17075  14520  12301   -644     98  -1769       O  
+ATOM   1416  N   ALA B  95     -27.170  -9.967  -0.337  1.00 78.47           N  
+ANISOU 1416  N   ALA B  95    11311  10048   8454     26    480  -1068       N  
+ATOM   1417  CA  ALA B  95     -28.574  -9.572  -0.290  1.00 77.65           C  
+ANISOU 1417  CA  ALA B  95    11250   9907   8344    296    683  -1084       C  
+ATOM   1418  C   ALA B  95     -29.499 -10.685   0.182  1.00 81.60           C  
+ANISOU 1418  C   ALA B  95    11531  10661   8809    409    797   -947       C  
+ATOM   1419  O   ALA B  95     -30.560 -10.398   0.728  1.00 86.75           O  
+ANISOU 1419  O   ALA B  95    12220  11380   9361    612    978   -982       O  
+ATOM   1420  CB  ALA B  95     -29.025  -9.052  -1.654  1.00 73.64           C  
+ANISOU 1420  CB  ALA B  95    10698   9230   8050    400    717  -1040       C  
+ATOM   1421  N   ILE B  96     -29.102 -11.941  -0.054  1.00 86.66           N  
+ANISOU 1421  N   ILE B  96    11957  11441   9527    280    698   -787       N  
+ATOM   1422  CA  ILE B  96     -29.871 -13.143   0.330  1.00 86.56           C  
+ANISOU 1422  CA  ILE B  96    11762  11641   9485    314    780   -627       C  
+ATOM   1423  C   ILE B  96     -29.477 -13.617   1.721  1.00 84.77           C  
+ANISOU 1423  C   ILE B  96    11641  11556   9011    242    759   -633       C  
+ATOM   1424  O   ILE B  96     -29.685 -14.761   2.056  1.00 83.12           O  
+ANISOU 1424  O   ILE B  96    11324  11491   8764    195    764   -478       O  
+ATOM   1425  CB  ILE B  96     -29.623 -14.323  -0.663  1.00 90.19           C  
+ANISOU 1425  CB  ILE B  96    12007  12122  10139    213    673   -451       C  
+ATOM   1426  CG1 ILE B  96     -30.006 -13.941  -2.082  1.00 90.30           C  
+ANISOU 1426  CG1 ILE B  96    11917  12018  10374    272    679   -433       C  
+ATOM   1427  CG2 ILE B  96     -30.408 -15.587  -0.291  1.00 96.15           C  
+ANISOU 1427  CG2 ILE B  96    12623  13049  10858    195    743   -278       C  
+ATOM   1428  CD1 ILE B  96     -31.453 -13.513  -2.211  1.00 91.73           C  
+ANISOU 1428  CD1 ILE B  96    12017  12259  10577    456    854   -422       C  
+ATOM   1429  N   MET B  97     -28.867 -12.760   2.519  1.00 96.04           N  
+ANISOU 1429  N   MET B  97    13305  12930  10255    212    718   -806       N  
+ATOM   1430  CA  MET B  97     -28.752 -13.030   3.959  1.00108.10           C  
+ANISOU 1430  CA  MET B  97    14968  14612  11491    186    733   -832       C  
+ATOM   1431  C   MET B  97     -29.333 -11.852   4.784  1.00112.54           C  
+ANISOU 1431  C   MET B  97    15786  15132  11839    327    893  -1037       C  
+ATOM   1432  O   MET B  97     -30.047 -12.090   5.759  1.00113.07           O  
+ANISOU 1432  O   MET B  97    15896  15370  11696    422   1051  -1023       O  
+ATOM   1433  CB  MET B  97     -27.301 -13.410   4.291  1.00103.41           C  
+ANISOU 1433  CB  MET B  97    14414  14054  10823     -6    483   -827       C  
+ATOM   1434  CG  MET B  97     -26.722 -14.327   3.206  1.00102.45           C  
+ANISOU 1434  CG  MET B  97    14057  13920  10946    -76    347   -664       C  
+ATOM   1435  SD  MET B  97     -25.246 -15.322   3.559  1.00117.34           S  
+ANISOU 1435  SD  MET B  97    15877  15943  12762   -203     84   -561       S  
+ATOM   1436  CE  MET B  97     -24.043 -14.027   3.907  1.00103.96           C  
+ANISOU 1436  CE  MET B  97    14327  14224  10946   -367    -80   -786       C  
+ATOM   1437  N   LEU B  98     -29.087 -10.604   4.344  1.00119.26           N  
+ANISOU 1437  N   LEU B  98    16822  15748  12742    354    871  -1217       N  
+ATOM   1438  CA  LEU B  98     -29.639  -9.356   4.952  1.00123.66           C  
+ANISOU 1438  CA  LEU B  98    17689  16180  13115    531   1024  -1437       C  
+ATOM   1439  C   LEU B  98     -30.866  -9.535   5.875  1.00124.53           C  
+ANISOU 1439  C   LEU B  98    17792  16500  13023    769   1286  -1430       C  
+ATOM   1440  O   LEU B  98     -32.001  -9.708   5.420  1.00113.44           O  
+ANISOU 1440  O   LEU B  98    16173  15192  11737    973   1471  -1329       O  
+ATOM   1441  CB  LEU B  98     -29.945  -8.322   3.862  1.00113.25           C  
+ANISOU 1441  CB  LEU B  98    16446  14588  11996    658   1065  -1512       C  
+TER    1442      LEU B  98                                                      
+HETATM 1443 FE1  FES A 201     -15.271 -14.172 -26.506  1.00 44.84          FE  
+ANISOU 1443 FE1  FES A 201     6513   5131   5392   -266   1479   -131      FE  
+HETATM 1444 FE2  FES A 201     -13.886 -13.246 -28.774  1.00 43.12          FE  
+ANISOU 1444 FE2  FES A 201     6178   4976   5230   -316   1145   -235      FE  
+HETATM 1445  S1  FES A 201     -13.297 -13.107 -26.664  1.00 54.52           S  
+ANISOU 1445  S1  FES A 201     7773   6436   6506    -96   1188   -102       S  
+HETATM 1446  S2  FES A 201     -15.933 -14.020 -28.613  1.00 59.15           S  
+ANISOU 1446  S2  FES A 201     8119   7001   7354   -510   1368   -300       S  
+HETATM 1447 CL    CL A 202      -7.271  -6.453 -50.333  1.00 66.75          CL  
+ANISOU 1447 CL    CL A 202     8476   9756   7127    -15    320    384      CL  
+HETATM 1448 FE1  FES B 201     -24.764 -23.326  -3.870  1.00 41.78          FE  
+ANISOU 1448 FE1  FES B 201     5702   5769   4400    126   -205    391      FE  
+HETATM 1449 FE2  FES B 201     -25.332 -22.283  -6.421  1.00 37.10          FE  
+ANISOU 1449 FE2  FES B 201     4925   5056   4113     76   -116    248      FE  
+HETATM 1450  S1  FES B 201     -23.427 -22.583  -5.443  1.00 50.74           S  
+ANISOU 1450  S1  FES B 201     6641   6936   5699    218   -266    253       S  
+HETATM 1451  S2  FES B 201     -26.631 -22.784  -4.792  1.00 52.36           S  
+ANISOU 1451  S2  FES B 201     6974   7030   5890    -29    -34    366       S  
+HETATM 1452 CL    CL B 202     -35.655 -14.036 -25.892  1.00 63.11          CL  
+ANISOU 1452 CL    CL B 202     7828   8449   7699    533   -737    677      CL  
+HETATM 1453  O   HOH A 301      -5.604   9.621 -30.726  1.00 71.67           O  
+ANISOU 1453  O   HOH A 301     9741   6570  10921   -503   1286  -1155       O  
+HETATM 1454  O   HOH A 302      -9.976  -2.053 -46.606  1.00 60.80           O  
+ANISOU 1454  O   HOH A 302     7211   8696   7192    404    154    904       O  
+HETATM 1455  O   HOH A 303     -25.242 -15.543 -37.047  1.00 85.19           O  
+ANISOU 1455  O   HOH A 303     9346  12270  10750  -2562    894  -1311       O  
+HETATM 1456  O   HOH A 304      -8.615  -1.310 -44.946  1.00 51.68           O  
+ANISOU 1456  O   HOH A 304     6188   7004   6442    421    264    856       O  
+HETATM 1457  O   HOH A 305      -6.495 -20.103 -36.227  1.00 66.33           O  
+ANISOU 1457  O   HOH A 305     9914   6783   8504   -320   2093   -239       O  
+HETATM 1458  O   HOH A 306     -15.756 -22.203 -33.220  1.00 75.41           O  
+ANISOU 1458  O   HOH A 306    10813   7895   9944  -1695   2618   -965       O  
+HETATM 1459  O   HOH A 307     -29.136  -5.336 -24.489  1.00 68.01           O  
+ANISOU 1459  O   HOH A 307     7381   9218   9240    419   2448    230       O  
+HETATM 1460  O   HOH A 308     -31.273  -2.568 -37.684  1.00 80.28           O  
+ANISOU 1460  O   HOH A 308     6166  13841  10493    472    476   1418       O  
+HETATM 1461  O   HOH A 309     -12.248  -4.951 -30.108  1.00 43.84           O  
+ANISOU 1461  O   HOH A 309     5908   5247   5501      5    448   -372       O  
+HETATM 1462  O   HOH A 310       1.254  -8.613 -35.928  1.00 55.59           O  
+ANISOU 1462  O   HOH A 310     7059   6744   7319    249    231    444       O  
+HETATM 1463  O   HOH A 311     -14.907  -5.701 -24.000  1.00 59.32           O  
+ANISOU 1463  O   HOH A 311     8290   7265   6981    168    940   -557       O  
+HETATM 1464  O   HOH A 312     -10.817  -7.018 -29.284  1.00 41.62           O  
+ANISOU 1464  O   HOH A 312     5784   4984   5045    -27    423   -341       O  
+HETATM 1465  O   HOH A 313       2.334  -6.045 -37.941  1.00 48.64           O  
+ANISOU 1465  O   HOH A 313     5952   5811   6716    139    202    488       O  
+HETATM 1466  O   HOH A 314     -15.179  -4.981 -27.668  1.00 53.48           O  
+ANISOU 1466  O   HOH A 314     7171   6503   6643     87    768   -437       O  
+HETATM 1467  O   HOH A 315      -0.012 -13.050 -38.710  1.00 53.80           O  
+ANISOU 1467  O   HOH A 315     7374   6131   6936    327    934    389       O  
+HETATM 1468  O   HOH A 316      -4.588   7.953 -34.979  1.00 55.02           O  
+ANISOU 1468  O   HOH A 316     7176   4879   8848   -164   1054   -241       O  
+HETATM 1469  O   HOH A 317      -7.401   1.030 -46.099  1.00 71.07           O  
+ANISOU 1469  O   HOH A 317     8592   9256   9152    695    478   1286       O  
+HETATM 1470  O   HOH B 301     -17.227  -5.370 -20.274  1.00 67.76           O  
+ANISOU 1470  O   HOH B 301     9358   8307   8078  -2373    512    237       O  
+HETATM 1471  O   HOH B 302     -16.773 -19.622 -11.270  1.00 63.07           O  
+ANISOU 1471  O   HOH B 302     7080   9333   7548    271   -261    -62       O  
+HETATM 1472  O   HOH B 303     -33.187 -25.431 -12.986  1.00 63.64           O  
+ANISOU 1472  O   HOH B 303     8214   8071   7895   -876   -197    526       O  
+HETATM 1473  O   HOH B 304     -33.633 -10.672 -27.113  1.00 59.09           O  
+ANISOU 1473  O   HOH B 304     8015   7375   7058    804   -526    819       O  
+HETATM 1474  O   HOH B 305     -29.987  -6.037  -4.511  1.00 66.34           O  
+ANISOU 1474  O   HOH B 305    10207   7566   7433    765    801  -1097       O  
+HETATM 1475  O   HOH B 306     -13.899 -20.518 -11.569  1.00 81.57           O  
+ANISOU 1475  O   HOH B 306     8854  12357   9778    587   -327     -7       O  
+HETATM 1476  O   HOH B 307     -22.312 -17.195  -9.418  1.00 47.15           O  
+ANISOU 1476  O   HOH B 307     5812   6486   5616   -124    -89   -163       O  
+HETATM 1477  O   HOH B 308     -34.144 -23.559 -20.590  1.00 51.64           O  
+ANISOU 1477  O   HOH B 308     6612   6627   6378   -778   -651    271       O  
+HETATM 1478  O   HOH B 309     -24.795 -27.082 -17.164  1.00 48.12           O  
+ANISOU 1478  O   HOH B 309     7064   5473   5745    466   -276     10       O  
+HETATM 1479  O   HOH B 310     -32.217 -13.054   4.356  1.00 88.41           O  
+ANISOU 1479  O   HOH B 310    12239  12424   8929    628   1337   -721       O  
+HETATM 1480  O   HOH B 311     -23.165 -14.400  -5.121  1.00 48.01           O  
+ANISOU 1480  O   HOH B 311     6333   6580   5329   -351    -63   -448       O  
+HETATM 1481  O   HOH B 312     -20.108 -19.770 -22.685  1.00 54.43           O  
+ANISOU 1481  O   HOH B 312     6815   7445   6419    397    344   -109       O  
+HETATM 1482  O   HOH B 313     -29.379 -10.101 -22.915  1.00 62.52           O  
+ANISOU 1482  O   HOH B 313     8624   7375   7757    322     17    426       O  
+HETATM 1483  O   HOH B 314     -23.815 -23.224 -24.077  1.00 46.69           O  
+ANISOU 1483  O   HOH B 314     6623   5782   5333    479     -2   -224       O  
+HETATM 1484  O   HOH B 315     -31.917  -9.402 -21.408  1.00 63.89           O  
+ANISOU 1484  O   HOH B 315     8654   7581   8038    758    -17    436       O  
+HETATM 1485  O   HOH B 316     -43.034 -22.451  -5.025  1.00 82.32           O  
+ANISOU 1485  O   HOH B 316     8131  13423   9723  -1213   1232   1263       O  
+HETATM 1486  O   HOH B 317     -13.942 -19.466 -15.267  1.00 82.69           O  
+ANISOU 1486  O   HOH B 317     8966  12435  10017    416     49    -55       O  
+HETATM 1487  O   HOH B 318     -23.364 -14.585  -8.816  1.00 50.88           O  
+ANISOU 1487  O   HOH B 318     6512   6761   6060   -288     14   -313       O  
+HETATM 1488  O   HOH B 319     -41.794 -18.985   0.026  0.50 60.91           O  
+ANISOU 1488  O   HOH B 319     5945  10874   6323     -9   2050    811       O  
+HETATM 1489  O   HOH B 320     -29.672 -24.828 -29.742  1.00 55.49           O  
+ANISOU 1489  O   HOH B 320     8618   6605   5859   -263   -781   -333       O  
+HETATM 1490  O   HOH B 321     -38.215 -20.833 -12.380  1.00 58.66           O  
+ANISOU 1490  O   HOH B 321     6197   8706   7384   -584    159    654       O  
+CONECT  301 1443                                                                
+CONECT  333 1443                                                                
+CONECT  351 1444                                                                
+CONECT  569 1444                                                                
+CONECT 1022 1448                                                                
+CONECT 1054 1448                                                                
+CONECT 1072 1449                                                                
+CONECT 1290 1449                                                                
+CONECT 1443  301  333 1445 1446                                                 
+CONECT 1444  351  569 1445 1446                                                 
+CONECT 1445 1443 1444                                                           
+CONECT 1446 1443 1444                                                           
+CONECT 1448 1022 1054 1450 1451                                                 
+CONECT 1449 1072 1290 1450 1451                                                 
+CONECT 1450 1448 1449                                                           
+CONECT 1451 1448 1449                                                           
+MASTER      410    0    4    7   14    0    6    6 1488    2   16   18          
+END                                                                             
diff --git a/test/jalview/ext/rbvi/chimera/4zho.xml.gz b/test/jalview/ext/rbvi/chimera/4zho.xml.gz
new file mode 100644 (file)
index 0000000..c7e6bd2
Binary files /dev/null and b/test/jalview/ext/rbvi/chimera/4zho.xml.gz differ
diff --git a/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java b/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java
new file mode 100644 (file)
index 0000000..c9e1cad
--- /dev/null
@@ -0,0 +1,34 @@
+package jalview.ext.rbvi.chimera;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+public class AtomSpecModelTest
+{
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(model.getAtomSpec(), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(model.getAtomSpec(), "#1:2-4.A");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A,5-7.B");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(model.getAtomSpec(), "#1:2-5.A,8.A,5-7.B");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(model.getAtomSpec(), "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(model.getAtomSpec(), "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
+  }
+
+}
index ffb886c..2c973ca 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertTrue;
-
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
+import jalview.gui.SequenceRenderer;
+import jalview.schemes.JalviewColourScheme;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
-import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.SortedMap;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -46,58 +56,10 @@ public class ChimeraCommandsTest
   }
 
   @Test(groups = { "Functional" })
-  public void testAddColourRange()
-  {
-    Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, SortedMap<Integer, Map<String, List<int[]>>>>();
-    ChimeraCommands.addColourRange(map, Color.pink, 1, 2, 4, "A");
-    ChimeraCommands.addColourRange(map, Color.pink, 1, 8, 8, "A");
-    ChimeraCommands.addColourRange(map, Color.pink, 1, 5, 7, "B");
-    ChimeraCommands.addColourRange(map, Color.red, 1, 3, 5, "A");
-    ChimeraCommands.addColourRange(map, Color.red, 0, 1, 4, "B");
-    ChimeraCommands.addColourRange(map, Color.orange, 0, 5, 9, "C");
-
-    // three colours mapped
-    assertEquals(3, map.keySet().size());
-
-    // Red has two models, Pink and Orange one each
-    assertEquals(2, map.get(Color.red).keySet().size());
-    assertEquals(1, map.get(Color.orange).keySet().size());
-    assertEquals(1, map.get(Color.pink).keySet().size());
-
-    // pink model 1 has two chains, red.0 / red.1 / orange.0 one each
-    assertEquals(2, map.get(Color.pink).get(1).keySet().size());
-    assertEquals(1, map.get(Color.red).get(0).keySet().size());
-    assertEquals(1, map.get(Color.red).get(1).keySet().size());
-    assertEquals(1, map.get(Color.orange).get(0).keySet().size());
-
-    // inspect positions
-    List<int[]> posList = map.get(Color.pink).get(1).get("A");
-    assertEquals(2, posList.size());
-    assertTrue(Arrays.equals(new int[] { 2, 4 }, posList.get(0)));
-    assertTrue(Arrays.equals(new int[] { 8, 8 }, posList.get(1)));
-
-    posList = map.get(Color.pink).get(1).get("B");
-    assertEquals(1, posList.size());
-    assertTrue(Arrays.equals(new int[] { 5, 7 }, posList.get(0)));
-
-    posList = map.get(Color.red).get(0).get("B");
-    assertEquals(1, posList.size());
-    assertTrue(Arrays.equals(new int[] { 1, 4 }, posList.get(0)));
-
-    posList = map.get(Color.red).get(1).get("A");
-    assertEquals(1, posList.size());
-    assertTrue(Arrays.equals(new int[] { 3, 5 }, posList.get(0)));
-
-    posList = map.get(Color.orange).get(0).get("C");
-    assertEquals(1, posList.size());
-    assertTrue(Arrays.equals(new int[] { 5, 9 }, posList.get(0)));
-  }
-
-  @Test(groups = { "Functional" })
   public void testBuildColourCommands()
   {
 
-    Map<Color, SortedMap<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, SortedMap<Integer, Map<String, List<int[]>>>>();
+    Map<Object, AtomSpecModel> map = new LinkedHashMap<Object, AtomSpecModel>();
     ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A");
     ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B");
     ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A");
@@ -106,13 +68,153 @@ public class ChimeraCommandsTest
     ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A");
     ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A");
     ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.red, 0, 6, 9, "A");
 
     // Colours should appear in the Chimera command in the order in which
-    // they were added; within colour, by model, by chain, and positions as
-    // added
+    // they were added; within colour, by model, by chain, ranges in start order
     String command = ChimeraCommands.buildColourCommands(map).get(0);
     assertEquals(
-            "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:8.A,3-5.A; color #ff0000 #0:3-5.A",
-            command);
+            command,
+            "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testBuildSetAttributeCommands()
+  {
+    /*
+     * make a map of { featureType, {featureValue, {residue range specification } } }
+     */
+    Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
+    Map<Object, AtomSpecModel> featureValues = new HashMap<Object, AtomSpecModel>();
+    
+    /*
+     * start with just one feature/value...
+     */
+    featuresMap.put("chain", featureValues);
+    ChimeraCommands.addColourRange(featureValues, "X", 0, 8, 20, "A");
+  
+    List<String> commands = ChimeraCommands
+            .buildSetAttributeCommands(featuresMap);
+    assertEquals(1, commands.size());
+
+    /*
+     * feature name gets a jv_ namespace prefix
+     * feature value is quoted in case it contains spaces
+     */
+    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A");
+
+    // add same feature value, overlapping range
+    ChimeraCommands.addColourRange(featureValues, "X", 0, 3, 9, "A");
+    // same feature value, contiguous range
+    ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "A");
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    assertEquals(1, commands.size());
+    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
+
+    // same feature value and model, different chain
+    ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "B");
+    // same feature value and chain, different model
+    ChimeraCommands.addColourRange(featureValues, "X", 1, 26, 30, "A");
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    assertEquals(1, commands.size());
+    assertEquals(commands.get(0),
+            "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
+
+    // same feature, different value
+    ChimeraCommands.addColourRange(featureValues, "Y", 0, 40, 50, "A");
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    assertEquals(2, commands.size());
+    // commands are ordered by feature type but not by value
+    // so use contains to test for the expected command:
+    assertTrue(commands
+            .contains("setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
+    assertTrue(commands.contains("setattr r jv_chain 'Y' #0:40-50.A"));
+
+    featuresMap.clear();
+    featureValues.clear();
+    featuresMap.put("side-chain binding!", featureValues);
+    ChimeraCommands.addColourRange(featureValues,
+            "<html>metal <a href=\"http:a.b.c/x\"> 'ion!", 0, 7, 15,
+            "A");
+    // feature names are sanitised to change non-alphanumeric to underscore
+    // feature values are sanitised to encode single quote characters
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    assertTrue(commands
+            .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
+  }
+
+  /**
+   * Tests for the method that prefixes and sanitises a feature name so it can
+   * be used as a valid, namespaced attribute name in Chimera
+   */
+  @Test(groups = { "Functional" })
+  public void testMakeAttributeName()
+  {
+    assertEquals(ChimeraCommands.makeAttributeName(null), "jv_");
+    assertEquals(ChimeraCommands.makeAttributeName(""), "jv_");
+    assertEquals(ChimeraCommands.makeAttributeName("helix"), "jv_helix");
+    assertEquals(ChimeraCommands.makeAttributeName("Hello World 24"),
+            "jv_Hello_World_24");
+    assertEquals(
+            ChimeraCommands.makeAttributeName("!this is-a_very*{odd(name"),
+            "jv__this_is_a_very__odd_name");
+    // name ending in color gets underscore appended
+    assertEquals(ChimeraCommands.makeAttributeName("helixColor"),
+            "jv_helixColor_");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetColourBySequenceCommands_hiddenColumns()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    StructureSelectionManager ssm = new StructureSelectionManager();
+
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+
+    StructureMappingcommandSet[] commands = ChimeraCommands
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+    assertEquals(1, commands.length);
+    assertEquals(1, commands[0].commands.length);
+    String theCommand = commands[0].commands[0];
+    // M colour is #82827d (see strand.html help page)
+    assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
+    // H colour is #60609f
+    assertTrue(theCommand.contains("color #60609f #0:22.A"));
+    // V colour is #ffff00
+    assertTrue(theCommand.contains("color #ffff00 #1:22.B"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B"));
+    // S and G are both coloured #4949b6
+    assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
   }
 }
index f4fb40b..29fd092 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
 
+import jalview.api.FeatureRenderer;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 import jalview.gui.Preferences;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.FileLoader;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
+import jalview.ws.sifts.SiftsClient;
+import jalview.ws.sifts.SiftsException;
+import jalview.ws.sifts.SiftsSettings;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Vector;
 import jalview.io.DataSourceType;
 
 import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -48,15 +68,25 @@ public class JalviewChimeraView
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
+  private JalviewStructureDisplayI chimeraViewer;
+
   /**
    * @throws java.lang.Exception
    */
   @BeforeClass(alwaysRun = true)
   public static void setUpBeforeClass() throws Exception
   {
-    jalview.bin.Jalview.main(new String[] {
-        "-noquestionnaire -nonews -props",
+    Jalview.main(new String[] { "-noquestionnaire", "-nonews", "-props",
         "test/jalview/ext/rbvi/chimera/testProps.jvprops" });
+    Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
+            ViewerType.CHIMERA.name());
+    Cache.setProperty("SHOW_ANNOTATIONS", "false");
+    Cache.setProperty(Preferences.STRUCT_FROM_PDB, "false");
+    Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
+            ViewerType.CHIMERA.name());
+    Cache.setProperty("MAP_WITH_SIFTS", "true");
+    // TODO this should not be necessary!
+    SiftsSettings.setMapWithSifts(true);
   }
 
   /**
@@ -65,57 +95,388 @@ public class JalviewChimeraView
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public void tearDownAfterTest() throws Exception
+  {
+    SiftsClient.setMockSiftsFile(null);
+    if (chimeraViewer != null)
+    {
+      chimeraViewer.closeViewer(true);
+    }
   }
 
-  @Test(groups = { "Functional" })
+  /**
+   * Load 1GAQ and view the first structure for which a PDB id is found. Note no
+   * network connection is needed - PDB file is read locally, SIFTS fetch fails
+   * so mapping falls back to Needleman-Wunsch - ok for this test.
+   */
+  // External as local install of Chimera required
+  @Test(groups = { "External" })
   public void testSingleSeqViewChimera()
   {
-    Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
-            ViewerType.CHIMERA.name());
     String inFile = "examples/1gaq.txt";
-    AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded(
-            inFile, DataSourceType.FILE);
-    assertTrue("Didn't read input file " + inFile, af != null);
-    for (SequenceI sq : af.getViewport().getAlignment().getSequences())
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Failed to create AlignFrame");
+    SequenceI sq = af.getViewport().getAlignment().getSequenceAt(0);
+    assertEquals(sq.getName(), "1GAQ|A");
+    SequenceI dsq = sq.getDatasetSequence();
+    Vector<PDBEntry> pdbIds = dsq.getAllPDBEntries();
+    assertEquals(pdbIds.size(), 1);
+    PDBEntry pdbEntry = pdbIds.get(0);
+    assertEquals(pdbEntry.getId(), "1GAQ");
+    StructureViewer structureViewer = new StructureViewer(af.getViewport()
+            .getStructureSelectionManager());
+    chimeraViewer = structureViewer.viewStructures(pdbEntry,
+            new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
+    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
+            .getBinding();
+
+    /*
+     * Wait for viewer load thread to complete
+     */
+    while (!binding.isFinishedInit())
     {
-      System.out.println("** sq=" + sq.getName());
-      SequenceI dsq = sq.getDatasetSequence();
-      while (dsq.getDatasetSequence() != null)
+      try
+      {
+        Thread.sleep(500);
+      } catch (InterruptedException e)
       {
-        dsq = dsq.getDatasetSequence();
       }
-      if (dsq.getAllPDBEntries() != null
-              && dsq.getAllPDBEntries().size() > 0)
+    }
+
+    assertTrue(binding.isChimeraRunning(), "Failed to start Chimera");
+
+    assertEquals(chimeraViewer.getBinding().getPdbCount(), 1);
+    chimeraViewer.closeViewer(true);
+    chimeraViewer = null;
+    return;
+  }
+
+  /**
+   * Test for writing Jalview features as attributes on mapped residues in
+   * Chimera. Note this uses local copies of PDB and SIFTS file, no network
+   * connection required.
+   * 
+   * @throws IOException
+   * @throws SiftsException
+   */
+  // External as this requires a local install of Chimera
+  @Test(groups = { "External" })
+  public void testTransferFeatures() throws IOException, SiftsException
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Failed to create AlignFrame");
+    SequenceI sq = af.getViewport().getAlignment().findName("FER2_ARATH");
+    assertNotNull(sq, "Didn't find FER2_ARATH");
+
+    /*
+     * need a Uniprot dbref for SIFTS mapping to work!!
+     */
+    sq.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
+
+    /*
+     * use local test PDB and SIFTS files
+     */
+    String pdbFilePath = new File(
+            "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
+    PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
+    String siftsFilePath = new File(
+            "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
+            .getPath();
+    SiftsClient.setMockSiftsFile(new File(siftsFilePath));
+
+    StructureViewer structureViewer = new StructureViewer(af.getViewport()
+            .getStructureSelectionManager());
+    chimeraViewer = structureViewer.viewStructures(pdbEntry,
+            new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
+
+    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
+            .getBinding();
+    do
+    {
+      try
+      {
+        Thread.sleep(500);
+      } catch (InterruptedException e)
+      {
+      }
+    } while (!binding.isFinishedInit());
+
+    assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
+
+    assertEquals(binding.getPdbCount(), 1);
+
+    /*
+     * check mapping is (sequence) 53-145 to (structure) 2-94 A/B
+     * (or possibly 52-145 to 1-94 - see JAL-2319)
+     */
+    StructureSelectionManager ssm = binding.getSsm();
+    String pdbFile = binding.getStructureFiles()[0];
+    StructureMapping[] mappings = ssm.getMapping(pdbFile);
+    assertTrue(mappings[0].getMappingDetailsOutput().contains("SIFTS"),
+            "Failed to perform SIFTS mapping");
+    assertEquals(mappings.length, 2);
+    assertEquals(mappings[0].getChain(), "A");
+    assertEquals(mappings[0].getPDBResNum(53), 2);
+    assertEquals(mappings[0].getPDBResNum(145), 94);
+    assertEquals(mappings[1].getChain(), "B");
+    assertEquals(mappings[1].getPDBResNum(53), 2);
+    assertEquals(mappings[1].getPDBResNum(145), 94);
+
+    /*
+     * now add some features to FER2_ARATH 
+     */
+    // feature on a sequence region not mapped to structure:
+    sq.addSequenceFeature(new SequenceFeature("transit peptide",
+            "chloroplast", 1, 51, Float.NaN, null));
+    // feature on a region mapped to structure:
+    sq.addSequenceFeature(new SequenceFeature("domain",
+            "2Fe-2S ferredoxin-type", 55, 145, Float.NaN, null));
+    // on sparse positions of the sequence
+    sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
+            "Iron-Sulfur (2Fe-2S)", 91, 91, Float.NaN, null));
+    sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
+            "Iron-Sulfur (2Fe-2S)", 96, 96, Float.NaN, null));
+    // on a sequence region that is partially mapped to structure:
+    sq.addSequenceFeature(new SequenceFeature("helix", null, 50, 60,
+            Float.NaN, null));
+    // and again:
+    sq.addSequenceFeature(new SequenceFeature("chain", null, 50, 70,
+            Float.NaN, null));
+    // add numeric valued features - score is set as attribute value
+    sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 62,
+            62, -2.1f, null));
+    sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 65,
+            65, 3.6f, null));
+    sq.addSequenceFeature(new SequenceFeature("RESNUM", "ALA:   2  4zhoA",
+            53, 53, Float.NaN, null));
+
+    /*
+     * set all features visible except for chain
+     */
+    af.setShowSeqFeatures(true);
+    FeatureRenderer fr = af.getFeatureRenderer();
+    fr.setVisible("transit peptide");
+    fr.setVisible("domain");
+    fr.setVisible("metal ion-binding site");
+    fr.setVisible("helix");
+    fr.setVisible("kd");
+    fr.setVisible("RESNUM");
+
+    /*
+     * 'perform' menu action to copy visible features to
+     * attributes in Chimera
+     */
+    // TODO rename and pull up method to binding interface
+    // once functionality is added for Jmol as well
+    binding.sendFeaturesToViewer(af.getViewport().getAlignPanel());
+
+    /*
+     * give Chimera time to open the commands file and execute it
+     */
+    try
+    {
+      Thread.sleep(1000);
+    } catch (InterruptedException e)
+    {
+    }
+
+    /*
+     * ask Chimera for its residue attribute names
+     */
+    List<String> reply = binding.sendChimeraCommand("list resattr", true);
+    // prefixed and sanitised attribute names for Jalview features:
+    assertTrue(reply.contains("resattr jv_domain"));
+    assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
+    assertTrue(reply.contains("resattr jv_helix"));
+    assertTrue(reply.contains("resattr jv_kd"));
+    assertTrue(reply.contains("resattr jv_RESNUM"));
+    // feature is not on a mapped region - no attribute created
+    assertFalse(reply.contains("resattr jv_transit_peptide"));
+    // feature is not visible - no attribute created
+    assertFalse(reply.contains("resattr jv_chain"));
+
+    /*
+     * ask Chimera for residues with an attribute
+     * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
+     */
+    reply = binding.sendChimeraCommand(
+            "list resi att jv_metal_ion_binding_site", true);
+    assertEquals(reply.size(), 4);
+    assertTrue(reply
+            .contains("residue id #0:40.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
+    assertTrue(reply
+            .contains("residue id #0:45.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
+    assertTrue(reply
+            .contains("residue id #0:40.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
+    assertTrue(reply
+            .contains("residue id #0:45.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
+
+    /*
+     * check attributes with score values
+     * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
+     */
+    reply = binding.sendChimeraCommand("list resi att jv_kd", true);
+    assertEquals(reply.size(), 4);
+    assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
+    assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
+    assertTrue(reply.contains("residue id #0:11.B jv_kd -2.1 index 11"));
+    assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
+
+    /*
+     * list residues with positive kd score 
+     */
+    reply = binding.sendChimeraCommand(
+            "list resi spec :*/jv_kd>0 attr jv_kd", true);
+    assertEquals(reply.size(), 2);
+    assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
+    assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
+
+    SiftsClient.setMockSiftsFile(null);
+    chimeraViewer.closeViewer(true);
+    chimeraViewer = null;
+  }
+
+  /**
+   * Test for creating Jalview features from attributes on mapped residues in
+   * Chimera. Note this uses local copies of PDB and SIFTS file, no network
+   * connection required.
+   * 
+   * @throws IOException
+   * @throws SiftsException
+   */
+  // External as this requires a local install of Chimera
+  @Test(groups = { "External" })
+  public void testGetAttributes() throws IOException, SiftsException
+  {
+    String inFile = "examples/uniref50.fa";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
+            DataSourceType.FILE);
+    assertNotNull(af, "Failed to create AlignFrame");
+    SequenceI fer2Arath = af.getViewport().getAlignment()
+            .findName("FER2_ARATH");
+    assertNotNull(fer2Arath, "Didn't find FER2_ARATH");
+  
+    /*
+     * need a Uniprot dbref for SIFTS mapping to work!!
+     */
+    fer2Arath.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
+  
+    /*
+     * use local test PDB and SIFTS files
+     */
+    String pdbFilePath = new File(
+            "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
+    PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
+    String siftsFilePath = new File(
+            "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
+            .getPath();
+    SiftsClient.setMockSiftsFile(new File(siftsFilePath));
+  
+    StructureViewer structureViewer = new StructureViewer(af.getViewport()
+            .getStructureSelectionManager());
+    chimeraViewer = structureViewer.viewStructures(pdbEntry,
+            new SequenceI[] { fer2Arath }, af.getCurrentView()
+                    .getAlignPanel());
+  
+    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
+            .getBinding();
+    do
+    {
+      try
+      {
+        Thread.sleep(500);
+      } catch (InterruptedException e)
       {
-        for (int q = 0; q < dsq.getAllPDBEntries().size(); q++)
-        {
-          final StructureViewer structureViewer = new StructureViewer(af
-                  .getViewport().getStructureSelectionManager());
-          structureViewer.setViewerType(ViewerType.CHIMERA);
-          JalviewStructureDisplayI chimeraViewer = structureViewer
-                  .viewStructures(dsq.getAllPDBEntries().elementAt(q),
-                          new SequenceI[] { sq }, af.getCurrentView()
-                                  .getAlignPanel());
-          /*
-           * Wait for viewer load thread to complete
-           */
-          while (!chimeraViewer.getBinding().isFinishedInit())
-          {
-            try
-            {
-              Thread.sleep(500);
-            } catch (InterruptedException e)
-            {
-            }
-          }
-          assertEquals(1, chimeraViewer.getBinding().getPdbCount());
-          chimeraViewer.closeViewer(true);
-          // todo: break here means only once through this loop?
-          break;
-        }
-        break;
       }
+    } while (!binding.isFinishedInit());
+  
+    assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
+  
+    assertEquals(binding.getPdbCount(), 1);
+  
+    /*
+     * 'perform' menu action to copy visible features to
+     * attributes in Chimera
+     */
+    // TODO rename and pull up method to binding interface
+    // once functionality is added for Jmol as well
+    binding.copyStructureAttributesToFeatures("isHelix", af.getViewport()
+            .getAlignPanel());
+
+    /*
+     * verify 22 residues have isHelix feature
+     * (may merge into ranges in future)
+     */
+    af.setShowSeqFeatures(true);
+    FeatureRenderer fr = af.getFeatureRenderer();
+    fr.setVisible("isHelix");
+    for (int res = 75; res <= 83; res++)
+    {
+      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
     }
+    for (int res = 117; res <= 123; res++)
+    {
+      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
+    }
+    for (int res = 129; res <= 131; res++)
+    {
+      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
+    }
+    for (int res = 143; res <= 145; res++)
+    {
+      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
+    }
+
+    /*
+     * fetch a numeric valued attribute
+     */
+    binding.copyStructureAttributesToFeatures("phi", af.getViewport()
+            .getAlignPanel());
+    fr.setVisible("phi");
+    List<SequenceFeature> fs = fr.findFeaturesAtRes(fer2Arath, 54);
+    assertEquals(fs.size(), 3);
+    assertEquals(fs.get(0).getType(), "RESNUM");
+    assertEquals(fs.get(1).getType(), "phi");
+    assertEquals(fs.get(2).getType(), "phi");
+    assertEquals(fs.get(1).getDescription(), "A"); // chain
+    assertEquals(fs.get(2).getDescription(), "B");
+    assertEquals(fs.get(1).getScore(), -131.0713f, 0.001f);
+    assertEquals(fs.get(2).getScore(), -127.39512, 0.001f);
+
+    /*
+     * tear down - also in AfterMethod
+     */
+    SiftsClient.setMockSiftsFile(null);
+    chimeraViewer.closeViewer(true);
+    chimeraViewer = null;
+  }
+
+  /**
+   * Helper method to verify new feature at a sequence position
+   * 
+   * @param seq
+   * @param fr
+   * @param res
+   * @param featureType
+   */
+  protected void checkFeaturesAtRes(SequenceI seq, FeatureRenderer fr,
+          int res, String featureType)
+  {
+    String where = "at position " + res;
+    List<SequenceFeature> fs = fr.findFeaturesAtRes(seq, res);
+    assertEquals(fs.size(), 2, where);
+    assertEquals(fs.get(0).getType(), "RESNUM", where);
+    SequenceFeature sf = fs.get(1);
+    assertEquals(sf.getType(), featureType, where);
+    assertEquals(sf.getFeatureGroup(), "Chimera", where);
+    assertEquals(sf.getDescription(), "True", where);
+    assertEquals(sf.getScore(), Float.NaN, where);
   }
 }
index 95da22e..5c08d8e 100644 (file)
@@ -1,85 +1,82 @@
-#---JalviewX Properties File---
-#Fri Apr 25 09:54:25 BST 2014
-SCREEN_Y=768
-SCREEN_X=936
-SHOW_WSDISCOVERY_ERRORS=true
-LATEST_VERSION=2.8.0b1
-SHOW_CONSERVATION=true
+ANNOTATIONCOLOUR_MAX=ff0000
+ANNOTATIONCOLOUR_MIN=ffc800
+ANTI_ALIAS=false
+AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
+AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+AUTO_CALC_CONSENSUS=true
+BLC_JVSUFFIX=true
+BUILD_DATE=01 November 2013
+CLUSTAL_JVSUFFIX=true
+DAS_ACTIVE_SOURCE=uniprot\t
+DAS_LOCAL_SOURCE=
+DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/
+DEFAULT_COLOUR=None
+DEFAULT_FILE_FORMAT=FASTA
+FASTA_JVSUFFIX=true
+FIGURE_AUTOIDWIDTH=false
+FIGURE_USERIDWIDTH=
+FONT_NAME=SansSerif
+FONT_SIZE=10
+FONT_STYLE=plain
+GAP_SYMBOL=-
+ID_ITALICS=true
+JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
+JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
 JALVIEW_RSS_WINDOW_SCREEN_WIDTH=550
+JALVIEW_RSS_WINDOW_SCREEN_X=0
+JALVIEW_RSS_WINDOW_SCREEN_Y=0
+JAVA_CONSOLE_SCREEN_HEIGHT=162
 JAVA_CONSOLE_SCREEN_WIDTH=450
+JAVA_CONSOLE_SCREEN_X=830
+JAVA_CONSOLE_SCREEN_Y=475
+JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
 LAST_DIRECTORY=/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples
-ID_ITALICS=true
-SORT_ALIGNMENT=No sort
-SHOW_IDENTITY=true
-WSMENU_BYHOST=false
-SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
-SHOW_FULLSCREEN=false
-RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
-FONT_NAME=SansSerif
-BLC_JVSUFFIX=true
-VERSION_CHECK=false
-YEAR=2011
-SHOW_DBREFS_TOOLTIP=true
+LATEST_VERSION=2.8.0b1
 MSF_JVSUFFIX=true
-SCREENGEOMETRY_HEIGHT=1600
-JAVA_CONSOLE_SCREEN_Y=475
-JAVA_CONSOLE_SCREEN_X=830
+NOQUESTIONNAIRES=true
+PAD_GAPS=false
 PFAM_JVSUFFIX=true
+PILEUP_JVSUFFIX=true
 PIR_JVSUFFIX=true
-STARTUP_FILE=http\://www.jalview.org/examples/exampleFile_2_3.jar
-JAVA_CONSOLE_SCREEN_HEIGHT=162
 PIR_MODELLER=false
-GAP_SYMBOL=-
-SHOW_QUALITY=true
+RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
+RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
+RIGHT_ALIGN_IDS=false
+RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
+SCREEN_HEIGHT=650
+SCREEN_WIDTH=900
+SCREEN_X=936
+SCREEN_Y=768
+SCREENGEOMETRY_HEIGHT=1600
+SCREENGEOMETRY_WIDTH=2560
+SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
+SHOW_ANNOTATIONS=true
+SHOW_CONSENSUS_HISTOGRAM=true
+SHOW_CONSENSUS_LOGO=false
+SHOW_CONSERVATION=true
+SHOW_DBREFS_TOOLTIP=true
+SHOW_ENFIN_SERVICES=true
+SHOW_FULLSCREEN=false
+SHOW_GROUP_CONSENSUS=false
 SHOW_GROUP_CONSERVATION=false
+SHOW_IDENTITY=true
+SHOW_JAVA_CONSOLE=false
+SHOW_JVSUFFIX=true
 SHOW_JWS2_SERVICES=true
 SHOW_NPFEATS_TOOLTIP=true
-FONT_STYLE=plain
-ANTI_ALIAS=false
-SORT_BY_TREE=false
-RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
-AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
-JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
-SHOW_GROUP_CONSENSUS=false
-SHOW_CONSENSUS_HISTOGRAM=true
 SHOW_OVERVIEW=false
-AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
-FIGURE_AUTOIDWIDTH=false
-SCREEN_WIDTH=900
-ANNOTATIONCOLOUR_MIN=ffc800
+SHOW_QUALITY=true
 SHOW_STARTUP_FILE=false
-RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
-DEFAULT_FILE_FORMAT=FASTA
-SHOW_JAVA_CONSOLE=false
-VERSION=2.8b1
-FIGURE_USERIDWIDTH=
-WSMENU_BYTYPE=false
-DEFAULT_COLOUR=None
-NOQUESTIONNAIRES=true
-JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
-BUILD_DATE=01 November 2013
-PILEUP_JVSUFFIX=true
-SHOW_CONSENSUS_LOGO=false
-SCREENGEOMETRY_WIDTH=2560
-SHOW_ANNOTATIONS=true
-JALVIEW_RSS_WINDOW_SCREEN_Y=0
-USAGESTATS=false
-JALVIEW_RSS_WINDOW_SCREEN_X=0
 SHOW_UNCONSERVED=false
-SHOW_JVSUFFIX=true
-DAS_LOCAL_SOURCE=
-SCREEN_HEIGHT=650
-ANNOTATIONCOLOUR_MAX=ff0000
-AUTO_CALC_CONSENSUS=true
-FASTA_JVSUFFIX=true
-DAS_ACTIVE_SOURCE=uniprot\t
-JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
-PAD_GAPS=false
-CLUSTAL_JVSUFFIX=true
-SHOW_ENFIN_SERVICES=true
-FONT_SIZE=10
-RIGHT_ALIGN_IDS=false
+SHOW_WSDISCOVERY_ERRORS=true
+SORT_ALIGNMENT=No sort
+SORT_BY_TREE=false
+STARTUP_FILE=
+USAGESTATS=false
 USE_PROXY=false
+VERSION_CHECK=false
+VERSION=2.8b1
 WRAP_ALIGNMENT=false
-#DAS_REGISTRY_URL=http\://www.dasregistry.org/das/ # retired 01/05/2015
-DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/
+WSMENU_BYHOST=false
+WSMENU_BYTYPE=false
+YEAR=2011
index 901bffc..9912e44 100644 (file)
@@ -25,8 +25,8 @@ import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.gui.JvOptionPane;
 
+import javax.swing.JComboBox;
 import javax.swing.JInternalFrame;
-import javax.swing.JTextField;
 
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -98,17 +98,17 @@ public class PDBFTSPanelTest
     assertEquals(expectedString, outcome);
   }
 
-  @Test(groups = { "External" }, timeOut = 7000)
+  @Test(groups = { "External" }, timeOut = 8000)
   public void txt_search_ActionPerformedTest()
   {
     PDBFTSPanel searchPanel = new PDBFTSPanel(null);
     JInternalFrame mainFrame = searchPanel.getMainFrame();
-    JTextField txt_search = searchPanel.getTxtSearch();
+    JComboBox<String> txt_search = searchPanel.getTxtSearch();
 
     assertTrue(mainFrame.getTitle().length() == 20);
     assertTrue(mainFrame.getTitle()
             .equalsIgnoreCase("PDB Sequence Fetcher"));
-    txt_search.setText("ABC");
+    txt_search.setSelectedItem("ABC");
     try
     {
       // wait for web-service to handle response
index 2ea94a4..fed5992 100644 (file)
@@ -85,11 +85,13 @@ public class AlignFrameTest
      */
     assertFalse(alignFrame.hideFeatureColumns("exon", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getColumnSelection().getHiddenColumns()
+    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getHiddenRegions()
             .isEmpty());
     assertFalse(alignFrame.hideFeatureColumns("exon", false));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getColumnSelection().getHiddenColumns()
+    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getHiddenRegions()
             .isEmpty());
 
     /*
@@ -97,8 +99,9 @@ public class AlignFrameTest
      */
     assertFalse(alignFrame.hideFeatureColumns("Metal", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    List<int[]> hidden = alignFrame.getViewport().getColumnSelection()
-            .getHiddenColumns();
+    List<int[]> hidden = alignFrame.getViewport().getAlignment()
+            .getHiddenColumns()
+            .getHiddenRegions();
     assertTrue(hidden.isEmpty());
 
     /*
@@ -107,7 +110,8 @@ public class AlignFrameTest
      * [1-3], [6-8] base zero
      */
     assertTrue(alignFrame.hideFeatureColumns("Turn", true));
-    hidden = alignFrame.getViewport().getColumnSelection().getHiddenColumns();
+    hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getHiddenRegions();
     assertEquals(hidden.size(), 2);
     assertEquals(hidden.get(0)[0], 1);
     assertEquals(hidden.get(0)[1], 3);
index 06df70a..4660842 100644 (file)
@@ -50,6 +50,7 @@ import jalview.util.MapList;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -159,7 +160,7 @@ public class AlignViewportTest
     acf2.addMap(s1, s1, new MapList(new int[] { 1, 4 }, new int[] { 4, 1 },
             1, 1));
 
-    List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings = new ArrayList<>();
     mappings.add(acf1);
     mappings.add(acf2);
     af1.getViewport().getAlignment().setCodonFrames(mappings);
@@ -216,11 +217,11 @@ public class AlignViewportTest
     acf3.addMap(cs2, cs2, new MapList(new int[] { 1, 12 }, new int[] { 1,
         12 }, 1, 1));
 
-    List<AlignedCodonFrame> mappings1 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings1 = new ArrayList<>();
     mappings1.add(acf1);
     af1.getViewport().getAlignment().setCodonFrames(mappings1);
 
-    List<AlignedCodonFrame> mappings2 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings2 = new ArrayList<>();
     mappings2.add(acf2);
     mappings2.add(acf3);
     af2.getViewport().getAlignment().setCodonFrames(mappings2);
@@ -279,12 +280,12 @@ public class AlignViewportTest
     acf3.addMap(cs2, cs2, new MapList(new int[] { 1, 12 }, new int[] { 1,
         12 }, 1, 1));
 
-    List<AlignedCodonFrame> mappings1 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings1 = new ArrayList<>();
     mappings1.add(acf1);
     mappings1.add(acf2);
     af1.getViewport().getAlignment().setCodonFrames(mappings1);
 
-    List<AlignedCodonFrame> mappings2 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> mappings2 = new ArrayList<>();
     mappings2.add(acf2);
     mappings2.add(acf3);
     af2.getViewport().getAlignment().setCodonFrames(mappings2);
@@ -327,6 +328,8 @@ public class AlignViewportTest
             Boolean.TRUE.toString());
     Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
             Boolean.FALSE.toString());
+    Cache.applicationProperties.setProperty("SHOW_OCCUPANCY",
+            Boolean.FALSE.toString());
     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
             Boolean.FALSE.toString());
     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
@@ -386,7 +389,8 @@ public class AlignViewportTest
 
   /**
    * Verify that setting the selection group has the side-effect of setting the
-   * context on the group, unless it already has one
+   * context on the group, unless it already has one, but does not change
+   * whether the group is defined or not.
    */
   @Test(groups = { "Functional" })
   public void testSetSelectionGroup()
@@ -396,12 +400,56 @@ public class AlignViewportTest
     AlignViewport av = af.getViewport();
     SequenceGroup sg1 = new SequenceGroup();
     SequenceGroup sg2 = new SequenceGroup();
+    SequenceGroup sg3 = new SequenceGroup();
 
     av.setSelectionGroup(sg1);
     assertSame(sg1.getContext(), av.getAlignment()); // context set
+    assertFalse(sg1.isDefined()); // group not defined
 
-    sg2.setContext(sg1);
+    sg2.setContext(sg1, false);
     av.setSelectionGroup(sg2);
+    assertFalse(sg2.isDefined()); // unchanged
     assertSame(sg2.getContext(), sg1); // unchanged
+
+    // create a defined group
+    sg3.setContext(av.getAlignment(), true);
+    av.setSelectionGroup(sg3);
+    assertTrue(sg3.isDefined()); // unchanged
+  }
+  /**
+   * Verify that setting/clearing SHOW_OCCUPANCY preference adds or omits occupancy row from viewport
+   */
+  @Test(groups = { "Functional" })
+  public void testShowOrDontShowOccupancy()
+  {
+    // disable occupancy
+    jalview.bin.Cache.setProperty("SHOW_OCCUPANCY", Boolean.FALSE.toString());
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport av = af.getViewport();
+    Assert.assertNull(av.getAlignmentGapAnnotation(), "Preference did not disable occupancy row.");
+    int c = 0;
+    for (AlignmentAnnotation aa : av.getAlignment().findAnnotations(null,
+            null, "Occupancy"))
+    {
+      c++;
+    }
+    Assert.assertEquals(c, 0, "Expected zero occupancy rows.");
+    
+    // enable occupancy
+    jalview.bin.Cache.setProperty("SHOW_OCCUPANCY", Boolean.TRUE.toString());
+    af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    av = af.getViewport();
+    Assert.assertNotNull(av.getAlignmentGapAnnotation(), "Preference did not enable occupancy row.");
+    c = 0;
+    for (AlignmentAnnotation aa : av.getAlignment().findAnnotations(null,
+            null, av.getAlignmentGapAnnotation().label))
+    {
+      c++;
+    }
+    ;
+    Assert.assertEquals(c, 1, "Expected to find one occupancy row.");
+
   }
 }
diff --git a/test/jalview/gui/AlignmentPanelTest.java b/test/jalview/gui/AlignmentPanelTest.java
new file mode 100644 (file)
index 0000000..b228ba1
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.viewmodel.ViewportRanges;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class AlignmentPanelTest
+{
+  SequenceI seq1 = new Sequence(
+          "Seq1",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq2 = new Sequence(
+          "Seq2",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq3 = new Sequence(
+          "Seq3",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq4 = new Sequence(
+          "Seq4",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq5 = new Sequence(
+          "Seq5",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq6 = new Sequence(
+          "Seq6",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq7 = new Sequence(
+          "Seq7",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq8 = new Sequence(
+          "Seq8",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq9 = new Sequence(
+          "Seq9",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq10 = new Sequence(
+          "Seq10",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq11 = new Sequence(
+          "Seq11",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq12 = new Sequence(
+          "Seq12",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq13 = new Sequence(
+          "Seq13",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq14 = new Sequence(
+          "Seq14",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq15 = new Sequence(
+          "Seq15",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq16 = new Sequence(
+          "Seq16",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq17 = new Sequence(
+          "Seq17",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq18 = new Sequence(
+          "Seq18",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq19 = new Sequence(
+          "Seq19",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq20 = new Sequence(
+          "Seq20",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq21 = new Sequence(
+          "Seq21",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq22 = new Sequence(
+          "Seq22",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq23 = new Sequence(
+          "Seq23",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  AlignFrame af;
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp()
+  {
+    Jalview.main(new String[] { "-nonews", "-props",
+        "test/jalview/testProps.jvprops" });
+
+    Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+            Boolean.TRUE.toString());
+    af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
+            DataSourceType.FILE);
+
+    /*
+     * wait for Consensus thread to complete
+     */
+    synchronized (this)
+    {
+      while (af.getViewport().getConsensusSeq() == null)
+      {
+        try
+        {
+          wait(50);
+        } catch (InterruptedException e)
+        {
+        }
+      }
+    }
+  }
+
+
+  /**
+   * Test side effect that end residue is set correctly by setScrollValues, with
+   * or without hidden columns
+   */
+  @Test(groups = "Functional")
+  public void TestSetScrollValues()
+  {
+    ViewportRanges ranges = af.getViewport().getRanges();
+    af.alignPanel.setScrollValues(0, 0);
+
+    int oldres = ranges.getEndRes();
+    af.alignPanel.setScrollValues(-1, 5);
+
+    // setting -ve x value does not change residue
+    assertEquals(ranges.getEndRes(), oldres);
+
+    af.alignPanel.setScrollValues(0, 5);
+
+    // setting 0 as x value does not change residue
+    assertEquals(ranges.getEndRes(), oldres);
+
+    af.alignPanel.setScrollValues(5, 5);
+    // setting x value to 5 extends endRes by 5 residues
+    assertEquals(ranges.getEndRes(), oldres + 5);
+
+    // scroll to position after hidden columns sets endres to oldres (width) +
+    // position
+    int scrollpos = 60;
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), oldres + scrollpos);
+
+    // scroll to position within hidden columns, still sets endres to oldres +
+    // position
+    // not sure if this is actually correct behaviour but this is what Jalview
+    // currently does
+    scrollpos = 40;
+    af.getViewport().showAllHiddenColumns();
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), oldres + scrollpos);
+
+    // scroll to position within <width> distance of the end of the alignment
+    // endRes should be set to width of alignment - 1
+    scrollpos = 130;
+    af.getViewport().showAllHiddenColumns();
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), af.getViewport()
+            .getAlignment().getWidth() - 1);
+
+    // now hide some columns, and scroll to position within <width>
+    // distance of the end of the alignment
+    // endRes should be set to width of alignment - 1 - the number of hidden
+    // columns
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), af.getViewport()
+            .getAlignment().getWidth() - 1 - 21); // 21 is the number of hidden
+                                                  // columns
+  }
+}
index 38c1855..fbdda09 100644 (file)
@@ -326,7 +326,7 @@ public class AnnotationChooserTest
 
     types = AnnotationChooser.getAnnotationTypes(
             parentPanel.getAlignment(), false);
-    assertEquals("Not six annotation types", 6, types.size());
+    assertEquals("Not six annotation types", 7, types.size());
     assertTrue("IUPRED missing", types.contains("IUPRED"));
     assertTrue("JMol missing", types.contains("JMol"));
     assertTrue("Beauty missing", types.contains("Beauty"));
@@ -334,6 +334,7 @@ public class AnnotationChooserTest
     assertTrue("Consensus missing", types.contains("Consensus"));
     assertTrue("Quality missing", types.contains("Quality"));
     assertTrue("Conservation missing", types.contains("Conservation"));
+    assertTrue("Occupancy missing", types.contains("Occupancy"));
   }
 
   /**
@@ -357,18 +358,20 @@ public class AnnotationChooserTest
     AlignmentAnnotation[] anns = parentPanel.getAlignment()
             .getAlignmentAnnotation();
 
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertTrue(anns[autocalc + 2].visible); // JMol for seq3
+    assertTrue(anns[autocalc + 4].visible); // JMol for seq1
 
     setSelected(getTypeCheckbox("JMol"), true);
     assertTrue(anns[0].visible); // Conservation
     assertTrue(anns[1].visible); // Quality
     assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertFalse(anns[5].visible); // JMol for seq3 - not selected but hidden
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertFalse(anns[7].visible); // JMol for seq1 - selected and hidden
+    assertTrue(anns[3].visible); // Occupancy
+    assertTrue(anns[4].visible); // IUPred for seq0
+    assertTrue(anns[5].visible); // Beauty
+    assertFalse(anns[6].visible); // JMol for seq3 - not selected but hidden
+    assertTrue(anns[7].visible); // IUPRED for seq2
+    assertFalse(anns[8].visible); // JMol for seq1 - selected and hidden
   }
 
   /**
@@ -395,17 +398,19 @@ public class AnnotationChooserTest
     AlignmentAnnotation[] anns = parentPanel.getAlignment()
             .getAlignmentAnnotation();
 
-    assertTrue(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertTrue(anns[autocalc + 4].visible); // JMol for seq1
 
     setSelected(getTypeCheckbox("JMol"), true);
     assertTrue(anns[0].visible); // Conservation
     assertTrue(anns[1].visible); // Quality
     assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertTrue(anns[5].visible); // JMol for seq3 not in selection group
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertFalse(anns[7].visible); // JMol for seq1 in selection group
+    assertTrue(anns[3].visible); // Occupancy
+    assertTrue(anns[4].visible); // IUPred for seq0
+    assertTrue(anns[5].visible); // Beauty
+    assertTrue(anns[6].visible); // JMol for seq3 not in selection group
+    assertTrue(anns[7].visible); // IUPRED for seq2
+    assertFalse(anns[8].visible); // JMol for seq1 in selection group
   }
 
   /**
@@ -435,19 +440,35 @@ public class AnnotationChooserTest
 
     // select JMol - all hidden
     setSelected(typeCheckbox, true);
-    assertFalse(anns[5].visible); // JMol for seq3
-    assertFalse(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertFalse(anns[autocalc + 2].visible); // JMol for seq3
+    assertFalse(anns[autocalc + 4].visible); // JMol for seq1
 
     // deselect JMol - all unhidden
     setSelected(typeCheckbox, false);
-    assertTrue(anns[0].visible); // Conservation
-    assertTrue(anns[1].visible); // Quality
-    assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertTrue(anns[7].visible); // JMol for seq1
+    for (AlignmentAnnotation ann : anns)
+    {
+      assertTrue(ann.visible);
+    }
+  }
+
+  /**
+   * Returns a count of autocalculated annotations in the set provided
+   * 
+   * @param anns
+   * @return
+   */
+  private int countAutocalc(AlignmentAnnotation[] anns)
+  {
+    int count = 0;
+    for (AlignmentAnnotation ann : anns)
+    {
+      if (ann.autoCalculated)
+      {
+        count++;
+      }
+    }
+    return count;
   }
 
   /**
@@ -510,18 +531,15 @@ public class AnnotationChooserTest
     setSelected(allSequencesCheckbox, true);
     setSelected(hideCheckbox, true);
     setSelected(getTypeCheckbox("JMol"), true);
-    assertFalse(anns[5].visible); // JMol for seq3
-    assertFalse(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertFalse(anns[autocalc + 2].visible); // JMol for seq3
+    assertFalse(anns[autocalc + 4].visible); // JMol for seq1
     // ...now show them...
     setSelected(showCheckbox, true);
-    assertTrue(anns[0].visible); // Conservation
-    assertTrue(anns[1].visible); // Quality
-    assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertTrue(anns[7].visible); // JMol for seq1
+    for (AlignmentAnnotation ann : anns)
+    {
+      assertTrue(ann.visible);
+    }
   }
 
   /**
@@ -551,19 +569,16 @@ public class AnnotationChooserTest
     setSelected(hideCheckbox, true);
     setSelected(getTypeCheckbox("JMol"), true);
 
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertFalse(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertTrue(anns[autocalc + 2].visible); // JMol for seq3
+    assertFalse(anns[autocalc + 4].visible); // JMol for seq1
     // ...now show them...
     setSelected(showCheckbox, true);
 
-    assertTrue(anns[0].visible); // Conservation
-    assertTrue(anns[1].visible); // Quality
-    assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertTrue(anns[7].visible); // JMol for seq1
+    for (AlignmentAnnotation ann : anns)
+    {
+      assertTrue(ann.visible);
+    }
   }
 
   /**
@@ -592,19 +607,21 @@ public class AnnotationChooserTest
     final Checkbox typeCheckbox = getTypeCheckbox("JMol");
     // select JMol - all shown
     setSelected(typeCheckbox, true);
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertTrue(anns[autocalc + 2].visible); // JMol for seq3
+    assertTrue(anns[autocalc + 4].visible); // JMol for seq1
 
     // deselect JMol - all hidden
     setSelected(typeCheckbox, false);
     assertTrue(anns[0].visible); // Conservation
     assertTrue(anns[1].visible); // Quality
     assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertFalse(anns[5].visible); // JMol for seq3
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertFalse(anns[7].visible); // JMol for seq1
+    assertTrue(anns[3].visible); // Occupancy
+    assertTrue(anns[4].visible); // IUPred for seq0
+    assertTrue(anns[5].visible); // Beauty
+    assertFalse(anns[6].visible); // JMol for seq3
+    assertTrue(anns[7].visible); // IUPRED for seq2
+    assertFalse(anns[8].visible); // JMol for seq1
   }
 
   /**
@@ -633,19 +650,21 @@ public class AnnotationChooserTest
 
     // select JMol - should remain visible
     setSelected(getTypeCheckbox("JMol"), true);
-    assertTrue(anns[5].visible); // JMol for seq3
-    assertTrue(anns[7].visible); // JMol for seq1
+    int autocalc = countAutocalc(anns);
+    assertTrue(anns[autocalc + 2].visible); // JMol for seq3
+    assertTrue(anns[autocalc + 4].visible); // JMol for seq1
 
     // deselect JMol - should be hidden for selected sequences only
     setSelected(getTypeCheckbox("JMol"), false);
     assertTrue(anns[0].visible); // Conservation
     assertTrue(anns[1].visible); // Quality
     assertTrue(anns[2].visible); // Consensus
-    assertTrue(anns[3].visible); // IUPred for seq0
-    assertTrue(anns[4].visible); // Beauty
-    assertTrue(anns[5].visible); // JMol for seq3 not in selection group
-    assertTrue(anns[6].visible); // IUPRED for seq2
-    assertFalse(anns[7].visible); // JMol for seq1 in selection group
+    assertTrue(anns[3].visible); // Occupancy
+    assertTrue(anns[4].visible); // IUPred for seq0
+    assertTrue(anns[5].visible); // Beauty
+    assertTrue(anns[6].visible); // JMol for seq3 not in selection group
+    assertTrue(anns[7].visible); // IUPRED for seq2
+    assertFalse(anns[8].visible); // JMol for seq1 in selection group
   }
 
   /**
@@ -721,12 +740,12 @@ public class AnnotationChooserTest
 
     AlignmentAnnotation[] anns = parentPanel.getAlignment()
             .getAlignmentAnnotation();
-    // remember 3 annotations to skip (Conservation/Quality/Consensus)
-    assertFalse(testee.isInActionScope(anns[3]));
-    assertFalse(testee.isInActionScope(anns[4]));
-    assertFalse(testee.isInActionScope(anns[5]));
-    assertTrue(testee.isInActionScope(anns[6]));
-    assertTrue(testee.isInActionScope(anns[7]));
+    int autocalc = countAutocalc(anns);
+    assertFalse(testee.isInActionScope(anns[autocalc]));
+    assertFalse(testee.isInActionScope(anns[autocalc + 1]));
+    assertFalse(testee.isInActionScope(anns[autocalc + 2]));
+    assertTrue(testee.isInActionScope(anns[autocalc + 3]));
+    assertTrue(testee.isInActionScope(anns[autocalc + 4]));
   }
 
   /**
@@ -747,12 +766,12 @@ public class AnnotationChooserTest
 
     AlignmentAnnotation[] anns = parentPanel.getAlignment()
             .getAlignmentAnnotation();
-    // remember 3 annotations to skip (Conservation/Quality/Consensus)
-    assertTrue(testee.isInActionScope(anns[3]));
-    assertTrue(testee.isInActionScope(anns[4]));
-    assertTrue(testee.isInActionScope(anns[5]));
-    assertFalse(testee.isInActionScope(anns[6]));
-    assertFalse(testee.isInActionScope(anns[7]));
+    int autocalc = countAutocalc(anns);
+    assertTrue(testee.isInActionScope(anns[autocalc]));
+    assertTrue(testee.isInActionScope(anns[autocalc + 1]));
+    assertTrue(testee.isInActionScope(anns[autocalc + 2]));
+    assertFalse(testee.isInActionScope(anns[autocalc + 3]));
+    assertFalse(testee.isInActionScope(anns[autocalc + 4]));
   }
 
   /**
@@ -787,11 +806,12 @@ public class AnnotationChooserTest
     assertTrue(anns[0].visible); // Conservation
     assertTrue(anns[1].visible); // Quality
     assertTrue(anns[2].visible); // Consensus
-    assertFalse(anns[3].visible); // IUPRED
-    assertTrue(anns[4].visible); // Beauty (not seq-related)
-    assertFalse(anns[5].visible); // JMol
-    assertFalse(anns[6].visible); // IUPRED
-    assertFalse(anns[7].visible); // JMol
+    assertTrue(anns[3].visible); // Occupancy
+    assertFalse(anns[4].visible); // IUPRED
+    assertTrue(anns[5].visible); // Beauty (not seq-related)
+    assertFalse(anns[6].visible); // JMol
+    assertFalse(anns[7].visible); // IUPRED
+    assertFalse(anns[8].visible); // JMol
 
     // reset - should all be visible
     testee.resetOriginalState();
diff --git a/test/jalview/gui/AnnotationRowFilterTest.java b/test/jalview/gui/AnnotationRowFilterTest.java
new file mode 100644 (file)
index 0000000..69a41c5
--- /dev/null
@@ -0,0 +1,121 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.util.Vector;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for methods of base class of annotation column or colour chooser
+ */
+public class AnnotationRowFilterTest
+{
+  AlignFrame af;
+
+  private AnnotationRowFilter testee;
+
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty(
+            Preferences.SHOW_AUTOCALC_ABOVE, Boolean.TRUE.toString());
+    af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
+            DataSourceType.FILE);
+    testee = new AnnotationRowFilter(af.viewport, af.alignPanel)
+    {
+      @Override
+      public void valueChanged(boolean updateAllAnnotation)
+      {
+      }
+
+      @Override
+      public void updateView()
+      {
+      }
+
+      @Override
+      public void reset()
+      {
+      }
+    };
+  }
+
+  /**
+   * Test the method that builds the drop-down list of annotations to choose
+   * from for colour by annotation or select columns by annotation
+   */
+  @Test(groups = "Functional")
+  public void testGetAnnotationItems()
+  {
+    AlignmentI al = af.getViewport().getAlignment();
+    SequenceI seq1 = al.findSequenceMatch("FER_CAPAA")[0];
+    SequenceI seq2 = al.findSequenceMatch("FER_BRANA")[0];
+
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("ann1Label", "ann1",
+            null);
+    al.addAnnotation(ann1);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("Significance",
+            "ann2", null);
+    al.addAnnotation(ann2);
+    /*
+     * a second Significance alignment annotation
+     */
+    AlignmentAnnotation ann2a = new AlignmentAnnotation("Significance",
+            "ann2", null);
+    al.addAnnotation(ann2a);
+
+    AlignmentAnnotation ann3 = new AlignmentAnnotation("Jronn", "Jronn",
+            null);
+    ann3.setSequenceRef(seq1);
+    al.addAnnotation(ann3);
+    AlignmentAnnotation ann4 = new AlignmentAnnotation("Jronn", "Jronn",
+            null);
+    ann4.setSequenceRef(seq2);
+    al.addAnnotation(ann4);
+    AlignmentAnnotation ann5 = new AlignmentAnnotation("Jnet", "Jnet", null);
+    ann5.setSequenceRef(seq2);
+    al.addAnnotation(ann5);
+    /*
+     * a second Jnet annotation for FER_BRANA
+     */
+    AlignmentAnnotation ann6 = new AlignmentAnnotation("Jnet", "Jnet", null);
+    ann6.setSequenceRef(seq2);
+    al.addAnnotation(ann6);
+
+    /*
+     * drop-down items with 'Per-sequence only' not checked
+     */
+    Vector<String> items = testee.getAnnotationItems(false);
+    assertEquals(
+            items.toString(),
+            "[Conservation, Quality, Consensus, Occupancy, ann1Label, Significance, Significance_1, Jronn_FER_CAPAA, Jronn_FER_BRANA, Jnet_FER_BRANA, Jnet_FER_BRANA_2]");
+    assertEquals(testee.getAnnotationMenuLabel(ann1), "ann1Label");
+    assertEquals(testee.getAnnotationMenuLabel(ann2), "Significance");
+    assertEquals(testee.getAnnotationMenuLabel(ann2a), "Significance_1");
+    assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn_FER_CAPAA");
+    assertEquals(testee.getAnnotationMenuLabel(ann4), "Jronn_FER_BRANA");
+    assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet_FER_BRANA");
+    assertEquals(testee.getAnnotationMenuLabel(ann6), "Jnet_FER_BRANA_2");
+
+    /*
+     * drop-down items with 'Per-sequence only' checked
+     */
+    items = testee.getAnnotationItems(true);
+    assertEquals(items.toString(), "[Jronn, Jnet]");
+    // the first annotation of the type is associated with the menu item
+    assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn");
+    assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet");
+  }
+}
diff --git a/test/jalview/gui/SeqPanelTest.java b/test/jalview/gui/SeqPanelTest.java
new file mode 100644 (file)
index 0000000..53dff0e
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.gui;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class SeqPanelTest
+{
+  AlignFrame af;
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+  @Test(groups = "Functional")
+  public void testSetStatusReturnsPosOrMinusOne()
+  {
+    SequenceI seq1 = new Sequence("Seq1", "AACDE");
+    SequenceI seq2 = new Sequence("Seq2", "AA--E");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
+            al.getHeight());
+    AlignmentI visAl = alignFrame.getViewport().getAlignment();
+    // Test either side of gap
+    // This first assert fails due to JAL-2563
+    assertEquals(
+            alignFrame.alignPanel.getSeqPanel().setStatusMessage(
+                    visAl.getSequenceAt(1), 1, 1), 2);
+    assertEquals(
+            alignFrame.alignPanel.getSeqPanel().setStatusMessage(
+                    visAl.getSequenceAt(1), 4, 1), 3);
+    // Test gaps are -1
+    assertEquals(
+            alignFrame.alignPanel.getSeqPanel().setStatusMessage(
+                    visAl.getSequenceAt(1), 2, 1), -1);
+    assertEquals(
+            alignFrame.alignPanel.getSeqPanel().setStatusMessage(
+                    visAl.getSequenceAt(1), 3, 1), -1);
+  }
+
+  @Test(groups = "Functional")
+  public void testAmbiguousAminoAcidGetsStatusMessage()
+  {
+    SequenceI seq1 = new Sequence("Seq1", "ABCDE");
+    SequenceI seq2 = new Sequence("Seq2", "AB--E");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
+            al.getHeight());
+    AlignmentI visAl = alignFrame.getViewport().getAlignment();
+    // Test either side of gap
+    // This first assert fails due to JAL-2563
+    assertEquals(
+            alignFrame.alignPanel.getSeqPanel().setStatusMessage(
+                    visAl.getSequenceAt(1), 1, 1), 2);
+    assertTrue(alignFrame.statusBar.getText().contains("(2)"));
+  }
+}
index 81289b0..c80b830 100644 (file)
@@ -53,11 +53,29 @@ public class SequenceRendererTest
     av.setGlobalColourScheme(new ZappoColourScheme());
 
     // @see ResidueProperties.zappo
-    assertEquals(Color.pink, sr.getResidueColour(seq, 0, null)); // M
-    assertEquals(Color.green, sr.getResidueColour(seq, 2, null)); // T
-    assertEquals(Color.magenta, sr.getResidueColour(seq, 5, null)); // G
-    assertEquals(Color.orange, sr.getResidueColour(seq, 12, null)); // F
+    assertEquals(Color.pink, sr.getResidueBoxColour(seq, 0)); // M
+    assertEquals(Color.green, sr.getResidueBoxColour(seq, 2)); // T
+    assertEquals(Color.magenta, sr.getResidueBoxColour(seq, 5)); // G
+    assertEquals(Color.orange, sr.getResidueBoxColour(seq, 12)); // F
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetResidueBoxColour_none()
+  {
+    SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF");
+    AlignmentI al = new Alignment(new SequenceI[] { seq });
+    final AlignViewport av = new AlignViewport(al);
+    SequenceRenderer sr = new SequenceRenderer(av);
+
+    assertEquals(Color.white, sr.getResidueBoxColour(seq, 0));
+    assertEquals(Color.white, sr.getResidueBoxColour(seq, 2));
+
+    // set for overview
+    sr.forOverview = true;
+    assertEquals(Color.lightGray, sr.getResidueBoxColour(seq, 0));
+    assertEquals(Color.white, sr.getResidueBoxColour(seq, 2));
+  }
+
   // TODO more tests for getResidueBoxColour covering groups, feature rendering,
   // gaps, overview...
 
index b7eef0f..4535c93 100644 (file)
@@ -121,7 +121,7 @@ public class StructureChooserTest
     StructureChooser sc = new StructureChooser(selectedSeqs, seq, null);
     sc.populateFilterComboBox(false, false);
     int optionsSize = sc.getCmbFilterOption().getItemCount();
-    assertEquals(3, optionsSize); // if structures are not discovered then don't
+    assertEquals(2, optionsSize); // if structures are not discovered then don't
                                   // populate filter options
 
     sc.populateFilterComboBox(true, false);
@@ -136,7 +136,7 @@ public class StructureChooserTest
     assertEquals("Cached PDB Entries", filterOpt.getName());
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void fetchStructuresInfoTest()
   {
     SequenceI[] selectedSeqs = new SequenceI[] { seq };
index 885c673..6a00cde 100644 (file)
@@ -24,7 +24,7 @@ import static org.testng.AssertJUnit.assertNotNull;
 import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.gui.JvOptionPane;
 import jalview.io.AnnotationFile.ViewDef;
 
@@ -115,7 +115,7 @@ public class AnnotationFileIOTest
     try
     {
       AlignmentI al = readAlignmentFile(f);
-      ColumnSelection cs = new ColumnSelection();
+      HiddenColumns cs = new HiddenColumns();
       assertTrue(
               "Test "
                       + testname
@@ -124,6 +124,7 @@ public class AnnotationFileIOTest
                       DataSourceType.FILE));
 
       AnnotationFile aff = new AnnotationFile();
+      // ViewDef is not used by Jalview
       ViewDef v = aff.new ViewDef(null, al.getHiddenSequences(), cs,
               new Hashtable());
       String anfileout = new AnnotationFile().printAnnotations(
@@ -152,7 +153,8 @@ public class AnnotationFileIOTest
                       DataSourceType.PASTE));
 
       // test for consistency in io
-      StockholmFileTest.testAlignmentEquivalence(al, al_new, false);
+      StockholmFileTest.testAlignmentEquivalence(al, al_new, false, false,
+              false);
       return;
     } catch (Exception e)
     {
diff --git a/test/jalview/io/FileLoaderTest.java b/test/jalview/io/FileLoaderTest.java
new file mode 100644 (file)
index 0000000..968901f
--- /dev/null
@@ -0,0 +1,22 @@
+package jalview.io;
+
+import org.junit.Assert;
+import org.testng.annotations.Test;
+
+public class FileLoaderTest
+{
+
+  @Test(groups = { "Network" })
+  public void testDownloadStructuresIfInputFromURL()
+  {
+    String urlFile = "http://www.jalview.org/builds/develop/examples/3W5V.pdb";
+    FileLoader fileLoader = new FileLoader();
+    fileLoader.LoadFileWaitTillLoaded(urlFile, DataSourceType.URL,
+            FileFormat.PDB);
+    Assert.assertNotNull(fileLoader.file);
+    // The FileLoader's file is expected to be same as the original URL.
+    Assert.assertEquals(urlFile, fileLoader.file);
+    // Data source type expected to be DataSourceType.URL
+    Assert.assertEquals(DataSourceType.URL, fileLoader.protocol);
+  }
+}
index 3d800d8..dd4f6ba 100644 (file)
@@ -110,7 +110,8 @@ public class IdentifyFileTest
         {
             "examples/testdata/cullpdb_pc25_res3.0_R0.3_d150729_chains9361.fasta.15316",
             FileFormat.Fasta },
-
+        { "resources/scoreModel/pam250.scm", FileFormat.ScoreMatrix },
+        { "resources/scoreModel/blosum80.scm", FileFormat.ScoreMatrix }
     // { "examples/testdata/test.amsa", "AMSA" },
     // { "examples/test.jnet", "JnetFile" },
     };
index a8611cc..2aff5cc 100644 (file)
@@ -27,7 +27,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -36,6 +36,7 @@ import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueColourScheme;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -78,7 +79,7 @@ public class JSONFileTest
 
   private HashMap<String, SequenceGroup> expectedGrps = new HashMap<String, SequenceGroup>();
 
-  private ColumnSelection expectedColSel = new ColumnSelection();
+  private HiddenColumns expectedColSel = new HiddenColumns();
 
   private SequenceI[] expectedHiddenSeqs = new SequenceI[1];
 
@@ -90,6 +91,8 @@ public class JSONFileTest
 
   private JSONFile jf;
 
+  private AlignExportSettingI exportSettings;
+
   @BeforeTest(alwaysRun = true)
   public void setup() throws Exception
   {
@@ -127,13 +130,14 @@ public class JSONFileTest
       expectedSeqs.put(seq.getName(), seq);
     }
 
-    // create and add sequence groups
-    ArrayList<SequenceI> grpSeqs = new ArrayList<SequenceI>();
+    // create and add a sequence group
+    List<SequenceI> grpSeqs = new ArrayList<SequenceI>();
     grpSeqs.add(seqs[1]);
     grpSeqs.add(seqs[2]);
     grpSeqs.add(seqs[3]);
     grpSeqs.add(seqs[4]);
-    SequenceGroup seqGrp = new SequenceGroup(grpSeqs, "JGroup:1883305585",
+    SequenceGroup seqGrp = new SequenceGroup(grpSeqs,
+            "JGroup:1883305585",
             null, true, true, false, 21, 29);
     ColourSchemeI scheme = ColourSchemeMapper.getJalviewColourScheme(
             "zappo", seqGrp);
@@ -191,9 +195,9 @@ public class JSONFileTest
     TEST_SEQ_HEIGHT = expectedSeqs.size();
     TEST_GRP_HEIGHT = expectedGrps.size();
     TEST_ANOT_HEIGHT = expectedAnnots.size();
-    TEST_CS_HEIGHT = expectedColSel.getHiddenColumns().size();
+    TEST_CS_HEIGHT = expectedColSel.getHiddenRegions().size();
 
-    AlignExportSettingI exportSettings = new AlignExportSettingI()
+    exportSettings = new AlignExportSettingI()
     {
       @Override
       public boolean isExportHiddenSequences()
@@ -240,7 +244,7 @@ public class JSONFileTest
       jf = (JSONFile) formatAdapter.getAlignFile();
 
       AlignFrame af = new AlignFrame(alignment, jf.getHiddenSequences(),
-              jf.getColumnSelection(), AlignFrame.DEFAULT_WIDTH,
+              jf.getHiddenColumns(), AlignFrame.DEFAULT_WIDTH,
               AlignFrame.DEFAULT_HEIGHT);
       af.getViewport().setShowSequenceFeatures(jf.isShowSeqFeatures());
       String colourSchemeName = jf.getGlobalColourScheme();
@@ -309,13 +313,13 @@ public class JSONFileTest
   @Test(groups = { "Functional" })
   public void hiddenColsTest()
   {
-    ColumnSelection cs = testJsonFile.getColumnSelection();
+    HiddenColumns cs = testJsonFile.getHiddenColumns();
     Assert.assertNotNull(cs);
-    Assert.assertNotNull(cs.getHiddenColumns());
-    List<int[]> hiddenCols = cs.getHiddenColumns();
+    Assert.assertNotNull(cs.getHiddenRegions());
+    List<int[]> hiddenCols = cs.getHiddenRegions();
     Assert.assertEquals(hiddenCols.size(), TEST_CS_HEIGHT);
     Assert.assertEquals(hiddenCols.get(0), expectedColSel
-            .getHiddenColumns().get(0),
+            .getHiddenRegions().get(0),
             "Mismatched hidden columns!");
   }
 
@@ -337,6 +341,58 @@ public class JSONFileTest
             "Zappo colour scheme expected!");
   }
 
+  /**
+   * Test for bug JAL-2489, NPE when exporting BioJSON with global colour
+   * scheme, and a group colour scheme, set as 'None'
+   */
+  @Test(groups = { "Functional" })
+  public void testBioJSONRoundTripWithColourSchemeNone()
+  {
+    AppletFormatAdapter formatAdapter = new AppletFormatAdapter();
+
+    Alignment _alignment;
+    try
+    {
+      // load example BioJSON file
+      _alignment = (Alignment) formatAdapter.readFile(TEST_JSON_FILE,
+              DataSourceType.FILE, FileFormat.Json);
+      JSONFile bioJsonFile = (JSONFile) formatAdapter.getAlignFile();
+      AlignFrame alignFrame = new AlignFrame(_alignment,
+              bioJsonFile.getHiddenSequences(),
+              bioJsonFile.getHiddenColumns(), AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT);
+
+      /*
+       * Create a group on the alignment;
+       * Change global and group colour scheme to 'None' and perform round trip
+       */
+      SequenceGroup sg = new SequenceGroup();
+      sg.addSequence(_alignment.getSequenceAt(0), false);
+      sg.setColourScheme(null);
+      ColourSchemeI cs = ColourSchemeMapper.getJalviewColourScheme(
+              ResidueColourScheme.NONE, _alignment);
+      alignFrame.changeColour(cs);
+      alignFrame.getViewport().setFeaturesDisplayed(
+              bioJsonFile.getDisplayedFeatures());
+      formatAdapter = new AppletFormatAdapter(alignFrame.alignPanel,
+              exportSettings);
+      // export BioJSON string
+      String jsonOutput = formatAdapter.formatSequences(FileFormat.Json,
+              alignFrame.alignPanel.getAlignment(), false);
+      // read back Alignment from BioJSON string
+      formatAdapter = new AppletFormatAdapter();
+      formatAdapter.readFile(jsonOutput, DataSourceType.PASTE,
+              FileFormat.Json);
+      // assert 'None' colour scheme is retained after round trip
+      JSONFile _bioJsonFile = (JSONFile) formatAdapter.getAlignFile();
+      Assert.assertEquals(_bioJsonFile.getGlobalColourScheme(),
+              ResidueColourScheme.NONE);
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+    }
+  }
+
   @Test(groups = { "Functional" })
   public void isShowSeqFeaturesSet()
   {
@@ -509,4 +565,47 @@ public class JSONFileTest
     // System.out.println(">>>>>>>>>>>>>> features matched : " + matched);
     return matched;
   }
+
+  /**
+   * Test group roundtrip with null (None) group colour scheme
+   * 
+   * @throws IOException
+   */
+  @Test(groups = { "Functional" })
+  public void testGrpParsed_colourNone() throws IOException
+  {
+    AlignmentI copy = new Alignment(testAlignment);
+    SequenceGroup sg = testAlignment.getGroups().get(0);
+    SequenceGroup copySg = new SequenceGroup(new ArrayList<SequenceI>(),
+            sg.getName(),
+            null, sg.getDisplayBoxes(), sg.getDisplayText(),
+            sg.getColourText(), sg.getStartRes(), sg.getEndRes());
+    for (SequenceI seq : sg.getSequences())
+    {
+      int seqIndex = testAlignment.findIndex(seq);
+      copySg.addSequence(copy.getSequenceAt(seqIndex), false);
+    }
+    copy.addGroup(copySg);
+
+    AlignFrame af = new AlignFrame(copy, copy.getWidth(), copy.getHeight());
+    AppletFormatAdapter formatAdapter = new AppletFormatAdapter(
+            af.alignPanel);
+    String jsonOutput = formatAdapter.formatSequences(FileFormat.Json,
+            copy, false);
+    formatAdapter = new AppletFormatAdapter();
+    AlignmentI newAlignment = formatAdapter.readFile(jsonOutput,
+            DataSourceType.PASTE, FileFormat.Json);
+
+    Assert.assertNotNull(newAlignment.getGroups());
+    for (SequenceGroup seqGrp : newAlignment.getGroups())
+    {
+      SequenceGroup expectedGrp = expectedGrps.get(seqGrp.getName());
+      AssertJUnit.assertTrue(
+              "Failed SequenceGroup Test for >>> " + seqGrp.getName(),
+              isGroupMatched(expectedGrp, seqGrp));
+      passedCount++;
+    }
+    AssertJUnit.assertEquals("Some SequenceGroups did not pass the test",
+            TEST_GRP_HEIGHT, passedCount);
+  }
 }
index d198f0f..a92f5fb 100644 (file)
@@ -22,8 +22,8 @@ package jalview.io;
 
 import static org.testng.ConversionUtils.wrapDataProvider;
 
-import jalview.analysis.NJTree;
 import jalview.analysis.SequenceIdMatcher;
+import jalview.analysis.TreeModel;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequenceNode;
 import jalview.gui.JvOptionPane;
@@ -125,7 +125,8 @@ public class NewickFileTests
       stage = "Compare original and generated tree" + treename;
 
       Vector<SequenceNode> oseqs, nseqs;
-      oseqs = new NJTree(new SequenceI[0], nf).findLeaves(nf.getTree());
+      oseqs = new TreeModel(new SequenceI[0], null, nf).findLeaves(nf
+              .getTree());
       AssertJUnit.assertTrue(stage + "No nodes in original tree.",
               oseqs.size() > 0);
       SequenceI[] olsqs = new SequenceI[oseqs.size()];
@@ -133,7 +134,8 @@ public class NewickFileTests
       {
         olsqs[i] = (SequenceI) oseqs.get(i).element();
       }
-      nseqs = new NJTree(new SequenceI[0], nf_regen).findLeaves(nf_regen
+      nseqs = new TreeModel(new SequenceI[0], null, nf_regen)
+              .findLeaves(nf_regen
               .getTree());
       AssertJUnit.assertTrue(stage + "No nodes in regerated tree.",
               nseqs.size() > 0);
index f99c67a..d853350 100644 (file)
@@ -190,7 +190,8 @@ public class PhylipFileTests
             DataSourceType.PASTE, FileFormat.Phylip);
     assertNotNull("Couldn't parse reimported alignment data.", al_input);
 
-    StockholmFileTest.testAlignmentEquivalence(al, al_input, false);
+    StockholmFileTest.testAlignmentEquivalence(al, al_input, false, false,
+            false);
 
   }
 }
index d16fb5f..ae72de4 100644 (file)
@@ -53,7 +53,7 @@ public class RNAMLfileTest
   {
     StockholmFileTest.testFileIOwithFormat(new File(
             "examples/testdata/rna-alignment.xml"), FileFormat.Stockholm,
-            -1, -1);
+            -1, -1, true, true, true);
 
   }
 
diff --git a/test/jalview/io/ScoreMatrixFileTest.java b/test/jalview/io/ScoreMatrixFileTest.java
new file mode 100644 (file)
index 0000000..97349b5
--- /dev/null
@@ -0,0 +1,506 @@
+package jalview.io;
+
+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.assertTrue;
+import static org.testng.Assert.fail;
+
+import jalview.analysis.scoremodels.ScoreMatrix;
+import jalview.analysis.scoremodels.ScoreModels;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import org.testng.annotations.Test;
+
+public class ScoreMatrixFileTest
+{
+
+  /**
+   * Test a successful parse of a (small) score matrix file
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiMixedDelimiters()
+          throws MalformedURLException,
+          IOException
+  {
+    /*
+     * some messy but valid input data, with comma, space
+     * or tab (or combinations) as score value delimiters
+     * this example includes 'guide' symbols on score rows
+     */
+    String data = "ScoreMatrix MyTest (example)\n" + "A\tT\tU\tt\tx\t-\n"
+            + "A,1.1,1.2,1.3,1.4, 1.5, 1.6\n"
+            + "T,2.1 2.2 2.3 2.4 2.5 2.6\n"
+            + "U\t3.1\t3.2\t3.3\t3.4\t3.5\t3.6\t\n"
+            + "t, 5.1,5.3,5.3,5.4,5.5, 5.6\n"
+            + "x\t6.1, 6.2 6.3 6.4 6.5 6.6\n"
+            + "-, \t7.1\t7.2 7.3, 7.4, 7.5\t,7.6\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    ScoreMatrix sm = parser.parseMatrix();
+
+    assertNotNull(sm);
+    assertEquals(sm.getName(), "MyTest (example)");
+    assertEquals(sm.getSize(), 6);
+    assertNull(sm.getDescription());
+    assertTrue(sm.isDNA());
+    assertFalse(sm.isProtein());
+    assertEquals(sm.getMinimumScore(), 1.1f);
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1.1f);
+    assertEquals(sm.getPairwiseScore('A', 'T'), 1.2f);
+    assertEquals(sm.getPairwiseScore('a', 'T'), 1.2f); // A/a equivalent
+    assertEquals(sm.getPairwiseScore('A', 't'), 1.4f); // T/t not equivalent
+    assertEquals(sm.getPairwiseScore('a', 't'), 1.4f);
+    assertEquals(sm.getPairwiseScore('U', 'x'), 3.5f);
+    assertEquals(sm.getPairwiseScore('u', 'x'), 3.5f);
+    // X (upper) and '.' unmapped - get minimum score
+    assertEquals(sm.getPairwiseScore('U', 'X'), 1.1f);
+    assertEquals(sm.getPairwiseScore('A', '.'), 1.1f);
+    assertEquals(sm.getPairwiseScore('-', '-'), 7.6f);
+    assertEquals(sm.getPairwiseScore('A', (char) 128), 0f); // out of range
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_headerMissing()
+  {
+    String data;
+
+    data = "X Y\n1 2\n3 4\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Format error: 'ScoreMatrix <name>' should be the first non-comment line");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiNotEnoughRows()
+  {
+    String data = "ScoreMatrix MyTest\nX Y Z\n1 2 3\n4 5 6\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Expected 3 rows of score data in score matrix but only found 2");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiNotEnoughColumns()
+  {
+    String data = "ScoreMatrix MyTest\nX Y Z\n1 2 3\n4 5\n7 8 9\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Expected 3 scores at line 4: '4 5' but found 2");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiTooManyColumns()
+  {
+    /*
+     * with two too many columns:
+     */
+    String data = "ScoreMatrix MyTest\nX\tY\tZ\n1 2 3\n4 5 6 7\n8 9 10\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Expected 3 scores at line 4: '4 5 6 7' but found 4");
+    }
+
+    /*
+     * with guide character and one too many columns:
+     */
+    data = "ScoreMatrix MyTest\nX Y\nX 1 2\nY 3 4 5\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Expected 2 scores at line 4: 'Y 3 4 5' but found 3");
+    }
+
+    /*
+     * with no guide character and one too many columns
+     */
+    data = "ScoreMatrix MyTest\nX Y\n1 2\n3 4 5\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Expected 2 scores at line 4: '3 4 5' but found 3");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiTooManyRows()
+  {
+    String data = "ScoreMatrix MyTest\n\tX\tY\tZ\n1 2 3\n4 5 6\n7 8 9\n10 11 12\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Unexpected extra input line in score model file: '10 11 12'");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiBadDelimiter()
+  {
+    String data = "ScoreMatrix MyTest\n X Y Z\n1|2|3\n4|5|6\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Invalid score value '1|2|3' at line 3 column 0");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiBadFloat()
+  {
+    String data = "ScoreMatrix MyTest\n\tX\tY\tZ\n1 2 3\n4 five 6\n7 8 9\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Invalid score value 'five' at line 4 column 1");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiBadGuideCharacter()
+  {
+    String data = "ScoreMatrix MyTest\n\tX Y\nX 1 2\ny 3 4\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Error parsing score matrix at line 4, expected 'Y' but found 'y'");
+    }
+
+    data = "ScoreMatrix MyTest\n\tX Y\nXX 1 2\nY 3 4\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Error parsing score matrix at line 3, expected 'X' but found 'XX'");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiNameMissing()
+  {
+    /*
+     * Name missing on ScoreMatrix header line
+     */
+    String data = "ScoreMatrix\nX Y\n1 2\n3 4\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(
+              e.getMessage(),
+              "Format error: expected 'ScoreMatrix <name>', found 'ScoreMatrix' at line 1");
+    }
+  }
+
+  /**
+   * Test a successful parse of a (small) score matrix file
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiFormat() throws MalformedURLException,
+          IOException
+  {
+    // input including comment and blank lines
+    String data = "ScoreMatrix MyTest\n#comment\n\n" + "\tA\tB\tC\n"
+            + "A\t1.0\t2.0\t3.0\n" + "B\t4.0\t5.0\t6.0\n"
+            + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    ScoreMatrix sm = parser.parseMatrix();
+  
+    assertNotNull(sm);
+    assertEquals(sm.getName(), "MyTest");
+    assertEquals(parser.getMatrixName(), "MyTest");
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1.0f);
+    assertEquals(sm.getPairwiseScore('B', 'c'), 6.0f);
+    assertEquals(sm.getSize(), 3);
+  }
+
+  /**
+   * Test a successful parse of a (small) score matrix file
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaIndexBlosum80()
+          throws MalformedURLException,
+          IOException
+  {
+    FileParse fp = new FileParse("resources/scoreModel/blosum80.scm",
+            DataSourceType.FILE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    ScoreMatrix sm = parser.parseMatrix();
+  
+    assertNotNull(sm);
+    assertEquals(sm.getName(), "HENS920103");
+    assertEquals(sm.getDescription(),
+            "BLOSUM80 substitution matrix (Henikoff-Henikoff, 1992)");
+    assertFalse(sm.isDNA());
+    assertTrue(sm.isProtein());
+    assertEquals(20, sm.getSize());
+
+    assertEquals(sm.getPairwiseScore('A', 'A'), 7f);
+    assertEquals(sm.getPairwiseScore('A', 'R'), -3f);
+    assertEquals(sm.getPairwiseScore('r', 'a'), -3f); // A/a equivalent
+  }
+
+  /**
+   * Test a successful parse of a (small) score matrix file
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindexFormat() throws MalformedURLException,
+          IOException
+  {
+    /*
+     * aaindex format has scores for diagonal and below only
+     */
+    String data = "H MyTest\n" + "D My description\n" + "R PMID:1438297\n"
+            + "A Authors, names\n" + "T Journal title\n"
+            + "J Journal reference\n" + "* matrix in 1/3 Bit Units\n"
+            + "M rows = ABC, cols = ABC\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n"
+            + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    ScoreMatrix sm = parser.parseMatrix();
+  
+    assertNotNull(sm);
+    assertEquals(sm.getSize(), 3);
+    assertEquals(sm.getName(), "MyTest");
+    assertEquals(sm.getDescription(), "My description");
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1.0f);
+    assertEquals(sm.getPairwiseScore('A', 'B'), 4.0f);
+    assertEquals(sm.getPairwiseScore('A', 'C'), 7.0f);
+    assertEquals(sm.getPairwiseScore('B', 'A'), 4.0f);
+    assertEquals(sm.getPairwiseScore('B', 'B'), 5.0f);
+    assertEquals(sm.getPairwiseScore('B', 'C'), 8.0f);
+    assertEquals(sm.getPairwiseScore('C', 'C'), 9.0f);
+    assertEquals(sm.getPairwiseScore('C', 'B'), 8.0f);
+    assertEquals(sm.getPairwiseScore('C', 'A'), 7.0f);
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindex_mMissing()
+          throws MalformedURLException,
+          IOException
+  {
+    /*
+     * aaindex format but M cols=, rows= is missing
+     */
+    String data = "H MyTest\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n"
+            + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    try
+    {
+      parser.parseMatrix();
+      fail("Expected exception");
+    } catch (FileFormatException e)
+    {
+      assertEquals(e.getMessage(), "No alphabet specified in matrix file");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindex_rowColMismatch()
+          throws MalformedURLException,
+          IOException
+  {
+    String data = "H MyTest\n" + "M rows=ABC, cols=ABD\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n"
+            + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    try
+    {
+      parser.parseMatrix();
+      fail("Expected exception");
+    } catch (FileFormatException e)
+    {
+      assertEquals(
+              e.getMessage(),
+              "Unexpected aaIndex score matrix data at line 2: M rows=ABC, cols=ABD rows != cols");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_ncbiHeaderRepeated()
+  {
+    String data = "ScoreMatrix BLOSUM\nScoreMatrix PAM250\nX Y\n1 2\n3 4\n";
+    try
+    {
+      new ScoreMatrixFile(new FileParse(data, DataSourceType.PASTE))
+              .parseMatrix();
+      fail("expected exception");
+    } catch (IOException e)
+    {
+      assertEquals(e.getMessage(),
+              "Error: 'ScoreMatrix' repeated in file at line 2");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindex_tooManyRows()
+          throws MalformedURLException,
+          IOException
+  {
+    String data = "H MyTest\n" + "M rows=ABC, cols=ABC\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n" + "C\t7.0\t8.0\t9.0\n" + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    try
+    {
+      parser.parseMatrix();
+      fail("Expected exception");
+    } catch (FileFormatException e)
+    {
+      assertEquals(e.getMessage(), "Too many data rows in matrix file");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindex_extraDataLines()
+          throws MalformedURLException,
+          IOException
+  {
+    String data = "H MyTest\n" + "M rows=ABC, cols=ABC\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n" + "C\t7.0\t8.0\t9.0\n" + "something extra\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    try
+    {
+      parser.parseMatrix();
+      fail("Expected exception");
+    } catch (FileFormatException e)
+    {
+      assertEquals(e.getMessage(), "Too many data rows in matrix file");
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testParseMatrix_aaindex_tooFewColumns()
+          throws MalformedURLException,
+          IOException
+  {
+    String data = "H MyTest\n" + "M rows=ABC, cols=ABC\n" + "A\t1.0\n"
+            + "B\t4.0\t5.0\n" + "C\t7.0\t8.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+    try
+    {
+      parser.parseMatrix();
+      fail("Expected exception");
+    } catch (FileFormatException e)
+    {
+      assertEquals(
+              e.getMessage(),
+              "Expected 3 scores at line 5: 'C\t7.0\t8.0' but found 2");
+    }
+  }
+
+  /**
+   * Test a successful parse and register of a score matrix file
+   * 
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  @Test(groups = "Functional")
+  public void testParse_ncbiFormat() throws MalformedURLException,
+          IOException
+  {
+    assertNull(ScoreModels.getInstance().getScoreModel("MyNewTest", null));
+
+    String data = "ScoreMatrix MyNewTest\n" + "\tA\tB\tC\n"
+            + "A\t1.0\t2.0\t3.0\n" + "B\t4.0\t5.0\t6.0\n"
+            + "C\t7.0\t8.0\t9.0\n";
+    FileParse fp = new FileParse(data, DataSourceType.PASTE);
+    ScoreMatrixFile parser = new ScoreMatrixFile(fp);
+
+    parser.parse();
+  
+    ScoreMatrix sm = (ScoreMatrix) ScoreModels.getInstance().getScoreModel(
+            "MyNewTest", null);
+    assertNotNull(sm);
+    assertEquals(sm.getName(), "MyNewTest");
+    assertEquals(parser.getMatrixName(), "MyNewTest");
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1.0f);
+    assertEquals(sm.getPairwiseScore('B', 'c'), 6.0f);
+    assertEquals(sm.getSize(), 3);
+  }
+}
index 4028913..228c935 100644 (file)
@@ -33,10 +33,13 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.JvOptionPane;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -56,7 +59,8 @@ public class StockholmFileTest
   @Test(groups = { "Functional" })
   public void pfamFileIO() throws Exception
   {
-    testFileIOwithFormat(new File(PfamFile), FileFormat.Stockholm, -1, 0);
+    testFileIOwithFormat(new File(PfamFile), FileFormat.Stockholm, -1, 0,
+            false, false, false);
   }
 
   @Test(groups = { "Functional" })
@@ -81,7 +85,8 @@ public class StockholmFileTest
   @Test(groups = { "Functional" })
   public void rfamFileIO() throws Exception
   {
-    testFileIOwithFormat(new File(RfamFile), FileFormat.Stockholm, 2, 1);
+    testFileIOwithFormat(new File(RfamFile), FileFormat.Stockholm, 2, 1,
+            false, false, false);
   }
 
   /**
@@ -93,10 +98,14 @@ public class StockholmFileTest
    * @param ioformat
    *          - label for IO class used to write and read back in the data from
    *          f
+   * @param ignoreFeatures
+   * @param ignoreRowVisibility
+   * @param allowNullAnnotations
    */
 
   public static void testFileIOwithFormat(File f, FileFormatI ioformat,
-          int naliannot, int nminseqann)
+          int naliannot, int nminseqann, boolean ignoreFeatures,
+          boolean ignoreRowVisibility, boolean allowNullAnnotations)
   {
     System.out.println("Reading file: " + f);
     String ff = f.getPath();
@@ -130,7 +139,8 @@ public class StockholmFileTest
               "Identify routine could not recognise output generated by '"
                       + ioformat + "' writer",
               ioformat.equals(identifyoutput));
-      testAlignmentEquivalence(al, al_input, false);
+      testAlignmentEquivalence(al, al_input, ignoreFeatures,
+              ignoreRowVisibility, allowNullAnnotations);
       int numaliannot = 0, numsqswithali = 0;
       for (AlignmentAnnotation ala : al_input.getAlignmentAnnotation())
       {
@@ -174,6 +184,34 @@ public class StockholmFileTest
   public static void testAlignmentEquivalence(AlignmentI al,
           AlignmentI al_input, boolean ignoreFeatures)
   {
+    testAlignmentEquivalence(al, al_input, ignoreFeatures, false, false);
+  }
+
+  /**
+   * assert alignment equivalence - uses special comparators for RNA structure
+   * annotation rows.
+   * 
+   * @param al
+   *          'original'
+   * @param al_input
+   *          'secondary' or generated alignment from some datapreserving
+   *          transformation
+   * @param ignoreFeatures
+   *          when true, differences in sequence feature annotation are ignored
+   * 
+   * @param ignoreRowVisibility
+   *          when true, do not fail if there are differences in the visibility
+   *          of annotation rows
+   * @param allowNullAnnotation
+   *          when true, positions in alignment annotation that are null will be
+   *          considered equal to positions containing annotation where
+   *          Annotation.isWhitespace() returns true.
+   * 
+   */
+  public static void testAlignmentEquivalence(AlignmentI al,
+          AlignmentI al_input, boolean ignoreFeatures,
+          boolean ignoreRowVisibility, boolean allowNullAnnotation)
+  {
     assertNotNull("Original alignment was null", al);
     assertNotNull("Generated alignment was null", al_input);
 
@@ -201,13 +239,18 @@ public class StockholmFileTest
       {
         if (aa_new.length > i)
         {
-          assertTrue("Different alignment annotation at position " + i,
-                  equalss(aa_original[i], aa_new[i]));
+          assertEqualSecondaryStructure(
+                  "Different alignment annotation at position " + i,
+                  aa_original[i], aa_new[i], allowNullAnnotation);
           // compare graphGroup or graph properties - needed to verify JAL-1299
           assertEquals("Graph type not identical.", aa_original[i].graph,
                   aa_new[i].graph);
-          assertEquals("Visibility not identical.", aa_original[i].visible,
+          if (!ignoreRowVisibility)
+          {
+            assertEquals("Visibility not identical.",
+                    aa_original[i].visible,
                   aa_new[i].visible);
+          }
           assertEquals("Threshold line not identical.",
                   aa_original[i].threshold, aa_new[i].threshold);
           // graphGroup may differ, but pattern should be the same
@@ -310,8 +353,9 @@ public class StockholmFileTest
               {
                 annot_original = al.getSequenceAt(i).getAnnotation()[j];
                 annot_new = al_input.getSequenceAt(in).getAnnotation()[j];
-                assertTrue("Different annotation elements",
-                        equalss(annot_original, annot_new));
+                assertEqualSecondaryStructure(
+                        "Different annotation elements", annot_original,
+                        annot_new, allowNullAnnotation);
               }
             }
           }
@@ -333,39 +377,78 @@ public class StockholmFileTest
     }
   }
 
-  /*
-   * compare annotations
+  /**
+   * compare two annotation rows, with special support for secondary structure
+   * comparison. With RNA, only the value and the secondaryStructure symbols are
+   * compared, displayCharacter and description are ignored. Annotations where
+   * Annotation.isWhitespace() is true are always considered equal.
+   * 
+   * @param message
+   *          - not actually used yet..
+   * @param annot_or
+   *          - the original annotation
+   * @param annot_new
+   *          - the one compared to the original annotation
+   * @param allowNullEquivalence
+   *          when true, positions in alignment annotation that are null will be
+   *          considered equal to non-null positions for which
+   *          Annotation.isWhitespace() is true.
    */
-  private static boolean equalss(AlignmentAnnotation annot_or,
-          AlignmentAnnotation annot_new)
+  private static void assertEqualSecondaryStructure(String message,
+          AlignmentAnnotation annot_or, AlignmentAnnotation annot_new,
+          boolean allowNullEqivalence)
   {
+    // TODO: test to cover this assert behaves correctly for all allowed
+    // variations of secondary structure annotation row equivalence
     if (annot_or.annotations.length != annot_new.annotations.length)
     {
-      System.err.println("Different lengths for annotation row elements: "
+      fail("Different lengths for annotation row elements: "
               + annot_or.annotations.length + "!="
               + annot_new.annotations.length);
-      return false;
     }
+    boolean isRna = annot_or.isRNA();
+    assertTrue("Expected " + (isRna ? " valid RNA " : " no RNA ")
+            + " secondary structure in the row.",
+            isRna == annot_new.isRNA());
     for (int i = 0; i < annot_or.annotations.length; i++)
     {
       Annotation an_or = annot_or.annotations[i], an_new = annot_new.annotations[i];
       if (an_or != null && an_new != null)
       {
-        if (!an_or.displayCharacter.trim().equals(
-                an_new.displayCharacter.trim())
-                || !("" + an_or.secondaryStructure).trim().equals(
-                        ("" + an_new.secondaryStructure).trim())
-                || (an_or.description != an_new.description && !((an_or.description == null && an_new.description
-                        .trim().length() == 0)
-                        || (an_new.description == null && an_or.description
-                                .trim().length() == 0) || an_or.description
-                        .trim().equals(an_new.description.trim()))))
+
+        if (isRna)
         {
-          System.err.println("Annotation Element Mismatch\nElement " + i
-                  + " in original: " + annot_or.annotations[i].toString()
-                  + "\nElement " + i + " in new: "
-                  + annot_new.annotations[i].toString());
-          return false;
+          if (an_or.secondaryStructure != an_new.secondaryStructure
+                  || ((Float.isNaN(an_or.value) != Float
+                          .isNaN(an_new.value)) || an_or.value != an_new.value))
+          {
+            fail("Different RNA secondary structure at column " + i
+                    + " expected: [" + annot_or.annotations[i].toString()
+                    + "] but got: [" + annot_new.annotations[i].toString()
+                    + "]");
+          }
+        }
+        else
+        {
+          // not RNA secondary structure, so expect all elements to match...
+          if ((an_or.isWhitespace() != an_new.isWhitespace())
+                  || !an_or.displayCharacter.trim().equals(
+                  an_new.displayCharacter.trim())
+                  || !("" + an_or.secondaryStructure).trim().equals(
+                          ("" + an_new.secondaryStructure).trim())
+                  || (an_or.description != an_new.description && !((an_or.description == null && an_new.description
+                          .trim().length() == 0)
+                          || (an_new.description == null && an_or.description
+                                  .trim().length() == 0) || an_or.description
+                          .trim().equals(an_new.description.trim())))
+                  || !((Float.isNaN(an_or.value) && Float
+                          .isNaN(an_new.value)) || an_or.value == an_new.value))
+          {
+            fail("Annotation Element Mismatch\nElement " + i
+                    + " in original: " + annot_or.annotations[i].toString()
+                    + "\nElement " + i + " in new: "
+                    + annot_new.annotations[i].toString());
+          }
         }
       }
       else if (annot_or.annotations[i] == null
@@ -375,19 +458,202 @@ public class StockholmFileTest
       }
       else
       {
-        System.err.println("Annotation Element Mismatch\nElement "
-                + i
-                + " in original: "
-                + (annot_or.annotations[i] == null ? "is null"
-                        : annot_or.annotations[i].toString())
-                + "\nElement "
-                + i
-                + " in new: "
-                + (annot_new.annotations[i] == null ? "is null"
-                        : annot_new.annotations[i].toString()));
-        return false;
+        if (allowNullEqivalence)
+        {
+          if (an_or != null && an_or.isWhitespace())
+
+          {
+            continue;
+          }
+          if (an_new != null && an_new.isWhitespace())
+          {
+            continue;
+          }
+        }
+        // need also to test for null in one, non-SS annotation in other...
+        fail("Annotation Element Mismatch\nElement " + i + " in original: "
+                + (an_or == null ? "is null" : an_or.toString())
+                + "\nElement " + i + " in new: "
+                + (an_new == null ? "is null" : an_new.toString()));
+      }
+    }
+  }
+
+  /**
+   * @see assertEqualSecondaryStructure - test if two secondary structure
+   *      annotations are not equal
+   * @param message
+   * @param an_orig
+   * @param an_new
+   * @param allowNullEquivalence
+   */
+  public static void assertNotEqualSecondaryStructure(String message,
+          AlignmentAnnotation an_orig, AlignmentAnnotation an_new,
+          boolean allowNullEquivalence)
+  {
+    boolean thrown = false;
+    try
+    {
+      assertEqualSecondaryStructure("", an_orig, an_new,
+              allowNullEquivalence);
+    } catch (AssertionError af)
+    {
+      thrown = true;
+    }
+    if (!thrown)
+    {
+      fail("Expected difference for [" + an_orig + "] and [" + an_new + "]");
+    }
+  }
+  private AlignmentAnnotation makeAnnot(Annotation ae)
+  {
+    return new AlignmentAnnotation("label", "description", new Annotation[]
+    { ae });
+  }
+
+  @Test(groups={"Functional"})
+  public void testAnnotationEquivalence()
+  {
+    AlignmentAnnotation one = makeAnnot(new Annotation("", "", ' ', 1));
+    AlignmentAnnotation anotherOne = makeAnnot(new Annotation("", "", ' ',
+            1));
+    AlignmentAnnotation sheet = makeAnnot(new Annotation("","",'E',0f));
+    AlignmentAnnotation anotherSheet = makeAnnot(new Annotation("","",'E',0f)); 
+    AlignmentAnnotation sheetWithLabel = makeAnnot(new Annotation("1", "",
+            'E', 0f));
+    AlignmentAnnotation anotherSheetWithLabel = makeAnnot(new Annotation(
+            "1", "", 'E', 0f));
+    AlignmentAnnotation rnaNoDC = makeAnnot(new Annotation("","",'<',0f));
+    AlignmentAnnotation anotherRnaNoDC = makeAnnot(new Annotation("","",'<',0f));
+    AlignmentAnnotation rnaWithDC = makeAnnot(new Annotation("B", "", '<',
+            0f));
+    AlignmentAnnotation anotherRnaWithDC = makeAnnot(new Annotation("B",
+            "", '<', 0f));
+    
+    // check self equivalence
+    for (boolean allowNull : new boolean[] { true, false })
+    {
+      assertEqualSecondaryStructure("Should be equal", one, anotherOne,
+              allowNull);
+      assertEqualSecondaryStructure("Should be equal", sheet, anotherSheet,
+              allowNull);
+      assertEqualSecondaryStructure("Should be equal", sheetWithLabel,
+              anotherSheetWithLabel, allowNull);
+      assertEqualSecondaryStructure("Should be equal", rnaNoDC,
+              anotherRnaNoDC, allowNull);
+      assertEqualSecondaryStructure("Should be equal", rnaWithDC,
+              anotherRnaWithDC, allowNull);
+      // display character doesn't matter for RNA structure (for 2.10.2)
+      assertEqualSecondaryStructure("Should be equal", rnaWithDC, rnaNoDC,
+              allowNull);
+      assertEqualSecondaryStructure("Should be equal", rnaNoDC, rnaWithDC,
+              allowNull);
+    }
+
+    // verify others are different
+    List<AlignmentAnnotation> aaSet = Arrays.asList(one, sheet,
+            sheetWithLabel, rnaWithDC);
+    for (int p = 0; p < aaSet.size(); p++)
+    {
+      for (int q = 0; q < aaSet.size(); q++)
+      {
+        if (p != q)
+        {
+          assertNotEqualSecondaryStructure("Should be different",
+                    aaSet.get(p), aaSet.get(q), false);
+        }
+        else
+        {
+          assertEqualSecondaryStructure("Should be same", aaSet.get(p),
+                  aaSet.get(q), false);
+          assertEqualSecondaryStructure("Should be same", aaSet.get(p),
+                  aaSet.get(q), true);
+          assertNotEqualSecondaryStructure(
+                  "Should be different to empty anot", aaSet.get(p),
+                  makeAnnot(Annotation.EMPTY_ANNOTATION), false);
+          assertNotEqualSecondaryStructure(
+                  "Should be different to empty annot",
+                  makeAnnot(Annotation.EMPTY_ANNOTATION), aaSet.get(q),
+                  true);
+          assertNotEqualSecondaryStructure("Should be different to null",
+                  aaSet.get(p), makeAnnot(null), false);
+          assertNotEqualSecondaryStructure("Should be different to null",
+                  makeAnnot(null), aaSet.get(q), true);
+        }
       }
     }
-    return true;
+
+    // test null
+
+  }
+
+  String aliFile = ">Dm\nAAACCCUUUUACACACGGGAAAGGG";
+  String annFile = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n"
+          + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t"
+          + "(|(|(|(|, .|, .|, .|, .|)|)|)|)|\t0.0\nROWPROPERTIES\t"
+          + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false";
+
+  String annFileCurlyWuss = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n"
+          + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t"
+          + "(|(|(|(||{|{||{|{||)|)|)|)||}|}|}|}|\t0.0\nROWPROPERTIES\t"
+          + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false";
+  String annFileFullWuss = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n"
+          + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t"
+          + "(|(|(|(||{|{||[|[||)|)|)|)||}|}|]|]|\t0.0\nROWPROPERTIES\t"
+          + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false";
+
+  @Test(groups = { "Functional" })
+  public void secondaryStructureForRNASequence() throws Exception
+  {
+    roundTripSSForRNA(aliFile, annFile);
+  }
+
+  @Test(groups = { "Functional" })
+  public void curlyWUSSsecondaryStructureForRNASequence() throws Exception
+  {
+    roundTripSSForRNA(aliFile, annFileCurlyWuss);
+  }
+
+  @Test(groups = { "Functional" })
+  public void fullWUSSsecondaryStructureForRNASequence() throws Exception
+  {
+    roundTripSSForRNA(aliFile, annFileFullWuss);
+  }
+
+  @Test(groups = { "Functional" })
+  public void detectWussBrackets()
+  {
+    for (char ch : new char[] { '{', '}', '[', ']', '(', ')', '<', '>' })
+    {
+      Assert.assertTrue(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0),
+              "Didn't recognise " + ch + " as a WUSS bracket");
+    }
+    for (char ch : new char[] { '@', '!', 'V', 'Q', '*', ' ', '-', '.' })
+    {
+      Assert.assertFalse(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0),
+              "Shouldn't recognise " + ch + " as a WUSS bracket");
+    }
+  }
+  private static void roundTripSSForRNA(String aliFile, String annFile)
+          throws Exception
+  {
+    AlignmentI al = new AppletFormatAdapter().readFile(aliFile,
+            DataSourceType.PASTE, jalview.io.FileFormat.Fasta);
+    AnnotationFile aaf = new AnnotationFile();
+    aaf.readAnnotationFile(al, annFile, DataSourceType.PASTE);
+    al.getAlignmentAnnotation()[0].visible = true;
+
+    // TODO: create a better 'save as <format>' pattern
+    StockholmFile sf = new StockholmFile(al);
+
+    String stockholmFile = sf.print(al.getSequencesArray(), true);
+
+    AlignmentI newAl = new AppletFormatAdapter().readFile(stockholmFile,
+            DataSourceType.PASTE, jalview.io.FileFormat.Stockholm);
+    // AlignmentUtils.showOrHideSequenceAnnotations(newAl.getViewport()
+    // .getAlignment(), Arrays.asList("Secondary Structure"), newAl
+    // .getViewport().getAlignment().getSequences(), true, true);
+    testAlignmentEquivalence(al, newAl, true, true, true);
+
   }
 }
diff --git a/test/jalview/io/cache/AppCacheTest.java b/test/jalview/io/cache/AppCacheTest.java
new file mode 100644 (file)
index 0000000..5638028
--- /dev/null
@@ -0,0 +1,61 @@
+package jalview.io.cache;
+
+import java.util.LinkedHashSet;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AppCacheTest
+{
+  private AppCache appCache;
+
+  private static final String TEST_CACHE_KEY = "CACHE.UNIT_TEST";
+
+  private static final String TEST_FAKE_CACHE_KEY = "CACHE.UNIT_TEST_FAKE";
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpCache()
+  {
+    appCache = AppCache.getInstance();
+  }
+
+  public void generateTestCacheItems()
+  {
+    LinkedHashSet<String> testCacheItems = new LinkedHashSet<String>();
+    for (int x = 0; x < 10; x++)
+    {
+      testCacheItems.add("TestCache" + x);
+    }
+    appCache.putCache(TEST_CACHE_KEY, testCacheItems);
+    appCache.persistCache(TEST_CACHE_KEY);
+  }
+
+  @Test(groups = { "Functional" })
+  public void appCacheTest()
+  {
+    LinkedHashSet<String> cacheItems = appCache
+            .getAllCachedItemsFor(TEST_FAKE_CACHE_KEY);
+    Assert.assertEquals(cacheItems.size(), 0);
+    generateTestCacheItems();
+    cacheItems = appCache.getAllCachedItemsFor(TEST_CACHE_KEY);
+    Assert.assertEquals(cacheItems.size(), 10);
+    appCache.deleteCacheItems(TEST_CACHE_KEY);
+    cacheItems = appCache.getAllCachedItemsFor(TEST_CACHE_KEY);
+    Assert.assertEquals(cacheItems.size(), 0);
+  }
+
+  @Test(groups = { "Functional" })
+  public void appCacheLimitTest()
+  {
+    String limit = appCache.getCacheLimit(TEST_CACHE_KEY);
+    Assert.assertEquals(limit, "99");
+    limit = String.valueOf(appCache.updateCacheLimit(TEST_CACHE_KEY, 20));
+    Assert.assertEquals(limit, "20");
+    limit = appCache.getCacheLimit(TEST_CACHE_KEY);
+    Assert.assertEquals(limit, "20");
+    appCache.updateCacheLimit(TEST_CACHE_KEY, 99);
+  }
+
+
+}
diff --git a/test/jalview/io/cache/JvCacheableInputBoxTest.java b/test/jalview/io/cache/JvCacheableInputBoxTest.java
new file mode 100644 (file)
index 0000000..dfd7973
--- /dev/null
@@ -0,0 +1,70 @@
+package jalview.io.cache;
+
+import java.util.LinkedHashSet;
+
+import org.junit.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class JvCacheableInputBoxTest
+{
+
+  private AppCache appCache;
+
+  private static final String TEST_CACHE_KEY = "CACHE.UNIT_TEST";
+
+  private JvCacheableInputBox<String> cacheBox = new JvCacheableInputBox<String>(
+          TEST_CACHE_KEY);
+
+  @BeforeClass(alwaysRun = true)
+  private void setUpCache()
+  {
+    appCache = AppCache.getInstance();
+  }
+
+  @Test(groups = { "Functional" })
+  public void getUserInputTest()
+  {
+    String userInput = cacheBox.getUserInput();
+    Assert.assertEquals("", userInput);
+
+    String testInput = "TestInput";
+    cacheBox.addItem(testInput);
+    cacheBox.setSelectedItem(testInput);
+
+    try
+    {
+      // This 1ms delay is essential to prevent the
+      // assertion below from executing before
+      // swing thread finishes updating the combo-box
+      Thread.sleep(100);
+    } catch (InterruptedException e)
+    {
+      e.printStackTrace();
+    }
+    userInput = cacheBox.getUserInput();
+    Assert.assertEquals(testInput, userInput);
+  }
+
+  @Test(groups = { "Functional" })
+  public void updateCacheTest()
+  {
+    String testInput = "TestInput";
+    cacheBox.addItem(testInput);
+    cacheBox.setSelectedItem(testInput);
+    cacheBox.updateCache();
+    try
+    {
+      // This 1ms delay is essential to prevent the
+      // assertion below from executing before
+      // cacheBox.updateCache() finishes updating the cache
+      Thread.sleep(100);
+    } catch (InterruptedException e)
+    {
+      e.printStackTrace();
+    }
+    LinkedHashSet<String> foundCache = appCache
+            .getAllCachedItemsFor(TEST_CACHE_KEY);
+    Assert.assertTrue(foundCache.contains(testInput));
+  }
+}
index 95da22e..8ede59c 100644 (file)
@@ -31,6 +31,7 @@ JAVA_CONSOLE_SCREEN_HEIGHT=162
 PIR_MODELLER=false
 GAP_SYMBOL=-
 SHOW_QUALITY=true
+SHOW_OCCUPANCY=true
 SHOW_GROUP_CONSERVATION=false
 SHOW_JWS2_SERVICES=true
 SHOW_NPFEATS_TOOLTIP=true
index a4acbd0..97ded5a 100644 (file)
@@ -1,6 +1,8 @@
 package jalview.math;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
@@ -8,14 +10,17 @@ import java.util.Arrays;
 import java.util.Random;
 
 import org.testng.annotations.Test;
+import org.testng.internal.junit.ArrayAsserts;
 
 public class MatrixTest
 {
+  final static double DELTA = 0.000001d;
+
   @Test(groups = "Timing")
   public void testPreMultiply_timing()
   {
-    int rows = 500;
-    int cols = 1000;
+    int rows = 50; // increase to stress test timing
+    int cols = 100;
     double[][] d1 = new double[rows][cols];
     double[][] d2 = new double[cols][rows];
     Matrix m1 = new Matrix(d1);
@@ -37,20 +42,20 @@ public class MatrixTest
      * 1x3 times 3x1 is 1x1
      * 2x5 + 3x6 + 4*7 =  56
      */
-    Matrix m3 = m2.preMultiply(m1);
-    assertEquals(m3.rows, 1);
-    assertEquals(m3.cols, 1);
-    assertEquals(m3.value[0][0], 56d);
+    MatrixI m3 = m2.preMultiply(m1);
+    assertEquals(m3.height(), 1);
+    assertEquals(m3.width(), 1);
+    assertEquals(m3.getValue(0, 0), 56d);
 
     /*
      * 3x1 times 1x3 is 3x3
      */
     m3 = m1.preMultiply(m2);
-    assertEquals(m3.rows, 3);
-    assertEquals(m3.cols, 3);
-    assertEquals(Arrays.toString(m3.value[0]), "[10.0, 15.0, 20.0]");
-    assertEquals(Arrays.toString(m3.value[1]), "[12.0, 18.0, 24.0]");
-    assertEquals(Arrays.toString(m3.value[2]), "[14.0, 21.0, 28.0]");
+    assertEquals(m3.height(), 3);
+    assertEquals(m3.width(), 3);
+    assertEquals(Arrays.toString(m3.getRow(0)), "[10.0, 15.0, 20.0]");
+    assertEquals(Arrays.toString(m3.getRow(1)), "[12.0, 18.0, 24.0]");
+    assertEquals(Arrays.toString(m3.getRow(2)), "[14.0, 21.0, 28.0]");
   }
 
   @Test(
@@ -85,7 +90,18 @@ public class MatrixTest
   
   
   private boolean matrixEquals(Matrix m1, Matrix m2) {
-    return Arrays.deepEquals(m1.value, m2.value);
+    if (m1.width() != m2.width() || m1.height() != m2.height())
+    {
+      return false;
+    }
+    for (int i = 0; i < m1.height(); i++)
+    {
+      if (!Arrays.equals(m1.getRow(i), m2.getRow(i)))
+      {
+        return false;
+      }
+    }
+    return true;
   }
 
   @Test(groups = "Functional")
@@ -99,18 +115,18 @@ public class MatrixTest
      * (3020 30200)
      * (5040 50400)
      */
-    Matrix m1 = new Matrix(new double[][] { { 2, 3 }, { 4, 5 } });
-    Matrix m2 = new Matrix(new double[][] { { 10, 100 }, { 1000, 10000 } });
-    Matrix m3 = m1.postMultiply(m2);
-    assertEquals(Arrays.toString(m3.value[0]), "[3020.0, 30200.0]");
-    assertEquals(Arrays.toString(m3.value[1]), "[5040.0, 50400.0]");
+    MatrixI m1 = new Matrix(new double[][] { { 2, 3 }, { 4, 5 } });
+    MatrixI m2 = new Matrix(new double[][] { { 10, 100 }, { 1000, 10000 } });
+    MatrixI m3 = m1.postMultiply(m2);
+    assertEquals(Arrays.toString(m3.getRow(0)), "[3020.0, 30200.0]");
+    assertEquals(Arrays.toString(m3.getRow(1)), "[5040.0, 50400.0]");
 
     /*
      * also check m2.preMultiply(m1) - should be same as m1.postMultiply(m2) 
      */
     m3 = m2.preMultiply(m1);
-    assertEquals(Arrays.toString(m3.value[0]), "[3020.0, 30200.0]");
-    assertEquals(Arrays.toString(m3.value[1]), "[5040.0, 50400.0]");
+    assertEquals(Arrays.toString(m3.getRow(0)), "[3020.0, 30200.0]");
+    assertEquals(Arrays.toString(m3.getRow(1)), "[5040.0, 50400.0]");
 
     /*
      * m1 has more rows than columns
@@ -120,15 +136,15 @@ public class MatrixTest
     m1 = new Matrix(new double[][] { { 2 }, { 3 } });
     m2 = new Matrix(new double[][] { { 10, 100, 1000 } });
     m3 = m1.postMultiply(m2);
-    assertEquals(m3.rows, 2);
-    assertEquals(m3.cols, 3);
-    assertEquals(Arrays.toString(m3.value[0]), "[20.0, 200.0, 2000.0]");
-    assertEquals(Arrays.toString(m3.value[1]), "[30.0, 300.0, 3000.0]");
+    assertEquals(m3.height(), 2);
+    assertEquals(m3.width(), 3);
+    assertEquals(Arrays.toString(m3.getRow(0)), "[20.0, 200.0, 2000.0]");
+    assertEquals(Arrays.toString(m3.getRow(1)), "[30.0, 300.0, 3000.0]");
     m3 = m2.preMultiply(m1);
-    assertEquals(m3.rows, 2);
-    assertEquals(m3.cols, 3);
-    assertEquals(Arrays.toString(m3.value[0]), "[20.0, 200.0, 2000.0]");
-    assertEquals(Arrays.toString(m3.value[1]), "[30.0, 300.0, 3000.0]");
+    assertEquals(m3.height(), 2);
+    assertEquals(m3.width(), 3);
+    assertEquals(Arrays.toString(m3.getRow(0)), "[20.0, 200.0, 2000.0]");
+    assertEquals(Arrays.toString(m3.getRow(1)), "[30.0, 300.0, 3000.0]");
 
     /*
      * m1 has more columns than rows
@@ -141,19 +157,19 @@ public class MatrixTest
     m1 = new Matrix(new double[][] { { 2, 3, 4 } });
     m2 = new Matrix(new double[][] { { 5, 4 }, { 6, 3 }, { 7, 2 } });
     m3 = m1.postMultiply(m2);
-    assertEquals(m3.rows, 1);
-    assertEquals(m3.cols, 2);
-    assertEquals(m3.value[0][0], 56d);
-    assertEquals(m3.value[0][1], 25d);
+    assertEquals(m3.height(), 1);
+    assertEquals(m3.width(), 2);
+    assertEquals(m3.getRow(0)[0], 56d);
+    assertEquals(m3.getRow(0)[1], 25d);
 
     /*
      * and check premultiply equivalent
      */
     m3 = m2.preMultiply(m1);
-    assertEquals(m3.rows, 1);
-    assertEquals(m3.cols, 2);
-    assertEquals(m3.value[0][0], 56d);
-    assertEquals(m3.value[0][1], 25d);
+    assertEquals(m3.height(), 1);
+    assertEquals(m3.width(), 2);
+    assertEquals(m3.getRow(0)[0], 56d);
+    assertEquals(m3.getRow(0)[1], 25d);
   }
 
   @Test(groups = "Functional")
@@ -172,7 +188,8 @@ public class MatrixTest
       }
     }
     Matrix m1 = new Matrix(in);
-    Matrix m2 = m1.copy();
+    Matrix m2 = (Matrix) m1.copy();
+    assertNotSame(m1, m2);
     assertTrue(matrixEquals(m1, m2));
   }
 
@@ -200,12 +217,12 @@ public class MatrixTest
     // / origmat.print(System.out);
     // System.out.println();
     // System.out.println(" --- transpose matrix ---- ");
-    Matrix trans = origmat.transpose();
+    MatrixI trans = origmat.transpose();
   
     // trans.print(System.out);
     // System.out.println();
     // System.out.println(" --- OrigT * Orig ---- ");
-    Matrix symm = trans.postMultiply(origmat);
+    MatrixI symm = trans.postMultiply(origmat);
   
     // symm.print(System.out);
     // System.out.println();
@@ -255,4 +272,263 @@ public class MatrixTest
     // }
     // System.out.println();
   }
+
+  @Test(groups = "Timing")
+  public void testSign()
+  {
+    assertEquals(Matrix.sign(-1, -2), -1d);
+    assertEquals(Matrix.sign(-1, 2), 1d);
+    assertEquals(Matrix.sign(-1, 0), 1d);
+    assertEquals(Matrix.sign(1, -2), -1d);
+    assertEquals(Matrix.sign(1, 2), 1d);
+    assertEquals(Matrix.sign(1, 0), 1d);
+  }
+
+  /**
+   * Helper method to make values for a sparse, pseudo-random symmetric matrix
+   * 
+   * @param rows
+   * @param cols
+   * @param occupancy
+   *          one in 'occupancy' entries will be non-zero
+   * @return
+   */
+  public double[][] getSparseValues(int rows, int cols, int occupancy)
+  {
+    Random r = new Random(1729);
+
+    /*
+     * generate whole number values between -12 and +12
+     * (to mimic score matrices used in Jalview)
+     */
+    double[][] d = new double[rows][cols];
+    int m = 0;
+    for (int i = 0; i < rows; i++)
+    {
+      if (++m % occupancy == 0)
+      {
+        d[i][i] = r.nextInt() % 13; // diagonal
+      }
+      for (int j = 0; j < i; j++)
+      {
+        if (++m % occupancy == 0)
+        {
+          d[i][j] = r.nextInt() % 13;
+          d[j][i] = d[i][j];
+        }
+      }
+    }
+    return d;
+  
+  }
+
+  /**
+   * Verify that the results of method tred() are the same if the calculation is
+   * redone
+   */
+  @Test(groups = "Functional")
+  public void testTred_reproducible()
+  {
+    /*
+     * make a pseudo-random symmetric matrix as required for tred/tqli
+     */
+    int rows = 10;
+    int cols = rows;
+    double[][] d = getSparseValues(rows, cols, 3);
+  
+    /*
+     * make a copy of the values so m1, m2 are not
+     * sharing arrays!
+     */
+    double[][] d1 = new double[rows][cols];
+    for (int row = 0; row < rows; row++)
+    {
+      for (int col = 0; col < cols; col++)
+      {
+        d1[row][col] = d[row][col];
+      }
+    }
+    Matrix m1 = new Matrix(d);
+    Matrix m2 = new Matrix(d1);
+    assertMatricesMatch(m1, m2); // sanity check
+    m1.tred();
+    m2.tred();
+    assertMatricesMatch(m1, m2);
+  }
+
+  private void assertMatricesMatch(MatrixI m1, MatrixI m2)
+  {
+    if (m1.height() != m2.height())
+    {
+      fail("height mismatch");
+    }
+    if (m1.width() != m2.width())
+    {
+      fail("width mismatch");
+    }
+    for (int row = 0; row < m1.height(); row++)
+    {
+      for (int col = 0; col < m1.width(); col++)
+      {
+        double v2 = m2.getValue(row, col);
+        double v1 = m1.getValue(row, col);
+        if (Math.abs(v1 - v2) > DELTA)
+        {
+          fail(String.format("At [%d, %d] %f != %f", row, col, v1, v2));
+        }
+      }
+    }
+    ArrayAsserts.assertArrayEquals(m1.getD(), m2.getD(), 0.00001d);
+    ArrayAsserts.assertArrayEquals(m1.getE(), m2.getE(), 0.00001d);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindMinMax()
+  {
+    /*
+     * empty matrix case
+     */
+    Matrix m = new Matrix(new double[][] { {} });
+    assertNull(m.findMinMax());
+
+    /*
+     * normal case
+     */
+    double[][] vals = new double[2][];
+    vals[0] = new double[] {7d, 1d, -2.3d};
+    vals[1] = new double[] {-12d, 94.3d, -102.34d};
+    m = new Matrix(vals);
+    double[] minMax = m.findMinMax();
+    assertEquals(minMax[0], -102.34d);
+    assertEquals(minMax[1], 94.3d);
+  }
+
+  @Test(groups = { "Functional", "Timing" })
+  public void testFindMinMax_timing()
+  {
+    Random r = new Random();
+    int size = 1000; // increase to stress test timing
+    double[][] vals = new double[size][size];
+    double max = -Double.MAX_VALUE;
+    double min = Double.MAX_VALUE;
+    for (int i = 0; i < size; i++)
+    {
+      vals[i] = new double[size];
+      for (int j = 0; j < size; j++)
+      {
+        // use nextLong rather than nextDouble to include negative values
+        double d = r.nextLong();
+        if (d > max)
+        {
+          max = d;
+        }
+        if (d < min)
+        {
+          min = d;
+        }
+        vals[i][j] = d;
+      }
+    }
+    Matrix m = new Matrix(vals);
+    long now = System.currentTimeMillis();
+    double[] minMax = m.findMinMax();
+    System.out.println(String.format("findMinMax for %d x %d took %dms",
+            size, size, (System.currentTimeMillis() - now)));
+    assertEquals(minMax[0], min);
+    assertEquals(minMax[1], max);
+  }
+
+  /**
+   * Test range reversal with maximum value becoming zero
+   */
+  @Test(groups = "Functional")
+  public void testReverseRange_maxToZero()
+  {
+    Matrix m1 = new Matrix(
+            new double[][] { { 2, 3.5, 4 }, { -3.4, 4, 15 } });
+
+    /*
+     * subtract all from max: range -3.4 to 15 becomes 18.4 to 0
+     */
+    m1.reverseRange(true);
+    assertEquals(m1.getValue(0, 0), 13d, DELTA);
+    assertEquals(m1.getValue(0, 1), 11.5d, DELTA);
+    assertEquals(m1.getValue(0, 2), 11d, DELTA);
+    assertEquals(m1.getValue(1, 0), 18.4d, DELTA);
+    assertEquals(m1.getValue(1, 1), 11d, DELTA);
+    assertEquals(m1.getValue(1, 2), 0d, DELTA);
+
+    /*
+     * repeat operation - range is now 0 to 18.4
+     */
+    m1.reverseRange(true);
+    assertEquals(m1.getValue(0, 0), 5.4d, DELTA);
+    assertEquals(m1.getValue(0, 1), 6.9d, DELTA);
+    assertEquals(m1.getValue(0, 2), 7.4d, DELTA);
+    assertEquals(m1.getValue(1, 0), 0d, DELTA);
+    assertEquals(m1.getValue(1, 1), 7.4d, DELTA);
+    assertEquals(m1.getValue(1, 2), 18.4d, DELTA);
+  }
+
+  /**
+   * Test range reversal with minimum and maximum values swapped
+   */
+  @Test(groups = "Functional")
+  public void testReverseRange_swapMinMax()
+  {
+    Matrix m1 = new Matrix(
+            new double[][] { { 2, 3.5, 4 }, { -3.4, 4, 15 } });
+  
+    /*
+     * swap all values in min-max range
+     * = subtract from (min + max = 11.6) 
+     * range -3.4 to 15 becomes 18.4 to -3.4
+     */
+    m1.reverseRange(false);
+    assertEquals(m1.getValue(0, 0), 9.6d, DELTA);
+    assertEquals(m1.getValue(0, 1), 8.1d, DELTA);
+    assertEquals(m1.getValue(0, 2), 7.6d, DELTA);
+    assertEquals(m1.getValue(1, 0), 15d, DELTA);
+    assertEquals(m1.getValue(1, 1), 7.6d, DELTA);
+    assertEquals(m1.getValue(1, 2), -3.4d, DELTA);
+  
+    /*
+     * repeat operation - original values restored
+     */
+    m1.reverseRange(false);
+    assertEquals(m1.getValue(0, 0), 2d, DELTA);
+    assertEquals(m1.getValue(0, 1), 3.5d, DELTA);
+    assertEquals(m1.getValue(0, 2), 4d, DELTA);
+    assertEquals(m1.getValue(1, 0), -3.4d, DELTA);
+    assertEquals(m1.getValue(1, 1), 4d, DELTA);
+    assertEquals(m1.getValue(1, 2), 15d, DELTA);
+  }
+
+  @Test(groups = "Functional")
+  public void testMultiply()
+  {
+    Matrix m = new Matrix(new double[][] { { 2, 3.5, 4 }, { -3.4, 4, 15 } });
+    m.multiply(2d);
+    assertEquals(m.getValue(0, 0), 4d, DELTA);
+    assertEquals(m.getValue(0, 1), 7d, DELTA);
+    assertEquals(m.getValue(0, 2), 8d, DELTA);
+    assertEquals(m.getValue(1, 0), -6.8d, DELTA);
+    assertEquals(m.getValue(1, 1), 8d, DELTA);
+    assertEquals(m.getValue(1, 2), 30d, DELTA);
+  }
+
+  @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    double[][] values = new double[][] { { 1, 2, 3 }, { 4, 5, 6 } };
+    Matrix m = new Matrix(values);
+    assertEquals(m.getValue(0, 0), 1d, DELTA);
+
+    /*
+     * verify the matrix has a copy of the original array
+     */
+    assertNotSame(values[0], m.getRow(0));
+    values[0][0] = -1d;
+    assertEquals(m.getValue(0, 0), 1d, DELTA); // unchanged
+  }
 }
diff --git a/test/jalview/math/SparseMatrixTest.java b/test/jalview/math/SparseMatrixTest.java
new file mode 100644 (file)
index 0000000..3c2ccaa
--- /dev/null
@@ -0,0 +1,416 @@
+package jalview.math;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Random;
+
+import org.testng.annotations.Test;
+import org.testng.internal.junit.ArrayAsserts;
+
+public class SparseMatrixTest
+{
+  final static double DELTA = 0.0001d;
+
+  Random r = new Random(1729);
+
+  @Test(groups = "Functional")
+  public void testConstructor()
+  {
+    MatrixI m1 = new SparseMatrix(
+            new double[][] { { 2, 0, 4 }, { 0, 6, 0 } });
+    assertEquals(m1.getValue(0, 0), 2d);
+    assertEquals(m1.getValue(0, 1), 0d);
+    assertEquals(m1.getValue(0, 2), 4d);
+    assertEquals(m1.getValue(1, 0), 0d);
+    assertEquals(m1.getValue(1, 1), 6d);
+    assertEquals(m1.getValue(1, 2), 0d);
+  }
+
+  @Test(groups = "Functional")
+  public void testTranspose()
+  {
+    MatrixI m1 = new SparseMatrix(
+            new double[][] { { 2, 0, 4 }, { 5, 6, 0 } });
+    MatrixI m2 = m1.transpose();
+    assertTrue(m2 instanceof SparseMatrix);
+    assertEquals(m2.height(), 3);
+    assertEquals(m2.width(), 2);
+    assertEquals(m2.getValue(0, 0), 2d);
+    assertEquals(m2.getValue(0, 1), 5d);
+    assertEquals(m2.getValue(1, 0), 0d);
+    assertEquals(m2.getValue(1, 1), 6d);
+    assertEquals(m2.getValue(2, 0), 4d);
+    assertEquals(m2.getValue(2, 1), 0d);
+  }
+  @Test(groups = "Functional")
+  public void testPreMultiply()
+  {
+    MatrixI m1 = new SparseMatrix(new double[][] { { 2, 3, 4 } }); // 1x3
+    MatrixI m2 = new SparseMatrix(new double[][] { { 5 }, { 6 }, { 7 } }); // 3x1
+
+    /*
+     * 1x3 times 3x1 is 1x1
+     * 2x5 + 3x6 + 4*7 =  56
+     */
+    MatrixI m3 = m2.preMultiply(m1);
+    assertFalse(m3 instanceof SparseMatrix);
+    assertEquals(m3.height(), 1);
+    assertEquals(m3.width(), 1);
+    assertEquals(m3.getValue(0, 0), 56d);
+
+    /*
+     * 3x1 times 1x3 is 3x3
+     */
+    m3 = m1.preMultiply(m2);
+    assertEquals(m3.height(), 3);
+    assertEquals(m3.width(), 3);
+    assertEquals(m3.getValue(0, 0), 10d);
+    assertEquals(m3.getValue(0, 1), 15d);
+    assertEquals(m3.getValue(0, 2), 20d);
+    assertEquals(m3.getValue(1, 0), 12d);
+    assertEquals(m3.getValue(1, 1), 18d);
+    assertEquals(m3.getValue(1, 2), 24d);
+    assertEquals(m3.getValue(2, 0), 14d);
+    assertEquals(m3.getValue(2, 1), 21d);
+    assertEquals(m3.getValue(2, 2), 28d);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testPreMultiply_tooManyColumns()
+  {
+    Matrix m1 = new SparseMatrix(
+            new double[][] { { 2, 3, 4 }, { 3, 4, 5 } }); // 2x3
+
+    /*
+     * 2x3 times 2x3 invalid operation - 
+     * multiplier has more columns than multiplicand has rows
+     */
+    m1.preMultiply(m1);
+    fail("Expected exception");
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testPreMultiply_tooFewColumns()
+  {
+    Matrix m1 = new SparseMatrix(
+            new double[][] { { 2, 3, 4 }, { 3, 4, 5 } }); // 2x3
+
+    /*
+     * 3x2 times 3x2 invalid operation - 
+     * multiplier has more columns than multiplicand has row
+     */
+    m1.preMultiply(m1);
+    fail("Expected exception");
+  }
+  
+  @Test(groups = "Functional")
+  public void testPostMultiply()
+  {
+    /*
+     * Square matrices
+     * (2 3) . (10   100)
+     * (4 5)   (1000 10000)
+     * =
+     * (3020 30200)
+     * (5040 50400)
+     */
+    MatrixI m1 = new SparseMatrix(new double[][] { { 2, 3 }, { 4, 5 } });
+    MatrixI m2 = new SparseMatrix(new double[][] { { 10, 100 },
+        { 1000, 10000 } });
+    MatrixI m3 = m1.postMultiply(m2);
+    assertEquals(m3.getValue(0, 0), 3020d);
+    assertEquals(m3.getValue(0, 1), 30200d);
+    assertEquals(m3.getValue(1, 0), 5040d);
+    assertEquals(m3.getValue(1, 1), 50400d);
+
+    /*
+     * also check m2.preMultiply(m1) - should be same as m1.postMultiply(m2) 
+     */
+    MatrixI m4 = m2.preMultiply(m1);
+    assertMatricesMatch(m3, m4, 0.00001d);
+
+    /*
+     * m1 has more rows than columns
+     * (2).(10 100 1000) = (20 200 2000)
+     * (3)                 (30 300 3000)
+     */
+    m1 = new SparseMatrix(new double[][] { { 2 }, { 3 } });
+    m2 = new SparseMatrix(new double[][] { { 10, 100, 1000 } });
+    m3 = m1.postMultiply(m2);
+    assertEquals(m3.height(), 2);
+    assertEquals(m3.width(), 3);
+    assertEquals(m3.getValue(0, 0), 20d);
+    assertEquals(m3.getValue(0, 1), 200d);
+    assertEquals(m3.getValue(0, 2), 2000d);
+    assertEquals(m3.getValue(1, 0), 30d);
+    assertEquals(m3.getValue(1, 1), 300d);
+    assertEquals(m3.getValue(1, 2), 3000d);
+
+    m4 = m2.preMultiply(m1);
+    assertMatricesMatch(m3, m4, 0.00001d);
+
+    /*
+     * m1 has more columns than rows
+     * (2 3 4) . (5 4) = (56 25)
+     *           (6 3) 
+     *           (7 2)
+     * [0, 0] = 2*5 + 3*6 + 4*7 = 56
+     * [0, 1] = 2*4 + 3*3 + 4*2 = 25  
+     */
+    m1 = new SparseMatrix(new double[][] { { 2, 3, 4 } });
+    m2 = new SparseMatrix(new double[][] { { 5, 4 }, { 6, 3 }, { 7, 2 } });
+    m3 = m1.postMultiply(m2);
+    assertEquals(m3.height(), 1);
+    assertEquals(m3.width(), 2);
+    assertEquals(m3.getValue(0, 0), 56d);
+    assertEquals(m3.getValue(0, 1), 25d);
+
+    /*
+     * and check premultiply equivalent
+     */
+    m4 = m2.preMultiply(m1);
+    assertMatricesMatch(m3, m4, 0.00001d);
+  }
+
+  @Test(groups = "Timing")
+  public void testSign()
+  {
+    assertEquals(Matrix.sign(-1, -2), -1d);
+    assertEquals(Matrix.sign(-1, 2), 1d);
+    assertEquals(Matrix.sign(-1, 0), 1d);
+    assertEquals(Matrix.sign(1, -2), -1d);
+    assertEquals(Matrix.sign(1, 2), 1d);
+    assertEquals(Matrix.sign(1, 0), 1d);
+  }
+
+  /**
+   * Verify that the results of method tred() are the same for SparseMatrix as
+   * they are for Matrix (i.e. a regression test rather than an absolute test of
+   * correctness of results)
+   */
+  @Test(groups = "Functional")
+  public void testTred_matchesMatrix()
+  {
+    /*
+     * make a pseudo-random symmetric matrix as required for tred/tqli
+     */
+    int rows = 10;
+    int cols = rows;
+    double[][] d = getSparseValues(rows, cols, 3);
+
+    /*
+     * make a copy of the values so m1, m2 are not
+     * sharing arrays!
+     */
+    double[][] d1 = new double[rows][cols];
+    for (int row = 0; row < rows; row++)
+    {
+      for (int col = 0; col < cols; col++)
+      {
+        d1[row][col] = d[row][col];
+      }
+    }
+    Matrix m1 = new Matrix(d);
+    Matrix m2 = new SparseMatrix(d1);
+    assertMatricesMatch(m1, m2, 0.00001d); // sanity check
+    m1.tred();
+    m2.tred();
+    assertMatricesMatch(m1, m2, 0.00001d);
+  }
+
+  private void assertMatricesMatch(MatrixI m1, MatrixI m2, double delta)
+  {
+    if (m1.height() != m2.height())
+    {
+      fail("height mismatch");
+    }
+    if (m1.width() != m2.width())
+    {
+      fail("width mismatch");
+    }
+    for (int row = 0; row < m1.height(); row++)
+    {
+      for (int col = 0; col < m1.width(); col++)
+      {
+        double v2 = m2.getValue(row, col);
+        double v1 = m1.getValue(row, col);
+        if (Math.abs(v1 - v2) > DELTA)
+        {
+          fail(String.format("At [%d, %d] %f != %f", row, col, v1, v2));
+        }
+      }
+    }
+    ArrayAsserts.assertArrayEquals(m1.getD(), m2.getD(), delta);
+    ArrayAsserts.assertArrayEquals(m1.getE(), m2.getE(), 0.00001d);
+  }
+
+  @Test
+  public void testGetValue()
+  {
+    double[][] d = new double[][] { { 0, 0, 1, 0, 0 }, { 2, 3, 0, 0, 0 },
+        { 4, 0, 0, 0, 5 } };
+    MatrixI m = new SparseMatrix(d);
+    for (int row = 0; row < 3; row++)
+    {
+      for (int col = 0; col < 5; col++)
+      {
+        assertEquals(m.getValue(row, col), d[row][col],
+                String.format("At [%d, %d]", row, col));
+      }
+    }
+  }
+
+  /**
+   * Verify that the results of method tqli() are the same for SparseMatrix as
+   * they are for Matrix (i.e. a regression test rather than an absolute test of
+   * correctness of results)
+   * 
+   * @throws Exception
+   */
+  @Test(groups = "Functional")
+  public void testTqli_matchesMatrix() throws Exception
+  {
+    /*
+     * make a pseudo-random symmetric matrix as required for tred
+     */
+    int rows = 6;
+    int cols = rows;
+    double[][] d = getSparseValues(rows, cols, 3);
+  
+    /*
+     * make a copy of the values so m1, m2 are not
+     * sharing arrays!
+     */
+    double[][] d1 = new double[rows][cols];
+    for (int row = 0; row < rows; row++)
+    {
+      for (int col = 0; col < cols; col++)
+      {
+        d1[row][col] = d[row][col];
+      }
+    }
+    Matrix m1 = new Matrix(d);
+    Matrix m2 = new SparseMatrix(d1);
+
+    // have to do tred() before doing tqli()
+    m1.tred();
+    m2.tred();
+    assertMatricesMatch(m1, m2, 0.00001d);
+
+    m1.tqli();
+    m2.tqli();
+    assertMatricesMatch(m1, m2, 0.00001d);
+  }
+
+  /**
+   * Helper method to make values for a sparse, pseudo-random symmetric matrix
+   * 
+   * @param rows
+   * @param cols
+   * @param occupancy
+   *          one in 'occupancy' entries will be non-zero
+   * @return
+   */
+  public double[][] getSparseValues(int rows, int cols, int occupancy)
+  {
+    /*
+     * generate whole number values between -12 and +12
+     * (to mimic score matrices used in Jalview)
+     */
+    double[][] d = new double[rows][cols];
+    int m = 0;
+    for (int i = 0; i < rows; i++)
+    {
+      if (++m % occupancy == 0)
+      {
+        d[i][i] = r.nextInt() % 13; // diagonal
+      }
+      for (int j = 0; j < i; j++)
+      {
+        if (++m % occupancy == 0)
+        {
+          d[i][j] = r.nextInt() % 13;
+          d[j][i] = d[i][j];
+        }
+      }
+    }
+    return d;
+
+  }
+
+  /**
+   * Test that verifies that the result of preMultiply is a SparseMatrix if more
+   * than 80% zeroes, else a Matrix
+   */
+  @Test(groups = "Functional")
+  public void testPreMultiply_sparseProduct()
+  {
+    MatrixI m1 = new SparseMatrix(new double[][] { { 1 }, { 0 }, { 0 },
+        { 0 }, { 0 } }); // 5x1
+    MatrixI m2 = new SparseMatrix(new double[][] { { 1, 1, 1, 1 } }); // 1x4
+  
+    /*
+     * m1.m2 makes a row of 4 1's, and 4 rows of zeros
+     * 20% non-zero so not 'sparse'
+     */
+    MatrixI m3 = m2.preMultiply(m1);
+    assertFalse(m3 instanceof SparseMatrix);
+
+    /*
+     * replace a 1 with a 0 in the product:
+     * it is now > 80% zero so 'sparse'
+     */
+    m2 = new SparseMatrix(new double[][] { { 1, 1, 1, 0 } });
+    m3 = m2.preMultiply(m1);
+    assertTrue(m3 instanceof SparseMatrix);
+  }
+
+  @Test(groups = "Functional")
+  public void testFillRatio()
+  {
+    SparseMatrix m1 = new SparseMatrix(new double[][] { { 2, 0, 4, 1, 0 },
+    { 0, 6, 0, 0, 0 } });
+    assertEquals(m1.getFillRatio(), 0.4f);
+  }
+
+  /**
+   * Verify that the results of method tred() are the same if the calculation is
+   * redone
+   */
+  @Test(groups = "Functional")
+  public void testTred_reproducible()
+  {
+    /*
+     * make a pseudo-random symmetric matrix as required for tred/tqli
+     */
+    int rows = 10;
+    int cols = rows;
+    double[][] d = getSparseValues(rows, cols, 3);
+  
+    /*
+     * make a copy of the values so m1, m2 are not
+     * sharing arrays!
+     */
+    double[][] d1 = new double[rows][cols];
+    for (int row = 0; row < rows; row++)
+    {
+      for (int col = 0; col < cols; col++)
+      {
+        d1[row][col] = d[row][col];
+      }
+    }
+    Matrix m1 = new SparseMatrix(d);
+    Matrix m2 = new SparseMatrix(d1);
+    assertMatricesMatch(m1, m2, 1.0e16); // sanity check
+    m1.tred();
+    m2.tred();
+    assertMatricesMatch(m1, m2, 0.00001d);
+  }
+}
\ No newline at end of file
diff --git a/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java b/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
new file mode 100644 (file)
index 0000000..59566ed
--- /dev/null
@@ -0,0 +1,448 @@
+package jalview.renderer.seqfeatures;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.FeatureRenderer;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.schemes.FeatureColour;
+
+import java.awt.Color;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for feature colour determination, including but not limited to
+ * <ul>
+ * <li>gap position</li>
+ * <li>no features present</li>
+ * <li>features present but show features turned off</li>
+ * <li>features displayed but selected feature turned off</li>
+ * <li>features displayed but feature group turned off</li>
+ * <li>feature displayed but none at the specified position</li>
+ * <li>multiple features at position, with no transparency</li>
+ * <li>multiple features at position, with transparency</li>
+ * <li>score graduated feature colour</li>
+ * <li>contact feature start at the selected position</li>
+ * <li>contact feature end at the selected position</li>
+ * <li>contact feature straddling the selected position (not shown)</li>
+ * </ul>
+ */
+public class FeatureColourFinderTest
+{
+  private AlignViewport av;
+
+  private SequenceI seq;
+
+  private FeatureColourFinder finder;
+
+  private AlignFrame af;
+
+  private FeatureRenderer fr;
+
+  @BeforeTest(alwaysRun = true)
+  public void setUp()
+  {
+    // aligned column 8 is sequence position 6
+    String s = ">s1\nABCDE---FGHIJKLMNOPQRSTUVWXYZ\n";
+    af = new FileLoader().LoadFileWaitTillLoaded(s,
+            DataSourceType.PASTE);
+    av = af.getViewport();
+    seq = av.getAlignment().getSequenceAt(0);
+    fr = af.getFeatureRenderer();
+    finder = new FeatureColourFinder(fr);
+  }
+
+  /**
+   * Clear down any sequence features before each test (not as easy as it
+   * sounds...)
+   */
+  @BeforeMethod(alwaysRun = true)
+  public void setUpBeforeTest()
+  {
+    SequenceFeature[] sfs = seq.getSequenceFeatures();
+    if (sfs != null)
+    {
+      for (SequenceFeature sf : sfs)
+      {
+        seq.deleteFeature(sf);
+      }
+    }
+    fr.findAllFeatures(true);
+
+    /*
+     * reset all feature groups to visible
+     */
+    for (String group : fr.getGroups(false))
+    {
+      fr.setGroupVisibility(group, true);
+    }
+
+    fr.clearRenderOrder();
+    av.setShowSequenceFeatures(true);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_noFeatures()
+  {
+    av.setShowSequenceFeatures(false);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    av.setShowSequenceFeatures(true);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_noFeaturesShown()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(false);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_singleFeatureAtPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_gapPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, 0f,
+            null));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(null, seq, 6);
+    assertEquals(c, Color.white);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_multipleFeaturesAtPositionNoTransparency()
+  {
+    /*
+     * featuresAdded -> FeatureRendererModel.updateRenderOrder which adds any
+     * new features 'on top' (but reverses the order of any added features)
+     */
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
+            Float.NaN, "DomainGroup"));
+    FeatureColour green = new FeatureColour(Color.green);
+    fr.setColour("Domain", green);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * expect Domain (green) to be rendered above Metal (red)
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.green);
+
+    /*
+     * now promote Metal above Domain
+     * - currently no way other than mimicking reordering of
+     * table in Feature Settings
+     */
+    Object[][] data = new Object[2][];
+    data[0] = new Object[] { "Metal", red, true };
+    data[1] = new Object[] { "Domain", green, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * ..and turn off display of Metal
+     */
+    data[0][2] = false;
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.green);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_singleFeatureNotAtPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 8, 12,
+            Float.NaN, "MetalGroup"));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    // column 2 = sequence position 3
+    Color c = finder.findFeatureColour(Color.blue, seq, 2);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_featureTypeNotDisplayed()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * turn off display of Metal - is this the easiest way to do it??
+     */
+    Object[][] data = new Object[1][];
+    data[0] = new Object[] { "Metal", red, false };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    /*
+     * turn display of Metal back on
+     */
+    data[0] = new Object[] { "Metal", red, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_featureGroupNotDisplayed()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * turn off display of MetalGroup
+     */
+    fr.setGroupVisibility("MetalGroup", false);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    /*
+     * turn display of MetalGroup back on
+     */
+    fr.setGroupVisibility("MetalGroup", true);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_contactFeature()
+  {
+    /*
+     * currently contact feature == type "Disulphide Bond" or "Disulfide Bond" !!
+     */
+    seq.addSequenceFeature(new SequenceFeature("Disulphide Bond",
+            "Contact", 2, 12, Float.NaN, "Disulphide"));
+    fr.setColour("Disulphide Bond", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * Contact positions are residues 2 and 12
+     * which are columns 1 and 14
+     * positions in between don't count for a contact feature!
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+    c = finder.findFeatureColour(Color.blue, seq, 8);
+    assertEquals(c, Color.blue);
+    c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, Color.red);
+    c = finder.findFeatureColour(Color.blue, seq, 14);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_graduatedFeatureColour()
+  {
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2,
+            2, 0f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4,
+            4, 5f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7,
+            7, 10f, "KdGroup"));
+
+    /*
+     * graduated colour from 0 to 10
+     */
+    Color min = new Color(100, 50, 150);
+    Color max = new Color(200, 0, 100);
+    FeatureColourI fc = new FeatureColour(min, max, 0, 10);
+    fr.setColour("kd", fc);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * position 2, column 1, score 0 - minimum colour in range
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, min);
+
+    /*
+     * position 7, column 9, score 10 - maximum colour in range
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 9);
+    assertEquals(c, max);
+
+    /*
+     * position 4, column 3, score 5 - half way from min to max
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 3);
+    assertEquals(c, new Color(150, 25, 125));
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_transparencySingleFeature()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+  
+    /*
+     * the FeatureSettings transparency slider has range 0-70 which
+     * corresponds to a transparency value of 1 - 0.3
+     * A value of 0.4 gives a combination of
+     * 0.4 * red(255, 0, 0) + 0.6 * cyan(0, 255, 255) = (102, 153, 153)
+     */
+    fr.setTransparency(0.4f);
+    Color c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(102, 153, 153));
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_transparencyTwoFeatures()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
+            Float.NaN, "DomainGroup"));
+    FeatureColour green = new FeatureColour(Color.green);
+    fr.setColour("Domain", green);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+  
+    /*
+     * Domain (green) rendered above Metal (red) above background (cyan)
+     * 1) 0.6 * red(255, 0, 0) + 0.4 * cyan(0, 255, 255) = (153, 102, 102)
+     * 2) 0.6* green(0, 255, 0) + 0.4 * (153, 102, 102) = (61, 194, 41) rounded
+     */
+    fr.setTransparency(0.6f);
+    Color c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(61, 194, 41));
+  
+    /*
+     * now promote Metal above Domain
+     * - currently no way other than mimicking reordering of
+     * table in Feature Settings
+     * Metal (red) rendered above Domain (green) above background (cyan)
+     * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102)
+     * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded
+     */
+    Object[][] data = new Object[2][];
+    data[0] = new Object[] { "Metal", red, true };
+    data[1] = new Object[] { "Domain", green, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(153, 102, 41));
+  
+    /*
+     * ..and turn off display of Metal
+     * Domain (green) above background (pink)
+     * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70)
+     */
+    data[0][2] = false;
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.pink, seq, 10);
+    assertEquals(c, new Color(102, 223, 70));
+  }
+
+  @Test(groups = "Functional")
+  public void testNoFeaturesDisplayed()
+  {
+    /*
+     * no features on alignment to render
+     */
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * add a feature
+     * it will be automatically set visible but we leave
+     * the viewport configured not to show features
+     */
+    av.setShowSequenceFeatures(false);
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * turn on feature display
+     */
+    av.setShowSequenceFeatures(true);
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * turn off display of Metal
+     */
+    Object[][] data = new Object[1][];
+    data[0] = new Object[] { "Metal", red, false };
+    fr.setFeaturePriority(data);
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * turn display of Metal back on
+     */
+    fr.setVisible("Metal");
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * turn off MetalGroup - has no effect here since the group of a
+     * sequence feature instance is independent of its type
+     */
+    fr.setGroupVisibility("MetalGroup", false);
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * a finder with no feature renderer
+     */
+    FeatureColourFinder finder2 = new FeatureColourFinder(null);
+    assertTrue(finder2.noFeaturesDisplayed());
+  }
+}
diff --git a/test/jalview/schemes/AnnotationColourGradientTest.java b/test/jalview/schemes/AnnotationColourGradientTest.java
new file mode 100644 (file)
index 0000000..1c93856
--- /dev/null
@@ -0,0 +1,300 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.awt.Color;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AnnotationColourGradientTest
+{
+  final static int WIDTH = 11;
+
+  final static int THRESHOLD_FIVE = 5;
+
+  private AlignmentAnnotation ann;
+
+  private SequenceI seq;
+
+  private AlignmentI al;
+
+  Color minColour = new Color(50, 200, 150);
+
+  Color maxColour = new Color(150, 100, 250);
+
+  /**
+   * Setup creates an annotation over 11 columns with values 0-10 and threshold
+   * 5
+   */
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Annotation[] anns = new Annotation[WIDTH];
+    /*
+     * set annotations with values 0-10, graded colours
+     */
+    for (int col = 0; col < WIDTH; col++)
+    {
+      int hue = col * 20;
+      Color colour = new Color(hue, hue, hue);
+      anns[col] = new Annotation("a", "a", 'a', col, colour);
+    }
+
+    seq = new Sequence("", "");
+    al = new Alignment(new SequenceI[]{ seq});
+    
+    /*
+     * AlignmentAnnotation constructor works out min-max range
+     */
+    ann = new AlignmentAnnotation("", "", anns);
+    ann.setThreshold(new GraphLine(THRESHOLD_FIVE, "", Color.RED));
+    seq.addAlignmentAnnotation(ann);
+  }
+
+  @Test(groups = "Functional")
+  public void testShadeCalculation_noThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * column <n> is n/10 of the way from minCol to maxCol
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour above threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testShadeCalculation_aboveThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * colour is derived regardless of the threshold value 
+       * (the renderer will suppress colouring if above/below threshold)
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    /*
+     * now make 6-10 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < THRESHOLD_FIVE; col++)
+    {
+      /*
+       * colours below the threshold are computed as before
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+    for (int col = THRESHOLD_FIVE; col < WIDTH; col++)
+    {
+      /*
+       * colours for values >= threshold are graduated
+       * range is 6-10 so steps of 100/5 = 20
+       */
+      int factor = col - THRESHOLD_FIVE;
+      Color expected = new Color(50 + 20 * factor, 200 - 20 * factor,
+              150 + 20 * factor);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour below threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testShadeCalculation_belowThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * colour is derived regardless of the threshold value 
+       * (the renderer will suppress colouring if above/below threshold)
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    /*
+     * now make 0-5 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = THRESHOLD_FIVE + 1; col < WIDTH; col++)
+    {
+      /*
+       * colours above the threshold are computed as before
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    for (int col = 0; col <= THRESHOLD_FIVE; col++)
+    {
+      /*
+       * colours for values <= threshold are graduated
+       * range is 0-5 so steps of 100/5 = 20
+       */
+      Color expected = new Color(50 + 20 * col, 200 - 20 * col,
+              150 + 20 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour above threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testFindColour_aboveThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      /*
+       * expect white below threshold of 5
+       */
+      Color expected = col < 5 ? Color.white : new Color(50 + 10 * col,
+              200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  
+    /*
+     * now make 6-10 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      /*
+       * colours for values >= threshold are graduated
+       * range is 6-10 so steps of 100/5 = 20
+       */
+      int factor = col - THRESHOLD_FIVE;
+      Color expected = col < 5 ? Color.white : new Color(50 + 20 * factor,
+              200 - 20 * factor,
+              150 + 20 * factor);
+      Color result = testee.findColour('a', col, seq);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour below threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testFindColour_belowThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+  
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      Color expected = col > 5 ? Color.white : new Color(50 + 10 * col,
+              200 - 10 * col, 150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  
+    /*
+     * now make 0-5 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      /*
+       * colours for values <= threshold are graduated
+       * range is 0-5 so steps of 100/5 = 20
+       */
+      Color expected = col > 5 ? Color.white : new Color(50 + 20 * col,
+              200 - 20 * col, 150 + 20 * col);
+      Color result = testee.findColour('a', col, seq);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testFindColour_noThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      /*
+       * column <n> is n/10 of the way from minCol to maxCol
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testFindColour_originalColours()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    /*
+     * flag corresponding to 'use original colours' checkbox
+     * - just use the individual annotation colours
+     */
+    testee.setPredefinedColours(true);
+
+    /*
+     * the annotation colour is returned, except for column 0 where it is
+     * black - in this case the colour scheme colour overrides it
+     */
+    for (int col = 0; col < WIDTH; col++)
+    {
+      int hue = col * 20;
+      Color c = col == 0 ? minColour : new Color(hue, hue, hue);
+      assertEquals(testee.findColour('a', col, seq), c, "for column " + col);
+    }
+  }
+}
index 4618ed7..0aaa38c 100644 (file)
@@ -228,9 +228,9 @@ public class ColourSchemesTest
      * set and check Taylor colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Taylor.toString());
-    Color taylor1 = sr.getResidueBoxColour(seq, 88); // E 255,0,102
-    Color taylor2 = sr.getResidueBoxColour(seq, 89); // A 204,255,0
-    Color taylor3 = sr.getResidueBoxColour(seq, 90); // G 255,153,0
+    Color taylor1 = sr.getResidueColour(seq, 88, null); // E 255,0,102
+    Color taylor2 = sr.getResidueColour(seq, 89, null); // A 204,255,0
+    Color taylor3 = sr.getResidueColour(seq, 90, null); // G 255,153,0
     assertEquals(taylor1, new Color(255, 0, 102));
     assertEquals(taylor2, new Color(204, 255, 0));
     assertEquals(taylor3, new Color(255, 153, 0));
@@ -239,9 +239,9 @@ public class ColourSchemesTest
      * set and check Zappo colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Zappo.toString());
-    Color zappo1 = sr.getResidueBoxColour(seq, 88); // E red
-    Color zappo2 = sr.getResidueBoxColour(seq, 89); // A pink
-    Color zappo3 = sr.getResidueBoxColour(seq, 90); // G magenta
+    Color zappo1 = sr.getResidueColour(seq, 88, null); // E red
+    Color zappo2 = sr.getResidueColour(seq, 89, null); // A pink
+    Color zappo3 = sr.getResidueColour(seq, 90, null); // G magenta
     assertEquals(zappo1, Color.red);
     assertEquals(zappo2, Color.pink);
     assertEquals(zappo3, Color.magenta);
@@ -250,9 +250,9 @@ public class ColourSchemesTest
      * set 'stripy' colours - odd columns are Taylor and even are Zappo 
      */
     af.changeColour_actionPerformed("stripy");
-    Color stripy1 = sr.getResidueBoxColour(seq, 88);
-    Color stripy2 = sr.getResidueBoxColour(seq, 89);
-    Color stripy3 = sr.getResidueBoxColour(seq, 90);
+    Color stripy1 = sr.getResidueColour(seq, 88, null);
+    Color stripy2 = sr.getResidueColour(seq, 89, null);
+    Color stripy3 = sr.getResidueColour(seq, 90, null);
     assertEquals(stripy1, zappo1);
     assertEquals(stripy2, taylor2);
     assertEquals(stripy3, zappo3);
@@ -261,9 +261,9 @@ public class ColourSchemesTest
      * set and check Clustal colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Clustal.toString());
-    Color clustal1 = sr.getResidueBoxColour(seq, 88);
-    Color clustal2 = sr.getResidueBoxColour(seq, 89);
-    Color clustal3 = sr.getResidueBoxColour(seq, 90);
+    Color clustal1 = sr.getResidueColour(seq, 88, null);
+    Color clustal2 = sr.getResidueColour(seq, 89, null);
+    Color clustal3 = sr.getResidueColour(seq, 90, null);
     assertEquals(clustal1, ClustalColour.MAGENTA.colour);
     assertEquals(clustal2, ClustalColour.BLUE.colour);
     assertEquals(clustal3, ClustalColour.ORANGE.colour);
@@ -272,9 +272,9 @@ public class ColourSchemesTest
      * set 'MyClustal' colours - uses AWT colour equivalents
      */
     af.changeColour_actionPerformed("MyClustal");
-    Color myclustal1 = sr.getResidueBoxColour(seq, 88);
-    Color myclustal2 = sr.getResidueBoxColour(seq, 89);
-    Color myclustal3 = sr.getResidueBoxColour(seq, 90);
+    Color myclustal1 = sr.getResidueColour(seq, 88, null);
+    Color myclustal2 = sr.getResidueColour(seq, 89, null);
+    Color myclustal3 = sr.getResidueColour(seq, 90, null);
     assertEquals(myclustal1, Color.MAGENTA);
     assertEquals(myclustal2, Color.BLUE);
     assertEquals(myclustal3, Color.ORANGE);
diff --git a/test/jalview/schemes/ScoreMatrixPrinter.java b/test/jalview/schemes/ScoreMatrixPrinter.java
deleted file mode 100644 (file)
index a743163..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.schemes;
-
-import jalview.api.analysis.ScoreModelI;
-import jalview.gui.JvOptionPane;
-
-import java.util.Map;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-public class ScoreMatrixPrinter
-{
-
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
-  @Test(groups = { "Functional" })
-  public void printAllMatrices()
-  {
-    for (Map.Entry<String, ScoreModelI> sm : ResidueProperties.scoreMatrices
-            .entrySet())
-    {
-      System.out.println("Matrix " + sm.getKey());
-      System.out.println(sm.getValue().toString());
-    }
-  }
-
-  @Test(groups = { "Functional" })
-  public void printHTMLMatrices()
-  {
-    for (Map.Entry<String, ScoreModelI> _sm : ResidueProperties.scoreMatrices
-            .entrySet())
-    {
-      if (_sm.getValue() instanceof ScoreMatrix)
-      {
-        ScoreMatrix sm = (ScoreMatrix) _sm.getValue();
-        System.out.println("Matrix " + _sm.getKey());
-        System.out.println(sm.outputMatrix(true));
-      }
-    }
-  }
-
-}
diff --git a/test/jalview/structure/AtomSpecTest.java b/test/jalview/structure/AtomSpecTest.java
new file mode 100644 (file)
index 0000000..ea53131
--- /dev/null
@@ -0,0 +1,74 @@
+package jalview.structure;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+public class AtomSpecTest
+{
+  @Test
+  public void testFromChimeraAtomSpec()
+  {
+    AtomSpec as = AtomSpec.fromChimeraAtomspec("#1:12.B");
+    assertEquals(as.getModelNumber(), 1);
+    assertEquals(as.getPdbResNum(), 12);
+    assertEquals(as.getChain(), "B");
+    assertNull(as.getPdbFile());
+
+    // no model - default to zero
+    as = AtomSpec.fromChimeraAtomspec(":13.C");
+    assertEquals(as.getModelNumber(), 0);
+    assertEquals(as.getPdbResNum(), 13);
+    assertEquals(as.getChain(), "C");
+    assertNull(as.getPdbFile());
+
+    // model.submodel
+    as = AtomSpec.fromChimeraAtomspec("#3.2:15");
+    assertEquals(as.getModelNumber(), 3);
+    assertEquals(as.getPdbResNum(), 15);
+    assertEquals(as.getChain(), "");
+    assertNull(as.getPdbFile());
+
+    String spec = "3:12.B";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+
+    spec = "#3:12-14.B";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+
+    spec = "";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+
+    spec = null;
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec);
+      fail("Expected exception for " + spec);
+    } catch (NullPointerException e)
+    {
+      // ok
+    }
+  }
+}
diff --git a/test/jalview/structure/StructureMappingTest.java b/test/jalview/structure/StructureMappingTest.java
new file mode 100644 (file)
index 0000000..f26c5f1
--- /dev/null
@@ -0,0 +1,46 @@
+package jalview.structure;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class StructureMappingTest
+{
+  @Test(groups = "Functional")
+  public void testgetPDBResNumRanges()
+  {
+    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+
+    StructureMapping mapping = new StructureMapping(null, null, null, null,
+            map, null);
+
+    List<int[]> ranges = mapping.getPDBResNumRanges(1, 2);
+    assertTrue(ranges.isEmpty());
+
+    map.put(1, new int[] { 12, 20 }); // 1 maps to 12
+    ranges = mapping.getPDBResNumRanges(2, 3);
+    assertTrue(ranges.isEmpty());
+    ranges = mapping.getPDBResNumRanges(1, 2);
+    assertEquals(ranges.size(), 1);
+    assertEquals(ranges.get(0)[0], 12);
+    assertEquals(ranges.get(0)[1], 12);
+
+    map.put(2, new int[] { 13, 20 }); // 2 maps to 13
+    ranges = mapping.getPDBResNumRanges(1, 2);
+    assertEquals(ranges.size(), 1);
+    assertEquals(ranges.get(0)[0], 12);
+    assertEquals(ranges.get(0)[1], 13);
+
+    map.put(3, new int[] { 15, 20 }); // 3 maps to 15 - break
+    ranges = mapping.getPDBResNumRanges(1, 5);
+    assertEquals(ranges.size(), 2);
+    assertEquals(ranges.get(0)[0], 12);
+    assertEquals(ranges.get(0)[1], 13);
+    assertEquals(ranges.get(1)[0], 15);
+    assertEquals(ranges.get(1)[1], 15);
+  }
+}
index 0422537..c125ef6 100644 (file)
@@ -29,7 +29,7 @@ import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
 import jalview.datamodel.Sequence;
@@ -44,6 +44,7 @@ import jalview.structures.models.AAStructureBindingModel.SuperposeData;
 
 import java.awt.Color;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -137,7 +138,7 @@ public class AAStructureBindingModelTest
     testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
     {
       @Override
-      public String[] getPdbFile()
+      public String[] getStructureFiles()
       {
         return new String[] { "INLINE1YCS", "INLINE3A6S", "INLINE1OOT" };
       }
@@ -169,9 +170,10 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      public void superposeStructures(AlignmentI[] als, int[] alm,
-              ColumnSelection[] alc)
+      public String superposeStructures(AlignmentI[] als, int[] alm,
+              HiddenColumns[] alc)
       {
+        return null;
       }
 
       @Override
@@ -181,14 +183,7 @@ public class AAStructureBindingModelTest
 
       @Override
       protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, FeatureRenderer fr,
-              AlignmentI alignment)
-      {
-        return null;
-      }
-
-      @Override
-      public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
+              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
       {
         return null;
       }
@@ -215,6 +210,13 @@ public class AAStructureBindingModelTest
       public void colourByCharge()
       {
       }
+
+      @Override
+      public FeatureRenderer getFeatureRenderer(
+              AlignmentViewPanel alignment)
+      {
+        return null;
+      }
     };
   }
 
@@ -228,17 +230,20 @@ public class AAStructureBindingModelTest
     /*
      * create a data bean to hold data per structure file
      */
-    SuperposeData[] structs = new SuperposeData[testee.getPdbFile().length];
+    SuperposeData[] structs = new SuperposeData[testee.getStructureFiles().length];
     for (int i = 0; i < structs.length; i++)
     {
       structs[i] = testee.new SuperposeData(al.getWidth());
     }
     /*
-     * initialise array of 'superposable columns' to true (would be false for
+     * initialise BitSet of 'superposable columns' to true (would be false for
      * hidden columns)
      */
-    boolean[] matched = new boolean[al.getWidth()];
-    Arrays.fill(matched, true);
+    BitSet matched = new BitSet();
+    for (int i = 0; i < al.getWidth(); i++)
+    {
+      matched.set(i);
+    }
 
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
@@ -248,12 +253,12 @@ public class AAStructureBindingModelTest
     /*
      * only ungapped, structure-mapped columns are superposable
      */
-    assertFalse(matched[0]); // gap in first sequence
-    assertTrue(matched[1]);
-    assertFalse(matched[2]); // gap in third sequence
-    assertFalse(matched[3]); // gap in fourth sequence
-    assertTrue(matched[4]);
-    assertTrue(matched[5]); // gap in second sequence
+    assertFalse(matched.get(0)); // gap in first sequence
+    assertTrue(matched.get(1));
+    assertFalse(matched.get(2)); // gap in third sequence
+    assertFalse(matched.get(3)); // gap in fourth sequence
+    assertTrue(matched.get(4));
+    assertTrue(matched.get(5)); // gap in second sequence
 
     assertEquals("1YCS", structs[0].pdbId);
     assertEquals("3A6S", structs[1].pdbId);
@@ -278,13 +283,17 @@ public class AAStructureBindingModelTest
       structs[i] = testee.new SuperposeData(al.getWidth());
     }
     /*
-     * initialise array of 'superposable columns' to true (would be false for
+     * initialise BitSet of 'superposable columns' to true (would be false for
      * hidden columns)
      */
-    boolean[] matched = new boolean[al.getWidth()];
-    Arrays.fill(matched, true);
+    BitSet matched = new BitSet();
+    for (int i = 0; i < al.getWidth(); i++)
+    {
+      matched.set(i);
+    }
+
     // treat column 5 of the alignment as hidden
-    matched[4] = false;
+    matched.clear(4);
 
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
@@ -292,21 +301,11 @@ public class AAStructureBindingModelTest
     assertEquals(0, refStructure);
 
     // only ungapped, structure-mapped columns are not superposable
-    assertFalse(matched[0]);
-    assertTrue(matched[1]);
-    assertFalse(matched[2]);
-    assertFalse(matched[3]);
-    assertFalse(matched[4]); // superposable, but hidden, column
-    assertTrue(matched[5]);
-  }
-
-  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    return null;
-  }
-
-  public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
-  {
-    return null;
+    assertFalse(matched.get(0));
+    assertTrue(matched.get(1));
+    assertFalse(matched.get(2));
+    assertFalse(matched.get(3));
+    assertFalse(matched.get(4)); // superposable, but hidden, column
+    assertTrue(matched.get(5));
   }
 }
index 8c50082..9eeac9f 100644 (file)
@@ -131,6 +131,7 @@ public class UrlLinkDisplayTest {
 
     u.setValue(UrlLinkDisplay.NAME, "New Desc");
     Assert.assertEquals(u.getValue(UrlLinkDisplay.NAME), "New Desc");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.DATABASE), "New Desc");
 
     u.setValue(UrlLinkDisplay.DATABASE, "NewName");
     Assert.assertEquals(u.getValue(UrlLinkDisplay.DATABASE), "NewName");
index f955879..6f6841d 100644 (file)
@@ -115,7 +115,7 @@ public class ComparisonTest
   @Test(groups = { "Functional" })
   public void testPID_includingGaps()
   {
-    String seq1 = "ABCDEF";
+    String seq1 = "ABCDEFG"; // extra length here is ignored
     String seq2 = "abcdef";
     assertEquals("identical", 100f, Comparison.PID(seq1, seq2), 0.001f);
 
@@ -129,12 +129,14 @@ public class ComparisonTest
     int length = seq1.length();
 
     // match gap-residue, match gap-gap: 9/10 identical
+    // TODO should gap-gap be included in a PID score? JAL-791
     assertEquals(90f, Comparison.PID(seq1, seq2, 0, length, true, false),
             0.001f);
     // overloaded version of the method signature above:
     assertEquals(90f, Comparison.PID(seq1, seq2), 0.001f);
 
     // don't match gap-residue, match gap-gap: 7/10 identical
+    // TODO should gap-gap be included in a PID score?
     assertEquals(70f, Comparison.PID(seq1, seq2, 0, length, false, false),
             0.001f);
   }
@@ -163,7 +165,8 @@ public class ComparisonTest
   public void testPID_ungappedOnly()
   {
     // 5 identical, 2 gap-gap, 2 gap-residue, 1 mismatch
-    String seq1 = "a--b-cdefh";
+    // the extra length of seq1 is ignored
+    String seq1 = "a--b-cdefhr";
     String seq2 = "a---bcdefg";
     int length = seq1.length();
 
index b84e770..19c8438 100644 (file)
@@ -33,6 +33,7 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.Sequence;
@@ -300,49 +301,60 @@ public class MappingUtilsTest
     setupMappedAlignments();
 
     ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns hidden = new HiddenColumns();
 
     /*
      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
      * in dna respectively, overall 0-4
      */
     colsel.addElement(0);
-    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel,
-            proteinView, dnaView);
+    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns hs = new HiddenColumns();
+    MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
+            cs, hs);
     assertEquals("[0, 1, 2, 3, 4]", cs.getSelected().toString());
 
     /*
      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
      */
+    cs.clear();
     colsel.clear();
     colsel.addElement(1);
-    cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
+    MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
+            cs, hs);
     assertEquals("[0, 1, 2, 3]", cs.getSelected().toString());
 
     /*
      * Column 2 in protein picks up gaps only - no mapping
      */
+    cs.clear();
     colsel.clear();
     colsel.addElement(2);
-    cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
+    MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
+            dnaView, cs, hs);
     assertEquals("[]", cs.getSelected().toString());
 
     /*
      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
      * 6-9, 6-10, 5-8 respectively, overall to 5-10
      */
+    cs.clear();
     colsel.clear();
     colsel.addElement(3);
-    cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
+    MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
+            dnaView, cs, hs);
     assertEquals("[5, 6, 7, 8, 9, 10]", cs.getSelected().toString());
 
     /*
      * Combine selection of columns 1 and 3 to get a discontiguous mapped
      * selection
      */
+    cs.clear();
     colsel.clear();
     colsel.addElement(1);
     colsel.addElement(3);
-    cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
+    MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
+            dnaView, cs, hs);
     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]", cs.getSelected()
             .toString());
   }
@@ -407,14 +419,17 @@ public class MappingUtilsTest
     setupMappedAlignments();
 
     ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns hidden = new HiddenColumns();
 
     /*
      * Column 0 in dna picks up first bases which map to residue 1, columns 0-1
      * in protein.
      */
+    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns hs = new HiddenColumns();
     colsel.addElement(0);
-    ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, dnaView,
-            proteinView);
+    MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
+            cs, hs);
     assertEquals("[0, 1]", cs.getSelected().toString());
 
     /*
@@ -424,7 +439,9 @@ public class MappingUtilsTest
     colsel.addElement(3);
     colsel.addElement(4);
     colsel.addElement(5);
-    cs = MappingUtils.mapColumnSelection(colsel, dnaView, proteinView);
+    cs.clear();
+    MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
+            cs, hs);
     assertEquals("[0, 1, 3]", cs.getSelected().toString());
   }
 
@@ -432,8 +449,10 @@ public class MappingUtilsTest
   public void testMapColumnSelection_null() throws IOException
   {
     setupMappedAlignments();
-    ColumnSelection cs = MappingUtils.mapColumnSelection(null, dnaView,
-            proteinView);
+    ColumnSelection cs = new ColumnSelection();
+    HiddenColumns hs = new HiddenColumns();
+    MappingUtils.mapColumnSelection(null, null, dnaView, proteinView, cs,
+            hs);
     assertTrue("mapped selection not empty", cs.getSelected().isEmpty());
   }
 
@@ -882,69 +901,80 @@ public class MappingUtilsTest
     setupMappedAlignments();
 
     ColumnSelection proteinSelection = new ColumnSelection();
+    HiddenColumns hiddenCols = new HiddenColumns();
 
     /*
      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
      * in dna respectively, overall 0-4
      */
-    proteinSelection.hideColumns(0);
-    ColumnSelection dnaSelection = MappingUtils.mapColumnSelection(
-            proteinSelection, proteinView, dnaView);
+    proteinSelection.hideSelectedColumns(0, hiddenCols);
+    ColumnSelection dnaSelection = new ColumnSelection();
+    HiddenColumns dnaHidden = new HiddenColumns();
+    MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
+            proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[]", dnaSelection.getSelected().toString());
-    List<int[]> hidden = dnaSelection.getHiddenColumns();
+    List<int[]> hidden = dnaHidden.getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[0, 4]", Arrays.toString(hidden.get(0)));
 
     /*
      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
      */
-    proteinSelection.revealAllHiddenColumns();
+    dnaSelection = new ColumnSelection();
+    dnaHidden = new HiddenColumns();
+    hiddenCols.revealAllHiddenColumns(proteinSelection);
     // the unhidden columns are now marked selected!
     assertEquals("[0]", proteinSelection.getSelected().toString());
     // deselect these or hideColumns will be expanded to include 0
     proteinSelection.clear();
-    proteinSelection.hideColumns(1);
-    dnaSelection = MappingUtils.mapColumnSelection(proteinSelection,
-            proteinView, dnaView);
-    hidden = dnaSelection.getHiddenColumns();
+    proteinSelection.hideSelectedColumns(1, hiddenCols);
+    MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
+            proteinView, dnaView, dnaSelection, dnaHidden);
+    hidden = dnaHidden.getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
 
     /*
      * Column 2 in protein picks up gaps only - no mapping
      */
-    proteinSelection.revealAllHiddenColumns();
+    dnaSelection = new ColumnSelection();
+    dnaHidden = new HiddenColumns();
+    hiddenCols.revealAllHiddenColumns(proteinSelection);
     proteinSelection.clear();
-    proteinSelection.hideColumns(2);
-    dnaSelection = MappingUtils.mapColumnSelection(proteinSelection,
-            proteinView, dnaView);
-    assertTrue(dnaSelection.getHiddenColumns().isEmpty());
+    proteinSelection.hideSelectedColumns(2, hiddenCols);
+    MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
+            proteinView, dnaView, dnaSelection, dnaHidden);
+    assertTrue(dnaHidden.getHiddenRegions().isEmpty());
 
     /*
      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
      * 6-9, 6-10, 5-8 respectively, overall to 5-10
      */
-    proteinSelection.revealAllHiddenColumns();
+    dnaSelection = new ColumnSelection();
+    dnaHidden = new HiddenColumns();
+    hiddenCols.revealAllHiddenColumns(proteinSelection);
     proteinSelection.clear();
-    proteinSelection.hideColumns(3); // 5-10 hidden in dna
+    proteinSelection.hideSelectedColumns(3, hiddenCols); // 5-10 hidden in dna
     proteinSelection.addElement(1); // 0-3 selected in dna
-    dnaSelection = MappingUtils.mapColumnSelection(proteinSelection,
-            proteinView, dnaView);
+    MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
+            proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
-    hidden = dnaSelection.getHiddenColumns();
+    hidden = dnaHidden.getHiddenRegions();
     assertEquals(1, hidden.size());
     assertEquals("[5, 10]", Arrays.toString(hidden.get(0)));
 
     /*
      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
      */
-    proteinSelection.revealAllHiddenColumns();
+    dnaSelection = new ColumnSelection();
+    dnaHidden = new HiddenColumns();
+    hiddenCols.revealAllHiddenColumns(proteinSelection);
     proteinSelection.clear();
-    proteinSelection.hideColumns(1);
-    proteinSelection.hideColumns(3);
-    dnaSelection = MappingUtils.mapColumnSelection(proteinSelection,
-            proteinView, dnaView);
-    hidden = dnaSelection.getHiddenColumns();
+    proteinSelection.hideSelectedColumns(1, hiddenCols);
+    proteinSelection.hideSelectedColumns(3, hiddenCols);
+    MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
+            proteinView, dnaView, dnaSelection, dnaHidden);
+    hidden = dnaHidden.getHiddenRegions();
     assertEquals(2, hidden.size());
     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
     assertEquals("[5, 10]", Arrays.toString(hidden.get(1)));
diff --git a/test/jalview/util/SetUtilsTest.java b/test/jalview/util/SetUtilsTest.java
new file mode 100644 (file)
index 0000000..ad17d4f
--- /dev/null
@@ -0,0 +1,46 @@
+package jalview.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.awt.Color;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.testng.annotations.Test;
+
+public class SetUtilsTest
+{
+  @Test(groups = "Functional")
+  public void testCountDisjunction()
+  {
+    Set<Color> s1 = new HashSet<Color>();
+    assertEquals(SetUtils.countDisjunction(null, null), 0);
+    assertEquals(SetUtils.countDisjunction(s1, null), 0);
+    assertEquals(SetUtils.countDisjunction(null, s1), 0);
+    s1.add(Color.white);
+    assertEquals(SetUtils.countDisjunction(s1, null), 1);
+    assertEquals(SetUtils.countDisjunction(null, s1), 1);
+    assertEquals(SetUtils.countDisjunction(s1, null), 1);
+    assertEquals(SetUtils.countDisjunction(s1, s1), 0);
+
+    Set<Object> s2 = new HashSet<Object>();
+    assertEquals(SetUtils.countDisjunction(s2, s2), 0);
+    assertEquals(SetUtils.countDisjunction(s1, s2), 1);
+    assertEquals(SetUtils.countDisjunction(s2, s1), 1);
+
+    s1.add(Color.yellow);
+    s1.add(Color.blue);
+    s2.add(new Color(Color.yellow.getRGB()));
+
+    /*
+     * now s1 is {white, yellow, blue}
+     *     s2 is {yellow'}
+     */
+    assertEquals(SetUtils.countDisjunction(s1, s2), 2);
+    s2.add(Color.blue);
+    assertEquals(SetUtils.countDisjunction(s1, s2), 1);
+    s2.add(Color.pink);
+    assertEquals(SetUtils.countDisjunction(s1, s2), 2);
+
+  }
+}
diff --git a/test/jalview/viewmodel/OverviewDimensionsHideHiddenTest.java b/test/jalview/viewmodel/OverviewDimensionsHideHiddenTest.java
new file mode 100644 (file)
index 0000000..0e931eb
--- /dev/null
@@ -0,0 +1,982 @@
+/*
+ * 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.viewmodel;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceCollectionI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+
+import java.util.Hashtable;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class OverviewDimensionsHideHiddenTest
+{
+  AlignmentI al;
+
+  OverviewDimensionsHideHidden od;
+
+  // cached widths and heights
+  int boxWidth;
+  int boxHeight;
+  int viewHeight;
+  int viewWidth;
+  int alheight;
+  int alwidth;
+
+  ViewportRanges vpranges;
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+
+  HiddenColumns hiddenCols = new HiddenColumns();
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpAlignment()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    al = gen.generate(157, 525, 123, 5, 5);
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp()
+  {
+    if (!hiddenRepSequences.isEmpty())
+    {
+      al.getHiddenSequences().showAll(hiddenRepSequences);
+    }
+    ColumnSelection colsel = new ColumnSelection();
+    hiddenCols.revealAllHiddenColumns(colsel);
+    
+    vpranges = new ViewportRanges(al);
+    vpranges.setViewportStartAndHeight(0, 18);
+    vpranges.setViewportStartAndWidth(0, 63);
+
+    viewHeight = vpranges.getEndSeq() - vpranges.getStartSeq() + 1;
+    viewWidth = vpranges.getEndRes() - vpranges.getStartRes() + 1;
+
+    HiddenColumns hiddenCols = new HiddenColumns();
+
+    od = new OverviewDimensionsHideHidden(vpranges, true);
+    // Initial box sizing - default path through code
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+
+    mouseClick(od, 0, 0);
+    moveViewport(0, 0);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void cleanUp()
+  {
+    al = null;
+  }
+
+  /**
+   * Test that the OverviewDimensions constructor sets width and height
+   * correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testConstructor()
+  {
+    SequenceI seqa = new Sequence("Seq1", "ABC");
+    SequenceI seqb = new Sequence("Seq2", "ABC");
+    SequenceI seqc = new Sequence("Seq3", "ABC");
+    SequenceI seqd = new Sequence("Seq4", "ABC");
+    SequenceI seqe = new Sequence("Seq5",
+            "ABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+    int defaultGraphHeight = 20;
+    int maxWidth = 400;
+    int minWidth = 120;
+    int maxSeqHeight = 300;
+    int minSeqHeight = 40;
+
+    // test for alignment with width > height
+    SequenceI[] seqs1 = new SequenceI[] { seqa, seqb };
+    Alignment al1 = new Alignment(seqs1);
+    ViewportRanges props = new ViewportRanges(al1);
+
+    OverviewDimensions od = new OverviewDimensionsHideHidden(props, true);
+    int scaledHeight = 267;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), scaledHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), scaledHeight + defaultGraphHeight);
+
+    // test for alignment with width < height
+    SequenceI[] seqs2 = new SequenceI[] { seqa, seqb, seqc, seqd };
+    Alignment al2 = new Alignment(seqs2);
+    props = new ViewportRanges(al2);
+
+    od = new OverviewDimensionsHideHidden(props, true);
+    int scaledWidth = 300;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), scaledWidth);
+    assertEquals(od.getHeight(), scaledWidth + defaultGraphHeight);
+
+    // test for alignment with width > height and sequence height scaled below
+    // min value
+    SequenceI[] seqs3 = new SequenceI[] { seqe };
+    Alignment al3 = new Alignment(seqs3);
+    props = new ViewportRanges(al3);
+
+    od = new OverviewDimensionsHideHidden(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), minSeqHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), minSeqHeight + defaultGraphHeight);
+
+    // test for alignment with width < height and width scaled below min value
+    SequenceI[] seqs4 = new SequenceI[] { seqa, seqb, seqc, seqd, seqa,
+        seqb, seqc, seqd, seqa, seqb, seqc, seqd, seqa, seqb, seqc, seqd };
+    Alignment al4 = new Alignment(seqs4);
+    props = new ViewportRanges(al4);
+
+    od = new OverviewDimensionsHideHidden(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight + defaultGraphHeight);
+
+    Alignment al5 = new Alignment(seqs4);
+    props = new ViewportRanges(al5);
+
+    od = new OverviewDimensionsHideHidden(props, false);
+    assertEquals(od.getGraphHeight(), 0);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight);
+  }
+
+  /**
+   * Test that validation after mouse adjustments to boxX and boxY sets box
+   * dimensions and scroll values correctly, when there are no hidden rows or
+   * columns.
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromMouseClick()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // negative boxX value reset to 0
+    mouseClick(od, -5, 10);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartSeq(),
+            Math.round((float) 10 * alheight / od.getSequencesHeight()));
+    assertEquals(vpranges.getStartRes(), 0);
+
+    // negative boxY value reset to 0
+    mouseClick(od, 6, -2);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) 6 * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // overly large boxX value reset to width-boxWidth
+    mouseClick(od, 100, 6);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 6);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // overly large boxY value reset to sequenceHeight - boxHeight
+    mouseClick(od, 10, 520);
+    assertEquals(od.getBoxX(), 10);
+    assertEquals(od.getBoxY(), od.getSequencesHeight() - od.getBoxHeight());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+
+    // here (float) od.getBoxY() * alheight / od.getSequencesHeight() = 507.5
+    // and round rounds to 508; however we get 507 working with row values
+    // hence the subtraction of 1
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()) - 1);
+
+    // click past end of alignment, as above
+    mouseClick(od, 3000, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // move viewport so startRes non-zero and then mouseclick
+    moveViewportH(50);
+
+    // click at viewport position
+    int oldboxx = od.getBoxX();
+    int oldboxy = od.getBoxY();
+    mouseClick(od, od.getBoxX() + 5, od.getBoxY() + 2);
+    assertEquals(od.getBoxX(), oldboxx + 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getBoxY(), oldboxy + 2);
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // click at top corner
+    mouseClick(od, 0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide cols at start and check updated box position is correct
+    int lastHiddenCol = 30;
+    hiddenCols.hideColumns(0, lastHiddenCol);
+
+    testBoxIsAtClickPoint(0, 0);
+
+    // click to right of hidden columns, box moves to click point
+    testBoxIsAtClickPoint(40, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) 40 * alwidth / od.getWidth()));
+
+    // click to right of hidden columns such that box runs over right hand side
+    // of alignment
+    // box position is adjusted away from the edge
+    // overly large boxX value reset to width-boxWidth
+    int xpos = 100;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols in the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    testBoxIsAtClickPoint(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    
+    // hide columns 63-73, no change to box position or dimensions
+    int firstHidden = 63;
+    int lastHidden = 73;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    testBoxIsAtClickPoint(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so that it overlaps with hidden cols on one side
+    // box width, boxX and scrollCol as for unhidden case
+    int xpos = 55 - boxWidth; // 55 is position in overview approx halfway
+                              // between cols 60 and 70
+    mouseClick(od, xpos, 0);
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round(xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so that it completely covers hidden cols
+    // box width, boxX and scrollCol as for unhidden case
+    xpos = 33;
+    mouseClick(od, xpos, 0);
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so boxX is in hidden cols, box overhangs at right
+    // boxX and scrollCol at left of hidden area, box width unchanged
+    xpos = 50;
+    mouseClick(od, xpos, 0);
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so boxX is to right of hidden cols, but does not go beyond full
+    // width of alignment
+    // box width, boxX and scrollCol all as for non-hidden case
+    xpos = 75;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round(xpos * alwidth / od.getWidth()));
+    
+    // move box so it goes beyond full width of alignment
+    // boxX, scrollCol adjusted back, box width normal
+    xpos = 3000;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide columns 140-164, no change to box position or dimensions
+    int firstHidden = 140;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // click to left of hidden cols, without overlapping
+    // boxX, scrollCol and width as normal
+    int xpos = 5;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+
+    // click to left of hidden cols, with overlap
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = Math.round((float) 145 * od.getWidth() / alwidth) - boxWidth;
+    mouseClick(od, xpos, 0);
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // click off end of alignment
+    // boxX and scrollCol adjusted backwards, width normal
+    xpos = 3000;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+  }
+
+  /**
+   * Test that the box position is set correctly when set from the viewport,
+   * with no hidden rows or columns
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewport()
+  {
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right
+    moveViewportH(70);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport down
+    moveViewportV(100);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round(100 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to bottom right
+    moveViewport(98, 508);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 98 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round((float) 508 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment - need to make startRes by removing
+    // hidden cols because of how viewport/overview are implemented
+    moveViewport(98 - lastHidden - 1, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (98 - lastHidden - 1) * od.getWidth()
+                    / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * in the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsInMiddle()
+  {
+    int firstHidden = 68;
+    int lastHidden = 78;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to left of hidden columns with overlap
+    moveViewport(10, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 10 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden columns
+    moveViewport(63, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 63 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right of hidden columns, no overlap
+    moveViewport(80 - (lastHidden - firstHidden + 1), 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (80 - (lastHidden - firstHidden + 1))
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the end
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtEnd()
+  {
+    int firstHidden = 152;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to hidden columns
+    // viewport can't actually extend into hidden cols,
+    // so move to the far right edge of the viewport
+    moveViewport(firstHidden - viewWidth, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - viewWidth)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hideSequences(firstHidden, lastHidden);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    // move viewport to start of alignment:
+    // box moves to below hidden rows, height remains same
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    moveViewport(0, 525 - viewHeight - lastHidden - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (525 - viewHeight - lastHidden - 1)
+                    * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows in
+   * the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsInMiddle()
+  {
+    int firstHidden = 200;
+    int lastHidden = 210;
+    hideSequences(firstHidden, lastHidden);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden rows
+    moveViewport(0, 198);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), Math.round ((float)198 * od.getSequencesHeight()
+            / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the bottom
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtEnd()
+  {
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    // viewport sits above hidden rows and does not include them
+    moveViewport(0, firstHidden - viewHeight - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (firstHidden - viewHeight - 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows at start and check updated box position is correct
+    int lastHiddenRow = 30;
+    hideSequences(0, lastHiddenRow);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click below hidden rows
+    mouseClick(od, 0, 150);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 150);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows in middle and check updated box position is correct
+    // no changes
+    int firstHiddenRow = 50;
+    int lastHiddenRow = 54;
+    hideSequences(firstHiddenRow, lastHiddenRow);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows, so that box overlaps
+    int ypos = 35; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click so that box straddles hidden rows
+    ypos = 44; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows at end and check updated box position is correct
+    // no changes
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    // calculate with visible values
+    alheight = vpranges.getVisibleAlignmentHeight();
+    alwidth = vpranges.getVisibleAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows
+    int ypos = 40; // row 40
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows so box overlaps
+    // boxY, boxHeight remains same
+    ypos = 497; // row 497
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) firstHidden * od.getSequencesHeight()
+                    / alheight)
+                    - boxHeight);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /*
+   * Move viewport horizontally: startRes + previous width gives new horizontal extent. Vertical extent stays the same.
+   */
+  private void moveViewportH(int startRes)
+  {
+    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Move viewport vertically: startSeq and endSeq give new vertical extent. Horizontal extent stays the same.
+   */
+  private void moveViewportV(int startSeq)
+  {
+    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Move viewport horizontally and vertically.
+   */
+  private void moveViewport(int startRes, int startSeq)
+  {
+    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Mouse click as position x,y in overview window
+   */
+  private void mouseClick(OverviewDimensions od, int x, int y)
+  {
+    od.updateViewportFromMouse(x, y, al.getHiddenSequences(), hiddenCols);
+
+    // updates require an OverviewPanel to exist which it doesn't here
+    // so call setBoxPosition() as it would be called by the AlignmentPanel
+    // normally
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+  
+  /*
+   * Test that the box is positioned with the top left corner at xpos, ypos
+   * and with the original width and height
+   */
+  private void testBoxIsAtClickPoint(int xpos, int ypos)
+  {
+    mouseClick(od, xpos, ypos);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), ypos);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /*
+   * Hide sequences between start and end
+   */
+  private void hideSequences(int start, int end)
+  {
+    SequenceI[] allseqs = al.getSequencesArray();
+    SequenceGroup theseSeqs = new SequenceGroup();
+    
+    for (int i = start; i <= end; i++)
+    {
+      theseSeqs.addSequence(allseqs[i], false);
+      al.getHiddenSequences().hideSequence(allseqs[i]);
+    }
+
+    hiddenRepSequences.put(allseqs[start], theseSeqs);
+  }
+}
diff --git a/test/jalview/viewmodel/OverviewDimensionsShowHiddenTest.java b/test/jalview/viewmodel/OverviewDimensionsShowHiddenTest.java
new file mode 100644 (file)
index 0000000..1bc3bfa
--- /dev/null
@@ -0,0 +1,1025 @@
+/*
+ * 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.viewmodel;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceCollectionI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+
+import java.util.Hashtable;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class OverviewDimensionsShowHiddenTest
+{
+  AlignmentI al;
+  OverviewDimensionsShowHidden od;
+
+  // cached widths and heights
+  int boxWidth;
+  int boxHeight;
+  int viewHeight;
+  int viewWidth;
+  int alheight;
+  int alwidth;
+
+  ViewportRanges vpranges;
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+
+  HiddenColumns hiddenCols = new HiddenColumns();
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpAlignment()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    al = gen.generate(157, 525, 123, 5, 5);
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp()
+  {
+    if (!hiddenRepSequences.isEmpty())
+    {
+      al.getHiddenSequences().showAll(hiddenRepSequences);
+    }
+    ColumnSelection colsel = new ColumnSelection();
+    hiddenCols.revealAllHiddenColumns(colsel);
+    
+    vpranges = new ViewportRanges(al);
+    vpranges.setViewportStartAndHeight(0, 18);
+    vpranges.setViewportStartAndWidth(0, 63);
+
+    viewHeight = vpranges.getEndSeq() - vpranges.getStartSeq() + 1;
+    viewWidth = vpranges.getEndRes() - vpranges.getStartRes() + 1;
+
+    HiddenColumns hiddenCols = new HiddenColumns();
+
+    od = new OverviewDimensionsShowHidden(vpranges, true);
+    // Initial box sizing - default path through code
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+
+    mouseClick(od, 0, 0);
+    moveViewport(0, 0);
+
+    // calculate before hidden columns so we get absolute values
+    alheight = vpranges.getAbsoluteAlignmentHeight();
+    alwidth = vpranges.getAbsoluteAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void cleanUp()
+  {
+    al = null;
+  }
+
+  /**
+   * Test that the OverviewDimensions constructor sets width and height
+   * correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testConstructor()
+  {
+    SequenceI seqa = new Sequence("Seq1", "ABC");
+    SequenceI seqb = new Sequence("Seq2", "ABC");
+    SequenceI seqc = new Sequence("Seq3", "ABC");
+    SequenceI seqd = new Sequence("Seq4", "ABC");
+    SequenceI seqe = new Sequence("Seq5",
+            "ABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+    int defaultGraphHeight = 20;
+    int maxWidth = 400;
+    int minWidth = 120;
+    int maxSeqHeight = 300;
+    int minSeqHeight = 40;
+
+    // test for alignment with width > height
+    SequenceI[] seqs1 = new SequenceI[] { seqa, seqb };
+    Alignment al1 = new Alignment(seqs1);
+    ViewportRanges props = new ViewportRanges(al1);
+
+    OverviewDimensions od = new OverviewDimensionsShowHidden(props, true);
+    int scaledHeight = 267;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), scaledHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), scaledHeight + defaultGraphHeight);
+
+    // test for alignment with width < height
+    SequenceI[] seqs2 = new SequenceI[] { seqa, seqb, seqc, seqd };
+    Alignment al2 = new Alignment(seqs2);
+    props = new ViewportRanges(al2);
+
+    od = new OverviewDimensionsShowHidden(props, true);
+    int scaledWidth = 300;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), scaledWidth);
+    assertEquals(od.getHeight(), scaledWidth + defaultGraphHeight);
+
+    // test for alignment with width > height and sequence height scaled below
+    // min value
+    SequenceI[] seqs3 = new SequenceI[] { seqe };
+    Alignment al3 = new Alignment(seqs3);
+    props = new ViewportRanges(al3);
+
+    od = new OverviewDimensionsShowHidden(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), minSeqHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), minSeqHeight + defaultGraphHeight);
+
+    // test for alignment with width < height and width scaled below min value
+    SequenceI[] seqs4 = new SequenceI[] { seqa, seqb, seqc, seqd, seqa,
+        seqb, seqc, seqd, seqa, seqb, seqc, seqd, seqa, seqb, seqc, seqd };
+    Alignment al4 = new Alignment(seqs4);
+    props = new ViewportRanges(al4);
+
+    od = new OverviewDimensionsShowHidden(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight + defaultGraphHeight);
+
+    Alignment al5 = new Alignment(seqs4);
+    props = new ViewportRanges(al5);
+
+    od = new OverviewDimensionsShowHidden(props, false);
+    assertEquals(od.getGraphHeight(), 0);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight);
+  }
+
+  /**
+   * Test that validation after mouse adjustments to boxX and boxY sets box
+   * dimensions and scroll values correctly, when there are no hidden rows or
+   * columns.
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromMouseClick()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // negative boxX value reset to 0
+    mouseClick(od, -5, 10);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartSeq(),
+            Math.round((float) 10 * alheight / od.getSequencesHeight()));
+    assertEquals(vpranges.getStartRes(), 0);
+
+    // negative boxY value reset to 0
+    mouseClick(od, 6, -2);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) 6 * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // overly large boxX value reset to width-boxWidth
+    mouseClick(od, 100, 6);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 6);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // overly large boxY value reset to sequenceHeight - boxHeight
+    mouseClick(od, 10, 520);
+    assertEquals(od.getBoxX(), 10);
+    assertEquals(od.getBoxY(), od.getSequencesHeight() - od.getBoxHeight());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+
+    // here (float) od.getBoxY() * alheight / od.getSequencesHeight() = 507.5
+    // and round rounds to 508; however we get 507 working with row values
+    // hence the subtraction of 1
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()) - 1);
+
+    // click past end of alignment, as above
+    mouseClick(od, 3000, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // move viewport so startRes non-zero and then mouseclick
+    moveViewportH(50);
+
+    // click at viewport position
+    int oldboxx = od.getBoxX();
+    int oldboxy = od.getBoxY();
+    mouseClick(od, od.getBoxX() + 5, od.getBoxY() + 2);
+    assertEquals(od.getBoxX(), oldboxx + 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getBoxY(), oldboxy + 2);
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // click at top corner
+    mouseClick(od, 0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide cols at start and check updated box position is correct
+    // changes boxX but not boxwidth
+    int lastHiddenCol = 30;
+    hiddenCols.hideColumns(0, lastHiddenCol);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                    / alwidth));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // try to click in hidden cols, check box does not move
+    int xpos = 10;
+    mouseClick(od, xpos, 0);
+    assertEquals(
+            od.getBoxX(),
+            Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                    / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(), 0);
+
+    // click to right of hidden columns, box moves to click point
+    testBoxIsAtClickPoint(40, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) 40 * alwidth / od.getWidth())
+                    - (lastHiddenCol + 1));
+
+    // click to right of hidden columns such that box runs over right hand side
+    // of alignment
+    // box position is adjusted away from the edge
+    // overly large boxX value reset to width-boxWidth
+    xpos = 100;
+    mouseClick(od, xpos, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth())
+                    - (lastHiddenCol + 1));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols in the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    
+    // hide columns 63-73, no change to box position or dimensions
+    int firstHidden = 63;
+    int lastHidden = 73;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so that it overlaps with hidden cols on one side
+    // box width changes, boxX and scrollCol as for unhidden case
+    int xpos = 55 - boxWidth; // 55 is position in overview approx halfway
+                              // between cols 60 and 70
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round(xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so that it completely covers hidden cols
+    // box width changes, boxX and scrollCol as for hidden case
+    xpos = 33;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so boxX is in hidden cols, box overhangs at right
+    // boxX and scrollCol at left of hidden area, box width extends across
+    // hidden region
+    xpos = 50;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((float) (lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(), firstHidden - 1);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // move box so boxX is to right of hidden cols, but does not go beyond full
+    // width of alignment
+    // box width, boxX and scrollCol all as for non-hidden case
+    xpos = 75;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round(xpos * alwidth / od.getWidth())
+                    - (lastHidden - firstHidden + 1));
+    
+    // move box so it goes beyond full width of alignment
+    // boxX, scrollCol adjusted back, box width normal
+    xpos = 3000;
+    mouseClick(od, xpos, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(
+            vpranges.getStartRes(),
+            Math.round(((float) od.getBoxX() * alwidth / od.getWidth())
+                    - (lastHidden - firstHidden + 1)));
+    assertEquals(
+            vpranges.getStartSeq(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide columns 140-164, no change to box position or dimensions
+    int firstHidden = 140;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // click to left of hidden cols, without overlapping
+    // boxX, scrollCol and width as normal
+    int xpos = 5;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+
+    // click to left of hidden cols, with overlap
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = Math.round((float) 145 * od.getWidth() / alwidth) - boxWidth;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // click in hidden cols
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = 115;
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // click off end of alignment
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = 3000;
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(vpranges.getStartSeq(), 0);
+  }
+
+  /**
+   * Test that the box position is set correctly when set from the viewport,
+   * with no hidden rows or columns
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewport()
+  {
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right
+    moveViewportH(70);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport down
+    moveViewportV(100);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round(100 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to bottom right
+    moveViewport(98, 508);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 98 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round((float) 508 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (lastHidden + 1) * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment - need to make startRes by removing
+    // hidden cols because of how viewport/overview are implemented
+    moveViewport(98 - lastHidden - 1, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 98 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * in the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsInMiddle()
+  {
+    int firstHidden = 68;
+    int lastHidden = 78;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to left of hidden columns with overlap
+    moveViewport(10, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 10 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((float) (lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden columns
+    moveViewport(63, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 63 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right of hidden columns, no overlap
+    moveViewport(80 - (lastHidden - firstHidden + 1), 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 80 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the end
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtEnd()
+  {
+    int firstHidden = 152;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to hidden columns
+    // viewport can't actually extend into hidden cols,
+    // so move to the far right edge of the viewport
+    moveViewport(firstHidden - viewWidth, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - viewWidth)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box moves to below hidden rows, height remains same
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) (lastHidden + 1) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    moveViewport(0, 525 - viewHeight - lastHidden - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (525 - viewHeight) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows in
+   * the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsInMiddle()
+  {
+    int firstHidden = 200;
+    int lastHidden = 210;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden rows
+    moveViewport(0, 198);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), Math.round ((float)198 * od.getSequencesHeight()
+            / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            Math.round((float) (viewHeight + lastHidden - firstHidden + 1)
+                    * od.getSequencesHeight() / alheight));
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the bottom
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtEnd()
+  {
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    // viewport sits above hidden rows and does not include them
+    moveViewport(0, firstHidden - viewHeight - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (firstHidden - viewHeight - 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows at start and check updated box position is correct
+    // changes boxY but not boxheight
+    int lastHiddenRow = 30;
+    hideSequences(0, lastHiddenRow);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) (lastHiddenRow + 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click in hidden rows - same result
+    mouseClick(od, 0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (lastHiddenRow + 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click below hidden rows
+    mouseClick(od, 0, 150);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 150);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows in middle and check updated box position is correct
+    // no changes
+    int firstHiddenRow = 50;
+    int lastHiddenRow = 54;
+    hideSequences(firstHiddenRow, lastHiddenRow);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows, so that box overlaps
+    int ypos = 35; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            boxHeight
+                    + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                            * od.getSequencesHeight() / alheight));
+
+    // click so that box straddles hidden rows
+    ypos = 44; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            boxHeight
+                    + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                            * od.getSequencesHeight() / alheight));
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(vpranges.getStartRes(), 0);
+    assertEquals(vpranges.getStartSeq(), 0);
+
+    // hide rows at end and check updated box position is correct
+    // no changes
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows
+    int ypos = 40; // row 40
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows so box overlaps
+    // boxY moved upwards, boxHeight remains same
+    ypos = 497; // row 497
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (firstHidden - viewHeight)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click within hidden rows
+    ypos = 505;
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((firstHidden - viewHeight) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /*
+   * Move viewport horizontally: startRes + previous width gives new horizontal extent. Vertical extent stays the same.
+   */
+  private void moveViewportH(int startRes)
+  {
+    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Move viewport vertically: startSeq and endSeq give new vertical extent. Horizontal extent stays the same.
+   */
+  private void moveViewportV(int startSeq)
+  {
+    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Move viewport horizontally and vertically.
+   */
+  private void moveViewport(int startRes, int startSeq)
+  {
+    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+
+  /*
+   * Mouse click as position x,y in overview window
+   */
+  private void mouseClick(OverviewDimensions od, int x, int y)
+  {
+    od.updateViewportFromMouse(x, y, al.getHiddenSequences(), hiddenCols);
+
+    // updates require an OverviewPanel to exist which it doesn't here
+    // so call setBoxPosition() as it would be called by the AlignmentPanel
+    // normally
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+  }
+  
+  /*
+   * Test that the box is positioned with the top left corner at xpos, ypos
+   * and with the original width and height
+   */
+  private void testBoxIsAtClickPoint(int xpos, int ypos)
+  {
+    mouseClick(od, xpos, ypos);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), ypos);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /*
+   * Hide sequences between start and end
+   */
+  private void hideSequences(int start, int end)
+  {
+    SequenceI[] allseqs = al.getSequencesArray();
+    SequenceGroup theseSeqs = new SequenceGroup();
+    
+    for (int i = start; i <= end; i++)
+    {
+      theseSeqs.addSequence(allseqs[i], false);
+      al.getHiddenSequences().hideSequence(allseqs[i]);
+    }
+
+    hiddenRepSequences.put(allseqs[start], theseSeqs);
+  }
+}
diff --git a/test/jalview/viewmodel/ViewportRangesTest.java b/test/jalview/viewmodel/ViewportRangesTest.java
new file mode 100644 (file)
index 0000000..636f8dd
--- /dev/null
@@ -0,0 +1,568 @@
+package jalview.viewmodel;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenSequences;
+
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ViewportRangesTest {
+
+  AlignmentGenerator gen = new AlignmentGenerator(false);
+
+  AlignmentI al = gen.generate(20, 30, 1, 5, 5);
+
+  AlignmentI smallAl = gen.generate(7, 2, 2, 5, 5);
+
+  @BeforeMethod(alwaysRun = true)
+  public void cleanUp()
+  {
+    ColumnSelection sel = new ColumnSelection();
+    al.getHiddenColumns().revealAllHiddenColumns(sel);
+    al.getHiddenSequences().showAll(null);
+    smallAl.getHiddenColumns().revealAllHiddenColumns(sel);
+    smallAl.getHiddenSequences().showAll(null);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testViewportRanges() 
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    
+    assertEquals(vr.getStartRes(),0);
+    assertEquals(vr.getEndRes(), al.getWidth()-1);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetAbsoluteAlignmentHeight()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+
+    assertEquals(vr.getAbsoluteAlignmentHeight(), al.getHeight());
+
+    al.getHiddenSequences().hideSequence(al.getSequenceAt(3));
+    assertEquals(vr.getAbsoluteAlignmentHeight(), al.getHeight() + 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetAbsoluteAlignmentWidth()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    assertEquals(vr.getAbsoluteAlignmentWidth(), al.getWidth());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetEndRes()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setEndRes(-1);
+    assertEquals(vr.getEndRes(), 0);
+
+    vr.setEndRes(al.getWidth() - 1);
+    assertEquals(vr.getEndRes(), al.getWidth() - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetEndSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setEndSeq(-1);
+    assertEquals(vr.getEndSeq(), 0);
+
+    vr.setEndSeq(al.getHeight());
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+
+    vr.setEndRes(al.getHeight() - 1);
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetStartRes()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartRes(-1);
+    assertEquals(vr.getStartRes(), 0);
+
+    vr.setStartRes(al.getWidth());
+    assertEquals(vr.getStartRes(), al.getWidth() - 1);
+
+    vr.setStartRes(al.getWidth() - 1);
+    assertEquals(vr.getStartRes(), al.getWidth() - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetStartSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartSeq(-1);
+    assertEquals(vr.getStartSeq(), 0);
+
+    vr.setStartSeq(al.getHeight() - vr.getViewportHeight() + 1);
+    assertEquals(vr.getStartSeq(), al.getHeight() - vr.getViewportHeight());
+
+    vr.setStartSeq(al.getHeight() - vr.getViewportHeight());
+    assertEquals(vr.getStartSeq(), al.getHeight() - vr.getViewportHeight());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetStartEndRes()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartEndRes(-1, -1);
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 0);
+
+    vr.setStartEndRes(5, 19);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 19);
+
+    vr.setStartEndRes(al.getWidth(), al.getWidth());
+    assertEquals(vr.getEndRes(), al.getWidth() - 1);
+
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setStartEndRes(al.getWidth(), al.getWidth());
+    assertEquals(vrsmall.getEndRes(), 6);
+
+    // make visible alignment width = 0
+    smallAl.getHiddenColumns().hideColumns(0, 6);
+    vrsmall.setStartEndRes(0, 4);
+    assertEquals(vrsmall.getStartRes(), 0);
+    assertEquals(vrsmall.getEndRes(), 0);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetStartEndSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartEndSeq(-1, -1);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), 0);
+
+    vr.setStartEndSeq(5, 19);
+    assertEquals(vr.getStartSeq(), 5);
+    assertEquals(vr.getEndSeq(), 19);
+
+    vr.setStartEndSeq(al.getHeight(), al.getHeight());
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+
+    // make visible alignment height = 0
+    smallAl.getHiddenSequences().hideSequence(smallAl.getSequenceAt(0));
+    smallAl.getHiddenSequences().hideSequence(smallAl.getSequenceAt(0));
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setStartEndSeq(0, 3);
+    assertEquals(vrsmall.getStartSeq(), 0);
+    assertEquals(vrsmall.getEndSeq(), 0);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportHeight()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportHeight(13);
+    assertEquals(vr.getViewportHeight(), 13);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportWidth()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportWidth(13);
+    assertEquals(vr.getViewportWidth(), 13);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportStartAndHeight()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndHeight(2, 6);
+    assertEquals(vr.getViewportHeight(), 6);
+    assertEquals(vr.getStartSeq(), 2);
+
+    // reset -ve values of start to 0
+    vr.setViewportStartAndHeight(-1, 7);
+    assertEquals(vr.getViewportHeight(), 7);
+    assertEquals(vr.getStartSeq(), 0);
+
+    // reset out of bounds start values to within bounds
+    vr.setViewportStartAndHeight(35, 5);
+    assertEquals(vr.getViewportHeight(), 5);
+    assertEquals(vr.getStartSeq(), 24);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportStartAndWidth()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndWidth(2, 6);
+    assertEquals(vr.getViewportWidth(), 6);
+    assertEquals(vr.getStartRes(), 2);
+
+    // reset -ve values of start to 0
+    vr.setViewportStartAndWidth(-1, 7);
+    assertEquals(vr.getViewportWidth(), 7);
+    assertEquals(vr.getStartRes(), 0);
+
+    // reset out of bounds start values to within bounds
+    vr.setViewportStartAndWidth(35, 5);
+    assertEquals(vr.getViewportWidth(), 5);
+    assertEquals(vr.getStartRes(), 16);
+
+    // small alignment doesn't get bounds reset
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setViewportStartAndWidth(0, 63);
+    assertEquals(vrsmall.getViewportWidth(), 7);
+    assertEquals(vrsmall.getStartRes(), 0);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testPageUpDown()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndHeight(8, 6);
+    vr.pageDown();
+    assertEquals(vr.getStartSeq(), 13);
+
+    vr.pageUp();
+    assertEquals(vr.getStartSeq(), 8);
+
+    vr.pageUp();
+    assertEquals(vr.getStartSeq(), 3);
+
+    vr.pageUp();
+    // pageup does not go beyond 0, viewport height stays the same
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getViewportHeight(), 6);
+
+    vr.pageDown();
+    vr.pageDown();
+    vr.pageDown();
+    vr.pageDown();
+    vr.pageDown();
+
+    // pagedown to bottom does not go beyond end, and height stays same
+    assertEquals(vr.getStartSeq(), 24);
+    assertEquals(vr.getViewportHeight(), 6);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testScrollUp()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndHeight(1, 5);
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 0);
+    // can't scroll above top
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 0);
+
+    vr.setViewportStartAndHeight(24, 5);
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 25);
+    // can't scroll beyond bottom
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 25);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testScrollUpWithHidden()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+
+    // hide last sequence
+    HiddenSequences hidden = new HiddenSequences(al);
+    hidden.hideSequence(al.getSequenceAt(29));
+
+    vr.setViewportStartAndHeight(1, 5);
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 0);
+    // can't scroll above top
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 0);
+
+    vr.setViewportStartAndHeight(23, 5);
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 24);
+    // can't scroll beyond bottom
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 24);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testScrollRight()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndWidth(1, 5);
+    vr.scrollRight(false);
+    assertEquals(vr.getStartRes(), 0);
+    // can't scroll left past start
+    vr.scrollRight(false);
+    assertEquals(vr.getStartRes(), 0);
+
+    vr.setViewportStartAndWidth(15, 5);
+    vr.scrollRight(true);
+    assertEquals(vr.getStartRes(), 16);
+    // can't scroll right past end
+    vr.scrollRight(true);
+    assertEquals(vr.getStartRes(), 16);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testScrollRightWithHidden()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+
+    // hide last 2 columns
+    HiddenColumns cols = new HiddenColumns();
+    cols.hideColumns(19, 20);
+    al.setHiddenColumns(cols);
+
+    vr.setViewportStartAndWidth(1, 5);
+    vr.scrollRight(false);
+    assertEquals(vr.getStartRes(), 0);
+    // can't scroll left past start
+    vr.scrollRight(false);
+    assertEquals(vr.getStartRes(), 0);
+
+    vr.setViewportStartAndWidth(13, 5);
+    vr.scrollRight(true);
+    assertEquals(vr.getStartRes(), 14);
+    // can't scroll right past last visible col
+    vr.scrollRight(true);
+    assertEquals(vr.getStartRes(), 14);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testScrollToWrappedVisible()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndWidth(5, 10);
+
+    vr.scrollToWrappedVisible(0);
+    assertEquals(vr.getStartRes(), 0);
+
+    vr.scrollToWrappedVisible(10);
+    assertEquals(vr.getStartRes(), 10);
+
+    vr.scrollToWrappedVisible(15);
+    assertEquals(vr.getStartRes(), 10);
+  }
+
+  // leave until JAL-2388 is merged and we can do without viewport
+  /*@Test(groups = { "Functional" })
+  public void testScrollToVisible()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportStartAndWidth(12,5);
+    vr.setViewportStartAndHeight(10,6);
+    vr.scrollToVisible(13,14)
+    
+    // no change
+    assertEquals(vr.getStartRes(), 12);
+    assertEquals(vr.getStartSeq(), 10);
+    
+    vr.scrollToVisible(5,6);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getStartSeq(), 6);
+    
+    // test for hidden columns too
+  }*/
+
+  @Test(groups = { "Functional" })
+  public void testEventFiring()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    MockPropChangeListener l = new MockPropChangeListener(vr);
+    List<String> emptylist = new ArrayList<>();
+
+    vr.setViewportWidth(5);
+    vr.setViewportHeight(5);
+    l.reset();
+
+    // one event fired when startRes is called with new value
+    vr.setStartRes(4);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    // no event fired for same value
+    vr.setStartRes(4);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    vr.setEndRes(10);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    // no event fired for same value
+    vr.setEndRes(10);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    vr.setStartSeq(4);
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.setStartSeq(4);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    vr.setEndSeq(10);
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.setEndSeq(10);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    vr.setStartEndRes(2, 15);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    vr.setStartEndRes(2, 15);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    // check new value fired by event is corrected startres
+    vr.setStartEndRes(-1, 5);
+    assertTrue(l.verify(1, Arrays.asList("startres"), Arrays.asList(0)));
+    l.reset();
+
+    // check new value fired by event is corrected endres
+    vr.setStartEndRes(0, -1);
+    assertTrue(l.verify(1, Arrays.asList("endres"), Arrays.asList(0)));
+    l.reset();
+
+    vr.setStartEndSeq(2, 15);
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.setStartEndSeq(2, 15);
+    assertTrue(l.verify(0, emptylist));
+    l.reset();
+
+    vr.setStartEndRes(2, 2); // so seq and res values should be different, in
+                             // case of transposing in code
+    l.reset();
+
+    // check new value fired by event is corrected startseq
+    vr.setStartEndSeq(-1, 5);
+    assertTrue(l.verify(1, Arrays.asList("startseq"), Arrays.asList(0)));
+    l.reset();
+
+    // check new value fired by event is corrected endseq
+    vr.setStartEndSeq(0, -1);
+    assertTrue(l.verify(1, Arrays.asList("endseq"), Arrays.asList(0)));
+    l.reset();
+
+    // reset for later tests
+    vr.setStartEndSeq(2, 15);
+    l.reset();
+
+    // test viewport height and width setting triggers event
+    vr.setViewportHeight(10);
+    assertTrue(l.verify(1, Arrays.asList("endseq")));
+    l.reset();
+
+    vr.setViewportWidth(18);
+    assertTrue(l.verify(1, Arrays.asList("endres")));
+    l.reset();
+
+    // already has seq start set to 2, so triggers endseq
+    vr.setViewportStartAndHeight(2, 16);
+    assertTrue(l.verify(1, Arrays.asList("endseq")));
+    l.reset();
+
+    vr.setViewportStartAndWidth(1, 14);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    // test page up/down triggers event
+    vr.pageUp();
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.pageDown();
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    // test scrolling triggers event
+    vr.scrollUp(true);
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.scrollUp(false);
+    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    l.reset();
+
+    vr.scrollRight(true);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    vr.scrollRight(false);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+
+    vr.scrollToVisible(10, 10);
+    assertTrue(l.verify(4,
+            Arrays.asList("startseq", "startseq", "startseq", "startseq")));
+    l.reset();
+
+    vr.scrollToWrappedVisible(5);
+    assertTrue(l.verify(1, Arrays.asList("startres")));
+    l.reset();
+  }
+}
+
+// mock listener for property change events
+class MockPropChangeListener implements ViewportListenerI
+{
+  private int firecount = 0;
+
+  private List<String> events = new ArrayList<>();
+
+  private List<Integer> newvalues = new ArrayList<>();
+
+  public MockPropChangeListener(ViewportRanges vr)
+  {
+    vr.addPropertyChangeListener(this);
+  }
+
+  @Override
+  public void propertyChange(PropertyChangeEvent evt)
+  {
+    firecount++;
+    events.add(evt.getPropertyName());
+    newvalues.add((Integer) evt.getNewValue());
+  }
+
+  public boolean verify(int count, List<String> eventslist,
+          List<Integer> valueslist)
+  {
+    return (count == firecount) && events.equals(eventslist)
+            && newvalues.equals(valueslist);
+  }
+
+  public boolean verify(int count, List<String> eventslist)
+  {
+    return (count == firecount) && events.equals(eventslist);
+  }
+
+  public void reset()
+  {
+    firecount = 0;
+    events.clear();
+    newvalues.clear();
+  }
+}
diff --git a/test/jalview/ws/dbsources/RemoteFormatTest.java b/test/jalview/ws/dbsources/RemoteFormatTest.java
new file mode 100644 (file)
index 0000000..90d4472
--- /dev/null
@@ -0,0 +1,121 @@
+package jalview.ws.dbsources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignSeq;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefSource;
+import jalview.datamodel.SequenceI;
+import jalview.ext.ensembl.EnsemblGenomes;
+import jalview.fts.api.FTSData;
+import jalview.fts.api.FTSDataColumnI;
+import jalview.fts.api.FTSRestClientI;
+import jalview.fts.core.FTSRestRequest;
+import jalview.fts.core.FTSRestResponse;
+import jalview.fts.service.uniprot.UniProtFTSRestClient;
+import jalview.ws.SequenceFetcher;
+import jalview.ws.seqfetcher.DbSourceProxy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * A class to verify that remotely fetched data has an expected format and can
+ * be successfully processed by Jalview. This is intended as a first line of
+ * defence and early warning of service affecting changes to data fetched
+ * externally.
+ * <p>
+ * This is class is not intended to cover remote services e.g. alignment. Nor
+ * should it duplicate tests already provided by other classes (such as
+ * PDBFTSRestClientTest). Or maybe we will relocate those tests here...
+ */
+public class RemoteFormatTest
+{
+  SequenceFetcher sf;
+
+  @BeforeTest(alwaysRun = true)
+  public void setUp() throws Exception
+  {
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    // ensure 'add annotation from structure' is selected
+    Cache.applicationProperties.setProperty("STRUCT_FROM_PDB",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("ADD_SS_ANN",
+            Boolean.TRUE.toString());
+
+    sf = new SequenceFetcher(false);
+  }
+
+  @DataProvider(name = "AccessionData")
+  protected Object[][] getAccessions()
+  {
+    return new Object[][] { { DBRefSource.UNIPROT, "P30419" },
+        { DBRefSource.PDB, "1QIP" }, { DBRefSource.EMBL, "X53828" },
+        { DBRefSource.EMBLCDS, "CAA37824" },
+        { DBRefSource.ENSEMBL, "ENSG00000157764" },
+        { new EnsemblGenomes().getDbSource(), "DDB_G0283883" },
+        { new PfamFull().getDbSource(), "PF03760" },
+        { new PfamSeed().getDbSource(), "PF03760" },
+        { new RfamSeed().getDbSource(), "RF00014" } };
+  }
+
+  @Test(groups = "Network", dataProvider = "AccessionData")
+  public void testFetchAccession(String dbSource, String accessionId)
+          throws Exception
+  {
+    System.out.println("Fetching " + accessionId + " from " + dbSource);
+    List<DbSourceProxy> sps = sf.getSourceProxy(dbSource);
+    assertFalse(sps.isEmpty());
+    AlignmentI al = sps.get(0).getSequenceRecords(accessionId);
+    assertNotNull(al);
+    assertTrue(al.getHeight() > 0);
+    SequenceI sq = al.getSequenceAt(0);
+    // suppress this check as only Uniprot and PDB acquire PDB refs
+    // assertTrue(sq.getAllPDBEntries().size() > 0, "No PDBEntry on sequence.");
+    assertTrue(sq.getDBRefs().length > 0, "No DBRef on sequence.");
+    // suppress this test as only certain databases provide 'primary' dbrefs
+    // assertFalse(sq.getPrimaryDBRefs().isEmpty());
+    int length = AlignSeq.extractGaps("-. ", sq.getSequenceAsString())
+            .length();
+    assertEquals(sq.getEnd() - sq.getStart() + 1, length,
+            "Sequence start/end doesn't match number of residues in sequence");
+  }
+
+  @Test(groups = { "Network" })
+  public void testUniprotFreeTextSearch() throws Exception
+  {
+    List<FTSDataColumnI> wantedFields = new ArrayList<FTSDataColumnI>();
+    FTSRestClientI client = UniProtFTSRestClient.getInstance();
+    wantedFields.add(client.getDataColumnByNameOrCode("id"));
+    wantedFields.add(client.getDataColumnByNameOrCode("entry name"));
+    wantedFields.add(client.getDataColumnByNameOrCode("organism"));
+    wantedFields.add(client.getDataColumnByNameOrCode("reviewed")); // Status
+    wantedFields.add(client.getDataColumnByNameOrCode("length"));
+  
+    FTSRestRequest request = new FTSRestRequest();
+    request.setAllowEmptySeq(false);
+    request.setResponseSize(100);
+    request.setFieldToSearchBy("Search All");
+    request.setSearchTerm("metanephrops"); // lobster!
+    request.setWantedFields(wantedFields);
+  
+    FTSRestResponse response;
+    response = client.executeRequest(request);
+    assertTrue(response.getNumberOfItemsFound() > 20);
+    assertTrue(response.getSearchSummary() != null);
+    assertTrue(response.getSearchSummary().size() > 20);
+    // verify we successfully filtered out the header row (JAL-2485)
+    FTSData header = response.getSearchSummary().iterator().next();
+    assertFalse(
+            header.getSummaryData()[0].toString().equalsIgnoreCase("Entry"),
+            "Failed to filter out summary header row");
+  }
+}
index 2d317e4..e8b6c2b 100644 (file)
@@ -44,7 +44,11 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-@Test(groups = { "External" })
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class DisorderAnnotExportImport
 {
 
@@ -65,12 +69,19 @@ public class DisorderAnnotExportImport
 
   public static jalview.gui.AlignFrame af = null;
 
-  @BeforeClass(inheritGroups = true)
+  @BeforeClass(alwaysRun = true)
   public static void setUpBeforeClass() throws Exception
   {
     Cache.loadProperties("test/jalview/io/testProps.jvprops");
     Cache.initLogger();
     disc = JalviewJabawsTestUtils.getJabawsDiscoverer();
+
+    while (disc.isRunning())
+    {
+      // don't get services until discoverer has finished
+      Thread.sleep(100);
+    }
+
     iupreds = new ArrayList<Jws2Instance>();
     for (Jws2Instance svc : disc.getServices())
     {
@@ -100,7 +111,7 @@ public class DisorderAnnotExportImport
   /**
    * test for patches to JAL-1294
    */
-  @Test
+  @Test(groups = { "External", "Network" })
   public void testDisorderAnnotExport()
   {
     disorderClient = new AADisorderClient(iupreds.get(0), af, null, null);
@@ -166,7 +177,8 @@ public class DisorderAnnotExportImport
                       DataSourceType.PASTE));
 
       // test for consistency in io
-      StockholmFileTest.testAlignmentEquivalence(al, al_new, true);
+      StockholmFileTest.testAlignmentEquivalence(al, al_new, true, false,
+              false);
       return;
     } catch (Exception e)
     {
diff --git a/test/jalview/ws/jabaws/JpredJabaStructExportImport.java b/test/jalview/ws/jabaws/JpredJabaStructExportImport.java
deleted file mode 100644 (file)
index d5b6ed1..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.ws.jabaws;
-
-import static org.testng.AssertJUnit.assertNotNull;
-import static org.testng.AssertJUnit.assertTrue;
-
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentI;
-import jalview.gui.Jalview2XML;
-import jalview.gui.JvOptionPane;
-import jalview.io.AnnotationFile;
-import jalview.io.DataSourceType;
-import jalview.io.FileFormat;
-import jalview.io.FormatAdapter;
-import jalview.io.StockholmFileTest;
-import jalview.ws.jws2.JPred301Client;
-import jalview.ws.jws2.JabaParamStore;
-import jalview.ws.jws2.Jws2Discoverer;
-import jalview.ws.jws2.SequenceAnnotationWSClient;
-import jalview.ws.jws2.jabaws2.Jws2Instance;
-import jalview.ws.params.AutoCalcSetting;
-
-import java.awt.Component;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import compbio.metadata.Argument;
-import compbio.metadata.WrongParameterException;
-
-public class JpredJabaStructExportImport
-{
-
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
-  public static String testseqs = "examples/uniref50.fa";
-
-  public static Jws2Discoverer disc;
-
-  public static Jws2Instance jpredws;
-
-  jalview.ws.jws2.JPred301Client jpredClient;
-
-  public static jalview.gui.AlignFrame af = null;
-
-  @BeforeClass(alwaysRun = true)
-  public static void setUpBeforeClass() throws Exception
-  {
-    Cache.loadProperties("test/jalview/io/testProps.jvprops");
-    Cache.initLogger();
-    disc = JalviewJabawsTestUtils.getJabawsDiscoverer(false);
-
-    for (Jws2Instance svc : disc.getServices())
-    {
-      if (svc.getServiceTypeURI().toLowerCase().contains("jpred"))
-      {
-        jpredws = svc;
-      }
-    }
-
-    System.out.println("State of jpredws: " + jpredws);
-    Assert.assertNotNull(jpredws, "jpredws is null!");
-    jalview.io.FileLoader fl = new jalview.io.FileLoader(false);
-    af = fl.LoadFileWaitTillLoaded(testseqs, jalview.io.DataSourceType.FILE);
-    assertNotNull("Couldn't load test data ('" + testseqs + "')", af);
-  }
-
-  @AfterClass(alwaysRun = true)
-  public static void tearDownAfterClass() throws Exception
-  {
-    if (af != null)
-    {
-      af.setVisible(false);
-      af.dispose();
-    }
-  }
-
-  @Test(groups = { "Functional" })
-  public void testJPredStructOneSeqOnly()
-  {
-    af.selectAllSequenceMenuItem_actionPerformed(null);
-    af.getViewport()
-            .getSelectionGroup()
-            .addOrRemove(
-                    af.getViewport().getSelectionGroup().getSequenceAt(0),
-                    false);
-    af.hideSelSequences_actionPerformed(null);
-    jpredClient = new JPred301Client(jpredws, af, null, null);
-
-    assertTrue(
-            "Didn't find any default args to check for. Buggy implementation of hardwired arguments in client.",
-            jpredClient.selectDefaultArgs().size() > 0);
-
-    boolean success = false;
-    af.getViewport().getCalcManager().startWorker(jpredClient);
-    do
-    {
-      try
-      {
-        Thread.sleep(500);
-        List<Argument> args = JabaParamStore.getJabafromJwsArgs(af
-                .getViewport()
-                .getCalcIdSettingsFor(jpredClient.getCalcId())
-                .getArgumentSet()), defargs = jpredClient
-                .selectDefaultArgs();
-        for (Argument rg : args)
-        {
-          for (Argument defg : defargs)
-          {
-            if (defg.equals(rg))
-            {
-              success = true;
-            }
-          }
-        }
-        if (!success)
-        {
-          jpredClient.cancelCurrentJob();
-          Assert.fail("Jpred Client didn't run with hardwired default parameters.");
-        }
-
-      } catch (InterruptedException x)
-      {
-      }
-      ;
-    } while (af.getViewport().getCalcManager().isWorking());
-
-  }
-
-  @Test(groups = { "Functional" })
-  public void testJPredStructExport()
-  {
-
-    jpredClient = new JPred301Client(jpredws, af, null, null);
-
-    af.getViewport().getCalcManager().startWorker(jpredClient);
-
-    do
-    {
-      try
-      {
-        Thread.sleep(50);
-      } catch (InterruptedException x)
-      {
-      }
-      ;
-    } while (af.getViewport().getCalcManager().isWorking());
-
-    AlignmentI orig_alig = af.getViewport().getAlignment();
-
-    testAnnotationFileIO("Testing JPredWS Annotation IO", orig_alig);
-
-  }
-
-  public static void testAnnotationFileIO(String testname, AlignmentI al)
-  {
-    try
-    {
-      // what format would be appropriate for RNAalifold annotations?
-      String aligfileout = FileFormat.Pfam.getWriter(null).print(
-              al.getSequencesArray(), true);
-
-      String anfileout = new AnnotationFile()
-              .printAnnotationsForAlignment(al);
-      assertTrue(
-              "Test "
-                      + testname
-                      + "\nAlignment annotation file was not regenerated. Null string",
-              anfileout != null);
-      assertTrue(
-              "Test "
-                      + testname
-                      + "\nAlignment annotation file was not regenerated. Empty string",
-              anfileout.length() > "JALVIEW_ANNOTATION".length());
-
-      System.out.println("Output annotation file:\n" + anfileout
-              + "\n<<EOF\n");
-
-      // again what format would be appropriate?
-      AlignmentI al_new = new FormatAdapter().readFile(aligfileout,
-              DataSourceType.PASTE, FileFormat.Fasta);
-      assertTrue(
-              "Test "
-                      + testname
-                      + "\nregenerated annotation file did not annotate alignment.",
-              new AnnotationFile().readAnnotationFile(al_new, anfileout,
-                      DataSourceType.PASTE));
-
-      // test for consistency in io
-      StockholmFileTest.testAlignmentEquivalence(al, al_new, false);
-      return;
-    } catch (Exception e)
-    {
-      e.printStackTrace();
-    }
-    Assert.fail("Test "
-            + testname
-            + "\nCouldn't complete Annotation file roundtrip input/output/input test.");
-  }
-
-  @Test(groups = { "Functional" })
-  public void testJpredwsSettingsRecovery()
-  {
-    Assert.fail("not implemnented");
-    List<compbio.metadata.Argument> opts = new ArrayList<compbio.metadata.Argument>();
-    for (compbio.metadata.Argument rg : (List<compbio.metadata.Argument>) jpredws
-            .getRunnerConfig().getArguments())
-    {
-      if (rg.getDescription().contains("emperature"))
-      {
-        try
-        {
-          rg.setValue("292");
-        } catch (WrongParameterException q)
-        {
-          Assert.fail("Couldn't set the temperature parameter "
-                  + q.getStackTrace());
-        }
-        opts.add(rg);
-      }
-      if (rg.getDescription().contains("max"))
-      {
-        opts.add(rg);
-      }
-    }
-    jpredClient = new JPred301Client(jpredws, af, null, opts);
-
-    af.getViewport().getCalcManager().startWorker(jpredClient);
-
-    do
-    {
-      try
-      {
-        Thread.sleep(50);
-      } catch (InterruptedException x)
-      {
-      }
-      ;
-    } while (af.getViewport().getCalcManager().isWorking());
-    AutoCalcSetting oldacs = af.getViewport().getCalcIdSettingsFor(
-            jpredClient.getCalcId());
-    String oldsettings = oldacs.getWsParamFile();
-    // write out parameters
-    jalview.gui.AlignFrame nalf = null;
-    assertTrue("Couldn't write out the Jar file",
-            new Jalview2XML(false).saveAlignment(af,
-                    "testJPredWS_param.jar", "trial parameter writeout"));
-    assertTrue("Couldn't read back the Jar file", (nalf = new Jalview2XML(
-            false).loadJalviewAlign("testJpredWS_param.jar")) != null);
-    if (nalf != null)
-    {
-      AutoCalcSetting acs = af.getViewport().getCalcIdSettingsFor(
-              jpredClient.getCalcId());
-      assertTrue("Calc ID settings not recovered from viewport stash",
-              acs.equals(oldacs));
-      assertTrue(
-              "Serialised Calc ID settings not identical to those recovered from viewport stash",
-              acs.getWsParamFile().equals(oldsettings));
-      JMenu nmenu = new JMenu();
-      new SequenceAnnotationWSClient()
-              .attachWSMenuEntry(nmenu, jpredws, af);
-      assertTrue("Couldn't get menu entry for service",
-              nmenu.getItemCount() > 0);
-      for (Component itm : nmenu.getMenuComponents())
-      {
-        if (itm instanceof JMenuItem)
-        {
-          JMenuItem i = (JMenuItem) itm;
-          if (i.getText().equals(
-                  jpredws.getAlignAnalysisUI().getAAconToggle()))
-          {
-            i.doClick();
-            break;
-          }
-        }
-      }
-      while (af.getViewport().isCalcInProgress())
-      {
-        try
-        {
-          Thread.sleep(200);
-        } catch (Exception x)
-        {
-        }
-        ;
-      }
-      AutoCalcSetting acs2 = af.getViewport().getCalcIdSettingsFor(
-              jpredClient.getCalcId());
-      assertTrue(
-              "Calc ID settings after recalculation has not been recovered.",
-              acs2.getWsParamFile().equals(oldsettings));
-    }
-  }
-}
index 998524a..6af831f 100644 (file)
@@ -66,6 +66,11 @@ public class MinJabawsClientTests
       MsaWS msaservice = null;
       for (Services service : registry.getSupportedServices())
       {
+        if (service == null)
+        {
+          // the 'unsupported service'
+          continue;
+        }
         if (service.equals(Services.ClustalOWS))
         {
           msaservice = (MsaWS) Jws2Client.connect(url, service);
index f1430f6..089c29f 100644 (file)
@@ -55,6 +55,11 @@ import org.testng.annotations.Test;
 import compbio.metadata.Argument;
 import compbio.metadata.WrongParameterException;
 
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class RNAStructExportImport
 {
 
@@ -84,6 +89,12 @@ public class RNAStructExportImport
     Cache.initLogger();
     disc = JalviewJabawsTestUtils.getJabawsDiscoverer(false);
 
+    while (disc.isRunning())
+    {
+      // don't get services until discoverer has finished
+      Thread.sleep(100);
+    }
+
     for (Jws2Instance svc : disc.getServices())
     {
 
@@ -139,7 +150,7 @@ public class RNAStructExportImport
     }
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRNAAliFoldValidStructure()
   {
 
@@ -172,7 +183,7 @@ public class RNAStructExportImport
     }
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRNAStructExport()
   {
 
@@ -191,12 +202,14 @@ public class RNAStructExportImport
     } while (af.getViewport().getCalcManager().isWorking());
 
     AlignmentI orig_alig = af.getViewport().getAlignment();
-
-    testAnnotationFileIO("Testing RNAalifold Annotation IO", orig_alig);
+    // JBPNote: this assert fails (2.10.2) because the 'Reference Positions'
+    // annotation is mistakenly recognised as an RNA annotation row when read in
+    // as an annotation file.
+    verifyAnnotationFileIO("Testing RNAalifold Annotation IO", orig_alig);
 
   }
 
-  public static void testAnnotationFileIO(String testname, AlignmentI al)
+  static void verifyAnnotationFileIO(String testname, AlignmentI al)
   {
     try
     {
@@ -231,7 +244,8 @@ public class RNAStructExportImport
                       DataSourceType.PASTE));
 
       // test for consistency in io
-      StockholmFileTest.testAlignmentEquivalence(al, al_new, false);
+      StockholmFileTest.testAlignmentEquivalence(al, al_new, false, false,
+              false);
       return;
     } catch (Exception e)
     {
@@ -242,7 +256,7 @@ public class RNAStructExportImport
             + "\nCouldn't complete Annotation file roundtrip input/output/input test.");
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRnaalifoldSettingsRecovery()
   {
     List<Argument> opts = new ArrayList<Argument>();
index 0662e5b..c0aa2ee 100644 (file)
@@ -42,6 +42,11 @@ import compbio.metadata.Preset;
 import compbio.metadata.PresetManager;
 import compbio.metadata.WrongParameterException;
 
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class ParameterUtilsTest
 {
 
@@ -69,7 +74,7 @@ public class ParameterUtilsTest
     disc = JalviewJabawsTestUtils.getJabawsDiscoverer();
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testWriteParameterSet() throws WrongParameterException
   {
     for (Jws2Instance service : disc.getServices())
@@ -129,7 +134,7 @@ public class ParameterUtilsTest
             || serviceTests.contains(service.serviceType.toLowerCase());
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testCopyOption()
   {
     for (Jws2Instance service : disc.getServices())
@@ -153,7 +158,7 @@ public class ParameterUtilsTest
 
   /**
    */
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testCopyParameter()
   {
     for (Jws2Instance service : disc.getServices())
index 98ca303..f1dafcb 100644 (file)
  */
 package jalview.ws.seqfetcher;
 
+import static org.testng.Assert.assertTrue;
+
+import jalview.bin.Cache;
 import jalview.gui.JvOptionPane;
 
-import org.testng.AssertJUnit;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -36,15 +38,12 @@ public class DasSequenceFetcher
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testDasRegistryContact()
   {
-    jalview.bin.Cache.getDasSourceRegistry().refreshSources();
-    AssertJUnit
-            .assertTrue(
-                    "Expected to find at least one DAS source at the registry. Check config.",
-                    jalview.bin.Cache.getDasSourceRegistry().getSources()
-                            .size() > 0);
+    Cache.getDasSourceRegistry().refreshSources();
+    assertTrue(Cache.getDasSourceRegistry().getSources().isEmpty(),
+            "Expected to find no DAS sources at the registry. Check config.");
   }
 
 }
index d805e47..b92766e 100644 (file)
@@ -38,6 +38,8 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 import org.testng.Assert;
 import org.testng.FileAssert;
@@ -280,7 +282,19 @@ public class SiftsClientTest
               "A", testSeq, null);
       Assert.assertEquals(testSeq.getStart(), 1);
       Assert.assertEquals(testSeq.getEnd(), 147);
-      Assert.assertEquals(actualMapping, expectedMapping);
+      // Can't do Assert.assertEquals(actualMapping, expectedMapping);
+      // because this fails in our version of TestNG
+      Assert.assertEquals(actualMapping.size(), expectedMapping.size());
+      Iterator<Map.Entry<Integer, int[]>> it = expectedMapping.entrySet()
+              .iterator();
+      while (it.hasNext())
+      {
+        Map.Entry<Integer, int[]> pair = it.next();
+        Assert.assertTrue(actualMapping.containsKey(pair.getKey()));
+        Assert.assertEquals(actualMapping.get(pair.getKey()),
+                pair.getValue());
+      }
+
     } catch (Exception e)
     {
       e.printStackTrace();
@@ -399,7 +413,21 @@ groups = { "Network" },
 
     Assert.assertEquals(strucMapping.getMappingDetailsOutput(),
             expectedMappingOutput);
-    Assert.assertEquals(strucMapping.getMapping(), expectedMapping);
+
+    // Can't do Assert.assertEquals(strucMapping.getMapping(), expectedMapping);
+    // because this fails in our version of TestNG
+    Assert.assertEquals(strucMapping.getMapping().size(),
+            expectedMapping.size());
+    Iterator<Map.Entry<Integer, int[]>> it = expectedMapping.entrySet()
+            .iterator();
+    while (it.hasNext())
+    {
+      Map.Entry<Integer, int[]> pair = it.next();
+      Assert.assertTrue(strucMapping.getMapping()
+              .containsKey(pair.getKey()));
+      Assert.assertEquals(strucMapping.getMapping().get(pair.getKey()),
+              pair.getValue());
+    }
   }
 
   @Test(groups = { "Network" })
@@ -486,4 +514,22 @@ groups = { "Network" },
     Assert.assertNull(entityP);
 
   }
+
+  @Test(groups = { "Network" })
+  public void getLeadingIntegerFromString()
+  {
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("1234abcd", -1), 1234);
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("1234", -1),
+            1234);
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("abcd", -1), -1);
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("abcd1234", -1), -1);
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("None", -1), -1);
+    Assert.assertEquals(
+            SiftsClient.getLeadingIntegerValue("Null", -1), -1);
+  }
 }
diff --git a/test/junit/extensions/PA.java b/test/junit/extensions/PA.java
new file mode 100644 (file)
index 0000000..57c873f
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package junit.extensions;
+
+import java.util.Collection;
+
+/**
+ * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax
+ * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
+ * <p>
+ * a.k.a. The "ObjectMolester"
+ * <p>
+ * Here is an example of using this to access a private member: <br>
+ * Given the following class <code>MyClass</code>: <br>
+ * 
+ * <pre>
+ * public class MyClass {
+ *    private String name; // private attribute
+ * 
+ *    // private constructor
+ *    private MyClass() {
+ *       super();
+ *    }
+ * 
+ *    // private method
+ *    private void setName(String newName) {
+ *       this.name = newName;
+ *    }
+ * }
+ * </pre>
+ * 
+ * We now want to access the class: <br>
+ * 
+ * <pre>
+ * MyClass myObj = PA.instantiate(MyClass.class);
+ * PA.invokeMethod(myObj, &quot;setName(java.lang.String)&quot;, &quot;myNewName&quot;);
+ * String name = PA.getValue(myObj, &quot;name&quot;);
+ * </pre>
+ * 
+ * This class extends {@link PrivilegedAccessor} by re-throwing checked {@link Exception}s as {@link RuntimeException}s.
+ * 
+ * 
+ * @see PrivilegedAccessor
+ * 
+ * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
+ * @author Lubos Bistak (lubos@bistak.sk)
+ */
+public class PA {
+   private final Object instanceOrClass;
+
+   /**
+    * Private constructor to make it impossible to instantiate this class from outside of PA.
+    * 
+    * @param instanceOrClass
+    */
+   private PA(Object instanceOrClass) {
+      this.instanceOrClass = instanceOrClass;
+   }
+
+   /**
+    * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
+    * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
+    * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
+    * 
+    * @param instanceOrClass the object or class to get a string representation of
+    * @return a string representation of the given object
+    * 
+    * @see PrivilegedAccessor#toString(Object)
+    */
+   public static String toString(final Object instanceOrClass) {
+      return PrivilegedAccessor.toString(instanceOrClass);
+   }
+
+   /**
+    * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
+    * fields (public, private, protected, default) of all its super classes.
+    * 
+    * @param instanceOrClass the instance or class to get the fields of
+    * @return the collection of field names of the given instance or class
+    * 
+    * @see PrivilegedAccessor#getFieldNames(Object)
+    */
+   public static Collection<String> getFieldNames(final Object instanceOrClass) {
+      return PrivilegedAccessor.getFieldNames(instanceOrClass);
+   }
+
+   /**
+    * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
+    * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
+    * 
+    * @param instanceOrClass the instance or class to get the method signatures of
+    * @return the collection of method signatures of the given instance or class
+    * 
+    * @see PrivilegedAccessor#getMethodSignatures(Object)
+    */
+   public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
+      return PrivilegedAccessor.getMethodSignatures(instanceOrClass);
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws IllegalArgumentException if the field does not exist
+    * 
+    * @see PrivilegedAccessor#getValue(Object,String)
+    */
+   public static Object getValue(final Object instanceOrClass, final String fieldName) {
+      try {
+         return PrivilegedAccessor.getValue(instanceOrClass, fieldName);
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't get value of " + fieldName + " from " + instanceOrClass, e);
+      }
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object.
+    * 
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws IllegalArgumentException if the field does not exist
+    * 
+    * @see PA#getValue(Object,String)
+    */
+   public Object getValue(final String fieldName) {
+      return PA.getValue(instanceOrClass, fieldName);
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
+    * member class, you must provide the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param arguments the arguments to pass to the constructor
+    * @param argumentTypes the fully qualified types of the arguments of the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
+    *            parameters differ; if an unwrapping conversion for primitive arguments fails; if, after possible unwrapping, a
+    *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
+    *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
+    *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
+    *            underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.instantiate(fromClass, argumentTypes, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
+      }
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
+    * the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param arguments the arguments to pass to the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
+    *            parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a
+    *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
+    *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
+    *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
+    *            underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.instantiate(fromClass, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
+      }
+   }
+
+   /**
+    * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
+    * primitives.
+    * 
+    * @param instanceOrClass the instance or class to invoke the method on
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
+    *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
+    *            an argument couldn't be converted to match the expected type
+    * 
+    * @see PrivilegedAccessor#invokeMethod(Object,String,Object[])
+    */
+   public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.invokeMethod(instanceOrClass, methodSignature, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't invoke method " + methodSignature + " on " + instanceOrClass + " with arguments "
+            + arguments, e);
+      }
+   }
+
+   /**
+    * Calls a method with the given arguments. Arguments can be object types or representations for primitives.
+    * 
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
+    *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
+    *            an argument couldn't be converted to match the expected type
+    * @see PA#invokeMethod(Object, String, Object...)
+    */
+   public Object invokeMethod(final String methodSignature, final Object... arguments) {
+      return PA.invokeMethod(instanceOrClass, methodSignature, arguments);
+   }
+
+   /**
+    * Corrects varargs to their initial form. If you call a method with an object-array as last argument the Java varargs mechanism
+    * converts this array in single arguments. This method returns an object array if the arguments are all of the same type.
+    * 
+    * @param arguments the possibly converted arguments of a vararg method
+    * @return arguments possibly converted
+    */
+   private static Object[] correctVarargs(final Object... arguments) {
+      if ((arguments == null) || changedByVararg(arguments)) return new Object[] {arguments};
+      return arguments;
+   }
+
+   /**
+    * Tests if the arguments were changed by vararg. Arguments are changed by vararg if they are of a non primitive array type. E.g.
+    * arguments[] = Object[String[]] is converted to String[] while e.g. arguments[] = Object[int[]] is not converted and stays
+    * Object[int[]]
+    * 
+    * Unfortunately we can't detect the difference for arg = Object[primitive] since arguments[] = Object[Object[primitive]] which is
+    * converted to Object[primitive] and arguments[] = Object[primitive] which stays Object[primitive]
+    * 
+    * and we can't detect the difference for arg = Object[non primitive] since arguments[] = Object[Object[non primitive]] is converted
+    * to Object[non primitive] and arguments[] = Object[non primitive] stays Object[non primitive]
+    * 
+    * @param parameters the parameters
+    * @return true if parameters were changes by varargs, false otherwise
+    */
+   private static boolean changedByVararg(final Object[] parameters) {
+      if ((parameters.length == 0) || (parameters[0] == null)) return false;
+
+      if (parameters.getClass() == Object[].class) return false;
+
+      return true;
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param instanceOrClass the instance or class to set the field
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
+    *            <code>fieldName</code> can be found; or if the field was final
+    * 
+    * @see PrivilegedAccessor.setValue(Object,String,Object)
+    */
+   public static PA setValue(final Object instanceOrClass, final String fieldName, final Object value) {
+      try {
+         PrivilegedAccessor.setValue(instanceOrClass, fieldName, value);
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't set value " + value + " at " + fieldName + " in " + instanceOrClass, e);
+      }
+      return new PA(instanceOrClass);
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
+    *            <code>fieldName</code> can be found; or if the field was final
+    * 
+    * @see PA.setValue(Object,String,Object)
+    */
+   public PA setValue(final String fieldName, final Object value) {
+      PA.setValue(instanceOrClass, fieldName, value);
+      return this;
+   }
+}
diff --git a/test/junit/extensions/PrivilegedAccessor.java b/test/junit/extensions/PrivilegedAccessor.java
new file mode 100644 (file)
index 0000000..23f1c6e
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package junit.extensions;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax
+ * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
+ * <p>
+ * a.k.a. The "ObjectMolester"
+ * <p>
+ * Here is an example of using this to access a private member: <br>
+ * <code>myObject</code> is an object of type <code>MyClass</code>. <code>setName(String)</code> is a private method of
+ * <code>MyClass</code>.
+ * 
+ * <pre>
+ * PrivilegedAccessor.invokeMethod(myObject, &quot;setName(java.lang.String)&quot;, &quot;newName&quot;);
+ * </pre>
+ * 
+ * @author Charlie Hubbard (chubbard@iss.net)
+ * @author Prashant Dhokte (pdhokte@iss.net)
+ * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
+ * 
+ * @deprecated use PA instead. PA improves the functionality of PrivilegedAccessor by introducing support for varargs and removal of
+ *             the necessity to catch exceptions.
+ */
+@Deprecated
+final class PrivilegedAccessor
+{
+   /**
+    * Private constructor to make it impossible to instantiate this class.
+    */
+   private PrivilegedAccessor() {
+      assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
+   }
+
+   /**
+    * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
+    * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
+    * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
+    * 
+    * @param instanceOrClass the object or class to get a string representation of
+    * @return a string representation of the given object
+    */
+   public static String toString(final Object instanceOrClass) {
+      Collection<String> fields = getFieldNames(instanceOrClass);
+
+      if (fields.isEmpty())
+      {
+        return getClass(instanceOrClass).getName();
+      }
+
+      StringBuffer stringBuffer = new StringBuffer();
+
+      stringBuffer.append(getClass(instanceOrClass).getName() + " {");
+
+      for (String fieldName : fields) {
+         try {
+            stringBuffer.append(fieldName + "=" + getValue(instanceOrClass, fieldName) + ", ");
+         } catch (NoSuchFieldException e) {
+            assert false : "It should always be possible to get a field that was just here";
+         }
+      }
+
+      stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}");
+      return stringBuffer.toString();
+   }
+
+   /**
+    * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
+    * fields (public, private, protected, default) of all its super classes.
+    * 
+    * @param instanceOrClass the instance or class to get the fields of
+    * @return the collection of field names of the given instance or class
+    */
+   public static Collection<String> getFieldNames(final Object instanceOrClass) {
+      if (instanceOrClass == null)
+      {
+        return Collections.EMPTY_LIST;
+      }
+
+      Class<?> clazz = getClass(instanceOrClass);
+      Field[] fields = clazz.getDeclaredFields();
+      Collection<String> fieldNames = new ArrayList<String>(fields.length);
+
+      for (Field field : fields) {
+         fieldNames.add(field.getName());
+      }
+      fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
+
+      return fieldNames;
+   }
+
+   /**
+    * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
+    * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
+    * 
+    * @param instanceOrClass the instance or class to get the method signatures of
+    * @return the collection of method signatures of the given instance or class
+    */
+   public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
+      if (instanceOrClass == null)
+      {
+        return Collections.EMPTY_LIST;
+      }
+
+      Class<?> clazz = getClass(instanceOrClass);
+      Method[] methods = clazz.getDeclaredMethods();
+      Collection<String> methodSignatures = new ArrayList<String>(methods.length + Object.class.getDeclaredMethods().length);
+
+      for (Method method : methods) {
+         methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")");
+      }
+      methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
+
+      return methodSignatures;
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws NoSuchFieldException if the field does not exist
+    */
+   public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException {
+      Field field = getField(instanceOrClass, fieldName);
+      try {
+         return field.get(instanceOrClass);
+      } catch (IllegalAccessException e) {
+         assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place";
+         return null;
+      }
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
+    * the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param args the arguments to pass to the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
+    *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
+    *            parameter type by a method invocation conversion.
+    * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
+    *            inaccessible.
+    * @throws InvocationTargetException if the underlying constructor throws an exception.
+    * @throws NoSuchMethodException if the constructor could not be found
+    * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Object[] args) throws IllegalArgumentException,
+      InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+      return instantiate(fromClass, getParameterTypes(args), args);
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
+    * member class, you must provide the object it is a member of as first argument.
+    * 
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param args the arguments to pass to the constructor
+    * @param argumentTypes the fully qualified types of the arguments of the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
+    *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
+    *            parameter type by a method invocation conversion.
+    * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
+    *            inaccessible.
+    * @throws InvocationTargetException if the underlying constructor throws an exception.
+    * @throws NoSuchMethodException if the constructor could not be found
+    * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object[] args)
+      throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
+      NoSuchMethodException {
+      return getConstructor(fromClass, argumentTypes).newInstance(args);
+   }
+
+   /**
+    * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
+    * primitives.
+    * 
+    * @param instanceOrClass the instance or class to invoke the method on
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalAccessException if the method is inaccessible
+    * @throws InvocationTargetException if the underlying method throws an exception.
+    * @throws NoSuchMethodException if no method with the given <code>methodSignature</code> could be found
+    * @throws IllegalArgumentException if an argument couldn't be converted to match the expected type
+    */
+   public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object[] arguments)
+      throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+      if ((methodSignature.indexOf('(') == -1) || (methodSignature.indexOf('(') >= methodSignature.indexOf(')')))
+      {
+        throw new NoSuchMethodException(methodSignature);
+      }
+      Class<?>[] parameterTypes = getParameterTypes(methodSignature);
+      return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass,
+         getCorrectedArguments(parameterTypes, arguments));
+   }
+
+   /**
+    * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be
+    * mistaken by varargs.
+    * 
+    * @param parameterTypes the method signatue the given arguments should match
+    * @param arguments the arguments that should be corrected
+    * @return the corrected arguments
+    */
+   private static Object[] getCorrectedArguments(Class<?>[] parameterTypes, Object[] arguments) {
+      if (arguments == null)
+      {
+        return arguments;
+      }
+      if (parameterTypes.length > arguments.length)
+      {
+        return arguments;
+      }
+      if (parameterTypes.length < arguments.length)
+      {
+        return getCorrectedArguments(parameterTypes, new Object[] {arguments});
+      }
+
+      Object[] correctedArguments = new Object[arguments.length];
+      int currentArgument = 0;
+      for (Class<?> parameterType : parameterTypes) {
+         correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]);
+         currentArgument++;
+      }
+      return correctedArguments;
+   }
+
+   /**
+    * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be
+    * mistaken by varargs.
+    * 
+    * @param parameterType the type to match the given argument upon
+    * @param argument the argument to match the given parameterType
+    * @return the corrected argument
+    */
+   private static Object getCorrectedArgument(Class<?> parameterType, Object argument) {
+      if (!parameterType.isArray() || (argument == null)) {
+         return argument; // normal argument for normal parameterType
+      }
+
+      if (!argument.getClass().isArray()) {
+         return new Object[] {argument};
+      }
+
+      if (parameterType.equals(argument.getClass()))
+       {
+        return argument; // no need to cast
+      }
+
+      // (typed) array argument for (object) array parameterType, elements need to be casted
+      Object correctedArrayArgument = Array.newInstance(parameterType.getComponentType(), Array.getLength(argument));
+      for (int index = 0; index < Array.getLength(argument); index++) {
+         if (parameterType.getComponentType().isPrimitive()) { // rely on autoboxing
+            Array.set(correctedArrayArgument, index, Array.get(argument, index));
+         } else { // cast to expected type
+            try {
+               Array.set(correctedArrayArgument, index, parameterType.getComponentType().cast(Array.get(argument, index)));
+            } catch (ClassCastException e) {
+               throw new IllegalArgumentException("Argument " + argument + " of type " + argument.getClass()
+                  + " does not match expected argument type " + parameterType + ".");
+            }
+         }
+      }
+      return correctedArrayArgument;
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param instanceOrClass the instance or class to set the field
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws NoSuchFieldException if no field with the given <code>fieldName</code> can be found
+    * @throws IllegalAccessException possibly if the field was final
+    */
+   public static void setValue(final Object instanceOrClass, final String fieldName, final Object value) throws NoSuchFieldException,
+      IllegalAccessException {
+      Field field = getField(instanceOrClass, fieldName);
+      if (Modifier.isFinal(field.getModifiers())) {
+         PrivilegedAccessor.setValue(field, "modifiers", field.getModifiers() ^ Modifier.FINAL);
+      }
+      field.set(instanceOrClass, value);
+   }
+
+   /**
+    * Gets the class with the given className.
+    * 
+    * @param className the name of the class to get
+    * @return the class for the given className
+    * @throws ClassNotFoundException if the class could not be found
+    */
+   private static Class<?> getClassForName(final String className) throws ClassNotFoundException {
+      if (className.indexOf('[') > -1) {
+         Class<?> clazz = getClassForName(className.substring(0, className.indexOf('[')));
+         return Array.newInstance(clazz, 0).getClass();
+      }
+
+      if (className.indexOf("...") > -1) {
+         Class<?> clazz = getClassForName(className.substring(0, className.indexOf("...")));
+         return Array.newInstance(clazz, 0).getClass();
+      }
+
+      try {
+         return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+         return getSpecialClassForName(className);
+      }
+   }
+
+   /**
+    * Maps string representation of primitives to their corresponding classes.
+    */
+   private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(8);
+
+   /**
+    * Fills the map with all java primitives and their corresponding classes.
+    */
+   static {
+      PRIMITIVE_MAPPER.put("int", Integer.TYPE);
+      PRIMITIVE_MAPPER.put("float", Float.TYPE);
+      PRIMITIVE_MAPPER.put("double", Double.TYPE);
+      PRIMITIVE_MAPPER.put("short", Short.TYPE);
+      PRIMITIVE_MAPPER.put("long", Long.TYPE);
+      PRIMITIVE_MAPPER.put("byte", Byte.TYPE);
+      PRIMITIVE_MAPPER.put("char", Character.TYPE);
+      PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE);
+   }
+
+   /**
+    * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String)
+    * 
+    * @param className the name of the class to get
+    * @return the class for the given className
+    * @throws ClassNotFoundException if the class could not be found
+    */
+   private static Class<?> getSpecialClassForName(final String className) throws ClassNotFoundException {
+      if (PRIMITIVE_MAPPER.containsKey(className))
+      {
+        return PRIMITIVE_MAPPER.get(className);
+      }
+
+      if (missesPackageName(className))
+      {
+        return getStandardClassForName(className);
+      }
+
+      throw new ClassNotFoundException(className);
+   }
+
+   /**
+    * Gets a 'standard' java class for the given className.
+    * 
+    * @param className the className
+    * @return the class for the given className (if any)
+    * @throws ClassNotFoundException of no 'standard' java class was found for the given className
+    */
+   private static Class<?> getStandardClassForName(String className) throws ClassNotFoundException {
+      try {
+         return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+         try {
+            return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader());
+         } catch (ClassNotFoundException e1) {
+            throw new ClassNotFoundException(className);
+         }
+      }
+   }
+
+   /**
+    * Tests if the given className possibly misses its package name.
+    * 
+    * @param className the className
+    * @return true if the className might miss its package name, otherwise false
+    */
+   private static boolean missesPackageName(String className) {
+      if (className.contains("."))
+      {
+        return false;
+      }
+      if (className.startsWith(className.substring(0, 1).toUpperCase()))
+      {
+        return true;
+      }
+      return false;
+   }
+
+   /**
+    * Gets the constructor for a given class with the given parameters.
+    * 
+    * @param type the class to instantiate
+    * @param parameterTypes the types of the parameters
+    * @return the constructor
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static <T> Constructor<T> getConstructor(final Class<T> type, final Class<?>[] parameterTypes) throws NoSuchMethodException {
+      Constructor<T> constructor = type.getDeclaredConstructor(parameterTypes);
+      constructor.setAccessible(true);
+      return constructor;
+   }
+
+   /**
+    * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field to get
+    * @return the field
+    * @throws NoSuchFieldException if no such field can be found
+    * @throws InvalidParameterException if instanceOrClass was null
+    */
+   private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException,
+      InvalidParameterException {
+      if (instanceOrClass == null)
+      {
+        throw new InvalidParameterException("Can't get field on null object/class");
+      }
+
+      Class<?> type = getClass(instanceOrClass);
+
+      try {
+         Field field = type.getDeclaredField(fieldName);
+         field.setAccessible(true);
+         return field;
+      } catch (NoSuchFieldException e) {
+         if (type.getSuperclass() == null)
+        {
+          throw e;
+        }
+         return getField(type.getSuperclass(), fieldName);
+      }
+   }
+
+   /**
+    * Gets the class of the given parameter. If the parameter is a class, it is returned, if it is an object, its class is returned
+    * 
+    * @param instanceOrClass the instance or class to get the class of
+    * @return the class of the given parameter
+    */
+   private static Class<?> getClass(final Object instanceOrClass) {
+      if (instanceOrClass instanceof Class)
+      {
+        return (Class<?>) instanceOrClass;
+      }
+
+      return instanceOrClass.getClass();
+   }
+
+   /**
+    * Return the named method with a method signature matching classTypes from the given class.
+    * 
+    * @param type the class to get the method from
+    * @param methodName the name of the method to get
+    * @param parameterTypes the parameter-types of the method to get
+    * @return the method
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static Method getMethod(final Class<?> type, final String methodName, final Class<?>[] parameterTypes)
+      throws NoSuchMethodException {
+      try {
+         return type.getDeclaredMethod(methodName, parameterTypes);
+      } catch (NoSuchMethodException e) {
+         if (type.getSuperclass() == null)
+        {
+          throw e;
+        }
+         return getMethod(type.getSuperclass(), methodName, parameterTypes);
+      }
+   }
+
+   /**
+    * Gets the method with the given name and parameters from the given instance or class. If instanceOrClass is a class, then we get a
+    * static method.
+    * 
+    * @param instanceOrClass the instance or class to get the method of
+    * @param methodName the name of the method
+    * @param parameterTypes the parameter-types of the method to get
+    * @return the method
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static Method getMethod(final Object instanceOrClass, final String methodName, final Class<?>[] parameterTypes)
+      throws NoSuchMethodException {
+      Class<?> type;
+
+      type = getClass(instanceOrClass);
+
+      Method accessMethod = getMethod(type, methodName, parameterTypes);
+      accessMethod.setAccessible(true);
+      return accessMethod;
+   }
+
+   /**
+    * Gets the name of a method.
+    * 
+    * @param methodSignature the signature of the method
+    * @return the name of the method
+    */
+   private static String getMethodName(final String methodSignature) {
+      try {
+         return methodSignature.substring(0, methodSignature.indexOf('(')).trim();
+      } catch (StringIndexOutOfBoundsException e) {
+         assert false : "Signature must have been checked before this method was called";
+         return null;
+      }
+   }
+
+   /**
+    * Gets the types of the parameters.
+    * 
+    * @param parameters the parameters
+    * @return the class-types of the arguments
+    */
+   private static Class<?>[] getParameterTypes(final Object[] parameters) {
+      if (parameters == null)
+      {
+        return new Class[0];
+      }
+
+      Class<?>[] typesOfParameters = new Class[parameters.length];
+
+      for (int i = 0; i < parameters.length; i++) {
+         typesOfParameters[i] = parameters[i].getClass();
+      }
+      return typesOfParameters;
+   }
+
+   /**
+    * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is
+    * thrown.
+    * 
+    * @param methodSignature the signature of the method
+    * @return the parameter types as class[]
+    * @throws NoSuchMethodException if the method could not be found
+    * @throws IllegalArgumentException if one of the given parameters doesn't math the given methodSignature
+    */
+   private static Class<?>[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException {
+      String signature = getSignatureWithoutBraces(methodSignature);
+
+      StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
+      Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
+
+      for (int x = 0; tokenizer.hasMoreTokens(); x++) {
+         String className = tokenizer.nextToken();
+         try {
+            typesInSignature[x] = getClassForName(className);
+         } catch (ClassNotFoundException e) {
+            NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature);
+            noSuchMethodException.initCause(e);
+            throw noSuchMethodException;
+         }
+      }
+      return typesInSignature;
+   }
+
+   /**
+    * Gets the parameter types as a string.
+    * 
+    * @param classTypes the types to get as names.
+    * @return the parameter types as a string
+    * 
+    * @see java.lang.Class#argumentTypesToString(Class[])
+    */
+   private static String getParameterTypesAsString(final Class<?>[] classTypes) {
+      assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
+      if (classTypes.length == 0)
+      {
+        return "";
+      }
+
+      StringBuilder parameterTypes = new StringBuilder();
+      for (Class<?> clazz : classTypes) {
+         assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
+         parameterTypes.append(clazz.getName()).append(", ");
+      }
+
+      return parameterTypes.substring(0, parameterTypes.length() - 2);
+   }
+
+   /**
+    * Removes the braces around the methods signature.
+    * 
+    * @param methodSignature the signature with braces
+    * @return the signature without braces
+    */
+   private static String getSignatureWithoutBraces(final String methodSignature) {
+      try {
+         return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')'));
+      } catch (IndexOutOfBoundsException e) {
+         assert false : "signature must have been checked before this method";
+         return null;
+      }
+   }
+
+}