JAL-2069 spike updated with latest (FeatureTypeSettings)
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 22 Nov 2017 15:36:06 +0000 (15:36 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 22 Nov 2017 15:36:06 +0000 (15:36 +0000)
62 files changed:
help/helpTOC.xml
help/html/calculations/sorting.html
help/html/features/viewingpdbs.html
help/html/releases.html
help/html/whatsNew.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/FeatureColourI.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/ContiguousI.java
src/jalview/datamodel/Range.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceCursor.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/features/FeatureAttributes.java
src/jalview/datamodel/features/FeatureLocationI.java
src/jalview/datamodel/features/FeatureStore.java
src/jalview/datamodel/features/NCList.java
src/jalview/datamodel/features/NCNode.java
src/jalview/datamodel/features/RangeComparator.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/datamodel/features/SequenceFeaturesI.java
src/jalview/datamodel/xdb/uniprot/UniprotFeature.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/FeatureColourChooser.java [deleted file]
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java [new file with mode: 0644]
src/jalview/gui/IdCanvas.java
src/jalview/gui/JalviewDialog.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceRenderer.java
src/jalview/gui/StructureChooser.java
src/jalview/schemes/FeatureColour.java
src/jalview/util/IntRangeComparator.java
src/jalview/viewmodel/OverviewDimensionsHideHidden.java
src/jalview/viewmodel/OverviewDimensionsShowHidden.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/dbsources/Uniprot.java
test/jalview/datamodel/SequenceTest.java
test/jalview/datamodel/features/FeatureAttributesTest.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java
test/jalview/schemes/FeatureColourTest.java
test/jalview/viewmodel/ViewportRangesTest.java
test/jalview/ws/dbsources/UniprotTest.java
utils/InstallAnywhere/Jalview.iap_xml
utils/MessageBundleChecker.java

index 7ba4ee5..20dd8db 100755 (executable)
@@ -24,7 +24,6 @@
        <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>
                
                <tocitem text="Editing Alignments" target="edit" />
index aeb461a..a0412d0 100755 (executable)
@@ -46,8 +46,7 @@
     <li><p>
         <strong>Sort by Pairwise Identity</strong>
       </p>
-      <p>Places pairs of sequences together that align with the
-        greatest fraction of conserved residues.</p>
+      <p>Sorts sequences in the selection or alignment according to percent identity with respect to the first sequence in the view.</p>
       <p></li>
     <li><p>
         <strong>Sort by Tree Order</strong>
index 0fcbbf9..45d979f 100755 (executable)
@@ -56,7 +56,7 @@
         <li><strong>Viewing Cached Structures</strong><br />If
           previously downloaded structures are available for your
           sequences, the structure chooser will automatically offer them
-          via the <strong>Cached PDB Entries</strong> view. If you wish
+          via the <strong>Cached Structures</strong> view. If you wish
           to download new structures, select one of the PDBe selection
           criteria from the drop-down menu.</li>
       </ul></li>
index 6396313..1a48340 100755 (executable)
@@ -70,8 +70,7 @@ li:before {
     <tr>
       <td width="60" nowrap>
         <div align="center">
-          <strong><a name="Jalview.2.10.3">2.10.3</a><br />
-            <em>14/11/2017</em></strong>
+          <strong><a name="Jalview.2.10.3">2.10.3</a><br /> <em>17/11/2017</em></strong>
         </div>
       </td>
       <td><div align="left">
@@ -89,83 +88,227 @@ li:before {
               <!-- JAL-2773 -->Structure views don't get updated unless
               their colours have changed
             </li>
-            <li><!-- JAL-2495 -->All linked sequences are highlighted for a structure mousover (Jmol) or selection (Chimera)</li>
-            <li><!-- JAL-2790 -->'Cancel' button in progress bar for JABAWS AACon, RNAAliFold and Disorder prediction jobs
+            <li>
+              <!-- JAL-2495 -->All linked sequences are highlighted for
+              a structure mousover (Jmol) or selection (Chimera)
+            </li>
+            <li>
+              <!-- JAL-2790 -->'Cancel' button in progress bar for
+              JABAWS AACon, RNAAliFold and Disorder prediction jobs
+            </li>
+            <li>
+              <!-- JAL-2617 -->Stop codons are excluded in CDS/Protein
+              view from Ensembl locus cross-references
+            </li>
+            <li>
+              <!-- JAL-2685 -->Start/End limits are shown in Pairwise
+              Alignment report
+            </li>
+            <li>
+              <!-- JAL-2810 -->Sequence fetcher's Free text 'autosearch'
+              feature can be disabled
+            </li>
+            <li>
+              <!-- JAL-2810 -->Retrieve IDs tab added for UniProt and
+              PDB easier retrieval of sequences for lists of IDs
+            </li>
+            <li>
+              <!-- JAL-2758 -->Short names for sequences retrieved from
+              Uniprot
             </li>
-            
-            <li><!-- JAL-2617 -->Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references</li>
-            <li><!-- JAL-2685 -->Start/End limits are shown in Pairwise Alignment report</li>
-            <li><!-- JAL-2810 -->Sequence fetcher's Free text 'autosearch' feature can be disabled</li>
-            <li><!-- JAL-2810 -->Retrieve IDs tab added for UniProt and PDB easier retrieval of sequences for lists of IDs</li>
-             
           </ul>
           <em>Scripting</em>
           <ul>
-          <li>Groovy interpreter updated to 2.4.12</li>
-          <li>Example groovy script for generating a matrix of percent identity scores for current alignment.</li>
+            <li>Groovy interpreter updated to 2.4.12</li>
+            <li>Example groovy script for generating a matrix of
+              percent identity scores for current alignment.</li>
           </ul>
           <em>Testing and Deployment</em>
-          <ul><li><!-- JAL-2727 -->Test to catch memory leaks in Jalview UI</li></ul>
-          </div>
-      </td>
+          <ul>
+            <li>
+              <!-- JAL-2727 -->Test to catch memory leaks in Jalview UI
+            </li>
+          </ul>
+        </div></td>
       <td><div align="left">
           <em>General</em>
           <ul>
-            <li><!-- JAL-2643 -->Pressing tab after updating the colour threshold text field doesn't trigger an update to the alignment view</li>
-            <li><!-- JAL-2682 -->Race condition when parsing sequence ID strings in parallel</li>
-            <li><!-- JAL-2608 -->Overview windows are also closed when alignment window is closed</li>
-            <li><!-- JAL-2548 -->Export of features doesn't always respect group visibility</li> 
+            <li>
+              <!-- JAL-2643 -->Pressing tab after updating the colour
+              threshold text field doesn't trigger an update to the
+              alignment view
+            </li>
+            <li>
+              <!-- JAL-2682 -->Race condition when parsing sequence ID
+              strings in parallel
+            </li>
+            <li>
+              <!-- JAL-2608 -->Overview windows are also closed when
+              alignment window is closed
+            </li>
+            <li>
+              <!-- JAL-2548 -->Export of features doesn't always respect
+              group visibility
+            </li>
+            <li>
+              <!-- JAL-2831 -->Jumping from column 1 to column 100,000
+              takes a long time in Cursor mode
+            </li>
           </ul>
           <em>Desktop</em>
           <ul>
-            <li><!-- JAL-2777 -->Structures with whitespace chainCode cannot be viewed in Chimera</li>
-            <li><!-- JAL-2728 -->Protein annotation panel too high in CDS/Protein view
-            </li> 
-            <li><!-- JAL-2757 -->Can't edit the query after the server error warning icon is shown in Uniprot and PDB Free Text Search Dialogs
-            </li> 
-            <li><!-- JAL-2253 -->Slow EnsemblGenome ID lookup</li>
-            <li><!-- JAL-2529 -->Revised Ensembl REST API CDNA query</li>
-            <li><!-- JAL-2739 -->Hidden column marker in last column not rendered when switching back from Wrapped to normal view</li> 
-            <li><!-- JAL-2768 -->Annotation display corrupted when scrolling right in unwapped alignment view</li> 
-            <li><!-- JAL-2542 -->Existing features on subsequence incorrectly relocated when full sequence retrieved from database</li> 
-            <li><!-- JAL-2733 -->Last reported memory still shown when Desktop->Show Memory is unticked (OSX only)</li>
-            <li><!-- JAL-2658 -->Amend Features dialog doesn't allow features of same type and group to be selected for amending</li>
-            <li><!-- JAL-2524 -->Jalview becomes sluggish in wide alignments when hidden columns are present</li>
-            <li><!-- JAL-2392 -->Jalview freezes when loading and displaying several structures</li>
-            <li><!-- JAL-2732 -->Black outlines left after resizing or moving a window</li>
-            <li><!-- JAL-1900,JAL-1625 -->Unable to minimise windows within the Jalview desktop on OSX</li>
-            <li><!-- JAL-2667 -->Mouse wheel doesn't scroll vertically when in wrapped alignment mode</li>
-            <li><!-- JAL-2636 -->Scale mark not shown when close to right hand end of alignment</li>
-            <li><!-- JAL-2684 -->Pairwise alignment only aligns selected regions of each selected sequence</li>
-            <li><!-- JAL-2973 -->Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog</li>
-            <li><!-- JAL-2036 -->Show cross-references not enabled after restoring project until a new view is created</li>
-            <li><!-- JAL-2756 -->Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)</li>            
-            <li><!-- JAL-2775 -->Overview redraws whole window when box position is adjusted</li>
-            <li><!-- JAL-2225 -->Structure viewer doesn't map all chains in a multi-chain structure when viewing alignment involving more than one chain (since 2.10)</li>            
-           </ul>
-          <strong><em>Applet</em></strong><br/>
-           <ul>
-            <li><!-- JAL-2687 -->Concurrent modification exception when closing alignment panel</li> 
+            <li>
+              <!-- JAL-2777 -->Structures with whitespace chainCode
+              cannot be viewed in Chimera
+            </li>
+            <li>
+              <!-- JAL-2728 -->Protein annotation panel too high in
+              CDS/Protein view
+            </li>
+            <li>
+              <!-- JAL-2757 -->Can't edit the query after the server
+              error warning icon is shown in Uniprot and PDB Free Text
+              Search Dialogs
+            </li>
+            <li>
+              <!-- JAL-2253 -->Slow EnsemblGenome ID lookup
+            </li>
+            <li>
+              <!-- JAL-2529 -->Revised Ensembl REST API CDNA query
+            </li>
+            <li>
+              <!-- JAL-2739 -->Hidden column marker in last column not
+              rendered when switching back from Wrapped to normal view
+            </li>
+            <li>
+              <!-- JAL-2768 -->Annotation display corrupted when
+              scrolling right in unwapped alignment view
+            </li>
+            <li>
+              <!-- JAL-2542 -->Existing features on subsequence
+              incorrectly relocated when full sequence retrieved from
+              database
+            </li>
+            <li>
+              <!-- JAL-2733 -->Last reported memory still shown when
+              Desktop->Show Memory is unticked (OSX only)
+            </li>
+            <li>
+              <!-- JAL-2658 -->Amend Features dialog doesn't allow
+              features of same type and group to be selected for
+              amending
+            </li>
+            <li>
+              <!-- JAL-2524 -->Jalview becomes sluggish in wide
+              alignments when hidden columns are present
+            </li>
+            <li>
+              <!-- JAL-2392 -->Jalview freezes when loading and
+              displaying several structures
+            </li>
+            <li>
+              <!-- JAL-2732 -->Black outlines left after resizing or
+              moving a window
+            </li>
+            <li>
+              <!-- JAL-1900,JAL-1625 -->Unable to minimise windows
+              within the Jalview desktop on OSX
+            </li>
+            <li>
+              <!-- JAL-2667 -->Mouse wheel doesn't scroll vertically
+              when in wrapped alignment mode
+            </li>
+            <li>
+              <!-- JAL-2636 -->Scale mark not shown when close to right
+              hand end of alignment
+            </li>
+            <li>
+              <!-- JAL-2684 -->Pairwise alignment of selected regions of
+              each selected sequence do not have correct start/end
+              positions
+            </li>
+            <li>
+              <!-- JAL-2793 -->Alignment ruler height set incorrectly
+              after canceling the Alignment Window's Font dialog
+            </li>
+            <li>
+              <!-- JAL-2036 -->Show cross-references not enabled after
+              restoring project until a new view is created
+            </li>
+            <li>
+              <!-- JAL-2756 -->Warning popup about use of SEQUENCE_ID in
+              URL links appears when only default EMBL-EBI link is
+              configured (since 2.10.2b2)
+            </li>
+            <li>
+              <!-- JAL-2775 -->Overview redraws whole window when box
+              position is adjusted
+            </li>
+            <li>
+              <!-- JAL-2225 -->Structure viewer doesn't map all chains
+              in a multi-chain structure when viewing alignment
+              involving more than one chain (since 2.10)
+            </li>
+            <li>
+              <!-- JAL-2811 -->Double residue highlights in cursor mode
+              if new selection moves alignment window
+            </li>
+            <li>
+              <!-- JAL-2837,JAL-2840 -->Alignment vanishes when using
+              arrow key in cursor mode to pass hidden column marker
+            </li>
+            <li>
+              <!-- JAL-2679 -->Ensembl Genomes example ID changed to one
+              that produces correctly annotated transcripts and products
+            </li>
+            <li>
+              <!-- JAL-2776 -->Toggling a feature group after first time
+              doesn't update associated structure view
+            </li>
           </ul>
-          <strong><em>BioJSON</em></strong><br/>
+          <em>Applet</em><br />
           <ul>
-          <li>
-            <!-- JAL-2546 -->BioJSON export does not preserve non-positional features
-          </li>
+            <li>
+              <!-- JAL-2687 -->Concurrent modification exception when
+              closing alignment panel
+            </li>
           </ul>
-          <strong>Known Java 9 Issues</strong>
+          <em>BioJSON</em><br />
           <ul>
-            <li><!-- JAL-2902 -->Groovy Console very slow to open and is 
-            not responsive when entering characters (Webstart, Java 9.01, 
-            OSX 10.10)
+            <li>
+              <!-- JAL-2546 -->BioJSON export does not preserve
+              non-positional features
             </li>
           </ul>
-          <strong>New Known Issues</strong>
+          <em>New Known Issues</em>
           <ul>
-          <li><!-- JAL- --></li>
+            <li>
+              <!-- JAL-2541 -->Delete/Cut selection doesn't relocate
+              sequence features correctly (for many previous versions of
+              Jalview)
+            </li>
+            <li>
+              <!-- JAL-2841 -->Cursor mode unexpectedly scrolls when
+              using cursor in wrapped panel other than top
+            </li>
+            <li>
+              <!-- JAL-2791 -->Select columns containing feature ignores
+              graduated colour threshold
+            </li>
+            <li>
+              <!-- JAL-2822,JAL-2823 -->Edit sequence operation doesn't
+              always preserve numbering and sequence features
+            </li>
           </ul>
-          </div>
-      </td>
+          <em>Known Java 9 Issues</em>
+          <ul>
+            <li>
+              <!-- JAL-2902 -->Groovy Console very slow to open and is
+              not responsive when entering characters (Webstart, Java
+              9.01, OSX 10.10)
+            </li>
+          </ul>
+        </div></td>
     </tr>
     <tr>
       <td width="60" nowrap>
index 9cf1044..4bf1cec 100755 (executable)
     <strong>What's new in Jalview 2.10.3 ?</strong>
   </p>
   <p>
-    Version 2.10.3 was released in November 2017. The full list of
-    bug fixes and new features can be found in the <a
-      href="releases.html#Jalview.2.10.3"> 2.10.3 Release Notes</a>, but
-    the highlights are below.
+    Version 2.10.3 was released in November 2017. The major focus was to
+    improve Jalview's sequence features datamodel and the scalability of
+    the alignment rendering system. The full list of bug fixes and new
+    features can be found in the <a href="releases.html#Jalview.2.10.3">2.10.3
+      Release Notes</a>. Key improvements include:
   </p>
   <ul>
-    <li>Faster import and more responsive UI when working with wide alignments and handling hundreds and thousands of sequence features</li>
-    <li> 
-    <li>Improved usability with <a href="features/pdbsequencefetcher.html">PDB</a> and 
-    <a href="features/uniprotsequencefetcher.html">UniProt</a> Free Text Search
-      dialog, and new tab for retrieval of sequences for lists of IDs.</li>
+    <li>Faster and more responsive UI when importing and working
+      with wide alignments and handling hundreds and thousands of
+      sequence features</li>
+    <li>Improved usability with <a
+      href="features/pdbsequencefetcher.html">PDB</a> and <a
+      href="features/uniprotsequencefetcher.html">UniProt</a> Free Text
+      Search dialog, and new tab for retrieval of sequences for lists of
+      IDs.
+    </li>
+    <li>Short names assigned to sequences retrieved from UniProt</li>
+    <li>Groovy console upgraded to 2.4.12 (improved support for Java 9)</li>
   </ul>
   <p>
     <strong><a name="experimental">Experimental Features</a></strong>
   </p>
   <p>
-    This release of Jalview introduces an <em>Experimental Features</em>
-    option in the Jalview Desktop's <em>Tools</em> menu that allows you
-    to try out features that are still in development. To access the
-    experimental features below - first enable the <strong>Tools&#8594;Enable
-      Experimental Features</strong> option, and then restart Jalview.
+    Remember, please enable the <em>Experimental Features</em> option in
+    the Jalview Desktop's <em>Tools</em> menu, and then restart Jalview
+    if you want to try out features below:
   </p>
   <ul>
     <li><em>Annotation transfer between Chimera and Jalview</em><br />Two
@@ -55,6 +60,6 @@
         the Chimera viewer's Chimera menu</a> allow positional annotation to
       be exchanged between Chimera and Jalview.</li>
   </ul>
-
+  
 </body>
 </html>
index 9f1c71b..a592282 100644 (file)
@@ -242,7 +242,6 @@ label.documentation = Documentation
 label.about = About...
 label.show_sequence_limits = Show Sequence Limits
 action.feature_settings = Feature Settings...
-label.feature_settings = Feature Settings
 label.all_columns = All Columns
 label.all_sequences = All Sequences
 label.selected_columns = Selected Columns 
@@ -282,9 +281,9 @@ label.selection = Selection
 label.group_colour = Group Colour
 label.sequence = Sequence
 label.view_pdb_structure = View PDB Structure
-label.min_value = Min value:
-label.max_value = Max value:
-label.no_value = No value:
+label.min_value = Min value
+label.max_value = Max value
+label.no_value = No value
 label.new_feature = New Feature
 label.match_case = Match Case
 label.view_alignment_editor = View in alignment editor
@@ -533,7 +532,6 @@ label.threshold_feature_above_threshold = Above Threshold
 label.threshold_feature_below_threshold = Below Threshold
 label.adjust_threshold = Adjust threshold
 label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold.
-label.colour_by_label_tip = Display features of the same type with a different label using a different colour. (e.g. domain features)
 label.select_colour_minimum_value = Select Colour for Minimum Value
 label.select_colour_maximum_value = Select Colour for Maximum Value
 label.open_url_param = Open URL {0}
@@ -871,7 +869,7 @@ label.msa_service_is_unknown = The Multiple Sequence Alignment Service named {0}
 label.service_called_is_not_seq_search_service = The Service called \n{0}\nis not a \nSequence Search Service\!
 label.seq_search_service_is_unknown = The Sequence Search Service named {0} is unknown
 label.feature_type = Feature Type
-label.display = Display
+label.show = Show
 label.service_url = Service URL
 label.copied_sequences = Copied sequences
 label.cut_sequences = Cut Sequences
@@ -1336,20 +1334,23 @@ label.matchCondition_le = <=
 label.matchCondition_gt = >
 label.matchCondition_ge = >=
 label.numeric_required = The value should be numeric
-label.no_attributes = No attributes known
-label.no_numeric_attributes = No numeric attributes known
+label.filter = Filter
 label.filters = Filters
-label.match_condition = Match condition
 label.join_conditions = Join conditions with
-label.feature_to_filter = Feature to filter
-label.colour_by_value = Colour by value
-label.colour_by_text = Colour by text
 label.score = Score
-label.attribute = Attribute
 label.colour_by_label = Colour by label
 label.variable_colour = Variable colour
-label.select_new_colour = Select new colour
-label.no_feature_attributes = No feature attributes found
+label.select_colour = Select colour
 option.enable_disable_autosearch = When ticked, search is performed automatically.
 option.autosearch = Autosearch
 label.retrieve_ids = Retrieve IDs
+label.display_settings_for = Display settings for {0} features
+label.simple = Simple
+label.simple_colour = Simple Colour
+label.colour_by_text = Colour by text
+label.graduated_colour = Graduated Colour
+label.by_text_of = By text of
+label.by_range_of = By range of
+label.filters_tooltip = Click to set or amend filters
+label.or = Or
+label.and = And
\ No newline at end of file
index a7fff8e..31c3a86 100644 (file)
@@ -226,7 +226,6 @@ label.automatic_scrolling = Desplazamiento autom
 label.documentation = Documentación
 label.about = Acerca de...
 label.show_sequence_limits = Mostrar los límites de la secuencia
-label.feature_settings = Ajustar funciones...
 label.all_columns = Todas las columnas
 label.all_sequences = Todas las secuencias
 label.selected_columns = Columnas seleccionadas
@@ -243,6 +242,7 @@ label.apply_all_groups = Aplicar a todos los grupos
 label.autocalculated_annotation = Anotación autocalculada
 label.min_colour = Color mínimo
 label.max_colour = Color máximo
+label.no_colour = Sin color
 label.use_original_colours = Usar colores originales
 label.threshold_minmax = El umbral es mín/máx
 label.represent_group_with = Representar al grupo con
@@ -250,8 +250,9 @@ label.selection = Seleccionar
 label.group_colour = Color del grupo
 label.sequence = Secuencia
 label.view_pdb_structure = Ver estructura PDB
-label.min = Mín:
-label.max = Máx:
+label.max_value = Valor máximo
+label.min_value = Valor mínimo
+label.no_value = Sin valor
 label.colour_by_label = Color por etiquetas
 label.new_feature = Nueva función
 label.match_case = Hacer corresponder mayúsculas y minúsculas
@@ -456,6 +457,10 @@ label.settings_for_type = Ajustes para {0}
 label.view_full_application = Ver en la aplicación completa 
 label.load_associated_tree = Cargar Ã¡rbol asociado ...
 label.load_features_annotations = Cargar características/anotaciones ...
+label.load_vcf = Cargar variantes SNP desde fichero VCF texto o tab-indexado
+label.load_vcf_file = Cargar fichero VCF
+label.searching_vcf = Cargando variantes VCF...
+label.added_vcf= {0} variantes VCF añadidas a {1} secuencia(s)
 label.export_features = Exportar características...
 label.export_annotations = Exportar anotaciones ...
 label.to_upper_case = Pasar a mayúsculas
@@ -489,7 +494,6 @@ label.threshold_feature_above_threshold = Por encima del umbral
 label.threshold_feature_below_threshold = Por debajo del umbral
 label.adjust_threshold = Ajustar umbral
 label.toggle_absolute_relative_display_threshold = Cambiar entre mostrar el umbral absoluto y el relativo.
-label.display_features_same_type_different_label_using_different_colour = Mostrar las características del mismo tipo con una etiqueta diferente y empleando un color distinto (p.e. características del dominio)
 label.select_colour_minimum_value = Seleccionar el color para el valor mínimo
 label.select_colour_maximum_value = Seleccionar el color para el valor máximo
 label.open_url_param = Abrir URL {0}
@@ -791,7 +795,7 @@ label.msa_service_is_unknown = El Servicio de Alineamiento M
 label.service_called_is_not_seq_search_service = El Servicio llamando \n{0}\nno es un \nServicio de B\u00FAsqueda de Secuencias\!
 label.seq_search_service_is_unknown = El Servicio de Búsqueda de Sencuencias llamado {0} es desconocido
 label.feature_type = Tipo de característisca
-label.display = Representación
+label.show = Mostrar
 label.service_url = URL del servicio
 label.copied_sequences = Secuencias copiadas
 label.cut_sequences = Cortar secuencias
@@ -1319,3 +1323,35 @@ label.select_hidden_colour = Seleccionar color de las regiones ocultas
 label.overview = Resumen
 label.reset_to_defaults = Restablecen a los predeterminados
 label.oview_calc = Recalculando resumen
+label.feature_details = Detalles de característica 
+label.matchCondition_contains = Contiene
+label.matchCondition_notcontains = No contiene
+label.matchCondition_matches = Es igual a
+label.matchCondition_notmatches = No es igual a
+label.matchCondition_eq = =
+label.matchCondition_ne = not =
+label.matchCondition_lt = <
+label.matchCondition_le = <=
+label.matchCondition_gt = >
+label.matchCondition_ge = >=
+label.numeric_required = Valor numérico requerido
+label.filter = Filtro
+label.filters = Filtros
+label.join_conditions = Combinar condiciones con
+label.score = Puntuación
+label.colour_by_label = Colorear por texto
+label.variable_colour = Color variable
+label.select_colour = Seleccionar color
+option.enable_disable_autosearch = Marque para buscar automáticamente
+option.autosearch = Búsqueda automática
+label.retrieve_ids = Recuperar IDs
+label.display_settings_for = Visualización de características {0}
+label.simple = Simple
+label.simple_colour = Color simple
+label.colour_by_text = Colorear por texto
+label.graduated_colour = Color graduado
+label.by_text_of = Por texto de
+label.by_range_of = Por rango de
+label.filters_tooltip = Haga clic para configurar o modificar los filtros
+label.or = O
+label.and = Y
\ No newline at end of file
index 93773cc..0780271 100644 (file)
@@ -101,18 +101,6 @@ public interface FeatureColourI
   void setAboveThreshold(boolean b);
 
   /**
-   * Answers true if the threshold is the minimum value (when
-   * isAboveThreshold()) or maximum value (when isBelowThreshold()) of the
-   * colour range; only applicable when isGraduatedColour and either
-   * isAboveThreshold() or isBelowThreshold() answers true
-   * 
-   * @return
-   */
-  boolean isThresholdMinMax();
-
-  void setThresholdMinMax(boolean b);
-
-  /**
    * Returns the threshold value (if any), else zero
    * 
    * @return
index c06f7b1..50a9e33 100755 (executable)
@@ -778,5 +778,14 @@ public class AnnotationPanel extends Panel
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
+    else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      fastPaint(((int[]) evt.getNewValue())[0]
+              - ((int[]) evt.getOldValue())[0]);
+    }
+    else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      repaint();
+    }
   }
 }
index e9c377a..d9eae11 100644 (file)
@@ -58,6 +58,8 @@ public class FeatureColourChooser extends Panel implements ActionListener,
    */
   private static final int SCALE_FACTOR_1K = 1000;
 
+  private static final String COLON = ":";
+
   private JVDialog frame;
 
   private Frame owner;
@@ -198,8 +200,10 @@ public class FeatureColourChooser extends Panel implements ActionListener,
 
   private void jbInit() throws Exception
   {
-    Label minLabel = new Label(MessageManager.getString("label.min_value"));
-    Label maxLabel = new Label(MessageManager.getString("label.max_value"));
+    Label minLabel = new Label(
+            MessageManager.getString("label.min_value") + COLON);
+    Label maxLabel = new Label(
+            MessageManager.getString("label.max_value") + COLON);
     minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
     maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
     // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
index 9a67499..39a2747 100755 (executable)
  */
 package jalview.appletgui;
 
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN;
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN;
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN;
+
 import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
 import jalview.datamodel.AlignmentI;
@@ -584,18 +588,23 @@ public class FeatureSettings extends Panel
     Component[] comps = featurePanel.getComponents();
     int cSize = comps.length;
 
-    Object[][] tmp = new Object[cSize][3];
+    /*
+     * temporary! leave column[2] empty - used for Filter in
+     * gui.FeatureSettings
+     */
+    int columnCount = 4;
+    Object[][] tmp = new Object[cSize][columnCount];
     int tmpSize = 0;
     for (int i = 0; i < cSize; i++)
     {
       MyCheckbox check = (MyCheckbox) comps[i];
-      tmp[tmpSize][0] = check.type;
-      tmp[tmpSize][1] = fr.getFeatureStyle(check.type);
-      tmp[tmpSize][2] = new Boolean(check.getState());
+      tmp[tmpSize][TYPE_COLUMN /* 0 */] = check.type;
+      tmp[tmpSize][COLOUR_COLUMN /* 1 */] = fr.getFeatureStyle(check.type);
+      tmp[tmpSize][SHOW_COLUMN /* 3 */] = new Boolean(check.getState());
       tmpSize++;
     }
 
-    Object[][] data = new Object[tmpSize][3];
+    Object[][] data = new Object[tmpSize][columnCount];
     System.arraycopy(tmp, 0, data, 0, tmpSize);
 
     fr.setFeaturePriority(data);
index 5eddc4f..f5ea12e 100755 (executable)
@@ -448,5 +448,14 @@ public class IdCanvas extends Panel implements ViewportListenerI
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
+    else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      fastPaint(((int[]) evt.getNewValue())[1]
+              - ((int[]) evt.getOldValue())[1]);
+    }
+    else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      repaint();
+    }
   }
 }
index 7d4150d..04fb22b 100755 (executable)
@@ -468,7 +468,9 @@ public class ScalePanel extends Panel
     // Here we only want to fastpaint on a scroll, with resize using a normal
     // paint, so scroll events are identified as changes to the horizontal or
     // vertical start value.
-    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES)
+            || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)
+            || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
     {
       // scroll event, repaint panel
       repaint();
index f59967d..2420cf7 100755 (executable)
@@ -889,15 +889,37 @@ public class SeqCanvas extends Panel implements ViewportListenerI
   {
     String eventName = evt.getPropertyName();
 
+    if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
+    {
+      fastPaint = true;
+      repaint();
+      return;
+    }
+    else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      fastPaint = false;
+      repaint();
+      return;
+    }
+
     if (!av.getWrapAlignment())
     {
       int scrollX = 0;
-      if (eventName.equals(ViewportRanges.STARTRES))
+      if (eventName.equals(ViewportRanges.STARTRES)
+              || eventName.equals(ViewportRanges.STARTRESANDSEQ))
       {
         // Make sure we're not trying to draw a panel
         // larger than the visible window
+        if (eventName.equals(ViewportRanges.STARTRES))
+        {
+          scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+        }
+        else
+        {
+          scrollX = ((int[]) evt.getNewValue())[0]
+                  - ((int[]) evt.getOldValue())[0];
+        }
         ViewportRanges vpRanges = av.getRanges();
-        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
         int range = vpRanges.getEndRes() - vpRanges.getStartRes();
         if (scrollX > range)
         {
@@ -924,6 +946,10 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         // scroll
         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
       }
+      else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+      {
+        fastPaint(scrollX, 0);
+      }
     }
   }
 
index 9a61f5f..d74bbb7 100644 (file)
@@ -43,7 +43,6 @@ 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;
@@ -148,13 +147,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   void setCursorRow()
   {
     seqCanvas.cursorY = getKeyboardNo1() - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void setCursorColumn()
   {
     seqCanvas.cursorX = getKeyboardNo1() - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void setCursorRowAndColumn()
@@ -167,7 +166,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       seqCanvas.cursorX = getKeyboardNo1() - 1;
       seqCanvas.cursorY = getKeyboardNo2() - 1;
-      scrollToVisible();
+      scrollToVisible(true);
     }
   }
 
@@ -176,7 +175,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
 
     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void moveCursor(int dx, int dy)
@@ -202,10 +201,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         seqCanvas.cursorX = original;
       }
     }
-    scrollToVisible();
+    scrollToVisible(false);
   }
 
-  void scrollToVisible()
+  /**
+   * Scroll to make the cursor visible in the viewport.
+   * 
+   * @param jump
+   *          just jump to the location rather than scrolling
+   */
+  void scrollToVisible(boolean jump)
   {
     if (seqCanvas.cursorX < 0)
     {
@@ -226,44 +231,34 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
 
     endEditing();
-    if (av.getWrapAlignment())
+
+    boolean repaintNeeded = true;
+    if (jump)
     {
-      av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
+      // only need to repaint if the viewport did not move, as otherwise it will
+      // get a repaint
+      repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
+              seqCanvas.cursorY);
     }
     else
     {
-      ViewportRanges ranges = av.getRanges();
-      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-      while (seqCanvas.cursorY < ranges.getStartSeq())
+      if (av.getWrapAlignment())
       {
-        ranges.scrollUp(true);
+        av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
       }
-      while (seqCanvas.cursorY > ranges.getEndSeq())
-      {
-        ranges.scrollUp(false);
-      }
-      while (seqCanvas.cursorX < hidden
-              .adjustForHiddenColumns(ranges.getStartRes()))
-      {
-
-        if (!ranges.scrollRight(false))
-        {
-          break;
-        }
-      }
-      while (seqCanvas.cursorX > hidden
-              .adjustForHiddenColumns(ranges.getEndRes()))
+      else
       {
-        if (!ranges.scrollRight(true))
-        {
-          break;
-        }
+        av.getRanges().scrollToVisible(seqCanvas.cursorX,
+                seqCanvas.cursorY);
       }
     }
     setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
             seqCanvas.cursorX, seqCanvas.cursorY);
 
-    seqCanvas.repaint();
+    if (repaintNeeded)
+    {
+      seqCanvas.repaint();
+    }
   }
 
   void setSelectionAreaAtCursor(boolean topLeft)
index 8c5e4ac..f268d37 100755 (executable)
@@ -28,6 +28,7 @@ import jalview.util.LinkedIdentityHashSet;
 import jalview.util.MessageManager;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -1617,40 +1618,21 @@ public class Alignment implements AlignmentI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    List<AlignmentAnnotation> aa = new ArrayList<>();
     AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation();
     if (alignmentAnnotation != null)
     {
-      for (AlignmentAnnotation a : alignmentAnnotation)
-      {
-        if (a.getCalcId() == calcId || (a.getCalcId() != null
-                && calcId != null && a.getCalcId().equals(calcId)))
-        {
-          aa.add(a);
-        }
-      }
+      return AlignmentAnnotation.findAnnotation(
+              Arrays.asList(getAlignmentAnnotation()), calcId);
     }
-    return aa;
+    return Arrays.asList(new AlignmentAnnotation[] {});
   }
 
   @Override
   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
-    for (AlignmentAnnotation ann : getAlignmentAnnotation())
-    {
-      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);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotations(
+            Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
   }
 
   @Override
index 09facbf..f7bf4d8 100755 (executable)
@@ -24,6 +24,7 @@ import jalview.analysis.Rna;
 import jalview.analysis.SecStrConsensus.SimpleBP;
 import jalview.analysis.WUSSParseException;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -1471,4 +1472,71 @@ public class AlignmentAnnotation
   {
     return graphMin < graphMax;
   }
+
+  public static Iterable<AlignmentAnnotation> findAnnotations(
+          Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
+          String label)
+  {
+
+    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
+    for (AlignmentAnnotation ann : list)
+    {
+      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);
+      }
+    }
+    return aa;
+  }
+
+  /**
+   * Answer true if any annotation matches the calcId passed in (if not null).
+   * 
+   * @param list
+   *          annotation to search
+   * @param calcId
+   * @return
+   */
+  public static boolean hasAnnotation(List<AlignmentAnnotation> list,
+          String calcId)
+  {
+
+    if (calcId != null && !"".equals(calcId))
+    {
+      for (AlignmentAnnotation a : list)
+      {
+        if (a.getCalcId() == calcId)
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public static Iterable<AlignmentAnnotation> findAnnotation(
+          List<AlignmentAnnotation> list, String calcId)
+  {
+
+    List<AlignmentAnnotation> aa = new ArrayList<>();
+    if (calcId == null)
+    {
+      return aa;
+    }
+    for (AlignmentAnnotation a : list)
+    {
+
+      if (a.getCalcId() == calcId || (a.getCalcId() != null
+              && calcId != null && a.getCalcId().equals(calcId)))
+      {
+        aa.add(a);
+      }
+    }
+    return aa;
+  }
 }
index f2ae4b7..a9b1372 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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;
 
 public interface ContiguousI
index 7886713..8b6f617 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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;
 
 /**
index 9e81d81..441d8d0 100755 (executable)
@@ -1217,7 +1217,7 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   @Override
-  public void deleteChars(int i, int j)
+  public void deleteChars(final int i, final int j)
   {
     int newstart = start, newend = end;
     if (i >= sequence.length || i < 0)
@@ -1229,62 +1229,75 @@ public class Sequence extends ASequence implements SequenceI
     boolean createNewDs = false;
     // TODO: take a (second look) at the dataset creation validation method for
     // the very large sequence case
-    int eindex = -1, sindex = -1;
-    boolean ecalc = false, scalc = false;
+    int startIndex = findIndex(start) - 1;
+    int endIndex = findIndex(end) - 1;
+    int startDeleteColumn = -1; // for dataset sequence deletions
+    int deleteCount = 0;
+
     for (int s = i; s < j; s++)
     {
-      if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
+      if (Comparison.isGap(sequence[s]))
+      {
+        continue;
+      }
+      deleteCount++;
+      if (startDeleteColumn == -1)
       {
-        if (createNewDs)
+        startDeleteColumn = findPosition(s) - start;
+      }
+      if (createNewDs)
+      {
+        newend--;
+      }
+      else
+      {
+        if (startIndex == s)
         {
-          newend--;
+          /*
+           * deleting characters from start of sequence; new start is the
+           * sequence position of the next column (position to the right
+           * if the column position is gapped)
+           */
+          newstart = findPosition(j);
+          break;
         }
         else
         {
-          if (!scalc)
-          {
-            sindex = findIndex(start) - 1;
-            scalc = true;
-          }
-          if (sindex == s)
+          if (endIndex < j)
           {
-            // delete characters including start of sequence
-            newstart = findPosition(j);
-            break; // don't need to search for any more residue characters.
+            /*
+             * deleting characters at end of sequence; new end is the sequence
+             * position of the column before the deletion; subtract 1 if this is
+             * gapped since findPosition returns the next sequence position
+             */
+            newend = findPosition(i - 1);
+            if (Comparison.isGap(sequence[i - 1]))
+            {
+              newend--;
+            }
+            break;
           }
           else
           {
-            // delete characters after start.
-            if (!ecalc)
-            {
-              eindex = findIndex(end) - 1;
-              ecalc = true;
-            }
-            if (eindex < j)
-            {
-              // delete characters at end of sequence
-              newend = findPosition(i - 1);
-              break; // don't need to search for any more residue characters.
-            }
-            else
-            {
-              createNewDs = true;
-              newend--; // decrease end position by one for the deleted residue
-              // and search further
-            }
+            createNewDs = true;
+            newend--;
           }
         }
       }
     }
-    // deletion occured in the middle of the sequence
+
     if (createNewDs && this.datasetSequence != null)
     {
-      // construct a new sequence
+      /*
+       * if deletion occured in the middle of the sequence,
+       * construct a new dataset sequence and delete the residues
+       * that were deleted from the aligned sequence
+       */
       Sequence ds = new Sequence(datasetSequence);
+      ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
+      datasetSequence = ds;
       // TODO: remove any non-inheritable properties ?
       // TODO: create a sequence mapping (since there is a relation here ?)
-      ds.deleteChars(i, j);
-      datasetSequence = ds;
     }
     start = newstart;
     end = newend;
index b5929bf..24752bf 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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;
 
 /**
index e2f15e1..6b797d7 100755 (executable)
@@ -30,6 +30,7 @@ import java.awt.Color;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -1316,39 +1317,16 @@ public class SequenceGroup implements AnnotatedCollectionI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    List<AlignmentAnnotation> aa = new ArrayList<>();
-    if (calcId == null)
-    {
-      return aa;
-    }
-    for (AlignmentAnnotation a : getAlignmentAnnotation())
-    {
-      if (calcId.equals(a.getCalcId()))
-      {
-        aa.add(a);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotation(
+            Arrays.asList(getAlignmentAnnotation()), calcId);
   }
 
   @Override
   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
-    for (AlignmentAnnotation ann : getAlignmentAnnotation())
-    {
-      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);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotations(
+            Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
   }
 
   /**
@@ -1359,17 +1337,8 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public boolean hasAnnotation(String calcId)
   {
-    if (calcId != null && !"".equals(calcId))
-    {
-      for (AlignmentAnnotation a : getAlignmentAnnotation())
-      {
-        if (a.getCalcId() == calcId)
-        {
-          return true;
-        }
-      }
-    }
-    return false;
+    return AlignmentAnnotation
+            .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId);
   }
 
   /**
index c064373..fb723e6 100755 (executable)
@@ -193,13 +193,14 @@ 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 (column) position. If at a
+   * gap, returns the position of the next residue to the right. If beyond the
+   * end of the sequence, returns 1 more than the last residue position.
    * 
    * @param i
    *          column index in alignment (from 0..<length)
    * 
-   * @return TODO: JAL-2562 - residue number for residue (left of and) nearest
-   *         ith column
+   * @return
    */
   public int findPosition(int i);
 
index 7221d62..e359b62 100644 (file)
@@ -14,6 +14,11 @@ import java.util.TreeMap;
  */
 public class FeatureAttributes
 {
+  public enum Datatype
+  {
+    Character, Number, Mixed
+  }
+
   private static FeatureAttributes instance = new FeatureAttributes();
 
   /*
@@ -79,6 +84,8 @@ public class FeatureAttributes
      */
     boolean hasValue = false;
 
+    Datatype type;
+
     /**
      * Note one instance of this attribute, recording unique, non-null names,
      * and the min/max of any numerical values
@@ -95,12 +102,17 @@ public class FeatureAttributes
         try
         {
           float f = Float.valueOf(value);
-          min = Float.min(min, f);
-          max = Float.max(max, f);
+          min = hasValue ? Float.min(min, f) : f;
+          max = hasValue ? Float.max(max, f) : f;
           hasValue = true;
+          type = (type == null || type == Datatype.Number) ? Datatype.Number
+                  : Datatype.Mixed;
         } catch (NumberFormatException e)
         {
-          // ok, wasn't a number, ignore for min-max purposes
+          // not a number, ignore for min-max purposes
+          type = (type == null || type == Datatype.Character)
+                  ? Datatype.Character
+                  : Datatype.Mixed;
         }
       }
     }
@@ -118,6 +130,11 @@ public class FeatureAttributes
       return null;
     }
 
+    public Datatype getType()
+    {
+      return type;
+    }
+
     /**
      * Adds the given description to the list of known descriptions (without
      * duplication)
@@ -318,4 +335,26 @@ public class FeatureAttributes
     }
     attData.addDescription(description);
   }
+
+  /**
+   * Answers the datatype of the feature, which is one of Character, Number or
+   * Mixed (or null if not known), as discovered from values recorded.
+   * 
+   * @param featureType
+   * @param attName
+   * @return
+   */
+  public Datatype getDatatype(String featureType, String... attName)
+  {
+    Map<String[], AttributeData> atts = attributes.get(featureType);
+    if (atts != null)
+    {
+      AttributeData attData = atts.get(attName);
+      if (attData != null)
+      {
+        return attData.getType();
+      }
+    }
+    return null;
+  }
 }
index e651c13..378b8db 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index 51bee57..02ce1c5 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index b8160d3..ae58a69 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index 007f3b1..b991750 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index 26ffee1..b7d702d 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index 8d5ba58..fcf1b53 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.ContiguousI;
index 58beca2..80c4f9a 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.features;
 
 import jalview.datamodel.SequenceFeature;
index 4a359ff..b1ed275 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.xdb.uniprot;
 
 /**
index 12d1369..438e81b 100755 (executable)
@@ -1185,5 +1185,14 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
+    else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      fastPaint(((int[]) evt.getNewValue())[0]
+              - ((int[]) evt.getOldValue())[0]);
+    }
+    else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      repaint();
+    }
   }
 }
index 886762a..f674c7e 100644 (file)
@@ -169,7 +169,7 @@ public class CalculationChooser extends JPanel
     JPanel treePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
     treePanel.setOpaque(false);
 
-    JvSwingUtils.createItalicTitledBorder(treePanel,
+    JvSwingUtils.createTitledBorder(treePanel,
             MessageManager.getString("label.tree"), true);
 
     // then copy the inset dimensions for the border-less PCA panel
diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java
deleted file mode 100644 (file)
index da3819c..0000000
+++ /dev/null
@@ -1,1006 +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.gui;
-
-import jalview.api.FeatureColourI;
-import jalview.datamodel.GraphLine;
-import jalview.datamodel.features.FeatureAttributes;
-import jalview.schemes.FeatureColour;
-import jalview.util.MessageManager;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-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.ArrayList;
-import java.util.List;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JCheckBox;
-import javax.swing.JColorChooser;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JSlider;
-import javax.swing.JTextField;
-import javax.swing.border.LineBorder;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
-public class FeatureColourChooser extends JalviewDialog
-{
-  private static final String COLON = ":";
-
-  private static final int MAX_TOOLTIP_LENGTH = 50;
-
-  private static int NO_COLOUR_OPTION = 0;
-
-  private static int MIN_COLOUR_OPTION = 1;
-
-  private static int MAX_COLOUR_OPTION = 2;
-
-  private FeatureRenderer fr;
-
-  private FeatureColourI cs;
-
-  private FeatureColourI oldcs;
-
-  private AlignmentPanel ap;
-
-  private boolean adjusting = false;
-
-  private float min;
-
-  private float max;
-
-  private float scaleFactor;
-
-  private String type = null;
-
-  private JPanel minColour = new JPanel();
-
-  private JPanel maxColour = new JPanel();
-
-  private Color noColour;
-
-  private JComboBox<String> threshold = new JComboBox<>();
-
-  private JSlider slider = new JSlider();
-
-  private JTextField thresholdValue = new JTextField(20);
-
-  private JCheckBox thresholdIsMin = new JCheckBox();
-
-  private GraphLine threshline;
-
-  private Color oldmaxColour;
-
-  private Color oldminColour;
-
-  private Color oldNoColour;
-
-  private ActionListener colourEditor = null;
-
-  /*
-   * radio buttons to select what to colour by
-   * label, attribute text, score, attribute value
-   */
-  private JRadioButton byDescription = new JRadioButton();
-
-  private JRadioButton byAttributeText = new JRadioButton();
-
-  private JRadioButton byScore = new JRadioButton();
-
-  private JRadioButton byAttributeValue = new JRadioButton();
-
-  private ActionListener changeColourAction;
-
-  private ActionListener changeMinMaxAction;
-
-  /*
-   * choice of option for 'colour for no value'
-   */
-  private JComboBox<String> noValueCombo;
-
-  /*
-   * choice of attribute (if any) for 'colour by text'
-   */
-  private JComboBox<String> textAttributeCombo;
-
-  /*
-   * choice of attribute (if any) for 'colour by value'
-   */
-  private JComboBox<String> valueAttributeCombo;
-
-  /**
-   * Constructor
-   * 
-   * @param frender
-   * @param theType
-   */
-  public FeatureColourChooser(FeatureRenderer frender, String theType)
-  {
-    this(frender, false, theType);
-  }
-
-  /**
-   * Constructor, with option to make a blocking dialog (has to complete in the
-   * AWT event queue thread). Currently this option is always set to false.
-   * 
-   * @param frender
-   * @param blocking
-   * @param theType
-   */
-  FeatureColourChooser(FeatureRenderer frender, boolean blocking,
-          String theType)
-  {
-    this.fr = frender;
-    this.type = theType;
-    ap = fr.ap;
-    String title = MessageManager.formatMessage("label.variable_color_for",
-            new String[] { theType });
-    initDialogFrame(this, true, blocking, title, 470, 300);
-
-    slider.addChangeListener(new ChangeListener()
-    {
-      @Override
-      public void stateChanged(ChangeEvent evt)
-      {
-        if (!adjusting)
-        {
-          thresholdValue.setText((slider.getValue() / scaleFactor) + "");
-          sliderValueChanged();
-        }
-      }
-    });
-    slider.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mouseReleased(MouseEvent evt)
-      {
-        /*
-         * only update Overview and/or structure colouring
-         * when threshold slider drag ends (mouse up)
-         */
-        if (ap != null)
-        {
-          ap.paintAlignment(true, true);
-        }
-      }
-    });
-
-    // todo move all threshold setup inside a method
-    float mm[] = fr.getMinMax().get(theType)[0];
-    min = mm[0];
-    max = mm[1];
-
-    /*
-     * ensure scale factor allows a scaled range with
-     * 10 integer divisions ('ticks'); if we have got here,
-     * we should expect that max != min
-     */
-    scaleFactor = (max == min) ? 1f : 100f / (max - min);
-
-    oldcs = fr.getFeatureColours().get(theType);
-    if (!oldcs.isSimpleColour())
-    {
-      if (oldcs.isAutoScaled())
-      {
-        // update the scale
-        cs = new FeatureColour((FeatureColour) oldcs, min, max);
-      }
-      else
-      {
-        cs = new FeatureColour((FeatureColour) oldcs);
-      }
-    }
-    else
-    {
-      /*
-       * promote original simple color to a graduated color
-       * - by score if there is a score range, else by label
-       */
-      Color bl = oldcs.getColour();
-      if (bl == null)
-      {
-        bl = Color.BLACK;
-      }
-      // original colour becomes the maximum colour
-      cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
-      cs.setColourByLabel(mm[0] == mm[1]);
-    }
-    minColour.setBackground(oldminColour = cs.getMinColour());
-    maxColour.setBackground(oldmaxColour = cs.getMaxColour());
-    noColour = cs.getNoColour();
-
-    adjusting = true;
-
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-      return;
-    }
-
-    /*
-     * set the initial state of options on screen
-     */
-    if (cs.isColourByLabel())
-    {
-      if (cs.isColourByAttribute())
-      {
-        byAttributeText.setSelected(true);
-        textAttributeCombo.setEnabled(true);
-        String[] attributeName = cs.getAttributeName();
-        textAttributeCombo
-                .setSelectedItem(String.join(COLON, attributeName));
-      }
-      else
-      {
-        byDescription.setSelected(true);
-        textAttributeCombo.setEnabled(false);
-      }
-    }
-    else
-    {
-      if (cs.isColourByAttribute())
-      {
-        byAttributeValue.setSelected(true);
-        String[] attributeName = cs.getAttributeName();
-        valueAttributeCombo
-                .setSelectedItem(String.join(COLON, attributeName));
-        valueAttributeCombo.setEnabled(true);
-        updateMinMax();
-      }
-      else
-      {
-        byScore.setSelected(true);
-        valueAttributeCombo.setEnabled(false);
-      }
-    }
-
-    if (noColour == null)
-    {
-      noValueCombo.setSelectedIndex(NO_COLOUR_OPTION);
-    }
-    else if (noColour.equals(oldminColour))
-    {
-      noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION);
-    }
-    else if (noColour.equals(oldmaxColour))
-    {
-      noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION);
-    }
-
-    threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
-    threshline.value = cs.getThreshold();
-
-    if (cs.hasThreshold())
-    {
-      // initialise threshold slider and selector
-      threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2);
-      slider.setEnabled(true);
-      slider.setValue((int) (cs.getThreshold() * scaleFactor));
-      thresholdValue.setEnabled(true);
-    }
-
-    adjusting = false;
-
-    changeColour(false);
-    waitForInput();
-  }
-
-  /**
-   * Configures the initial layout
-   */
-  private void jbInit()
-  {
-    this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
-    this.setBackground(Color.white);
-
-    changeColourAction = new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        changeColour(true);
-      }
-    };
-
-    changeMinMaxAction = new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        updateMinMax();
-        changeColour(true);
-      }
-    };
-
-    /*
-     * this panel
-     *     detailsPanel
-     *         colourByTextPanel
-     *         colourByScorePanel
-     *     okCancelPanel
-     */
-    JPanel detailsPanel = new JPanel();
-    detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
-
-    JPanel colourByTextPanel = initColourByTextPanel();
-    detailsPanel.add(colourByTextPanel);
-
-    JPanel colourByValuePanel = initColourByValuePanel();
-    detailsPanel.add(colourByValuePanel);
-
-    /*
-     * 4 radio buttons select between colour by description, by
-     * attribute text, by score, or by attribute value
-     */
-    ButtonGroup bg = new ButtonGroup();
-    bg.add(byDescription);
-    bg.add(byAttributeText);
-    bg.add(byScore);
-    bg.add(byAttributeValue);
-
-    JPanel okCancelPanel = initOkCancelPanel();
-
-    this.add(detailsPanel);
-    this.add(okCancelPanel);
-  }
-
-  /**
-   * Updates the min-max range for a change in choice of Colour by Score, or
-   * Colour by Attribute (value)
-   */
-  protected void updateMinMax()
-  {
-    float[] minMax = null;
-    if (byScore.isSelected())
-    {
-      minMax = fr.getMinMax().get(type)[0];
-    }
-    else if (byAttributeValue.isSelected())
-    {
-      String attName = (String) valueAttributeCombo.getSelectedItem();
-      String[] attNames = attName.split(COLON);
-      minMax = FeatureAttributes.getInstance().getMinMax(type, attNames);
-    }
-    if (minMax != null)
-    {
-      min = minMax[0];
-      max = minMax[1];
-      scaleFactor = (max == min) ? 1f : 100f / (max - min);
-      slider.setValue((int) (min * scaleFactor));
-    }
-  }
-
-  /**
-   * Lay out fields for graduated colour by value
-   * 
-   * @return
-   */
-  protected JPanel initColourByValuePanel()
-  {
-    JPanel byValuePanel = new JPanel();
-    byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS));
-    JvSwingUtils.createItalicTitledBorder(byValuePanel,
-            MessageManager.getString("label.colour_by_value"), true);
-    byValuePanel.setBackground(Color.white);
-
-    /*
-     * first row - choose colour by score or by attribute, choose attribute
-     */
-    JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    byWhatPanel.setBackground(Color.white);
-    byValuePanel.add(byWhatPanel);
-
-    byScore.setText(MessageManager.getString("label.score"));
-    byWhatPanel.add(byScore);
-    byScore.addActionListener(changeMinMaxAction);
-
-    byAttributeValue.setText(MessageManager.getString("label.attribute"));
-    byAttributeValue.addActionListener(changeMinMaxAction);
-    byWhatPanel.add(byAttributeValue);
-
-    List<String[]> attNames = FeatureAttributes.getInstance()
-            .getAttributes(type);
-    valueAttributeCombo = populateAttributesDropdown(type, attNames, true);
-
-    /*
-     * if no numeric atttibutes found, disable colour by attribute value
-     */
-    if (valueAttributeCombo.getItemCount() == 0)
-    {
-      byAttributeValue.setEnabled(false);
-    }
-
-    byWhatPanel.add(valueAttributeCombo);
-
-    /*
-     * second row - min/max/no colours
-     */
-    JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    colourRangePanel.setBackground(Color.white);
-    byValuePanel.add(colourRangePanel);
-
-    minColour.setFont(JvSwingUtils.getLabelFont());
-    minColour.setBorder(BorderFactory.createLineBorder(Color.black));
-    minColour.setPreferredSize(new Dimension(40, 20));
-    minColour.setToolTipText(MessageManager.getString("label.min_colour"));
-    minColour.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent e)
-      {
-        if (minColour.isEnabled())
-        {
-          minColour_actionPerformed();
-        }
-      }
-    });
-
-    maxColour.setFont(JvSwingUtils.getLabelFont());
-    maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
-    maxColour.setPreferredSize(new Dimension(40, 20));
-    maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
-    maxColour.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent e)
-      {
-        if (maxColour.isEnabled())
-        {
-          maxColour_actionPerformed();
-        }
-      }
-    });
-    maxColour.setBorder(new LineBorder(Color.black));
-
-    noValueCombo = new JComboBox<>();
-    noValueCombo.addItem(MessageManager.getString("label.no_colour"));
-    noValueCombo.addItem(MessageManager.getString("label.min_colour"));
-    noValueCombo.addItem(MessageManager.getString("label.max_colour"));
-    noValueCombo.addItemListener(new ItemListener()
-    {
-      @Override
-      public void itemStateChanged(ItemEvent e)
-      {
-        setNoValueColour();
-      }
-    });
-
-    JLabel minText = new JLabel(MessageManager.getString("label.min_value"));
-    minText.setFont(JvSwingUtils.getLabelFont());
-    JLabel maxText = new JLabel(MessageManager.getString("label.max_value"));
-    maxText.setFont(JvSwingUtils.getLabelFont());
-    JLabel noText = new JLabel(MessageManager.getString("label.no_value"));
-    noText.setFont(JvSwingUtils.getLabelFont());
-
-    colourRangePanel.add(minText);
-    colourRangePanel.add(minColour);
-    colourRangePanel.add(maxText);
-    colourRangePanel.add(maxColour);
-    colourRangePanel.add(noText);
-    colourRangePanel.add(noValueCombo);
-
-    /*
-     * third row - threshold options and value
-     */
-    JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    thresholdPanel.setBackground(Color.white);
-    byValuePanel.add(thresholdPanel);
-
-    threshold.addActionListener(changeColourAction);
-    threshold.setToolTipText(MessageManager
-            .getString("label.threshold_feature_display_by_score"));
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_no_threshold")); // index 0
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_above_threshold")); // index 1
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_below_threshold")); // index 2
-
-    thresholdValue.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-    thresholdValue.addFocusListener(new FocusAdapter()
-    {
-      @Override
-      public void focusLost(FocusEvent 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));
-    slider.setToolTipText(MessageManager
-            .getString("label.adjust_threshold"));
-    thresholdValue.setEnabled(false);
-    thresholdValue.setColumns(7);
-
-    thresholdPanel.add(threshold);
-    thresholdPanel.add(slider);
-    thresholdPanel.add(thresholdValue);
-
-    /*
-     * 4th row - threshold is min / max
-     */
-    JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    isMinMaxPanel.setBackground(Color.white);
-    byValuePanel.add(isMinMaxPanel);
-    thresholdIsMin.setBackground(Color.white);
-    thresholdIsMin.setText(MessageManager
-            .getString("label.threshold_minmax"));
-    thresholdIsMin.setToolTipText(MessageManager
-            .getString("label.toggle_absolute_relative_display_threshold"));
-    thresholdIsMin.addActionListener(changeColourAction);
-    isMinMaxPanel.add(thresholdIsMin);
-
-    return byValuePanel;
-  }
-
-  /**
-   * Action on user choice of no / min / max colour to use when there is no
-   * value to colour by
-   */
-  protected void setNoValueColour()
-  {
-    int i = noValueCombo.getSelectedIndex();
-    if (i == NO_COLOUR_OPTION)
-    {
-      noColour = null;
-    }
-    else if (i == MIN_COLOUR_OPTION)
-    {
-      noColour = minColour.getBackground();
-    }
-    else if (i == MAX_COLOUR_OPTION)
-    {
-      noColour = maxColour.getBackground();
-    }
-    changeColour(true);
-  }
-
-  /**
-   * Lay out OK and Cancel buttons
-   * 
-   * @return
-   */
-  protected JPanel initOkCancelPanel()
-  {
-    JPanel okCancelPanel = new JPanel();
-    okCancelPanel.setBackground(Color.white);
-    okCancelPanel.add(ok);
-    okCancelPanel.add(cancel);
-    return okCancelPanel;
-  }
-
-  /**
-   * Lay out Colour by Label and attribute choice elements
-   * 
-   * @return
-   */
-  protected JPanel initColourByTextPanel()
-  {
-    JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    byTextPanel.setBackground(Color.white);
-    JvSwingUtils.createItalicTitledBorder(byTextPanel,
-            MessageManager.getString("label.colour_by_text"), true);
-
-    byDescription.setText(MessageManager.getString("label.label"));
-    byDescription.setToolTipText(MessageManager
-            .getString("label.colour_by_label_tip"));
-    byDescription.addActionListener(changeColourAction);
-    byTextPanel.add(byDescription);
-
-    byAttributeText.setText(MessageManager.getString("label.attribute"));
-    byAttributeText.addActionListener(changeColourAction);
-    byTextPanel.add(byAttributeText);
-
-    List<String[]> attNames = FeatureAttributes.getInstance()
-            .getAttributes(type);
-    textAttributeCombo = populateAttributesDropdown(type, attNames, false);
-    byTextPanel.add(textAttributeCombo);
-
-    /*
-     * disable colour by attribute if no attributes
-     */
-    if (attNames.isEmpty())
-    {
-      byAttributeText.setEnabled(false);
-    }
-
-    return byTextPanel;
-  }
-
-  /**
-   * Action on clicking the 'minimum colour' - open a colour chooser dialog, and
-   * set the selected colour (if the user does not cancel out of the dialog)
-   */
-  protected void minColour_actionPerformed()
-  {
-    Color col = JColorChooser.showDialog(this,
-            MessageManager.getString("label.select_colour_minimum_value"),
-            minColour.getBackground());
-    if (col != null)
-    {
-      minColour.setBackground(col);
-      minColour.setForeground(col);
-    }
-    minColour.repaint();
-    changeColour(true);
-  }
-
-  /**
-   * Action on clicking the 'maximum colour' - open a colour chooser dialog, and
-   * set the selected colour (if the user does not cancel out of the dialog)
-   */
-  protected void maxColour_actionPerformed()
-  {
-    Color col = JColorChooser.showDialog(this,
-            MessageManager.getString("label.select_colour_maximum_value"),
-            maxColour.getBackground());
-    if (col != null)
-    {
-      maxColour.setBackground(col);
-      maxColour.setForeground(col);
-    }
-    maxColour.repaint();
-    changeColour(true);
-  }
-
-  /**
-   * Constructs and sets the selected colour options as the colour for the
-   * feature type, and repaints the alignment, and optionally the Overview
-   * and/or structure viewer if open
-   * 
-   * @param updateStructsAndOverview
-   */
-  void changeColour(boolean updateStructsAndOverview)
-  {
-    // Check if combobox is still adjusting
-    if (adjusting)
-    {
-      return;
-    }
-
-    boolean aboveThreshold = false;
-    boolean belowThreshold = false;
-    if (threshold.getSelectedIndex() == 1)
-    {
-      aboveThreshold = true;
-    }
-    else if (threshold.getSelectedIndex() == 2)
-    {
-      belowThreshold = true;
-    }
-    boolean hasThreshold = aboveThreshold || belowThreshold;
-
-    slider.setEnabled(true);
-    thresholdValue.setEnabled(true);
-
-    /*
-     * make the feature colour
-     */
-    FeatureColourI acg;
-    if (cs.isColourByLabel())
-    {
-      acg = new FeatureColour(oldminColour, oldmaxColour, min, max);
-    }
-    else
-    {
-      acg = new FeatureColour(oldminColour = minColour.getBackground(),
-              oldmaxColour = maxColour.getBackground(),
-              oldNoColour = noColour, min, max);
-    }
-    String attribute = null;
-    textAttributeCombo.setEnabled(false);
-    valueAttributeCombo.setEnabled(false);
-    if (byAttributeText.isSelected())
-    {
-      attribute = (String) textAttributeCombo.getSelectedItem();
-      textAttributeCombo.setEnabled(true);
-      acg.setAttributeName(attribute.split(COLON));
-    }
-    else if (byAttributeValue.isSelected())
-    {
-      attribute = (String) valueAttributeCombo.getSelectedItem();
-      valueAttributeCombo.setEnabled(true);
-      acg.setAttributeName(attribute.split(COLON));
-    }
-    else
-    {
-      acg.setAttributeName((String) null);
-    }
-
-    if (!hasThreshold)
-    {
-      slider.setEnabled(false);
-      thresholdValue.setEnabled(false);
-      thresholdValue.setText("");
-      thresholdIsMin.setEnabled(false);
-    }
-    else if (threshline == null)
-    {
-      /*
-       * todo not yet implemented: visual indication of feature threshold
-       */
-      threshline = new GraphLine((max - min) / 2f, "Threshold",
-              Color.black);
-    }
-
-    if (hasThreshold)
-    {
-      adjusting = true;
-      acg.setThreshold(threshline.value);
-
-      float range = (max - min) * scaleFactor;
-
-      slider.setMinimum((int) (min * scaleFactor));
-      slider.setMaximum((int) (max * scaleFactor));
-      // slider.setValue((int) (threshline.value * scaleFactor));
-      slider.setValue(Math.round(threshline.value * scaleFactor));
-      thresholdValue.setText(threshline.value + "");
-      slider.setMajorTickSpacing((int) (range / 10f));
-      slider.setEnabled(true);
-      thresholdValue.setEnabled(true);
-      thresholdIsMin.setEnabled(!byDescription.isSelected());
-      adjusting = false;
-    }
-
-    acg.setAboveThreshold(aboveThreshold);
-    acg.setBelowThreshold(belowThreshold);
-    if (thresholdIsMin.isSelected() && hasThreshold)
-    {
-      acg.setAutoScaled(false);
-      if (aboveThreshold)
-      {
-        acg = new FeatureColour((FeatureColour) acg, threshline.value, max);
-      }
-      else
-      {
-        acg = new FeatureColour((FeatureColour) acg, min, threshline.value);
-      }
-    }
-    else
-    {
-      acg.setAutoScaled(true);
-    }
-    acg.setColourByLabel(byDescription.isSelected()
-            || byAttributeText.isSelected());
-
-    if (acg.isColourByLabel())
-    {
-      maxColour.setEnabled(false);
-      minColour.setEnabled(false);
-      noValueCombo.setEnabled(false);
-      maxColour.setBackground(this.getBackground());
-      maxColour.setForeground(this.getBackground());
-      minColour.setBackground(this.getBackground());
-      minColour.setForeground(this.getBackground());
-    }
-    else
-    {
-      maxColour.setEnabled(true);
-      minColour.setEnabled(true);
-      noValueCombo.setEnabled(true);
-      maxColour.setBackground(oldmaxColour);
-      maxColour.setForeground(oldmaxColour);
-      minColour.setBackground(oldminColour);
-      minColour.setForeground(oldminColour);
-      noColour = oldNoColour;
-    }
-
-    /*
-     * save the colour, and repaint stuff
-     */
-    fr.setColour(type, acg);
-    cs = acg;
-    ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
-  }
-
-  @Override
-  protected void raiseClosed()
-  {
-    if (this.colourEditor != null)
-    {
-      colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
-    }
-  }
-
-  @Override
-  public void okPressed()
-  {
-    changeColour(false);
-  }
-
-  @Override
-  public void cancelPressed()
-  {
-    reset();
-  }
-
-  /**
-   * Action when the user cancels the dialog. All previous settings should be
-   * restored and rendered on the alignment, and any linked Overview window or
-   * structure.
-   */
-  void reset()
-  {
-    fr.setColour(type, oldcs);
-    ap.paintAlignment(true, true);
-    cs = null;
-  }
-
-  /**
-   * Action on text entry of a threshold value
-   */
-  protected void thresholdValue_actionPerformed()
-  {
-    try
-    {
-      float f = Float.parseFloat(thresholdValue.getText());
-      slider.setValue((int) (f * scaleFactor));
-      threshline.value = f;
-
-      /*
-       * force repaint of any Overview window or structure
-       */
-      ap.paintAlignment(true, true);
-    } catch (NumberFormatException ex)
-    {
-    }
-  }
-
-  /**
-   * Action on change of threshold slider value. This may be done interactively
-   * (by moving the slider), or programmatically (to update the slider after
-   * manual input of a threshold value).
-   */
-  protected void sliderValueChanged()
-  {
-    /*
-     * squash rounding errors by forcing min/max of slider to 
-     * actual min/max of feature score range
-     */
-    int value = slider.getValue();
-    threshline.value = value == slider.getMaximum() ? max
-            : (value == slider.getMinimum() ? min : value / scaleFactor);
-    cs.setThreshold(threshline.value);
-
-    /*
-     * repaint alignment, but not Overview or structure,
-     * to avoid overload while dragging the slider
-     */
-    changeColour(false);
-  }
-
-  void addActionListener(ActionListener graduatedColorEditor)
-  {
-    if (colourEditor != null)
-    {
-      System.err.println(
-              "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
-    }
-    colourEditor = graduatedColorEditor;
-  }
-
-  /**
-   * Answers the last colour setting selected by user - either oldcs (which may
-   * be a java.awt.Color) or the new GraduatedColor
-   * 
-   * @return
-   */
-  FeatureColourI getLastColour()
-  {
-    if (cs == null)
-    {
-      return oldcs;
-    }
-    return cs;
-  }
-
-  /**
-   * A helper method to build the drop-down choice of attributes for a feature.
-   * Where metadata is available with a description for an attribute, that is
-   * added as a tooltip. The list may optionally be restricted to attributes for
-   * which we hold a range of numerical values (so suitable candidates for a
-   * graduated colour scheme).
-   * <p>
-   * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ",
-   * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele.
-   * 
-   * @param featureType
-   * @param attNames
-   * @param withNumericRange
-   */
-  protected JComboBox<String> populateAttributesDropdown(
-          String featureType, List<String[]> attNames,
-          boolean withNumericRange)
-  {
-    List<String> validAtts = new ArrayList<>();
-    List<String> tooltips = new ArrayList<>();
-
-    FeatureAttributes fa = FeatureAttributes.getInstance();
-    for (String[] attName : attNames)
-    {
-      if (withNumericRange)
-      {
-        float[] minMax = fa.getMinMax(featureType, attName);
-        if (minMax == null)
-        {
-          continue;
-        }
-      }
-      validAtts.add(String.join(COLON, attName));
-      String desc = fa.getDescription(featureType, attName);
-      if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
-      {
-        desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
-      }
-      tooltips.add(desc == null ? "" : desc);
-    }
-
-    JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
-            validAtts, tooltips);
-
-    attCombo.addItemListener(new ItemListener()
-    {
-      @Override
-      public void itemStateChanged(ItemEvent e)
-      {
-        changeMinMaxAction.actionPerformed(null);
-      }
-    });
-
-    if (validAtts.isEmpty())
-    {
-      attCombo.setToolTipText(MessageManager
-              .getString(withNumericRange ? "label.no_numeric_attributes"
-                      : "label.no_attributes"));
-    }
-
-    return attCombo;
-  }
-
-}
index 9c4b009..46f574e 100644 (file)
@@ -180,15 +180,15 @@ public class FeatureRenderer
     final JSpinner end = new JSpinner();
     start.setPreferredSize(new Dimension(80, 20));
     end.setPreferredSize(new Dimension(80, 20));
-    final FeatureRenderer me = this;
     final JLabel colour = new JLabel();
     colour.setOpaque(true);
     // colour.setBorder(BorderFactory.createEtchedBorder());
     colour.setMaximumSize(new Dimension(30, 16));
     colour.addMouseListener(new MouseAdapter()
     {
-      FeatureColourChooser fcc = null;
-
+      /*
+       * open colour chooser on click in colour panel
+       */
       @Override
       public void mousePressed(MouseEvent evt)
       {
@@ -205,28 +205,26 @@ public class FeatureRenderer
         }
         else
         {
-          if (fcc == null)
+          /*
+           * variable colour dialog - on OK, refetch the updated
+           * feature colour and update this display
+           */
+          final String ft = features.get(featureIndex).getType();
+          final String type = ft == null ? lastFeatureAdded : ft;
+          FeatureTypeSettings fcc = new FeatureTypeSettings(
+                  FeatureRenderer.this, type);
+          fcc.setRequestFocusEnabled(true);
+          fcc.requestFocus();
+          fcc.addActionListener(new ActionListener()
           {
-            final String ft = features.get(featureIndex).getType();
-            final String type = ft == null ? lastFeatureAdded : ft;
-            fcc = new FeatureColourChooser(me, type);
-            fcc.setRequestFocusEnabled(true);
-            fcc.requestFocus();
-
-            fcc.addActionListener(new ActionListener()
+            @Override
+            public void actionPerformed(ActionEvent e)
             {
-
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                fcol = fcc.getLastColour();
-                fcc = null;
-                setColour(type, fcol);
-                updateColourButton(mainPanel, colour, fcol);
-              }
-            });
-
-          }
+              fcol = FeatureRenderer.this.getFeatureStyle(ft);
+              setColour(type, fcol);
+              updateColourButton(mainPanel, colour, fcol);
+            }
+          });
         }
       }
     });
index ed98830..fedfe3f 100644 (file)
  */
 package jalview.gui;
 
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN;
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.FILTER_COLUMN;
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN;
+import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN;
+
 import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.FeatureAttributes;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -35,10 +39,6 @@ import jalview.util.Format;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.QuickSort;
-import jalview.util.ReverseListIterator;
-import jalview.util.matcher.Condition;
-import jalview.util.matcher.KeyedMatcher;
-import jalview.util.matcher.KeyedMatcherI;
 import jalview.util.matcher.KeyedMatcherSet;
 import jalview.util.matcher.KeyedMatcherSetI;
 import jalview.viewmodel.AlignmentViewport;
@@ -49,16 +49,13 @@ 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.Graphics;
 import java.awt.GridLayout;
-import java.awt.LayoutManager;
+import java.awt.Point;
 import java.awt.Rectangle;
 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;
@@ -72,7 +69,6 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -85,14 +81,11 @@ import java.util.Vector;
 import javax.help.HelpSetException;
 import javax.swing.AbstractCellEditor;
 import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JColorChooser;
-import javax.swing.JComboBox;
 import javax.swing.JDialog;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
@@ -100,26 +93,24 @@ import javax.swing.JLayeredPane;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
-import javax.swing.JRadioButton;
 import javax.swing.JScrollPane;
 import javax.swing.JSlider;
-import javax.swing.JTabbedPane;
 import javax.swing.JTable;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.plaf.basic.BasicArrowButton;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
 
 public class FeatureSettings extends JPanel
         implements FeatureSettingsControllerI
 {
+  private static final int COLUMN_COUNT = 4;
+
   private static final String COLON = ":";
 
   private static final int MIN_WIDTH = 400;
@@ -156,7 +147,7 @@ public class FeatureSettings extends JPanel
   JPanel groupPanel;
 
   JSlider transparency = new JSlider();
-  
+
   /*
    * when true, constructor is still executing - so ignore UI events
    */
@@ -182,29 +173,6 @@ public class FeatureSettings extends JPanel
    */
   Map<String, float[]> typeWidth = null;
 
-  /*
-   * fields of the feature filters tab
-   */
-  private JPanel filtersPane;
-
-  private JPanel chooseFiltersPanel;
-
-  private JComboBox<String> filteredFeatureChoice;
-
-  private JRadioButton andFilters;
-
-  private JRadioButton orFilters;
-
-  /*
-   * filters for the currently selected feature type
-   */
-  private List<KeyedMatcherI> filters;
-
-  private JTextArea filtersAsText;
-
-  // set white normally, black to debug layout
-  private Color debugBorderColour = Color.white;
-
   /**
    * Constructor
    * 
@@ -235,25 +203,48 @@ public class FeatureSettings extends JPanel
       @Override
       public String getToolTipText(MouseEvent e)
       {
-        if (table.columnAtPoint(e.getPoint()) == 0)
+        String tip = null;
+        int column = table.columnAtPoint(e.getPoint());
+        switch (column)
         {
-          /*
-           * Tooltip for feature name only
-           */
-          return JvSwingUtils.wrapTooltip(true, MessageManager
+        case TYPE_COLUMN:
+          tip = JvSwingUtils.wrapTooltip(true, MessageManager
                   .getString("label.feature_settings_click_drag"));
+          break;
+        case FILTER_COLUMN:
+          int row = table.rowAtPoint(e.getPoint());
+          KeyedMatcherSet o = (KeyedMatcherSet) table.getValueAt(row,
+                  column);
+          tip = o.isEmpty()
+                  ? MessageManager.getString("label.filters_tooltip")
+                  : o.toString();
+          break;
+        default:
+          break;
         }
-        return null;
+        return tip;
       }
     };
     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
     table.setFont(new Font("Verdana", Font.PLAIN, 12));
-    table.setDefaultRenderer(Color.class, new ColorRenderer());
-
-    table.setDefaultEditor(Color.class, new ColorEditor(this));
 
+    // table.setDefaultRenderer(Color.class, new ColorRenderer());
+    // table.setDefaultEditor(Color.class, new ColorEditor(this));
+    //
     table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
+
+    table.setDefaultEditor(KeyedMatcherSet.class, new FilterEditor(this));
+    table.setDefaultRenderer(KeyedMatcherSet.class, new FilterRenderer());
+
+    TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
+            new ColorRenderer(), new ColorEditor(this));
+    table.addColumn(colourColumn);
+
+    TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
+            new FilterRenderer(), new FilterEditor(this));
+    table.addColumn(filterColumn);
+
     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 
     table.addMouseListener(new MouseAdapter()
@@ -262,11 +253,12 @@ public class FeatureSettings extends JPanel
       public void mousePressed(MouseEvent evt)
       {
         selectedRow = table.rowAtPoint(evt.getPoint());
+        String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
         if (evt.isPopupTrigger())
         {
-          popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
-                  evt.getX(), evt.getY());
+          Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
+          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
+                  evt.getY());
         }
         else if (evt.getClickCount() == 2)
         {
@@ -274,8 +266,7 @@ public class FeatureSettings extends JPanel
           boolean toggleSelection = Platform.isControlDown(evt);
           boolean extendSelection = evt.isShiftDown();
           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
-                  invertSelection, extendSelection, toggleSelection,
-                  (String) table.getValueAt(selectedRow, 0));
+                  invertSelection, extendSelection, toggleSelection, type);
         }
       }
 
@@ -286,9 +277,10 @@ public class FeatureSettings extends JPanel
         selectedRow = table.rowAtPoint(evt.getPoint());
         if (evt.isPopupTrigger())
         {
-          popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
-                  evt.getX(), evt.getY());
+          String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
+          Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
+          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
+                  evt.getY());
         }
       }
     });
@@ -344,8 +336,8 @@ public class FeatureSettings extends JPanel
         if (!fs.resettingTable && !fs.handlingUpdate)
         {
           fs.handlingUpdate = true;
-          fs.resetTable(null); // new groups may be added with new seuqence
-          // feature types only
+          fs.resetTable(null);
+          // new groups may be added with new sequence feature types only
           fs.handlingUpdate = false;
         }
       }
@@ -383,7 +375,7 @@ public class FeatureSettings extends JPanel
     inConstruction = false;
   }
 
-  protected void popupSort(final int selectedRow, final String type,
+  protected void popupSort(final int rowSelected, final String type,
           final Object typeCol, final Map<String, float[][]> minmax, int x,
           int y)
   {
@@ -443,15 +435,17 @@ public class FeatureSettings extends JPanel
         {
           if (featureColour.isSimpleColour())
           {
-            FeatureColourChooser fc = new FeatureColourChooser(me.fr, type);
+            FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
             fc.addActionListener(this);
           }
           else
           {
             // bring up simple color chooser
             colorChooser = new JColorChooser();
+            String title = MessageManager
+                    .getString("label.select_colour");
             JDialog dialog = JColorChooser.createDialog(me,
-                    "Select new Colour", true, // modal
+                    title, true, // modal
                     colorChooser, this, // OK button handler
                     null); // no CANCEL button handler
             colorChooser.setColor(featureColour.getMaxColour());
@@ -460,20 +454,25 @@ public class FeatureSettings extends JPanel
         }
         else
         {
-          if (e.getSource() instanceof FeatureColourChooser)
+          if (e.getSource() instanceof FeatureTypeSettings)
           {
-            FeatureColourChooser fc = (FeatureColourChooser) e.getSource();
-            table.setValueAt(fc.getLastColour(), selectedRow, 1);
+            /*
+             * update after OK in feature colour dialog; the updated
+             * colour will have already been set in the FeatureRenderer
+             */
+            FeatureColourI fci = fr.getFeatureColours().get(type);
+            table.setValueAt(fci, rowSelected, 1);
             table.validate();
           }
           else
           {
             // probably the color chooser!
             table.setValueAt(new FeatureColour(colorChooser.getColor()),
-                    selectedRow, 1);
+                    rowSelected, 1);
             table.validate();
             me.updateFeatureRenderer(
-                    ((FeatureTableModel) table.getModel()).getData(), false);
+                    ((FeatureTableModel) table.getModel()).getData(),
+                    false);
           }
         }
       }
@@ -548,8 +547,6 @@ public class FeatureSettings extends JPanel
       }
     }
 
-    populateFilterableFeatures();
-
     resetTable(null);
 
     validate();
@@ -654,7 +651,8 @@ public class FeatureSettings extends JPanel
       }
     }
 
-    Object[][] data = new Object[displayableTypes.size()][3];
+    int columnCount = COLUMN_COUNT;
+    Object[][] data = new Object[displayableTypes.size()][columnCount];
     int dataIndex = 0;
 
     if (fr.hasRenderOrder())
@@ -677,9 +675,13 @@ public class FeatureSettings extends JPanel
           continue;
         }
 
-        data[dataIndex][0] = type;
-        data[dataIndex][1] = fr.getFeatureStyle(type);
-        data[dataIndex][2] = new Boolean(
+        data[dataIndex][TYPE_COLUMN] = type;
+        data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
+        KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type);
+        data[dataIndex][FILTER_COLUMN] = featureFilter == null
+                ? new KeyedMatcherSet()
+                : featureFilter;
+        data[dataIndex][SHOW_COLUMN] = new Boolean(
                 af.getViewport().getFeaturesDisplayed().isVisible(type));
         dataIndex++;
         displayableTypes.remove(type);
@@ -693,27 +695,31 @@ public class FeatureSettings extends JPanel
     while (!displayableTypes.isEmpty())
     {
       String type = displayableTypes.iterator().next();
-      data[dataIndex][0] = type;
+      data[dataIndex][TYPE_COLUMN] = type;
 
-      data[dataIndex][1] = fr.getFeatureStyle(type);
-      if (data[dataIndex][1] == null)
+      data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
+      if (data[dataIndex][COLOUR_COLUMN] == null)
       {
         // "Colour has been updated in another view!!"
         fr.clearRenderOrder();
         return;
       }
-
-      data[dataIndex][2] = new Boolean(true);
+      KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type);
+      data[dataIndex][FILTER_COLUMN] = featureFilter == null
+              ? new KeyedMatcherSet()
+              : featureFilter;
+      data[dataIndex][SHOW_COLUMN] = new Boolean(true);
       dataIndex++;
       displayableTypes.remove(type);
     }
 
     if (originalData == null)
     {
-      originalData = new Object[data.length][3];
+      int size = data[0].length;
+      originalData = new Object[data.length][size];
       for (int i = 0; i < data.length; i++)
       {
-        System.arraycopy(data[i], 0, originalData[i], 0, 3);
+        System.arraycopy(data[i], 0, originalData[i], 0, size);
       }
     }
     else
@@ -734,8 +740,8 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Updates 'originalData' (used for restore on Cancel) if we detect that
-   * changes have been made outwith this dialog
+   * 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>
@@ -751,27 +757,27 @@ public class FeatureSettings extends JPanel
             .getData();
     for (Object[] row : foundData)
     {
-      String type = (String) row[0];
+      String type = (String) row[TYPE_COLUMN];
       boolean found = false;
       for (Object[] current : currentData)
       {
-        if (type.equals(current[0]))
+        if (type.equals(current[TYPE_COLUMN]))
         {
           found = true;
           /*
            * currently dependent on object equality here;
            * really need an equals method on FeatureColour
            */
-          if (!row[1].equals(current[1]))
+          if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
           {
             /*
              * feature colour has changed externally - update originalData
              */
             for (Object[] original : originalData)
             {
-              if (type.equals(original[0]))
+              if (type.equals(original[TYPE_COLUMN]))
               {
-                original[1] = row[1];
+                original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
                 break;
               }
             }
@@ -784,10 +790,11 @@ public class FeatureSettings extends JPanel
         /*
          * new feature detected - add to original data (on top)
          */
-        Object[][] newData = new Object[originalData.length + 1][3];
+        int size = currentData[0].length;
+        Object[][] newData = new Object[originalData.length + 1][size];
         for (int i = 0; i < originalData.length; i++)
         {
-          System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
+          System.arraycopy(originalData[i], 0, newData[i + 1], 0, size);
         }
         newData[0] = row;
         originalData = newData;
@@ -797,8 +804,8 @@ public class FeatureSettings extends JPanel
 
   /**
    * 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.
+   * foundGroups set. This enables removing a group from the display when the last
+   * feature in that group is deleted.
    * 
    * @param foundGroups
    */
@@ -1004,9 +1011,9 @@ public class FeatureSettings extends JPanel
   {
     for (int i = 0; i < table.getRowCount(); i++)
     {
-      Boolean value = (Boolean) table.getValueAt(i, 2);
+      Boolean value = (Boolean) table.getValueAt(i, SHOW_COLUMN);
 
-      table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
+      table.setValueAt(new Boolean(!value.booleanValue()), i, SHOW_COLUMN);
     }
   }
 
@@ -1020,17 +1027,16 @@ public class FeatureSettings extends JPanel
     float[] width = new float[data.length];
     float[] awidth;
     float max = 0;
-    int num = 0;
+
     for (int i = 0; i < data.length; i++)
     {
-      awidth = typeWidth.get(data[i][0]);
+      awidth = typeWidth.get(data[i][TYPE_COLUMN]);
       if (awidth[0] > 0)
       {
         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
         // weight - but have to make per
         // sequence, too (awidth[2])
         // if (width[i]==1) // hack to distinguish single width sequences.
-        num++;
       }
       else
       {
@@ -1047,16 +1053,17 @@ public class FeatureSettings extends JPanel
       // awidth = (float[]) typeWidth.get(data[i][0]);
       if (width[i] == 0)
       {
-        width[i] = fr.getOrder(data[i][0].toString());
+        width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
         if (width[i] < 0)
         {
-          width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
+          width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
+                  i / data.length);
         }
       }
       else
       {
         width[i] /= max; // normalize
-        fr.setOrder(data[i][0].toString(), width[i]); // store for later
+        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
       }
       if (i > 0)
       {
@@ -1090,8 +1097,8 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Update the priority order of features; only repaint if this changed the
-   * order of visible features
+   * Update the priority order of features; only repaint if this changed the order
+   * of visible features
    * 
    * @param data
    * @param visibleNew
@@ -1111,8 +1118,6 @@ public class FeatureSettings extends JPanel
     JPanel settingsPane = new JPanel();
     settingsPane.setLayout(new BorderLayout());
 
-    filtersPane = new JPanel();
-
     dasSettingsPane.setLayout(new BorderLayout());
 
     JPanel bigPanel = new JPanel();
@@ -1257,7 +1262,7 @@ public class FeatureSettings extends JPanel
         if (!inConstruction)
         {
           fr.setTransparency((100 - transparency.getValue()) / 100f);
-          af.alignPanel.paintAlignment(true,true);
+          af.alignPanel.paintAlignment(true, true);
         }
       }
     });
@@ -1298,15 +1303,6 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    JTabbedPane tabbedPane = new JTabbedPane();
-    this.add(tabbedPane, BorderLayout.CENTER);
-    tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
-            settingsPane);
-    tabbedPane.addTab(MessageManager.getString("label.filters"),
-            filtersPane);
-    // tabbedPane.addTab(MessageManager.getString("label.das_settings"),
-    // dasSettingsPane);
-
     JPanel transPanel = new JPanel(new GridLayout(1, 2));
     bigPanel.add(transPanel, BorderLayout.SOUTH);
 
@@ -1331,444 +1327,7 @@ public class FeatureSettings extends JPanel
     dasButtonPanel.add(saveDAS);
     settingsPane.add(bigPanel, BorderLayout.CENTER);
     settingsPane.add(buttonPanel, BorderLayout.SOUTH);
-
-    initFiltersTab();
-  }
-
-  /**
-   * Populates initial layout of the feature attribute filters panel
-   */
-  protected void initFiltersTab()
-  {
-    filters = new ArrayList<>();
-
-    /*
-     * choose feature type
-     */
-    JPanel chooseTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    chooseTypePanel.setBackground(Color.white);
-    JvSwingUtils.createItalicTitledBorder(chooseTypePanel,
-            MessageManager.getString("label.feature_type"), true);
-    filteredFeatureChoice = new JComboBox<>();
-    filteredFeatureChoice.addItemListener(new ItemListener()
-    {
-      @Override
-      public void itemStateChanged(ItemEvent e)
-      {
-        refreshFiltersDisplay();
-      }
-    });
-    chooseTypePanel.add(new JLabel(MessageManager
-            .getString("label.feature_to_filter")));
-    chooseTypePanel.add(filteredFeatureChoice);
-    populateFilterableFeatures();
-
-    /*
-     * the panel with the filters for the selected feature type
-     */
-    JPanel filtersPanel = new JPanel();
-    filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
-    filtersPanel.setBackground(Color.white);
-    JvSwingUtils.createItalicTitledBorder(filtersPanel,
-            MessageManager.getString("label.filters"), true);
-
-    /*
-     * add AND or OR radio buttons
-     */
-    JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    andOrPanel.setBackground(Color.white);
-    andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour));
-    andFilters = new JRadioButton("And");
-    orFilters = new JRadioButton("Or");
-    ActionListener actionListener = new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        filtersChanged();
-      }
-    };
-    andFilters.addActionListener(actionListener);
-    orFilters.addActionListener(actionListener);
-    ButtonGroup andOr = new ButtonGroup();
-    andOr.add(andFilters);
-    andOr.add(orFilters);
-    andFilters.setSelected(true);
-    andOrPanel.add(new JLabel(MessageManager
-            .getString("label.join_conditions")));
-    andOrPanel.add(andFilters);
-    andOrPanel.add(orFilters);
-    filtersPanel.add(andOrPanel);
-
-    /*
-     * panel with filters - populated by refreshFiltersDisplay
-     */
-    chooseFiltersPanel = new JPanel();
-    LayoutManager box = new BoxLayout(chooseFiltersPanel,
-            BoxLayout.Y_AXIS);
-    chooseFiltersPanel.setLayout(box);
-    filtersPanel.add(chooseFiltersPanel);
-
-    /*
-     * a read-only text view of the current filters
-     */
-    JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5));
-    showFiltersPanel.setBackground(Color.white);
-    JvSwingUtils.createItalicTitledBorder(showFiltersPanel,
-            MessageManager.getString("label.match_condition"), true);
-    filtersAsText = new JTextArea();
-    filtersAsText.setLineWrap(true);
-    filtersAsText.setWrapStyleWord(true);
-    showFiltersPanel.add(filtersAsText);
-
-    filtersPane.setLayout(new BorderLayout());
-    filtersPane.add(chooseTypePanel, BorderLayout.NORTH);
-    filtersPane.add(filtersPanel, BorderLayout.CENTER);
-    filtersPane.add(showFiltersPanel, BorderLayout.SOUTH);
-
-    /*
-     * update display for initial feature type selection
-     */
-    refreshFiltersDisplay();
-  }
-
-  /**
-   * Adds entries to the 'choose feature to filter' drop-down choice. Only
-   * feature types which have known attributes (so can be filtered) are
-   * included, so recall this method to update the list (check for newly added
-   * attributes).
-   */
-  protected void populateFilterableFeatures()
-  {
-    /*
-     * suppress action handler while updating the list
-     */
-    ItemListener listener = filteredFeatureChoice.getItemListeners()[0];
-    filteredFeatureChoice.removeItemListener(listener);
-
-    filteredFeatureChoice.removeAllItems();
-    ReverseListIterator<String> types = new ReverseListIterator<>(
-            fr.getRenderOrder());
-
-    boolean found = false;
-    while (types.hasNext())
-    {
-      String type = types.next();
-      if (FeatureAttributes.getInstance().hasAttributes(type))
-      {
-        filteredFeatureChoice.addItem(type);
-        found = true;
-      }
-    }
-    if (!found)
-    {
-      filteredFeatureChoice.addItem(MessageManager
-              .getString("label.no_feature_attributes"));
-      filteredFeatureChoice.setEnabled(false);
-    }
-
-    filteredFeatureChoice.addItemListener(listener);
-  }
-
-  /**
-   * Refreshes the display to show any filters currently configured for the
-   * selected feature type (editable, with 'remove' option), plus one extra row
-   * for adding a condition. This should be called on change of selected feature
-   * type, or after a filter has been removed, added or amended.
-   */
-  protected void refreshFiltersDisplay()
-  {
-    /*
-     * clear the panel and list of filter conditions
-     */
-    chooseFiltersPanel.removeAll();
-    filters.clear();
-
-    /*
-     * look up attributes known for feature type
-     */
-    String selectedType = (String) filteredFeatureChoice.getSelectedItem();
-    List<String[]> attNames = FeatureAttributes.getInstance()
-            .getAttributes(selectedType);
-
-    /*
-     * if this feature type has filters set, load them first
-     */
-    KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType);
-    filtersAsText.setText("");
-    if (featureFilters != null)
-    {
-      filtersAsText.setText(featureFilters.toString());
-      if (!featureFilters.isAnded())
-      {
-        orFilters.setSelected(true);
-      }
-      featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
-    }
-
-    /*
-     * and an empty filter for the user to populate (add)
-     */
-    KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "",
-            (String) null);
-    filters.add(noFilter);
-
-    /*
-     * render the conditions in rows, each in its own JPanel
-     */
-    int filterIndex = 0;
-    for (KeyedMatcherI filter : filters)
-    {
-      String[] attName = filter.getKey();
-      Condition condition = filter.getMatcher()
-              .getCondition();
-      String pattern = filter.getMatcher().getPattern();
-      JPanel row = addFilter(attName, attNames, condition, pattern, filterIndex);
-      row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
-      chooseFiltersPanel.add(row);
-      filterIndex++;
-    }
-    // chooseFiltersPanel.add(Box.createVerticalGlue());
-
-    filtersPane.validate();
-    filtersPane.repaint();
-  }
-
-  /**
-   * A helper method that constructs a panel with one filter condition:
-   * <ul>
-   * <li>a drop-down list of attribute names to choose from</li>
-   * <li>a drop-down list of conditions to choose from</li>
-   * <li>a text field for input of a match pattern</li>
-   * <li>optionally, a 'remove' button</li>
-   * </ul>
-   * If attribute, condition or pattern are not null, they are set as defaults for
-   * the input fields. The 'remove' button is added unless the pattern is null or
-   * empty (incomplete filter condition).
-   * 
-   * @param attName
-   * @param attNames
-   * @param cond
-   * @param pattern
-   * @param filterIndex
-   * @return
-   */
-  protected JPanel addFilter(String[] attName, List<String[]> attNames,
-          Condition cond, String pattern, int filterIndex)
-  {
-    JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
-    filterRow.setBackground(Color.white);
-
-    /*
-     * drop-down choice of attribute, with description as a tooltip 
-     * if we can obtain it
-     */
-    String featureType = (String) filteredFeatureChoice.getSelectedItem();
-    final JComboBox<String> attCombo = populateAttributesDropdown(
-            featureType, attNames);
-    JComboBox<Condition> condCombo = new JComboBox<>();
-    JTextField patternField = new JTextField(8);
-
-    /*
-     * action handlers that validate and (if valid) apply changes
-     */
-    ActionListener actionListener = new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        if (attCombo.getSelectedItem() != null)
-        {
-          if (validateFilter(patternField, condCombo))
-          {
-            updateFilter(attCombo, condCombo, patternField, filterIndex);
-            filtersChanged();
-          }
-        }
-      }
-    };
-    ItemListener itemListener = new ItemListener()
-    {
-      @Override
-      public void itemStateChanged(ItemEvent e)
-      {
-        actionListener.actionPerformed(null);
-      }
-    };
-
-    if (attName == null) // the 'add a condition' row
-    {
-      attCombo.setSelectedItem(null);
-    }
-    else
-    {
-      attCombo.setSelectedItem(String.join(COLON, attName));
-    }
-    attCombo.addItemListener(itemListener);
-
-    filterRow.add(attCombo);
-
-    /*
-     * drop-down choice of test condition
-     */
-    for (Condition c : Condition.values())
-    {
-      condCombo.addItem(c);
-    }
-    if (cond != null)
-    {
-      condCombo.setSelectedItem(cond);
-    }
-    condCombo.addItemListener(itemListener);
-    filterRow.add(condCombo);
-
-    /*
-     * pattern to match against
-     */
-    patternField.setText(pattern);
-    patternField.addActionListener(actionListener);
-    patternField.addFocusListener(new FocusAdapter()
-    {
-      @Override
-      public void focusLost(FocusEvent e)
-      {
-        actionListener.actionPerformed(null);
-      }
-    });
-    filterRow.add(patternField);
-
-    /*
-     * add remove button if filter is populated (non-empty pattern)
-     */
-    if (pattern != null && pattern.trim().length() > 0)
-    {
-      // todo: gif for button drawing '-' or 'x'
-      JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
-      removeCondition.setToolTipText(MessageManager
-              .getString("label.delete_row"));
-      removeCondition.addActionListener(new ActionListener()
-      {
-        @Override
-        public void actionPerformed(ActionEvent e)
-        {
-          filters.remove(filterIndex);
-          filtersChanged();
-        }
-      });
-      filterRow.add(removeCondition);
-    }
-
-    return filterRow;
-  }
-
-  /**
-   * A helper method to build the drop-down choice of attributes for a feature.
-   * Where metadata is available with a description for an attribute, that is
-   * added as a tooltip.
-   * 
-   * @param featureType
-   * @param attNames
-   */
-  protected JComboBox<String> populateAttributesDropdown(
-          String featureType, List<String[]> attNames)
-  {
-    List<String> displayNames = new ArrayList<>();
-    List<String> tooltips = new ArrayList<>();
-    FeatureAttributes fa = FeatureAttributes.getInstance();
-    for (String[] attName : attNames)
-    {
-      String desc = fa.getDescription(featureType, attName);
-      if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
-      {
-        desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
-      }
-      displayNames.add(String.join(COLON, attName));
-      tooltips.add(desc == null ? "" : desc);
-    }
-
-    JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
-            displayNames, tooltips);
-    if (attNames.isEmpty())
-    {
-      attCombo.setToolTipText(MessageManager
-              .getString("label.no_attributes"));
-    }
-    return attCombo;
-  }
-
-  /**
-   * Action on any change to feature filtering, namely
-   * <ul>
-   * <li>change of selected attribute</li>
-   * <li>change of selected condition</li>
-   * <li>change of match pattern</li>
-   * <li>removal of a condition</li>
-   * </ul>
-   * The action should be to
-   * <ul>
-   * <li>parse and validate the filters</li>
-   * <li>if valid, update the filter text box</li>
-   * <li>and apply the filters to the viewport</li>
-   * </ul>
-   */
-  protected void filtersChanged()
-  {
-    /*
-     * update the filter conditions for the feature type
-     */
-    String featureType = (String) filteredFeatureChoice.getSelectedItem();
-    boolean anded = andFilters.isSelected();
-    KeyedMatcherSetI combined = new KeyedMatcherSet();
-
-    for (KeyedMatcherI filter : filters)
-    {
-      String pattern = filter.getMatcher().getPattern();
-      if (pattern.trim().length() > 0)
-      {
-        if (anded)
-        {
-          combined.and(filter);
-        }
-        else
-        {
-          combined.or(filter);
-        }
-      }
-    }
-
-    /*
-     * save the filter conditions in the FeatureRenderer
-     * (note this might now be an empty filter with no conditions)
-     */
-    fr.setFeatureFilter(featureType, combined);
-
-    filtersAsText.setText(combined.toString());
-
-    refreshFiltersDisplay();
-
-    af.alignPanel.paintAlignment(true, true);
-  }
-
-  /**
-   * Constructs a filter condition from the given input fields, and replaces the
-   * condition at filterIndex with the new one
-   * 
-   * @param attCombo
-   * @param condCombo
-   * @param valueField
-   * @param filterIndex
-   */
-  protected void updateFilter(JComboBox<String> attCombo,
-          JComboBox<Condition> condCombo, JTextField valueField,
-          int filterIndex)
-  {
-    String attName = (String) attCombo.getSelectedItem();
-    Condition cond = (Condition) condCombo.getSelectedItem();
-    String pattern = valueField.getText();
-    KeyedMatcherI km = new KeyedMatcher(cond, pattern,
-            attName.split(COLON));
-
-    filters.set(filterIndex, km);
+    this.add(settingsPane);
   }
 
   public void fetchDAS_actionPerformed(ActionEvent e)
@@ -1928,68 +1487,25 @@ public class FeatureSettings extends JPanel
             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
   }
 
-  /**
-   * Answers true unless a numeric condition has been selected with a
-   * non-numeric value. Sets the value field to RED with a tooltip if in error.
-   * <p>
-   * If the pattern entered is empty, this method returns false, but does not
-   * mark the field as invalid. This supports selecting an attribute for a new
-   * condition before a match pattern has been entered.
-   * 
-   * @param value
-   * @param condCombo
-   */
-  protected boolean validateFilter(JTextField value,
-          JComboBox<Condition> condCombo)
-  {
-    if (value == null || condCombo == null)
-    {
-      return true; // fields not populated
-    }
-  
-    Condition cond = (Condition) condCombo.getSelectedItem();
-    value.setBackground(Color.white);
-    value.setToolTipText("");
-    String v1 = value.getText().trim();
-    if (v1.length() == 0)
-    {
-      return false;
-    }
-
-    if (cond.isNumeric())
-    {
-      try
-      {
-        Float.valueOf(v1);
-      } catch (NumberFormatException e)
-      {
-        value.setBackground(Color.red);
-        value.setToolTipText(MessageManager
-                .getString("label.numeric_required"));
-        return false;
-      }
-    }
-  
-    return true;
-  }
-
   // ///////////////////////////////////////////////////////////////////////
   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
   // ///////////////////////////////////////////////////////////////////////
   class FeatureTableModel extends AbstractTableModel
   {
-    FeatureTableModel(Object[][] data)
-    {
-      this.data = data;
-    }
 
     private String[] columnNames = {
         MessageManager.getString("label.feature_type"),
         MessageManager.getString("action.colour"),
-        MessageManager.getString("label.display") };
+        MessageManager.getString("label.filter"),
+        MessageManager.getString("label.show") };
 
     private Object[][] data;
 
+    FeatureTableModel(Object[][] data)
+    {
+      this.data = data;
+    }
+
     public Object[][] getData()
     {
       return data;
@@ -2029,10 +1545,14 @@ public class FeatureSettings extends JPanel
       return data[row][col];
     }
 
+    /**
+     * Answers the class of the object in column c of the first row of the table
+     */
     @Override
-    public Class getColumnClass(int c)
+    public Class<?> getColumnClass(int c)
     {
-      return getValueAt(0, c).getClass();
+      Object v = getValueAt(0, c);
+      return v == null ? null : v.getClass();
     }
 
     @Override
@@ -2109,6 +1629,54 @@ public class FeatureSettings extends JPanel
     }
   }
 
+  class FilterRenderer extends JLabel implements TableCellRenderer
+  {
+    javax.swing.border.Border unselectedBorder = null;
+
+    javax.swing.border.Border selectedBorder = null;
+
+    public FilterRenderer()
+    {
+      setOpaque(true); // MUST do this for background to show up.
+      setHorizontalTextPosition(SwingConstants.CENTER);
+      setVerticalTextPosition(SwingConstants.CENTER);
+    }
+
+    @Override
+    public Component getTableCellRendererComponent(JTable tbl,
+            Object filter, boolean isSelected, boolean hasFocus, int row,
+            int column)
+    {
+      KeyedMatcherSetI theFilter = (KeyedMatcherSetI) filter;
+      setOpaque(true);
+      String asText = theFilter.toString();
+      setBackground(tbl.getBackground());
+      this.setText(asText);
+      this.setIcon(null);
+
+      if (isSelected)
+      {
+        if (selectedBorder == null)
+        {
+          selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
+                  tbl.getSelectionBackground());
+        }
+        setBorder(selectedBorder);
+      }
+      else
+      {
+        if (unselectedBorder == null)
+        {
+          unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
+                  tbl.getBackground());
+        }
+        setBorder(unselectedBorder);
+      }
+
+      return this;
+    }
+  }
+
   /**
    * update comp using rendering settings from gcol
    * 
@@ -2196,11 +1764,250 @@ public class FeatureSettings extends JPanel
       }
       else
       {
-        comp.setToolTipText(tt.append(" ").append(comp.getToolTipText())
-                .toString());
+        comp.setToolTipText(
+                tt.append(" ").append(comp.getToolTipText()).toString());
       }
     }
   }
+
+  class ColorEditor extends AbstractCellEditor
+          implements TableCellEditor, ActionListener
+  {
+    FeatureSettings me;
+
+    FeatureColourI currentColor;
+
+    FeatureTypeSettings chooser;
+
+    String type;
+
+    JButton button;
+
+    JColorChooser colorChooser;
+
+    JDialog dialog;
+
+    protected static final String EDIT = "edit";
+
+    int rowSelected = 0;
+
+    public ColorEditor(FeatureSettings me)
+    {
+      this.me = me;
+      // Set up the editor (from the table's point of view),
+      // which is a button.
+      // This button brings up the color chooser dialog,
+      // which is the editor from the user's point of view.
+      button = new JButton();
+      button.setActionCommand(EDIT);
+      button.addActionListener(this);
+      button.setBorderPainted(false);
+      // Set up the dialog that the button brings up.
+      colorChooser = new JColorChooser();
+      dialog = JColorChooser.createDialog(button,
+              MessageManager.getString("label.select_colour"), true, // modal
+              colorChooser, this, // OK button handler
+              null); // no CANCEL button handler
+    }
+
+    /**
+     * Handles events from the editor button and from the dialog's OK button.
+     */
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      // todo test e.getSource() instead here
+      if (EDIT.equals(e.getActionCommand()))
+      {
+        // The user has clicked the cell, so
+        // bring up the dialog.
+        if (currentColor.isSimpleColour())
+        {
+          // bring up simple color chooser
+          button.setBackground(currentColor.getColour());
+          colorChooser.setColor(currentColor.getColour());
+          dialog.setVisible(true);
+        }
+        else
+        {
+          // bring up graduated chooser.
+          chooser = new FeatureTypeSettings(me.fr, type);
+          chooser.setRequestFocusEnabled(true);
+          chooser.requestFocus();
+          chooser.addActionListener(this);
+          chooser.showTab(true);
+        }
+        // Make the renderer reappear.
+        fireEditingStopped();
+
+      }
+      else
+      {
+        if (currentColor.isSimpleColour())
+        {
+          /*
+           * read off colour picked in colour chooser after OK pressed
+           */
+          currentColor = new FeatureColour(colorChooser.getColor());
+          me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
+        }
+        else
+        {
+          /*
+           * after OK in variable colour dialog, any changes to colour 
+           * (or filters!) are already set in FeatureRenderer, so just
+           * update table data without triggering updateFeatureRenderer
+           */
+          currentColor = fr.getFeatureColours().get(type);
+          KeyedMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
+          if (currentFilter == null)
+          {
+            currentFilter = new KeyedMatcherSet();
+          }
+          Object[] data = ((FeatureTableModel) table.getModel())
+                  .getData()[rowSelected];
+          data[COLOUR_COLUMN] = currentColor;
+          data[FILTER_COLUMN] = currentFilter;
+        }
+        fireEditingStopped();
+        me.table.validate();
+      }
+    }
+
+    // Implement the one CellEditor method that AbstractCellEditor doesn't.
+    @Override
+    public Object getCellEditorValue()
+    {
+      return currentColor;
+    }
+
+    // Implement the one method defined by TableCellEditor.
+    @Override
+    public Component getTableCellEditorComponent(JTable theTable, Object value,
+            boolean isSelected, int row, int column)
+    {
+      currentColor = (FeatureColourI) value;
+      this.rowSelected = row;
+      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      button.setOpaque(true);
+      button.setBackground(me.getBackground());
+      if (!currentColor.isSimpleColour())
+      {
+        JLabel btn = new JLabel();
+        btn.setSize(button.getSize());
+        FeatureSettings.renderGraduatedColor(btn, currentColor);
+        button.setBackground(btn.getBackground());
+        button.setIcon(btn.getIcon());
+        button.setText(btn.getText());
+      }
+      else
+      {
+        button.setText("");
+        button.setIcon(null);
+        button.setBackground(currentColor.getColour());
+      }
+      return button;
+    }
+  }
+
+  /**
+   * The cell editor for the Filter column. It displays the text of any filters
+   * for the feature type in that row (in full as a tooltip, possible abbreviated
+   * as display text). On click in the cell, opens the Feature Display Settings
+   * dialog at the Filters tab.
+   */
+  class FilterEditor extends AbstractCellEditor
+          implements TableCellEditor, ActionListener
+  {
+    FeatureSettings me;
+
+    KeyedMatcherSetI currentFilter;
+
+    Point lastLocation;
+
+    String type;
+
+    JButton button;
+
+    protected static final String EDIT = "edit";
+
+    int rowSelected = 0;
+
+    public FilterEditor(FeatureSettings me)
+    {
+      this.me = me;
+      button = new JButton();
+      button.setActionCommand(EDIT);
+      button.addActionListener(this);
+      button.setBorderPainted(false);
+    }
+
+    /**
+     * Handles events from the editor button
+     */
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      if (button == e.getSource())
+      {
+        FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
+        chooser.addActionListener(this);
+        chooser.setRequestFocusEnabled(true);
+        chooser.requestFocus();
+        if (lastLocation != null)
+        {
+          // todo open at its last position on screen
+          chooser.setBounds(lastLocation.x, lastLocation.y,
+                  chooser.getWidth(), chooser.getHeight());
+          chooser.validate();
+        }
+        chooser.showTab(false);
+        fireEditingStopped();
+      }
+      else if (e.getSource() instanceof Component)
+      {
+
+        /*
+         * after OK in variable colour dialog, any changes to filter
+         * (or colours!) are already set in FeatureRenderer, so just
+         * update table data without triggering updateFeatureRenderer
+         */
+        FeatureColourI currentColor = fr.getFeatureColours().get(type);
+        currentFilter = me.fr.getFeatureFilter(type);
+        if (currentFilter == null)
+        {
+          currentFilter = new KeyedMatcherSet();
+        }
+        Object[] data = ((FeatureTableModel) table.getModel())
+                .getData()[rowSelected];
+        data[COLOUR_COLUMN] = currentColor;
+        data[FILTER_COLUMN] = currentFilter;
+        fireEditingStopped();
+        me.table.validate();
+      }
+    }
+
+    @Override
+    public Object getCellEditorValue()
+    {
+      return currentFilter;
+    }
+
+    @Override
+    public Component getTableCellEditorComponent(JTable theTable, Object value,
+            boolean isSelected, int row, int column)
+    {
+      currentFilter = (KeyedMatcherSetI) value;
+      this.rowSelected = row;
+      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      button.setOpaque(true);
+      button.setBackground(me.getBackground());
+      button.setText(currentFilter.toString());
+      button.setToolTipText(currentFilter.toString());
+      button.setIcon(null);
+      return button;
+    }
+  }
 }
 
 class FeatureIcon implements Icon
@@ -2284,125 +2091,3 @@ class FeatureIcon implements Icon
     }
   }
 }
-
-class ColorEditor extends AbstractCellEditor
-        implements TableCellEditor, ActionListener
-{
-  FeatureSettings me;
-
-  FeatureColourI currentColor;
-
-  FeatureColourChooser chooser;
-
-  String type;
-
-  JButton button;
-
-  JColorChooser colorChooser;
-
-  JDialog dialog;
-
-  protected static final String EDIT = "edit";
-
-  int selectedRow = 0;
-
-  public ColorEditor(FeatureSettings me)
-  {
-    this.me = me;
-    // Set up the editor (from the table's point of view),
-    // which is a button.
-    // This button brings up the color chooser dialog,
-    // which is the editor from the user's point of view.
-    button = new JButton();
-    button.setActionCommand(EDIT);
-    button.addActionListener(this);
-    button.setBorderPainted(false);
-    // Set up the dialog that the button brings up.
-    colorChooser = new JColorChooser();
-    dialog = JColorChooser.createDialog(button,
-            MessageManager.getString("label.select_new_colour"), true, // modal
-            colorChooser, this, // OK button handler
-            null); // no CANCEL button handler
-  }
-
-  /**
-   * Handles events from the editor button and from the dialog's OK button.
-   */
-  @Override
-  public void actionPerformed(ActionEvent e)
-  {
-
-    if (EDIT.equals(e.getActionCommand()))
-    {
-      // The user has clicked the cell, so
-      // bring up the dialog.
-      if (currentColor.isSimpleColour())
-      {
-        // bring up simple color chooser
-        button.setBackground(currentColor.getColour());
-        colorChooser.setColor(currentColor.getColour());
-        dialog.setVisible(true);
-      }
-      else
-      {
-        // bring up graduated chooser.
-        chooser = new FeatureColourChooser(me.fr, type);
-        chooser.setRequestFocusEnabled(true);
-        chooser.requestFocus();
-        chooser.addActionListener(this);
-      }
-      // Make the renderer reappear.
-      fireEditingStopped();
-
-    }
-    else
-    { // User pressed dialog's "OK" button.
-      if (currentColor.isSimpleColour())
-      {
-        currentColor = new FeatureColour(colorChooser.getColor());
-      }
-      else
-      {
-        currentColor = chooser.getLastColour();
-      }
-      me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
-      fireEditingStopped();
-      me.table.validate();
-    }
-  }
-
-  // Implement the one CellEditor method that AbstractCellEditor doesn't.
-  @Override
-  public Object getCellEditorValue()
-  {
-    return currentColor;
-  }
-
-  // Implement the one method defined by TableCellEditor.
-  @Override
-  public Component getTableCellEditorComponent(JTable table, Object value,
-          boolean isSelected, int row, int column)
-  {
-    currentColor = (FeatureColourI) value;
-    this.selectedRow = row;
-    type = me.table.getValueAt(row, 0).toString();
-    button.setOpaque(true);
-    button.setBackground(me.getBackground());
-    if (!currentColor.isSimpleColour())
-    {
-      JLabel btn = new JLabel();
-      btn.setSize(button.getSize());
-      FeatureSettings.renderGraduatedColor(btn, currentColor);
-      button.setBackground(btn.getBackground());
-      button.setIcon(btn.getIcon());
-      button.setText(btn.getText());
-    }
-    else
-    {
-      button.setText("");
-      button.setIcon(null);
-      button.setBackground(currentColor.getColour());
-    }
-    return button;
-  }
-}
diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java
new file mode 100644 (file)
index 0000000..e280091
--- /dev/null
@@ -0,0 +1,1548 @@
+/*
+ * 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.AlignmentViewPanel;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.features.FeatureAttributes;
+import jalview.datamodel.features.FeatureAttributes.Datatype;
+import jalview.schemes.FeatureColour;
+import jalview.util.ColorUtils;
+import jalview.util.MessageManager;
+import jalview.util.matcher.Condition;
+import jalview.util.matcher.KeyedMatcher;
+import jalview.util.matcher.KeyedMatcherI;
+import jalview.util.matcher.KeyedMatcherSet;
+import jalview.util.matcher.KeyedMatcherSetI;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.LayoutManager;
+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.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSlider;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.LineBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.basic.BasicArrowButton;
+
+/**
+ * A dialog where the user can configure colour scheme, and any filters, for one
+ * feature type
+ * <p>
+ * (Was FeatureColourChooser prior to Jalview 1.11, renamed with the addition of
+ * filter options)
+ */
+public class FeatureTypeSettings extends JalviewDialog
+{
+  private static final int RADIO_WIDTH = 130;
+
+  private static final String COLON = ":";
+
+  private static final int MAX_TOOLTIP_LENGTH = 50;
+
+  private static final int NO_COLOUR_OPTION = 0;
+
+  private static final int MIN_COLOUR_OPTION = 1;
+
+  private static final int MAX_COLOUR_OPTION = 2;
+
+  private static final int ABOVE_THRESHOLD_OPTION = 1;
+
+  private static final int BELOW_THRESHOLD_OPTION = 2;
+
+  /*
+   * FeatureRenderer holds colour scheme and filters for feature types
+   */
+  private final FeatureRenderer fr; // todo refactor to allow interface type here
+
+  /*
+   * the view panel to update when settings change
+   */
+  private final AlignmentViewPanel ap;
+
+  private final String featureType;
+
+  /*
+   * the colour and filters to reset to on Cancel
+   */
+  private final FeatureColourI originalColour;
+
+  private final KeyedMatcherSetI originalFilter;
+
+  /*
+   * set flag to true when setting values programmatically,
+   * to avoid invocation of action handlers
+   */
+  private boolean adjusting = false;
+
+  private float min;
+
+  private float max;
+
+  private float scaleFactor;
+
+  /*
+   * radio button group, to select what to colour by:
+   * simple colour, by category (text), or graduated
+   */
+  private JRadioButton simpleColour = new JRadioButton();
+
+  private JRadioButton byCategory = new JRadioButton();
+
+  private JRadioButton graduatedColour = new JRadioButton();
+
+  private JPanel singleColour = new JPanel();
+
+  private JPanel minColour = new JPanel();
+
+  private JPanel maxColour = new JPanel();
+
+  private JComboBox<String> threshold = new JComboBox<>();
+
+  private JSlider slider = new JSlider();
+
+  private JTextField thresholdValue = new JTextField(20);
+
+  private JCheckBox thresholdIsMin = new JCheckBox();
+
+  private GraphLine threshline;
+
+  private ActionListener featureSettings = null;
+
+  private ActionListener changeColourAction;
+
+  /*
+   * choice of option for 'colour for no value'
+   */
+  private JComboBox<String> noValueCombo;
+
+  /*
+   * choice of what to colour by text (Label or attribute)
+   */
+  private JComboBox<String> colourByTextCombo;
+
+  /*
+   * choice of what to colour by range (Score or attribute)
+   */
+  private JComboBox<String> colourByRangeCombo;
+
+  private JRadioButton andFilters;
+
+  private JRadioButton orFilters;
+
+  /*
+   * filters for the currently selected feature type
+   */
+  private List<KeyedMatcherI> filters;
+
+  // set white normally, black to debug layout
+  private Color debugBorderColour = Color.white;
+
+  private JPanel chooseFiltersPanel;
+
+  private JTabbedPane tabbedPane;
+
+  /**
+   * Constructor
+   * 
+   * @param frender
+   * @param theType
+   */
+  public FeatureTypeSettings(FeatureRenderer frender, String theType)
+  {
+    this(frender, false, theType);
+  }
+
+  /**
+   * Constructor, with option to make a blocking dialog (has to complete in the
+   * AWT event queue thread). Currently this option is always set to false.
+   * 
+   * @param frender
+   * @param blocking
+   * @param theType
+   */
+  FeatureTypeSettings(FeatureRenderer frender, boolean blocking,
+          String theType)
+  {
+    this.fr = frender;
+    this.featureType = theType;
+    ap = fr.ap;
+    originalFilter = fr.getFeatureFilter(theType);
+    originalColour = fr.getFeatureColours().get(theType);
+
+    adjusting = true;
+
+    try
+    {
+      initialise();
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+      return;
+    }
+
+    updateColoursTab();
+
+    updateFiltersTab();
+
+    adjusting = false;
+
+    colourChanged(false);
+
+    String title = MessageManager
+            .formatMessage("label.display_settings_for", new String[]
+            { theType });
+    initDialogFrame(this, true, blocking, title, 600, 360);
+
+    waitForInput();
+  }
+
+  /**
+   * Configures the widgets on the Colours tab according to the current feature
+   * colour scheme
+   */
+  private void updateColoursTab()
+  {
+    FeatureColourI fc = fr.getFeatureColours().get(featureType);
+
+    /*
+     * suppress action handling while updating values programmatically
+     */
+    adjusting = true;
+    try
+    {
+      /*
+       * single colour
+       */
+      if (fc.isSimpleColour())
+      {
+        simpleColour.setSelected(true);
+        singleColour.setBackground(fc.getColour());
+        singleColour.setForeground(fc.getColour());
+      }
+
+      /*
+       * colour by text (Label or attribute text)
+       */
+      if (fc.isColourByLabel())
+      {
+        byCategory.setSelected(true);
+        colourByTextCombo.setEnabled(colourByTextCombo.getItemCount() > 1);
+        if (fc.isColourByAttribute())
+        {
+          String[] attributeName = fc.getAttributeName();
+          colourByTextCombo
+                  .setSelectedItem(toAttributeDisplayName(attributeName));
+        }
+        else
+        {
+          colourByTextCombo
+                  .setSelectedItem(MessageManager.getString("label.label"));
+        }
+      }
+      else
+      {
+        colourByTextCombo.setEnabled(false);
+      }
+
+      if (!fc.isGraduatedColour())
+      {
+        colourByRangeCombo.setEnabled(false);
+        minColour.setEnabled(false);
+        maxColour.setEnabled(false);
+        noValueCombo.setEnabled(false);
+        threshold.setEnabled(false);
+        slider.setEnabled(false);
+        thresholdValue.setEnabled(false);
+        thresholdIsMin.setEnabled(false);
+        return;
+      }
+
+      /*
+       * Graduated colour, by score or attribute value range
+       */
+      graduatedColour.setSelected(true);
+      colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1);
+      minColour.setEnabled(true);
+      maxColour.setEnabled(true);
+      noValueCombo.setEnabled(true);
+      threshold.setEnabled(true);
+      minColour.setBackground(fc.getMinColour());
+      maxColour.setBackground(fc.getMaxColour());
+
+      if (fc.isColourByAttribute())
+      {
+        String[] attributeName = fc.getAttributeName();
+        colourByRangeCombo
+                .setSelectedItem(toAttributeDisplayName(attributeName));
+      }
+      else
+      {
+        colourByRangeCombo
+                .setSelectedItem(MessageManager.getString("label.score"));
+      }
+      Color noColour = fc.getNoColour();
+      if (noColour == null)
+      {
+        noValueCombo.setSelectedIndex(NO_COLOUR_OPTION);
+      }
+      else if (noColour.equals(fc.getMinColour()))
+      {
+        noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION);
+      }
+      else if (noColour.equals(fc.getMaxColour()))
+      {
+        noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION);
+      }
+
+      /*
+       * update min-max scaling if there is a range to work with,
+       * else disable the widgets (this shouldn't happen if only 
+       * valid options are offered in the combo box)
+       */
+      scaleFactor = (max == min) ? 1f : 100f / (max - min);
+      float range = (max - min) * scaleFactor;
+      slider.setMinimum((int) (min * scaleFactor));
+      slider.setMaximum((int) (max * scaleFactor));
+      slider.setMajorTickSpacing((int) (range / 10f));
+
+      threshline = new GraphLine((max - min) / 2f, "Threshold",
+              Color.black);
+      threshline.value = fc.getThreshold();
+
+      if (fc.hasThreshold())
+      {
+        threshold.setSelectedIndex(
+                fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION
+                        : BELOW_THRESHOLD_OPTION);
+        slider.setEnabled(true);
+        slider.setValue((int) (fc.getThreshold() * scaleFactor));
+        thresholdValue.setText(String.valueOf(getRoundedSliderValue()));
+        thresholdValue.setEnabled(true);
+        thresholdIsMin.setEnabled(true);
+      }
+      else
+      {
+        slider.setEnabled(false);
+        thresholdValue.setEnabled(false);
+        thresholdIsMin.setEnabled(false);
+      }
+      thresholdIsMin.setSelected(!fc.isAutoScaled());
+    } finally
+    {
+      adjusting = false;
+    }
+  }
+
+  /**
+   * Configures the initial layout
+   */
+  private void initialise()
+  {
+    this.setLayout(new BorderLayout());
+    tabbedPane = new JTabbedPane();
+    this.add(tabbedPane, BorderLayout.CENTER);
+
+    /*
+     * an ActionListener that applies colour changes
+     */
+    changeColourAction = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        colourChanged(true);
+      }
+    };
+
+    /*
+     * first tab: colour options
+     */
+    JPanel coloursPanel = initialiseColoursPanel();
+    tabbedPane.addTab(MessageManager.getString("action.colour"),
+            coloursPanel);
+
+    /*
+     * second tab: filter options
+     */
+    JPanel filtersPanel = initialiseFiltersPanel();
+    tabbedPane.addTab(MessageManager.getString("label.filters"),
+            filtersPanel);
+
+    JPanel okCancelPanel = initialiseOkCancelPanel();
+
+    this.add(okCancelPanel, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Updates the min-max range if Colour By selected item is Score, or an
+   * attribute, with a min-max range
+   */
+  protected void updateMinMax()
+  {
+    if (!graduatedColour.isSelected())
+    {
+      return;
+    }
+
+    float[] minMax = null;
+    String colourBy = (String) colourByRangeCombo.getSelectedItem();
+    if (MessageManager.getString("label.score").equals(colourBy))
+    {
+      minMax = fr.getMinMax().get(featureType)[0];
+    }
+    else
+    {
+      // colour by attribute range
+      String[] attNames = fromAttributeDisplayName(colourBy);
+      minMax = FeatureAttributes.getInstance().getMinMax(featureType,
+              attNames);
+    }
+
+    if (minMax != null)
+    {
+      min = minMax[0];
+      max = minMax[1];
+    }
+  }
+
+  /**
+   * Lay out fields for graduated colour (by score or attribute value)
+   * 
+   * @return
+   */
+  private JPanel initialiseGraduatedColourPanel()
+  {
+    JPanel graduatedColourPanel = new JPanel();
+    graduatedColourPanel.setLayout(
+            new BoxLayout(graduatedColourPanel, BoxLayout.Y_AXIS));
+    JvSwingUtils.createTitledBorder(graduatedColourPanel,
+            MessageManager.getString("label.graduated_colour"), true);
+    graduatedColourPanel.setBackground(Color.white);
+
+    /*
+     * first row: graduated colour radio button, score/attribute drop-down
+     */
+    JPanel graduatedChoicePanel = new JPanel(
+            new FlowLayout(FlowLayout.LEFT));
+    graduatedChoicePanel.setBackground(Color.white);
+    graduatedColour = new JRadioButton(
+            MessageManager.getString("label.by_range_of") + COLON);
+    graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
+    graduatedColour.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        if (graduatedColour.isSelected())
+        {
+          colourChanged(true);
+        }
+      }
+    });
+    graduatedChoicePanel.add(graduatedColour);
+
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(featureType);
+    colourByRangeCombo = populateAttributesDropdown(attNames, true, false);
+    colourByRangeCombo.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        colourChanged(true);
+      }
+    });
+
+    /*
+     * disable graduated colour option if no range found
+     */
+    graduatedColour.setEnabled(colourByRangeCombo.getItemCount() > 0);
+
+    graduatedChoicePanel.add(colourByRangeCombo);
+    graduatedColourPanel.add(graduatedChoicePanel);
+
+    /*
+     * second row - min/max/no colours
+     */
+    JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    colourRangePanel.setBackground(Color.white);
+    graduatedColourPanel.add(colourRangePanel);
+
+    minColour.setFont(JvSwingUtils.getLabelFont());
+    minColour.setBorder(BorderFactory.createLineBorder(Color.black));
+    minColour.setPreferredSize(new Dimension(40, 20));
+    minColour.setToolTipText(MessageManager.getString("label.min_colour"));
+    minColour.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
+      {
+        if (minColour.isEnabled())
+        {
+          showColourChooser(minColour, "label.select_colour_minimum_value");
+        }
+      }
+    });
+
+    maxColour.setFont(JvSwingUtils.getLabelFont());
+    maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
+    maxColour.setPreferredSize(new Dimension(40, 20));
+    maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
+    maxColour.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
+      {
+        if (maxColour.isEnabled())
+        {
+          showColourChooser(maxColour, "label.select_colour_maximum_value");
+        }
+      }
+    });
+    maxColour.setBorder(new LineBorder(Color.black));
+
+    /*
+     * default max colour to current colour (if a plain colour),
+     * or to Black if colour by label;  make min colour a pale
+     * version of max colour
+     */
+    FeatureColourI fc = fr.getFeatureColours().get(featureType);
+    Color bg = fc.isSimpleColour() ? fc.getColour() : Color.BLACK;
+    maxColour.setBackground(bg);
+    minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f));
+
+    noValueCombo = new JComboBox<>();
+    noValueCombo.addItem(MessageManager.getString("label.no_colour"));
+    noValueCombo.addItem(MessageManager.getString("label.min_colour"));
+    noValueCombo.addItem(MessageManager.getString("label.max_colour"));
+    noValueCombo.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        colourChanged(true);
+      }
+    });
+
+    JLabel minText = new JLabel(
+            MessageManager.getString("label.min_value") + COLON);
+    minText.setFont(JvSwingUtils.getLabelFont());
+    JLabel maxText = new JLabel(
+            MessageManager.getString("label.max_value") + COLON);
+    maxText.setFont(JvSwingUtils.getLabelFont());
+    JLabel noText = new JLabel(
+            MessageManager.getString("label.no_value") + COLON);
+    noText.setFont(JvSwingUtils.getLabelFont());
+
+    colourRangePanel.add(minText);
+    colourRangePanel.add(minColour);
+    colourRangePanel.add(maxText);
+    colourRangePanel.add(maxColour);
+    colourRangePanel.add(noText);
+    colourRangePanel.add(noValueCombo);
+
+    /*
+     * third row - threshold options and value
+     */
+    JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    thresholdPanel.setBackground(Color.white);
+    graduatedColourPanel.add(thresholdPanel);
+
+    threshold.addActionListener(changeColourAction);
+    threshold.setToolTipText(MessageManager
+            .getString("label.threshold_feature_display_by_score"));
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_no_threshold")); // index 0
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_above_threshold")); // index 1
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_below_threshold")); // index 2
+
+    thresholdValue.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        thresholdValue_actionPerformed();
+      }
+    });
+    thresholdValue.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent 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));
+    slider.setToolTipText(
+            MessageManager.getString("label.adjust_threshold"));
+
+    slider.addChangeListener(new ChangeListener()
+    {
+      @Override
+      public void stateChanged(ChangeEvent evt)
+      {
+        if (!adjusting)
+        {
+          thresholdValue
+                  .setText(String.valueOf(slider.getValue() / scaleFactor));
+          sliderValueChanged();
+        }
+      }
+    });
+    slider.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mouseReleased(MouseEvent evt)
+      {
+        /*
+         * only update Overview and/or structure colouring
+         * when threshold slider drag ends (mouse up)
+         */
+        if (ap != null)
+        {
+          ap.paintAlignment(true, true);
+        }
+      }
+    });
+
+    thresholdValue.setEnabled(false);
+    thresholdValue.setColumns(7);
+
+    thresholdPanel.add(threshold);
+    thresholdPanel.add(slider);
+    thresholdPanel.add(thresholdValue);
+
+    thresholdIsMin.setBackground(Color.white);
+    thresholdIsMin
+            .setText(MessageManager.getString("label.threshold_minmax"));
+    thresholdIsMin.setToolTipText(MessageManager
+            .getString("label.toggle_absolute_relative_display_threshold"));
+    thresholdIsMin.addActionListener(changeColourAction);
+    thresholdPanel.add(thresholdIsMin);
+
+    return graduatedColourPanel;
+  }
+
+  /**
+   * Lay out OK and Cancel buttons
+   * 
+   * @return
+   */
+  private JPanel initialiseOkCancelPanel()
+  {
+    JPanel okCancelPanel = new JPanel();
+    // okCancelPanel.setBackground(Color.white);
+    okCancelPanel.add(ok);
+    okCancelPanel.add(cancel);
+    return okCancelPanel;
+  }
+
+  /**
+   * Lay out Colour options panel, containing
+   * <ul>
+   * <li>plain colour, with colour picker</li>
+   * <li>colour by text, with choice of Label or other attribute</li>
+   * <li>colour by range, of score or other attribute, when available</li>
+   * </ul>
+   * 
+   * @return
+   */
+  private JPanel initialiseColoursPanel()
+  {
+    JPanel colourByPanel = new JPanel();
+    colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS));
+
+    /*
+     * simple colour radio button and colour picker
+     */
+    JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    simpleColourPanel.setBackground(Color.white);
+    JvSwingUtils.createTitledBorder(simpleColourPanel,
+            MessageManager.getString("label.simple"), true);
+    colourByPanel.add(simpleColourPanel);
+
+    simpleColour = new JRadioButton(
+            MessageManager.getString("label.simple_colour"));
+    simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
+    simpleColour.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        if (simpleColour.isSelected() && !adjusting)
+        {
+          showColourChooser(singleColour, "label.select_colour");
+        }
+      }
+
+    });
+    
+    singleColour.setFont(JvSwingUtils.getLabelFont());
+    singleColour.setBorder(BorderFactory.createLineBorder(Color.black));
+    singleColour.setPreferredSize(new Dimension(40, 20));
+    singleColour.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
+      {
+        if (simpleColour.isSelected())
+        {
+          showColourChooser(singleColour, "label.select_colour");
+        }
+      }
+    });
+    simpleColourPanel.add(simpleColour); // radio button
+    simpleColourPanel.add(singleColour); // colour picker button
+
+    /*
+     * colour by text (category) radio button and drop-down choice list
+     */
+    JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    byTextPanel.setBackground(Color.white);
+    JvSwingUtils.createTitledBorder(byTextPanel,
+            MessageManager.getString("label.colour_by_text"), true);
+    colourByPanel.add(byTextPanel);
+    byCategory = new JRadioButton(
+            MessageManager.getString("label.by_text_of") + COLON);
+    byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
+    byCategory.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        if (byCategory.isSelected())
+        {
+          colourChanged(true);
+        }
+      }
+    });
+    byTextPanel.add(byCategory);
+
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(featureType);
+    colourByTextCombo = populateAttributesDropdown(attNames, false, true);
+    colourByTextCombo.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        colourChanged(true);
+      }
+    });
+    byTextPanel.add(colourByTextCombo);
+
+    /*
+     * graduated colour panel
+     */
+    JPanel graduatedColourPanel = initialiseGraduatedColourPanel();
+    colourByPanel.add(graduatedColourPanel);
+
+    /*
+     * 3 radio buttons select between simple colour, 
+     * by category (text), or graduated
+     */
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(simpleColour);
+    bg.add(byCategory);
+    bg.add(graduatedColour);
+
+    return colourByPanel;
+  }
+
+  private void showColourChooser(JPanel colourPanel, String key)
+  {
+    Color col = JColorChooser.showDialog(this,
+            MessageManager.getString(key), colourPanel.getBackground());
+    if (col != null)
+    {
+      colourPanel.setBackground(col);
+      colourPanel.setForeground(col);
+    }
+    colourPanel.repaint();
+    colourChanged(true);
+  }
+
+  /**
+   * Constructs and sets the selected colour options as the colour for the feature
+   * type, and repaints the alignment, and optionally the Overview and/or
+   * structure viewer if open
+   * 
+   * @param updateStructsAndOverview
+   */
+  void colourChanged(boolean updateStructsAndOverview)
+  {
+    if (adjusting)
+    {
+      /*
+       * ignore action handlers while setting values programmatically
+       */
+      return;
+    }
+
+    /*
+     * ensure min-max range is for the latest choice of 
+     * 'graduated colour by'
+     */
+    updateMinMax();
+
+    FeatureColourI acg = makeColourFromInputs();
+
+    /*
+     * save the colour, and repaint stuff
+     */
+    fr.setColour(featureType, acg);
+    ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
+
+    updateColoursTab();
+  }
+
+  /**
+   * Converts the input values into an instance of FeatureColour
+   * 
+   * @return
+   */
+  private FeatureColourI makeColourFromInputs()
+  {
+    /*
+     * easiest case - a single colour
+     */
+    if (simpleColour.isSelected())
+    {
+      return new FeatureColour(singleColour.getBackground());
+    }
+
+    /*
+     * next easiest case - colour by Label, or attribute text
+     */
+    if (byCategory.isSelected())
+    {
+      Color c = this.getBackground();
+      FeatureColourI fc = new FeatureColour(c, c, null, 0f, 0f);
+      fc.setColourByLabel(true);
+      String byWhat = (String) colourByTextCombo.getSelectedItem();
+      if (!MessageManager.getString("label.label").equals(byWhat))
+      {
+        fc.setAttributeName(fromAttributeDisplayName(byWhat));
+      }
+      return fc;
+    }
+
+    /*
+     * remaining case - graduated colour by score, or attribute value
+     */
+    Color noColour = null;
+    if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION)
+    {
+      noColour = minColour.getBackground();
+    }
+    else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION)
+    {
+      noColour = maxColour.getBackground();
+    }
+
+    float thresh = 0f;
+    try
+    {
+      thresh = Float.valueOf(thresholdValue.getText());
+    } catch (NumberFormatException e)
+    {
+      // invalid inputs are already handled on entry
+    }
+
+    /*
+     * min-max range is to (or from) threshold value if 
+     * 'threshold is min/max' is selected 
+     */
+    float minValue = min;
+    float maxValue = max;
+    final int thresholdOption = threshold.getSelectedIndex();
+    if (thresholdIsMin.isSelected()
+            && thresholdOption == ABOVE_THRESHOLD_OPTION)
+    {
+      minValue = thresh;
+    }
+    if (thresholdIsMin.isSelected()
+            && thresholdOption == BELOW_THRESHOLD_OPTION)
+    {
+      maxValue = thresh;
+    }
+
+    /*
+     * make the graduated colour
+     */
+    FeatureColourI fc = new FeatureColour(minColour.getBackground(),
+            maxColour.getBackground(), noColour, minValue, maxValue);
+
+    /*
+     * set attribute to colour by if selected
+     */
+    String byWhat = (String) colourByRangeCombo.getSelectedItem();
+    if (!MessageManager.getString("label.score").equals(byWhat))
+    {
+      fc.setAttributeName(fromAttributeDisplayName(byWhat));
+    }
+
+    /*
+     * set threshold options and 'autoscaled' which is
+     * false if 'threshold is min/max' is selected
+     * else true (colour range is on actual range of values)
+     */
+    fc.setThreshold(thresh);
+    fc.setAutoScaled(!thresholdIsMin.isSelected());
+    fc.setAboveThreshold(thresholdOption == ABOVE_THRESHOLD_OPTION);
+    fc.setBelowThreshold(thresholdOption == BELOW_THRESHOLD_OPTION);
+
+    if (threshline == null)
+    {
+      /*
+       * todo not yet implemented: visual indication of feature threshold
+       */
+      threshline = new GraphLine((max - min) / 2f, "Threshold",
+              Color.black);
+    }
+
+    return fc;
+  }
+
+  /**
+   * A helper method that converts a 'compound' attribute name from its display
+   * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" }
+   * 
+   * @param attribute
+   * @return
+   */
+  private String[] fromAttributeDisplayName(String attribute)
+  {
+    return attribute == null ? null : attribute.split(COLON);
+  }
+
+  /**
+   * A helper method that converts a 'compound' attribute name to its display
+   * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" }
+   * 
+   * @param attName
+   * @return
+   */
+  private String toAttributeDisplayName(String[] attName)
+  {
+    return attName == null ? "" : String.join(COLON, attName);
+  }
+
+  @Override
+  protected void raiseClosed()
+  {
+    if (this.featureSettings != null)
+    {
+      featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
+    }
+  }
+
+  /**
+   * Action on OK is just to dismiss the dialog - any changes have already been
+   * applied
+   */
+  @Override
+  public void okPressed()
+  {
+  }
+
+  /**
+   * Action on Cancel is to restore colour scheme and filters as they were when
+   * the dialog was opened
+   */
+  @Override
+  public void cancelPressed()
+  {
+    fr.setColour(featureType, originalColour);
+    fr.setFeatureFilter(featureType, originalFilter);
+    ap.paintAlignment(true, true);
+  }
+
+  /**
+   * Action on text entry of a threshold value
+   */
+  protected void thresholdValue_actionPerformed()
+  {
+    try
+    {
+      adjusting = true;
+      float f = Float.parseFloat(thresholdValue.getText());
+      slider.setValue((int) (f * scaleFactor));
+      threshline.value = f;
+      thresholdValue.setBackground(Color.white); // ok
+
+      /*
+       * force repaint of any Overview window or structure
+       */
+      ap.paintAlignment(true, true);
+    } catch (NumberFormatException ex)
+    {
+      thresholdValue.setBackground(Color.red); // not ok
+    } finally
+    {
+      adjusting = false;
+    }
+  }
+
+  /**
+   * Action on change of threshold slider value. This may be done interactively
+   * (by moving the slider), or programmatically (to update the slider after
+   * manual input of a threshold value).
+   */
+  protected void sliderValueChanged()
+  {
+    threshline.value = getRoundedSliderValue();
+
+    /*
+     * repaint alignment, but not Overview or structure,
+     * to avoid overload while dragging the slider
+     */
+    colourChanged(false);
+  }
+
+  /**
+   * Converts the slider value to its absolute value by dividing by the
+   * scaleFactor. Rounding errors are squashed by forcing min/max of slider range
+   * to the actual min/max of feature score range
+   * 
+   * @return
+   */
+  private float getRoundedSliderValue()
+  {
+    int value = slider.getValue();
+    float f = value == slider.getMaximum() ? max
+            : (value == slider.getMinimum() ? min : value / scaleFactor);
+    return f;
+  }
+
+  void addActionListener(ActionListener listener)
+  {
+    if (featureSettings != null)
+    {
+      System.err.println(
+              "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
+    }
+    featureSettings = listener;
+  }
+
+  /**
+   * A helper method to build the drop-down choice of attributes for a feature. If
+   * 'withRange' is true, then Score, and any attributes with a min-max range, are
+   * added. If 'withText' is true, Label and any known attributes are added. This
+   * allows 'categorical numerical' attributes e.g. codon position to be coloured
+   * by text.
+   * <p>
+   * Where metadata is available with a description for an attribute, that is
+   * added as a tooltip.
+   * <p>
+   * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ",
+   * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele.
+   * <p>
+   * This method does not add any ActionListener to the JComboBox.
+   * 
+   * @param attNames
+   * @param withRange
+   * @param withText
+   */
+  protected JComboBox<String> populateAttributesDropdown(
+          List<String[]> attNames, boolean withRange, boolean withText)
+  {
+    List<String> displayAtts = new ArrayList<>();
+    List<String> tooltips = new ArrayList<>();
+
+    if (withText)
+    {
+      displayAtts.add(MessageManager.getString("label.label"));
+      tooltips.add(MessageManager.getString("label.description"));
+    }
+    if (withRange)
+    {
+      float[][] minMax = fr.getMinMax().get(featureType);
+      if (minMax != null && minMax[0][0] != minMax[0][1])
+      {
+        displayAtts.add(MessageManager.getString("label.score"));
+        tooltips.add(MessageManager.getString("label.score"));
+      }
+    }
+
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    for (String[] attName : attNames)
+    {
+      float[] minMax = fa.getMinMax(featureType, attName);
+      boolean hasRange = minMax != null && minMax[0] != minMax[1];
+      if (!withText && !hasRange)
+      {
+        continue;
+      }
+      displayAtts.add(toAttributeDisplayName(attName));
+      String desc = fa.getDescription(featureType, attName);
+      if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
+      {
+        desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
+      }
+      tooltips.add(desc == null ? "" : desc);
+    }
+
+    JComboBox<String> attCombo = JvSwingUtils
+            .buildComboWithTooltips(displayAtts, tooltips);
+
+    return attCombo;
+  }
+
+  /**
+   * Populates initial layout of the feature attribute filters panel
+   */
+  private JPanel initialiseFiltersPanel()
+  {
+    filters = new ArrayList<>();
+
+    JPanel filtersPanel = new JPanel();
+    filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
+    filtersPanel.setBackground(Color.white);
+    JvSwingUtils.createTitledBorder(filtersPanel,
+            MessageManager.getString("label.filters"), true);
+
+    JPanel andOrPanel = initialiseAndOrPanel();
+    filtersPanel.add(andOrPanel);
+
+    /*
+     * panel with filters - populated by refreshFiltersDisplay
+     */
+    chooseFiltersPanel = new JPanel();
+    LayoutManager box = new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS);
+    chooseFiltersPanel.setLayout(box);
+    filtersPanel.add(chooseFiltersPanel);
+
+    return filtersPanel;
+  }
+
+  /**
+   * Lays out the panel with radio buttons to AND or OR filter conditions
+   * 
+   * @return
+   */
+  private JPanel initialiseAndOrPanel()
+  {
+    JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    andOrPanel.setBackground(Color.white);
+    andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour));
+    andFilters = new JRadioButton(MessageManager.getString("label.and"));
+    orFilters = new JRadioButton(MessageManager.getString("label.or"));
+    ActionListener actionListener = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        filtersChanged();
+      }
+    };
+    andFilters.addActionListener(actionListener);
+    orFilters.addActionListener(actionListener);
+    ButtonGroup andOr = new ButtonGroup();
+    andOr.add(andFilters);
+    andOr.add(orFilters);
+    andFilters.setSelected(true);
+    andOrPanel.add(
+            new JLabel(MessageManager.getString("label.join_conditions")));
+    andOrPanel.add(andFilters);
+    andOrPanel.add(orFilters);
+    return andOrPanel;
+  }
+
+  /**
+   * Refreshes the display to show any filters currently configured for the
+   * selected feature type (editable, with 'remove' option), plus one extra row
+   * for adding a condition. This should be called after a filter has been
+   * removed, added or amended.
+   */
+  private void updateFiltersTab()
+  {
+    /*
+     * clear the panel and list of filter conditions
+     */
+    chooseFiltersPanel.removeAll();
+    filters.clear();
+
+    /*
+     * look up attributes known for feature type
+     */
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(featureType);
+
+    /*
+     * if this feature type has filters set, load them first
+     */
+    KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType);
+    if (featureFilters != null)
+    {
+      if (!featureFilters.isAnded())
+      {
+        orFilters.setSelected(true);
+      }
+      featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
+    }
+
+    /*
+     * and an empty filter for the user to populate (add)
+     */
+    KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "",
+            (String) null);
+    filters.add(noFilter);
+
+    /*
+     * render the conditions in rows, each in its own JPanel
+     */
+    int filterIndex = 0;
+    for (KeyedMatcherI filter : filters)
+    {
+      String[] attName = filter.getKey();
+      Condition condition = filter.getMatcher().getCondition();
+      String pattern = filter.getMatcher().getPattern();
+      JPanel row = addFilter(attName, attNames, condition, pattern,
+              filterIndex);
+      row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
+      chooseFiltersPanel.add(row);
+      filterIndex++;
+    }
+
+    this.validate();
+    this.repaint();
+  }
+
+  /**
+   * A helper method that constructs a row (panel) with one filter condition:
+   * <ul>
+   * <li>a drop-down list of attribute names to choose from</li>
+   * <li>a drop-down list of conditions to choose from</li>
+   * <li>a text field for input of a match pattern</li>
+   * <li>optionally, a 'remove' button</li>
+   * </ul>
+   * If attribute, condition or pattern are not null, they are set as defaults for
+   * the input fields. The 'remove' button is added unless the pattern is null or
+   * empty (incomplete filter condition).
+   * 
+   * @param attName
+   * @param attNames
+   * @param cond
+   * @param pattern
+   * @param filterIndex
+   * @return
+   */
+  protected JPanel addFilter(String[] attName, List<String[]> attNames,
+          Condition cond, String pattern, int filterIndex)
+  {
+    JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    filterRow.setBackground(Color.white);
+
+    /*
+     * drop-down choice of attribute, with description as a tooltip 
+     * if we can obtain it
+     */
+    final JComboBox<String> attCombo = populateAttributesDropdown(attNames,
+            true, true);
+    JComboBox<Condition> condCombo = new JComboBox<>();
+    JTextField patternField = new JTextField(8);
+
+    /*
+     * action handlers that validate and (if valid) apply changes
+     */
+    ActionListener actionListener = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        if (attCombo.getSelectedItem() != null)
+        {
+          if (validateFilter(patternField, condCombo))
+          {
+            updateFilter(attCombo, condCombo, patternField, filterIndex);
+            filtersChanged();
+          }
+        }
+      }
+    };
+    ItemListener itemListener = new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        actionListener.actionPerformed(null);
+      }
+    };
+
+    if (attName == null) // the 'add a condition' row
+    {
+      attCombo.setSelectedIndex(0);
+    }
+    else
+    {
+      attCombo.setSelectedItem(toAttributeDisplayName(attName));
+    }
+    attCombo.addItemListener(itemListener);
+
+    filterRow.add(attCombo);
+
+    /*
+     * drop-down choice of test condition
+     */
+    populateConditions((String) attCombo.getSelectedItem(), cond,
+            condCombo);
+    condCombo.addItemListener(itemListener);
+    filterRow.add(condCombo);
+
+    /*
+     * pattern to match against
+     */
+    patternField.setText(pattern);
+    patternField.addActionListener(actionListener);
+    patternField.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+        actionListener.actionPerformed(null);
+      }
+    });
+    filterRow.add(patternField);
+
+    /*
+     * add remove button if filter is populated (non-empty pattern)
+     */
+    if (pattern != null && pattern.trim().length() > 0)
+    {
+      // todo: gif for button drawing '-' or 'x'
+      JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
+      removeCondition
+              .setToolTipText(MessageManager.getString("label.delete_row"));
+      removeCondition.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          filters.remove(filterIndex);
+          filtersChanged();
+        }
+      });
+      filterRow.add(removeCondition);
+    }
+
+    return filterRow;
+  }
+
+  /**
+   * Populates the drop-down list of comparison conditions for the given attribute
+   * name. The conditions added depend on the datatype of the attribute values.
+   * The supplied condition is set as the selected item in the list, provided it
+   * is in the list.
+   * 
+   * @param attName
+   * @param cond
+   * @param condCombo
+   */
+  private void populateConditions(String attName, Condition cond,
+          JComboBox<Condition> condCombo)
+  {
+    Datatype type = FeatureAttributes.getInstance().getDatatype(featureType,
+            attName);
+    if (MessageManager.getString("label.label").equals(attName))
+    {
+      type = Datatype.Character;
+    }
+    else if (MessageManager.getString("label.score").equals(attName))
+    {
+      type = Datatype.Number;
+    }
+
+    for (Condition c : Condition.values())
+    {
+      if ((c.isNumeric() && type != Datatype.Character)
+              || (!c.isNumeric() && type != Datatype.Number))
+      {
+        condCombo.addItem(c);
+      }
+    }
+
+    /*
+     * set the selected condition (does nothing if not in the list)
+     */
+    if (cond != null)
+    {
+      condCombo.setSelectedItem(cond);
+    }
+  }
+
+  /**
+   * Answers true unless a numeric condition has been selected with a non-numeric
+   * value. Sets the value field to RED with a tooltip if in error.
+   * <p>
+   * If the pattern entered is empty, this method returns false, but does not mark
+   * the field as invalid. This supports selecting an attribute for a new
+   * condition before a match pattern has been entered.
+   * 
+   * @param value
+   * @param condCombo
+   */
+  protected boolean validateFilter(JTextField value,
+          JComboBox<Condition> condCombo)
+  {
+    if (value == null || condCombo == null)
+    {
+      return true; // fields not populated
+    }
+
+    Condition cond = (Condition) condCombo.getSelectedItem();
+    value.setBackground(Color.white);
+    value.setToolTipText("");
+    String v1 = value.getText().trim();
+    if (v1.length() == 0)
+    {
+      return false;
+    }
+
+    if (cond.isNumeric())
+    {
+      try
+      {
+        Float.valueOf(v1);
+      } catch (NumberFormatException e)
+      {
+        value.setBackground(Color.red);
+        value.setToolTipText(
+                MessageManager.getString("label.numeric_required"));
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Constructs a filter condition from the given input fields, and replaces the
+   * condition at filterIndex with the new one
+   * 
+   * @param attCombo
+   * @param condCombo
+   * @param valueField
+   * @param filterIndex
+   */
+  protected void updateFilter(JComboBox<String> attCombo,
+          JComboBox<Condition> condCombo, JTextField valueField,
+          int filterIndex)
+  {
+    String attName = (String) attCombo.getSelectedItem();
+    Condition cond = (Condition) condCombo.getSelectedItem();
+    String pattern = valueField.getText();
+    KeyedMatcherI km = new KeyedMatcher(cond, pattern,
+            fromAttributeDisplayName(attName));
+
+    filters.set(filterIndex, km);
+  }
+
+  /**
+   * Makes the dialog visible, at the Feature Colour tab or at the Filters tab
+   * 
+   * @param coloursTab
+   */
+  public void showTab(boolean coloursTab)
+  {
+    setVisible(true);
+    tabbedPane.setSelectedIndex(coloursTab ? 0 : 1);
+  }
+
+  /**
+   * Action on any change to feature filtering, namely
+   * <ul>
+   * <li>change of selected attribute</li>
+   * <li>change of selected condition</li>
+   * <li>change of match pattern</li>
+   * <li>removal of a condition</li>
+   * </ul>
+   * The inputs are parsed into a combined filter and this is set for the feature
+   * type, and the alignment redrawn.
+   */
+  protected void filtersChanged()
+  {
+    /*
+     * update the filter conditions for the feature type
+     */
+    boolean anded = andFilters.isSelected();
+    KeyedMatcherSetI combined = new KeyedMatcherSet();
+
+    for (KeyedMatcherI filter : filters)
+    {
+      String pattern = filter.getMatcher().getPattern();
+      if (pattern.trim().length() > 0)
+      {
+        if (anded)
+        {
+          combined.and(filter);
+        }
+        else
+        {
+          combined.or(filter);
+        }
+      }
+    }
+
+    /*
+     * save the filter conditions in the FeatureRenderer
+     * (note this might now be an empty filter with no conditions)
+     */
+    fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
+    ap.paintAlignment(true, true);
+
+    updateFiltersTab();
+  }
+}
index a7dff86..085b259 100755 (executable)
@@ -564,5 +564,14 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     {
       fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
     }
+    else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      fastPaint(((int[]) evt.getNewValue())[1]
+              - ((int[]) evt.getOldValue())[1]);
+    }
+    else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      repaint();
+    }
   }
 }
index 1008203..1d7bf3d 100644 (file)
@@ -136,8 +136,8 @@ public abstract class JalviewDialog extends JPanel
   {
     try
     {
-      frame.dispose();
       raiseClosed();
+      frame.dispose();
     } catch (Exception ex)
     {
     }
index 79e0cef..4658668 100644 (file)
@@ -356,12 +356,13 @@ public final class JvSwingUtils
 
   /**
    * Adds a titled border to the component in the default font and position (top
-   * left)
+   * left), optionally witht italic text
    * 
    * @param comp
    * @param title
+   * @param italic
    */
-  public static void createItalicTitledBorder(JComponent comp,
+  public static TitledBorder createTitledBorder(JComponent comp,
           String title, boolean italic)
   {
     Font font = comp.getFont();
@@ -374,6 +375,8 @@ public final class JvSwingUtils
             title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION,
             font);
     comp.setBorder(titledBorder);
+
+    return titledBorder;
   }
 
 }
index e677769..798c833 100755 (executable)
@@ -549,7 +549,9 @@ public class ScalePanel extends JPanel
     // Here we only want to fastpaint on a scroll, with resize using a normal
     // paint, so scroll events are identified as changes to the horizontal or
     // vertical start value.
-    if (evt.getPropertyName().equals(ViewportRanges.STARTRES))
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRES)
+            || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)
+            || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
     {
       // scroll event, repaint panel
       repaint();
index 2a9c704..433d2ec 100755 (executable)
@@ -296,47 +296,48 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       int transX = 0;
       int transY = 0;
 
-    gg.copyArea(horizontal * charWidth, vertical * charHeight,
-            img.getWidth(), img.getHeight(), -horizontal * charWidth,
-            -vertical * charHeight);
+      gg.copyArea(horizontal * charWidth, vertical * charHeight,
+              img.getWidth(), img.getHeight(), -horizontal * charWidth,
+              -vertical * charHeight);
 
-    if (horizontal > 0) // scrollbar pulled right, image to the left
-    {
-      transX = (endRes - startRes - horizontal) * charWidth;
-      startRes = endRes - horizontal;
-    }
-    else if (horizontal < 0)
-    {
-      endRes = startRes - horizontal;
-    }
-    else if (vertical > 0) // scroll down
-    {
-      startSeq = endSeq - vertical;
-
-      if (startSeq < ranges.getStartSeq())
-      { // ie scrolling too fast, more than a page at a time
-        startSeq = ranges.getStartSeq();
+      if (horizontal > 0) // scrollbar pulled right, image to the left
+      {
+        transX = (endRes - startRes - horizontal) * charWidth;
+        startRes = endRes - horizontal;
       }
-      else
+      else if (horizontal < 0)
       {
-        transY = img.getHeight() - ((vertical + 1) * charHeight);
+        endRes = startRes - horizontal;
       }
-    }
-    else if (vertical < 0)
-    {
-      endSeq = startSeq - vertical;
 
-      if (endSeq > ranges.getEndSeq())
+      if (vertical > 0) // scroll down
+      {
+        startSeq = endSeq - vertical;
+
+        if (startSeq < ranges.getStartSeq())
+        { // ie scrolling too fast, more than a page at a time
+          startSeq = ranges.getStartSeq();
+        }
+        else
+        {
+          transY = img.getHeight() - ((vertical + 1) * charHeight);
+        }
+      }
+      else if (vertical < 0)
       {
-        endSeq = ranges.getEndSeq();
+        endSeq = startSeq - vertical;
+
+        if (endSeq > ranges.getEndSeq())
+        {
+          endSeq = ranges.getEndSeq();
+        }
       }
-    }
 
-    gg.translate(transX, transY);
-    drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
-    gg.translate(-transX, -transY);
+      gg.translate(transX, transY);
+      drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
+      gg.translate(-transX, -transY);
 
-    repaint();
+      repaint();
     } finally
     {
       fastpainting = false;
@@ -410,6 +411,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       // lcimg is a local *copy* of img which we'll draw selectImage on top of
       BufferedImage lcimg = buildLocalImage(selectImage);
       g.drawImage(lcimg, 0, 0, this);
+
+    }
+
+    if (av.cursorMode)
+    {
+      drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
+              ranges.getStartSeq(), ranges.getEndSeq());
     }
   }
   
@@ -508,6 +516,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
               AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
       g2d.drawImage(selectImage, 0, 0, this);
     }
+
     g2d.dispose();
 
     return lcimg;
@@ -771,8 +780,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
      * white fill the region to be drawn (so incremental fast paint doesn't
      * scribble over an existing image)
      */
-    gg.setColor(Color.white);
-    gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
+    g.setColor(Color.white);
+    g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
             wrappedRepeatHeightPx);
 
     drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
@@ -912,9 +921,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int canvasWidth,
           int canvasHeight, int startRes)
   {
-       int charHeight = av.getCharHeight();
-       int charWidth = av.getCharWidth();
-         
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+      
     // height gap above each panel
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
@@ -1141,13 +1150,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           }
         }
       }
-
-      if (av.cursorMode && cursorY == i && cursorX >= startRes
-              && cursorX <= endRes)
-      {
-        seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
-                offset + ((i - startSeq) * charHeight));
-      }
     }
 
     if (av.getSelectionGroup() != null
@@ -1245,6 +1247,94 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return selectionImage;
   }
 
+  /**
+   * Draw the cursor as a separate image and overlay
+   * 
+   * @param startRes
+   *          start residue of area to draw cursor in
+   * @param endRes
+   *          end residue of area to draw cursor in
+   * @param startSeq
+   *          start sequence of area to draw cursor in
+   * @param endSeq
+   *          end sequence of are to draw cursor in
+   * @return a transparent image of the same size as the sequence canvas, with
+   *         the cursor drawn on it, if any
+   */
+  private void drawCursor(Graphics g, int startRes, int endRes,
+          int startSeq,
+          int endSeq)
+  {
+    // convert the cursorY into a position on the visible alignment
+    int cursor_ypos = cursorY;
+
+    // don't do work unless we have to
+    if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
+    {
+      int yoffset = 0;
+      int xoffset = 0;
+      int startx = startRes;
+      int endx = endRes;
+
+      // convert the cursorX into a position on the visible alignment
+      int cursor_xpos = av.getAlignment().getHiddenColumns()
+              .findColumnPosition(cursorX);
+
+      if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
+      {
+
+        if (av.getWrapAlignment())
+        {
+          // work out the correct offsets for the cursor
+          int charHeight = av.getCharHeight();
+          int charWidth = av.getCharWidth();
+          int canvasWidth = getWidth();
+          int canvasHeight = getHeight();
+
+          // height gap above each panel
+          int hgap = charHeight;
+          if (av.getScaleAboveWrapped())
+          {
+            hgap += charHeight;
+          }
+
+          int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
+                  / charWidth;
+          int cHeight = av.getAlignment().getHeight() * charHeight;
+
+          endx = startx + cWidth - 1;
+          int ypos = hgap; // vertical offset
+
+          // iterate down the wrapped panels
+          while ((ypos <= canvasHeight) && (endx < cursor_xpos))
+          {
+            // update vertical offset
+            ypos += cHeight + getAnnotationHeight() + hgap;
+
+            // update horizontal offset
+            startx += cWidth;
+            endx = startx + cWidth - 1;
+          }
+          yoffset = ypos;
+          xoffset = labelWidthWest;
+        }
+
+        // now check if cursor is within range for x values
+        if (cursor_xpos >= startx && cursor_xpos <= endx)
+        {
+          // get the character the cursor is drawn at
+          SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
+          char s = seq.getCharAt(cursorX);
+
+          seqRdr.drawCursor(g, s,
+                  xoffset + (cursor_xpos - startx) * av.getCharWidth(),
+                  yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
+        }
+      }
+    }
+  }
+
+
   /*
    * Set up graphics for selection group
    */
@@ -1276,8 +1366,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq, int offset)
   {
-       int charWidth = av.getCharWidth();
-         
+    int charWidth = av.getCharWidth();
+          
     if (!av.hasHiddenColumns())
     {
       drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
@@ -1339,9 +1429,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int startRes, int endRes, int startSeq, int endSeq,
           int verticalOffset)
   {
-       int charHeight = av.getCharHeight();
-       int charWidth = av.getCharWidth();
-         
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
     int visWidth = (endRes - startRes + 1) * charWidth;
 
     int oldY = -1;
@@ -1349,140 +1438,141 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     boolean inGroup = false;
     int top = -1;
     int bottom = -1;
-
-    int sx = -1;
     int sy = -1;
-    int xwidth = -1;
 
-    for (i = startSeq; i <= endSeq; i++)
-    {
-      // position of start residue of group relative to startRes, in pixels
-      sx = (group.getStartRes() - startRes) * charWidth;
+    List<SequenceI> seqs = group.getSequences(null);
 
-      // width of group in pixels
-      xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
-              - 1;
+    // position of start residue of group relative to startRes, in pixels
+    int sx = (group.getStartRes() - startRes) * charWidth;
 
-      sy = verticalOffset + (i - startSeq) * charHeight;
+    // width of group in pixels
+    int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
+            * charWidth) - 1;
 
-      if (sx + xwidth < 0 || sx > visWidth)
-      {
-        continue;
-      }
-
-      if ((sx <= (endRes - startRes) * charWidth)
-              && group.getSequences(null)
-                      .contains(av.getAlignment().getSequenceAt(i)))
+    if (!(sx + xwidth < 0 || sx > visWidth))
+    {
+      for (i = startSeq; i <= endSeq; 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;
-          }
+        sy = verticalOffset + (i - startSeq) * charHeight;
 
-          oldY = sy;
-          inGroup = true;
-        }
-      }
-      else
-      {
-        if (inGroup)
+        if ((sx <= (endRes - startRes) * charWidth)
+                && seqs.contains(av.getAlignment().getSequenceAt(i)))
         {
-          // if start position is visible, draw vertical line to left of
-          // group
-          if (sx >= 0 && sx < visWidth)
+          if ((bottom == -1)
+                  && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
           {
-            g.drawLine(sx, oldY, sx, sy);
+            bottom = sy + charHeight;
           }
 
-          // if end position is visible, draw vertical line to right of
-          // group
-          if (sx + xwidth < visWidth)
-          {
-            g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
-          }
-
-          if (sx < 0)
-          {
-            xwidth += sx;
-            sx = 0;
-          }
-
-          // don't let width extend beyond current block, or group extent
-          // fixes JAL-2672
-          if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
-          {
-            xwidth = (endRes - startRes + 1) * charWidth - sx;
-          }
-          
-          // draw horizontal line at top of group
-          if (top != -1)
+          if (!inGroup)
           {
-            g.drawLine(sx, top, sx + xwidth, top);
-            top = -1;
-          }
+            if (((top == -1) && (i == 0)) || !seqs
+                    .contains(av.getAlignment().getSequenceAt(i - 1)))
+            {
+              top = sy;
+            }
 
-          // draw horizontal line at bottom of group
-          if (bottom != -1)
-          {
-            g.drawLine(sx, bottom, sx + xwidth, bottom);
-            bottom = -1;
+            oldY = sy;
+            inGroup = true;
           }
+        }
+        else if (inGroup)
+        {
+          drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
+          drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
 
+          // reset top and bottom
+          top = -1;
+          bottom = -1;
           inGroup = false;
         }
       }
-    }
-
-    if (inGroup)
-    {
-      sy = verticalOffset + ((i - startSeq) * charHeight);
-      if (sx >= 0 && sx < visWidth)
+      if (inGroup)
       {
-        g.drawLine(sx, oldY, sx, sy);
+        sy = verticalOffset + ((i - startSeq) * charHeight);
+        drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
+        drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
       }
+    }
+  }
 
-      if (sx + xwidth < visWidth)
-      {
-        g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
-      }
+  /**
+   * Draw horizontal selection group boundaries at top and bottom positions
+   * 
+   * @param g
+   *          graphics object to draw on
+   * @param sx
+   *          start x position
+   * @param xwidth
+   *          width of gap
+   * @param visWidth
+   *          visWidth maximum available width
+   * @param top
+   *          position to draw top of group at
+   * @param bottom
+   *          position to draw bottom of group at
+   */
+  private void drawHorizontals(Graphics2D g, int sx, int xwidth,
+          int visWidth, int top, int bottom)
+  {
+    int width = xwidth;
+    int startx = sx;
+    if (startx < 0)
+    {
+      width += startx;
+      startx = 0;
+    }
 
-      if (sx < 0)
-      {
-        xwidth += sx;
-        sx = 0;
-      }
+    // don't let width extend beyond current block, or group extent
+    // fixes JAL-2672
+    if (startx + width >= visWidth)
+    {
+      width = visWidth - startx;
+    }
 
-      if (sx + xwidth > visWidth)
-      {
-        xwidth = visWidth;
-      }
-      else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
-      {
-        xwidth = (endRes - startRes + 1) * charWidth;
-      }
+    if (top != -1)
+    {
+      g.drawLine(startx, top, startx + width, top);
+    }
 
-      if (top != -1)
-      {
-        g.drawLine(sx, top, sx + xwidth, top);
-        top = -1;
-      }
+    if (bottom != -1)
+    {
+      g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
+    }
+  }
 
-      if (bottom != -1)
-      {
-        g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
-        bottom = -1;
-      }
+  /**
+   * Draw vertical lines at sx and sx+xwidth providing they lie within
+   * [0,visWidth)
+   * 
+   * @param g
+   *          graphics object to draw on
+   * @param sx
+   *          start x position
+   * @param xwidth
+   *          width of gap
+   * @param visWidth
+   *          visWidth maximum available width
+   * @param oldY
+   *          top y value
+   * @param sy
+   *          bottom y value
+   */
+  private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
+          int oldY, int sy)
+  {
+    // if start position is visible, draw vertical line to left of
+    // group
+    if (sx >= 0 && sx < visWidth)
+    {
+      g.drawLine(sx, oldY, sx, sy);
+    }
 
-      inGroup = false;
+    // if end position is visible, draw vertical line to right of
+    // group
+    if (sx + xwidth < visWidth)
+    {
+      g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
     }
   }
   
@@ -1569,7 +1659,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   protected boolean drawMappedPositions(SearchResultsI results)
   {
-    if (results == null)
+    if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
     {
       return false;
     }
@@ -1660,15 +1750,31 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       repaint();
       return;
     }
+    else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
+    {
+      fastPaint = false;
+      repaint();
+      return;
+    }
 
     int scrollX = 0;
-    if (eventName.equals(ViewportRanges.STARTRES))
+    if (eventName.equals(ViewportRanges.STARTRES)
+            || eventName.equals(ViewportRanges.STARTRESANDSEQ))
     {
       // Make sure we're not trying to draw a panel
       // larger than the visible window
+      if (eventName.equals(ViewportRanges.STARTRES))
+      {
+        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
+      }
+      else
+      {
+        scrollX = ((int[]) evt.getNewValue())[0]
+                - ((int[]) evt.getOldValue())[0];
+      }
       ViewportRanges vpRanges = av.getRanges();
-      scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
-      int range = vpRanges.getViewportWidth();
+
+      int range = vpRanges.getEndRes() - vpRanges.getStartRes();
       if (scrollX > range)
       {
         scrollX = range;
@@ -1677,30 +1783,43 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       {
         scrollX = -range;
       }
-    }
-
-    // Both scrolling and resizing change viewport ranges: scrolling changes
-    // both start and end points, but resize only changes end values.
-    // Here we only want to fastpaint on a scroll, with resize using a normal
-    // paint, so scroll events are identified as changes to the horizontal or
-    // vertical start value.
 
-    // scroll - startres and endres both change
-    if (eventName.equals(ViewportRanges.STARTRES))
-    {
-      if (av.getWrapAlignment())
+      // Both scrolling and resizing change viewport ranges: scrolling changes
+      // both start and end points, but resize only changes end values.
+      // Here we only want to fastpaint on a scroll, with resize using a normal
+      // paint, so scroll events are identified as changes to the horizontal or
+      // vertical start value.
+      if (eventName.equals(ViewportRanges.STARTRES))
       {
-        fastPaintWrapped(scrollX);
+         if (av.getWrapAlignment())
+          {
+            fastPaintWrapped(scrollX);
+          }
+          else
+          {
+            fastPaint(scrollX, 0);
+          }
       }
-      else
+      else if (eventName.equals(ViewportRanges.STARTSEQ))
       {
-        fastPaint(scrollX, 0);
+        // scroll
+        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+      }
+      else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+      {
+        if (av.getWrapAlignment())
+        {
+          fastPaintWrapped(scrollX);
+        }
+        else
+        {
+          fastPaint(scrollX, 0);
+        }
+        // bizarrely, we only need to scroll on the x value here as fastpaint
+        // copies the full height of the image anyway. Passing in the y value
+        // causes nasty repaint artefacts, which only disappear on a full
+        // repaint.
       }
-    }
-    else if (eventName.equals(ViewportRanges.STARTSEQ))
-    {
-      // scroll
-      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
     }
   }
 
@@ -1717,7 +1836,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   {
     ViewportRanges ranges = av.getRanges();
 
-    if (Math.abs(scrollX) > ranges.getViewportWidth())
+    // if (Math.abs(scrollX) > ranges.getViewportWidth())
+    // JAL-2836, 2836 temporarily removed wrapped fastpaint for release 2.10.3
+    if (true)
     {
       /*
        * shift of more than one view width is 
@@ -1967,7 +2088,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   protected boolean drawMappedPositionsWrapped(SearchResultsI results)
   {
-    if (results == null)
+    if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
     {
       return false;
     }
index 29f68c1..d5a13f3 100644 (file)
@@ -316,13 +316,13 @@ public class SeqPanel extends JPanel
   void setCursorRow()
   {
     seqCanvas.cursorY = getKeyboardNo1() - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void setCursorColumn()
   {
     seqCanvas.cursorX = getKeyboardNo1() - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void setCursorRowAndColumn()
@@ -335,7 +335,7 @@ public class SeqPanel extends JPanel
     {
       seqCanvas.cursorX = getKeyboardNo1() - 1;
       seqCanvas.cursorY = getKeyboardNo2() - 1;
-      scrollToVisible();
+      scrollToVisible(true);
     }
   }
 
@@ -344,7 +344,7 @@ public class SeqPanel extends JPanel
     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
 
     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1;
-    scrollToVisible();
+    scrollToVisible(true);
   }
 
   void moveCursor(int dx, int dy)
@@ -372,10 +372,16 @@ public class SeqPanel extends JPanel
       }
     }
 
-    scrollToVisible();
+    scrollToVisible(false);
   }
 
-  void scrollToVisible()
+  /**
+   * Scroll to make the cursor visible in the viewport.
+   * 
+   * @param jump
+   *          just jump to the location rather than scrolling
+   */
+  void scrollToVisible(boolean jump)
   {
     if (seqCanvas.cursorX < 0)
     {
@@ -396,20 +402,44 @@ public class SeqPanel extends JPanel
     }
 
     endEditing();
-    if (av.getWrapAlignment())
+
+    boolean repaintNeeded = true;
+    if (jump)
     {
-      av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
+      // only need to repaint if the viewport did not move, as otherwise it will
+      // get a repaint
+      repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX,
+              seqCanvas.cursorY);
     }
     else
     {
-      av.getRanges().scrollToVisible(seqCanvas.cursorX, seqCanvas.cursorY);
+      if (av.getWrapAlignment())
+      {
+        // scrollToWrappedVisible expects x-value to have hidden cols subtracted
+        int x = av.getAlignment().getHiddenColumns()
+                .findColumnPosition(seqCanvas.cursorX);
+        av.getRanges().scrollToWrappedVisible(x);
+      }
+      else
+      {
+        av.getRanges().scrollToVisible(seqCanvas.cursorX,
+                seqCanvas.cursorY);
+      }
     }
-    setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
+
+    if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX))
+    {
+      setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY),
             seqCanvas.cursorX, seqCanvas.cursorY);
+    }
 
-    seqCanvas.repaint();
+    if (repaintNeeded)
+    {
+      seqCanvas.repaint();
+    }
   }
 
+
   void setSelectionAreaAtCursor(boolean topLeft)
   {
     SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
index 0a1e8ef..81b394b 100755 (executable)
@@ -481,21 +481,30 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     }
   }
 
-  public void drawCursor(SequenceI seq, int res, int x1, int y1)
+  /**
+   * Draw a sequence canvas cursor
+   * 
+   * @param g
+   *          graphics context to draw on
+   * @param s
+   *          character to draw at cursor
+   * @param x1
+   *          x position of cursor in graphics context
+   * @param y1
+   *          y position of cursor in graphics context
+   */
+  public void drawCursor(Graphics g, char s, int x1, int y1)
   {
     int pady = av.getCharHeight() / 5;
     int charOffset = 0;
-    graphics.setColor(Color.black);
-    graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
+    g.setColor(Color.black);
+    g.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
 
     if (av.isValidCharWidth())
     {
-      graphics.setColor(Color.white);
-
-      char s = seq.getCharAt(res);
-
+      g.setColor(Color.white);
       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
-      graphics.drawString(String.valueOf(s), charOffset + x1,
+      g.drawString(String.valueOf(s), charOffset + x1,
               (y1 + av.getCharHeight()) - pady);
     }
 
index 37632ef..7c386f1 100644 (file)
@@ -553,7 +553,7 @@ public class StructureChooser extends GStructureChooser
 
     if (cachedPDBExists)
     {
-      FilterOption cachedOption = new FilterOption("Cached PDB Entries",
+      FilterOption cachedOption = new FilterOption("Cached Structures",
               "-", VIEWS_LOCAL_PDB, false);
       cmb_filterOption.addItem(cachedOption);
       cmb_filterOption.setSelectedItem(cachedOption);
index 71a89b0..aa0b640 100644 (file)
@@ -49,7 +49,7 @@ import java.util.StringTokenizer;
  */
 public class FeatureColour implements FeatureColourI
 {
-  static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
+  static final Color DEFAULT_NO_COLOUR = null;
 
   private static final String BAR = "|";
 
@@ -93,8 +93,6 @@ public class FeatureColour implements FeatureColourI
 
   private boolean aboveThreshold;
 
-  private boolean thresholdIsMinOrMax;
-
   private boolean isHighToLow;
 
   private boolean autoScaled;
@@ -392,6 +390,15 @@ public class FeatureColour implements FeatureColourI
     updateBounds(min, max);
   }
 
+  /**
+   * Constructor for a graduated colour
+   * 
+   * @param low
+   * @param high
+   * @param noValueColour
+   * @param min
+   * @param max
+   */
   public FeatureColour(Color low, Color high, Color noValueColour,
           float min, float max)
   {
@@ -524,18 +531,6 @@ public class FeatureColour implements FeatureColourI
   }
 
   @Override
-  public boolean isThresholdMinMax()
-  {
-    return thresholdIsMinOrMax;
-  }
-
-  @Override
-  public void setThresholdMinMax(boolean b)
-  {
-    thresholdIsMinOrMax = b;
-  }
-
-  @Override
   public float getThreshold()
   {
     return threshold;
@@ -607,8 +602,7 @@ public class FeatureColour implements FeatureColourI
     /*
      * graduated colour case, optionally with threshold
      * may be based on feature score on an attribute value
-     * Float.NaN is assigned minimum visible score colour
-     * no such attribute is assigned the 'no value' colour
+     * Float.NaN, or no value, is assigned the 'no value' colour
      */
     float scr = feature.getScore();
     if (attributeName != null)
index cb32a0e..a0a29f2 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * 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.util;
 
 import java.util.Comparator;
index c525bc6..c158ce7 100644 (file)
@@ -132,9 +132,7 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions
       }
     }
 
-    // update viewport
-    ranges.setStartRes(xAsRes);
-    ranges.setStartSeq(yAsSeq);
+    ranges.setStartResAndSeq(xAsRes, yAsSeq);
   }
 
   @Override
index 0bda56e..9dde16e 100644 (file)
@@ -176,8 +176,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     }
 
     // update viewport
-    ranges.setStartRes(visXAsRes);
-    ranges.setStartSeq(visYAsSeq);
+    ranges.setStartResAndSeq(visXAsRes, visYAsSeq);
   }
 
   /**
index 24ff57f..c7a3fa1 100644 (file)
@@ -24,11 +24,10 @@ 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
+ * 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
 {
@@ -40,6 +39,10 @@ public class ViewportRanges extends ViewportProperties
 
   public static final String ENDSEQ = "endseq";
 
+  public static final String STARTRESANDSEQ = "startresandseq";
+
+  public static final String MOVE_VIEWPORT = "move_viewport";
+
   private boolean wrappedMode = false;
 
   // start residue of viewport
@@ -130,6 +133,31 @@ public class ViewportRanges extends ViewportProperties
    */
   public void setStartEndRes(int start, int end)
   {
+    int[] oldvalues = updateStartEndRes(start, end);
+    int oldstartres = oldvalues[0];
+    int oldendres = oldvalues[1];
+
+    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);
+    }
+  }
+
+  /**
+   * Update start and end residue values, adjusting for width constraints if
+   * necessary
+   * 
+   * @param start
+   *          start residue
+   * @param end
+   *          end residue
+   * @return array containing old start and end residue values
+   */
+  private int[] updateStartEndRes(int start, int end)
+  {
     int oldstartres = this.startRes;
 
     /*
@@ -162,14 +190,7 @@ public class ViewportRanges extends ViewportProperties
     {
       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);
-    }
+    return new int[] { oldstartres, oldendres };
   }
 
   /**
@@ -203,6 +224,31 @@ public class ViewportRanges extends ViewportProperties
    */
   public void setStartEndSeq(int start, int end)
   {
+    int[] oldvalues = updateStartEndSeq(start, end);
+    int oldstartseq = oldvalues[0];
+    int oldendseq = oldvalues[1];
+
+    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);
+    }
+  }
+
+  /**
+   * Update start and end sequence values, adjusting for height constraints if
+   * necessary
+   * 
+   * @param start
+   *          start sequence
+   * @param end
+   *          end sequence
+   * @return array containing old start and end sequence values
+   */
+  private int[] updateStartEndSeq(int start, int end)
+  {
     int oldstartseq = this.startSeq;
     int visibleHeight = getVisibleAlignmentHeight();
     if (start > visibleHeight - 1)
@@ -231,14 +277,7 @@ public class ViewportRanges extends ViewportProperties
     {
       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);
-    }
+    return new int[] { oldstartseq, oldendseq };
   }
 
   /**
@@ -255,6 +294,34 @@ public class ViewportRanges extends ViewportProperties
   }
 
   /**
+   * Set start residue and start sequence together (fires single event). The
+   * event supplies a pair of old values and a pair of new values: [old start
+   * residue, old start sequence] and [new start residue, new start sequence]
+   * 
+   * @param res
+   *          the start residue
+   * @param seq
+   *          the start sequence
+   */
+  public void setStartResAndSeq(int res, int seq)
+  {
+    int width = getViewportWidth();
+    int[] oldresvalues = updateStartEndRes(res, res + width - 1);
+
+    int startseq = seq;
+    int height = getViewportHeight();
+    if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
+    {
+      startseq = getVisibleAlignmentHeight() - height;
+    }
+    int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1);
+
+    int[] old = new int[] { oldresvalues[0], oldseqvalues[0] };
+    int[] newresseq = new int[] { startRes, startSeq };
+    changeSupport.firePropertyChange(STARTRESANDSEQ, old, newresseq);
+  }
+
+  /**
    * Get start residue of viewport
    */
   public int getStartRes()
@@ -477,18 +544,33 @@ public class ViewportRanges extends ViewportProperties
    * the startRes changed, else false.
    * 
    * @param res
-   *          residue position to scroll to
+   *          residue position to scroll to NB visible position not absolute
+   *          alignment position
    * @return
    */
   public boolean scrollToWrappedVisible(int res)
   {
-    int oldStartRes = startRes;
-    int width = getViewportWidth();
-
-    if (res >= oldStartRes && res < oldStartRes + width)
+    int newStartRes = calcWrappedStartResidue(res);
+    if (newStartRes == startRes)
     {
       return false;
     }
+    setStartRes(newStartRes);
+
+    return true;
+  }
+
+  /**
+   * Calculate wrapped start residue from visible start residue
+   * 
+   * @param res
+   *          visible start residue
+   * @return left column of panel res will be located in
+   */
+  private int calcWrappedStartResidue(int res)
+  {
+    int oldStartRes = startRes;
+    int width = getViewportWidth();
 
     boolean up = res < oldStartRes;
     int widthsToScroll = Math.abs((res - oldStartRes) / width);
@@ -504,19 +586,16 @@ public class ViewportRanges extends ViewportProperties
     {
       newStartRes = 0;
     }
-
-    setStartRes(newStartRes);
-
-    return true;
+    return newStartRes;
   }
 
   /**
    * Scroll so that (x,y) is visible. Fires a property change event.
    * 
    * @param x
-   *          x position in alignment
+   *          x position in alignment (absolute position)
    * @param y
-   *          y position in alignment
+   *          y position in alignment (absolute position)
    */
   public void scrollToVisible(int x, int y)
   {
@@ -528,7 +607,7 @@ public class ViewportRanges extends ViewportProperties
     {
       scrollUp(false);
     }
-
+    
     HiddenColumns hidden = al.getHiddenColumns();
     while (x < hidden.adjustForHiddenColumns(startRes))
     {
@@ -547,6 +626,62 @@ public class ViewportRanges extends ViewportProperties
   }
 
   /**
+   * Set the viewport location so that a position is visible
+   * 
+   * @param x
+   *          column to be visible: absolute position in alignment
+   * @param y
+   *          row to be visible: absolute position in alignment
+   */
+  public boolean setViewportLocation(int x, int y)
+  {
+    boolean changedLocation = false;
+
+    // convert the x,y location to visible coordinates
+    int visX = al.getHiddenColumns().findColumnPosition(x);
+    int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y);
+
+    // if (vis_x,vis_y) is already visible don't do anything
+    if (startRes > visX || visX > endRes
+            || startSeq > visY && visY > endSeq)
+    {
+      int[] old = new int[] { startRes, startSeq };
+      int[] newresseq;
+      if (wrappedMode)
+      {
+        int newstartres = calcWrappedStartResidue(visX);
+        setStartRes(newstartres);
+        newresseq = new int[] { startRes, startSeq };
+      }
+      else
+      {
+        // set the viewport x location to contain vis_x
+        int newstartres = visX;
+        int width = getViewportWidth();
+        if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1)
+        {
+          newstartres = getVisibleAlignmentWidth() - width;
+        }
+        updateStartEndRes(newstartres, newstartres + width - 1);
+
+        // set the viewport y location to contain vis_y
+        int newstartseq = visY;
+        int height = getViewportHeight();
+        if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1)
+        {
+          newstartseq = getVisibleAlignmentHeight() - height;
+        }
+        updateStartEndSeq(newstartseq, newstartseq + height - 1);
+
+        newresseq = new int[] { startRes, startSeq };
+      }
+      changedLocation = true;
+      changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq);
+    }
+    return changedLocation;
+  }
+
+  /**
    * Adjust sequence position for page up. Fires a property change event.
    */
   public void pageUp()
index 28fceec..4797675 100644 (file)
@@ -49,6 +49,17 @@ import java.util.concurrent.ConcurrentHashMap;
 public abstract class FeatureRendererModel
         implements jalview.api.FeatureRenderer
 {
+  /*
+   * column indices of fields in Feature Settings table
+   * todo: transfer valuers as data beans instead of Object[][]
+   */
+  public static final int TYPE_COLUMN = 0;
+
+  public static final int COLOUR_COLUMN = 1;
+
+  public static final int FILTER_COLUMN = 2;
+
+  public static final int SHOW_COLUMN = 3;
 
   /*
    * global transparency for feature
@@ -297,9 +308,13 @@ public abstract class FeatureRendererModel
     List<SequenceFeature> features = sequence.findFeatures(column, column,
             visibleTypes);
 
+    /*
+     * include features unless their feature group is not displayed, or
+     * they are hidden (have no colour) based on a filter or colour threshold
+     */
     for (SequenceFeature sf : features)
     {
-      if (!featureGroupNotShown(sf))
+      if (!featureGroupNotShown(sf) && getColour(sf) != null)
       {
         result.add(sf);
       }
@@ -716,9 +731,9 @@ public abstract class FeatureRendererModel
     {
       for (int i = 0; i < data.length; i++)
       {
-        String type = data[i][0].toString();
-        setColour(type, (FeatureColourI) data[i][1]);
-        if (((Boolean) data[i][2]).booleanValue())
+        String type = data[i][TYPE_COLUMN].toString();
+        setColour(type, (FeatureColourI) data[i][COLOUR_COLUMN]);
+        if (((Boolean) data[i][SHOW_COLUMN]).booleanValue())
         {
           av_featuresdisplayed.setVisible(type);
         }
@@ -993,7 +1008,7 @@ public abstract class FeatureRendererModel
   
     for (SequenceFeature sf : features)
     {
-      if (!featureGroupNotShown(sf))
+      if (!featureGroupNotShown(sf) && getColour(sf) != null)
       {
         result.add(sf);
       }
@@ -1120,8 +1135,13 @@ public abstract class FeatureRendererModel
   protected boolean featureMatchesFilters(SequenceFeature sf)
   {
     KeyedMatcherSetI filter = featureFilters.get(sf.getType());
-    return filter == null ? true : filter.matches(key -> sf
-            .getValueAsString(key));
+    // TODO temporary fudge for Score and Label
+    return filter == null ? true
+            : filter.matches(
+                    key -> "Label".equals(key[0]) ? sf.getDescription()
+                            : ("Score".equals(key[0])
+                                    ? String.valueOf(sf.getScore())
+                                    : sf.getValueAsString(key)));
   }
 
 }
index c9beb8e..73775cf 100644 (file)
@@ -310,23 +310,18 @@ public class Uniprot extends DbSourceProxyImpl
   /**
    *
    * @param entry
-   *          UniportEntry
+   *          UniprotEntry
    * @return The accession id(s) and name(s) delimited by '|'.
    */
   public static String getUniprotEntryId(UniprotEntry entry)
   {
     StringBuilder name = new StringBuilder(32);
-    // name.append("UniProt/Swiss-Prot");
-    // use 'canonicalised' name for optimal id matching
-    name.append(DBRefSource.UNIPROT);
-    for (String accessionId : entry.getAccession())
-    {
-      name.append(BAR_DELIMITER);
-      name.append(accessionId);
-    }
     for (String n : entry.getName())
     {
-      name.append(BAR_DELIMITER);
+      if (name.length() > 0)
+      {
+        name.append(BAR_DELIMITER);
+      }
       name.append(n);
     }
     return name.toString();
index c0cb09c..a084a8e 100644 (file)
@@ -1803,4 +1803,82 @@ public class SequenceTest
     sq.checkValidRange();
     assertEquals(22, sq.getEnd());
   }
+
+  @Test(groups = { "Functional" })
+  public void testDeleteChars_withGaps()
+  {
+    /*
+     * delete gaps only
+     */
+    SequenceI sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
+    sq.deleteChars(1, 2); // delete first gap
+    assertEquals("AB-C", sq.getSequenceAsString());
+    assertEquals(8, sq.getStart());
+    assertEquals(10, sq.getEnd());
+    assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
+
+    /*
+     * delete gaps and residues at start (no new dataset sequence)
+     */
+    sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    sq.deleteChars(0, 3); // delete A-B
+    assertEquals("-C", sq.getSequenceAsString());
+    assertEquals(10, sq.getStart());
+    assertEquals(10, sq.getEnd());
+    assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
+
+    /*
+     * delete gaps and residues at end (no new dataset sequence)
+     */
+    sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    sq.deleteChars(2, 5); // delete B-C
+    assertEquals("A-", sq.getSequenceAsString());
+    assertEquals(8, sq.getStart());
+    assertEquals(8, sq.getEnd());
+    assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
+
+    /*
+     * delete gaps and residues internally (new dataset sequence)
+     * first delete from gap to residue
+     */
+    sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    sq.deleteChars(1, 3); // delete -B
+    assertEquals("A-C", sq.getSequenceAsString());
+    assertEquals(8, sq.getStart());
+    assertEquals(9, sq.getEnd());
+    assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
+    assertEquals(8, sq.getDatasetSequence().getStart());
+    assertEquals(9, sq.getDatasetSequence().getEnd());
+
+    /*
+     * internal delete from gap to gap
+     */
+    sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    sq.deleteChars(1, 4); // delete -B-
+    assertEquals("AC", sq.getSequenceAsString());
+    assertEquals(8, sq.getStart());
+    assertEquals(9, sq.getEnd());
+    assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
+    assertEquals(8, sq.getDatasetSequence().getStart());
+    assertEquals(9, sq.getDatasetSequence().getEnd());
+
+    /*
+     * internal delete from residue to residue
+     */
+    sq = new Sequence("test/8-10", "A-B-C");
+    sq.createDatasetSequence();
+    sq.deleteChars(2, 3); // delete B
+    assertEquals("A--C", sq.getSequenceAsString());
+    assertEquals(8, sq.getStart());
+    assertEquals(9, sq.getEnd());
+    assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
+    assertEquals(8, sq.getDatasetSequence().getStart());
+    assertEquals(9, sq.getDatasetSequence().getEnd());
+  }
 }
index e464326..4b7a435 100644 (file)
@@ -1,18 +1,35 @@
 package jalview.datamodel.features;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
-import java.util.Comparator;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.features.FeatureAttributes.Datatype;
 
-import junit.extensions.PA;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
 
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class FeatureAttributesTest
 {
 
   /**
+   * clear down attributes map after tests
+   */
+  @AfterMethod
+  public void tearDown()
+  {
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    ((Map<?, ?>) PA.getValue(fa, "attributes")).clear();
+  }
+
+  /**
    * Test the method that keeps attribute names in non-case-sensitive order,
    * including handling of 'compound' names
    */
@@ -38,4 +55,66 @@ public class FeatureAttributesTest
     assertTrue(comp.compare(new String[] { "CSQ", "ac" }, new String[] {
         "csq", "AF" }) < 0);
   }
+
+  @Test
+  public void testGetMinMax()
+  {
+    SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20,
+            "group");
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    assertNull(fa.getMinMax("Pfam", "kd"));
+    sf.setValue("domain", "xyz");
+    assertNull(fa.getMinMax("Pfam", "kd"));
+    sf.setValue("kd", "some text");
+    assertNull(fa.getMinMax("Pfam", "kd"));
+    sf.setValue("kd", "1.3");
+    assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { 1.3f, 1.3f });
+    sf.setValue("kd", "-2.6");
+    assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { -2.6f, 1.3f });
+    Map<String, String> csq = new HashMap<>();
+    csq.put("AF", "-3");
+    sf.setValue("CSQ", csq);
+    assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"),
+            new float[]
+            { -3f, -3f });
+    csq.put("AF", "4");
+    sf.setValue("CSQ", csq);
+    assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"),
+            new float[]
+            { -3f, 4f });
+  }
+
+  /**
+   * Test the method that returns an attribute description, provided it is
+   * recorded and unique
+   */
+  @Test
+  public void testGetDescription()
+  {
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    // with no description returns null
+    assertNull(fa.getDescription("Pfam", "kd"));
+    // with a unique description, returns that value
+    fa.addDescription("Pfam", "desc1", "kd");
+    assertEquals(fa.getDescription("Pfam", "kd"), "desc1");
+    // with ambiguous description, returns null
+    fa.addDescription("Pfam", "desc2", "kd");
+    assertNull(fa.getDescription("Pfam", "kd"));
+  }
+
+  @Test
+  public void testDatatype()
+  {
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    assertNull(fa.getDatatype("Pfam", "kd"));
+    SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20,
+            "group");
+    sf.setValue("kd", "-1");
+    sf.setValue("domain", "Metal");
+    sf.setValue("phase", "1");
+    sf.setValue("phase", "reverse");
+    assertEquals(fa.getDatatype("Pfam", "kd"), Datatype.Number);
+    assertEquals(fa.getDatatype("Pfam", "domain"), Datatype.Character);
+    assertEquals(fa.getDatatype("Pfam", "phase"), Datatype.Mixed);
+  }
 }
index 5607b4b..246337d 100644 (file)
@@ -1,8 +1,8 @@
 package jalview.io.vcf;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
 
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.Mapping;
@@ -21,7 +21,9 @@ import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.Map;
 
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class VCFLoaderTest
@@ -64,6 +66,17 @@ public class VCFLoaderTest
       // insertion G/GA is transferred to nucleotide but not to peptide
       "17\t45051613\t.\tG\tGA,C\t1666.64\tRF\tAC=15;AF=3.0e-03,2.0e-03" };
 
+  @BeforeClass
+  public void setUp()
+  {
+    /*
+     * configure to capture all available VCF and VEP (CSQ) fields
+     */
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    Cache.setProperty("VCF_FIELDS", ".*");
+    Cache.setProperty("VEP_FIELDS", ".*");
+  }
+
   @Test(groups = "Functional")
   public void testDoLoad() throws IOException
   {
@@ -432,49 +445,56 @@ public class VCFLoaderTest
     assertEquals(sf.getScore(), 0.1f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,A");
     // gene features include Consequence for all transcripts
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    Map map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(1);
     assertEquals(sf.getBegin(), 5);
     assertEquals(sf.getEnd(), 5);
     assertEquals(sf.getScore(), 0.2f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,T");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(2);
     assertEquals(sf.getBegin(), 9);
     assertEquals(sf.getEnd(), 11); // deletion over 3 positions
     assertEquals(sf.getScore(), 0.3f, DELTA);
     assertEquals(sf.getValue("alleles"), "CGG,C");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(3);
     assertEquals(sf.getBegin(), 13);
     assertEquals(sf.getEnd(), 13);
     assertEquals(sf.getScore(), 0.5f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,T");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(4);
     assertEquals(sf.getBegin(), 13);
     assertEquals(sf.getEnd(), 13);
     assertEquals(sf.getScore(), 0.4f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,G");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(5);
     assertEquals(sf.getBegin(), 17);
     assertEquals(sf.getEnd(), 17);
     assertEquals(sf.getScore(), 0.7f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,G");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     sf = geneFeatures.get(6);
     assertEquals(sf.getBegin(), 17);
     assertEquals(sf.getEnd(), 17); // insertion
     assertEquals(sf.getScore(), 0.6f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,AC");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2);
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
 
     /*
      * verify variant feature(s) added to transcript3
@@ -492,24 +512,25 @@ public class VCFLoaderTest
     assertEquals(sf.getScore(), 0.2f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,T");
     // transcript features only have Consequence for that transcripts
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript3"));
+    map = (Map) sf.getValue("CSQ");
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3");
 
     sf = transcriptFeatures.get(1);
     assertEquals(sf.getBegin(), 11);
     assertEquals(sf.getEnd(), 11);
     assertEquals(sf.getScore(), 0.7f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,G");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript3"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3");
 
     sf = transcriptFeatures.get(2);
     assertEquals(sf.getBegin(), 11);
     assertEquals(sf.getEnd(), 11);
     assertEquals(sf.getScore(), 0.6f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,AC");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript3"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3");
 
     /*
      * verify variants computed on protein product for transcript3
@@ -548,31 +569,31 @@ public class VCFLoaderTest
     assertEquals(sf.getEnd(), 7);
     assertEquals(sf.getScore(), 0.5f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,T");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript4"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4");
 
     sf = transcriptFeatures.get(1);
     assertEquals(sf.getBegin(), 7);
     assertEquals(sf.getEnd(), 7);
     assertEquals(sf.getScore(), 0.4f, DELTA);
     assertEquals(sf.getValue("alleles"), "C,G");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript4"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4");
 
     sf = transcriptFeatures.get(2);
     assertEquals(sf.getBegin(), 11);
     assertEquals(sf.getEnd(), 11);
     assertEquals(sf.getScore(), 0.7f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,G");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript4"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4");
 
     sf = transcriptFeatures.get(3);
     assertEquals(sf.getBegin(), 11);
     assertEquals(sf.getEnd(), 11);
     assertEquals(sf.getScore(), 0.6f, DELTA);
     assertEquals(sf.getValue("alleles"), "A,AC");
-    assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1);
-    assertTrue(sf.getValue("CSQ").toString().contains("transcript4"));
+    assertEquals(map.size(), 9);
+    assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4");
   }
 }
index 438feba..745eec3 100644 (file)
@@ -2,6 +2,7 @@ package jalview.renderer.seqfeatures;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import jalview.api.AlignViewportI;
@@ -12,10 +13,15 @@ import jalview.gui.AlignFrame;
 import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
 import jalview.schemes.FeatureColour;
+import jalview.util.matcher.Condition;
+import jalview.util.matcher.KeyedMatcher;
+import jalview.util.matcher.KeyedMatcherSet;
+import jalview.util.matcher.KeyedMatcherSetI;
 
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -252,6 +258,37 @@ public class FeatureRendererTest
     features = fr.findFeaturesAtColumn(seq, 5);
     assertEquals(features.size(), 1);
     assertTrue(features.contains(sf8));
+    
+    /*
+     * give "Type3" features a graduated colour scheme
+     * - first with no threshold
+     */
+    FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
+            10f);
+    fr.getFeatureColours().put("Type3", gc);
+    features = fr.findFeaturesAtColumn(seq, 8);
+    assertTrue(features.contains(sf4));
+    // now with threshold > 2f - feature score of 1f is excluded
+    gc.setAboveThreshold(true);
+    gc.setThreshold(2f);
+    features = fr.findFeaturesAtColumn(seq, 8);
+    assertFalse(features.contains(sf4));
+
+    /*
+     * make "Type3" graduated colour by attribute "AF"
+     * - first with no attribute held - feature should be excluded
+     */
+    gc.setAttributeName("AF");
+    features = fr.findFeaturesAtColumn(seq, 8);
+    assertFalse(features.contains(sf4));
+    // now with the attribute above threshold - should be included
+    sf4.setValue("AF", "2.4");
+    features = fr.findFeaturesAtColumn(seq, 8);
+    assertTrue(features.contains(sf4));
+    // now with the attribute below threshold - should be excluded
+    sf4.setValue("AF", "1.4");
+    features = fr.findFeaturesAtColumn(seq, 8);
+    assertFalse(features.contains(sf4));
   }
 
   @Test(groups = "Functional")
@@ -319,4 +356,152 @@ public class FeatureRendererTest
     assertFalse(features.contains(sf2) && features.contains(sf3));
     assertTrue(features.contains(sf5));
   }
+
+  @Test(groups = "Functional")
+  public void testGetColour()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
+            DataSourceType.PASTE);
+    AlignViewportI av = af.getViewport();
+    FeatureRenderer fr = new FeatureRenderer(av);
+
+    /*
+     * simple colour, feature type and group displayed
+     */
+    FeatureColourI fc = new FeatureColour(Color.red);
+    fr.getFeatureColours().put("Cath", fc);
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+            "group1");
+    assertEquals(fr.getColour(sf1), Color.red);
+
+    /*
+     * hide feature type, then unhide
+     */
+    Object[][] data = new Object[1][];
+    data[0] = new Object[] { "Cath", fc, false };
+    fr.setFeaturePriority(data);
+    assertNull(fr.getColour(sf1));
+    data[0] = new Object[] { "Cath", fc, true };
+    fr.setFeaturePriority(data);
+    assertEquals(fr.getColour(sf1), Color.red);
+
+    /*
+     * hide feature group, then unhide
+     */
+    fr.setGroupVisibility("group1", false);
+    assertNull(fr.getColour(sf1));
+    fr.setGroupVisibility("group1", true);
+    assertEquals(fr.getColour(sf1), Color.red);
+
+    /*
+     * graduated colour by score, no threshold, no score
+     * 
+     */
+    FeatureColourI gc = new FeatureColour(Color.yellow, Color.red,
+            Color.green, 1f, 11f);
+    fr.getFeatureColours().put("Cath", gc);
+    assertEquals(fr.getColour(sf1), Color.green);
+
+    /*
+     * graduated colour by score, no threshold, with score value
+     */
+    SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
+            "group1");
+    // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
+    Color expected = new Color(255, 128, 0);
+    assertEquals(fr.getColour(sf2), expected);
+    
+    /*
+     * above threshold, score is above threshold - no change
+     */
+    gc.setAboveThreshold(true);
+    gc.setThreshold(5f);
+    assertEquals(fr.getColour(sf2), expected);
+
+    /*
+     * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
+     * or from yellow(255, 255, 0) to red(255, 0, 0)
+     */
+    gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f);
+    fr.getFeatureColours().put("Cath", gc);
+    gc.setAutoScaled(false); // this does little other than save a checkbox setting!
+    assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
+
+    /*
+     * feature score is below threshold - no colour
+     */
+    gc.setAboveThreshold(true);
+    gc.setThreshold(7f);
+    assertNull(fr.getColour(sf2));
+
+    /*
+     * feature score is above threshold - no colour
+     */
+    gc.setBelowThreshold(true);
+    gc.setThreshold(3f);
+    assertNull(fr.getColour(sf2));
+
+    /*
+     * colour by feature attribute value
+     * first with no value held
+     */
+    gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f);
+    fr.getFeatureColours().put("Cath", gc);
+    gc.setAttributeName("AF");
+    assertEquals(fr.getColour(sf2), Color.green);
+
+    // with non-numeric attribute value
+    sf2.setValue("AF", "Five");
+    assertEquals(fr.getColour(sf2), Color.green);
+
+    // with numeric attribute value
+    sf2.setValue("AF", "6");
+    assertEquals(fr.getColour(sf2), expected);
+
+    // with numeric value outwith threshold
+    gc.setAboveThreshold(true);
+    gc.setThreshold(10f);
+    assertNull(fr.getColour(sf2));
+
+    // with filter on AF < 4
+    gc.setAboveThreshold(false);
+    assertEquals(fr.getColour(sf2), expected);
+    KeyedMatcherSetI filter = new KeyedMatcherSet();
+    filter.and(new KeyedMatcher(Condition.LT, 4f, "AF"));
+    fr.setFeatureFilter("Cath", filter);
+    assertNull(fr.getColour(sf2));
+
+    // with filter on 'Consequence contains missense'
+    filter = new KeyedMatcherSet();
+    filter.and(new KeyedMatcher(Condition.Contains, "missense",
+            "Consequence"));
+    fr.setFeatureFilter("Cath", filter);
+    // if feature has no Consequence attribute, no colour
+    assertNull(fr.getColour(sf2));
+    // if attribute does not match filter, no colour
+    sf2.setValue("Consequence", "Synonymous");
+    assertNull(fr.getColour(sf2));
+    // attribute matches filter
+    sf2.setValue("Consequence", "Missense variant");
+    assertEquals(fr.getColour(sf2), expected);
+
+    // with filter on CSQ.Feature contains "ENST01234"
+    filter = new KeyedMatcherSet();
+    filter.and(new KeyedMatcher(Condition.Matches, "ENST01234", "CSQ",
+            "Feature"));
+    fr.setFeatureFilter("Cath", filter);
+    // if feature has no CSQ data, no colour
+    assertNull(fr.getColour(sf2));
+    // if CSQ data does not include Feature, no colour
+    Map<String, String> csqData = new HashMap<>();
+    csqData.put("BIOTYPE", "Transcript");
+    sf2.setValue("CSQ", csqData);
+    assertNull(fr.getColour(sf2));
+    // if attribute does not match filter, no colour
+    csqData.put("Feature", "ENST9876");
+    assertNull(fr.getColour(sf2));
+    // attribute matches filter
+    csqData.put("Feature", "ENST01234");
+    assertEquals(fr.getColour(sf2), expected);
+  }
 }
index db4fe40..72c29d3 100644 (file)
@@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.AssertJUnit.fail;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
 import jalview.datamodel.SequenceFeature;
 import jalview.gui.JvOptionPane;
@@ -33,11 +34,11 @@ import jalview.util.Format;
 
 import java.awt.Color;
 
-import junit.extensions.PA;
-
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class FeatureColourTest
 {
 
@@ -121,7 +122,7 @@ public class FeatureColourTest
     assertTrue(fc1.isColourByLabel());
     assertFalse(fc1.isGraduatedColour());
     assertTrue(fc1.isColourByAttribute());
-    assertEquals("AF", fc1.getAttributeName());
+    assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
 
     /*
      * colour by attribute (value)
@@ -134,7 +135,7 @@ public class FeatureColourTest
     assertTrue(fc1.isGraduatedColour());
     assertFalse(fc1.isColourByLabel());
     assertTrue(fc1.isColourByAttribute());
-    assertEquals("AF", fc1.getAttributeName());
+    assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
     assertTrue(fc1.isAboveThreshold());
     assertEquals(12f, fc1.getThreshold());
     assertEquals(Color.gray, fc1.getMinColour());
@@ -190,7 +191,7 @@ public class FeatureColourTest
     assertTrue(fc1.isGraduatedColour());
     assertFalse(fc1.isColourByLabel());
     assertTrue(fc1.isColourByAttribute());
-    assertEquals("AF", fc1.getAttributeName());
+    assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
     assertEquals(13f, fc1.getMin());
     assertEquals(36f, fc1.getMax());
     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
@@ -226,7 +227,7 @@ public class FeatureColourTest
     assertFalse(fc1.isGraduatedColour());
     assertTrue(fc1.isColourByLabel());
     assertTrue(fc1.isColourByAttribute());
-    assertEquals("AC", fc1.getAttributeName());
+    assertArrayEquals(new String[] { "AC" }, fc1.getAttributeName());
     assertEquals(13f, fc1.getMin());
     assertEquals(36f, fc1.getMax());
   }
index c0cb4ba..41a313f 100644 (file)
@@ -85,7 +85,6 @@ public class ViewportRangesTest {
     vr.setEndSeq(al.getHeight());
     assertEquals(vr.getEndSeq(), al.getHeight() - 1);
 
-    // vr.setEndRes(al.getHeight() - 1);
     vr.setEndSeq(al.getHeight() - 1);
     assertEquals(vr.getEndSeq(), al.getHeight() - 1);
   }
@@ -169,6 +168,24 @@ public class ViewportRangesTest {
   }
 
   @Test(groups = { "Functional" })
+  public void testSetStartResAndSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setViewportHeight(10);
+    vr.setStartResAndSeq(3, 6);
+    assertEquals(vr.getStartRes(), 3);
+    assertEquals(vr.getStartSeq(), 6);
+    assertEquals(vr.getEndRes(), 3 + vr.getViewportWidth() - 1);
+    assertEquals(vr.getEndSeq(), 6 + vr.getViewportHeight() - 1);
+
+    vr.setStartResAndSeq(10, 25);
+    assertEquals(vr.getStartRes(), 10);
+    assertEquals(vr.getStartSeq(), 19);
+    assertEquals(vr.getEndRes(), 10 + vr.getViewportWidth() - 1);
+    assertEquals(vr.getEndSeq(), 19 + vr.getViewportHeight() - 1);
+  }
+
+  @Test(groups = { "Functional" })
   public void testSetViewportHeight()
   {
     ViewportRanges vr = new ViewportRanges(al);
@@ -385,14 +402,13 @@ public class ViewportRangesTest {
     assertEquals(vr.getEndRes(), 52);
   }
 
-  // leave until JAL-2388 is merged and we can do without viewport
-  /*@Test(groups = { "Functional" })
+  @Test(groups = { "Functional" })
   public void testScrollToVisible()
   {
     ViewportRanges vr = new ViewportRanges(al);
     vr.setViewportStartAndWidth(12,5);
     vr.setViewportStartAndHeight(10,6);
-    vr.scrollToVisible(13,14)
+    vr.scrollToVisible(13, 14);
     
     // no change
     assertEquals(vr.getStartRes(), 12);
@@ -403,7 +419,15 @@ public class ViewportRangesTest {
     assertEquals(vr.getStartSeq(), 6);
     
     // test for hidden columns too
-  }*/
+    al.getHiddenColumns().hideColumns(1, 3);
+    vr.scrollToVisible(13, 3);
+    assertEquals(vr.getStartRes(), 6);
+    assertEquals(vr.getStartSeq(), 3);
+
+    vr.scrollToVisible(2, 9);
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getStartSeq(), 4);
+  }
 
   @Test(groups = { "Functional" })
   public void testEventFiring()
@@ -418,7 +442,7 @@ public class ViewportRangesTest {
 
     // one event fired when startRes is called with new value
     vr.setStartRes(4);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
     l.reset();
 
     // no event fired for same value
@@ -427,7 +451,7 @@ public class ViewportRangesTest {
     l.reset();
 
     vr.setStartSeq(4);
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.setStartSeq(4);
@@ -435,7 +459,7 @@ public class ViewportRangesTest {
     l.reset();
 
     vr.setEndSeq(10);
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.setEndSeq(10);
@@ -443,7 +467,7 @@ public class ViewportRangesTest {
     l.reset();
 
     vr.setStartEndRes(2, 15);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
     l.reset();
 
     vr.setStartEndRes(2, 15);
@@ -452,16 +476,18 @@ public class ViewportRangesTest {
 
     // check new value fired by event is corrected startres
     vr.setStartEndRes(-1, 5);
-    assertTrue(l.verify(1, Arrays.asList("startres"), Arrays.asList(0)));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.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)));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDRES),
+            Arrays.asList(0)));
     l.reset();
 
     vr.setStartEndSeq(2, 15);
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.setStartEndSeq(2, 15);
@@ -474,12 +500,14 @@ public class ViewportRangesTest {
 
     // check new value fired by event is corrected startseq
     vr.setStartEndSeq(-1, 5);
-    assertTrue(l.verify(1, Arrays.asList("startseq"), Arrays.asList(0)));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.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)));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ),
+            Arrays.asList(0)));
     l.reset();
 
     // reset for later tests
@@ -488,51 +516,52 @@ public class ViewportRangesTest {
 
     // test viewport height and width setting triggers event
     vr.setViewportHeight(10);
-    assertTrue(l.verify(1, Arrays.asList("endseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ)));
     l.reset();
 
     vr.setViewportWidth(18);
-    assertTrue(l.verify(1, Arrays.asList("endres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDRES)));
     l.reset();
 
     // already has seq start set to 2, so triggers endseq
     vr.setViewportStartAndHeight(2, 16);
-    assertTrue(l.verify(1, Arrays.asList("endseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ)));
     l.reset();
 
     vr.setViewportStartAndWidth(1, 14);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
     l.reset();
 
     // test page up/down triggers event
     vr.pageUp();
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.pageDown();
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     // test scrolling triggers event
     vr.scrollUp(true);
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.scrollUp(false);
-    assertTrue(l.verify(1, Arrays.asList("startseq")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ)));
     l.reset();
 
     vr.scrollRight(true);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
     l.reset();
 
     vr.scrollRight(false);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
     l.reset();
 
     vr.scrollToVisible(10, 10);
     assertTrue(l.verify(4,
-            Arrays.asList("startseq", "startseq", "startseq", "startseq")));
+            Arrays.asList(ViewportRanges.STARTSEQ, ViewportRanges.STARTSEQ,
+                    ViewportRanges.STARTSEQ, ViewportRanges.STARTSEQ)));
     l.reset();
 
     /*
@@ -544,7 +573,15 @@ public class ViewportRangesTest {
     l.reset();
 
     vr.scrollToWrappedVisible(25);
-    assertTrue(l.verify(1, Arrays.asList("startres")));
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES)));
+    l.reset();
+
+    // test setStartResAndSeq triggers one event
+    vr.setStartResAndSeq(5, 7);
+    assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRESANDSEQ),
+            Arrays.asList(5, 7)));
+
+    l.reset();
   }
 
   @Test(groups = { "Functional" })
@@ -823,6 +860,76 @@ public class ViewportRangesTest {
     assertEquals(vr.getStartSeq(), 1);
     assertEquals(vr.getStartRes(), 43);
   }
+
+  @Test(groups = { "Functional" })
+  public void testSetViewportLocation()
+  {
+    AlignmentI al2 = gen.generate(60, 80, 1, 0, 0);
+
+    ViewportRanges vr = new ViewportRanges(al2);
+
+    // start with viewport on 5-14
+    vr.setViewportStartAndWidth(5, 10);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 14);
+
+    vr.setViewportStartAndHeight(3, 13);
+    assertEquals(vr.getStartSeq(), 3);
+    assertEquals(vr.getEndSeq(), 15);
+
+    // set location to (8,5) - no change
+    vr.setViewportLocation(8, 5);
+    assertEquals(vr.getStartRes(), 5);
+    assertEquals(vr.getEndRes(), 14);
+    assertEquals(vr.getStartSeq(), 3);
+    assertEquals(vr.getEndSeq(), 15);
+
+    // set location to (40,50) - change to top left (40,50)
+    vr.setViewportLocation(40, 50);
+    assertEquals(vr.getStartRes(), 40);
+    assertEquals(vr.getEndRes(), 49);
+    assertEquals(vr.getStartSeq(), 50);
+    assertEquals(vr.getEndSeq(), 62);
+
+    // set location past end of alignment - resets to leftmost pos
+    vr.setViewportLocation(63, 85);
+    assertEquals(vr.getStartRes(), 50);
+    assertEquals(vr.getEndRes(), 59);
+    assertEquals(vr.getStartSeq(), 67);
+    assertEquals(vr.getEndSeq(), 79);
+
+    // hide some columns
+    al2.getHiddenColumns().hideColumns(20, 50);
+    vr.setViewportLocation(55, 4);
+    assertEquals(vr.getStartRes(), 19);
+    assertEquals(vr.getEndRes(), 28);
+    assertEquals(vr.getStartSeq(), 4);
+    assertEquals(vr.getEndSeq(), 16);
+
+    // hide some sequences
+    al2.getHiddenSequences().hideSequence(al2.getSequenceAt(3));
+    al2.getHiddenSequences().hideSequence(al2.getSequenceAt(4));
+    vr.setViewportLocation(17, 5);
+    assertEquals(vr.getStartRes(), 17);
+    assertEquals(vr.getEndRes(), 26);
+    assertEquals(vr.getStartSeq(), 3);
+    assertEquals(vr.getEndSeq(), 15);
+
+    // set wrapped mode
+    vr.setWrappedMode(true);
+    vr.setViewportLocation(1, 8);
+    assertEquals(vr.getStartRes(), 0);
+    assertEquals(vr.getEndRes(), 9);
+    assertEquals(vr.getStartSeq(), 3);
+    assertEquals(vr.getEndSeq(), 15);
+
+    // try further down the alignment
+    vr.setViewportLocation(57, 5);
+    assertEquals(vr.getStartRes(), 20);
+    assertEquals(vr.getEndRes(), 29);
+    assertEquals(vr.getStartSeq(), 3);
+    assertEquals(vr.getEndSeq(), 15);
+  }
 }
 
 // mock listener for property change events
@@ -844,7 +951,15 @@ class MockPropChangeListener implements ViewportListenerI
   {
     firecount++;
     events.add(evt.getPropertyName());
-    newvalues.add((Integer) evt.getNewValue());
+    if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      newvalues.add(((int[]) evt.getNewValue())[0]);
+      newvalues.add(((int[]) evt.getNewValue())[1]);
+    }
+    else
+    {
+      newvalues.add((Integer) evt.getNewValue());
+    }
   }
 
   public boolean verify(int count, List<String> eventslist,
index 2d4be71..f98ef85 100644 (file)
@@ -163,11 +163,11 @@ public class UniprotTest
             new StringReader(UNIPROT_XML)).get(0);
 
     /*
-     * name formatted as source | accession ids | names
-     * source database converted to Jalview canonical name
+     * name formatted with Uniprot Entry name
      */
-    String expectedName = "UNIPROT|A9CKP4|A9CKP5|A9CKP4_AGRT5|A9CKP4_AGRT6";
-    assertEquals(expectedName, Uniprot.getUniprotEntryId(entry));
+    String expectedName = "A9CKP4_AGRT5|A9CKP4_AGRT6";
+    assertEquals(expectedName,
+            Uniprot.getUniprotEntryId(entry));
   }
 
   /**
index abe9d4b..a649cb4 100755 (executable)
@@ -2025,7 +2025,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[groovy-all-2.4.6-indy.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2043,7 +2043,7 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[groovy-all-2.4.6-indy.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
                                                                <long>6149494</long>
@@ -5270,7 +5270,7 @@ Press "Done" to quit the installer.]]></string>
                                        </method>
                                        <method name="put">
                                                <string><![CDATA[com.zerog.ia.installer.options.valid.vm.list]]></string>
-                                               <string><![CDATA[1.8+]]></string>
+                                               <string><![CDATA[1.8*]]></string>
                                        </method>
                                        <method name="put">
                                                <string><![CDATA[com.zerog.ia.project.build.last.date]]></string>
@@ -5366,7 +5366,7 @@ Press "Done" to quit the installer.]]></string>
                                        </method>
                                        <method name="put">
                                                <string><![CDATA[com.zerog.ia.installer.options.platform.macosx.vm.version]]></string>
-                                               <string><![CDATA[1.8+]]></string>
+                                               <string><![CDATA[1.8*]]></string>
                                        </method>
                                        <method name="put">
                                                <string><![CDATA[com.zerog.ia.build.platform.java.novm]]></string>
index 4489a93..c870f6d 100644 (file)
@@ -24,6 +24,7 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Properties;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.regex.Pattern;
 
@@ -89,7 +90,9 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
 
   private int javaCount;
 
-  private HashSet<String> invalidKeys;
+  private Set<String> invalidKeys;
+
+  private Set<String> dynamicKeys;
 
   /**
    * Runs the scan given the path to the root of Java source directories
@@ -125,7 +128,7 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
   private void doMain(String srcPath) throws IOException
   {
     System.out.println("Scanning " + srcPath
-            + " for calls to MessageManager");
+            + " for calls to MessageManager\n");
     sourcePath = srcPath;
     loadMessages();
     File dir = new File(srcPath);
@@ -134,7 +137,10 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
       System.out.println(srcPath + " not found");
       return;
     }
-    invalidKeys = new HashSet<String>();
+
+    invalidKeys = new HashSet<>();
+    dynamicKeys = new HashSet<>();
+
     if (dir.isDirectory())
     {
       scanDirectory(dir);
@@ -152,17 +158,60 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
   private void reportResults()
   {
     System.out.println("\nScanned " + javaCount + " source files");
-    System.out.println("Message.properties has " + messages.size()
+    System.out.println(
+            "Messages.properties has " + messages.size()
             + " keys");
-    System.out.println("Found " + invalidKeys.size()
-            + " possibly invalid parameter calls");
+    if (!invalidKeys.isEmpty())
+    {
+      System.out.println("Found " + invalidKeys.size()
+              + " possibly invalid parameter call"
+              + (invalidKeys.size() > 1 ? "s" : ""));
+    }
 
-    System.out.println(messageKeys.size()
-            + " keys not found, either unused or constructed dynamically");
+    System.out.println("Keys not found, assumed constructed dynamically:");
+    int dynamicCount = 0;
     for (String key : messageKeys)
     {
-      System.out.println("    " + key);
+      if (isDynamic(key))
+      {
+        System.out.println("    " + key);
+        dynamicCount++;
+      }
+    }
+
+    if (dynamicCount < messageKeys.size())
+    {
+      System.out.println((messageKeys.size() - dynamicCount)
+              + " keys not found, possibly unused");
+      for (String key : messageKeys)
+      {
+        if (!isDynamic(key))
+        {
+          System.out.println("    " + key);
+        }
+      }
+    }
+    System.out
+            .println("(Run i18nAnt.xml to compare other message bundles)");
+  }
+
+  /**
+   * Answers true if the key starts with one of the recorded dynamic key stubs,
+   * else false
+   * 
+   * @param key
+   * @return
+   */
+  private boolean isDynamic(String key)
+  {
+    for (String dynamic : dynamicKeys)
+    {
+      if (key.startsWith(dynamic))
+      {
+        return true;
+      }
     }
+    return false;
   }
 
   /**
@@ -275,14 +324,17 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
         continue;
       }
 
+      String messageKey = getMessageKey(method, methodArgs);
+
       if (METHOD3 == method)
       {
         System.out.println(String.format("Dynamic key at %s line %s %s",
                 path.substring(sourcePath.length()), lineNos, line));
+        String key = messageKey.substring(1, messageKey.length() - 1);
+        dynamicKeys.add(key);
         continue;
       }
 
-      String messageKey = getMessageKey(method, methodArgs);
       if (messageKey == null)
       {
         System.out.println(String.format("Trouble parsing %s line %s %s",
@@ -370,7 +422,7 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner
     messages.load(reader);
     reader.close();
 
-    messageKeys = new TreeSet<String>();
+    messageKeys = new TreeSet<>();
     for (Object key : messages.keySet())
     {
       messageKeys.add((String) key);