Merge branch 'develop' into features/JAL-1933_occupancy
authorJim Procter <jprocter@issues.jalview.org>
Thu, 27 Apr 2017 10:11:48 +0000 (11:11 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Thu, 27 Apr 2017 10:11:48 +0000 (11:11 +0100)
116 files changed:
help/html/colourSchemes/user.html
help/html/colourSchemes/userDefined_java6.gif [deleted file]
help/html/colourSchemes/userDefined_java7.gif
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/analysis/AAFrequency.java
src/jalview/api/AlignViewportI.java
src/jalview/api/FeatureRenderer.java
src/jalview/api/SequenceRenderer.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationColourChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/ExtJmol.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/SequenceRenderer.java
src/jalview/bin/Cache.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/HiddenSequences.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/varna/VarnaCommands.java
src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AnnotationRowFilter.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ColourMenuHelper.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/JDatabaseTree.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceRenderer.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TextColourChooser.java
src/jalview/gui/TreeCanvas.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/JSONFile.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/jbgui/GUserDefinedColours.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/seqfeatures/FeatureColourFinder.java [new file with mode: 0644]
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/schemes/ColourSchemeLoader.java [new file with mode: 0644]
src/jalview/schemes/ColourSchemes.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/OverviewDimensions.java [new file with mode: 0644]
src/jalview/viewmodel/ViewportProperties.java [new file with mode: 0644]
src/jalview/viewmodel/ViewportRanges.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/workers/ConservationThread.java
src/jalview/ws/DasSequenceFeatureFetcher.java
src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java
src/jalview/ws/jws2/Jws2Discoverer.java
src/jalview/ws/sifts/SiftsClient.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/HiddenSequencesTest.java
test/jalview/datamodel/SequenceGroupTest.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/gui/AlignmentPanelTest.java [new file with mode: 0644]
test/jalview/gui/AnnotationRowFilterTest.java [new file with mode: 0644]
test/jalview/gui/SequenceRendererTest.java
test/jalview/io/JSONFileTest.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java [new file with mode: 0644]
test/jalview/schemes/AnnotationColourGradientTest.java [new file with mode: 0644]
test/jalview/schemes/ColourSchemesTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/viewmodel/OverviewDimensionsTest.java [new file with mode: 0644]
test/jalview/viewmodel/ViewportRangesTest.java [new file with mode: 0644]
test/jalview/ws/jabaws/DisorderAnnotExportImport.java
test/jalview/ws/jabaws/RNAStructExportImport.java
test/jalview/ws/jws2/ParameterUtilsTest.java
test/jalview/ws/seqfetcher/DasSequenceFetcher.java
test/jalview/ws/sifts/SiftsClientTest.java
test/junit/extensions/PA.java [new file with mode: 0644]
test/junit/extensions/PrivilegedAccessor.java [new file with mode: 0644]

index fb6c356..c2fde1c 100755 (executable)
@@ -27,7 +27,7 @@
     <strong>User Defined Colours</strong>
   </p>
   <p>
-    <img src="userDefined_java6.gif" width="815" height="402">
+    <img src="userDefined_java7.gif" width="815" height="402">
   </p>
   <p>
     You may define any number of new colour schemes, each with a unique
@@ -41,7 +41,7 @@
     The <strong>Case Sensitive</strong> option allows you to choose
     distinct colours for upper and lower case residue codes.
   <p>
-    The <strong>Lower Case Colour</strong> option allows you to apply a selected colour
+    The <strong>Colour All Lower Case  </strong> option allows you to apply a selected colour
     to all lower case residues.
   <p>
     Click <strong>Apply</strong> or <strong>OK</strong> to set your new
   <br> Any saved colour schemes will be automatically loaded the
   next time you use Jalview.
   <br>
-  <br>
-  <em>Note: the screenshot shows the appearance when running Java
-    version 6. For Java 7 (from Jalview 2.8.2) only the Swatches colour
-    chooser is currently supported (for reasons of available screen
-    space).</em>
-  <p />
 </body>
 </html>
diff --git a/help/html/colourSchemes/userDefined_java6.gif b/help/html/colourSchemes/userDefined_java6.gif
deleted file mode 100644 (file)
index d737e80..0000000
Binary files a/help/html/colourSchemes/userDefined_java6.gif and /dev/null differ
index f0ced11..de80b84 100644 (file)
Binary files a/help/html/colourSchemes/userDefined_java7.gif and b/help/html/colourSchemes/userDefined_java7.gif differ
index 138cf89..4ebca62 100644 (file)
@@ -3,6 +3,9 @@ action.reset_services = Reset Services
 action.merge_results = Merge Results
 action.load_scheme = Load scheme
 action.save_scheme = Save scheme
+label.scheme_changed = Changes to scheme ''{0}'' have not been saved.<br><br>Save changes, or continue without saving to make a new colour scheme.
+label.save_changes = Save Changes
+label.dont_save_changes = Don't Save
 action.save_image = Save Image
 action.paste = Paste
 action.show_html_source = Show HTML Source
@@ -68,7 +71,6 @@ action.show_gaps = Show Gaps
 action.show_hidden_markers = Show Hidden Markers
 action.find = Find
 action.undefine_groups = Undefine Groups
-action.create_groups = Create Groups
 action.make_groups_selection = Make Groups For Selection
 action.copy = Copy
 action.cut = Cut
@@ -515,7 +517,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences =
 label.standard_databases = Standard Databases
 label.fetch_embl_uniprot = Fetch from EMBL/EMBLCDS or Uniprot/PDB and any selected DAS sources
 label.reset_min_max_colours_to_defaults = Reset min and max colours to defaults from user preferences.
-label.align_structures_using_linked_alignment_views = Superpose structures using selected alignment view
+label.align_structures_using_linked_alignment_views = Superpose structures using {0} selected alignment view(s)
 label.connect_to_session = Connect to session {0}
 label.threshold_feature_display_by_score = Threshold the feature display by score.
 label.threshold_feature_no_threshold = No Threshold
@@ -669,8 +671,7 @@ label.2d_rna_sequence_name = 2D RNA - {0}
 label.edit_name_and_description_current_group = Edit name and description of current group
 label.from_file = From File
 label.enter_pdb_id = Enter PDB Id (or pdbid:chaincode)
-label.text_colour = Text Colour
-action.set_text_colour = Text Colour...
+label.text_colour = Text Colour...
 label.structure = Structure
 label.show_pdbstruct_dialog = 3D Structure Data...
 label.view_rna_structure = VARNA 2D Structure
@@ -716,10 +717,13 @@ label.pdb_file = PDB file
 label.colour_with_jmol = Colour with Jmol
 label.colour_with_chimera = Colour with Chimera
 label.superpose_structures = Superpose Structures
+error.superposition_failed = Superposition failed: {0}
+label.insufficient_residues = Not enough aligned residues ({0}) to perform superposition
 label.jmol = Jmol
 label.chimera = Chimera
 label.create_chimera_attributes = Write Jalview features
 label.create_chimera_attributes_tip = Set Chimera residue attributes for visible features
+label.attributes_set = {0} attribute values set on Chimera
 label.sort_alignment_by_tree = Sort Alignment By Tree
 label.mark_unlinked_leaves = Mark Unlinked Leaves
 label.associate_leaves_with = Associate Leaves With
index d408fee..bdd61fe 100644 (file)
@@ -3,6 +3,9 @@ action.reset_services = Reiniciar servicios
 action.merge_results = Unificar resultados
 action.load_scheme = Cargar esquema
 action.save_scheme = Guardar esquema
+label.scheme_changed = Cambios en el esquema ''{0}'' no se han guardado.<br><br>Guardar cambios, o continuar sin guardar para hacer un nuevo esquema.
+label.save_changes = Guardar cambios
+label.dont_save_changes = No guardar
 action.save_image = Guardar imagen
 action.paste = Pegar
 action.show_html_source = Mostrar código HTML
@@ -66,7 +69,6 @@ action.show_gaps = Mostrar huecos
 action.show_hidden_markers = Mostrar marcadores ocultos
 action.find = Buscar
 action.undefine_groups = Grupos sin definir
-action.create_groups = Crear grupos
 action.make_groups_selection = Hacer grupos para seleccionar
 action.copy = Copiar
 action.cut = Cortar
@@ -476,7 +478,7 @@ label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences =
 label.standard_databases = Bases de datos estándar
 label.fetch_embl_uniprot = Recuperar de EMBL/EMBLCDS o Uniprot/PDB y de cualquier fuente DAS seleccionada
 label.reset_min_max_colours_to_defaults = Reiniciar los colores min y max colours a los valores por defecto establecidos en las preferencias de usuario
-label.align_structures_using_linked_alignment_views = Alinear las estructuras utlizando las {0} vistas de alineamiento enlazadas
+label.align_structures_using_linked_alignment_views = Alinear las estructuras utilizando las {0} vista(s) de alineamiento enlazada(s)
 label.connect_to_session = Conectar a la sesión {0}
 label.threshold_feature_display_by_score = Filtrar la característica mostrada por puntuación.
 label.threshold_feature_no_threshold = Sin umbral
@@ -621,7 +623,7 @@ label.2d_rna_sequence_name = 2D RNA - {0}
 label.edit_name_and_description_current_group = Editar el nombre y la descripción del grupo actual
 label.from_file = desde fichero
 label.enter_pdb_id = Introducir PDB Id
-label.text_colour = Color del texto
+label.text_colour = Color de texto...
 label.structure = Estructura
 label.create_sequence_details_report_annotation_for = Anotación para {0}
 label.sequence_details_for = Detalles de la secuencia para {0}
@@ -1141,6 +1143,9 @@ action.annotations=Anotaciones
 label.nuc_alignment_colour=Color del Alineamiento Nucleotídico
 label.copy_format_from=Copiar formato de
 label.chimera=Chimera
+label.create_chimera_attributes = Escribir características de Jalview
+label.create_chimera_attributes_tip = Establecer atributos en Chimera para características visibles 
+label.attributes_set = {0} valores de atributos establecidos en Chimera
 label.open_split_window=Abrir ventana dividida
 label.open_split_window?=¿Quieres abrir ventana dividida, con cDNA y proteína vinculadas?
 status.searching_for_pdb_structures=Buscando Estructuras PDB
@@ -1158,7 +1163,6 @@ label.invalid_search=Texto de b
 action.export_annotations=Exportar Anotaciones
 action.set_as_reference=Marcar como Referencia
 action.unmark_as_reference=Desmarcar como Referencia
-action.set_text_colour=Color de Texto...
 label.chimera_failed=Error al abrir Chimera - está instalado?\nCompruebe ruta en Preferencias, Estructura
 label.find=Buscar
 label.select_pdb_file=Seleccionar Fichero PDB
@@ -1180,9 +1184,14 @@ label.hide_insertions=Ocultar Inserciones
 info.change_threshold_mode_to_enable=Cambiar Modo de Umbral para Habilitar
 label.separate_multiple_query_values=Introducir uno o mas {0}s separados por punto y coma ";"
 label.let_chimera_manage_structure_colours=Deja que Chimera maneje colores de estructuras
+label.fetch_chimera_attributes = Buscar atributos desde Chimera
+label.fetch_chimera_attributes_tip = Copiar atributo de Chimera a característica de Jalview
 label.view_rna_structure=Estructura 2D VARNA
 label.scale_protein_to_cdna_tip=Hacer a los residuos de proteínas de la misma anchura que los codones en ventanas divididas
 label.colour_with_chimera=Colorear con Chimera
+label.superpose_structures = Superponer estructuras
+error.superposition_failed = Superposición fallido: {0}
+label.insufficient_residues = Residuos alineados ({0}) insuficentes para superponer
 label.show_pdbstruct_dialog=Datos de Estructura 3D...
 label.hide_all=Ocultar todos
 label.invert=Invertir
@@ -1288,4 +1297,4 @@ label.edit_sequence_url_link = Editar link de secuencia URL
 warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben ser Ãºnicos y no pueden ser ids de MIRIAM
 label.invalid_name = Nombre inválido !
 label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
-label.urllinks = Enlaces
\ No newline at end of file
+label.urllinks = Enlaces
index aac796c..3ae0650 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
@@ -577,6 +578,8 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
       showFeatures = true;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+
     PDBChain chain;
     if (bysequence && pdb != null)
     {
@@ -604,25 +607,16 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.startCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.startCol = fr.findFeatureColour(tmp.startCol,
-                            sequence[s], pos);
-                  }
+                  tmp.startCol = sr.getResidueColour(sequence[s], pos,
+                          finder);
                 }
                 pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1;
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.endCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.endCol = fr.findFeatureColour(tmp.endCol,
-                            sequence[s], pos);
-                  }
+                  tmp.endCol = sr
+                          .getResidueColour(sequence[s], pos, finder);
                 }
-
               }
             }
           }
index 292de91..08bca8c 100644 (file)
@@ -28,6 +28,7 @@ import jalview.gui.FeatureRenderer;
 import jalview.gui.SequenceRenderer;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
@@ -546,6 +547,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
       showFeatures = true;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
     PDBChain chain;
     if (bysequence && pdb != null)
     {
@@ -573,23 +575,15 @@ public class PDBCanvas extends JPanel implements MouseListener,
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.startCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.startCol = fr.findFeatureColour(tmp.startCol,
-                            sequence[s], pos);
-                  }
+                  tmp.startCol = sr.getResidueColour(sequence[s], pos,
+                          finder);
                 }
                 pos = mapping[m].getSeqPos(tmp.at2.resNumber) - 1;
                 if (pos > 0)
                 {
                   pos = sequence[s].findIndex(pos);
-                  tmp.endCol = sr.getResidueBoxColour(sequence[s], pos);
-                  if (showFeatures)
-                  {
-                    tmp.endCol = fr.findFeatureColour(tmp.endCol,
-                            sequence[s], pos);
-                  }
+                  tmp.endCol = sr
+                          .getResidueColour(sequence[s], pos, finder);
                 }
 
               }
index ffa413b..ee16f94 100755 (executable)
@@ -291,7 +291,7 @@ public class AAFrequency
   /**
    * Derive the gap count annotation row.
    * 
-   * @param consensus
+   * @param gaprow
    *          the annotation row to add annotations to
    * @param profiles
    *          the source consensus data
@@ -300,11 +300,11 @@ public class AAFrequency
    * @param endCol
    *          end column (exclusive)
    */
-  public static void completeGapAnnot(AlignmentAnnotation consensus,
+  public static void completeGapAnnot(AlignmentAnnotation gaprow,
           ProfilesI profiles, int startCol, int endCol, long nseq)
   {
-    if (consensus == null || consensus.annotations == null
-            || consensus.annotations.length < endCol)
+    if (gaprow == null || gaprow.annotations == null
+            || gaprow.annotations.length < endCol)
     {
       /*
        * called with a bad alignment annotation row 
@@ -313,8 +313,8 @@ public class AAFrequency
       return;
     }
     // always set ranges again
-    consensus.graphMax = nseq;
-    consensus.graphMin = 0;
+    gaprow.graphMax = nseq;
+    gaprow.graphMin = 0;
     for (int i = startCol; i < endCol; i++)
     {
       ProfileI profile = profiles.get(i);
@@ -324,7 +324,7 @@ public class AAFrequency
          * happens if sequences calculated over were 
          * shorter than alignment width
          */
-        consensus.annotations[i] = null;
+        gaprow.annotations[i] = null;
         return;
       }
 
@@ -332,7 +332,7 @@ public class AAFrequency
 
       String description = String.valueOf(gapped);
 
-      consensus.annotations[i] = new Annotation(description, description,
+      gaprow.annotations[i] = new Annotation(description, description,
               '\0',
               gapped);
     }
index 634521c..8b07340 100644 (file)
@@ -33,6 +33,7 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.util.Hashtable;
@@ -46,7 +47,13 @@ import java.util.Map;
 public interface AlignViewportI extends ViewStyleI
 {
 
-  int getEndRes();
+  /**
+   * Get the ranges object containing details of the start and end sequences and
+   * residues
+   * 
+   * @return
+   */
+  public ViewportRanges getRanges();
 
   /**
    * calculate the height for visible annotation, revalidating bounds where
index f54231e..7123b8c 100644 (file)
@@ -24,6 +24,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 
 import java.awt.Color;
+import java.awt.Graphics;
 import java.util.List;
 import java.util.Map;
 
@@ -37,18 +38,32 @@ public interface FeatureRenderer
 {
 
   /**
-   * compute the perceived colour for a given column position in sequenceI,
-   * taking transparency and feature visibility into account.
+   * Computes the feature colour for a given sequence and column position,
+   * taking into account sequence feature locations, feature colour schemes,
+   * render ordering, feature and feature group visibility, and transparency.
+   * <p>
+   * The graphics argument should be provided if transparency is applied
+   * (getTransparency() < 1). With feature transparency, visible features are
+   * written to the graphics context and the composite colour may be read off
+   * from it. In this case, the returned feature colour is not the composite
+   * colour but that of the last feature drawn.
+   * <p>
+   * If no transparency applies, then the graphics argument may be null, and the
+   * returned colour is the one that would be drawn for the feature.
+   * <p>
+   * Returns null if there is no visible feature at the position.
+   * <p>
+   * This is provided to support rendering of feature colours other than on the
+   * sequence alignment, including by structure viewers and the overview window.
+   * Note this method takes no account of whether the sequence or column is
+   * hidden.
    * 
-   * @param col
-   *          - background colour (due to alignment/group shading schemes, etc).
-   * @param sequenceI
-   *          - sequence providing features
-   * @param r
-   *          - column position
+   * @param sequence
+   * @param column
+   * @param g
    * @return
    */
-  Color findFeatureColour(Color col, SequenceI sequenceI, int r);
+  Color findFeatureColour(SequenceI sequence, int column, Graphics g);
 
   /**
    * trigger the feature discovery process for a newly created feature renderer.
@@ -170,4 +185,19 @@ public interface FeatureRenderer
    */
   void setVisible(String featureType);
 
+  /**
+   * Sets the transparency value, between 0 (full transparency) and 1 (no
+   * transparency)
+   * 
+   * @param value
+   */
+  void setTransparency(float value);
+
+  /**
+   * Returns the transparency value, between 0 (full transparency) and 1 (no
+   * transparency)
+   * 
+   * @return
+   */
+  float getTransparency();
 }
index d708902..54f7fb6 100644 (file)
 package jalview.api;
 
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 
 import java.awt.Color;
 
 public interface SequenceRenderer
 {
 
-  Color getResidueBoxColour(SequenceI sequenceI, int r);
-
-  Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr);
+  Color getResidueColour(SequenceI seq, int position,
+          FeatureColourFinder finder);
 
 }
index 6a0b390..2646ede 100644 (file)
@@ -75,6 +75,7 @@ import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Canvas;
@@ -420,6 +421,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   @Override
   public void keyPressed(KeyEvent evt)
   {
+    ViewportRanges ranges = viewport.getRanges();
+
     if (viewport.cursorMode
             && ((evt.getKeyCode() >= KeyEvent.VK_0 && evt.getKeyCode() <= KeyEvent.VK_9) || (evt
                     .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt
@@ -571,8 +574,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
               new String[] { (viewport.cursorMode ? "on" : "off") }));
       if (viewport.cursorMode)
       {
-        alignPanel.seqPanel.seqCanvas.cursorX = viewport.startRes;
-        alignPanel.seqPanel.seqCanvas.cursorY = viewport.startSeq;
+        alignPanel.seqPanel.seqCanvas.cursorX = ranges.getStartRes();
+        alignPanel.seqPanel.seqCanvas.cursorY = ranges.getStartSeq();
       }
       break;
 
@@ -598,8 +601,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       }
       else
       {
-        alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                - viewport.endSeq + viewport.startSeq);
+        alignPanel.setScrollValues(ranges.getStartRes(),
+                2 * ranges.getStartSeq() - ranges.getEndSeq());
       }
       break;
 
@@ -610,8 +613,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       }
       else
       {
-        alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                + viewport.endSeq - viewport.startSeq);
+        alignPanel
+                .setScrollValues(ranges.getStartRes(), ranges.getEndSeq());
       }
       break;
 
@@ -1068,6 +1071,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       delete_actionPerformed();
     }
+    else if (source == createGroup)
+    {
+      createGroup_actionPerformed();
+    }
+    else if (source == unGroup)
+    {
+      unGroup_actionPerformed();
+    }
     else if (source == grpsFromSelection)
     {
       makeGrpsFromSelection_actionPerformed();
@@ -2063,7 +2074,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
             seqs, 0, viewport.getAlignment().getWidth(),
             viewport.getAlignment()));
 
-    viewport.setEndSeq(viewport.getAlignment().getHeight());
+    viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight());
     viewport.getAlignment().getWidth();
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
@@ -2299,6 +2310,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   void trimAlignment(boolean trimLeft)
   {
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
     ColumnSelection colSel = viewport.getColumnSelection();
     int column;
 
@@ -2321,20 +2334,20 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       }
       else
       {
-        seqs = viewport.getAlignment().getSequencesArray();
+        seqs = al.getSequencesArray();
       }
 
       TrimRegionCommand trimRegion;
       if (trimLeft)
       {
         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
-                column, viewport.getAlignment());
-        viewport.setStartRes(0);
+                column, al);
+        ranges.setStartRes(0);
       }
       else
       {
         trimRegion = new TrimRegionCommand("Remove Right", false, seqs,
-                column, viewport.getAlignment());
+                column, al);
       }
 
       statusBar.setText(MessageManager.formatMessage(
@@ -2343,23 +2356,25 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
                       .toString() }));
       addHistoryItem(trimRegion);
 
-      for (SequenceGroup sg : viewport.getAlignment().getGroups())
+      for (SequenceGroup sg : al.getGroups())
       {
         if ((trimLeft && !sg.adjustForRemoveLeft(column))
                 || (!trimLeft && !sg.adjustForRemoveRight(column)))
         {
-          viewport.getAlignment().deleteGroup(sg);
+          al.deleteGroup(sg);
         }
       }
 
-      viewport.firePropertyChange("alignment", null, viewport
-              .getAlignment().getSequences());
+      viewport.firePropertyChange("alignment", null, al.getSequences());
     }
   }
 
   public void removeGappedColumnMenuItem_actionPerformed()
   {
-    int start = 0, end = viewport.getAlignment().getWidth() - 1;
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
+    int start = 0;
+    int end = ranges.getAbsoluteAlignmentWidth() - 1;
 
     SequenceI[] seqs;
     if (viewport.getSelectionGroup() != null)
@@ -2387,22 +2402,24 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     // This is to maintain viewport position on first residue
     // of first sequence
-    SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    SequenceI seq = al.getSequenceAt(0);
+    int startRes = seq.findPosition(ranges.getStartRes());
     // ShiftList shifts;
     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
     // edit.alColumnChanges=shifts.getInverse();
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
-    viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-            .getSequences());
+    ranges.setStartRes(seq.findIndex(startRes) - 1);
+    viewport.firePropertyChange("alignment", null, al.getSequences());
 
   }
 
   public void removeAllGapsMenuItem_actionPerformed()
   {
-    int start = 0, end = viewport.getAlignment().getWidth() - 1;
+    AlignmentI al = viewport.getAlignment();
+    ViewportRanges ranges = viewport.getRanges();
+    int start = 0;
+    int end = ranges.getAbsoluteAlignmentWidth() - 1;
 
     SequenceI[] seqs;
     if (viewport.getSelectionGroup() != null)
@@ -2419,16 +2436,15 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     // This is to maintain viewport position on first residue
     // of first sequence
-    SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    SequenceI seq = al.getSequenceAt(0);
+    int startRes = seq.findPosition(ranges.getStartRes());
 
     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
-            viewport.getAlignment()));
+            al));
 
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    ranges.setStartRes(seq.findIndex(startRes) - 1);
 
-    viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-            .getSequences());
+    viewport.firePropertyChange("alignment", null, al.getSequences());
 
   }
 
@@ -2673,9 +2689,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     boolean selected = conservationMenuItem.getState();
     modifyConservation.setEnabled(selected);
     viewport.setConservationSelected(selected);
-
-    // viewport.setAbovePIDThreshold(false);
-    // abovePIDThreshold.setState(false);
+    viewport.getResidueShading().setConservationApplied(selected);
 
     changeColour(viewport.getGlobalColourScheme());
 
@@ -2694,8 +2708,11 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     boolean selected = abovePIDThreshold.getState();
     modifyPID.setEnabled(selected);
     viewport.setAbovePIDThreshold(selected);
-    // conservationMenuItem.setState(false);
-    // viewport.setConservationSelected(false);
+    if (!selected)
+    {
+      viewport.getResidueShading().setThreshold(0,
+              viewport.isIgnoreGapsConsensus());
+    }
 
     changeColour(viewport.getGlobalColourScheme());
 
@@ -3329,7 +3346,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
             .getString("action.make_groups_selection"));
     grpsFromSelection.addActionListener(this);
     createGroup.setLabel(MessageManager.getString("action.create_group"));
+    createGroup.addActionListener(this);
     unGroup.setLabel(MessageManager.getString("action.remove_group"));
+    unGroup.addActionListener(this);
+
     annotationColumnSelection.setLabel(MessageManager
             .getString("action.select_by_annotation"));
     annotationColumnSelection.addActionListener(this);
index fc087c6..065c503 100644 (file)
@@ -35,16 +35,16 @@ import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShader;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.UserColourScheme;
-import jalview.structure.CommandListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Font;
 
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, VamsasSource, CommandListener
+        SelectionSource
 {
   boolean cursorMode = false;
 
@@ -75,12 +75,10 @@ public class AlignViewport extends AlignmentViewport implements
     calculator = new jalview.workers.AlignCalcManager();
     this.applet = applet;
     alignment = al;
+    ranges = new ViewportRanges(this.alignment);
     // we always pad gaps
     this.setPadGaps(true);
-    this.startRes = 0;
-    this.endRes = al.getWidth() - 1;
-    this.startSeq = 0;
-    this.endSeq = al.getHeight() - 1;
+
     if (applet != null)
     {
       // get the width and height scaling factors if they were specified
@@ -299,7 +297,7 @@ public class AlignViewport extends AlignmentViewport implements
 
   public void resetSeqLimits(int height)
   {
-    setEndSeq(height / getCharHeight());
+    ranges.setEndSeq(height / getCharHeight());
   }
 
   public void setCurrentTree(NJTree tree)
index e97c347..3ae0394 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
 import jalview.structure.StructureSelectionManager;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -65,6 +66,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   AnnotationLabels alabels;
 
+  ViewportRanges vpRanges;
+
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
 
@@ -73,6 +76,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   {
     alignFrame = null;
     av = null;
+    vpRanges = null;
     seqPanel = null;
     seqPanelHolder = null;
     sequenceHolderPanel = null;
@@ -96,6 +100,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
     alignFrame = af;
     this.av = av;
+    vpRanges = av.getRanges();
     seqPanel = new SeqPanel(av, this);
     idPanel = new IdPanel(av, this);
     scalePanel = new ScalePanel(av, this);
@@ -126,7 +131,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        setScrollValues(av.getStartRes(), av.getStartSeq());
+        setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
         if (getSize().height > 0
                 && annotationPanelHolder.getSize().height > 0)
         {
@@ -383,7 +388,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
        */
       if (centre)
       {
-        int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
+        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
         start = Math.max(start - offset, 0);
         end = Math.min(end + offset, seq.getEnd() - 1);
       }
@@ -468,33 +473,34 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       // setScrollValues(start, seqIndex);
       // }
       // logic copied from jalview.gui.AlignmentPanel:
-      if ((startv = av.getStartRes()) >= start)
+      if ((startv = vpRanges.getStartRes()) >= start)
       {
         /*
          * Scroll left to make start of search results visible
          */
         setScrollValues(start - 1, seqIndex);
       }
-      else if ((endv = av.getEndRes()) <= end)
+      else if ((endv = vpRanges.getEndRes()) <= end)
       {
         /*
          * Scroll right to make end of search results visible
          */
         setScrollValues(startv + 1 + end - endv, seqIndex);
       }
-      else if ((starts = av.getStartSeq()) > seqIndex)
+      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
       {
         /*
          * Scroll up to make start of search results visible
          */
-        setScrollValues(av.getStartRes(), seqIndex);
+        setScrollValues(vpRanges.getStartRes(), seqIndex);
       }
-      else if ((ends = av.getEndSeq()) <= seqIndex)
+      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
       {
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
+        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                + 1);
       }
       /*
        * Else results are already visible - no need to scroll
@@ -516,10 +522,11 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   {
     int cwidth = seqPanel.seqCanvas
             .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-    if (res <= av.getStartRes() || res >= (av.getStartRes() + cwidth))
+    if (res <= vpRanges.getStartRes()
+            || res >= (vpRanges.getStartRes() + cwidth))
     {
       vscroll.setValue(res / cwidth);
-      av.startRes = vscroll.getValue() * cwidth;
+      vpRanges.setStartRes(vscroll.getValue() * cwidth);
     }
   }
 
@@ -632,8 +639,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
 
   public void setWrapAlignment(boolean wrap)
   {
-    av.startSeq = 0;
-    av.startRes = 0;
+    vpRanges.setStartSeq(0);
+    vpRanges.setStartRes(0);
     scalePanelHolder.setVisible(!wrap);
 
     hscroll.setVisible(!wrap);
@@ -724,7 +731,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     {
       x = 0;
     }
-    ;
+
 
     hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
     vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
@@ -762,17 +769,10 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       x = 0;
     }
 
-    av.setStartSeq(y);
-
-    int endSeq = y + vextent;
-    if (endSeq > av.getAlignment().getHeight())
-    {
-      endSeq = av.getAlignment().getHeight();
-    }
-
-    av.setEndSeq(endSeq);
-    av.setStartRes(x);
-    av.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
+    vpRanges.setStartSeq(y);
+    vpRanges.setEndSeq(y + vextent);
+    vpRanges.setStartRes(x);
+    vpRanges.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
             .getCharWidth())) - 1);
 
     hscroll.setValues(x, hextent, 0, width);
@@ -789,8 +789,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = av.getStartRes();
-    int oldY = av.getStartSeq();
+    int oldX = vpRanges.getStartRes();
+    int oldY = vpRanges.getStartSeq();
 
     if (evt == null || evt.getSource() == apvscroll)
     {
@@ -804,8 +804,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     if (evt == null || evt.getSource() == hscroll)
     {
       int x = hscroll.getValue();
-      av.setStartRes(x);
-      av.setEndRes(x + seqPanel.seqCanvas.getSize().width
+      vpRanges.setStartRes(x);
+      vpRanges.setEndRes(x + seqPanel.seqCanvas.getSize().width
               / av.getCharWidth() - 1);
     }
 
@@ -816,14 +816,14 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       {
         int rowSize = seqPanel.seqCanvas
                 .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
-        av.setStartRes(vscroll.getValue() * rowSize);
-        av.setEndRes((vscroll.getValue() + 1) * rowSize);
+        vpRanges.setStartRes(vscroll.getValue() * rowSize);
+        vpRanges.setEndRes((vscroll.getValue() + 1) * rowSize);
       }
       else
       {
-        av.setStartSeq(offy);
-        av.setEndSeq(offy + seqPanel.seqCanvas.getSize().height
-                / av.getCharHeight());
+        vpRanges.setStartSeq(offy);
+        vpRanges.setEndSeq(offy + seqPanel.seqCanvas.getSize().height
+                / av.getCharHeight() - 1);
       }
     }
 
@@ -832,8 +832,8 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       overviewPanel.setBoxPosition();
     }
 
-    int scrollX = av.startRes - oldX;
-    int scrollY = av.startSeq - oldY;
+    int scrollX = vpRanges.getStartRes() - oldX;
+    int scrollY = vpRanges.getStartSeq() - oldY;
 
     if (av.getWrapAlignment() || !fastPaint || av.MAC)
     {
@@ -843,13 +843,13 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     {
       // Make sure we're not trying to draw a panel
       // larger than the visible window
-      if (scrollX > av.endRes - av.startRes)
+      if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
       {
-        scrollX = av.endRes - av.startRes;
+        scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
       }
-      else if (scrollX < av.startRes - av.endRes)
+      else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
       {
-        scrollX = av.startRes - av.endRes;
+        scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
       }
 
       idPanel.idCanvas.fastPaint(scrollY);
@@ -858,7 +858,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
       scalePanel.repaint();
       if (av.isShowAnnotation())
       {
-        annotationPanel.fastPaint(av.getStartRes() - oldX);
+        annotationPanel.fastPaint(vpRanges.getStartRes() - oldX);
       }
     }
     sendViewPosition();
@@ -955,8 +955,9 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   private void sendViewPosition()
   {
     StructureSelectionManager.getStructureSelectionManager(av.applet)
-            .sendViewPosition(this, av.startRes, av.endRes, av.startSeq,
-                    av.endSeq);
+            .sendViewPosition(this, vpRanges.getStartRes(),
+                    vpRanges.getEndRes(), vpRanges.getStartSeq(),
+                    vpRanges.getEndSeq());
   }
 
   /**
@@ -1024,7 +1025,7 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
     }
     else
     {
-      setScrollValues(av.getStartRes(), av.getStartSeq());
+      setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
     }
 
     seqPanel.seqCanvas.repaint();
index 487b75c..f516bc9 100644 (file)
@@ -46,7 +46,8 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
-import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Vector;
 
 public class AnnotationColourChooser extends Panel implements
@@ -60,9 +61,15 @@ public class AnnotationColourChooser extends Panel implements
 
   ColourSchemeI oldcs;
 
-  Hashtable oldgroupColours;
+  Map<SequenceGroup, ColourSchemeI> oldgroupColours;
 
-  jalview.datamodel.AlignmentAnnotation currentAnnotation;
+  /*
+   * map from annotation to its menu item display label
+   * - so we know which item to pre-select on restore
+   */
+  private Map<AlignmentAnnotation, String> annotationLabels;
+
+  AlignmentAnnotation currentAnnotation;
 
   boolean adjusting = false;
 
@@ -78,17 +85,10 @@ public class AnnotationColourChooser extends Panel implements
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new Hashtable();
+      oldgroupColours = new HashMap<SequenceGroup, ColourSchemeI>();
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-        if (sg.getColourScheme() != null)
-        {
-          oldgroupColours.put(sg, sg.getColourScheme());
-        }
-        else
-        {
-          oldgroupColours.put(sg, "null");
-        }
+        oldgroupColours.put(sg, sg.getColourScheme());
       }
     }
     this.av = av;
@@ -119,24 +119,7 @@ public class AnnotationColourChooser extends Panel implements
       // seqAssociated.setState(acg.isSeqAssociated());
     }
 
-    Vector<String> list = new Vector<String>();
-    int index = 1;
-    for (int i = 0; i < anns.length; i++)
-    {
-      String label = anns[i].label;
-      if (anns[i].sequenceRef != null)
-      {
-        label = label + "_" + anns[i].sequenceRef.getName();
-      }
-      if (!list.contains(label))
-      {
-        list.addElement(label);
-      }
-      else
-      {
-        list.addElement(label + "_" + (index++));
-      }
-    }
+    Vector<String> list = getAnnotationItems();
 
     for (int i = 0; i < list.size(); i++)
     {
@@ -153,7 +136,8 @@ public class AnnotationColourChooser extends Panel implements
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      annotations.select(acg.getAnnotation());
+      String label = annotationLabels.get(acg.getAnnotation());
+      annotations.select(label);
       switch (acg.getAboveThreshold())
       {
       case AnnotationColourGradient.NO_THRESHOLD:
@@ -170,7 +154,7 @@ public class AnnotationColourChooser extends Panel implements
                 MessageManager
                         .getString("error.implementation_error_dont_know_threshold_annotationcolourgradient"));
       }
-      thresholdIsMin.setState(acg.thresholdIsMinMax);
+      thresholdIsMin.setState(acg.isThresholdIsMinMax());
       thresholdValue.setText("" + acg.getAnnotationThreshold());
     }
 
@@ -186,6 +170,51 @@ public class AnnotationColourChooser extends Panel implements
     validate();
   }
 
+  /**
+   * Builds and returns a list of menu items (display text) for choice of
+   * annotation. Also builds a map between annotations and their display labels.
+   * 
+   * @return
+   */
+  protected Vector<String> getAnnotationItems()
+  {
+    // TODO remove duplication with gui.AnnotationRowFilter
+    // TODO add 'per sequence only' option / parameter
+
+    annotationLabels = new HashMap<AlignmentAnnotation, String>();
+    Vector<String> list = new Vector<String>();
+    AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
+    if (anns == null)
+    {
+      return list;
+    }
+    int index = 1;
+    for (int i = 0; i < anns.length; i++)
+    {
+      String label = anns[i].label;
+      if (anns[i].sequenceRef != null)
+      {
+        /*
+         * be helpful and include sequence id in label for
+         * sequence-associated annotation (JAL-2236)
+         */
+        label = label + "_" + anns[i].sequenceRef.getName();
+      }
+      if (!list.contains(label))
+      {
+        list.addElement(label);
+        annotationLabels.put(anns[i], label);
+      }
+      else
+      {
+        label = label + "_" + (index++);
+        list.addElement(label);
+        annotationLabels.put(anns[i], label);
+      }
+    }
+    return list;
+  }
+
   private void setDefaultMinMax()
   {
     minColour.setBackground(av.applet.getDefaultColourParameter(
@@ -501,7 +530,7 @@ public class AnnotationColourChooser extends Panel implements
       acg.setPredefinedColours(true);
     }
 
-    acg.thresholdIsMinMax = thresholdIsMin.getState();
+    acg.setThresholdIsMinMax(thresholdIsMin.getState());
 
     av.setGlobalColourScheme(acg);
 
@@ -510,7 +539,6 @@ public class AnnotationColourChooser extends Panel implements
     {
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-
         if (sg.getColourScheme() == null)
         {
           continue;
@@ -527,7 +555,6 @@ public class AnnotationColourChooser extends Panel implements
                   currentAnnotation, minColour.getBackground(), maxColour
                           .getBackground(), aboveThreshold));
         }
-
       }
     }
 
@@ -543,20 +570,10 @@ public class AnnotationColourChooser extends Panel implements
     {
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-        Object cs = oldgroupColours.get(sg);
-        if (cs instanceof ColourSchemeI)
-        {
-          sg.setColourScheme((ColourSchemeI) cs);
-        }
-        else
-        {
-          // probably the "null" string we set it to if it was null originally.
-          sg.setColourScheme(null);
-        }
+        sg.setColourScheme(oldgroupColours.get(sg));
       }
     }
     ap.paintAlignment(true);
-
   }
 
   @Override
index b28ccc7..ad74b25 100755 (executable)
@@ -339,7 +339,8 @@ public class AnnotationLabels extends Panel implements ActionListener,
                 av.calcPanelHeight());
         f.height += dif;
         ap.seqPanelHolder.setPreferredSize(f);
-        ap.setScrollValues(av.getStartRes(), av.getStartSeq());
+        ap.setScrollValues(av.getRanges().getStartRes(), av.getRanges()
+                .getStartSeq());
         ap.validate();
         // ap.paintAlignment(true);
         ap.addNotify();
index 6012c1a..0ec7adf 100755 (executable)
@@ -462,7 +462,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       }
     }
 
-    int column = evt.getX() / av.getCharWidth() + av.getStartRes();
+    int column = evt.getX() / av.getCharWidth()
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
@@ -618,7 +619,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
     gg.setColor(Color.white);
     gg.fillRect(0, 0, getSize().width, getSize().height);
-    drawComponent(gg, av.startRes, av.endRes + 1);
+    drawComponent(gg, av.getRanges().getStartRes(), av.getRanges()
+            .getEndRes() + 1);
 
     g.drawImage(image, 0, 0, this);
   }
@@ -635,7 +637,7 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
     gg.copyArea(0, 0, imgWidth, getSize().height,
             -horizontal * av.getCharWidth(), 0);
-    int sr = av.startRes, er = av.endRes + 1, transX = 0;
+    int sr = av.getRanges().getStartRes(), er = av.getRanges().getEndRes() + 1, transX = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
index f938cad..9b8a235 100644 (file)
@@ -54,21 +54,7 @@ class AppletJmolBinding extends JalviewJmolBinding
   public jalview.api.FeatureRenderer getFeatureRenderer(
           AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (appletJmolBinding.ap.av.isShowSequenceFeatures())
-    {
-      if (appletJmolBinding.fr == null)
-      {
-        appletJmolBinding.fr = new jalview.appletgui.FeatureRenderer(
-                appletJmolBinding.ap.av);
-      }
-
-      appletJmolBinding.fr
-              .transferSettings(appletJmolBinding.ap.seqPanel.seqCanvas
-                      .getFeatureRenderer());
-    }
-
-    return appletJmolBinding.fr;
+    return appletJmolBinding.ap.getFeatureRenderer();
   }
 
   @Override
index 189fe88..b369318 100644 (file)
@@ -82,10 +82,10 @@ public class ExtJmol extends JalviewJmolBinding
   @Override
   public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
+    AlignmentPanel alignPanel = (AlignmentPanel) alignment;
+    if (alignPanel.av.isShowSequenceFeatures())
     {
-      return ap.getFeatureRenderer();
+      return alignPanel.getFeatureRenderer();
     }
     else
     {
index 67ca8e9..b88a1dc 100644 (file)
@@ -377,9 +377,6 @@ public class FeatureRenderer extends
 
     if (dialog.accept)
     {
-      // This ensures that the last sequence
-      // is refreshed and new features are rendered
-      lastSeq = null;
       lastFeatureAdded = name.getText().trim();
       lastFeatureGroupAdded = source.getText().trim();
       lastDescriptionAdded = description.getText().replace('\n', ' ');
index 2c454a4..1b9fbf9 100755 (executable)
@@ -696,8 +696,7 @@ public class FeatureSettings extends Panel implements ItemListener,
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
     fr.setTransparency((100 - transparency.getValue()) / 100f);
-    ap.seqPanel.seqCanvas.repaint();
-
+    ap.paintAlignment(true);
   }
 
   class MyCheckbox extends Checkbox
index d72e91f..abcbd70 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.appletgui;
 
 import jalview.datamodel.SequenceI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.Font;
@@ -103,28 +104,32 @@ public class IdCanvas extends Panel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     gg.copyArea(0, 0, getSize().width, imgHeight, 0,
             -vertical * av.getCharHeight());
 
-    int ss = av.startSeq, es = av.endSeq, transY = 0;
+    int ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transY = 0;
     if (vertical > 0) // scroll down
     {
       ss = es - vertical;
-      if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
+      if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
+                                     // at a
+                                 // time
       {
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - vertical * av.getCharHeight();
+        transY = imgHeight - ((vertical + 1) * av.getCharHeight());
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -180,7 +185,7 @@ public class IdCanvas extends Panel
     gg.setFont(italic);
 
     gg.fillRect(0, 0, getSize().width, getSize().height);
-    drawIds(av.startSeq, av.endSeq);
+    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
     g.drawImage(image, 0, 0, this);
   }
 
@@ -233,9 +238,10 @@ public class IdCanvas extends Panel
 
       int cHeight = alheight * avcharHeight + hgap + annotationHeight;
 
-      int rowSize = av.getEndRes() - av.getStartRes();
+      int rowSize = av.getRanges().getEndRes()
+              - av.getRanges().getStartRes();
       // Draw the rest of the panels
-      for (int ypos = hgap, row = av.startRes; (ypos <= getSize().height)
+      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getSize().height)
               && (row < maxwidth); ypos += cHeight, row += rowSize)
       {
         for (int i = starty; i < alheight; i++)
@@ -263,7 +269,7 @@ public class IdCanvas extends Panel
     {
       // Now draw the id strings
       SequenceI seq;
-      for (int i = starty; i < endy; i++)
+      for (int i = starty; i <= endy; i++)
       {
 
         seq = av.getAlignment().getSequenceAt(i);
index b03a638..e47c50a 100755 (executable)
@@ -253,13 +253,13 @@ public class IdPanel extends Panel implements MouseListener,
       return;
     }
 
-    if (mouseDragging && e.getY() < 0 && av.getStartSeq() > 0)
+    if (mouseDragging && e.getY() < 0 && av.getRanges().getStartSeq() > 0)
     {
       scrollThread = new ScrollThread(true);
     }
 
     if (mouseDragging && e.getY() >= getSize().height
-            && av.getAlignment().getHeight() > av.getEndSeq())
+            && av.getAlignment().getHeight() > av.getRanges().getEndSeq())
     {
       scrollThread = new ScrollThread(false);
     }
@@ -398,9 +398,10 @@ public class IdPanel extends Panel implements MouseListener,
     int index = av.getAlignment().findIndex(list.get(0));
 
     // do we need to scroll the panel?
-    if (av.getStartSeq() > index || av.getEndSeq() < index)
+    if (av.getRanges().getStartSeq() > index
+            || av.getRanges().getEndSeq() < index)
     {
-      alignPanel.setScrollValues(av.getStartRes(), index);
+      alignPanel.setScrollValues(av.getRanges().getStartRes(), index);
     }
   }
 
@@ -431,10 +432,10 @@ public class IdPanel extends Panel implements MouseListener,
         if (alignPanel.scrollUp(up))
         {
           // scroll was ok, so add new sequence to selection
-          int seq = av.getStartSeq();
+          int seq = av.getRanges().getStartSeq();
           if (!up)
           {
-            seq = av.getEndSeq();
+            seq = av.getRanges().getEndSeq();
           }
 
           if (seq < lastid)
index 9b2be4c..3ef2936 100755 (executable)
@@ -20,7 +20,9 @@
  */
 package jalview.appletgui;
 
-import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.viewmodel.OverviewDimensions;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -37,38 +39,34 @@ import java.awt.event.MouseMotionListener;
 public class OverviewPanel extends Panel implements Runnable,
         MouseMotionListener, MouseListener
 {
-  Image miniMe;
+  private OverviewDimensions od;
 
-  Image offscreen;
+  private Image miniMe;
 
-  AlignViewport av;
+  private Image offscreen;
 
-  AlignmentPanel ap;
+  private AlignViewport av;
 
-  float scalew = 1f;
+  private AlignmentPanel ap;
 
-  float scaleh = 1f;
+  private boolean resizing = false;
 
-  public int width, sequencesHeight;
-
-  int graphHeight = 20;
-
-  int boxX = -1, boxY = -1, boxWidth = -1, boxHeight = -1;
-
-  boolean resizing = false;
+  // This is set true if the user resizes whilst
+  // the overview is being calculated
+  private boolean resizeAgain = false;
 
   // Can set different properties in this seqCanvas than
   // main visible SeqCanvas
-  SequenceRenderer sr;
+  private SequenceRenderer sr;
 
-  FeatureRenderer fr;
+  private FeatureRenderer fr;
 
-  Frame nullFrame;
+  private Frame nullFrame;
 
-  public OverviewPanel(AlignmentPanel ap)
+  public OverviewPanel(AlignmentPanel alPanel)
   {
-    this.av = ap.av;
-    this.ap = ap;
+    this.av = alPanel.av;
+    this.ap = alPanel;
     setLayout(null);
     nullFrame = new Frame();
     nullFrame.addNotify();
@@ -79,45 +77,18 @@ public class OverviewPanel extends Panel implements Runnable,
     sr.forOverview = true;
     fr = new FeatureRenderer(av);
 
-    // scale the initial size of overviewpanel to shape of alignment
-    float initialScale = (float) av.getAlignment().getWidth()
-            / (float) av.getAlignment().getHeight();
-
-    if (av.getSequenceConsensusHash() == null)
-    {
-      graphHeight = 0;
-    }
-
-    if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-    {
-      // wider
-      width = 400;
-      sequencesHeight = (int) (400f / initialScale);
-      if (sequencesHeight < 40)
-      {
-        sequencesHeight = 40;
-      }
-    }
-    else
-    {
-      // taller
-      width = (int) (400f * initialScale);
-      sequencesHeight = 300;
-      if (width < 120)
-      {
-        width = 120;
-      }
-    }
+    od = new OverviewDimensions(av.getRanges(),
+            (av.isShowAnnotation() && av.getSequenceConsensusHash() != null));
 
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
     addComponentListener(new ComponentAdapter()
     {
 
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        if (getSize().width != width
-                || getSize().height != sequencesHeight + graphHeight)
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
         {
           updateOverviewImage();
         }
@@ -155,79 +126,32 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
   @Override
   public void mouseReleased(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
   @Override
   public void mouseDragged(MouseEvent evt)
   {
-    boxX = evt.getX();
-    boxY = evt.getY();
-    checkValid();
+    mouseAction(evt);
   }
 
-  void checkValid()
+  private void mouseAction(MouseEvent evt)
   {
-    if (boxY < 0)
-    {
-      boxY = 0;
-    }
-
-    if (boxY > (sequencesHeight - boxHeight))
-    {
-      boxY = sequencesHeight - boxHeight + 1;
-    }
-
-    if (boxX < 0)
-    {
-      boxX = 0;
-    }
-
-    if (boxX > (width - boxWidth))
-    {
-      if (av.hasHiddenColumns())
-      {
-        // Try smallest possible box
-        boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
-      }
-      boxX = width - boxWidth;
-    }
-
-    int col = (int) (boxX / scalew / av.getCharWidth());
-    int row = (int) (boxY / scaleh / av.getCharHeight());
-
-    if (av.hasHiddenColumns())
-    {
-      if (!av.getColumnSelection().isVisible(col))
-      {
-        return;
-      }
-
-      col = av.getColumnSelection().findColumnPosition(col);
-    }
-
-    if (av.hasHiddenRows())
-    {
-      row = av.getAlignment().getHiddenSequences()
-              .findIndexWithoutHiddenSeqs(row);
-    }
-
-    ap.setScrollValues(col, row);
+    od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av
+            .getRanges());
+    ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
     ap.paintAlignment(false);
   }
 
   /**
-   * DOCUMENT ME!
+   * Updates the overview image when the related alignment panel is updated
    */
   public void updateOverviewImage()
   {
@@ -246,27 +170,20 @@ public class OverviewPanel extends Panel implements Runnable,
 
     if ((getSize().width > 0) && (getSize().height > 0))
     {
-      width = getSize().width;
-      sequencesHeight = getSize().height - graphHeight;
+      od.setWidth(getSize().width);
+      od.setHeight(getSize().height);
     }
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
 
     Thread thread = new Thread(this);
     thread.start();
     repaint();
   }
 
-  // This is set true if the user resizes whilst
-  // the overview is being calculated
-  boolean resizeAgain = false;
-
   @Override
   public void run()
   {
     miniMe = null;
-    int alwidth = av.getAlignment().getWidth();
-    int alheight = av.getAlignment().getHeight()
-            + av.getAlignment().getHiddenSequences().getSize();
 
     if (av.isShowSequenceFeatures())
     {
@@ -275,135 +192,37 @@ public class OverviewPanel extends Panel implements Runnable,
 
     if (getSize().width > 0 && getSize().height > 0)
     {
-      width = getSize().width;
-      sequencesHeight = getSize().height - graphHeight;
+      od.setWidth(getSize().width);
+      od.setHeight(getSize().height);
     }
 
-    setSize(new Dimension(width, sequencesHeight + graphHeight));
-
-    int fullsizeWidth = alwidth * av.getCharWidth();
-    int fullsizeHeight = alheight * av.getCharHeight();
+    setSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    miniMe = nullFrame.createImage(width, sequencesHeight + graphHeight);
-    offscreen = nullFrame.createImage(width, sequencesHeight + graphHeight);
+    miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
+    offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
 
     Graphics mg = miniMe.getGraphics();
-    float sampleCol = (float) alwidth / (float) width;
-    float sampleRow = (float) alheight / (float) sequencesHeight;
-
-    int lastcol = 0, lastrow = 0;
-    int xstart = 0, ystart = 0;
-    Color color = Color.yellow;
-    int row, col, sameRow = 0, sameCol = 0;
-    jalview.datamodel.SequenceI seq;
-    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-            .hasHiddenColumns();
-    boolean hiddenRow = false;
-    AlignmentI alignment = av.getAlignment();
-    for (row = 0; row <= sequencesHeight; row++)
-    {
-      if (resizeAgain)
-      {
-        break;
-      }
-      if ((int) (row * sampleRow) == lastrow)
-      {
-        sameRow++;
-        continue;
-      }
-
-      hiddenRow = false;
-      if (hasHiddenRows)
-      {
-        seq = alignment.getHiddenSequences().getHiddenSequence(lastrow);
-        if (seq == null)
-        {
-          int index = alignment.getHiddenSequences()
-                  .findIndexWithoutHiddenSeqs(lastrow);
-
-          seq = alignment.getSequenceAt(index);
-        }
-        else
-        {
-          hiddenRow = true;
-        }
-      }
-      else
-      {
-        seq = alignment.getSequenceAt(lastrow);
-      }
-
-      for (col = 0; col < width; col++)
-      {
-        if ((int) (col * sampleCol) == lastcol
-                && (int) (row * sampleRow) == lastrow)
-        {
-          sameCol++;
-          continue;
-        }
-
-        lastcol = (int) (col * sampleCol);
-
-        if (seq.getLength() > lastcol)
-        {
-          color = sr.getResidueBoxColour(seq, lastcol);
-
-          if (av.isShowSequenceFeatures())
-          {
-            color = fr.findFeatureColour(color, seq, lastcol);
-          }
-        }
-        else
-        {
-          color = Color.white; // White
-        }
-
-        if (hiddenRow
-                || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                        lastcol)))
-        {
-          color = color.darker().darker();
-        }
 
-        mg.setColor(color);
-        if (sameCol == 1 && sameRow == 1)
-        {
-          mg.drawLine(xstart, ystart, xstart, ystart);
-        }
-        else
-        {
-          mg.fillRect(xstart, ystart, sameCol, sameRow);
-        }
+    int alwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getAbsoluteHeight();
+    float sampleCol = alwidth / (float) od.getWidth();
+    float sampleRow = alheight / (float) od.getSequencesHeight();
 
-        xstart = col;
-        sameCol = 1;
-      }
-      lastrow = (int) (row * sampleRow);
-      ystart = row;
-      sameRow = 1;
-    }
+    buildImage(sampleRow, sampleCol, mg);
 
-    if (av.getAlignmentConservationAnnotation() != null)
+    // check for conservation annotation to make sure overview works for DNA too
+    if (av.isShowAnnotation()
+            && (av.getAlignmentConservationAnnotation() != null))
     {
-      for (col = 0; col < width; col++)
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
       {
-        if (resizeAgain)
-        {
-          break;
-        }
-        lastcol = (int) (col * sampleCol);
-        {
-          mg.translate(col, sequencesHeight);
-          ap.annotationPanel.renderer.drawGraph(mg,
-                  av.getAlignmentConservationAnnotation(),
-                  av.getAlignmentConservationAnnotation().annotations,
-                  (int) (sampleCol) + 1, graphHeight,
-                  (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-          mg.translate(-col, -sequencesHeight);
-        }
+        mg.translate(col, od.getSequencesHeight());
+        ap.annotationPanel.renderer.drawGraph(mg,
+                av.getAlignmentConservationAnnotation(),
+                av.getAlignmentConservationAnnotation().annotations,
+                (int) (sampleCol) + 1, od.getGraphHeight(),
+                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+        mg.translate(-col, -od.getSequencesHeight());
       }
     }
     System.gc();
@@ -419,52 +238,104 @@ public class OverviewPanel extends Panel implements Runnable,
     }
   }
 
-  public void setBoxPosition()
+  /*
+   * Build the overview panel image
+   */
+  private void buildImage(float sampleRow, float sampleCol, Graphics mg)
   {
-    int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-    int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-            .getHiddenSequences().getSize())
-            * av.getCharHeight();
-
-    int startRes = av.getStartRes();
-    int endRes = av.getEndRes();
+    int lastcol = 0;
+    int lastrow = 0;
+    int xstart = 0;
+    int ystart = 0;
+    Color color = Color.yellow;
+    int sameRow = 0;
+    int sameCol = 0;
 
-    if (av.hasHiddenColumns())
-    {
-      startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-      endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-    }
+    SequenceI seq = null;
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
 
-    int startSeq = av.startSeq;
-    int endSeq = av.endSeq;
+    final boolean hasHiddenCols = av.hasHiddenColumns();
+    boolean hiddenRow = false;
 
-    if (av.hasHiddenRows())
+    for (int row = 0; row <= od.getSequencesHeight() && !resizeAgain; row++)
     {
-      startSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(startSeq);
-
-      endSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(endSeq);
+      if ((int) (row * sampleRow) == lastrow)
+      {
+        sameRow++;
+      }
+      else
+      {
+        // get the sequence which would be at alignment index 'lastrow' if no
+        // columns were hidden, and determine whether it is hidden or not
+        hiddenRow = av.getAlignment().isHidden(lastrow);
+        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
 
+        for (int col = 0; col < od.getWidth(); col++)
+        {
+          if ((int) (col * sampleCol) == lastcol
+                  && (int) (row * sampleRow) == lastrow)
+          {
+            sameCol++;
+          }
+          else
+          {
+            lastcol = (int) (col * sampleCol);
+
+            color = getColumnColourFromSequence(seq, hiddenRow,
+                    hasHiddenCols, lastcol, finder);
+
+            mg.setColor(color);
+            if (sameCol == 1 && sameRow == 1)
+            {
+              mg.drawLine(xstart, ystart, xstart, ystart);
+            }
+            else
+            {
+              mg.fillRect(xstart, ystart, sameCol, sameRow);
+            }
+
+            xstart = col;
+            sameCol = 1;
+          }
+        }
+        lastrow = (int) (row * sampleRow);
+        ystart = row;
+        sameRow = 1;
+      }
     }
+  }
 
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    boxX = (int) (startRes * av.getCharWidth() * scalew);
-    boxY = (int) (startSeq * av.getCharHeight() * scaleh);
-
-    if (av.hasHiddenColumns())
+  /*
+   * Find the colour of a sequence at a specified column position
+   */
+  private Color getColumnColourFromSequence(
+          jalview.datamodel.SequenceI seq, boolean hiddenRow,
+          boolean hasHiddenCols, int lastcol, FeatureColourFinder finder)
+  {
+    Color color = Color.white;
+    if (seq.getLength() > lastcol)
     {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
+      color = sr.getResidueColour(seq, lastcol, finder);
     }
-    else
+
+    if (hiddenRow
+            || (hasHiddenCols && !av.getColumnSelection()
+                    .isVisible(lastcol)))
     {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
+      color = color.darker().darker();
     }
+    return color;
+  }
 
-    boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
-
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   */
+  public void setBoxPosition()
+  {
+    od.setBoxPosition(av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
     repaint();
   }
 
@@ -482,8 +353,7 @@ public class OverviewPanel extends Panel implements Runnable,
     {
       og.drawImage(miniMe, 0, 0, this);
       og.setColor(Color.red);
-      og.drawRect(boxX, boxY, boxWidth, boxHeight);
-      og.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+      od.drawBox(og);
       g.drawImage(offscreen, 0, 0, this);
     }
   }
index ed07b63..15d82a5 100755 (executable)
@@ -76,7 +76,7 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    int x = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
     final int res;
 
     if (av.hasHiddenColumns())
@@ -229,7 +229,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   {
     mouseDragging = false;
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (res > av.getAlignment().getWidth())
     {
@@ -276,7 +277,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     mouseDragging = true;
     ColumnSelection cs = av.getColumnSelection();
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
     res = Math.max(0, res);
     res = cs.adjustForHiddenColumns(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
@@ -324,7 +326,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
       return;
     }
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     res = av.getColumnSelection().adjustForHiddenColumns(res);
 
@@ -350,7 +353,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
   @Override
   public void paint(Graphics g)
   {
-    drawScale(g, av.getStartRes(), av.getEndRes(), getSize().width,
+    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
+            getSize().width,
             getSize().height);
   }
 
index 5d6bb07..89df11f 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -211,17 +212,19 @@ public class SeqCanvas extends Panel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     updateViewport();
 
     // Its possible on certain browsers that the call to fastpaint
     // is faster than it can paint, so this check here catches
     // this possibility
-    if (lastsr + horizontal != av.startRes)
+    if (lastsr + horizontal != ranges.getStartRes())
     {
-      horizontal = av.startRes - lastsr;
+      horizontal = ranges.getStartRes() - lastsr;
     }
 
-    lastsr = av.startRes;
+    lastsr = ranges.getStartRes();
 
     fastPaint = true;
     gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
@@ -229,7 +232,9 @@ public class SeqCanvas extends Panel
             imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
             -vertical * avcharHeight);
 
-    int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0;
+    int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
+            .getStartSeq(), es = ranges
+            .getEndSeq(), transX = 0, transY = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
     {
@@ -244,21 +249,23 @@ public class SeqCanvas extends Panel
     else if (vertical > 0) // scroll down
     {
       ss = es - vertical;
-      if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
+      if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
+                                     // at a
+                                 // time
       {
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - vertical * avcharHeight;
+        transY = imgHeight - ((vertical + 1) * avcharHeight);
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -329,13 +336,16 @@ public class SeqCanvas extends Panel
     gg.setColor(Color.white);
     gg.fillRect(0, 0, imgWidth, imgHeight);
 
+    ViewportRanges ranges = av.getRanges();
+
     if (av.getWrapAlignment())
     {
-      drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);
+      drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
     }
     else
     {
-      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+              ranges.getStartSeq(), ranges.getEndSeq(), 0);
     }
 
     g.drawImage(img, 0, 0, this);
@@ -421,7 +431,7 @@ public class SeqCanvas extends Panel
 
     av.setWrappedWidth(cWidth);
 
-    av.endRes = av.startRes + cWidth;
+    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth);
 
     int endx;
     int ypos = hgap;
@@ -491,7 +501,7 @@ public class SeqCanvas extends Panel
         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
       }
 
-      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
+      drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
       g.setClip(null);
 
       if (av.isShowAnnotation())
@@ -570,7 +580,7 @@ public class SeqCanvas extends Panel
             g1.setColor(Color.blue);
             g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
                     0 + offset, (blockEnd - blockStart + 1) * avcharWidth
-                            - 1, (endSeq - startSeq) * avcharHeight
+                            - 1, (endSeq - startSeq + 1) * avcharHeight
                             + offset);
           }
 
@@ -610,7 +620,7 @@ public class SeqCanvas extends Panel
 
     // / First draw the sequences
     // ///////////////////////////
-    for (int i = startSeq; i < endSeq; i++)
+    for (int i = startSeq; i <= endSeq; i++)
     {
       nextSeq = av.getAlignment().getSequenceAt(i);
 
@@ -625,7 +635,7 @@ public class SeqCanvas extends Panel
       if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                + ((i - startSeq) * avcharHeight));
+                + ((i - startSeq) * avcharHeight), false);
       }
 
       // / Highlight search Results once all sequences have been drawn
@@ -694,7 +704,7 @@ public class SeqCanvas extends Panel
         int bottom = -1;
         int alHeight = av.getAlignment().getHeight() - 1;
 
-        for (i = startSeq; i < endSeq; i++)
+        for (i = startSeq; i <= endSeq; i++)
         {
           sx = (group.getStartRes() - startRes) * avcharWidth;
           sy = offset + ((i - startSeq) * avcharHeight);
index 1352fe9..0e12703 100644 (file)
@@ -41,6 +41,7 @@ import jalview.structure.VamsasSource;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Font;
@@ -226,16 +227,17 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
     else
     {
-      while (seqCanvas.cursorY < av.startSeq)
+      ViewportRanges ranges = av.getRanges();
+      while (seqCanvas.cursorY < ranges.getStartSeq())
       {
         ap.scrollUp(true);
       }
-      while (seqCanvas.cursorY + 1 > av.endSeq)
+      while (seqCanvas.cursorY + 1 > ranges.getEndSeq())
       {
         ap.scrollUp(false);
       }
       while (seqCanvas.cursorX < av.getColumnSelection()
-              .adjustForHiddenColumns(av.startRes))
+              .adjustForHiddenColumns(ranges.getStartRes()))
       {
 
         if (!ap.scrollRight(false))
@@ -244,7 +246,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         }
       }
       while (seqCanvas.cursorX > av.getColumnSelection()
-              .adjustForHiddenColumns(av.endRes))
+              .adjustForHiddenColumns(ranges.getEndRes()))
       {
         if (!ap.scrollRight(true))
         {
@@ -624,14 +626,14 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getStartRes() / cwidth;
+      wrappedBlock += av.getRanges().getStartRes() / cwidth;
 
       res = wrappedBlock * cwidth + x / av.getCharWidth();
 
     }
     else
     {
-      res = (x / av.getCharWidth()) + av.getStartRes();
+      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
     }
 
     if (av.hasHiddenColumns())
@@ -681,7 +683,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
     else
     {
-      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
+      seq = Math.min((y / av.getCharHeight())
+              + av.getRanges().getStartSeq(),
+              av
               .getAlignment().getHeight() - 1);
       if (seq < 0)
       {
@@ -1643,8 +1647,10 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       oldSeq = -1;
     }
 
-    if (res > av.endRes || res < av.startRes || y < av.startSeq
-            || y > av.endSeq)
+    if (res > av.getRanges().getEndRes()
+            || res < av.getRanges().getStartRes()
+            || y < av.getRanges().getStartSeq()
+            || y > av.getRanges().getEndSeq())
     {
       mouseExited(evt);
     }
@@ -1742,13 +1748,15 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         if (evt != null)
         {
 
-          if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)
+          if (mouseDragging && evt.getY() < 0
+                  && av.getRanges().getStartSeq() > 0)
           {
             running = ap.scrollUp(true);
           }
 
           if (mouseDragging && evt.getY() >= getSize().height
-                  && av.getAlignment().getHeight() > av.getEndSeq())
+                  && av.getAlignment().getHeight() > av.getRanges()
+                          .getEndSeq())
           {
             running = ap.scrollUp(false);
           }
@@ -1890,8 +1898,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollTo(int row, int column)
   {
 
-    row = row < 0 ? ap.av.startSeq : row;
-    column = column < 0 ? ap.av.startRes : column;
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
     ap.scrollTo(column, column, row, true, true);
   }
 
@@ -1903,8 +1911,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollToRow(int row)
   {
 
-    row = row < 0 ? ap.av.startSeq : row;
-    ap.scrollTo(ap.av.startRes, ap.av.startRes, row, true, true);
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges()
+            .getStartRes(), row, true, true);
   }
 
   /**
@@ -1915,8 +1924,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   public void scrollToColumn(int column)
   {
 
-    column = column < 0 ? ap.av.startRes : column;
-    ap.scrollTo(column, column, ap.av.startSeq, true, true);
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
+    ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true);
   }
 
   /**
index 86d1f98..78ed4a3 100755 (executable)
  */
 package jalview.appletgui;
 
-import jalview.api.FeatureRenderer;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 
 import java.awt.Color;
 import java.awt.Font;
@@ -69,8 +69,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     this.renderGaps = renderGaps;
   }
 
-  @Override
-  public Color getResidueBoxColour(SequenceI seq, int i)
+  protected Color getResidueBoxColour(SequenceI seq, int i)
   {
     allGroups = av.getAlignment().findAllGroups(seq);
 
@@ -96,20 +95,20 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
    * 
    * @param seq
    * @param position
-   * @param fr
+   * @param finder
    * @return
    */
   @Override
   public Color getResidueColour(final SequenceI seq, int position,
-          FeatureRenderer fr)
+          FeatureColourFinder finder)
   {
     // TODO replace 8 or so code duplications with calls to this method
     // (refactored as needed)
     Color col = getResidueBoxColour(seq, position);
 
-    if (fr != null)
+    if (finder != null)
     {
-      col = fr.findFeatureColour(col, seq, position);
+      col = finder.findFeatureColour(col, seq, position);
     }
     return col;
   }
index 48c1ee9..da3cb92 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.bin;
 
 import jalview.datamodel.PDBEntry;
 import jalview.gui.UserDefinedColours;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureImportSettings;
@@ -1037,7 +1038,7 @@ public class Cache
       String file = st.nextToken();
       try
       {
-        UserColourScheme ucs = ColourSchemes.loadColourScheme(file);
+        UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
         if (ucs != null)
         {
           if (coloursFound.length() > 0)
index a6f2bf4..2dafc0c 100755 (executable)
@@ -185,14 +185,7 @@ public class Alignment implements AlignmentI
     return AlignmentUtils.getSequencesByName(this);
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+
   @Override
   public SequenceI getSequenceAt(int i)
   {
@@ -206,6 +199,28 @@ public class Alignment implements AlignmentI
     return null;
   }
 
+  @Override
+  public SequenceI getSequenceAtAbsoluteIndex(int i)
+  {
+    SequenceI seq = null;
+    if (getHiddenSequences().getSize() > 0)
+    {
+      seq = getHiddenSequences().getHiddenSequence(i);
+      if (seq == null)
+      {
+        // didn't find the sequence in the hidden sequences, get it from the
+        // alignment
+        int index = getHiddenSequences().findIndexWithoutHiddenSeqs(i);
+        seq = getSequenceAt(index);
+      }
+    }
+    else
+    {
+      seq = getSequenceAt(i);
+    }
+    return seq;
+  }
+
   /**
    * Adds a sequence to the alignment. Recalculates maxLength and size. Note
    * this currently does not recalculate whether or not the alignment is
@@ -320,30 +335,21 @@ public class Alignment implements AlignmentI
     }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param s
-   *          DOCUMENT ME!
-   */
   @Override
   public void deleteSequence(SequenceI s)
   {
-    deleteSequence(findIndex(s));
+    synchronized (sequences)
+    {
+      deleteSequence(findIndex(s));
+    }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   */
   @Override
   public void deleteSequence(int i)
   {
-    if (i > -1 && i < getHeight())
+    synchronized (sequences)
     {
-      synchronized (sequences)
+      if (i > -1 && i < getHeight())
       {
         sequences.remove(i);
         hiddenSequences.adjustHeightSequenceDeleted(i);
@@ -351,6 +357,18 @@ public class Alignment implements AlignmentI
     }
   }
 
+  @Override
+  public void deleteHiddenSequence(int i)
+  {
+    synchronized (sequences)
+    {
+      if (i > -1 && i < getHeight())
+      {
+        sequences.remove(i);
+      }
+    }
+  }
+
   /*
    * (non-Javadoc)
    * 
@@ -668,22 +686,19 @@ public class Alignment implements AlignmentI
     return -1;
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+
   @Override
   public int getHeight()
   {
     return sequences.size();
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
+  @Override
+  public int getAbsoluteHeight()
+  {
+    return sequences.size() + getHiddenSequences().getSize();
+  }
+
   @Override
   public int getWidth()
   {
@@ -769,6 +784,12 @@ public class Alignment implements AlignmentI
     return true;
   }
 
+  @Override
+  public boolean isHidden(int alignmentIndex)
+  {
+    return (getHiddenSequences().getHiddenSequence(alignmentIndex) != null);
+  }
+
   /**
    * Delete all annotations, including auto-calculated if the flag is set true.
    * Returns true if at least one annotation was deleted, else false.
@@ -990,6 +1011,10 @@ public class Alignment implements AlignmentI
     }
     else if (dataset == null && data != null)
     {
+      if (data == this)
+      {
+        throw new IllegalArgumentException("Circular dataset reference");
+      }
       if (!(data instanceof Alignment))
       {
         throw new Error(
index bbd3ce4..6117baf 100755 (executable)
  */
 package jalview.datamodel;
 
-import jalview.analysis.Rna;
-import jalview.analysis.SecStrConsensus.SimpleBP;
-import jalview.analysis.WUSSParseException;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -32,6 +28,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import jalview.analysis.Rna;
+import jalview.analysis.SecStrConsensus.SimpleBP;
+import jalview.analysis.WUSSParseException;
+
 /**
  * DOCUMENT ME!
  * 
@@ -867,6 +867,10 @@ public class AlignmentAnnotation
   @Override
   public String toString()
   {
+    if (annotations == null)
+    {
+      return "";
+    }
     StringBuilder buffer = new StringBuilder(256);
 
     for (int i = 0; i < annotations.length; i++)
index 5d185cd..2abb1f8 100755 (executable)
@@ -31,13 +31,22 @@ import java.util.Set;
 public interface AlignmentI extends AnnotatedCollectionI
 {
   /**
-   * Calculates the number of sequences in an alignment
+   * Calculates the number of sequences in an alignment, excluding hidden
+   * sequences
    * 
    * @return Number of sequences in alignment
    */
   int getHeight();
 
   /**
+   * Calculates the number of sequences in an alignment, including hidden
+   * sequences
+   * 
+   * @return Number of sequences in alignment
+   */
+  int getAbsoluteHeight();
+
+  /**
    * 
    * Calculates the maximum width of the alignment, including gaps.
    * 
@@ -65,6 +74,15 @@ public interface AlignmentI extends AnnotatedCollectionI
   boolean isAligned(boolean includeHidden);
 
   /**
+   * Answers if the sequence at alignmentIndex is hidden
+   * 
+   * @param alignmentIndex
+   *          the index to check
+   * @return true if the sequence is hidden
+   */
+  boolean isHidden(int alignmentIndex);
+
+  /**
    * Gets sequences as a Synchronized collection
    * 
    * @return All sequences in alignment.
@@ -90,6 +108,17 @@ public interface AlignmentI extends AnnotatedCollectionI
   SequenceI getSequenceAt(int i);
 
   /**
+   * Find a specific sequence in this alignment.
+   * 
+   * @param i
+   *          Index of required sequence in full alignment, i.e. if all columns
+   *          were visible
+   * 
+   * @return SequenceI at given index.
+   */
+  SequenceI getSequenceAtAbsoluteIndex(int i);
+
+  /**
    * Returns a map of lists of sequences keyed by sequence name.
    * 
    * @return
@@ -118,7 +147,9 @@ public interface AlignmentI extends AnnotatedCollectionI
   SequenceI replaceSequenceAt(int i, SequenceI seq);
 
   /**
-   * Deletes a sequence from the alignment
+   * Deletes a sequence from the alignment. Updates hidden sequences to account
+   * for the removed sequence. Do NOT use this method to delete sequences which
+   * are just hidden.
    * 
    * @param s
    *          Sequence to be deleted.
@@ -126,7 +157,9 @@ public interface AlignmentI extends AnnotatedCollectionI
   void deleteSequence(SequenceI s);
 
   /**
-   * Deletes a sequence from the alignment.
+   * Deletes a sequence from the alignment. Updates hidden sequences to account
+   * for the removed sequence. Do NOT use this method to delete sequences which
+   * are just hidden.
    * 
    * @param i
    *          Index of sequence to be deleted.
@@ -134,6 +167,14 @@ public interface AlignmentI extends AnnotatedCollectionI
   void deleteSequence(int i);
 
   /**
+   * Deletes a sequence in the alignment which has been hidden.
+   * 
+   * @param i
+   *          Index of sequence to be deleted
+   */
+  void deleteHiddenSequence(int i);
+
+  /**
    * Finds sequence in alignment using sequence name as query.
    * 
    * @param name
@@ -545,4 +586,5 @@ public interface AlignmentI extends AnnotatedCollectionI
    * @return
    */
   public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols);
+
 }
index 98a7fe2..97bc5a3 100644 (file)
@@ -690,8 +690,8 @@ public class ColumnSelection
    * left-most visible column will always be returned.
    * 
    * @param hiddenColumn
-   *          int
-   * @return int
+   *          the column index in the full alignment including hidden columns
+   * @return the position of the column in the visible alignment
    */
   public int findColumnPosition(int hiddenColumn)
   {
@@ -708,15 +708,89 @@ public class ColumnSelection
           result -= region[1] + 1 - region[0];
         }
       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
-      if (hiddenColumn > region[0] && hiddenColumn < region[1])
-      {
-        return region[0] + hiddenColumn - result;
+
+      if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
+       {
+         // Here the hidden column is within a region, so
+         // we want to return the position of region[0]-1, adjusted for any
+         // earlier hidden columns.
+         // Calculate the difference between the actual hidden col position
+         // and region[0]-1, and then subtract from result to convert result from
+         // the adjusted hiddenColumn value to the adjusted region[0]-1 value
+
+        // However, if the region begins at 0 we cannot return region[0]-1
+        // just return 0
+        if (region[0] == 0)
+        {
+          return 0;
+        }
+        else
+        {
+          return result - (hiddenColumn - region[0] + 1);
+        }
       }
     }
     return result; // return the shifted position after removing hidden columns.
   }
 
   /**
+   * Find the visible column which is a given visible number of columns to the
+   * left of another visible column. i.e. for a startColumn x, the column which
+   * is distance 1 away will be column x-1.
+   * 
+   * @param visibleDistance
+   *          the number of visible columns to offset by
+   * @param startColumn
+   *          the column to start from
+   * @return the position of the column in the visible alignment
+   */
+  public int subtractVisibleColumns(int visibleDistance, int startColumn)
+  {
+    int distance = visibleDistance;
+
+    // in case startColumn is in a hidden region, move it to the left
+    int start = adjustForHiddenColumns(findColumnPosition(startColumn));
+
+    // get index of hidden region to left of start
+    int index = getHiddenIndexLeft(start);
+    if (index == -1)
+    {
+      // no hidden regions to left of startColumn
+      return start - distance;
+    }
+
+    // walk backwards through the alignment subtracting the counts of visible
+    // columns from distance
+    int[] region;
+    int gap = 0;
+    int nextstart = start;
+
+    while ((index > -1) && (distance - gap > 0))
+    {
+      // subtract the gap to right of region from distance
+      distance -= gap;
+      start = nextstart;
+
+      // calculate the next gap
+      region = hiddenColumns.get(index);
+      gap = start - region[1];
+
+      // set start to just to left of current region
+      nextstart = region[0] - 1;
+      index--;
+    }
+
+    if (distance - gap > 0)
+    {
+      // fell out of loop because there are no more hidden regions
+      distance -= gap;
+      return nextstart - distance;
+    }
+    return start - distance;
+
+  }
+
+  /**
    * Use this method to determine where the next hiddenRegion starts
    * 
    * @param hiddenRegion
@@ -805,6 +879,35 @@ public class ColumnSelection
 
   }
 
+  /**
+   * This method returns the index of the hidden region to the left of a column
+   * position. If the column is in a hidden region it returns the index of the
+   * region to the left. If there is no hidden region to the left it returns -1.
+   * 
+   * @param pos
+   *          int
+   */
+  private int getHiddenIndexLeft(int pos)
+  {
+    if (hiddenColumns != null)
+    {
+      int index = hiddenColumns.size() - 1;
+      do
+      {
+        int[] region = hiddenColumns.elementAt(index);
+        if (pos > region[1])
+        {
+          return index;
+        }
+
+        index--;
+      } while (index > -1);
+    }
+
+    return -1;
+
+  }
+
   public void hideSelectedColumns()
   {
     synchronized (selection)
index 9e2cf72..6950c28 100755 (executable)
@@ -154,8 +154,8 @@ public class HiddenSequences
       hiddenSequences = new SequenceI[alignment.getHeight()];
     }
 
-    int alignmentIndex = alignment.findIndex(sequence);
-    alignmentIndex = adjustForHiddenSeqs(alignmentIndex);
+    int absAlignmentIndex = alignment.findIndex(sequence);
+    int alignmentIndex = adjustForHiddenSeqs(absAlignmentIndex);
 
     if (hiddenSequences[alignmentIndex] != null)
     {
@@ -164,7 +164,7 @@ public class HiddenSequences
 
     hiddenSequences[alignmentIndex] = sequence;
 
-    alignment.deleteSequence(sequence);
+    alignment.deleteHiddenSequence(absAlignmentIndex);
   }
 
   public List<SequenceI> showAll(
@@ -246,6 +246,12 @@ public class HiddenSequences
     return hiddenSequences == null ? null : hiddenSequences[alignmentIndex];
   }
 
+  /**
+   * Convert absolute alignment index to visible alignment index
+   * 
+   * @param alignmentIndex
+   * @return
+   */
   public int findIndexWithoutHiddenSeqs(int alignmentIndex)
   {
     if (hiddenSequences == null)
@@ -254,8 +260,14 @@ public class HiddenSequences
     }
     int index = 0;
     int hiddenSeqs = 0;
+    int diff = 0;
     if (hiddenSequences.length <= alignmentIndex)
     {
+      // if the alignmentIndex runs past the end of hidden sequences
+      // and therefore actually past the end of the alignment
+      // store the difference to add back on at the end, so that behaviour
+      // is consistent with hidden columns behaviour (used by overview panel)
+      diff = alignmentIndex - hiddenSequences.length + 1;
       alignmentIndex = hiddenSequences.length - 1;
     }
 
@@ -268,9 +280,50 @@ public class HiddenSequences
       index++;
     }
 
-    return (alignmentIndex - hiddenSeqs);
+    return (alignmentIndex - hiddenSeqs + diff);
+  }
+
+  /**
+   * Find the visible row which is a given visible number of rows above another
+   * visible row. i.e. for a startRow x, the row which is distance 1 away will
+   * be row x-1.
+   * 
+   * @param visibleDistance
+   *          the number of visible rows to offset by
+   * @param startRow
+   *          the row to start from
+   * @return the position of the row in the visible alignment
+   */
+  public int subtractVisibleRows(int visibleDistance, int startRow)
+  {
+    // walk upwards through the alignment
+    // count all the non-null sequences until we have visibleDistance counted
+    // then return the next visible sequence
+    if (hiddenSequences == null)
+    {
+      return startRow - visibleDistance;
+    }
+
+    int index = startRow;
+    int count = 0;
+    while ((index > -1) && (count < visibleDistance))
+    {
+      if (hiddenSequences[index] == null)
+      {
+        // count visible sequences
+        count++;
+      }
+      index--;
+    }
+    return index;
   }
 
+  /**
+   * Convert alignment index from visible alignment to absolute alignment
+   * 
+   * @param alignmentIndex
+   * @return
+   */
   public int adjustForHiddenSeqs(int alignmentIndex)
   {
     if (hiddenSequences == null)
index e37c55e..1246d23 100755 (executable)
@@ -1358,7 +1358,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     AnnotatedCollectionI ref = ctx;
     while (ref != null)
     {
-      if (ref == this)
+      if (ref == this || ref.getContext() == ctx)
       {
         throw new IllegalArgumentException(
                 "Circular reference in SequenceGroup.context");
index bf80831..94df99a 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.ext.jmol;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
@@ -34,6 +35,7 @@ import jalview.structure.AtomSpec;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.MessageManager;
 
 import java.awt.Color;
 import java.awt.Container;
@@ -43,6 +45,7 @@ import java.io.File;
 import java.net.URL;
 import java.security.AccessControlException;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -227,21 +230,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   }
 
   /**
-   * Construct and send a command to align structures against a reference
-   * structure, based on one or more sequence alignments
-   * 
-   * @param _alignment
-   *          an array of alignments to process
-   * @param _refStructure
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param _hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * {@inheritDoc}
    */
   @Override
-  public void superposeStructures(AlignmentI[] _alignment,
+  public String superposeStructures(AlignmentI[] _alignment,
           int[] _refStructure, ColumnSelection[] _hiddenCols)
   {
     while (viewer.isScriptExecuting())
@@ -261,7 +253,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     String[] files = getPdbFile();
     if (!waitForFileLoad(files))
     {
-      return;
+      return null;
     }
 
     StringBuilder selectioncom = new StringBuilder(256);
@@ -277,6 +269,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       nSeconds = " " + (2.0 / files.length) + " ";
       // if (nSeconds).substring(0,5)+" ";
     }
+
     // see JAL-1345 - should really automatically turn off the animation for
     // large numbers of structures, but Jmol doesn't seem to allow that.
     // nSeconds = " ";
@@ -302,14 +295,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       }
 
       /*
-       * 'matched' array will hold 'true' for visible alignment columns where
+       * 'matched' bit j will be set for visible alignment columns j where
        * all sequences have a residue with a mapping to the PDB structure
        */
-      // TODO could use a BitSet for matched
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
+      BitSet matched = new BitSet();
+      for (int m = 0; m < alignment.getWidth(); m++)
       {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
       }
 
       SuperposeData[] structures = new SuperposeData[files.length];
@@ -334,17 +329,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       }
 
       String[] selcom = new String[files.length];
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
+      int nmatched = matched.cardinality();
       if (nmatched < 4)
       {
-        // TODO: bail out here because superposition illdefined?
+        return (MessageManager.formatMessage(
+"label.insufficient_residues",
+                nmatched));
       }
 
       /*
@@ -359,35 +349,35 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           boolean run = false;
           StringBuilder molsel = new StringBuilder();
           molsel.append("{");
-          for (int r = 0; r < matched.length; r++)
+
+          int nextColumnMatch = matched.nextSetBit(0);
+          while (nextColumnMatch != -1)
           {
-            if (matched[r])
+            int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch];
+            if (lpos != pdbResNo - 1)
             {
-              int pdbResNo = structures[pdbfnum].pdbResNo[r];
-              if (lpos != pdbResNo - 1)
+              // discontinuity
+              if (lpos != -1)
               {
-                // discontinuity
-                if (lpos != -1)
-                {
-                  molsel.append(lpos);
-                  molsel.append(chainCd);
-                  molsel.append("|");
-                }
-                run = false;
+                molsel.append(lpos);
+                molsel.append(chainCd);
+                molsel.append("|");
               }
-              else
+              run = false;
+            }
+            else
+            {
+              // continuous run - and lpos >-1
+              if (!run)
               {
-                // continuous run - and lpos >-1
-                if (!run)
-                {
-                  // at the beginning, so add dash
-                  molsel.append(lpos);
-                  molsel.append("-");
-                }
-                run = true;
+                // at the beginning, so add dash
+                molsel.append(lpos);
+                molsel.append("-");
               }
-              lpos = pdbResNo;
+              run = true;
             }
+            lpos = pdbResNo;
+            nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
           }
           /*
            * add final selection phrase
@@ -473,6 +463,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
               + selectioncom.toString() + "); cartoons; ");
       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
     }
+
+    return null;
   }
 
   public void evalStateCommand(String command)
@@ -507,17 +499,15 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   /**
    * @param files
    * @param sr
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   @Override
   protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
   {
     return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, fr, alignment);
+            getSequence(), sr, viewPanel);
   }
 
   /**
index d5676c5..23e0a6f 100644 (file)
  */
 package jalview.ext.jmol;
 
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Routines for generating Jmol commands for Jalview/Jmol binding another
@@ -50,11 +55,15 @@ public class JmolCommands
    */
   public static StructureMappingcommandSet[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
-
-    ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    ColumnSelection cs = viewport.getColumnSelection();
+    AlignmentI al = viewport.getAlignment();
+    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
 
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
@@ -74,9 +83,9 @@ public class JmolCommands
         for (int sp, m = 0; m < mapping.length; m++)
         {
           if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+                  && (sp = al.findIndex(sequence[pdbfnum][s])) > -1)
           {
-            SequenceI asp = alignment.getSequenceAt(sp);
+            SequenceI asp = al.getSequenceAt(sp);
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
@@ -93,12 +102,17 @@ public class JmolCommands
 
               lastPos = pos;
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+              Color col = sr.getResidueColour(sequence[pdbfnum][s], r,
+                      finder);
 
-              if (fr != null)
+              /*
+               * shade hidden regions darker
+               */
+              if (!cs.isVisible(r))
               {
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
+                col = Color.GRAY;
               }
+
               String newSelcom = (mapping[m].getChain() != " " ? ":"
                       + mapping[m].getChain() : "")
                       + "/"
index c4aa2e7..95757fd 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
@@ -57,16 +61,16 @@ public class ChimeraCommands
    * @param sequence
    * @param sr
    * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   public static StructureMappingcommandSet[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
     Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
-            sequence, sr, fr, alignment);
+            sequence, sr, viewPanel);
 
     List<String> colourCommands = buildColourCommands(colourMap);
 
@@ -173,9 +177,8 @@ public class ChimeraCommands
 
   /**
    * <pre>
-   * Build a data structure which maps contiguous subsequences for each colour. 
-   * This generates a data structure from which we can easily generate the 
-   * Chimera command for colour by sequence.
+   * Build a data structure which records contiguous subsequences for each colour. 
+   * From this we can easily generate the Chimera command for colour by sequence.
    * Color
    *     Model number
    *         Chain
@@ -185,11 +188,17 @@ public class ChimeraCommands
    */
   protected static Map<Object, AtomSpecModel> buildColoursMap(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          SequenceI[][] sequence, SequenceRenderer sr,
+          AlignmentViewPanel viewPanel)
   {
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    ColumnSelection cs = viewport.getColumnSelection();
+    AlignmentI al = viewport.getAlignment();
     Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<Object, AtomSpecModel>();
     Color lastColour = null;
+
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
@@ -207,9 +216,9 @@ public class ChimeraCommands
         {
           final SequenceI seq = sequence[pdbfnum][s];
           if (mapping[m].getSequence() == seq
-                  && (sp = alignment.findIndex(seq)) > -1)
+                  && (sp = al.findIndex(seq)) > -1)
           {
-            SequenceI asp = alignment.getSequenceAt(sp);
+            SequenceI asp = al.getSequenceAt(sp);
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
@@ -224,7 +233,16 @@ public class ChimeraCommands
                 continue;
               }
 
-              Color colour = sr.getResidueColour(seq, r, fr);
+              Color colour = sr.getResidueColour(seq, r, finder);
+
+              /*
+               * darker colour for hidden regions
+               */
+              if (!cs.isVisible(r))
+              {
+                colour = Color.GRAY;
+              }
+
               final String chain = mapping[m].getChain();
 
               /*
@@ -296,16 +314,15 @@ public class ChimeraCommands
    * @param ssm
    * @param files
    * @param seqs
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment)
+          SequenceI[][] seqs, AlignmentViewPanel viewPanel)
   {
     Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
-            ssm, files, seqs, fr, alignment);
+            ssm, files, seqs, viewPanel);
 
     List<String> commands = buildSetAttributeCommands(featureMap);
 
@@ -325,22 +342,28 @@ public class ChimeraCommands
    * @param ssm
    * @param files
    * @param seqs
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment)
+          SequenceI[][] seqs, AlignmentViewPanel viewPanel)
   {
     Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
 
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    if (fr == null)
+    {
+      return theMap;
+    }
+
     List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
     if (visibleFeatures.isEmpty())
     {
       return theMap;
     }
 
+    AlignmentI alignment = viewPanel.getAlignment();
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
@@ -473,10 +496,13 @@ public class ChimeraCommands
         /*
          * for each distinct value recorded for this feature type,
          * add a command to set the attribute on the mapped residues
+         * Put values in single quotes, encoding any embedded single quotes
          */
         StringBuilder sb = new StringBuilder(128);
-        sb.append("setattr r ").append(attributeName).append(" \"")
-                .append(value.toString()).append("\" ");
+        String featureValue = value.toString();
+        featureValue = featureValue.replaceAll("\\'", "&#39;");
+        sb.append("setattr r ").append(attributeName).append(" '")
+                .append(featureValue).append("' ");
         sb.append(values.get(value).getAtomSpec());
         commands.add(sb.toString());
       }
index 2042ac4..ffce90c 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.ext.rbvi.chimera;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
@@ -29,7 +28,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResultMatchI;
-import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.httpserver.AbstractRequestHandler;
@@ -49,6 +48,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.BindException;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
@@ -172,13 +172,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       if (getSsm() != null)
       {
         getSsm().addStructureViewerListener(this);
-        // ssm.addSelectionListener(this);
-        FeatureRenderer fr = getFeatureRenderer(null);
-        if (fr != null)
-        {
-          fr.featuresAdded();
-        }
-        refreshGUI();
       }
       return true;
     } catch (Exception q)
@@ -340,21 +333,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Construct and send a command to align structures against a reference
-   * structure, based on one or more sequence alignments
-   * 
-   * @param _alignment
-   *          an array of alignments to process
-   * @param _refStructure
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param _hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * {@inheritDoc}
    */
   @Override
-  public void superposeStructures(AlignmentI[] _alignment,
+  public String superposeStructures(AlignmentI[] _alignment,
           int[] _refStructure, ColumnSelection[] _hiddenCols)
   {
     StringBuilder allComs = new StringBuilder(128);
@@ -362,7 +344,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 
     if (!waitForFileLoad(files))
     {
-      return;
+      return null;
     }
 
     refreshPdbEntries();
@@ -381,13 +363,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
 
       /*
-       * 'matched' array will hold 'true' for visible alignment columns where
+       * 'matched' bit i will be set for visible alignment columns i where
        * all sequences have a residue with a mapping to the PDB structure
        */
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
+      BitSet matched = new BitSet();
+      for (int m = 0; m < alignment.getWidth(); m++)
       {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
       }
 
       SuperposeData[] structures = new SuperposeData[files.length];
@@ -411,17 +396,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         refStructure = candidateRefStructure;
       }
 
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
+      int nmatched = matched.cardinality();
       if (nmatched < 4)
       {
-        // TODO: bail out here because superposition illdefined?
+        return MessageManager.formatMessage("label.insufficient_residues",
+                nmatched);
       }
 
       /*
@@ -434,41 +413,41 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         int lpos = -1;
         boolean run = false;
         StringBuilder molsel = new StringBuilder();
-        for (int r = 0; r < matched.length; r++)
+
+        int nextColumnMatch = matched.nextSetBit(0);
+        while (nextColumnMatch != -1)
         {
-          if (matched[r])
+          int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
+          if (lpos != pdbResNum - 1)
           {
-            int pdbResNum = structures[pdbfnum].pdbResNo[r];
-            if (lpos != pdbResNum - 1)
+            /*
+             * discontiguous - append last residue now
+             */
+            if (lpos != -1)
             {
-              /*
-               * discontiguous - append last residue now
-               */
-              if (lpos != -1)
-              {
-                molsel.append(String.valueOf(lpos));
-                molsel.append(chainCd);
-                molsel.append(",");
-              }
-              run = false;
+              molsel.append(String.valueOf(lpos));
+              molsel.append(chainCd);
+              molsel.append(",");
             }
-            else
+            run = false;
+          }
+          else
+          {
+            /*
+             * extending a contiguous run
+             */
+            if (!run)
             {
               /*
-               * extending a contiguous run
+               * start the range selection
                */
-              if (!run)
-              {
-                /*
-                 * start the range selection
-                 */
-                molsel.append(String.valueOf(lpos));
-                molsel.append("-");
-              }
-              run = true;
+              molsel.append(String.valueOf(lpos));
+              molsel.append("-");
             }
-            lpos = pdbResNum;
+            run = true;
           }
+          lpos = pdbResNum;
+          nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
         }
 
         /*
@@ -544,6 +523,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
                 .append(";" + command.toString());
       }
     }
+
+    String error = null;
     if (selectioncom.length() > 0)
     {
       // TODO: visually distinguish regions that were superposed
@@ -557,9 +538,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
       allComs.append("; ~display all; chain @CA|P; ribbon ")
               .append(selectioncom.toString()).append("; focus");
-      sendChimeraCommand(allComs.toString(), false);
+      List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+              true);
+      for (String reply : chimeraReplies)
+      {
+        if (reply.toLowerCase().contains("unequal numbers of atoms"))
+        {
+          error = reply;
+        }
+      }
     }
-
+    return error;
   }
 
   /**
@@ -701,17 +690,15 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   /**
    * @param files
    * @param sr
-   * @param fr
-   * @param alignment
+   * @param viewPanel
    * @return
    */
   @Override
   protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
+          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
   {
     return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, fr, alignment);
+            getSequence(), sr, viewPanel);
   }
 
   /**
@@ -1106,30 +1093,22 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * features visible in Jalview
    * 
    * @param avp
+   * @return
    */
-  public void sendFeaturesToViewer(AlignmentViewPanel avp)
+  public int sendFeaturesToViewer(AlignmentViewPanel avp)
   {
     // TODO refactor as required to pull up to an interface
     AlignmentI alignment = avp.getAlignment();
-    FeatureRenderer fr = getFeatureRenderer(avp);
-
-    /*
-     * fr is null if feature display is turned off
-     */
-    if (fr == null)
-    {
-      return;
-    }
 
     String[] files = getPdbFile();
     if (files == null)
     {
-      return;
+      return 0;
     }
 
     StructureMappingcommandSet commandSet = ChimeraCommands
             .getSetAttributeCommandsForFeatures(getSsm(), files,
-                    getSequence(), fr, alignment);
+                    getSequence(), avp);
     String[] commands = commandSet.commands;
     if (commands.length > 10)
     {
@@ -1142,6 +1121,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         sendAsynchronousCommand(command, null);
       }
     }
+    return commands.length;
   }
 
   /**
@@ -1274,7 +1254,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       /*
        * locate the mapped position in the alignment (if any)
        */
-      SearchResults sr = getSsm()
+      SearchResultsI sr = getSsm()
               .findAlignmentPositionsForStructurePositions(atoms);
 
       /*
index a41e10e..d65f1d5 100644 (file)
  */
 package jalview.ext.varna;
 
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
 
@@ -47,7 +47,8 @@ public class VarnaCommands
    */
   public static String[] getColourBySequenceCommand(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+          SequenceI[][] sequence, SequenceRenderer sr,
+          FeatureColourFinder finder,
           AlignmentI alignment)
   {
     ArrayList<String> str = new ArrayList<String>();
@@ -58,7 +59,9 @@ public class VarnaCommands
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
 
       if (mapping == null || mapping.length < 1)
+      {
         continue;
+      }
 
       int lastPos = -1;
       for (int s = 0; s < sequence[pdbfnum].length; s++)
@@ -79,14 +82,15 @@ public class VarnaCommands
               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
 
               if (pos < 1 || pos == lastPos)
+              {
                 continue;
+              }
 
               lastPos = pos;
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+              Color col = sr.getResidueColour(sequence[pdbfnum][s], r,
+                      finder);
 
-              if (fr != null)
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
               String newSelcom = (mapping[m].getChain() != " " ? ":"
                       + mapping[m].getChain() : "")
                       + "/"
index 27bfca8..a23df4c 100644 (file)
@@ -157,14 +157,14 @@ public class UniProtFTSRestClient extends FTSRestClient
     if (foundDataRow != null && foundDataRow.length > 0)
     {
       result = new ArrayList<FTSData>();
-      String titleRow = getDataColumnsFieldsAsTabDelimitedString(uniprotRestRequest
-              .getWantedFields());
-      // System.out.println(">>>>Title row : " + titleRow);
+      boolean firstRow = true;
       for (String dataRow : foundDataRow)
       {
-        if (dataRow.equalsIgnoreCase(titleRow))
+        // The first data row is usually the header data. This should be
+        // filtered out from the rest of the data See: JAL-2485
+        if (firstRow)
         {
-          // System.out.println(">>>>>>>>>> matched!!!");
+          firstRow = false;
           continue;
         }
         // System.out.println(dataRow);
index b5fc817..d4c87d8 100644 (file)
@@ -86,6 +86,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.schemes.TCoffeeColourScheme;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
 import jalview.ws.jws1.Discoverer;
@@ -160,6 +161,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   AlignViewport viewport;
 
+  ViewportRanges vpRanges;
+
   public AlignViewControllerI avc;
 
   List<AlignmentPanel> alignPanels = new ArrayList<AlignmentPanel>();
@@ -331,6 +334,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
     }
 
+    vpRanges = viewport.getRanges();
     avc = new jalview.controller.AlignViewController(this, viewport,
             alignPanel);
     if (viewport.getAlignmentConservationAnnotation() == null)
@@ -640,8 +644,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   new String[] { (viewport.cursorMode ? "on" : "off") }));
           if (viewport.cursorMode)
           {
-            alignPanel.getSeqPanel().seqCanvas.cursorX = viewport.startRes;
-            alignPanel.getSeqPanel().seqCanvas.cursorY = viewport.startSeq;
+            alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges
+                    .getStartRes();
+            alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges
+                    .getStartSeq();
           }
           alignPanel.getSeqPanel().seqCanvas.repaint();
           break;
@@ -679,8 +685,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           else
           {
-            alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                    - viewport.endSeq + viewport.startSeq);
+            alignPanel.setScrollValues(vpRanges.getStartRes(),
+                    2 * vpRanges.getStartSeq() - vpRanges.getEndSeq());
           }
           break;
         case KeyEvent.VK_PAGE_DOWN:
@@ -690,8 +696,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           else
           {
-            alignPanel.setScrollValues(viewport.startRes, viewport.startSeq
-                    + viewport.endSeq - viewport.startSeq);
+            alignPanel.setScrollValues(vpRanges.getStartRes(),
+                    vpRanges.getEndSeq());
           }
           break;
         }
@@ -2141,7 +2147,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
 
         // propagate alignment changed.
-        viewport.setEndSeq(alignment.getHeight());
+        vpRanges.setEndSeq(alignment.getHeight());
         if (annotationAdded)
         {
           // Duplicate sequence annotation in all views.
@@ -2545,7 +2551,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
                 column, viewport.getAlignment());
-        viewport.setStartRes(0);
+        vpRanges.setStartRes(0);
       }
       else
       {
@@ -2612,13 +2618,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    int startRes = seq.findPosition(vpRanges.getStartRes());
     // ShiftList shifts;
     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
     // edit.alColumnChanges=shifts.getInverse();
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
 
@@ -2651,12 +2657,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(viewport.startRes);
+    int startRes = seq.findPosition(vpRanges.getStartRes());
 
     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
             viewport.getAlignment()));
 
-    viewport.setStartRes(seq.findIndex(startRes) - 1);
+    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
 
     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
             .getSequences());
@@ -2744,6 +2750,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      */
     newap.av.replaceMappings(viewport.getAlignment());
 
+    /*
+     * start up cDNA consensus (if applicable) now mappings are in place
+     */
+    if (newap.av.initComplementConsensus())
+    {
+      newap.refresh(true); // adjust layout of annotations
+    }
+
     newap.av.viewName = getNewViewName(viewTitle);
 
     addAlignmentPanel(newap, true);
@@ -3321,7 +3335,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      */
     if (ResidueColourScheme.USER_DEFINED.equals(name))
     {
-      new UserDefinedColours(alignPanel, null);
+      new UserDefinedColours(alignPanel);
       return;
     }
 
@@ -3342,10 +3356,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public void changeColour(ColourSchemeI cs)
   {
     // TODO: pull up to controller method
-    if (cs != null)
-    {
-      ColourMenuHelper.setColourSelected(colourMenu, cs.getSchemeName());
-    }
+    ColourMenuHelper.setColourSelected(colourMenu, cs);
 
     viewport.setGlobalColourScheme(cs);
 
@@ -5694,10 +5705,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     colourMenu.add(annotationColour);
 
     ColourSchemeI colourScheme = viewport.getGlobalColourScheme();
-    String schemeName = colourScheme == null ? null : colourScheme
-            .getSchemeName();
-
-    ColourMenuHelper.setColourSelected(colourMenu, schemeName);
+    ColourMenuHelper.setColourSelected(colourMenu, colourScheme);
   }
 }
 
index e0efa7c..602e3a1 100644 (file)
@@ -46,12 +46,12 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.UserColourScheme;
-import jalview.structure.CommandListener;
 import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 import jalview.ws.params.AutoCalcSetting;
 
 import java.awt.Container;
@@ -72,7 +72,7 @@ import javax.swing.JInternalFrame;
  * @version $Revision: 1.141 $
  */
 public class AlignViewport extends AlignmentViewport implements
-        SelectionSource, CommandListener
+        SelectionSource
 {
   Font font;
 
@@ -238,10 +238,7 @@ public class AlignViewport extends AlignmentViewport implements
 
   void init()
   {
-    this.startRes = 0;
-    this.endRes = alignment.getWidth() - 1;
-    this.startSeq = 0;
-    this.endSeq = alignment.getHeight() - 1;
+    ranges = new ViewportRanges(this.alignment);
     applyViewProperties();
 
     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
@@ -855,7 +852,7 @@ public class AlignViewport extends AlignmentViewport implements
       }
     }
 
-    setEndSeq(getAlignment().getHeight());
+    ranges.setEndSeq(getAlignment().getHeight());
     firePropertyChange("alignment", null, getAlignment().getSequences());
   }
 
index e61b042..8ade5d6 100644 (file)
@@ -35,6 +35,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -69,6 +70,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 {
   public AlignViewport av;
 
+  ViewportRanges vpRanges;
+
   OverviewPanel overviewPanel;
 
   private SeqPanel seqPanel;
@@ -91,9 +94,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
 
-  int hextent = 0;
+  private int hextent = 0;
 
-  int vextent = 0;
+  private int vextent = 0;
 
   /*
    * Flag set while scrolling to follow complementary cDNA/protein scroll. When
@@ -113,6 +116,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     alignFrame = af;
     this.av = av;
+    vpRanges = av.getRanges();
     setSeqPanel(new SeqPanel(av, this));
     setIdPanel(new IdPanel(av, this));
 
@@ -377,7 +381,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
        */
       if (centre)
       {
-        int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
+        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
         start = Math.max(start - offset, 0);
         end = end + offset - 1;
       }
@@ -413,7 +417,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       // + av.getStartSeq() + ", ends=" + av.getEndSeq());
       if (!av.getWrapAlignment())
       {
-        if ((startv = av.getStartRes()) >= start)
+        if ((startv = vpRanges.getStartRes()) >= start)
         {
           /*
            * Scroll left to make start of search results visible
@@ -421,7 +425,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           // setScrollValues(start - 1, seqIndex); // plus one residue
           setScrollValues(start, seqIndex);
         }
-        else if ((endv = av.getEndRes()) <= end)
+        else if ((endv = vpRanges.getEndRes()) <= end)
         {
           /*
            * Scroll right to make end of search results visible
@@ -429,19 +433,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
           // setScrollValues(startv + 1 + end - endv, seqIndex); // plus one
           setScrollValues(startv + end - endv, seqIndex);
         }
-        else if ((starts = av.getStartSeq()) > seqIndex)
+        else if ((starts = vpRanges.getStartSeq()) > seqIndex)
         {
           /*
            * Scroll up to make start of search results visible
            */
-          setScrollValues(av.getStartRes(), seqIndex);
+          setScrollValues(vpRanges.getStartRes(), seqIndex);
         }
-        else if ((ends = av.getEndSeq()) <= seqIndex)
+        else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
         {
           /*
            * Scroll down to make end of search results visible
            */
-          setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
+          setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                  + 1);
         }
         /*
          * Else results are already visible - no need to scroll
@@ -464,10 +469,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     int cwidth = getSeqPanel().seqCanvas
             .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-    if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
+    if (res < vpRanges.getStartRes()
+            || res >= (vpRanges.getStartRes() + cwidth))
     {
       vscroll.setValue((res / cwidth));
-      av.startRes = vscroll.getValue() * cwidth;
+      vpRanges.setStartRes(vscroll.getValue() * cwidth);
     }
 
   }
@@ -591,7 +597,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     fontChanged();
     setAnnotationVisible(av.isShowAnnotation());
     boolean wrap = av.getWrapAlignment();
-    av.startSeq = 0;
+    vpRanges.setStartSeq(0);
     scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
     idwidthAdjuster.setVisible(!wrap);
@@ -688,7 +694,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void setScrollValues(int x, int y)
   {
-    // System.err.println("Scroll " + this.av.viewName + " to " + x + "," + y);
     if (av == null || av.getAlignment() == null)
     {
       return;
@@ -698,12 +703,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     if (av.hasHiddenColumns())
     {
+      // reset the width to exclude hidden columns
       width = av.getColumnSelection().findColumnPosition(width);
     }
 
-    av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
-            .getCharWidth())) - 1);
-
     hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
     vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
@@ -737,6 +740,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
       x = 0;
     }
 
+    // update endRes after x has (possibly) been adjusted
+    vpRanges.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
+            .getCharWidth())) - 1);
+
     /*
      * each scroll adjustment triggers adjustmentValueChanged, which resets the
      * 'do not scroll complement' flag; ensure it is the same for both
@@ -757,14 +764,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = av.getStartRes();
-    int oldY = av.getStartSeq();
+    int oldX = vpRanges.getStartRes();
+    int oldY = vpRanges.getStartSeq();
 
     if (evt.getSource() == hscroll)
     {
       int x = hscroll.getValue();
-      av.setStartRes(x);
-      av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
+      vpRanges.setStartRes(x);
+      vpRanges.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
               .getCharWidth())) - 1);
     }
 
@@ -778,8 +785,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
         {
           int rowSize = getSeqPanel().seqCanvas
                   .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-          av.setStartRes(offy * rowSize);
-          av.setEndRes((offy + 1) * rowSize);
+          vpRanges.setStartRes(offy * rowSize);
+          vpRanges.setEndRes((offy + 1) * rowSize);
         }
         else
         {
@@ -791,16 +798,18 @@ public class AlignmentPanel extends GAlignmentPanel implements
             @Override
             public void run()
             {
-              setScrollValues(av.getStartRes(), av.getStartSeq());
+              setScrollValues(vpRanges.getStartRes(),
+                      vpRanges.getStartSeq());
             }
           });
         }
       }
       else
       {
-        av.setStartSeq(offy);
-        av.setEndSeq(offy
-                + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()));
+        vpRanges.setStartSeq(offy);
+        vpRanges.setEndSeq(offy
+                + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight())
+                - 1);
       }
     }
 
@@ -809,8 +818,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
       overviewPanel.setBoxPosition();
     }
 
-    int scrollX = av.startRes - oldX;
-    int scrollY = av.startSeq - oldY;
+    int scrollX = vpRanges.getStartRes() - oldX;
+    int scrollY = vpRanges.getStartSeq() - oldY;
 
     if (av.getWrapAlignment() || !fastPaint)
     {
@@ -820,13 +829,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       // Make sure we're not trying to draw a panel
       // larger than the visible window
-      if (scrollX > av.endRes - av.startRes)
+      if (scrollX > vpRanges.getEndRes() - vpRanges.getStartRes())
       {
-        scrollX = av.endRes - av.startRes;
+        scrollX = vpRanges.getEndRes() - vpRanges.getStartRes();
       }
-      else if (scrollX < av.startRes - av.endRes)
+      else if (scrollX < vpRanges.getStartRes() - vpRanges.getEndRes())
       {
-        scrollX = av.startRes - av.endRes;
+        scrollX = vpRanges.getStartRes() - vpRanges.getEndRes();
       }
 
       if (scrollX != 0 || scrollY != 0)
@@ -926,7 +935,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     else
     {
-      setScrollValues(av.getStartRes(), av.getStartSeq());
+      setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
     }
   }
 
@@ -1875,4 +1884,26 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     return this.dontScrollComplement;
   }
+
+  /**
+   * Redraw sensibly.
+   * 
+   * @adjustHeight if true, try to recalculate panel height for visible
+   *               annotations
+   */
+  protected void refresh(boolean adjustHeight)
+  {
+    validateAnnotationDimensions(adjustHeight);
+    addNotify();
+    if (adjustHeight)
+    {
+      // sort, repaint, update overview
+      paintAlignment(true);
+    }
+    else
+    {
+      // lightweight repaint
+      repaint();
+    }
+  }
 }
index f6352d7..8500888 100644 (file)
@@ -21,6 +21,8 @@
 package jalview.gui;
 
 import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.GraphLine;
 import jalview.datamodel.SequenceGroup;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
@@ -35,9 +37,11 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Hashtable;
+import java.util.Vector;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
 import javax.swing.JInternalFrame;
@@ -49,27 +53,21 @@ import net.miginfocom.swing.MigLayout;
 @SuppressWarnings("serial")
 public class AnnotationColourChooser extends AnnotationRowFilter
 {
+  private static final int ONETHOUSAND = 1000;
 
-  ColourSchemeI oldcs;
+  private ColourSchemeI oldcs;
 
-  Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
+  private JButton defColours;
 
-  /**
-   * enabled if the user is dragging the slider - try to keep updates to a
-   * minimun
-   */
+  private Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
 
-  JComboBox<String> annotations;
+  private JCheckBox useOriginalColours = new JCheckBox();
 
-  JButton defColours = new JButton();
+  private JPanel minColour = new JPanel();
 
-  JPanel jPanel1 = new JPanel();
+  private JPanel maxColour = new JPanel();
 
-  JPanel jPanel2 = new JPanel();
-
-  BorderLayout borderLayout1 = new BorderLayout();
-
-  private JComboBox<String> threshold = new JComboBox<String>();
+  private JCheckBox thresholdIsMin = new JCheckBox();
 
   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
   {
@@ -108,7 +106,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      currentColours.setSelected(acg.isPredefinedColours()
+      useOriginalColours.setSelected(acg.isPredefinedColours()
               || acg.getBaseColour() != null);
       if (!acg.isPredefinedColours() && acg.getBaseColour() == null)
       {
@@ -118,15 +116,17 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       seqAssociated.setSelected(acg.isSeqAssociated());
 
     }
-    annotations = new JComboBox<String>(
-            getAnnotationItems(seqAssociated.isSelected()));
+    Vector<String> annotItems = getAnnotationItems(seqAssociated
+            .isSelected());
+    annotations = new JComboBox<String>(annotItems);
 
     populateThresholdComboBox(threshold);
 
     if (oldcs instanceof AnnotationColourGradient)
     {
       AnnotationColourGradient acg = (AnnotationColourGradient) oldcs;
-      annotations.setSelectedItem(acg.getAnnotation());
+      String label = getAnnotationMenuLabel(acg.getAnnotation());
+      annotations.setSelectedItem(label);
       switch (acg.getAboveThreshold())
       {
       case AnnotationColourGradient.NO_THRESHOLD:
@@ -143,16 +143,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter
                 MessageManager
                         .getString("error.implementation_error_dont_know_about_threshold_setting"));
       }
-      thresholdIsMin.setSelected(acg.thresholdIsMinMax);
+      thresholdIsMin.setSelected(acg.isThresholdIsMinMax());
       thresholdValue.setText("" + acg.getAnnotationThreshold());
     }
 
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-    }
+    jbInit();
     adjusting = false;
 
     updateView();
@@ -160,19 +155,11 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     frame.pack();
   }
 
-  public AnnotationColourChooser()
+  @Override
+  protected void jbInit()
   {
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
+    super.jbInit();
 
-  private void jbInit() throws Exception
-  {
     minColour.setFont(JvSwingUtils.getLabelFont());
     minColour.setBorder(BorderFactory.createEtchedBorder());
     minColour.setPreferredSize(new Dimension(40, 20));
@@ -203,26 +190,8 @@ public class AnnotationColourChooser extends AnnotationRowFilter
         }
       }
     });
-    ok.setOpaque(false);
-    ok.setText(MessageManager.getString("action.ok"));
-    ok.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        ok_actionPerformed();
-      }
-    });
-    cancel.setOpaque(false);
-    cancel.setText(MessageManager.getString("action.cancel"));
-    cancel.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        cancel_actionPerformed();
-      }
-    });
+
+    defColours = new JButton();
     defColours.setOpaque(false);
     defColours.setText(MessageManager.getString("action.set_defaults"));
     defColours.setToolTipText(MessageManager
@@ -237,48 +206,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       }
     });
 
-    annotations.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        annotations_actionPerformed();
-      }
-    });
-    getThreshold().addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        threshold_actionPerformed();
-      }
-    });
-    thresholdValue.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-    slider.setPaintLabels(false);
-    slider.setPaintTicks(true);
-    slider.setBackground(Color.white);
-    slider.setEnabled(false);
-    slider.setOpaque(false);
-    slider.setPreferredSize(new Dimension(100, 32));
-    thresholdValue.setEnabled(false);
-    thresholdValue.setColumns(7);
-    currentColours.setFont(JvSwingUtils.getLabelFont());
-    currentColours.setOpaque(false);
-    currentColours.setText(MessageManager
+    useOriginalColours.setFont(JvSwingUtils.getLabelFont());
+    useOriginalColours.setOpaque(false);
+    useOriginalColours.setText(MessageManager
             .getString("label.use_original_colours"));
-    currentColours.addActionListener(new ActionListener()
+    useOriginalColours.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        currentColours_actionPerformed();
+        originalColours_actionPerformed();
       }
     });
     thresholdIsMin.setBackground(Color.white);
@@ -307,7 +244,9 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       }
     });
 
-    this.setLayout(borderLayout1);
+    this.setLayout(new BorderLayout());
+    JPanel jPanel1 = new JPanel();
+    JPanel jPanel2 = new JPanel();
     jPanel2.setLayout(new MigLayout("", "[left][center][right]", "[][][]"));
     jPanel1.setBackground(Color.white);
     jPanel2.setBackground(Color.white);
@@ -316,7 +255,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     jPanel1.add(cancel);
     jPanel2.add(annotations, "grow, wrap");
     jPanel2.add(seqAssociated);
-    jPanel2.add(currentColours);
+    jPanel2.add(useOriginalColours);
     JPanel colpanel = new JPanel(new FlowLayout());
     colpanel.setBackground(Color.white);
     colpanel.add(minColour);
@@ -391,7 +330,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
   {
     if (slider.isEnabled())
     {
-      if (currentColours.isSelected()
+      if (useOriginalColours.isSelected()
               && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
       {
         updateView();
@@ -403,24 +342,16 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     }
   }
 
-  public JComboBox<String> getThreshold()
+  public void originalColours_actionPerformed()
   {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox<String> threshold)
-  {
-    this.threshold = threshold;
-  }
-
-  public void currentColours_actionPerformed()
-  {
-    if (currentColours.isSelected())
+    boolean selected = useOriginalColours.isSelected();
+    if (selected)
     {
       reset();
     }
-    maxColour.setEnabled(!currentColours.isSelected());
-    minColour.setEnabled(!currentColours.isSelected());
+    maxColour.setEnabled(!selected);
+    minColour.setEnabled(!selected);
+    thresholdIsMin.setEnabled(!selected);
     updateView();
   }
 
@@ -441,7 +372,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
 
     slider.setEnabled(true);
     thresholdValue.setEnabled(true);
-    thresholdIsMin.setEnabled(true);
+    thresholdIsMin.setEnabled(!useOriginalColours.isSelected());
 
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
@@ -455,7 +386,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     {
       getCurrentAnnotation()
               .setThreshold(
-                      new jalview.datamodel.GraphLine(
+                      new GraphLine(
                               (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
                               "Threshold", Color.black));
     }
@@ -463,19 +394,19 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
     {
       adjusting = true;
-      float range = getCurrentAnnotation().graphMax * 1000
-              - getCurrentAnnotation().graphMin * 1000;
+      float range = getCurrentAnnotation().graphMax * ONETHOUSAND
+              - getCurrentAnnotation().graphMin * ONETHOUSAND;
 
-      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
-      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
-      slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
+      slider.setMinimum((int) (getCurrentAnnotation().graphMin * ONETHOUSAND));
+      slider.setMaximum((int) (getCurrentAnnotation().graphMax * ONETHOUSAND));
+      slider.setValue((int) (getCurrentAnnotation().threshold.value * ONETHOUSAND));
       thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
       slider.setMajorTickSpacing((int) (range / 10f));
       slider.setEnabled(true);
       thresholdValue.setEnabled(true);
       adjusting = false;
     }
-    colorAlignmContaining(getCurrentAnnotation(), selectedThresholdItem);
+    colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem);
 
     ap.alignmentChanged();
     // ensure all associated views (overviews, structures, etc) are notified of
@@ -483,4 +414,60 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     ap.paintAlignment(true);
   }
 
+  protected boolean colorAlignmentContaining(AlignmentAnnotation currentAnn, int selectedThresholdOption)
+  {
+  
+    AnnotationColourGradient acg = null;
+    if (useOriginalColours.isSelected())
+    {
+      acg = new AnnotationColourGradient(currentAnn,
+              av.getGlobalColourScheme(), selectedThresholdOption);
+    }
+    else
+    {
+      acg = new AnnotationColourGradient(currentAnn,
+              minColour.getBackground(), maxColour.getBackground(),
+              selectedThresholdOption);
+    }
+    acg.setSeqAssociated(seqAssociated.isSelected());
+  
+    if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f)
+    {
+      acg.setPredefinedColours(true);
+    }
+  
+    acg.setThresholdIsMinMax(thresholdIsMin.isSelected());
+  
+    av.setGlobalColourScheme(acg);
+  
+    if (av.getAlignment().getGroups() != null)
+    {
+  
+      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+      {
+        if (sg.cs == null)
+        {
+          continue;
+        }
+  
+        if (useOriginalColours.isSelected())
+        {
+          sg.setColourScheme(new AnnotationColourGradient(currentAnn, sg
+                  .getColourScheme(), selectedThresholdOption));
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+        }
+        else
+        {
+          sg.setColourScheme(new AnnotationColourGradient(currentAnn,
+                  minColour.getBackground(), maxColour.getBackground(),
+                  selectedThresholdOption));
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+        }
+      }
+    }
+    return false;
+  }
+
 }
index 1290d70..637eb30 100644 (file)
@@ -30,7 +30,6 @@ import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
 import java.awt.BorderLayout;
 import java.awt.CardLayout;
 import java.awt.Color;
-import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
@@ -55,28 +54,10 @@ import net.miginfocom.swing.MigLayout;
 public class AnnotationColumnChooser extends AnnotationRowFilter implements
         ItemListener
 {
-
-  private JComboBox<String> annotations;
-
-  private JPanel actionPanel = new JPanel();
-
-  private JPanel thresholdPanel = new JPanel();
-
   private JPanel switchableViewsPanel = new JPanel(new CardLayout());
 
-  private CardLayout switchableViewsLayout = (CardLayout) (switchableViewsPanel
-          .getLayout());
-
-  private JPanel noGraphFilterView = new JPanel();
-
-  private JPanel graphFilterView = new JPanel();
-
   private JPanel annotationComboBoxPanel = new JPanel();
 
-  private BorderLayout borderLayout1 = new BorderLayout();
-
-  private JComboBox<String> threshold = new JComboBox<String>();
-
   private StructureFilterPanel gStructureFilterPanel;
 
   private StructureFilterPanel ngStructureFilterPanel;
@@ -107,17 +88,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
 
   private ColumnSelection oldColumnSelection;
 
-  public AnnotationColumnChooser()
-  {
-    try
-    {
-      jbInit();
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-
   public AnnotationColumnChooser(AlignViewport av, final AlignmentPanel ap)
   {
     super(av, ap);
@@ -169,72 +139,27 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     frame.pack();
   }
 
-  private void jbInit() throws Exception
+  @Override
+  protected void jbInit()
   {
-    ok.setOpaque(false);
-    ok.setText(MessageManager.getString("action.ok"));
-    ok.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        ok_actionPerformed();
-      }
-    });
-
-    cancel.setOpaque(false);
-    cancel.setText(MessageManager.getString("action.cancel"));
-    cancel.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        cancel_actionPerformed();
-      }
-    });
-
-    annotations.addItemListener(this);
-    annotations.setToolTipText(MessageManager
-            .getString("info.select_annotation_row"));
-    threshold.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        threshold_actionPerformed();
-      }
-    });
-
-    thresholdValue.setEnabled(false);
-    thresholdValue.setColumns(7);
-    thresholdValue.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-
-    slider.setPaintLabels(false);
-    slider.setPaintTicks(true);
-    slider.setBackground(Color.white);
-    slider.setEnabled(false);
-    slider.setOpaque(false);
-    slider.setPreferredSize(new Dimension(100, 32));
+    super.jbInit();
 
+    JPanel thresholdPanel = new JPanel();
     thresholdPanel.setBorder(new TitledBorder(MessageManager
             .getString("label.threshold_filter")));
     thresholdPanel.setBackground(Color.white);
     thresholdPanel.setFont(JvSwingUtils.getLabelFont());
     thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]"));
 
+    JPanel actionPanel = new JPanel();
     actionPanel.setBackground(Color.white);
     actionPanel.setFont(JvSwingUtils.getLabelFont());
 
+    JPanel graphFilterView = new JPanel();
     graphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
     graphFilterView.setBackground(Color.white);
 
+    JPanel noGraphFilterView = new JPanel();
     noGraphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
     noGraphFilterView.setBackground(Color.white);
 
@@ -270,7 +195,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     switchableViewsPanel.add(graphFilterView,
             AnnotationColumnChooser.GRAPH_VIEW);
 
-    this.setLayout(borderLayout1);
+    this.setLayout(new BorderLayout());
     this.add(annotationComboBoxPanel, java.awt.BorderLayout.PAGE_START);
     this.add(switchableViewsPanel, java.awt.BorderLayout.CENTER);
     this.add(actionPanel, java.awt.BorderLayout.SOUTH);
@@ -280,7 +205,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     this.validate();
   }
 
-  public void updateThresholdPanelToolTip()
+  protected void updateThresholdPanelToolTip()
   {
     thresholdValue.setToolTipText("");
     slider.setToolTipText("");
@@ -297,7 +222,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
   }
 
   @Override
-  public void reset()
+  protected void reset()
   {
     if (this.getOldColumnSelection() != null)
     {
@@ -338,26 +263,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     }
   }
 
-  public JComboBox<String> getThreshold()
-  {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox<String> threshold)
-  {
-    this.threshold = threshold;
-  }
-
-  public JComboBox<String> getAnnotations()
-  {
-    return annotations;
-  }
-
-  public void setAnnotations(JComboBox<String> annotations)
-  {
-    this.annotations = annotations;
-  }
-
   @Override
   public void updateView()
   {
@@ -568,6 +473,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     selectedAnnotationChanged();
   }
 
+  @Override
   public void selectedAnnotationChanged()
   {
     String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW;
@@ -585,6 +491,8 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     ngFurtherActionPanel.syncState();
     ngStructureFilterPanel.syncState();
 
+    CardLayout switchableViewsLayout = (CardLayout) switchableViewsPanel
+            .getLayout();
     switchableViewsLayout.show(switchableViewsPanel, currentView);
     updateView();
   }
index 4b774d6..c9535d0 100755 (executable)
@@ -292,33 +292,11 @@ public class AnnotationLabels extends JPanel implements MouseListener,
       aa[selectedRow].scaleColLabel = !aa[selectedRow].scaleColLabel;
     }
 
-    refresh(fullRepaint);
+    ap.refresh(fullRepaint);
 
   }
 
   /**
-   * Redraw sensibly.
-   * 
-   * @adjustHeight if true, try to recalculate panel height for visible
-   *               annotations
-   */
-  protected void refresh(boolean adjustHeight)
-  {
-    ap.validateAnnotationDimensions(adjustHeight);
-    ap.addNotify();
-    if (adjustHeight)
-    {
-      // sort, repaint, update overview
-      ap.paintAlignment(true);
-    }
-    else
-    {
-      // lightweight repaint
-      ap.repaint();
-    }
-  }
-
-  /**
    * DOCUMENT ME!
    * 
    * @param e
@@ -420,7 +398,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
             // ann.visible = false;
             // }
             // }
-            refresh(true);
+            ap.refresh(true);
           }
         });
         pop.add(hideType);
index b1f0edb..84f3e6c 100755 (executable)
@@ -55,7 +55,6 @@ import java.util.List;
 
 import javax.swing.JColorChooser;
 import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.Scrollable;
@@ -708,7 +707,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       return;
     }
 
-    int column = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int column = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
@@ -905,7 +905,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         return;
       }
     }
-    imgWidth = (av.endRes - av.startRes + 1) * av.getCharWidth();
+    imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes() + 1)
+            * av.getCharWidth();
     if (imgWidth < 1)
     {
       return;
@@ -946,7 +947,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       imageFresh = true;
     }
 
-    drawComponent(gg, av.startRes, av.endRes + 1);
+    drawComponent(gg, av.getRanges().getStartRes(), av.getRanges()
+            .getEndRes() + 1);
     imageFresh = false;
     g.drawImage(image, 0, 0, this);
   }
@@ -976,8 +978,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     gg.copyArea(0, 0, imgWidth, getHeight(),
             -horizontal * av.getCharWidth(), 0);
     long mtime = System.currentTimeMillis();
-    int sr = av.startRes;
-    int er = av.endRes + 1;
+    int sr = av.getRanges().getStartRes();
+    int er = av.getRanges().getEndRes() + 1;
     int transX = 0;
 
     if (horizontal > 0) // scrollbar pulled right, image to the left
index 7705bc3..c2bf41b 100644 (file)
@@ -22,14 +22,21 @@ package jalview.gui;
 
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.GraphLine;
-import jalview.datamodel.SequenceGroup;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
 import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Vector;
 
 import javax.swing.JButton;
@@ -51,22 +58,10 @@ public abstract class AnnotationRowFilter extends JPanel
 
   protected int[] annmap;
 
-  protected boolean enableSeqAss = false;
-
-  private AlignmentAnnotation currentAnnotation;
-
   protected boolean adjusting = false;
 
-  protected JCheckBox currentColours = new JCheckBox();
-
-  protected JPanel minColour = new JPanel();
-
-  protected JPanel maxColour = new JPanel();
-
   protected JCheckBox seqAssociated = new JCheckBox();
 
-  protected JCheckBox thresholdIsMin = new JCheckBox();
-
   protected JSlider slider = new JSlider();
 
   protected JTextField thresholdValue = new JTextField(20);
@@ -83,6 +78,38 @@ public abstract class AnnotationRowFilter extends JPanel
    */
   protected boolean sliderDragging = false;
 
+  protected JComboBox<String> threshold = new JComboBox<String>();
+
+  protected JComboBox<String> annotations;
+
+  /*
+   * map from annotation to its menu item display label
+   * - so we know which item to pre-select on restore
+   */
+  private Map<AlignmentAnnotation, String> annotationLabels;
+
+  private AlignmentAnnotation currentAnnotation;
+
+  /**
+   * Constructor
+   * 
+   * @param viewport
+   * @param alignPanel
+   */
+  public AnnotationRowFilter(AlignViewport viewport, final AlignmentPanel alignPanel)
+  {
+    this.av = viewport;
+    this.ap = alignPanel;
+    thresholdValue.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+        thresholdValue_actionPerformed();
+      }
+    });
+  }
+
   protected void addSliderChangeListener()
   {
 
@@ -132,33 +159,27 @@ public abstract class AnnotationRowFilter extends JPanel
     });
   }
 
-  public AnnotationRowFilter(AlignViewport av, final AlignmentPanel ap)
-  {
-    this.av = av;
-    this.ap = ap;
-    thresholdValue.addFocusListener(new FocusAdapter()
-    {
-      @Override
-      public void focusLost(FocusEvent e)
-      {
-        thresholdValue_actionPerformed();
-      }
-    });
-  }
-
-  public AnnotationRowFilter()
-  {
-
-  }
-
+/**
+ * Builds and returns a list of menu items (display text) for choice of
+ * annotation. Also builds maps between annotations, their positions in the
+ * list, and their display labels in the list.
+ * 
+ * @param isSeqAssociated
+ * @return
+ */
   public Vector<String> getAnnotationItems(boolean isSeqAssociated)
   {
+    annotationLabels = new HashMap<AlignmentAnnotation, String>();
+
     Vector<String> list = new Vector<String>();
     int index = 1;
     int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
+    seqAssociated.setEnabled(false);
     for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
     {
-      if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
+      AlignmentAnnotation annotation = av.getAlignment()
+              .getAlignmentAnnotation()[i];
+      if (annotation.sequenceRef == null)
       {
         if (isSeqAssociated)
         {
@@ -167,30 +188,29 @@ public abstract class AnnotationRowFilter extends JPanel
       }
       else
       {
-        enableSeqAss = true;
+        seqAssociated.setEnabled(true);
       }
-      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
+      String label = annotation.label;
       // add associated sequence ID if available
-      if (!isSeqAssociated
-              && av.getAlignment().getAlignmentAnnotation()[i].sequenceRef != null)
+      if (!isSeqAssociated && annotation.sequenceRef != null)
       {
-        label = label
-                + "_"
-                + av.getAlignment().getAlignmentAnnotation()[i].sequenceRef
-                        .getName();
+        label = label + "_" + annotation.sequenceRef.getName();
       }
       // make label unique
       if (!list.contains(label))
       {
         anmap[list.size()] = i;
         list.add(label);
+        annotationLabels.put(annotation, label);
       }
       else
       {
         if (!isSeqAssociated)
         {
           anmap[list.size()] = i;
-          list.add(label + "_" + (index++));
+          label = label + "_" + (index++);
+          list.add(label);
+          annotationLabels.put(annotation, label);
         }
       }
     }
@@ -213,11 +233,6 @@ public abstract class AnnotationRowFilter extends JPanel
     return selectedThresholdItem;
   }
 
-  public void modelChanged()
-  {
-    seqAssociated.setEnabled(enableSeqAss);
-  }
-
   public void ok_actionPerformed()
   {
     try
@@ -240,22 +255,22 @@ public abstract class AnnotationRowFilter extends JPanel
     }
   }
 
-  public void thresholdCheck_actionPerformed()
+  protected void thresholdCheck_actionPerformed()
   {
     updateView();
   }
 
-  public void annotations_actionPerformed()
+  protected void selectedAnnotationChanged()
   {
     updateView();
   }
 
-  public void threshold_actionPerformed()
+  protected void threshold_actionPerformed()
   {
     updateView();
   }
 
-  public void thresholdValue_actionPerformed()
+  protected void thresholdValue_actionPerformed()
   {
     try
     {
@@ -267,27 +282,34 @@ public abstract class AnnotationRowFilter extends JPanel
     }
   }
 
-  public void thresholdIsMin_actionPerformed()
+  protected void thresholdIsMin_actionPerformed()
   {
     updateView();
   }
 
-  protected void populateThresholdComboBox(JComboBox<String> threshold)
+  protected void populateThresholdComboBox(JComboBox<String> thresh)
   {
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_no_threshold"));
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_above_threshold"));
-    threshold.addItem(MessageManager
+    thresh.addItem(MessageManager
             .getString("label.threshold_feature_below_threshold"));
   }
 
-  protected void seqAssociated_actionPerformed(JComboBox<String> annotations)
+  /**
+   * Rebuilds the drop-down list of annotations to choose from when the 'per
+   * sequence only' checkbox is checked or unchecked.
+   * 
+   * @param anns
+   */
+  protected void seqAssociated_actionPerformed(JComboBox<String> anns)
   {
     adjusting = true;
-    String cursel = (String) annotations.getSelectedItem();
-    boolean isvalid = false, isseqs = seqAssociated.isSelected();
-    annotations.removeAllItems();
+    String cursel = (String) anns.getSelectedItem();
+    boolean isvalid = false;
+    boolean isseqs = seqAssociated.isSelected();
+    anns.removeAllItems();
     for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
     {
       if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
@@ -295,20 +317,22 @@ public abstract class AnnotationRowFilter extends JPanel
         isvalid = true;
         cursel = anitem;
       }
-      annotations.addItem(anitem);
+      anns.addItem(anitem);
     }
-    adjusting = false;
     if (isvalid)
     {
-      annotations.setSelectedItem(cursel);
+      anns.setSelectedItem(cursel);
     }
     else
     {
-      if (annotations.getItemCount() > 0)
+      if (anns.getItemCount() > 0)
       {
-        annotations.setSelectedIndex(0);
+        anns.setSelectedIndex(0);
       }
     }
+    adjusting = false;
+
+    updateView();
   }
 
   protected void propagateSeqAssociatedThreshold(boolean allAnnotation,
@@ -339,76 +363,107 @@ public abstract class AnnotationRowFilter extends JPanel
     }
   }
 
-  protected boolean colorAlignmContaining(AlignmentAnnotation currentAnn,
-          int selectedThresholdOption)
+  public AlignmentAnnotation getCurrentAnnotation()
   {
+    return currentAnnotation;
+  }
 
-    AnnotationColourGradient acg = null;
-    if (currentColours.isSelected())
-    {
-      acg = new AnnotationColourGradient(currentAnn,
-              av.getGlobalColourScheme(), selectedThresholdOption);
-    }
-    else
-    {
-      acg = new AnnotationColourGradient(currentAnn,
-              minColour.getBackground(), maxColour.getBackground(),
-              selectedThresholdOption);
-    }
-    acg.setSeqAssociated(seqAssociated.isSelected());
+  protected void setCurrentAnnotation(AlignmentAnnotation annotation)
+  {
+    this.currentAnnotation = annotation;
+  }
 
-    if (currentAnn.graphMin == 0f && currentAnn.graphMax == 0f)
-    {
-      acg.setPredefinedColours(true);
-    }
+  protected abstract void valueChanged(boolean updateAllAnnotation);
 
-    acg.thresholdIsMinMax = thresholdIsMin.isSelected();
+  protected abstract void updateView();
 
-    av.setGlobalColourScheme(acg);
+  protected abstract void reset();
 
-    if (av.getAlignment().getGroups() != null)
+  protected String getAnnotationMenuLabel(AlignmentAnnotation ann)
+  {
+    return annotationLabels.get(ann);
+  }
+
+  protected void jbInit()
+  {
+    ok.setOpaque(false);
+    ok.setText(MessageManager.getString("action.ok"));
+    ok.addActionListener(new ActionListener()
     {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        ok_actionPerformed();
+      }
+    });
 
-      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+    cancel.setOpaque(false);
+    cancel.setText(MessageManager.getString("action.cancel"));
+    cancel.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
       {
-        if (sg.cs == null)
-        {
-          continue;
-        }
+        cancel_actionPerformed();
+      }
+    });
 
-        AnnotationColourGradient scheme = null;
-        if (currentColours.isSelected())
-        {
-          scheme = new AnnotationColourGradient(currentAnn,
-                  sg.getColourScheme(), selectedThresholdOption);
-        }
-        else
-        {
-          scheme = new AnnotationColourGradient(currentAnn,
-                  minColour.getBackground(), maxColour.getBackground(),
-                  selectedThresholdOption);
-        }
-        scheme.setSeqAssociated(seqAssociated.isSelected());
-        sg.setColourScheme(scheme);
+    annotations.addItemListener(new ItemListener()
+    {
+      @Override
+      public void itemStateChanged(ItemEvent e)
+      {
+        selectedAnnotationChanged();
       }
-    }
-    return false;
+    });
+    annotations.setToolTipText(MessageManager
+            .getString("info.select_annotation_row"));
+
+    threshold.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        threshold_actionPerformed();
+      }
+    });
+
+    thresholdValue.setEnabled(false);
+    thresholdValue.setColumns(7);
+    thresholdValue.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        thresholdValue_actionPerformed();
+      }
+    });
+
+    slider.setPaintLabels(false);
+    slider.setPaintTicks(true);
+    slider.setBackground(Color.white);
+    slider.setEnabled(false);
+    slider.setOpaque(false);
+    slider.setPreferredSize(new Dimension(100, 32));
   }
 
-  public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation()
+  public JComboBox<String> getThreshold()
   {
-    return currentAnnotation;
+    return threshold;
   }
 
-  public void setCurrentAnnotation(
-          jalview.datamodel.AlignmentAnnotation currentAnnotation)
+  public void setThreshold(JComboBox<String> thresh)
   {
-    this.currentAnnotation = currentAnnotation;
+    this.threshold = thresh;
   }
 
-  public abstract void valueChanged(boolean updateAllAnnotation);
-
-  public abstract void updateView();
+  public JComboBox<String> getAnnotations()
+  {
+    return annotations;
+  }
 
-  public abstract void reset();
+  public void setAnnotations(JComboBox<String> anns)
+  {
+    this.annotations = anns;
+  }
 }
index 75e0c5e..f822358 100644 (file)
@@ -40,8 +40,6 @@ public class AppJmolBinding extends JalviewJmolBinding
 {
   private AppJmol appJmolWindow;
 
-  private FeatureRenderer fr = null;
-
   public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol)
   {
@@ -50,26 +48,6 @@ public class AppJmolBinding extends JalviewJmolBinding
   }
 
   @Override
-  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    AlignmentPanel ap = (alignment == null) ? appJmolWindow
-            .getAlignmentPanel() : (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
-    {
-      if (fr == null)
-      {
-        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
-      }
-      else
-      {
-        ap.updateFeatureRenderer(fr);
-      }
-    }
-
-    return fr;
-  }
-
-  @Override
   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
   {
     return new SequenceRenderer(((AlignmentPanel) alignment).av);
@@ -215,4 +193,18 @@ public class AppJmolBinding extends JalviewJmolBinding
   {
     return appJmolWindow;
   }
+
+  @Override
+  public jalview.api.FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment)
+  {
+    AlignmentPanel ap = (alignment == null) ? appJmolWindow
+            .getAlignmentPanel() : (AlignmentPanel) alignment;
+    if (ap.av.isShowSequenceFeatures())
+    {
+      return ap.av.getAlignPanel().getSeqPanel().seqCanvas.fr;
+    }
+
+    return null;
+  }
 }
index 67eddca..ec9feb7 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.FeatureRenderer;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
@@ -76,6 +77,10 @@ public class ChimeraViewFrame extends StructureViewerBase
 
   private Random random = new Random();
 
+  private int myWidth = 500;
+
+  private int myHeight = 150;
+
   /**
    * Initialise menu options.
    */
@@ -185,7 +190,9 @@ public class ChimeraViewFrame extends StructureViewerBase
    */
   protected void sendFeaturesToChimera()
   {
-    jmb.sendFeaturesToViewer(getAlignmentPanel());
+    int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
+    statusBar.setText(MessageManager.formatMessage("label.attributes_set",
+            count));
   }
 
   /**
@@ -254,7 +261,7 @@ public class ChimeraViewFrame extends StructureViewerBase
       useAlignmentPanelForSuperposition(ap);
     }
     jmb.setColourBySequence(true);
-    setSize(400, 400); // probably should be a configurable/dynamic default here
+    setSize(myWidth, myHeight);
     initMenus();
 
     addingStructures = false;
@@ -607,6 +614,16 @@ public class ChimeraViewFrame extends StructureViewerBase
       jmb.setFinishedInit(true);
       jmb.setLoadingFromArchive(false);
 
+      /*
+       * ensure that any newly discovered features (e.g. RESNUM)
+       * are added to any open feature settings dialog
+       */
+      FeatureRenderer fr = getBinding().getFeatureRenderer(null);
+      if (fr != null)
+      {
+        fr.featuresAdded();
+      }
+
       // refresh the sequence colours for the new structure(s)
       for (AlignmentPanel ap : _colourwith)
       {
@@ -852,19 +869,18 @@ public class ChimeraViewFrame extends StructureViewerBase
   }
 
   /**
-   * Override superclass method to make the 'Chimera' menu always visible, but
-   * 'Superpose with...' only enabled if there is more than one structure shown
+   * Sends commands to align structures according to associated alignment(s).
+   * 
+   * @return
    */
   @Override
-  public void updateTitleAndMenus()
+  protected String alignStructs_withAllAlignPanels()
   {
-    super.updateTitleAndMenus();
-    viewerActionMenu.setVisible(true);
-    viewSelectionMenu.setEnabled(false);
-    if (getBinding().getPdbFile().length > 1
-            && getBinding().getSequence().length > 1)
+    String reply = super.alignStructs_withAllAlignPanels();
+    if (reply != null)
     {
-      viewSelectionMenu.setEnabled(true);
+      statusBar.setText("Superposition failed: " + reply);
     }
+    return reply;
   }
 }
index 31780d6..b2b9574 100644 (file)
@@ -3,6 +3,7 @@ package jalview.gui;
 import jalview.bin.Cache;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.UserColourScheme;
@@ -22,6 +23,12 @@ public class ColourMenuHelper
 {
   public interface ColourChangeListener
   {
+    /**
+     * Change colour scheme to the selected scheme
+     * 
+     * @param name
+     *          the registered (unique) name of a colour scheme
+     */
     void changeColour_actionPerformed(String name);
   }
 
@@ -36,7 +43,7 @@ public class ColourMenuHelper
    * <li>Clustal</li>
    * <li>...other 'built-in' colours</li>
    * <li>...any user-defined colours</li>
-   * <li>User Defined..</li>
+   * <li>User Defined..(only for AlignFrame menu)</li>
    * </ul>
    * 
    * @param colourMenu
@@ -76,7 +83,7 @@ public class ColourMenuHelper
     }
 
     /*
-     * scan registered colour schemes (built-in or user-defined
+     * scan registered colour schemes (built-in or user-defined)
      * and add them to the menu (in the order they were registered)
      */
     Iterable<ColourSchemeI> colourSchemes = ColourSchemes.getInstance()
@@ -103,14 +110,14 @@ public class ColourMenuHelper
         /*
          * user-defined colour scheme loaded on startup or during the
          * Jalview session; right-click on this offers the option to
-         * remove it as a colour choice
+         * remove it as a colour choice (unless currently selected)
          */
         radioItem.addMouseListener(new MouseAdapter()
         {
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isPopupTrigger()) // Mac
+            if (evt.isPopupTrigger() && !radioItem.isSelected()) // Mac
             {
               offerRemoval();
             }
@@ -119,7 +126,7 @@ public class ColourMenuHelper
           @Override
           public void mouseReleased(MouseEvent evt)
           {
-            if (evt.isPopupTrigger()) // Windows
+            if (evt.isPopupTrigger() && !radioItem.isSelected()) // Windows
             {
               offerRemoval();
             }
@@ -170,6 +177,7 @@ public class ColourMenuHelper
       final String label = MessageManager.getString("action.user_defined");
       JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem(
               label);
+      userDefinedColour.setName(ResidueColourScheme.USER_DEFINED);
       userDefinedColour.addActionListener(new ActionListener()
       {
         @Override
@@ -186,20 +194,22 @@ public class ColourMenuHelper
   }
 
   /**
-   * Marks as selected the colour menu item matching the given name, or the
-   * first item ('None') if no match is found
+   * Marks as selected the colour menu item matching the given colour scheme, or
+   * the first item ('None') if no match is found. If the colour scheme is a
+   * user defined scheme, but not in the menu (this arises if a new scheme is
+   * defined and applied but not saved to file), then menu option
+   * "User Defined.." is selected.
    * 
    * @param colourMenu
-   * @param colourName
+   * @param cs
    */
-  public static void setColourSelected(JMenu colourMenu, String colourName)
+  public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
   {
-    if (colourName == null)
-    {
-      return;
-    }
+    String colourName = cs == null ? ResidueColourScheme.NONE : cs
+            .getSchemeName();
 
     JRadioButtonMenuItem none = null;
+    JRadioButtonMenuItem userDefined = null;
 
     /*
      * select the radio button whose name matches the colour name
@@ -209,38 +219,39 @@ public class ColourMenuHelper
     {
       if (menuItem instanceof JRadioButtonMenuItem)
       {
-        String buttonName = ((JRadioButtonMenuItem) menuItem).getName();
-        if (colourName.equals(buttonName))
+        JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) menuItem;
+        String buttonName = radioButton.getName();
+        if (buttonName.equals(colourName))
         {
-          ((JRadioButtonMenuItem) menuItem).setSelected(true);
+          radioButton.setSelected(true);
           return;
         }
         if (ResidueColourScheme.NONE.equals(buttonName))
         {
-          none = (JRadioButtonMenuItem) menuItem;
+          none = radioButton;
+        }
+        if (ResidueColourScheme.USER_DEFINED.equals(buttonName))
+        {
+          userDefined = radioButton;
         }
       }
     }
-    if (none != null)
+
+    /*
+     * no match by name; select User Defined.. if current scheme is a 
+     * user defined one, else select None
+     */
+    if (cs instanceof UserColourScheme && userDefined != null)
+    {
+      userDefined.setSelected(true);
+    }
+    else if (none != null)
     {
       none.setSelected(true);
     }
   }
 
   /**
-   * Marks as selected the colour menu item matching the given colour scheme, or
-   * the first item ('None') if no match is found
-   * 
-   * @param colourMenu
-   * @param cs
-   */
-  public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
-  {
-    setColourSelected(colourMenu, cs == null ? ResidueColourScheme.NONE
-            : cs.getSchemeName());
-  }
-
-  /**
    * Updates the USER_DEFINE_COLOURS preference to remove any de-registered
    * colour scheme
    */
@@ -258,7 +269,7 @@ public class ColourMenuHelper
     {
       try
       {
-        UserColourScheme ucs = ColourSchemes.loadColourScheme(file);
+        UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
         if (ucs != null
                 && ColourSchemes.getInstance().nameExists(ucs.getName()))
         {
index dc16a57..7d0eb7f 100644 (file)
@@ -2854,13 +2854,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
     {
-      if (jalview.ws.jws2.Jws2Discoverer.getDiscoverer().isRunning())
-      {
-        jalview.ws.jws2.Jws2Discoverer.getDiscoverer().setAborted(true);
-      }
       t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(
               changeSupport);
-
     }
     Thread t3 = null;
     {
index ed6a3c5..f519f99 100644 (file)
@@ -46,7 +46,6 @@ import java.util.Comparator;
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSpinner;
@@ -61,8 +60,7 @@ import javax.swing.SwingConstants;
  * @version $Revision$
  */
 public class FeatureRenderer extends
-        jalview.renderer.seqfeatures.FeatureRenderer implements
-        jalview.api.FeatureRenderer
+        jalview.renderer.seqfeatures.FeatureRenderer
 {
   Color resBoxColour;
 
@@ -339,9 +337,6 @@ public class FeatureRenderer extends
 
     if (reply == JvOptionPane.OK_OPTION && name.getText().length() > 0)
     {
-      // This ensures that the last sequence
-      // is refreshed and new features are rendered
-      lastSeq = null;
       lastFeatureAdded = name.getText().trim();
       lastFeatureGroupAdded = source.getText().trim();
       lastDescriptionAdded = description.getText().replaceAll("\n", " ");
index 37be8bc..aad0776 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.gui;
 
 import jalview.datamodel.SequenceI;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -158,33 +159,35 @@ public class IdCanvas extends JPanel
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     gg.copyArea(0, 0, getWidth(), imgHeight, 0,
             -vertical * av.getCharHeight());
 
-    int ss = av.startSeq;
-    int es = av.endSeq;
+    int ss = ranges.getStartSeq();
+    int es = ranges.getEndSeq();
     int transY = 0;
 
     if (vertical > 0) // scroll down
     {
       ss = es - vertical;
 
-      if (ss < av.startSeq)
+      if (ss < ranges.getStartSeq())
       { // ie scrolling too fast, more than a page at a time
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - (vertical * av.getCharHeight());
+        transY = imgHeight - ((vertical + 1) * av.getCharHeight());
       }
     }
-    else if (vertical < 0)
+    else if (vertical < 0) // scroll up
     {
       es = ss - vertical;
 
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -240,7 +243,7 @@ public class IdCanvas extends JPanel
     gg.setColor(Color.white);
     gg.fillRect(0, 0, getWidth(), imgHeight);
 
-    drawIds(av.getStartSeq(), av.endSeq);
+    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
 
     g.drawImage(image, 0, 0, this);
   }
@@ -314,10 +317,11 @@ public class IdCanvas extends JPanel
 
       int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
 
-      int rowSize = av.getEndRes() - av.getStartRes();
+      int rowSize = av.getRanges().getEndRes()
+              - av.getRanges().getStartRes();
 
       // Draw the rest of the panels
-      for (int ypos = hgap, row = av.startRes; (ypos <= getHeight())
+      for (int ypos = hgap, row = av.getRanges().getStartRes(); (ypos <= getHeight())
               && (row < maxwidth); ypos += cHeight, row += rowSize)
       {
         for (int i = starty; i < alheight; i++)
@@ -354,7 +358,7 @@ public class IdCanvas extends JPanel
 
       SequenceI sequence;
       // Now draw the id strings
-      for (int i = starty; i < endy; i++)
+      for (int i = starty; i <= endy; i++)
       {
         sequence = av.getAlignment().getSequenceAt(i);
 
index 6ae19f0..2074900 100755 (executable)
@@ -242,13 +242,14 @@ public class IdPanel extends JPanel implements MouseListener,
       return;
     }
 
-    if (mouseDragging && (e.getY() < 0) && (av.getStartSeq() > 0))
+    if (mouseDragging && (e.getY() < 0)
+            && (av.getRanges().getStartSeq() > 0))
     {
       scrollThread = new ScrollThread(true);
     }
 
     if (mouseDragging && (e.getY() >= getHeight())
-            && (av.getAlignment().getHeight() > av.getEndSeq()))
+            && (av.getAlignment().getHeight() > av.getRanges().getEndSeq()))
     {
       scrollThread = new ScrollThread(false);
     }
@@ -442,9 +443,10 @@ public class IdPanel extends JPanel implements MouseListener,
     int index = av.getAlignment().findIndex(list.get(0));
 
     // do we need to scroll the panel?
-    if ((av.getStartSeq() > index) || (av.getEndSeq() < index))
+    if ((av.getRanges().getStartSeq() > index)
+            || (av.getRanges().getEndSeq() < index))
     {
-      alignPanel.setScrollValues(av.getStartRes(), index);
+      alignPanel.setScrollValues(av.getRanges().getStartRes(), index);
     }
   }
 
@@ -486,11 +488,11 @@ public class IdPanel extends JPanel implements MouseListener,
         if (alignPanel.scrollUp(up))
         {
           // scroll was ok, so add new sequence to selection
-          int seq = av.getStartSeq();
+          int seq = av.getRanges().getStartSeq();
 
           if (!up)
           {
-            seq = av.getEndSeq();
+            seq = av.getRanges().getEndSeq();
           }
 
           if (seq < lastid)
index 2a3d788..d92f6c0 100644 (file)
@@ -88,9 +88,12 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
 
   private JLabel dbstatus, dbstatex;
 
+  private JPanel mainPanel = new JPanel(new BorderLayout());
+
   public JDatabaseTree(jalview.ws.SequenceFetcher sfetch)
   {
-    initDialogFrame(this, true, false,
+    mainPanel.add(this);
+    initDialogFrame(mainPanel, true, false,
             MessageManager
                     .getString("label.select_database_retrieval_source"),
             650, 490);
@@ -148,19 +151,19 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
     }
     // and sort the tree
     sortTreeNodes(root);
-    svp = new JScrollPane();
-    // svp.setAutoscrolls(true);
     dbviews = new JTree(new DefaultTreeModel(root, false));
     dbviews.setCellRenderer(new DbTreeRenderer(this));
 
     dbviews.getSelectionModel().setSelectionMode(
             TreeSelectionModel.SINGLE_TREE_SELECTION);
-    svp.getViewport().setView(dbviews);
-    // svp.getViewport().setMinimumSize(new Dimension(300,200));
-    // svp.setSize(300,250);
-    // JPanel panel=new JPanel();
-    // panel.setSize(new Dimension(350,220));
-    // panel.add(svp);
+    svp = new JScrollPane(dbviews);
+    svp.setMinimumSize(new Dimension(100, 200));
+    svp.setPreferredSize(new Dimension(200, 400));
+    svp.setMaximumSize(new Dimension(300, 600));
+
+    JPanel panel = new JPanel(new BorderLayout());
+    panel.setSize(new Dimension(350, 220));
+    panel.add(svp);
     dbviews.addTreeSelectionListener(new TreeSelectionListener()
     {
 
@@ -200,7 +203,6 @@ public class JDatabaseTree extends JalviewDialog implements KeyListener
     dbstat.add(dbstatex);
     jc.add(dbstat, BorderLayout.SOUTH);
     jc.validate();
-    // j.setPreferredSize(new Dimension(300,50));
     add(jc, BorderLayout.CENTER);
     ok.setEnabled(false);
     j.add(ok);
index 3ac453f..c19f005 100644 (file)
@@ -29,6 +29,7 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.GraphLine;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceGroup;
@@ -77,7 +78,6 @@ import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.FeatureColour;
-import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureSelectionManager;
@@ -87,6 +87,7 @@ import jalview.util.Platform;
 import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 import jalview.ws.jws2.Jws2Discoverer;
@@ -755,6 +756,7 @@ public class Jalview2XML
     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
 
     AlignViewport av = ap.av;
+    ViewportRanges vpRanges = av.getRanges();
 
     JalviewModel object = new JalviewModel();
     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
@@ -1270,8 +1272,8 @@ public class Jalview2XML
       view.setWidth(size.width);
       view.setHeight(size.height);
 
-      view.setStartRes(av.startRes);
-      view.setStartSeq(av.startSeq);
+      view.setStartRes(vpRanges.getStartRes());
+      view.setStartSeq(vpRanges.getStartSeq());
 
       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
       {
@@ -1711,6 +1713,15 @@ public class Jalview2XML
     return matchedFile;
   }
 
+  /**
+   * Populates the AnnotationColours xml for save. This captures the settings of
+   * the options in the 'Colour by Annotation' dialog.
+   * 
+   * @param acg
+   * @param userColours
+   * @param jms
+   * @return
+   */
   private AnnotationColours constructAnnotationColours(
           AnnotationColourGradient acg, List<UserColourScheme> userColours,
           JalviewModelSequence jms)
@@ -1718,8 +1729,9 @@ public class Jalview2XML
     AnnotationColours ac = new AnnotationColours();
     ac.setAboveThreshold(acg.getAboveThreshold());
     ac.setThreshold(acg.getAnnotationThreshold());
-    ac.setAnnotation(acg.getAnnotation());
-    if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
+    // 2.10.2 save annotationId (unique) not annotation label
+    ac.setAnnotation(acg.getAnnotation().annotationId);
+    if (acg.getBaseColour() instanceof UserColourScheme)
     {
       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
               userColours, jms));
@@ -2639,10 +2651,12 @@ public class Jalview2XML
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                    finalErrorMessage, "Error "
-                            + (saving ? "saving" : "loading")
-                            + " Jalview file", JvOptionPane.WARNING_MESSAGE);
+            JvOptionPane
+                    .showInternalMessageDialog(Desktop.desktop,
+                            finalErrorMessage, "Error "
+                                    + (saving ? "saving" : "loading")
+                                    + " Jalview file",
+                            JvOptionPane.WARNING_MESSAGE);
           }
         });
       }
@@ -4448,8 +4462,8 @@ public class Jalview2XML
     af.viewport.setThresholdTextColour(view.getTextColThreshold());
     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
             .isShowUnconserved() : false);
-    af.viewport.setStartRes(view.getStartRes());
-    af.viewport.setStartSeq(view.getStartSeq());
+    af.viewport.getRanges().setStartRes(view.getStartRes());
+    af.viewport.getRanges().setStartSeq(view.getStartSeq());
     af.alignPanel.updateLayout();
     ColourSchemeI cs = null;
     // apply colourschemes
@@ -4689,12 +4703,21 @@ public class Jalview2XML
     return af;
   }
 
+  /**
+   * Reads saved data to restore Colour by Annotation settings
+   * 
+   * @param viewAnnColour
+   * @param af
+   * @param al
+   * @param jms
+   * @param checkGroupAnnColour
+   * @return
+   */
   private ColourSchemeI constructAnnotationColour(
           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
           JalviewModelSequence jms, boolean checkGroupAnnColour)
   {
     boolean propagateAnnColour = false;
-    ColourSchemeI cs = null;
     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
     if (checkGroupAnnColour && al.getGroups() != null
             && al.getGroups().size() > 0)
@@ -4702,7 +4725,7 @@ public class Jalview2XML
       // pre 2.8.1 behaviour
       // check to see if we should transfer annotation colours
       propagateAnnColour = true;
-      for (jalview.datamodel.SequenceGroup sg : al.getGroups())
+      for (SequenceGroup sg : al.getGroups())
       {
         if (sg.getColourScheme() instanceof AnnotationColourGradient)
         {
@@ -4710,107 +4733,84 @@ public class Jalview2XML
         }
       }
     }
-    // int find annotation
-    if (annAlignment.getAlignmentAnnotation() != null)
+
+    /*
+     * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
+     */
+    String annotationId = viewAnnColour.getAnnotation();
+    AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
+
+    /*
+     * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
+     */
+    if (matchedAnnotation == null && annAlignment.getAlignmentAnnotation() != null)
     {
       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
       {
-        if (annAlignment.getAlignmentAnnotation()[i].label
-                .equals(viewAnnColour.getAnnotation()))
+        if (annotationId
+                .equals(annAlignment.getAlignmentAnnotation()[i].label))
         {
-          if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
-          {
-            annAlignment.getAlignmentAnnotation()[i]
-                    .setThreshold(new jalview.datamodel.GraphLine(
-                            viewAnnColour.getThreshold(), "Threshold",
-                            java.awt.Color.black)
-
-                    );
-          }
-
-          if (viewAnnColour.getColourScheme().equals(
-                  ResidueColourScheme.NONE))
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    new java.awt.Color(viewAnnColour.getMinColour()),
-                    new java.awt.Color(viewAnnColour.getMaxColour()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          else if (viewAnnColour.getColourScheme().startsWith("ucs"))
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    getUserColourScheme(jms,
-                            viewAnnColour.getColourScheme()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          else
-          {
-            cs = new AnnotationColourGradient(
-                    annAlignment.getAlignmentAnnotation()[i],
-                    ColourSchemeProperty.getColourScheme(al,
-                            viewAnnColour.getColourScheme()),
-                    viewAnnColour.getAboveThreshold());
-          }
-          if (viewAnnColour.hasPerSequence())
-          {
-            ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
-                    .isPerSequence());
-          }
-          if (viewAnnColour.hasPredefinedColours())
-          {
-            ((AnnotationColourGradient) cs)
-                    .setPredefinedColours(viewAnnColour
-                            .isPredefinedColours());
-          }
-          if (propagateAnnColour && al.getGroups() != null)
-          {
-            // Also use these settings for all the groups
-            for (int g = 0; g < al.getGroups().size(); g++)
-            {
-              jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
-
-              if (sg.cs == null)
-              {
-                continue;
-              }
+          matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
+          break;
+        }
+      }
+    }
+    if (matchedAnnotation == null)
+    {
+      System.err.println("Failed to match annotation colour scheme for "
+              + annotationId);
+      return null;
+    }
+    if (matchedAnnotation.getThreshold() == null)
+    {
+      matchedAnnotation.setThreshold(new GraphLine(viewAnnColour.getThreshold(),
+              "Threshold", Color.black));
+    }
 
-              /*
-               * if (viewAnnColour.getColourScheme().equals(ResidueColourScheme.NONE)) { sg.cs =
-               * new AnnotationColourGradient(
-               * annAlignment.getAlignmentAnnotation()[i], new
-               * java.awt.Color(viewAnnColour. getMinColour()), new
-               * java.awt.Color(viewAnnColour. getMaxColour()),
-               * viewAnnColour.getAboveThreshold()); } else
-               */
-              {
-                sg.setColourScheme(new AnnotationColourGradient(
-                        annAlignment.getAlignmentAnnotation()[i], sg
-                                .getColourScheme(), viewAnnColour
-                                .getAboveThreshold()));
-                if (cs instanceof AnnotationColourGradient)
-                {
-                  if (viewAnnColour.hasPerSequence())
-                  {
-                    ((AnnotationColourGradient) cs)
-                            .setSeqAssociated(viewAnnColour.isPerSequence());
-                  }
-                  if (viewAnnColour.hasPredefinedColours())
-                  {
-                    ((AnnotationColourGradient) cs)
-                            .setPredefinedColours(viewAnnColour
-                                    .isPredefinedColours());
-                  }
-                }
-              }
+    AnnotationColourGradient cs = null;
+    if (viewAnnColour.getColourScheme().equals("None"))
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation, new Color(
+              viewAnnColour.getMinColour()), new Color(
+              viewAnnColour.getMaxColour()),
+              viewAnnColour.getAboveThreshold());
+    }
+    else if (viewAnnColour.getColourScheme().startsWith("ucs"))
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation, getUserColourScheme(
+              jms, viewAnnColour.getColourScheme()),
+              viewAnnColour.getAboveThreshold());
+    }
+    else
+    {
+      cs = new AnnotationColourGradient(matchedAnnotation,
+              ColourSchemeProperty.getColourScheme(al,
+                      viewAnnColour.getColourScheme()),
+              viewAnnColour.getAboveThreshold());
+    }
 
-            }
-          }
+    boolean perSequenceOnly = viewAnnColour.isPerSequence();
+    boolean useOriginalColours = viewAnnColour.isPredefinedColours();
+    cs.setSeqAssociated(perSequenceOnly);
+    cs.setPredefinedColours(useOriginalColours);
 
-          break;
+    if (propagateAnnColour && al.getGroups() != null)
+    {
+      // Also use these settings for all the groups
+      for (int g = 0; g < al.getGroups().size(); g++)
+      {
+        SequenceGroup sg = al.getGroups().get(g);
+        if (sg.getGroupColourScheme() == null)
+        {
+          continue;
         }
 
+        AnnotationColourGradient groupScheme = new AnnotationColourGradient(
+                matchedAnnotation, sg.getColourScheme(),
+                viewAnnColour.getAboveThreshold());
+        sg.setColourScheme(groupScheme);
+        groupScheme.setSeqAssociated(perSequenceOnly);
+        groupScheme.setPredefinedColours(useOriginalColours);
       }
     }
     return cs;
index e751a2c..6235cbe 100755 (executable)
@@ -367,8 +367,8 @@ public class Jalview2XML_V1
 
     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
             view.getHeight());
-    af.viewport.setStartRes(view.getStartRes());
-    af.viewport.setStartSeq(view.getStartSeq());
+    af.viewport.getRanges().setStartRes(view.getStartRes());
+    af.viewport.getRanges().setStartSeq(view.getStartSeq());
     af.viewport.setShowAnnotation(view.getShowAnnotation());
     af.viewport.setAbovePIDThreshold(view.getPidSelected());
     af.viewport.setColourText(view.getShowColourText());
index a1f05bd..c9b35d8 100644 (file)
@@ -34,9 +34,6 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
 {
   private ChimeraViewFrame cvf;
 
-  private FeatureRenderer fr = null;
-
-
   public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
           StructureSelectionManager ssm, PDBEntry[] pdbentry,
           SequenceI[][] sequenceIs, DataSourceType protocol)
@@ -52,17 +49,10 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
             : (AlignmentPanel) alignment;
     if (ap.av.isShowSequenceFeatures())
     {
-      if (fr == null)
-      {
-        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
-      }
-      else
-      {
-        ap.updateFeatureRenderer(fr);
-      }
+      return ap.getSeqPanel().seqCanvas.fr;
     }
 
-    return fr;
+    return null;
   }
 
   @Override
index 1c48690..c530fdc 100755 (executable)
  */
 package jalview.gui;
 
+import jalview.datamodel.SequenceI;
 import jalview.renderer.AnnotationRenderer;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.viewmodel.OverviewDimensions;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -35,57 +38,51 @@ import java.awt.image.BufferedImage;
 import javax.swing.JPanel;
 
 /**
- * DOCUMENT ME!
+ * Panel displaying an overview of the full alignment, with an interactive box
+ * representing the viewport onto the alignment.
  * 
  * @author $author$
  * @version $Revision$
  */
 public class OverviewPanel extends JPanel implements Runnable
 {
-  BufferedImage miniMe;
+  private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
 
-  AlignViewport av;
+  private final AnnotationRenderer renderer = new AnnotationRenderer();
 
-  AlignmentPanel ap;
+  private OverviewDimensions od;
 
-  final AnnotationRenderer renderer = new AnnotationRenderer();
+  private BufferedImage miniMe;
 
-  float scalew = 1f;
-
-  float scaleh = 1f;
-
-  int width;
-
-  int sequencesHeight;
-
-  int graphHeight = 20;
-
-  int boxX = -1;
+  private BufferedImage lastMiniMe = null;
 
-  int boxY = -1;
+  private AlignViewport av;
 
-  int boxWidth = -1;
+  private AlignmentPanel ap;
 
-  int boxHeight = -1;
+  //
+  private boolean resizing = false;
 
-  boolean resizing = false;
+  // This is set true if the user resizes whilst
+  // the overview is being calculated
+  private boolean resizeAgain = false;
 
   // Can set different properties in this seqCanvas than
   // main visible SeqCanvas
-  SequenceRenderer sr;
+  private SequenceRenderer sr;
 
   jalview.renderer.seqfeatures.FeatureRenderer fr;
 
   /**
    * Creates a new OverviewPanel object.
    * 
-   * @param ap
-   *          DOCUMENT ME!
+   * @param alPanel
+   *          The alignment panel which is shown in the overview panel
    */
-  public OverviewPanel(AlignmentPanel ap)
+  public OverviewPanel(AlignmentPanel alPanel)
   {
-    this.av = ap.av;
-    this.ap = ap;
+    this.av = alPanel.av;
+    this.ap = alPanel;
     setLayout(null);
 
     sr = new SequenceRenderer(av);
@@ -93,44 +90,17 @@ public class OverviewPanel extends JPanel implements Runnable
     sr.forOverview = true;
     fr = new FeatureRenderer(ap);
 
-    // scale the initial size of overviewpanel to shape of alignment
-    float initialScale = (float) av.getAlignment().getWidth()
-            / (float) av.getAlignment().getHeight();
-
-    if (av.getAlignmentConservationAnnotation() == null)
-    {
-      graphHeight = 0;
-    }
-
-    if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-    {
-      // wider
-      width = 400;
-      sequencesHeight = (int) (400f / initialScale);
-      if (sequencesHeight < 40)
-      {
-        sequencesHeight = 40;
-      }
-    }
-    else
-    {
-      // taller
-      width = (int) (400f * initialScale);
-      sequencesHeight = 300;
-
-      if (width < 120)
-      {
-        width = 120;
-      }
-    }
+    od = new OverviewDimensions(av.getRanges(),
+            (av.isShowAnnotation() && av
+                    .getAlignmentConservationAnnotation() != null));
 
     addComponentListener(new ComponentAdapter()
     {
       @Override
       public void componentResized(ComponentEvent evt)
       {
-        if ((getWidth() != width)
-                || (getHeight() != (sequencesHeight + graphHeight)))
+        if ((getWidth() != od.getWidth())
+                || (getHeight() != (od.getHeight())))
         {
           updateOverviewImage();
         }
@@ -144,11 +114,10 @@ public class OverviewPanel extends JPanel implements Runnable
       {
         if (!av.getWrapAlignment())
         {
-          // TODO: feature: jv2.5 detect shift drag and update selection from
-          // it.
-          boxX = evt.getX();
-          boxY = evt.getY();
-          checkValid();
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av
+                  .getColumnSelection(), av.getRanges());
+          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
         }
       }
     });
@@ -160,9 +129,10 @@ public class OverviewPanel extends JPanel implements Runnable
       {
         if (!av.getWrapAlignment())
         {
-          boxX = evt.getX();
-          boxY = evt.getY();
-          checkValid();
+          od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                  .getAlignment().getHiddenSequences(), av
+                  .getColumnSelection(), av.getRanges());
+          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
         }
       }
     });
@@ -171,60 +141,7 @@ public class OverviewPanel extends JPanel implements Runnable
   }
 
   /**
-   * DOCUMENT ME!
-   */
-  void checkValid()
-  {
-    if (boxY < 0)
-    {
-      boxY = 0;
-    }
-
-    if (boxY > (sequencesHeight - boxHeight))
-    {
-      boxY = sequencesHeight - boxHeight + 1;
-    }
-
-    if (boxX < 0)
-    {
-      boxX = 0;
-    }
-
-    if (boxX > (width - boxWidth))
-    {
-      if (av.hasHiddenColumns())
-      {
-        // Try smallest possible box
-        boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
-      }
-      boxX = width - boxWidth;
-    }
-
-    int col = (int) (boxX / scalew / av.getCharWidth());
-    int row = (int) (boxY / scaleh / av.getCharHeight());
-
-    if (av.hasHiddenColumns())
-    {
-      if (!av.getColumnSelection().isVisible(col))
-      {
-        return;
-      }
-
-      col = av.getColumnSelection().findColumnPosition(col);
-    }
-
-    if (av.hasHiddenRows())
-    {
-      row = av.getAlignment().getHiddenSequences()
-              .findIndexWithoutHiddenSeqs(row);
-    }
-
-    ap.setScrollValues(col, row);
-
-  }
-
-  /**
-   * DOCUMENT ME!
+   * Updates the overview image when the related alignment panel is updated
    */
   public void updateOverviewImage()
   {
@@ -238,24 +155,17 @@ public class OverviewPanel extends JPanel implements Runnable
 
     if ((getWidth() > 0) && (getHeight() > 0))
     {
-      width = getWidth();
-      sequencesHeight = getHeight() - graphHeight;
+      od.setWidth(getWidth());
+      od.setHeight(getHeight());
     }
 
-    setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
 
     Thread thread = new Thread(this);
     thread.start();
     repaint();
   }
 
-  // This is set true if the user resizes whilst
-  // the overview is being calculated
-  boolean resizeAgain = false;
-
-  /**
-   * DOCUMENT ME!
-   */
   @Override
   public void run()
   {
@@ -266,148 +176,47 @@ public class OverviewPanel extends JPanel implements Runnable
       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
     }
 
-    int alwidth = av.getAlignment().getWidth();
-    int alheight = av.getAlignment().getHeight()
-            + av.getAlignment().getHiddenSequences().getSize();
-
-    setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
-
-    int fullsizeWidth = alwidth * av.getCharWidth();
-    int fullsizeHeight = alheight * av.getCharHeight();
+    // why do we need to set preferred size again? was set in
+    // updateOverviewImage
+    setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
 
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    miniMe = new BufferedImage(width, sequencesHeight + graphHeight,
+    miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
             BufferedImage.TYPE_INT_RGB);
 
     Graphics mg = miniMe.getGraphics();
     mg.setColor(Color.orange);
-    mg.fillRect(0, 0, width, miniMe.getHeight());
-
-    float sampleCol = (float) alwidth / (float) width;
-    float sampleRow = (float) alheight / (float) sequencesHeight;
+    mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
 
-    int lastcol = -1, lastrow = -1;
-    int color = Color.white.getRGB();
-    int row, col;
-    jalview.datamodel.SequenceI seq;
-    final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-            .hasHiddenColumns();
-    boolean hiddenRow = false;
-    // get hidden row and hidden column map once at beginning.
-    // clone featureRenderer settings to avoid race conditions... if state is
-    // updated just need to refresh again
-    for (row = 0; row < sequencesHeight; row++)
-    {
-      if (resizeAgain)
-      {
-        break;
-      }
-      if ((int) (row * sampleRow) == lastrow)
-      {
-        // No need to recalculate the colours,
-        // Just copy from the row above
-        for (col = 0; col < width; col++)
-        {
-          if (resizeAgain)
-          {
-            break;
-          }
-          miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
-        }
-        continue;
-      }
-
-      lastrow = (int) (row * sampleRow);
-
-      hiddenRow = false;
-      if (hasHiddenRows)
-      {
-        seq = av.getAlignment().getHiddenSequences()
-                .getHiddenSequence(lastrow);
-        if (seq == null)
-        {
-          int index = av.getAlignment().getHiddenSequences()
-                  .findIndexWithoutHiddenSeqs(lastrow);
-
-          seq = av.getAlignment().getSequenceAt(index);
-        }
-        else
-        {
-          hiddenRow = true;
-        }
-      }
-      else
-      {
-        seq = av.getAlignment().getSequenceAt(lastrow);
-      }
-
-      if (seq == null)
-      {
-        System.out.println(lastrow + " null");
-        continue;
-      }
-
-      for (col = 0; col < width; col++)
-      {
-        if (resizeAgain)
-        {
-          break;
-        }
-        if ((int) (col * sampleCol) == lastcol
-                && (int) (row * sampleRow) == lastrow)
-        {
-          miniMe.setRGB(col, row, color);
-          continue;
-        }
-
-        lastcol = (int) (col * sampleCol);
-
-        if (seq.getLength() > lastcol)
-        {
-          color = sr.getResidueBoxColour(seq, lastcol).getRGB();
-
-          if (av.isShowSequenceFeatures())
-          {
-            color = fr.findFeatureColour(color, seq, lastcol);
-          }
-        }
-        else
-        {
-          color = -1; // White
-        }
+    // calculate sampleCol and sampleRow
+    // alignment width is max number of residues/bases
+    // alignment height is number of sequences
+    int alwidth = av.getAlignment().getWidth();
+    int alheight = av.getAlignment().getAbsoluteHeight();
 
-        if (hiddenRow
-                || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                        lastcol)))
-        {
-          color = new Color(color).darker().darker().getRGB();
-        }
+    // sampleCol or sampleRow is the width/height allocated to each residue
+    // in particular, sometimes we may need more than one row/col of the
+    // BufferedImage allocated
+    // sampleCol is how much of a residue to assign to each pixel
+    // sampleRow is how many sequences to assign to each pixel
+    float sampleCol = alwidth / (float) od.getWidth();
+    float sampleRow = alheight / (float) od.getSequencesHeight();
 
-        miniMe.setRGB(col, row, color);
+    buildImage(sampleRow, sampleCol);
 
-      }
-    }
-
-    if (av.getAlignmentConservationAnnotation() != null)
+    // check for conservation annotation to make sure overview works for DNA too
+    if (av.isShowAnnotation()
+            && (av.getAlignmentConservationAnnotation() != null))
     {
       renderer.updateFromAlignViewport(av);
-      for (col = 0; col < width; col++)
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
       {
-        if (resizeAgain)
-        {
-          break;
-        }
-        lastcol = (int) (col * sampleCol);
-        {
-          mg.translate(col, sequencesHeight);
-          renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
-                  av.getAlignmentConservationAnnotation().annotations,
-                  (int) (sampleCol) + 1, graphHeight,
-                  (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-          mg.translate(-col, -sequencesHeight);
-        }
+        mg.translate(col, od.getSequencesHeight());
+        renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
+                av.getAlignmentConservationAnnotation().annotations,
+                (int) (sampleCol) + 1, od.getGraphHeight(),
+                (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+        mg.translate(-col, -od.getSequencesHeight());
+
       }
     }
     System.gc();
@@ -427,66 +236,97 @@ public class OverviewPanel extends JPanel implements Runnable
     setBoxPosition();
   }
 
-  /**
-   * DOCUMENT ME!
+  /*
+   * Build the overview panel image
    */
-  public void setBoxPosition()
+  private void buildImage(float sampleRow, float sampleCol)
   {
-    int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-    int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-            .getHiddenSequences().getSize())
-            * av.getCharHeight();
+    int lastcol = -1;
+    int lastrow = -1;
+    int rgbColour = Color.white.getRGB();
 
-    int startRes = av.getStartRes();
-    int endRes = av.getEndRes();
+    SequenceI seq = null;
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
 
-    if (av.hasHiddenColumns())
+    final boolean hasHiddenCols = av.hasHiddenColumns();
+    boolean hiddenRow = false;
+    // get hidden row and hidden column map once at beginning.
+    // clone featureRenderer settings to avoid race conditions... if state is
+    // updated just need to refresh again
+    for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
     {
-      startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-      endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-    }
+      boolean doCopy = true;
+      int currentrow = (int) (row * sampleRow);
+      if (currentrow != lastrow)
+      {
+        doCopy = false;
 
-    int startSeq = av.startSeq;
-    int endSeq = av.endSeq;
+        lastrow = currentrow;
 
-    if (av.hasHiddenRows())
-    {
-      startSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(startSeq);
+        // get the sequence which would be at alignment index 'lastrow' if no
+        // rows were hidden, and determine whether it is hidden or not
+        hiddenRow = av.getAlignment().isHidden(lastrow);
+        seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
+      }
 
-      endSeq = av.getAlignment().getHiddenSequences()
-              .adjustForHiddenSeqs(endSeq);
+      for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
+      {
+        if (doCopy)
+        {
+          rgbColour = miniMe.getRGB(col, row - 1);
+        }
+        else if ((int) (col * sampleCol) != lastcol
+                || (int) (row * sampleRow) != lastrow)
+        {
+          lastcol = (int) (col * sampleCol);
+          rgbColour = getColumnColourFromSequence(seq, hiddenRow,
+                  hasHiddenCols, lastcol, finder);
+        }
+        // else we just use the color we already have , so don't need to set it
 
+        miniMe.setRGB(col, row, rgbColour);
+      }
     }
+  }
 
-    scalew = (float) width / (float) fullsizeWidth;
-    scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-
-    boxX = (int) (startRes * av.getCharWidth() * scalew);
-    boxY = (int) (startSeq * av.getCharHeight() * scaleh);
+  /*
+   * Find the colour of a sequence at a specified column position
+   */
+  private int getColumnColourFromSequence(
+          jalview.datamodel.SequenceI seq,
+          boolean hiddenRow, boolean hasHiddenCols, int lastcol,
+          FeatureColourFinder finder)
+  {
+    Color color = Color.white;
 
-    if (av.hasHiddenColumns())
+    if ((seq != null) && (seq.getLength() > lastcol))
     {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
+       color = sr.getResidueColour(seq, lastcol, finder);
     }
-    else
+
+    if (hiddenRow
+            || (hasHiddenCols && !av.getColumnSelection()
+                    .isVisible(lastcol)))
     {
-      boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
+      color = color.darker().darker();
     }
 
-    boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
-
-    repaint();
+    return color.getRGB();
   }
 
-  private BufferedImage lastMiniMe = null;
-
   /**
-   * DOCUMENT ME!
+   * Update the overview panel box when the associated alignment panel is
+   * changed
    * 
-   * @param g
-   *          DOCUMENT ME!
    */
+  public void setBoxPosition()
+  {
+    od.setBoxPosition(av.getAlignment()
+            .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+    repaint();
+  }
+
+
   @Override
   public void paintComponent(Graphics g)
   {
@@ -501,7 +341,7 @@ public class OverviewPanel extends JPanel implements Runnable
       {
         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
       }
-      g.setColor(new Color(100, 100, 100, 25));
+      g.setColor(TRANS_GREY);
       g.fillRect(0, 0, getWidth(), getHeight());
     }
     else if (lastMiniMe != null)
@@ -509,13 +349,12 @@ public class OverviewPanel extends JPanel implements Runnable
       g.drawImage(lastMiniMe, 0, 0, this);
       if (lastMiniMe != miniMe)
       {
-        g.setColor(new Color(100, 100, 100, 25));
+        g.setColor(TRANS_GREY);
         g.fillRect(0, 0, getWidth(), getHeight());
       }
     }
-    // TODO: render selected regions
+
     g.setColor(Color.red);
-    g.drawRect(boxX, boxY, boxWidth, boxHeight);
-    g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+    od.drawBox(g);
   }
 }
index 81c3d4f..660c651 100644 (file)
@@ -43,12 +43,10 @@ import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FormatAdapter;
 import jalview.io.SequenceAnnotationReport;
-import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.PIDColourScheme;
-import jalview.schemes.ResidueColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
@@ -1179,12 +1177,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         hideInsertions_actionPerformed(e);
       }
     });
-    /*
-     * annotationMenuItem.setText("By Annotation");
-     * annotationMenuItem.addActionListener(new ActionListener() { public void
-     * actionPerformed(ActionEvent actionEvent) {
-     * annotationMenuItem_actionPerformed(actionEvent); } });
-     */
+
     groupMenu.add(sequenceSelDetails);
     add(groupMenu);
     add(sequenceMenu);
@@ -1620,24 +1613,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     refresh();
   }
 
-  public void annotationMenuItem_actionPerformed(ActionEvent actionEvent)
-  {
-    SequenceGroup sg = getGroup();
-    if (sg == null)
-    {
-      return;
-    }
-
-    AnnotationColourGradient acg = new AnnotationColourGradient(
-            sequence.getAnnotation()[0], null,
-            AnnotationColourGradient.NO_THRESHOLD);
-
-    acg.setPredefinedColours(true);
-    sg.setColourScheme(acg);
-
-    refresh();
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -2008,28 +1983,19 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   public void changeColour_actionPerformed(String colourSchemeName)
   {
     SequenceGroup sg = getGroup();
-    if (ResidueColourScheme.USER_DEFINED.equals(colourSchemeName))
-    {
-      /*
-       * open a panel to load or configure a user-defined colour scheme
-       */
-      new UserDefinedColours(ap, sg);
-    }
-    else
+    /*
+     * switch to the chosen colour scheme (or null for None)
+     */
+    ColourSchemeI colourScheme = ColourSchemes.getInstance()
+            .getColourScheme(colourSchemeName, sg,
+                    ap.av.getHiddenRepSequences());
+    sg.setColourScheme(colourScheme);
+    if (colourScheme instanceof Blosum62ColourScheme
+            || colourScheme instanceof PIDColourScheme)
     {
-      /*
-       * switch to the chosen colour scheme (or null for None)
-       */
-      ColourSchemeI colourScheme = ColourSchemes.getInstance().getColourScheme(
-              colourSchemeName, sg, ap.av.getHiddenRepSequences());
-      sg.setColourScheme(colourScheme);
-      if (colourScheme instanceof Blosum62ColourScheme
-              || colourScheme instanceof PIDColourScheme)
-      {
-        sg.cs.setConsensus(AAFrequency.calculate(
-                sg.getSequences(ap.av.getHiddenRepSequences()),
-                sg.getStartRes(), sg.getEndRes() + 1));
-      }
+      sg.cs.setConsensus(AAFrequency.calculate(
+              sg.getSequences(ap.av.getHiddenRepSequences()),
+              sg.getStartRes(), sg.getEndRes() + 1));
     }
 
     refresh();
index 8961f21..de21be6 100755 (executable)
@@ -101,7 +101,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    int x = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
     final int res;
 
     if (av.hasHiddenColumns())
@@ -282,7 +282,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   {
     mouseDragging = false;
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     if (av.hasHiddenColumns())
     {
@@ -337,7 +338,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     mouseDragging = true;
     ColumnSelection cs = av.getColumnSelection();
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
     res = Math.max(0, res);
     res = cs.adjustForHiddenColumns(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
@@ -389,7 +391,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
       return;
     }
 
-    int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
+    int res = (evt.getX() / av.getCharWidth())
+            + av.getRanges().getStartRes();
 
     res = av.getColumnSelection().adjustForHiddenColumns(res);
 
@@ -419,7 +422,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   @Override
   public void paintComponent(Graphics g)
   {
-    drawScale(g, av.getStartRes(), av.getEndRes(), getWidth(), getHeight());
+    drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
+            getWidth(), getHeight());
   }
 
   // scalewidth will normally be screenwidth,
index d015292..64e5fdc 100755 (executable)
@@ -26,6 +26,7 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.BasicStroke;
 import java.awt.BorderLayout;
@@ -279,10 +280,11 @@ public class SeqCanvas extends JComponent
     gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
             imgHeight, -horizontal * charWidth, -vertical * charHeight);
 
-    int sr = av.startRes;
-    int er = av.endRes;
-    int ss = av.startSeq;
-    int es = av.endSeq;
+    ViewportRanges ranges = av.getRanges();
+    int sr = ranges.getStartRes();
+    int er = ranges.getEndRes();
+    int ss = ranges.getStartSeq();
+    int es = ranges.getEndSeq();
     int transX = 0;
     int transY = 0;
 
@@ -300,22 +302,22 @@ public class SeqCanvas extends JComponent
     {
       ss = es - vertical;
 
-      if (ss < av.startSeq)
+      if (ss < ranges.getStartSeq())
       { // ie scrolling too fast, more than a page at a time
-        ss = av.startSeq;
+        ss = ranges.getStartSeq();
       }
       else
       {
-        transY = imgHeight - (vertical * charHeight);
+        transY = imgHeight - ((vertical + 1) * charHeight);
       }
     }
     else if (vertical < 0)
     {
       es = ss - vertical;
 
-      if (es > av.endSeq)
+      if (es > ranges.getEndSeq())
       {
-        es = av.endSeq;
+        es = ranges.getEndSeq();
       }
     }
 
@@ -395,13 +397,15 @@ public class SeqCanvas extends JComponent
     gg.setColor(Color.white);
     gg.fillRect(0, 0, imgWidth, imgHeight);
 
+    ViewportRanges ranges = av.getRanges();
     if (av.getWrapAlignment())
     {
-      drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);
+      drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
     }
     else
     {
-      drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+              ranges.getStartSeq(), ranges.getEndSeq(), 0);
     }
 
     g.drawImage(lcimg, 0, 0, this);
@@ -503,7 +507,7 @@ public class SeqCanvas extends JComponent
 
     av.setWrappedWidth(cWidth);
 
-    av.endRes = av.startRes + cWidth;
+    av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth);
 
     int endx;
     int ypos = hgap;
@@ -584,7 +588,7 @@ public class SeqCanvas extends JComponent
                 (int) clip.getBounds().getHeight());
       }
 
-      drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
+      drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
 
       if (av.isShowAnnotation())
       {
@@ -679,7 +683,7 @@ public class SeqCanvas extends JComponent
 
           g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
                   0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
-                  (endSeq - startSeq) * charHeight + offset);
+                  (endSeq - startSeq + 1) * charHeight + offset);
         }
 
         g1.translate(-screenY * charWidth, 0);
@@ -718,7 +722,7 @@ public class SeqCanvas extends JComponent
 
     // / First draw the sequences
     // ///////////////////////////
-    for (int i = startSeq; i < endSeq; i++)
+    for (int i = startSeq; i <= endSeq; i++)
     {
       nextSeq = av.getAlignment().getSequenceAt(i);
       if (nextSeq == null)
@@ -733,7 +737,7 @@ public class SeqCanvas extends JComponent
       if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                + ((i - startSeq) * charHeight));
+                + ((i - startSeq) * charHeight), false);
       }
 
       // / Highlight search Results once all sequences have been drawn
@@ -802,7 +806,7 @@ public class SeqCanvas extends JComponent
         int top = -1;
         int bottom = -1;
 
-        for (i = startSeq; i < endSeq; i++)
+        for (i = startSeq; i <= endSeq; i++)
         {
           sx = (group.getStartRes() - startRes) * charWidth;
           sy = offset + ((i - startSeq) * charHeight);
index 37b4852..db7aa36 100644 (file)
@@ -209,7 +209,7 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       wrappedBlock = y / cHeight;
-      wrappedBlock += av.getStartRes() / cwidth;
+      wrappedBlock += av.getRanges().getStartRes() / cwidth;
 
       res = wrappedBlock * cwidth + x / av.getCharWidth();
 
@@ -222,11 +222,11 @@ public class SeqPanel extends JPanel implements MouseListener,
         // right-hand gutter
         x = seqCanvas.getX() + seqCanvas.getWidth();
       }
-      res = (x / av.getCharWidth()) + av.getStartRes();
-      if (res > av.getEndRes())
+      res = (x / av.getCharWidth()) + av.getRanges().getStartRes();
+      if (res > av.getRanges().getEndRes())
       {
         // moused off right
-        res = av.getEndRes();
+        res = av.getRanges().getEndRes();
       }
     }
 
@@ -262,7 +262,9 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     else
     {
-      seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
+      seq = Math.min((y / av.getCharHeight())
+              + av.getRanges().getStartSeq(),
+              av
               .getAlignment().getHeight() - 1);
     }
 
@@ -385,18 +387,18 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
     else
     {
-      while (seqCanvas.cursorY < av.startSeq)
+      while (seqCanvas.cursorY < av.getRanges().getStartSeq())
       {
         ap.scrollUp(true);
       }
-      while (seqCanvas.cursorY + 1 > av.endSeq)
+      while (seqCanvas.cursorY + 1 > av.getRanges().getEndSeq())
       {
         ap.scrollUp(false);
       }
       if (!av.getWrapAlignment())
       {
         while (seqCanvas.cursorX < av.getColumnSelection()
-                .adjustForHiddenColumns(av.startRes))
+                .adjustForHiddenColumns(av.getRanges().getStartRes()))
         {
           if (!ap.scrollRight(false))
           {
@@ -404,7 +406,7 @@ public class SeqPanel extends JPanel implements MouseListener,
           }
         }
         while (seqCanvas.cursorX > av.getColumnSelection()
-                .adjustForHiddenColumns(av.endRes))
+                .adjustForHiddenColumns(av.getRanges().getEndRes()))
         {
           if (!ap.scrollRight(true))
           {
@@ -1772,9 +1774,9 @@ public class SeqPanel extends JPanel implements MouseListener,
       changeStartRes = true;
     }
 
-    if (res < av.getStartRes())
+    if (res < av.getRanges().getStartRes())
     {
-      res = av.getStartRes();
+      res = av.getRanges().getStartRes();
     }
 
     if (changeEndRes)
@@ -1908,13 +1910,15 @@ public class SeqPanel extends JPanel implements MouseListener,
       {
         if (evt != null)
         {
-          if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
+          if (mouseDragging && (evt.getY() < 0)
+                  && (av.getRanges().getStartSeq() > 0))
           {
             running = ap.scrollUp(true);
           }
 
           if (mouseDragging && (evt.getY() >= getHeight())
-                  && (av.getAlignment().getHeight() > av.getEndSeq()))
+                  && (av.getAlignment().getHeight() > av.getRanges()
+                          .getEndSeq()))
           {
             running = ap.scrollUp(false);
           }
index 95c3261..1c0420d 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.api.FeatureRenderer;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.ResidueShaderI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.util.Comparison;
 
 import java.awt.Color;
@@ -53,14 +53,13 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
   boolean forOverview = false;
 
   /**
-   * Creates a new SequenceRenderer object.
+   * Creates a new SequenceRenderer object
    * 
-   * @param av
-   *          DOCUMENT ME!
+   * @param viewport
    */
-  public SequenceRenderer(AlignViewport av)
+  public SequenceRenderer(AlignViewport viewport)
   {
-    this.av = av;
+    this.av = viewport;
   }
 
   /**
@@ -83,8 +82,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     this.renderGaps = renderGaps;
   }
 
-  @Override
-  public Color getResidueBoxColour(SequenceI seq, int i)
+  protected Color getResidueBoxColour(SequenceI seq, int i)
   {
     // rate limiting step when rendering overview for lots of groups
     allGroups = av.getAlignment().findAllGroups(seq);
@@ -111,20 +109,18 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
    * 
    * @param seq
    * @param position
-   * @param fr
+   * @param finder
    * @return
    */
   @Override
   public Color getResidueColour(final SequenceI seq, int position,
-          FeatureRenderer fr)
+          FeatureColourFinder finder)
   {
-    // TODO replace 8 or so code duplications with calls to this method
-    // (refactored as needed)
     Color col = getResidueBoxColour(seq, position);
 
-    if (fr != null)
+    if (finder != null)
     {
-      col = fr.findFeatureColour(col, seq, position);
+      col = finder.findFeatureColour(col, seq, position);
     }
     return col;
   }
index 44a429c..d7f7c31 100644 (file)
@@ -747,9 +747,9 @@ public abstract class StructureViewerBase extends GStructureViewer
       public void itemStateChanged(ItemEvent e)
       {
         alignStructs.setEnabled(!_alignwith.isEmpty());
-        alignStructs
-                .setToolTipText(MessageManager
-                        .getString("label.align_structures_using_linked_alignment_views"));
+        alignStructs.setToolTipText(MessageManager.formatMessage(
+                "label.align_structures_using_linked_alignment_views",
+                _alignwith.size()));
       }
     };
     viewSelectionMenu = new ViewSelectionMenu(
@@ -783,16 +783,24 @@ public abstract class StructureViewerBase extends GStructureViewer
   public void setJalviewColourScheme(ColourSchemeI cs) {
     getBinding().setJalviewColourScheme(cs);
   }
+
+  /**
+   * Sends commands to the structure viewer to superimpose structures based on
+   * currently associated alignments. May optionally return an error message for
+   * the operation.
+   */
   @Override
-  protected void alignStructs_actionPerformed(ActionEvent actionEvent)
+  protected String alignStructs_actionPerformed(
+          ActionEvent actionEvent)
   {
-    alignStructs_withAllAlignPanels();
+    return alignStructs_withAllAlignPanels();
   }
-  protected void alignStructs_withAllAlignPanels()
+
+  protected String alignStructs_withAllAlignPanels()
   {
     if (getAlignmentPanel() == null)
     {
-      return;
+      return null;
     }
   
     if (_alignwith.size() == 0)
@@ -800,6 +808,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       _alignwith.add(getAlignmentPanel());
     }
   
+    String reply = null;
     try
     {
       AlignmentI[] als = new Alignment[_alignwith.size()];
@@ -813,7 +822,13 @@ public abstract class StructureViewerBase extends GStructureViewer
         alm[a] = -1;
         alc[a++] = ap.av.getColumnSelection();
       }
-      getBinding().superposeStructures(als, alm, alc);
+      reply = getBinding().superposeStructures(als, alm, alc);
+      if (reply != null)
+      {
+        String text = MessageManager.formatMessage(
+                "error.superposition_failed", reply);
+        statusBar.setText(text);
+      }
     } catch (Exception e)
     {
       StringBuffer sp = new StringBuffer();
@@ -824,7 +839,9 @@ public abstract class StructureViewerBase extends GStructureViewer
       Cache.log.info("Couldn't align structures with the " + sp.toString()
               + "associated alignment panels.", e);
     }
+    return reply;
   }
+
   @Override
   public void background_actionPerformed(ActionEvent actionEvent)
   {
@@ -954,6 +971,10 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   protected abstract String getViewerName();
+
+  /**
+   * Configures the title and menu items of the viewer panel.
+   */
   public void updateTitleAndMenus()
   {
     AAStructureBindingModel binding = getBinding();
@@ -965,10 +986,30 @@ public abstract class StructureViewerBase extends GStructureViewer
     setChainMenuItems(binding.getChainNames());
   
     this.setTitle(binding.getViewerTitle(getViewerName(), true));
-    if (binding.getPdbFile().length > 1 && binding.getSequence().length > 1)
+
+    /*
+     * enable 'Superpose with' if more than one mapped structure
+     */
+    viewSelectionMenu.setEnabled(false);
+    if (getBinding().getPdbFile().length > 1
+            && getBinding().getSequence().length > 1)
     {
-      viewerActionMenu.setVisible(true);
+      viewSelectionMenu.setEnabled(true);
     }
+
+    /*
+     * Show action menu if it has any enabled items
+     */
+    viewerActionMenu.setVisible(false);
+    for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
+    {
+      if (viewerActionMenu.getItem(i).isEnabled())
+      {
+        viewerActionMenu.setVisible(true);
+        break;
+      }
+    }
+
     if (!binding.isLoadingFromArchive())
     {
       seqColour_actionPerformed(null);
index 49fdaf7..91e05c6 100644 (file)
@@ -28,11 +28,12 @@ import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.swing.BorderFactory;
 import javax.swing.JColorChooser;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JSlider;
 import javax.swing.event.ChangeEvent;
@@ -44,36 +45,43 @@ public class TextColourChooser
 
   SequenceGroup sg;
 
-  public void chooseColour(AlignmentPanel ap, SequenceGroup sg)
+  Color original1, original2;
+
+  int originalThreshold;
+
+  Map<SequenceGroup, Color> groupColour1;
+
+  Map<SequenceGroup, Color> groupColour2;
+
+  Map<SequenceGroup, Integer> groupThreshold;
+
+  /**
+   * Show a dialogue which allows the user to select two text colours and adjust
+   * a slider for the cross-over point
+   * 
+   * @param alignPanel
+   *          the AlignmentPanel context
+   * @param sequenceGroup
+   *          the SequenceGroup context (only for group pop-menu option)
+   */
+  public void chooseColour(AlignmentPanel alignPanel, SequenceGroup sequenceGroup)
   {
-    this.ap = ap;
-    this.sg = sg;
+    this.ap = alignPanel;
+    this.sg = sequenceGroup;
 
-    int original1, original2, originalThreshold;
-    if (sg == null)
-    {
-      original1 = ap.av.getTextColour().getRGB();
-      original2 = ap.av.getTextColour2().getRGB();
-      originalThreshold = ap.av.getThresholdTextColour();
-    }
-    else
-    {
-      original1 = sg.textColour.getRGB();
-      original2 = sg.textColour2.getRGB();
-      originalThreshold = sg.thresholdTextColour;
-    }
+    saveInitialSettings();
 
     final JSlider slider = new JSlider(0, 750, originalThreshold);
     final JPanel col1 = new JPanel();
     col1.setPreferredSize(new Dimension(40, 20));
     col1.setBorder(BorderFactory.createEtchedBorder());
     col1.setToolTipText(MessageManager.getString("label.dark_colour"));
-    col1.setBackground(new Color(original1));
+    col1.setBackground(original1);
     final JPanel col2 = new JPanel();
     col2.setPreferredSize(new Dimension(40, 20));
     col2.setBorder(BorderFactory.createEtchedBorder());
     col2.setToolTipText(MessageManager.getString("label.light_colour"));
-    col2.setBackground(new Color(original2));
+    col2.setBackground(original2);
     final JPanel bigpanel = new JPanel(new BorderLayout());
     JPanel panel = new JPanel();
     bigpanel.add(panel, BorderLayout.CENTER);
@@ -130,7 +138,7 @@ public class TextColourChooser
 
     int reply = JvOptionPane
             .showInternalOptionDialog(
-                    ap,
+                    alignPanel,
                     bigpanel,
                     MessageManager
                             .getString("label.adjunst_foreground_text_colour_threshold"),
@@ -139,19 +147,81 @@ public class TextColourChooser
 
     if (reply == JvOptionPane.CANCEL_OPTION)
     {
-      if (sg == null)
-      {
-        ap.av.setTextColour(new Color(original1));
-        ap.av.setTextColour2(new Color(original2));
-        ap.av.setThresholdTextColour(originalThreshold);
-      }
-      else
+      restoreInitialSettings();
+    }
+  }
+
+  /**
+   * Restore initial settings on Cancel
+   */
+  protected void restoreInitialSettings()
+  {
+    if (sg == null)
+    {
+      ap.av.setTextColour(original1);
+      ap.av.setTextColour2(original2);
+      ap.av.setThresholdTextColour(originalThreshold);
+    }
+    else
+    {
+      sg.textColour = original1;
+      sg.textColour2 = original2;
+      sg.thresholdTextColour = originalThreshold;
+    }
+
+    /*
+     * if 'Apply To All Groups' was in force, there will be 
+     * group-specific settings to restore as well
+     */
+    for (SequenceGroup group : this.groupColour1.keySet())
+    {
+      group.textColour = groupColour1.get(group);
+      group.textColour2 = groupColour2.get(group);
+      group.thresholdTextColour = groupThreshold.get(group);
+    }
+  }
+
+  /**
+   * Save settings on entry, for restore on Cancel
+   */
+  protected void saveInitialSettings()
+  {
+    groupColour1 = new HashMap<SequenceGroup, Color>();
+    groupColour2 = new HashMap<SequenceGroup, Color>();
+    groupThreshold = new HashMap<SequenceGroup, Integer>();
+
+    if (sg == null)
+    {
+      /*
+       * alignment scope
+       */
+      original1 = ap.av.getTextColour();
+      original2 = ap.av.getTextColour2();
+      originalThreshold = ap.av.getThresholdTextColour();
+      if (ap.av.getColourAppliesToAllGroups()
+              && ap.av.getAlignment().getGroups() != null)
       {
-        sg.textColour = new Color(original1);
-        sg.textColour2 = new Color(original2);
-        sg.thresholdTextColour = originalThreshold;
+        /*
+         * if applying changes to all groups, need to be able to 
+         * restore group settings as well
+         */
+        for (SequenceGroup group : ap.av.getAlignment().getGroups())
+        {
+          groupColour1.put(group, group.textColour);
+          groupColour2.put(group, group.textColour2);
+          groupThreshold.put(group, group.thresholdTextColour);
+        }
       }
     }
+    else
+    {
+      /*
+       * Sequence group scope
+       */
+      original1 = sg.textColour;
+      original2 = sg.textColour2;
+      originalThreshold = sg.thresholdTextColour;
+    }
   }
 
   void colour1Changed(Color col)
@@ -215,11 +285,11 @@ public class TextColourChooser
       return;
     }
 
-    for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+    for (SequenceGroup group : ap.av.getAlignment().getGroups())
     {
-      sg.textColour = ap.av.getTextColour();
-      sg.textColour2 = ap.av.getTextColour2();
-      sg.thresholdTextColour = ap.av.getThresholdTextColour();
+      group.textColour = ap.av.getTextColour();
+      group.textColour2 = ap.av.getTextColour2();
+      group.thresholdTextColour = ap.av.getThresholdTextColour();
     }
   }
 
index 54eed1a..9a38d4c 100755 (executable)
@@ -549,7 +549,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
   public void run()
   {
     PrinterJob printJob = PrinterJob.getPrinterJob();
-    PageFormat pf = printJob.pageDialog(printJob.defaultPage());
+    PageFormat defaultPage = printJob.defaultPage();
+    PageFormat pf = printJob.pageDialog(defaultPage);
+
+    if (defaultPage == pf)
+    {
+      /*
+       * user cancelled
+       */
+      return;
+    }
 
     printJob.setPrintable(this, pf);
 
index 83a8d24..f75a0a3 100755 (executable)
  */
 package jalview.gui;
 
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
-import jalview.datamodel.SequenceGroup;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.jbgui.GUserDefinedColours;
 import jalview.schemabinding.version2.Colour;
 import jalview.schemabinding.version2.JalviewUserColours;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
@@ -39,7 +38,6 @@ import jalview.util.MessageManager;
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Insets;
-import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.io.File;
@@ -71,7 +69,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
   private static final String LAST_DIRECTORY = "LAST_DIRECTORY";
 
-  private static final int MY_FRAME_HEIGHT = 420;
+  private static final int MY_FRAME_HEIGHT = 440;
 
   private static final int MY_FRAME_WIDTH = 810;
 
@@ -79,43 +77,38 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
   AlignmentPanel ap;
 
-  SequenceGroup seqGroup;
-
-  List<JButton> selectedButtons;
-
+  /*
+   * the colour scheme when the dialog was opened, or
+   * the scheme last saved to file
+   */
   ColourSchemeI oldColourScheme;
 
-  JInternalFrame frame;
+  /*
+   * flag is true if the colour scheme has been changed since the
+   * dialog was opened, or the changes last saved to file
+   */
+  boolean changed;
 
-  JalviewStructureDisplayI structureViewer;
+  JInternalFrame frame;
 
   List<JButton> upperCaseButtons;
 
   List<JButton> lowerCaseButtons;
 
   /**
-   * Creates a new UserDefinedColours object.
+   * Creates and displays a new UserDefinedColours panel
    * 
-   * @param ap
-   * @param sg
+   * @param alignPanel
    */
-  public UserDefinedColours(AlignmentPanel ap, SequenceGroup sg)
+  public UserDefinedColours(AlignmentPanel alignPanel)
   {
     this();
 
     lcaseColour.setEnabled(false);
 
-    this.ap = ap;
-    seqGroup = sg;
+    this.ap = alignPanel;
 
-    if (seqGroup != null)
-    {
-      oldColourScheme = seqGroup.getColourScheme();
-    }
-    else
-    {
-      oldColourScheme = ap.av.getGlobalColourScheme();
-    }
+    oldColourScheme = alignPanel.av.getGlobalColourScheme();
 
     if (oldColourScheme instanceof UserColourScheme)
     {
@@ -139,29 +132,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
     showFrame();
   }
 
-  public UserDefinedColours(JalviewStructureDisplayI viewer,
-          ColourSchemeI oldcs)
-  {
-    this();
-    this.structureViewer = viewer;
-
-    colorChooser.getSelectionModel().addChangeListener(this);
-
-    oldColourScheme = oldcs;
-
-    if (oldColourScheme instanceof UserColourScheme)
-    {
-      schemeName.setText(((UserColourScheme) oldColourScheme)
-              .getSchemeName());
-    }
-
-    resetButtonPanel(false);
-
-    showFrame();
-
-  }
-
-  public UserDefinedColours()
+  UserDefinedColours()
   {
     super();
     selectedButtons = new ArrayList<JButton>();
@@ -175,11 +146,6 @@ public class UserDefinedColours extends GUserDefinedColours implements
     Desktop.addInternalFrame(frame,
             MessageManager.getString("label.user_defined_colours"),
             MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
-
-    if (seqGroup != null)
-    {
-      frame.setTitle(frame.getTitle() + " (" + seqGroup.getName() + ")");
-    }
   }
 
   /**
@@ -276,14 +242,9 @@ public class UserDefinedColours extends GUserDefinedColours implements
   {
     JButton button = null;
     final Color newColour = colorChooser.getColor();
-    for (int i = 0; i < selectedButtons.size(); i++)
-    {
-      button = selectedButtons.get(i);
-      button.setBackground(newColour);
-      button.setForeground(ColorUtils.brighterThan(newColour));
-    }
     if (lcaseColour.isSelected())
     {
+      selectedButtons.clear();
       for (int i = 0; i < lowerCaseButtons.size(); i++)
       {
         button = lowerCaseButtons.get(i);
@@ -291,6 +252,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
         button.setForeground(ColorUtils.brighterThan(button.getBackground()));
       }
     }
+    for (int i = 0; i < selectedButtons.size(); i++)
+    {
+      button = selectedButtons.get(i);
+      button.setBackground(newColour);
+      button.setForeground(ColorUtils.brighterThan(newColour));
+    }
+
+    changed = true;
   }
 
   /**
@@ -478,8 +447,17 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
     else
     {
+      /*
+       * OK is treated as 'apply colours and close'
+       */
       applyButton_actionPerformed();
 
+      /*
+       * If editing a named colour scheme, warn if changes
+       * have not been saved
+       */
+      warnIfUnsavedChanges();
+
       try
       {
         frame.setClosed(true);
@@ -490,6 +468,57 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
+   * If we have made changes to an existing user defined colour scheme but not
+   * saved them, show a dialog with the option to save. If the user chooses to
+   * save, do so, else clear the colour scheme name to indicate a new colour
+   * scheme.
+   */
+  protected void warnIfUnsavedChanges()
+  {
+    if (!changed)
+    {
+      return;
+    }
+
+    String name = schemeName.getText().trim();
+    if (oldColourScheme != null && !"".equals(name)
+            && name.equals(oldColourScheme.getSchemeName()))
+    {
+      String message = MessageManager.formatMessage("label.scheme_changed",
+              name);
+      String title = MessageManager.getString("label.save_changes");
+      String[] options = new String[] { title,
+          MessageManager.getString("label.dont_save_changes"), };
+      final String question = JvSwingUtils.wrapTooltip(true, message);
+      int response = JvOptionPane.showOptionDialog(Desktop.desktop,
+              question, title, JvOptionPane.DEFAULT_OPTION,
+              JvOptionPane.PLAIN_MESSAGE, null, options, options[0]);
+
+      boolean saved = false;
+      if (response == 0)
+      {
+        /*
+         * prompt to save changes to file
+         */
+        saved = savebutton_actionPerformed();
+      }
+
+      /*
+       * if user chooses not to save (either in this dialog or in the
+       * save as dialogs), treat this as a new user defined colour scheme
+       */
+      if (!saved)
+      {
+        /*
+         * clear scheme name and re-apply as an anonymous scheme
+         */
+        schemeName.setText("");
+        applyButton_actionPerformed();
+      }
+    }
+  }
+
+  /**
    * Returns true if the user has not made any colour selection (including if
    * 'case-sensitive' selected and no lower-case colour chosen).
    * 
@@ -507,8 +536,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * Applies the current colour scheme to the alignment, sequence group or
-   * structure view.
+   * Applies the current colour scheme to the alignment or sequence group
    */
   @Override
   protected void applyButton_actionPerformed()
@@ -523,21 +551,15 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
     UserColourScheme ucs = getSchemeFromButtons();
 
-    if (seqGroup != null)
-    {
-      seqGroup.setColourScheme(ucs);
-      ap.paintAlignment(true);
-    }
-    else if (ap != null)
-    {
-      ap.alignFrame.changeColour(ucs);
-    }
-    else if (structureViewer != null)
-    {
-      structureViewer.setJalviewColourScheme(ucs);
-    }
+    ap.alignFrame.changeColour(ucs);
   }
 
+  /**
+   * Constructs an instance of UserColourScheme with the residue colours
+   * currently set on the buttons on the panel
+   * 
+   * @return
+   */
   UserColourScheme getSchemeFromButtons()
   {
 
@@ -589,22 +611,19 @@ public class UserDefinedColours extends GUserDefinedColours implements
       ucs.setLowerCaseColours(newColours);
     }
 
-    // if (ap != null)
-    // {
-    // ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
-    // }
-
     return ucs;
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
+   * Action on clicking Load scheme button.
+   * <ul>
+   * <li>Open a file chooser to browse for files with extension .jc</li>
+   * <li>Load in the colour scheme and transfer it to this panel's buttons</li>
+   * <li>Register the loaded colour scheme</li>
+   * </ul>
    */
   @Override
-  protected void loadbutton_actionPerformed(ActionEvent e)
+  protected void loadbutton_actionPerformed()
   {
     upperCaseButtons = new ArrayList<JButton>();
     lowerCaseButtons = new ArrayList<JButton>();
@@ -625,7 +644,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
     File choice = chooser.getSelectedFile();
     Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 
-    UserColourScheme ucs = ColourSchemes.loadColourScheme(choice
+    UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(choice
             .getAbsolutePath());
     Color[] colors = ucs.getColours();
     schemeName.setText(ucs.getSchemeName());
@@ -674,7 +693,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
       {
         colours = colours.substring(0, colours.indexOf("|"));
       }
-      ret = ColourSchemes.loadColourScheme(colours);
+      ret = ColourSchemeLoader.loadColourScheme(colours);
     }
 
     if (ret == null)
@@ -686,13 +705,23 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * DOCUMENT ME!
+   * Action on pressing the Save button.
+   * <ul>
+   * <li>Check a name has been entered</li>
+   * <li>Warn if the name already exists, remove any existing scheme of the same
+   * name if overwriting</li>
+   * <li>Do the standard file chooser thing to write with extension .jc</li>
+   * <li>If saving changes (possibly not yet applied) to the currently selected
+   * colour scheme, then apply the changes, as it is too late to back out now</li>
+   * <li>Don't apply the changes if the currently selected scheme is different,
+   * to allow a new scheme to be configured and saved but not applied</li>
+   * </ul>
+   * Returns true if the scheme is saved to file, false if it is not
    * 
-   * @param e
-   *          DOCUMENT ME!
+   * @return
    */
   @Override
-  protected void savebutton_actionPerformed(ActionEvent e)
+  protected boolean savebutton_actionPerformed()
   {
     String name = schemeName.getText().trim();
     if (name.length() < 1)
@@ -701,7 +730,7 @@ public class UserDefinedColours extends GUserDefinedColours implements
               .getString("label.user_colour_scheme_must_have_name"),
               MessageManager.getString("label.no_name_colour_scheme"),
               JvOptionPane.WARNING_MESSAGE);
-      return;
+      return false;
     }
 
     if (ColourSchemes.getInstance().nameExists(name))
@@ -714,9 +743,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
               JvOptionPane.YES_NO_OPTION);
       if (reply != JvOptionPane.YES_OPTION)
       {
-        return;
+        return false;
       }
-      ColourSchemes.getInstance().removeColourScheme(name);
     }
     JalviewFileChooser chooser = new JalviewFileChooser("jc",
             "Jalview User Colours");
@@ -729,12 +757,28 @@ public class UserDefinedColours extends GUserDefinedColours implements
 
     int value = chooser.showSaveDialog(this);
 
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    if (value != JalviewFileChooser.APPROVE_OPTION)
+    {
+      return false;
+    }
+
+    File file = chooser.getSelectedFile();
+    UserColourScheme updatedScheme = addNewColourScheme(file.getPath());
+    saveToFile(file);
+    changed = false;
+
+    /*
+     * changes saved - apply to alignment if we are changing 
+     * the currently selected colour scheme; also make the updated
+     * colours the 'backout' scheme on Cancel
+     */
+    if (oldColourScheme != null
+            && name.equals(oldColourScheme.getSchemeName()))
     {
-      File file = chooser.getSelectedFile();
-      addNewColourScheme(file.getPath());
-      saveToFile(file);
+      oldColourScheme = updatedScheme;
+      applyButton_actionPerformed();
     }
+    return true;
   }
 
   /**
@@ -744,8 +788,9 @@ public class UserDefinedColours extends GUserDefinedColours implements
    * the colour scheme.
    * 
    * @param filePath
+   * @return
    */
-  protected void addNewColourScheme(String filePath)
+  protected UserColourScheme addNewColourScheme(String filePath)
   {
     /*
      * update the delimited list of user defined colour files in
@@ -776,6 +821,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
     {
       ap.alignFrame.buildColourMenu();
     }
+
+    return ucs;
   }
 
   /**
@@ -790,7 +837,8 @@ public class UserDefinedColours extends GUserDefinedColours implements
      * marshal to file
      */
     JalviewUserColours ucs = new JalviewUserColours();
-    ucs.setSchemeName(schemeName.getText());
+    String name = schemeName.getText();
+    ucs.setSchemeName(name);
     try
     {
       PrintWriter out = new PrintWriter(new OutputStreamWriter(
@@ -813,30 +861,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
   }
 
   /**
-   * On cancel, restores the colour scheme before the dialogue was opened
-   * 
-   * @param e
+   * On cancel, restores the colour scheme that was selected before the dialogue
+   * was opened
    */
   @Override
-  protected void cancelButton_actionPerformed(ActionEvent e)
+  protected void cancelButton_actionPerformed()
   {
-    if (ap != null)
-    {
-      if (seqGroup != null)
-      {
-        seqGroup.setColourScheme(oldColourScheme);
-      }
-      else
-      {
-        ap.alignFrame.changeColour(oldColourScheme);
-      }
-      ap.paintAlignment(true);
-    }
-
-    if (structureViewer != null)
-    {
-      structureViewer.setJalviewColourScheme(oldColourScheme);
-    }
+    ap.alignFrame.changeColour(oldColourScheme);
+    ap.paintAlignment(true);
 
     try
     {
@@ -846,8 +878,14 @@ public class UserDefinedColours extends GUserDefinedColours implements
     }
   }
 
+  /**
+   * Action on selecting or deselecting the Case Sensitive option. When
+   * selected, separate buttons are shown for lower case residues, and the panel
+   * is resized to accommodate them. Also, the checkbox for 'apply colour to all
+   * lower case' is enabled.
+   */
   @Override
-  public void caseSensitive_actionPerformed(ActionEvent e)
+  public void caseSensitive_actionPerformed()
   {
     boolean selected = caseSensitive.isSelected();
     resetButtonPanel(selected);
index 27ebe5a..583bbc0 100644 (file)
@@ -46,6 +46,7 @@ import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.json.binding.biojson.v1.SequenceFeaturesPojo;
 import jalview.json.binding.biojson.v1.SequenceGrpPojo;
 import jalview.json.binding.biojson.v1.SequencePojo;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.schemes.JalviewColourScheme;
 import jalview.schemes.ResidueColourScheme;
 import jalview.util.ColorUtils;
@@ -328,6 +329,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
       return sequenceFeaturesPojo;
     }
 
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+
     for (SequenceI seq : sqs)
     {
       SequenceI dataSetSequence = seq.getDatasetSequence();
@@ -350,7 +353,7 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
                   String.valueOf(seq.hashCode()));
 
           String featureColour = (fr == null) ? null : jalview.util.Format
-                  .getHexString(fr.findFeatureColour(Color.white, seq,
+                  .getHexString(finder.findFeatureColour(Color.white, seq,
                           seq.findIndex(sf.getBegin())));
           jsonFeature.setXstart(seq.findIndex(sf.getBegin()) - 1);
           jsonFeature.setXend(seq.findIndex(sf.getEnd()));
@@ -764,7 +767,8 @@ public class JSONFile extends AlignFile implements ComplexAlignFile
         }
       }
     }
-    globalColourScheme = viewport.getGlobalColourScheme().getSchemeName();
+    globalColourScheme = (viewport.getGlobalColourScheme() == null) ? ResidueColourScheme.NONE
+            : viewport.getGlobalColourScheme().getSchemeName();
     setDisplayedFeatures(viewport.getFeaturesDisplayed());
     showSeqFeatures = viewport.isShowSequenceFeatures();
 
index 4f833bc..7580222 100644 (file)
@@ -221,8 +221,8 @@ public class MouseOverStructureListener extends JSFunctionExec implements
       ArrayList<String[]> ccomands = new ArrayList<String[]>();
       ArrayList<String> pdbfn = new ArrayList<String>();
       StructureMappingcommandSet[] colcommands = JmolCommands
-              .getColourBySequenceCommand(ssm, modelSet, sequence, sr, fr,
-                      ((AlignmentViewPanel) source).getAlignment());
+              .getColourBySequenceCommand(ssm, modelSet, sequence, sr,
+                      (AlignmentViewPanel) source);
       if (colcommands == null)
       {
         return;
@@ -298,6 +298,7 @@ public class MouseOverStructureListener extends JSFunctionExec implements
     return _listenerfn;
   }
 
+  @Override
   public void finalize() throws Throwable
   {
     jvlite = null;
index b39f4a8..b759d64 100755 (executable)
@@ -113,7 +113,7 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenu colourMenu = new JMenu();
 
-  protected JRadioButtonMenuItem textColour;
+  protected JMenuItem textColour;
 
   protected JCheckBoxMenuItem conservationMenuItem;
 
@@ -975,7 +975,7 @@ public class GAlignFrame extends JInternalFrame
     });
 
     JMenuItem createGroup = new JMenuItem(
-            MessageManager.getString("action.create_groups"));
+            MessageManager.getString("action.create_group"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit
             .getDefaultToolkit().getMenuShortcutKeyMask(), false);
     al = new ActionListener()
@@ -1653,6 +1653,7 @@ public class GAlignFrame extends JInternalFrame
 
     formatMenu.setText(MessageManager.getString("action.format"));
     JMenu selectMenu = new JMenu(MessageManager.getString("action.select"));
+
     idRightAlign.setText(MessageManager
             .getString("label.right_align_sequence_id"));
     idRightAlign.addActionListener(new ActionListener()
@@ -1904,6 +1905,12 @@ public class GAlignFrame extends JInternalFrame
     // selectMenu.add(listenToViewSelections);
   }
 
+  protected void configureSelectMenu()
+  {
+    // TODO Auto-generated method stub
+
+  }
+
   /**
    * Constructs the entries on the Colour menu (but does not add them to the
    * menu).
@@ -1921,8 +1928,8 @@ public class GAlignFrame extends JInternalFrame
       }
     });
 
-    textColour = new JRadioButtonMenuItem(
-            MessageManager.getString("action.set_text_colour"));
+    textColour = new JMenuItem(
+            MessageManager.getString("label.text_colour"));
     textColour.addActionListener(new ActionListener()
     {
       @Override
index 5c7a31a..d8f3f61 100644 (file)
@@ -219,9 +219,8 @@ public abstract class GStructureViewer extends JInternalFrame implements
   {
   }
 
-  protected void alignStructs_actionPerformed(ActionEvent actionEvent)
-  {
-  }
+  protected abstract String alignStructs_actionPerformed(
+          ActionEvent actionEvent);
 
   public void pdbFile_actionPerformed(ActionEvent actionEvent)
   {
index aa5319c..5384cc0 100755 (executable)
@@ -137,7 +137,7 @@ public class GUserDefinedColours extends JPanel
     gridLayout.setRows(5);
     okButton.setFont(new java.awt.Font("Verdana", 0, 11));
     okButton.setText(MessageManager.getString("action.ok"));
-    okButton.addActionListener(new java.awt.event.ActionListener()
+    okButton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -157,32 +157,32 @@ public class GUserDefinedColours extends JPanel
     });
     loadbutton.setFont(new java.awt.Font("Verdana", 0, 11));
     loadbutton.setText(MessageManager.getString("action.load_scheme"));
-    loadbutton.addActionListener(new java.awt.event.ActionListener()
+    loadbutton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        loadbutton_actionPerformed(e);
+        loadbutton_actionPerformed();
       }
     });
     savebutton.setFont(new java.awt.Font("Verdana", 0, 11));
     savebutton.setText(MessageManager.getString("action.save_scheme"));
-    savebutton.addActionListener(new java.awt.event.ActionListener()
+    savebutton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        savebutton_actionPerformed(e);
+        savebutton_actionPerformed();
       }
     });
     cancelButton.setFont(JvSwingUtils.getLabelFont());
     cancelButton.setText(MessageManager.getString("action.cancel"));
-    cancelButton.addActionListener(new java.awt.event.ActionListener()
+    cancelButton.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        cancelButton_actionPerformed(e);
+        cancelButton_actionPerformed();
       }
     });
     this.setBackground(new Color(212, 208, 223));
@@ -218,7 +218,7 @@ public class GUserDefinedColours extends JPanel
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        caseSensitive_actionPerformed(e);
+        caseSensitive_actionPerformed();
       }
     });
     lcaseColour
@@ -282,18 +282,13 @@ public class GUserDefinedColours extends JPanel
    * @param e
    *          DOCUMENT ME!
    */
-  protected void loadbutton_actionPerformed(ActionEvent e)
+  protected void loadbutton_actionPerformed()
   {
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param e
-   *          DOCUMENT ME!
-   */
-  protected void savebutton_actionPerformed(ActionEvent e)
+  protected boolean savebutton_actionPerformed()
   {
+    return false;
   }
 
   /**
@@ -302,16 +297,16 @@ public class GUserDefinedColours extends JPanel
    * @param e
    *          DOCUMENT ME!
    */
-  protected void cancelButton_actionPerformed(ActionEvent e)
+  protected void cancelButton_actionPerformed()
   {
   }
 
-  public void caseSensitive_actionPerformed(ActionEvent e)
+  public void caseSensitive_actionPerformed()
   {
 
   }
 
-  public void lcaseColour_actionPerformed(ActionEvent e)
+  public void lcaseColour_actionPerformed()
   {
 
   }
index 6f84a2e..3a27c7d 100644 (file)
@@ -307,7 +307,7 @@ public class AnnotationRenderer
   public void updateFromAlignViewport(AlignViewportI av)
   {
     charWidth = av.getCharWidth();
-    endRes = av.getEndRes();
+    endRes = av.getRanges().getEndRes();
     charHeight = av.getCharHeight();
     hasHiddenColumns = av.hasHiddenColumns();
     validCharWidth = av.isValidCharWidth();
diff --git a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java
new file mode 100644 (file)
index 0000000..1db2004
--- /dev/null
@@ -0,0 +1,124 @@
+package jalview.renderer.seqfeatures;
+
+import jalview.api.FeatureRenderer;
+import jalview.api.FeaturesDisplayedI;
+import jalview.datamodel.SequenceI;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+/**
+ * A helper class to find feature colour using an associated FeatureRenderer
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class FeatureColourFinder
+{
+  /*
+   * the class we delegate feature finding to
+   */
+  private FeatureRenderer featureRenderer;
+
+  /*
+   * a 1-pixel image on which features can be drawn, for the case where
+   * transparency allows 'see-through' of multiple feature colours
+   */
+  private BufferedImage offscreenImage;
+
+  /**
+   * Constructor
+   * 
+   * @param fr
+   */
+  public FeatureColourFinder(FeatureRenderer fr)
+  {
+    featureRenderer = fr;
+    offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+  }
+
+  /**
+   * Answers the feature colour to show for the given sequence and column
+   * position. This delegates to the FeatureRenderer to find the colour, which
+   * will depend on feature location, visibility, ordering, colour scheme, and
+   * whether or not transparency is applied. For feature rendering with
+   * transparency, this class provides a dummy 'offscreen' graphics context
+   * where multiple feature colours can be overlaid and the combined colour read
+   * back.
+   * <p>
+   * This method is not thread-safe when transparency is applied, since a shared
+   * BufferedImage would be used by all threads to hold the composite colour at
+   * a position. Each thread should use a separate instance of this class.
+   * 
+   * @param defaultColour
+   * @param seq
+   * @param column
+   *          alignment column position (base zero)
+   * @return
+   */
+  public Color findFeatureColour(Color defaultColour, SequenceI seq,
+          int column)
+  {
+    if (noFeaturesDisplayed())
+    {
+      return defaultColour;
+    }
+
+    Graphics g = null;
+
+    /*
+     * if transparency applies, provide a notional 1x1 graphics context 
+     * that has been primed with the default colour
+     */
+    if (featureRenderer.getTransparency() != 1f)
+    {
+      g = offscreenImage.getGraphics();
+      if (defaultColour != null)
+      {
+        offscreenImage.setRGB(0, 0, defaultColour.getRGB());
+      }
+    }
+
+    Color c = featureRenderer.findFeatureColour(seq, column, g);
+    if (c == null)
+    {
+      return defaultColour;
+    }
+
+    if (g != null)
+    {
+      c = new Color(offscreenImage.getRGB(0, 0));
+    }
+    return c;
+  }
+
+  /**
+   * Answers true if feature display is turned off, or there are no features
+   * configured to be visible
+   * 
+   * @return
+   */
+  boolean noFeaturesDisplayed()
+  {
+    if (featureRenderer == null
+            || !featureRenderer.getViewport().isShowSequenceFeatures())
+    {
+      return true;
+    }
+
+    if (!((FeatureRendererModel) featureRenderer).hasRenderOrder())
+    {
+      return true;
+    }
+
+    FeaturesDisplayedI displayed = featureRenderer.getFeaturesDisplayed();
+    if (displayed == null || displayed.getVisibleFeatureCount() == 0)
+    {
+      return true;
+    }
+
+    return false;
+  }
+}
index 9e0089f..72ac2c8 100644 (file)
@@ -23,6 +23,7 @@ package jalview.renderer.seqfeatures;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
 import java.awt.AlphaComposite;
@@ -30,28 +31,11 @@ import java.awt.Color;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
 
 public class FeatureRenderer extends FeatureRendererModel
 {
-
-  FontMetrics fm;
-
-  int charOffset;
-
-  boolean offscreenRender = false;
-
-  protected SequenceI lastSeq;
-
-  char s;
-
-  int i;
-
-  int av_charHeight, av_charWidth;
-
-  boolean av_validCharWidth, av_isShowSeqFeatureHeight;
-
-  private Integer currentColour;
+  private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
+          .getInstance(AlphaComposite.SRC_OVER, 1.0f);
 
   /**
    * Constructor given a viewport
@@ -63,273 +47,252 @@ public class FeatureRenderer extends FeatureRendererModel
     this.av = viewport;
   }
 
-  protected void updateAvConfig()
+  /**
+   * Renders the sequence using the given feature colour between the given start
+   * and end columns. Returns true if at least one column is drawn, else false
+   * (the feature range does not overlap the start and end positions).
+   * 
+   * @param g
+   * @param seq
+   * @param featureStart
+   * @param featureEnd
+   * @param featureColour
+   * @param start
+   * @param end
+   * @param y1
+   * @param colourOnly
+   * @return
+   */
+  boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
+          int featureEnd, Color featureColour, int start, int end, int y1,
+          boolean colourOnly)
   {
-    av_charHeight = av.getCharHeight();
-    av_charWidth = av.getCharWidth();
-    av_validCharWidth = av.isValidCharWidth();
-    av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
-  }
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    boolean validCharWidth = av.isValidCharWidth();
 
-  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    if (featureStart > end || featureEnd < start)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-
-        g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
-                av_charHeight);
-
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.white);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      return false;
+    }
 
-      }
+    if (featureStart < start)
+    {
+      featureStart = start;
     }
-  }
+    if (featureEnd >= end)
+    {
+      featureEnd = end;
+    }
+    int pady = (y1 + charHeight) - charHeight / 5;
 
-  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1, byte[] bs)
-  {
-    updateAvConfig();
-    if (((fstart <= end) && (fend >= start)))
+    FontMetrics fm = g.getFontMetrics();
+    for (int i = featureStart; i <= featureEnd; i++)
     {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
+      char s = seq.getCharAt(i);
 
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av_charHeight) - av_charHeight / 5;
-      int ystrt = 0, yend = av_charHeight;
-      if (bs[0] != 0)
-      {
-        // signed - zero is always middle of residue line.
-        if (bs[1] < 128)
-        {
-          yend = av_charHeight * (128 - bs[1]) / 512;
-          ystrt = av_charHeight - yend / 2;
-        }
-        else
-        {
-          ystrt = av_charHeight / 2;
-          yend = av_charHeight * (bs[1] - 128) / 512;
-        }
-      }
-      else
+      if (Comparison.isGap(s))
       {
-        yend = av_charHeight * bs[1] / 255;
-        ystrt = av_charHeight - yend;
-
+        continue;
       }
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
 
-        g.setColor(featureColour);
-        int x = (i - start) * av_charWidth;
-        g.drawRect(x, y1, av_charWidth, av_charHeight);
-        g.fillRect(x, y1 + ystrt, av_charWidth, yend);
+      g.setColor(featureColour);
 
-        if (offscreenRender || !av_validCharWidth)
-        {
-          continue;
-        }
+      g.fillRect((i - start) * charWidth, y1, charWidth,
+              charHeight);
 
-        g.setColor(Color.black);
-        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av_charWidth * (i - start)), pady);
+      if (colourOnly || !validCharWidth)
+      {
+        continue;
       }
-    }
-  }
-
-  BufferedImage offscreenImage;
 
-  @Override
-  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
-  {
-    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
+      g.setColor(Color.white);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s), charOffset
+              + (charWidth * (i - start)), pady);
+    }
+    return true;
   }
 
   /**
-   * This is used by Structure Viewers and the Overview Window to get the
-   * feature colour of the rendered sequence, returned as an RGB value
+   * Renders the sequence using the given SCORE feature colour between the given
+   * start and end columns. Returns true if at least one column is drawn, else
+   * false (the feature range does not overlap the start and end positions).
    * 
-   * @param defaultColour
+   * @param g
    * @param seq
-   * @param column
+   * @param fstart
+   * @param fend
+   * @param featureColour
+   * @param start
+   * @param end
+   * @param y1
+   * @param bs
+   * @param colourOnly
    * @return
    */
-  public synchronized int findFeatureColour(int defaultColour,
-          final SequenceI seq, int column)
+  boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
+          int fend, Color featureColour, int start, int end, int y1,
+          byte[] bs, boolean colourOnly)
   {
-    if (!av.isShowSequenceFeatures())
+    if (fstart > end || fend < start)
     {
-      return defaultColour;
+      return false;
     }
 
-    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
-    if (seq != lastSeq)
+    if (fstart < start)
+    { // fix for if the feature we have starts before the sequence start,
+      fstart = start; // but the feature end is still valid!!
+    }
+
+    if (fend >= end)
+    {
+      fend = end;
+    }
+    int charHeight = av.getCharHeight();
+    int pady = (y1 + charHeight) - charHeight / 5;
+    int ystrt = 0, yend = charHeight;
+    if (bs[0] != 0)
     {
-      lastSeq = seq;
-      lastSequenceFeatures = sequenceFeatures;
-      if (lastSequenceFeatures != null)
+      // signed - zero is always middle of residue line.
+      if (bs[1] < 128)
       {
-        sfSize = lastSequenceFeatures.length;
+        yend = charHeight * (128 - bs[1]) / 512;
+        ystrt = charHeight - yend / 2;
+      }
+      else
+      {
+        ystrt = charHeight / 2;
+        yend = charHeight * (bs[1] - 128) / 512;
       }
     }
     else
     {
-      if (lastSequenceFeatures != sequenceFeatures)
+      yend = charHeight * bs[1] / 255;
+      ystrt = charHeight - yend;
+
+    }
+
+    FontMetrics fm = g.getFontMetrics();
+    int charWidth = av.getCharWidth();
+
+    for (int i = fstart; i <= fend; i++)
+    {
+      char s = seq.getCharAt(i);
+
+      if (Comparison.isGap(s))
       {
-        lastSequenceFeatures = sequenceFeatures;
-        if (lastSequenceFeatures != null)
-        {
-          sfSize = lastSequenceFeatures.length;
-        }
+        continue;
       }
+
+      g.setColor(featureColour);
+      int x = (i - start) * charWidth;
+      g.drawRect(x, y1, charWidth, charHeight);
+      g.fillRect(x, y1 + ystrt, charWidth, yend);
+
+      if (colourOnly || !av.isValidCharWidth())
+      {
+        continue;
+      }
+
+      g.setColor(Color.black);
+      int charOffset = (charWidth - fm.charWidth(s)) / 2;
+      g.drawString(String.valueOf(s), charOffset
+              + (charWidth * (i - start)), pady);
     }
+    return true;
+  }
 
-    if (lastSequenceFeatures == null || sfSize == 0)
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Color findFeatureColour(SequenceI seq, int column, Graphics g)
+  {
+    if (!av.isShowSequenceFeatures())
     {
-      return defaultColour;
+      return null;
     }
 
-    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
+    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
+
+    if (sequenceFeatures == null || sequenceFeatures.length == 0)
     {
-      return Color.white.getRGB();
+      return null;
     }
 
-    // Only bother making an offscreen image if transparency is applied
-    if (transparency != 1.0f && offscreenImage == null)
+    if (Comparison.isGap(seq.getCharAt(column)))
     {
-      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+      return Color.white;
     }
 
-    currentColour = null;
-    // TODO: non-threadsafe - each rendering thread needs its own instance of
-    // the feature renderer - or this should be synchronized.
-    offscreenRender = true;
-
-    if (offscreenImage != null)
+    Color renderedColour = null;
+    if (transparency == 1.0f)
     {
-      offscreenImage.setRGB(0, 0, defaultColour);
-      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
-
-      return offscreenImage.getRGB(0, 0);
+      /*
+       * simple case - just find the topmost rendered visible feature colour
+       */
+      renderedColour = findFeatureColour(seq, seq.findPosition(column));
     }
     else
     {
-      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
-
-      if (currentColour == null)
-      {
-        return defaultColour;
-      }
-      else
-      {
-        return currentColour.intValue();
-      }
+      /*
+       * transparency case - draw all visible features in render order to
+       * build up a composite colour on the graphics context
+       */
+      renderedColour = drawSequence(g, seq, column, column, 0, true);
     }
-
+    return renderedColour;
   }
 
-  private volatile SequenceFeature[] lastSequenceFeatures;
-
-  int sfSize;
-
-  int sfindex;
-
-  int spos;
-
-  int epos;
-
   /**
-   * Draws the sequence on the graphics context, or just determines the colour
-   * that would be drawn (if flag offscreenrender is true).
+   * Draws the sequence features on the graphics context, or just determines the
+   * colour that would be drawn (if flag colourOnly is true). Returns the last
+   * colour drawn (which may not be the effective colour if transparency
+   * applies), or null if no feature is drawn in the range given.
    * 
    * @param g
+   *          the graphics context to draw on (may be null if colourOnly==true)
    * @param seq
    * @param start
-   *          start column (or sequence position in offscreenrender mode)
+   *          start column
    * @param end
-   *          end column (not used in offscreenrender mode)
+   *          end column
    * @param y1
    *          vertical offset at which to draw on the graphics
+   * @param colourOnly
+   *          if true, only do enough to determine the colour for the position,
+   *          do not draw the character
+   * @return
    */
-  public synchronized void drawSequence(Graphics g, final SequenceI seq,
-          int start, int end, int y1)
+  public synchronized Color drawSequence(final Graphics g,
+          final SequenceI seq, int start, int end, int y1,
+          boolean colourOnly)
   {
     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
     if (sequenceFeatures == null || sequenceFeatures.length == 0)
     {
-      return;
-    }
-
-    if (g != null)
-    {
-      fm = g.getFontMetrics();
+      return null;
     }
 
     updateFeatures();
 
-    if (lastSeq == null || seq != lastSeq
-            || sequenceFeatures != lastSequenceFeatures)
-    {
-      lastSeq = seq;
-      lastSequenceFeatures = sequenceFeatures;
-    }
-
-    if (transparency != 1 && g != null)
+    if (transparency != 1f && g != null)
     {
       Graphics2D g2 = (Graphics2D) g;
       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
               transparency));
     }
 
-    if (!offscreenRender)
-    {
-      spos = lastSeq.findPosition(start);
-      epos = lastSeq.findPosition(end);
-    }
+    int startPos = seq.findPosition(start);
+    int endPos = seq.findPosition(end);
+
+    int sfSize = sequenceFeatures.length;
+    Color drawnColour = null;
 
-    sfSize = lastSequenceFeatures.length;
+    /*
+     * iterate over features in ordering of their rendering (last is on top)
+     */
     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
     {
       String type = renderOrder[renderIndex];
@@ -340,27 +303,29 @@ public class FeatureRenderer extends FeatureRendererModel
 
       // loop through all features in sequence to find
       // current feature to render
-      for (sfindex = 0; sfindex < sfSize; sfindex++)
+      for (int sfindex = 0; sfindex < sfSize; sfindex++)
       {
-        final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
+        final SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
         if (!sequenceFeature.type.equals(type))
         {
           continue;
         }
 
+        /*
+         * a feature type may be flagged as shown but the group 
+         * an instance of it belongs to may be hidden
+         */
         if (featureGroupNotShown(sequenceFeature))
         {
           continue;
         }
 
         /*
-         * check feature overlaps the visible part of the alignment, 
-         * unless doing offscreenRender (to the Overview window or a 
-         * structure viewer) which is not limited 
+         * check feature overlaps the target range
+         * TODO: efficient retrieval of features overlapping a range
          */
-        if (!offscreenRender
-                && (sequenceFeature.getBegin() > epos || sequenceFeature
-                        .getEnd() < spos))
+        if (sequenceFeature.getBegin() > endPos
+                || sequenceFeature.getEnd() < startPos)
         {
           continue;
         }
@@ -368,58 +333,46 @@ public class FeatureRenderer extends FeatureRendererModel
         Color featureColour = getColour(sequenceFeature);
         boolean isContactFeature = sequenceFeature.isContactFeature();
 
-        if (offscreenRender && offscreenImage == null)
-        {
-          /*
-           * offscreen mode with no image (image is only needed if transparency 
-           * is applied to feature colours) - just check feature is rendered at 
-           * the requested position (start == sequence position in this mode)
-           */
-          boolean featureIsAtPosition = sequenceFeature.begin <= start
-                  && sequenceFeature.end >= start;
-          if (isContactFeature)
-          {
-            featureIsAtPosition = sequenceFeature.begin == start
-                    || sequenceFeature.end == start;
-          }
-          if (featureIsAtPosition)
-          {
-            // this is passed out to the overview and other sequence renderers
-            // (e.g. molecule viewer) to get displayed colour for rendered
-            // sequence
-            currentColour = new Integer(featureColour.getRGB());
-            // used to be retreived from av.featuresDisplayed
-            // currentColour = av.featuresDisplayed
-            // .get(sequenceFeatures[sfindex].type);
-
-          }
-        }
-        else if (isContactFeature)
+        if (isContactFeature)
         {
-          renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
+          boolean drawn = renderFeature(g, seq,
+                  seq.findIndex(sequenceFeature.begin) - 1,
                   seq.findIndex(sequenceFeature.begin) - 1, featureColour,
-                  start, end, y1);
-          renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
+                  start, end, y1, colourOnly);
+          drawn |= renderFeature(g, seq,
+                  seq.findIndex(sequenceFeature.end) - 1,
                   seq.findIndex(sequenceFeature.end) - 1, featureColour,
-                  start, end, y1);
-
+                  start, end, y1, colourOnly);
+          if (drawn)
+          {
+            drawnColour = featureColour;
+          }
         }
         else if (showFeature(sequenceFeature))
         {
-          if (av_isShowSeqFeatureHeight
+          if (av.isShowSequenceFeaturesHeight()
                   && !Float.isNaN(sequenceFeature.score))
           {
-            renderScoreFeature(g, seq,
+            boolean drawn = renderScoreFeature(g, seq,
                     seq.findIndex(sequenceFeature.begin) - 1,
-                    seq.findIndex(sequenceFeature.end) - 1,
-                    featureColour, start, end, y1,
-                    normaliseScore(sequenceFeature));
+                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
+                    start, end, y1, normaliseScore(sequenceFeature),
+                    colourOnly);
+            if (drawn)
+            {
+              drawnColour = featureColour;
+            }
           }
           else
           {
-            renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
-                    seq.findIndex(sequenceFeature.end) - 1,
-                    featureColour, start, end, y1);
+            boolean drawn = renderFeature(g, seq,
+                    seq.findIndex(sequenceFeature.begin) - 1,
+                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
+                    start, end, y1, colourOnly);
+            if (drawn)
+            {
+              drawnColour = featureColour;
+            }
           }
         }
       }
@@ -427,10 +380,14 @@ public class FeatureRenderer extends FeatureRendererModel
 
     if (transparency != 1.0f && g != null)
     {
+      /*
+       * reset transparency
+       */
       Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              1.0f));
+      g2.setComposite(NO_TRANSPARENCY);
     }
+
+    return drawnColour;
   }
 
   /**
@@ -459,7 +416,78 @@ public class FeatureRenderer extends FeatureRendererModel
   @Override
   public void featuresAdded()
   {
-    lastSeq = null;
     findAllFeatures();
   }
+
+  /**
+   * Returns the sequence feature colour rendered at the given sequence
+   * position, or null if none found. The feature of highest render order (i.e.
+   * on top) is found, subject to both feature type and feature group being
+   * visible, and its colour returned.
+   * 
+   * @param seq
+   * @param pos
+   * @return
+   */
+  Color findFeatureColour(SequenceI seq, int pos)
+  {
+    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
+    if (sequenceFeatures == null || sequenceFeatures.length == 0)
+    {
+      return null;
+    }
+  
+    /*
+     * check for new feature added while processing
+     */
+    updateFeatures();
+
+    /*
+     * inspect features in reverse renderOrder (the last in the array is 
+     * displayed on top) until we find one that is rendered at the position
+     */
+    for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
+    {
+      String type = renderOrder[renderIndex];
+      if (!showFeatureOfType(type))
+      {
+        continue;
+      }
+
+      for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
+      {
+        SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
+        if (!sequenceFeature.type.equals(type))
+        {
+          continue;
+        }
+
+        if (featureGroupNotShown(sequenceFeature))
+        {
+          continue;
+        }
+
+        /*
+         * check the column position is within the feature range
+         * (or is one of the two contact positions for a contact feature)
+         */
+        boolean featureIsAtPosition = sequenceFeature.begin <= pos
+                && sequenceFeature.end >= pos;
+        if (sequenceFeature.isContactFeature())
+        {
+          featureIsAtPosition = sequenceFeature.begin == pos
+                  || sequenceFeature.end == pos;
+        }
+        if (featureIsAtPosition)
+        {
+          return getColour(sequenceFeature);
+        }
+      }
+    }
+  
+    /*
+     * no displayed feature found at position
+     */
+    return null;
+  }
 }
index 1a3e9ef..220d3ab 100755 (executable)
@@ -27,6 +27,8 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
+import jalview.renderer.AnnotationRenderer;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.IdentityHashMap;
@@ -40,15 +42,25 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   public static final int ABOVE_THRESHOLD = 1;
 
-  public AlignmentAnnotation annotation;
+  private final AlignmentAnnotation annotation;
 
-  int aboveAnnotationThreshold = -1;
+  private final int aboveAnnotationThreshold;
 
   public boolean thresholdIsMinMax = false;
 
-  GraphLine annotationThreshold;
+  private GraphLine annotationThreshold;
 
-  float r1, g1, b1, rr, gg, bb;
+  private int redMin;
+
+  private int greenMin;
+
+  private int blueMin;
+
+  private int redRange;
+
+  private int greenRange;
+
+  private int blueRange;
 
   private boolean predefinedColours = false;
 
@@ -61,7 +73,7 @@ public class AnnotationColourGradient extends FollowerColourScheme
    */
   private boolean noGradient = false;
 
-  IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
+  private IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
 
   @Override
   public ColourSchemeI getInstance(AnnotatedCollectionI sg,
@@ -72,12 +84,12 @@ public class AnnotationColourGradient extends FollowerColourScheme
     acg.thresholdIsMinMax = thresholdIsMinMax;
     acg.annotationThreshold = (annotationThreshold == null) ? null
             : new GraphLine(annotationThreshold);
-    acg.r1 = r1;
-    acg.g1 = g1;
-    acg.b1 = b1;
-    acg.rr = rr;
-    acg.gg = gg;
-    acg.bb = bb;
+    acg.redMin = redMin;
+    acg.greenMin = greenMin;
+    acg.blueMin = blueMin;
+    acg.redRange = redRange;
+    acg.greenRange = greenRange;
+    acg.blueRange = blueRange;
     acg.predefinedColours = predefinedColours;
     acg.seqAssociated = seqAssociated;
     acg.noGradient = noGradient;
@@ -109,12 +121,12 @@ public class AnnotationColourGradient extends FollowerColourScheme
       annotationThreshold = annotation.threshold;
     }
     // clear values so we don't get weird black bands...
-    r1 = 254;
-    g1 = 254;
-    b1 = 254;
-    rr = 0;
-    gg = 0;
-    bb = 0;
+    redMin = 254;
+    greenMin = 254;
+    blueMin = 254;
+    redRange = 0;
+    greenRange = 0;
+    blueRange = 0;
 
     noGradient = true;
     checkLimits();
@@ -135,13 +147,13 @@ public class AnnotationColourGradient extends FollowerColourScheme
       annotationThreshold = annotation.threshold;
     }
 
-    r1 = minColour.getRed();
-    g1 = minColour.getGreen();
-    b1 = minColour.getBlue();
+    redMin = minColour.getRed();
+    greenMin = minColour.getGreen();
+    blueMin = minColour.getBlue();
 
-    rr = maxColour.getRed() - r1;
-    gg = maxColour.getGreen() - g1;
-    bb = maxColour.getBlue() - b1;
+    redRange = maxColour.getRed() - redMin;
+    greenRange = maxColour.getGreen() - greenMin;
+    blueRange = maxColour.getBlue() - blueMin;
 
     noGradient = false;
     checkLimits();
@@ -211,9 +223,9 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   float aamin = 0f, aamax = 0f;
 
-  public String getAnnotation()
+  public AlignmentAnnotation getAnnotation()
   {
-    return annotation.label;
+    return annotation;
   }
 
   public int getAboveThreshold()
@@ -235,12 +247,13 @@ public class AnnotationColourGradient extends FollowerColourScheme
 
   public Color getMinColour()
   {
-    return new Color((int) r1, (int) g1, (int) b1);
+    return new Color(redMin, greenMin, blueMin);
   }
 
   public Color getMaxColour()
   {
-    return new Color((int) (r1 + rr), (int) (g1 + gg), (int) (b1 + bb));
+    return new Color(redMin + redRange, greenMin + greenRange, blueMin
+            + blueRange);
   }
 
   /**
@@ -258,137 +271,157 @@ public class AnnotationColourGradient extends FollowerColourScheme
   }
 
   /**
-   * DOCUMENT ME!
+   * Returns the colour for a given character and position in a sequence
    * 
-   * @param n
-   *          DOCUMENT ME!
+   * @param c
+   *          the residue character
    * @param j
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
+   *          the aligned position
+   * @param seq
+   *          the sequence
+   * @return
    */
   @Override
   public Color findColour(char c, int j, SequenceI seq)
   {
-    Color currentColour = Color.white;
-    AlignmentAnnotation annotation = (seqAssociated && seqannot != null ? seqannot
+    /*
+     * locate the annotation we are configured to colour by
+     */
+    AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot
             .get(seq) : this.annotation);
-    if (annotation == null)
+
+    /*
+     * if gap or no annotation at position, no colour (White)
+     */
+    if (ann == null || ann.annotations == null
+            || j >= ann.annotations.length || ann.annotations[j] == null
+            || Comparison.isGap(c))
     {
-      return currentColour;
+      return Color.white;
     }
-    // if ((threshold == 0) || aboveThreshold(c, j))
-    // {
-    if (annotation.annotations != null && j < annotation.annotations.length
-            && annotation.annotations[j] != null
-            && !jalview.util.Comparison.isGap(c))
+
+    Annotation aj = ann.annotations[j];
+    // 'use original colours' => colourScheme != null
+    // -> look up colour to be used
+    // predefined colours => preconfigured shading
+    // -> only use original colours reference if thresholding enabled &
+    // minmax exists
+    // annotation.hasIcons => null or black colours replaced with glyph
+    // colours
+    // -> reuse original colours if present
+    // -> if thresholding enabled then return colour on non-whitespace glyph
+
+    /*
+     * if threshold applies, and annotation fails the test - no colour (white)
+     */
+    if (annotationThreshold != null)
     {
-      Annotation aj = annotation.annotations[j];
-      // 'use original colours' => colourScheme != null
-      // -> look up colour to be used
-      // predefined colours => preconfigured shading
-      // -> only use original colours reference if thresholding enabled &
-      // minmax exists
-      // annotation.hasIcons => null or black colours replaced with glyph
-      // colours
-      // -> reuse original colours if present
-      // -> if thresholding enabled then return colour on non-whitespace glyph
-
-      if (aboveAnnotationThreshold == NO_THRESHOLD
-              || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value
-                      : aj.value <= annotationThreshold.value)))
+      if ((aboveAnnotationThreshold == ABOVE_THRESHOLD && aj.value < annotationThreshold.value)
+              || (aboveAnnotationThreshold == BELOW_THRESHOLD && aj.value > annotationThreshold.value))
       {
-        if (predefinedColours && aj.colour != null
-                && !aj.colour.equals(Color.black))
-        {
-          currentColour = aj.colour;
-        }
-        else if (annotation.hasIcons
-                && annotation.graph == AlignmentAnnotation.NO_GRAPH)
+        return Color.white;
+      }
+    }
+
+    /*
+     * If 'use original colours' then return the colour of the annotation
+     * at the aligned position - computed using the background colour scheme
+     */
+    if (predefinedColours && aj.colour != null
+            && !aj.colour.equals(Color.black))
+    {
+      return aj.colour;
+    }
+
+    Color result = Color.white;
+    if (ann.hasIcons && ann.graph == AlignmentAnnotation.NO_GRAPH)
+    {
+      /*
+       * secondary structure symbol colouring
+       */
+      if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
+              && aj.secondaryStructure != '-')
+      {
+        if (getColourScheme() != null)
         {
-          if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
-                  && aj.secondaryStructure != '-')
-          {
-            if (getColourScheme() != null)
-            {
-              currentColour = getColourScheme().findColour(c, j, seq, null,
-                      0f);
-            }
-            else
-            {
-              if (annotation.isRNA())
-              {
-                currentColour = ColourSchemeProperty.rnaHelices[(int) aj.value];
-              }
-              else
-              {
-                currentColour = annotation.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR
-                        : annotation.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR
-                                : jalview.renderer.AnnotationRenderer.STEM_COLOUR;
-              }
-            }
-          }
-          else
-          {
-            //
-            return Color.white;
-          }
+          result = getColourScheme().findColour(c, j, seq, null, 0f);
         }
-        else if (noGradient)
+        else
         {
-          if (getColourScheme() != null)
+          if (ann.isRNA())
           {
-            currentColour = getColourScheme().findColour(c, j, seq, null,
-                    0f);
+            result = ColourSchemeProperty.rnaHelices[(int) aj.value];
           }
           else
           {
-            if (aj.colour != null)
-            {
-              currentColour = aj.colour;
-            }
+            result = ann.annotations[j].secondaryStructure == 'H' ? AnnotationRenderer.HELIX_COLOUR
+                    : ann.annotations[j].secondaryStructure == 'E' ? AnnotationRenderer.SHEET_COLOUR
+                            : AnnotationRenderer.STEM_COLOUR;
           }
         }
-        else
+      }
+      else
+      {
+        return Color.white;
+      }
+    }
+    else if (noGradient)
+    {
+      if (getColourScheme() != null)
+      {
+        result = getColourScheme().findColour(c, j, seq, null, 0f);
+      }
+      else
+      {
+        if (aj.colour != null)
         {
-          currentColour = shadeCalculation(annotation, j);
+          result = aj.colour;
         }
       }
-      // if (conservationColouring)
-      // {
-      // currentColour = applyConservation(currentColour, j);
-      // }
     }
-    // }
-    return currentColour;
+    else
+    {
+      result = shadeCalculation(ann, j);
+    }
+
+    return result;
   }
 
-  private Color shadeCalculation(AlignmentAnnotation annotation, int j)
+  /**
+   * Returns a graduated colour for the annotation at the given column. If there
+   * is a threshold value, and it is used as the top/bottom of the colour range,
+   * and the value satisfies the threshold condition, then a colour
+   * proportionate to the range from the threshold is calculated. For all other
+   * cases, a colour proportionate to the annotation's min-max range is
+   * calulated. Note that thresholding is _not_ done here (a colour is computed
+   * even if threshold is not passed).
+   * 
+   * @param ann
+   * @param col
+   * @return
+   */
+  Color shadeCalculation(AlignmentAnnotation ann, int col)
   {
-
-    // calculate a shade
     float range = 1f;
-    if (thresholdIsMinMax
-            && annotation.threshold != null
+    float value = ann.annotations[col].value;
+    if (thresholdIsMinMax && ann.threshold != null
             && aboveAnnotationThreshold == ABOVE_THRESHOLD
-            && annotation.annotations[j].value >= annotation.threshold.value)
+            && value >= ann.threshold.value)
     {
-      range = (annotation.annotations[j].value - annotation.threshold.value)
-              / (annotation.graphMax - annotation.threshold.value);
+      range = (value - ann.threshold.value)
+              / (ann.graphMax - ann.threshold.value);
     }
-    else if (thresholdIsMinMax && annotation.threshold != null
+    else if (thresholdIsMinMax && ann.threshold != null
             && aboveAnnotationThreshold == BELOW_THRESHOLD
-            && annotation.annotations[j].value >= annotation.graphMin)
+            && value <= ann.threshold.value)
     {
-      range = (annotation.annotations[j].value - annotation.graphMin)
-              / (annotation.threshold.value - annotation.graphMin);
+      range = (value - ann.graphMin) / (ann.threshold.value - ann.graphMin);
     }
     else
     {
-      if (annotation.graphMax != annotation.graphMin)
+      if (ann.graphMax != ann.graphMin)
       {
-        range = (annotation.annotations[j].value - annotation.graphMin)
-                / (annotation.graphMax - annotation.graphMin);
+        range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin);
       }
       else
       {
@@ -396,11 +429,11 @@ public class AnnotationColourGradient extends FollowerColourScheme
       }
     }
 
-    int dr = (int) (rr * range + r1), dg = (int) (gg * range + g1), db = (int) (bb
-            * range + b1);
+    int dr = (int) (redRange * range + redMin);
+    int dg = (int) (greenRange * range + greenMin);
+    int db = (int) (blueRange * range + blueMin);
 
     return new Color(dr, dg, db);
-
   }
 
   public boolean isPredefinedColours()
@@ -423,6 +456,16 @@ public class AnnotationColourGradient extends FollowerColourScheme
     seqAssociated = sassoc;
   }
 
+  public boolean isThresholdIsMinMax()
+  {
+    return thresholdIsMinMax;
+  }
+
+  public void setThresholdIsMinMax(boolean minMax)
+  {
+    this.thresholdIsMinMax = minMax;
+  }
+
   @Override
   public String getSchemeName()
   {
diff --git a/src/jalview/schemes/ColourSchemeLoader.java b/src/jalview/schemes/ColourSchemeLoader.java
new file mode 100644 (file)
index 0000000..8660f3e
--- /dev/null
@@ -0,0 +1,125 @@
+package jalview.schemes;
+
+import jalview.binding.JalviewUserColours;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import org.exolab.castor.xml.Unmarshaller;
+
+public class ColourSchemeLoader
+{
+
+  /**
+   * Loads a user defined colour scheme from file. The file should contain a
+   * definition of residue colours in XML format as defined in
+   * JalviewUserColours.xsd.
+   * 
+   * @param filePath
+   * 
+   * @return
+   */
+  public static UserColourScheme loadColourScheme(String filePath)
+  {
+    UserColourScheme ucs = null;
+    Color[] newColours = null;
+    File file = new File(filePath);
+    try
+    {
+      InputStreamReader in = new InputStreamReader(
+              new FileInputStream(file), "UTF-8");
+  
+      jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
+  
+      org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
+              jucs);
+      jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
+              .unmarshal(in);
+  
+      /*
+       * non-case-sensitive colours are for 20 amino acid codes,
+       * B, Z, X and Gap
+       * optionally, lower-case alternatives for all except Gap
+       */
+      newColours = new Color[24];
+      Color[] lowerCase = new Color[23];
+      boolean caseSensitive = false;
+  
+      String name;
+      int index;
+      for (int i = 0; i < jucs.getColourCount(); i++)
+      {
+        name = jucs.getColour(i).getName();
+        if (ResidueProperties.aa3Hash.containsKey(name))
+        {
+          index = ResidueProperties.aa3Hash.get(name).intValue();
+        }
+        else
+        {
+          index = ResidueProperties.aaIndex[name.charAt(0)];
+        }
+        if (index == -1)
+        {
+          continue;
+        }
+  
+        Color color = new Color(Integer.parseInt(jucs.getColour(i)
+                .getRGB(), 16));
+        if (name.toLowerCase().equals(name))
+        {
+          caseSensitive = true;
+          lowerCase[index] = color;
+        }
+        else
+        {
+          newColours[index] = color;
+        }
+      }
+  
+      /*
+       * instantiate the colour scheme
+       */
+      ucs = new UserColourScheme(newColours);
+      ucs.setName(jucs.getSchemeName());
+      if (caseSensitive)
+      {
+        ucs.setLowerCaseColours(lowerCase);
+      }
+    } catch (Exception ex)
+    {
+      // Could be old Jalview Archive format
+      try
+      {
+        InputStreamReader in = new InputStreamReader(new FileInputStream(
+                file), "UTF-8");
+  
+        jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
+  
+        jucs = JalviewUserColours.unmarshal(in);
+  
+        newColours = new Color[jucs.getColourCount()];
+  
+        for (int i = 0; i < 24; i++)
+        {
+          newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
+                  .getRGB(), 16));
+        }
+        ucs = new UserColourScheme(newColours);
+        ucs.setName(jucs.getSchemeName());
+      } catch (Exception ex2)
+      {
+        ex2.printStackTrace();
+      }
+  
+      if (newColours == null)
+      {
+        System.out.println("Error loading User ColourFile\n" + ex);
+      }
+    }
+  
+    return ucs;
+  }
+
+}
index 817fb01..269811b 100644 (file)
@@ -1,14 +1,9 @@
 package jalview.schemes;
 
-import jalview.binding.JalviewUserColours;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 
-import java.awt.Color;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -20,7 +15,7 @@ public class ColourSchemes
   private static ColourSchemes instance = new ColourSchemes();
 
   /*
-   * a map from scheme name to an instance of it
+   * a map from scheme name (lower-cased) to an instance of it
    */
   private Map<String, ColourSchemeI> schemes;
 
@@ -99,7 +94,10 @@ public class ColourSchemes
    */
   public void removeColourScheme(String name)
   {
-    schemes.remove(name);
+    if (name != null)
+    {
+      schemes.remove(name.toLowerCase());
+    }
   }
   
   /**
@@ -167,124 +165,6 @@ public class ColourSchemes
     {
       return false;
     }
-    name = name.toLowerCase();
-    for (ColourSchemeI scheme : getColourSchemes())
-    {
-      if (name.equals(scheme.getSchemeName().toLowerCase()))
-      {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Loads a user defined colour scheme from file. The file should contain a
-   * definition of residue colours in XML format as defined in
-   * JalviewUserColours.xsd.
-   * 
-   * @param filePath
-   * 
-   * @return
-   */
-  public static UserColourScheme loadColourScheme(String filePath)
-  {
-    UserColourScheme ucs = null;
-    Color[] newColours = null;
-    File file = new File(filePath);
-    try
-    {
-      InputStreamReader in = new InputStreamReader(
-              new FileInputStream(file), "UTF-8");
-  
-      jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
-  
-      org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
-              jucs);
-      jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
-              .unmarshal(in);
-  
-      /*
-       * non-case-sensitive colours are for 20 amino acid codes,
-       * B, Z, X and Gap
-       * optionally, lower-case alternatives for all except Gap
-       */
-      newColours = new Color[24];
-      Color[] lowerCase = new Color[23];
-      boolean caseSensitive = false;
-  
-      String name;
-      int index;
-      for (int i = 0; i < jucs.getColourCount(); i++)
-      {
-        name = jucs.getColour(i).getName();
-        if (ResidueProperties.aa3Hash.containsKey(name))
-        {
-          index = ResidueProperties.aa3Hash.get(name).intValue();
-        }
-        else
-        {
-          index = ResidueProperties.aaIndex[name.charAt(0)];
-        }
-        if (index == -1)
-        {
-          continue;
-        }
-  
-        Color color = new Color(Integer.parseInt(jucs.getColour(i)
-                .getRGB(), 16));
-        if (name.toLowerCase().equals(name))
-        {
-          caseSensitive = true;
-          lowerCase[index] = color;
-        }
-        else
-        {
-          newColours[index] = color;
-        }
-      }
-  
-      /*
-       * instantiate the colour scheme
-       */
-      ucs = new UserColourScheme(newColours);
-      ucs.setName(jucs.getSchemeName());
-      if (caseSensitive)
-      {
-        ucs.setLowerCaseColours(lowerCase);
-      }
-    } catch (Exception ex)
-    {
-      // Could be old Jalview Archive format
-      try
-      {
-        InputStreamReader in = new InputStreamReader(new FileInputStream(
-                file), "UTF-8");
-  
-        jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
-  
-        jucs = JalviewUserColours.unmarshal(in);
-  
-        newColours = new Color[jucs.getColourCount()];
-  
-        for (int i = 0; i < 24; i++)
-        {
-          newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
-                  .getRGB(), 16));
-        }
-        ucs = new UserColourScheme(newColours);
-        ucs.setName(jucs.getSchemeName());
-      } catch (Exception ex2)
-      {
-        ex2.printStackTrace();
-      }
-  
-      if (newColours == null)
-      {
-        System.out.println("Error loading User ColourFile\n" + ex);
-      }
-    }
-  
-    return ucs;
+    return schemes.containsKey(name.toLowerCase());
   }
 }
index 7519538..3ab642f 100644 (file)
@@ -589,11 +589,9 @@ public class StructureSelectionManager
     return pdb;
   }
 
-  private boolean isCIFFile(String filename)
+  public void addStructureMapping(StructureMapping sm)
   {
-    String fileExt = filename.substring(filename.lastIndexOf(".") + 1,
-            filename.length());
-    return "cif".equalsIgnoreCase(fileExt);
+    mappings.add(sm);
   }
 
   /**
@@ -824,10 +822,10 @@ public class StructureSelectionManager
    * @param atoms
    * @return
    */
-  public SearchResults findAlignmentPositionsForStructurePositions(
+  public SearchResultsI findAlignmentPositionsForStructurePositions(
           List<AtomSpec> atoms)
   {
-    SearchResults results = new SearchResults();
+    SearchResultsI results = new SearchResults();
     for (AtomSpec atom : atoms)
     {
       SequenceI lastseq = null;
index fda08fd..84475fe 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.structures.models;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
@@ -42,6 +41,7 @@ import jalview.util.MessageManager;
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 
 /**
@@ -521,15 +521,15 @@ public abstract class AAStructureBindingModel extends
    *          the sequence alignment which is the basis of structure
    *          superposition
    * @param matched
-   *          an array of booleans, indexed by alignment column, where true
-   *          indicates that every structure has a mapped residue present in the
-   *          column (so the column can participate in structure alignment)
+   *          a BitSet, where bit j is set to indicate that every structure has
+   *          a mapped residue present in column j (so the column can
+   *          participate in structure alignment)
    * @param structures
    *          an array of data beans corresponding to pdb file index
    * @return
    */
   protected int findSuperposableResidues(AlignmentI alignment,
-          boolean[] matched, SuperposeData[] structures)
+          BitSet matched, SuperposeData[] structures)
   {
     int refStructure = -1;
     String[] files = getPdbFile();
@@ -559,16 +559,16 @@ public abstract class AAStructureBindingModel extends
             {
               refStructure = pdbfnum;
             }
-            for (int r = 0; r < matched.length; r++)
+            for (int r = 0; r < alignment.getWidth(); r++)
             {
-              if (!matched[r])
+              if (!matched.get(r))
               {
                 continue;
               }
               int pos = getMappedPosition(theSequence, r, mapping);
               if (pos < 1 || pos == lastPos)
               {
-                matched[r] = false;
+                matched.clear(r);
                 continue;
               }
               lastPos = pos;
@@ -700,24 +700,29 @@ public abstract class AAStructureBindingModel extends
 
   public abstract void setJalviewColourScheme(ColourSchemeI cs);
 
-  public abstract void superposeStructures(AlignmentI[] als, int[] alm,
-          ColumnSelection[] alc);
-
-  public abstract void setBackgroundColour(Color col);
-
-  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment);
-
   /**
-   * returns the current featureRenderer that should be used to colour the
-   * structures
-   * 
-   * @param alignment
+   * Constructs and sends a command to align structures against a reference
+   * structure, based on one or more sequence alignments. May optionally return
+   * an error or warning message for the alignment command.
    * 
+   * @param alignments
+   *          an array of alignments to process
+   * @param structureIndices
+   *          an array of corresponding reference structures (index into pdb
+   *          file array); if a negative value is passed, the first PDB file
+   *          mapped to an alignment sequence is used as the reference for
+   *          superposition
+   * @param hiddenCols
+   *          an array of corresponding hidden columns for each alignment
    * @return
    */
-  public abstract FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment);
+  public abstract String superposeStructures(AlignmentI[] alignments, int[] structureIndices,
+          ColumnSelection[] hiddenCols);
+
+  public abstract void setBackgroundColour(Color col);
+
+  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
 
   /**
    * returns the current sequenceRenderer that should be used to colour the
@@ -743,8 +748,6 @@ public abstract class AAStructureBindingModel extends
    */
   public void colourBySequence(AlignmentViewPanel alignmentv)
   {
-    boolean showFeatures = alignmentv.getAlignViewport()
-            .isShowSequenceFeatures();
     if (!colourBySequence || !isLoadingFinished())
     {
       return;
@@ -757,15 +760,8 @@ public abstract class AAStructureBindingModel extends
   
     SequenceRenderer sr = getSequenceRenderer(alignmentv);
   
-    FeatureRenderer fr = null;
-    if (showFeatures)
-    {
-      fr = getFeatureRenderer(alignmentv);
-    }
-    AlignmentI alignment = alignmentv.getAlignment();
-  
     StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
-            files, sr, fr, alignment);
+            files, sr, alignmentv);
     colourBySequence(colourBySequenceCommands);
   }
 
@@ -773,4 +769,7 @@ public abstract class AAStructureBindingModel extends
   {
     return fileLoadingError != null && fileLoadingError.length() > 0;
   }
+
+  public abstract jalview.api.FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment);
 }
index 94d0dd1..47dceec 100644 (file)
@@ -78,6 +78,8 @@ import jalview.workers.StrucConsensusThread;
 public abstract class AlignmentViewport implements AlignViewportI,
         CommandListener, VamsasSource
 {
+  protected ViewportRanges ranges;
+
   protected ViewStyleI viewStyle = new ViewStyle();
 
   /**
@@ -1293,15 +1295,6 @@ public abstract class AlignmentViewport implements AlignViewportI,
    */
   private boolean followHighlight = true;
 
-  // TODO private with getters and setters?
-  public int startRes;
-
-  public int endRes;
-
-  public int startSeq;
-
-  public int endSeq;
-
   /**
    * Property change listener for changes in alignment
    * 
@@ -1911,10 +1904,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   /**
-   * If this is a protein alignment and there are mappings to cDNA, add the cDNA
-   * consensus annotation.
+   * If this is a protein alignment and there are mappings to cDNA, adds the
+   * cDNA consensus annotation and returns true, else returns false.
    */
-  public void initComplementConsensus()
+  public boolean initComplementConsensus()
   {
     if (!alignment.isNucleotide())
     {
@@ -1941,9 +1934,11 @@ public abstract class AlignmentViewport implements AlignViewportI,
                   "PID for cDNA", new Annotation[1], 0f, 100f,
                   AlignmentAnnotation.BAR_GRAPH);
           initConsensus(complementConsensus);
+          return true;
         }
       }
     }
+    return false;
   }
 
   private void initConsensus(AlignmentAnnotation aa)
@@ -2672,63 +2667,10 @@ public abstract class AlignmentViewport implements AlignViewportI,
     this.followHighlight = b;
   }
 
-  public int getStartRes()
-  {
-    return startRes;
-  }
-
   @Override
-  public int getEndRes()
-  {
-    return endRes;
-  }
-
-  public int getStartSeq()
-  {
-    return startSeq;
-  }
-
-  public void setStartRes(int res)
-  {
-    this.startRes = res;
-  }
-
-  public void setStartSeq(int seq)
-  {
-    this.startSeq = seq;
-  }
-
-  public void setEndRes(int res)
-  {
-    if (res > alignment.getWidth() - 1)
-    {
-      // log.System.out.println(" Corrected res from " + res + " to maximum " +
-      // (alignment.getWidth()-1));
-      res = alignment.getWidth() - 1;
-    }
-    if (res < 0)
-    {
-      res = 0;
-    }
-    this.endRes = res;
-  }
-
-  public void setEndSeq(int seq)
-  {
-    if (seq > alignment.getHeight())
-    {
-      seq = alignment.getHeight();
-    }
-    if (seq < 0)
-    {
-      seq = 0;
-    }
-    this.endSeq = seq;
-  }
-
-  public int getEndSeq()
+  public ViewportRanges getRanges()
   {
-    return endSeq;
+    return ranges;
   }
 
   /**
@@ -2768,7 +2710,8 @@ public abstract class AlignmentViewport implements AlignViewportI,
      * locate 'middle' column (true middle if an odd number visible, left of
      * middle if an even number visible)
      */
-    int middleColumn = getStartRes() + (getEndRes() - getStartRes()) / 2;
+    int middleColumn = ranges.getStartRes()
+            + (ranges.getEndRes() - ranges.getStartRes()) / 2;
     final HiddenSequences hiddenSequences = getAlignment()
             .getHiddenSequences();
 
@@ -2778,7 +2721,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
      */
     int lastSeq = alignment.getHeight() - 1;
     List<AlignedCodonFrame> seqMappings = null;
-    for (int seqNo = getStartSeq(); seqNo < lastSeq; seqNo++, seqOffset++)
+    for (int seqNo = ranges.getStartSeq(); seqNo < lastSeq; seqNo++, seqOffset++)
     {
       sequence = getAlignment().getSequenceAt(seqNo);
       if (hiddenSequences != null && hiddenSequences.isHidden(sequence))
diff --git a/src/jalview/viewmodel/OverviewDimensions.java b/src/jalview/viewmodel/OverviewDimensions.java
new file mode 100644 (file)
index 0000000..43680b5
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.viewmodel;
+
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenSequences;
+
+import java.awt.Graphics;
+
+public class OverviewDimensions
+{
+  // Default width and height values
+  private static final int DEFAULT_GRAPH_HEIGHT = 20;
+
+  private static final int MAX_WIDTH = 400;
+
+  private static final int MIN_WIDTH = 120;
+
+  private static final int MIN_SEQ_HEIGHT = 40;
+
+  private static final int MAX_SEQ_HEIGHT = 300;
+
+  // width of the overview panel
+  private int width;
+
+  // height of sequences part of the overview panel
+  private int sequencesHeight;
+
+  // height of the graphs part of the overview panel
+  private int graphHeight = DEFAULT_GRAPH_HEIGHT;
+
+  // dimensions of box outlining current extent of view in alignment panel
+  // location of left side of box
+  private int boxX = -1;
+
+  // location of bottom of box
+  private int boxY = -1;
+
+  // width of box
+  private int boxWidth = -1;
+
+  // height of box
+  private int boxHeight = -1;
+
+  // scroll position in viewport corresponding to boxX
+  private int scrollCol = -1;
+
+  // scroll position in viewport corresponding to boxY
+  private int scrollRow = -1;
+
+  /**
+   * Create an OverviewDimensions object
+   * 
+   * @param ranges
+   *          positional properties of the viewport
+   * @param showAnnotationPanel
+   *          true if the annotation panel is to be shown, false otherwise
+   */
+  public OverviewDimensions(ViewportRanges ranges,
+          boolean showAnnotationPanel)
+  {
+    // scale the initial size of overviewpanel to shape of alignment
+    float initialScale = (float) ranges.getAbsoluteAlignmentWidth()
+            / (float) ranges.getAbsoluteAlignmentHeight();
+
+    if (!showAnnotationPanel)
+    {
+      graphHeight = 0;
+    }
+
+    if (ranges.getAbsoluteAlignmentWidth() > ranges
+            .getAbsoluteAlignmentHeight())
+    {
+      // wider
+      width = MAX_WIDTH;
+      sequencesHeight = Math.round(MAX_WIDTH / initialScale);
+      if (sequencesHeight < MIN_SEQ_HEIGHT)
+      {
+        sequencesHeight = MIN_SEQ_HEIGHT;
+      }
+    }
+    else
+    {
+      // taller
+      width = Math.round(MAX_WIDTH * initialScale);
+      sequencesHeight = MAX_SEQ_HEIGHT;
+
+      if (width < MIN_WIDTH)
+      {
+        width = MIN_WIDTH;
+      }
+    }
+  }
+
+  /**
+   * Check box dimensions and scroll positions and correct if necessary
+   * 
+   * @param mousex
+   *          x position in overview panel
+   * @param mousey
+   *          y position in overview panel
+   * @param hiddenSeqs
+   *          hidden sequences
+   * @param hiddenCols
+   *          hidden columns
+   * @param ranges
+   *          viewport position properties
+   */
+  public void updateViewportFromMouse(int mousex, int mousey,
+          HiddenSequences hiddenSeqs, ColumnSelection hiddenCols,
+          ViewportRanges ranges)
+  {
+    int x = mousex;
+    int y = mousey;
+
+    int alwidth = ranges.getAbsoluteAlignmentWidth();
+    int alheight = ranges.getAbsoluteAlignmentHeight();
+
+    if (x < 0)
+    {
+      x = 0;
+    }
+
+    if (y < 0)
+    {
+      y = 0;
+    }
+
+    //
+    // Convert x value to residue position
+    //
+
+    // need to determine where scrollCol should be, given x
+    // to do this also need to know width of viewport, and some hidden column
+    // correction
+
+    // convert x to residues - this is an absolute position
+    int xAsRes = Math.round((float) x * alwidth / width);
+
+    // get viewport width in residues
+    int vpwidth = ranges.getEndRes() - ranges.getStartRes() + 1;
+
+    // get where x should be when accounting for hidden cols
+    // if x is in a hidden col region, shift to left - but we still need
+    // absolute position
+    // so convert back after getting visible region position
+    int visXAsRes = hiddenCols.findColumnPosition(xAsRes);
+
+    // check in case we went off the edge of the alignment
+    int visAlignWidth = hiddenCols.findColumnPosition(alwidth - 1);
+    if (visXAsRes + vpwidth - 1 > visAlignWidth)
+    {
+      // went past the end of the alignment, adjust backwards
+
+      // if last position was before the end of the alignment, need to update
+      if ((scrollCol + vpwidth - 1) < visAlignWidth)
+      {
+        visXAsRes = hiddenCols.findColumnPosition(hiddenCols
+                .subtractVisibleColumns(vpwidth - 1, alwidth - 1));
+      }
+      else
+      {
+        visXAsRes = scrollCol;
+      }
+    }
+
+    //
+    // Convert y value to sequence position
+    //
+
+    // convert y to residues
+    int yAsSeq = Math.round((float) y * alheight / sequencesHeight);
+
+    // get viewport height in sequences
+    // add 1 because height includes both endSeq and startSeq
+    int vpheight = ranges.getEndSeq() - ranges.getStartSeq() + 1;
+
+    // get where y should be when accounting for hidden rows
+    // if y is in a hidden row region, shift up - but we still need absolute
+    // position,
+    // so convert back after getting visible region position
+    yAsSeq = hiddenSeqs.adjustForHiddenSeqs(hiddenSeqs
+            .findIndexWithoutHiddenSeqs(yAsSeq));
+
+    // check in case we went off the edge of the alignment
+    int visAlignHeight = hiddenSeqs.findIndexWithoutHiddenSeqs(alheight);
+    int visYAsRes = hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq);
+    if (visYAsRes + vpheight - 1 > visAlignHeight)
+    {
+      // went past the end of the alignment, adjust backwards
+      if ((scrollRow + vpheight - 1) < visAlignHeight)
+      {
+        visYAsRes = hiddenSeqs.findIndexWithoutHiddenSeqs(hiddenSeqs
+                .subtractVisibleRows(vpheight - 1, alheight - 1));
+      }
+      else
+      {
+        visYAsRes = scrollRow;
+      }
+    }
+
+    // update scroll values
+    scrollCol = visXAsRes;
+    scrollRow = visYAsRes;
+
+  }
+
+  /**
+   * Update the overview panel box when the associated alignment panel is
+   * changed
+   * 
+   * @param hiddenSeqs
+   *          hidden sequences
+   * @param hiddenCols
+   *          hidden columns
+   * @param ranges
+   *          viewport position properties
+   */
+  public void setBoxPosition(HiddenSequences hiddenSeqs,
+          ColumnSelection hiddenCols, ViewportRanges ranges)
+  {
+    int alwidth = ranges.getAbsoluteAlignmentWidth();
+    int alheight = ranges.getAbsoluteAlignmentHeight();
+
+    // work with absolute values of startRes and endRes
+    int startRes = hiddenCols.adjustForHiddenColumns(ranges.getStartRes());
+    int endRes = hiddenCols.adjustForHiddenColumns(ranges.getEndRes());
+
+    // work with absolute values of startSeq and endSeq
+    int startSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getStartSeq());
+    int endSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getEndSeq());
+
+    // boxX, boxY is the x,y location equivalent to startRes, startSeq
+    boxX = Math.round((float) startRes * width / alwidth);
+    boxY = Math.round((float) startSeq * sequencesHeight / alheight);
+
+    // boxWidth is the width in residues translated to pixels
+    // since the box includes both the start and end residues, add 1 to the
+    // difference
+    boxWidth = Math
+            .round((float) (endRes - startRes + 1) * width / alwidth);
+    // boxHeight is the height in sequences translated to pixels
+    boxHeight = Math.round((float) (endSeq - startSeq + 1)
+            * sequencesHeight
+            / alheight);
+  }
+
+  /**
+   * Draw the overview panel's viewport box on a graphics object
+   * 
+   * @param g
+   *          the graphics object to draw on
+   */
+  public void drawBox(Graphics g)
+  {
+    g.drawRect(boxX, boxY, boxWidth, boxHeight);
+    g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+  }
+
+  public int getScrollCol()
+  {
+    return scrollCol;
+  }
+
+  public int getScrollRow()
+  {
+    return scrollRow;
+  }
+
+  // TODO should be removed, when unit test has mock Graphics object available
+  // to check boxX/boxY
+  public int getBoxX()
+  {
+    return boxX;
+  }
+
+  // TODO should be removed, when unit test has mock Graphics object available
+  // to check boxX/boxY
+  public int getBoxY()
+  {
+    return boxY;
+  }
+
+  // TODO should be removed, when unit test has mock Graphics object available
+  public int getBoxWidth()
+  {
+    return boxWidth;
+  }
+
+  // TODO should be removed, when unit test has mock Graphics object available
+  public int getBoxHeight()
+  {
+    return boxHeight;
+  }
+
+  public void setWidth(int w)
+  {
+    width = w;
+  }
+
+  public void setHeight(int h)
+  {
+    sequencesHeight = h - graphHeight;
+  }
+
+  public int getWidth()
+  {
+    return width;
+  }
+
+  public int getHeight()
+  {
+    return sequencesHeight + graphHeight;
+  }
+
+  public int getSequencesHeight()
+  {
+    return sequencesHeight;
+  }
+
+  public int getGraphHeight()
+  {
+    return graphHeight;
+  }
+}
diff --git a/src/jalview/viewmodel/ViewportProperties.java b/src/jalview/viewmodel/ViewportProperties.java
new file mode 100644 (file)
index 0000000..246806e
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.viewmodel;
+
+public abstract class ViewportProperties
+{
+
+}
diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java
new file mode 100644 (file)
index 0000000..c91d2d9
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.viewmodel;
+
+import jalview.datamodel.AlignmentI;
+
+/**
+ * Embryonic class which: Supplies and updates viewport properties relating to
+ * position such as: start and end residues and sequences; ideally will serve
+ * hidden columns/rows too. Intention also to support calculations for
+ * positioning, scrolling etc. such as finding the middle of the viewport,
+ * checking for scrolls off screen
+ */
+public class ViewportRanges extends ViewportProperties
+{
+  // start residue of viewport
+  private int startRes;
+
+  // end residue of viewport
+  private int endRes;
+
+  // start sequence of viewport
+  private int startSeq;
+
+  // end sequence of viewport
+  private int endSeq;
+
+  // alignment
+  private AlignmentI al;
+
+  /**
+   * Constructor
+   * 
+   * @param alignment
+   *          the viewport's alignment
+   */
+  public ViewportRanges(AlignmentI alignment)
+  {
+    // initial values of viewport settings
+    this.startRes = 0;
+    this.endRes = alignment.getWidth() - 1;
+    this.startSeq = 0;
+    this.endSeq = alignment.getHeight() - 1;
+    this.al = alignment;
+  }
+
+  /**
+   * Get alignment width in cols, including hidden cols
+   */
+  public int getAbsoluteAlignmentWidth()
+  {
+    return al.getWidth();
+  }
+
+  /**
+   * Get alignment height in rows, including hidden rows
+   */
+  public int getAbsoluteAlignmentHeight()
+  {
+    return al.getHeight() + al.getHiddenSequences().getSize();
+  }
+
+  /**
+   * Set first residue visible in the viewport
+   * 
+   * @param res
+   *          residue position
+   */
+  public void setStartRes(int res)
+  {
+    if (res > al.getWidth() - 1)
+    {
+      res = al.getWidth() - 1;
+    }
+    else if (res < 0)
+    {
+      res = 0;
+    }
+    this.startRes = res;
+  }
+
+  /**
+   * Set last residue visible in the viewport
+   * 
+   * @param res
+   *          residue position
+   */
+  public void setEndRes(int res)
+  {
+    if (res >= al.getWidth())
+    {
+      res = al.getWidth() - 1;
+    }
+    else if (res < 0)
+    {
+      res = 0;
+    }
+    this.endRes = res;
+  }
+
+  /**
+   * Set the first sequence visible in the viewport
+   * 
+   * @param seq
+   *          sequence position
+   */
+  public void setStartSeq(int seq)
+  {
+    if (seq > al.getHeight() - 1)
+    {
+      seq = al.getHeight() - 1;
+    }
+    else if (seq < 0)
+    {
+      seq = 0;
+    }
+    this.startSeq = seq;
+  }
+
+  /**
+   * Set the last sequence visible in the viewport
+   * 
+   * @param seq
+   *          sequence position
+   */
+  public void setEndSeq(int seq)
+  {
+    if (seq >= al.getHeight())
+    {
+      seq = al.getHeight() - 1;
+    }
+    else if (seq < 0)
+    {
+      seq = 0;
+    }
+    this.endSeq = seq;
+  }
+
+  /**
+   * Get start residue of viewport
+   */
+  public int getStartRes()
+  {
+    return startRes;
+  }
+
+  /**
+   * Get end residue of viewport
+   */
+  public int getEndRes()
+  {
+    return endRes;
+  }
+
+  /**
+   * Get start sequence of viewport
+   */
+  public int getStartSeq()
+  {
+    return startSeq;
+  }
+
+  /**
+   * Get end sequence of viewport
+   */
+  public int getEndSeq()
+  {
+    return endSeq;
+  }
+}
index 8468329..84c9477 100644 (file)
@@ -550,11 +550,13 @@ public abstract class FeatureRendererModel implements
   }
 
   /**
-   * calculate the render colour for a specific feature using current feature
-   * settings.
+   * Returns the configured colour for a particular feature instance. This
+   * includes calculation of 'colour by label', or of a graduated score colour,
+   * if applicable. It does not take into account feature visibility or colour
+   * transparency.
    * 
    * @param feature
-   * @return render colour for the given feature
+   * @return
    */
   public Color getColour(SequenceFeature feature)
   {
@@ -586,11 +588,13 @@ public abstract class FeatureRendererModel implements
     featureColours.put(featureType, col);
   }
 
+  @Override
   public void setTransparency(float value)
   {
     transparency = value;
   }
 
+  @Override
   public float getTransparency()
   {
     return transparency;
@@ -822,7 +826,7 @@ public abstract class FeatureRendererModel implements
    * @return list of groups
    */
   @Override
-  public List getGroups(boolean visible)
+  public List<String> getGroups(boolean visible)
   {
     if (featureGroups != null)
     {
index e71c4f5..571234c 100644 (file)
@@ -55,7 +55,7 @@ public class ConservationThread extends AlignCalcWorker
     {
       calcMan.notifyStart(this); // updatingConservation = true;
 
-      while (!calcMan.notifyWorking(this))
+      while ((calcMan != null) && (!calcMan.notifyWorking(this)))
       {
         try
         {
@@ -69,7 +69,8 @@ public class ConservationThread extends AlignCalcWorker
           ex.printStackTrace();
         }
       }
-      if (alignViewport.isClosed())
+      if ((alignViewport == null) || (calcMan == null)
+              || (alignViewport.isClosed()))
       {
         abortAndDestroy();
         return;
@@ -114,6 +115,12 @@ public class ConservationThread extends AlignCalcWorker
     }
     calcMan.workerComplete(this);
 
+    if ((alignViewport == null) || (calcMan == null)
+            || (alignViewport.isClosed()))
+    {
+      abortAndDestroy();
+      return;
+    }
     if (ap != null)
     {
       ap.paintAlignment(true);
index 676a4b6..4d3dd2f 100644 (file)
@@ -617,8 +617,8 @@ public class DasSequenceFeatureFetcher
     }
     af.getFeatureRenderer().featuresAdded();
 
-    int start = af.getViewport().getStartSeq();
-    int end = af.getViewport().getEndSeq();
+    int start = af.getViewport().getRanges().getStartSeq();
+    int end = af.getViewport().getRanges().getEndSeq();
     int index;
     for (index = start; index < end; index++)
     {
index b184ff2..e0f7f70 100644 (file)
@@ -69,6 +69,7 @@ public class DasSourceRegistry implements DasSourceRegistryI,
     return loadingDasSources;
   }
 
+  @Override
   public String getDasRegistryURL()
   {
     String registry = jalview.bin.Cache.getDefault("DAS_REGISTRY_URL",
@@ -150,9 +151,8 @@ public class DasSourceRegistry implements DasSourceRegistryI,
       return dsrc;
     } catch (Exception ex)
     {
-      System.err.println("Failed to contact DAS1 registry at "
-              + registryURL);
-      ex.printStackTrace();
+      System.out.println("DAS1 registry at " + registryURL
+              + " no longer exists");
       return new ArrayList<jalviewSourceI>();
     }
   }
index 1b2c708..12a08a0 100644 (file)
@@ -67,7 +67,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   /*
    * the .jalview_properties entry for JWS2 URLS
    */
-  final static String JWS2HOSTURLS = "JWS2HOSTURLS";
+  private final static String JWS2HOSTURLS = "JWS2HOSTURLS";
 
   /*
    * Singleton instance
@@ -85,12 +85,17 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
           this);
 
-  Vector<String> invalidServiceUrls = null, urlsWithoutServices = null,
-          validServiceUrls = null;
+  private Vector<String> invalidServiceUrls = null;
 
-  boolean running = false, aborted = false;
+  private Vector<String> urlsWithoutServices = null;
 
-  Thread oldthread = null;
+  private Vector<String> validServiceUrls = null;
+
+  private volatile boolean running = false;
+
+  private volatile boolean aborted = false;
+
+  private Thread oldthread = null;
 
   /**
    * holds list of services.
@@ -143,9 +148,9 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   public void setAborted(boolean aborted)
   {
     this.aborted = aborted;
-
   }
 
+  @Override
   public void run()
   {
 
@@ -167,6 +172,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
         {
         }
       }
+      aborted = false;
       Cache.log.debug("Old discovery thread has finished.");
     }
     running = true;
@@ -179,7 +185,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
       ignoredServices.add(ignored);
     }
 
-    changeSupport.firePropertyChange("services", services, new Vector());
+    changeSupport.firePropertyChange("services", services,
+            new Vector<Jws2Instance>());
     oldthread = Thread.currentThread();
     try
     {
@@ -263,7 +270,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     if (!aborted)
     {
       // resort services according to order found in jabaws service list
-      // also ensure servics for each host are ordered in same way.
+      // also ensure services for each host are ordered in same way.
 
       if (services != null && services.size() > 0)
       {
@@ -290,7 +297,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     }
     oldthread = null;
     running = false;
-    changeSupport.firePropertyChange("services", new Vector(), services);
+    changeSupport.firePropertyChange("services",
+            new Vector<Jws2Instance>(), services);
   }
 
   /**
@@ -321,7 +329,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     service.hasParameters();
     if (validServiceUrls == null)
     {
-      validServiceUrls = new Vector();
+      validServiceUrls = new Vector<String>();
     }
     validServiceUrls.add(jwsservers);
   }
@@ -330,6 +338,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * attach all available web services to the appropriate submenu in the given
    * JMenu
    */
+  @Override
   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
   {
     // dynamically regenerate service list.
@@ -348,8 +357,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     {
       return;
     }
-    boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
-            .getDefault("WSMENU_BYTYPE", false);
+
     /**
      * eventually, JWS2 services will appear under the same align/etc submenus.
      * for moment we keep them separate.
@@ -442,27 +450,19 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
             {
               new Thread(new Runnable()
               {
+                @Override
                 public void run()
                 {
                   setPreferredServiceFor(alignFrame, sv.serviceType,
                           sv.action, sv);
                   changeSupport.firePropertyChange("services",
-                          new Vector(), services);
+                          new Vector<Jws2Instance>(), services);
                 };
               }).start();
 
             }
           });
         }
-        /*
-         * hitm.addActionListener(new ActionListener() {
-         * 
-         * @Override public void actionPerformed(ActionEvent arg0) { new
-         * Thread(new Runnable() {
-         * 
-         * @Override public void run() { new SetPreferredServer(alignFrame,
-         * service.serviceType, service.action); } }).start(); } });
-         */
       }
     }
   }
@@ -481,7 +481,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
      * for moment we keep them separate.
      */
     JMenu atpoint;
-    MsaWSClient msacl = new MsaWSClient();
+
     List<String> hostLabels = new ArrayList<String>();
     Hashtable<String, String> lasthostFor = new Hashtable<String, String>();
     Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
@@ -590,6 +590,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
             new PropertyChangeListener()
             {
 
+              @Override
               public void propertyChange(PropertyChangeEvent evt)
               {
                 if (getDiscoverer().services != null)
@@ -765,6 +766,22 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     return true;
   }
 
+  public boolean restart()
+  {
+    synchronized (this)
+    {
+      if (running)
+      {
+        aborted = true;
+      }
+      else
+      {
+        running = true;
+      }
+      return aborted;
+    }
+  }
+
   /**
    * Start a fresh discovery thread and notify the given object when we're
    * finished. Any known existing threads will be killed before this one is
@@ -775,6 +792,16 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    */
   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
   {
+    /*    if (restart())
+        {
+          return;
+        }
+        else
+        {
+          Thread thr = new Thread(this);
+          thr.start();
+        }
+       */
     if (isRunning())
     {
       setAborted(true);
index ea65125..c69581f 100644 (file)
@@ -607,8 +607,12 @@ public class SiftsClient implements SiftsClientI
                     .getDbResNum());
           } catch (NumberFormatException nfe)
           {
-            resNum = (pdbRefDb == null) ? Integer.valueOf(residue
-                    .getDbResNum()) : Integer.valueOf(pdbRefDb
+            if (pdbRefDb == null || pdbRefDb.getDbResNum().equals("null"))
+            {
+              resNum = UNASSIGNED;
+              continue;
+            }
+            resNum = Integer.valueOf(pdbRefDb
                     .getDbResNum().split("[a-zA-Z]")[0]);
             continue;
           }
index 7af77f5..c5d09c1 100644 (file)
@@ -27,6 +27,7 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.analysis.AlignmentGenerator;
 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
@@ -1196,4 +1197,76 @@ public class AlignmentTest
     assertNull(a.findGroup(seq2, 8));
   }
 
+  @Test(groups = { "Functional" })
+  public void testDeleteSequenceByIndex()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete sequence 10, alignment reduced by 1
+    int height = a.getAbsoluteHeight();
+    a.deleteSequence(10);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete -ve index, nothing happens
+    a.deleteSequence(-1);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete beyond end of alignment, nothing happens
+    a.deleteSequence(14);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testDeleteSequenceBySeq()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete sequence 10, alignment reduced by 1
+    int height = a.getAbsoluteHeight();
+    SequenceI seq = a.getSequenceAt(10);
+    a.deleteSequence(seq);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+
+    // try to delete non-existent sequence, nothing happens
+    seq = new Sequence("cds", "GCCTCGGAT");
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testDeleteHiddenSequence()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI a = gen.generate(20, 15, 123, 5, 5);
+
+    // delete a sequence which is hidden, check it is NOT removed from hidden
+    // sequences
+    int height = a.getAbsoluteHeight();
+    SequenceI seq = a.getSequenceAt(2);
+    a.getHiddenSequences().hideSequence(seq);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+    a.deleteSequence(2);
+    assertEquals(a.getAbsoluteHeight(), height - 1);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+
+    // delete a sequence which is not hidden, check hiddenSequences are not
+    // affected
+    a.deleteSequence(10);
+    assertEquals(a.getAbsoluteHeight(), height - 2);
+    assertEquals(a.getHiddenSequences().getSize(), 1);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testSetDataset_selfReference()
+  {
+    SequenceI seq = new Sequence("a", "a");
+    AlignmentI alignment = new Alignment(new SequenceI[] { seq });
+    alignment.setDataset(alignment);
+  }
 }
index 1d819c9..4d3f611 100644 (file)
@@ -105,9 +105,97 @@ public class ColumnSelectionTest
     cs.hideColumns(4, 4);
     assertEquals(4, cs.findColumnPosition(5));
 
+    // hiding column 4 moves column 4 to position 3
+    assertEquals(3, cs.findColumnPosition(4));
+
     // hiding columns 1 and 2 moves column 5 to column 2
     cs.hideColumns(1, 2);
     assertEquals(2, cs.findColumnPosition(5));
+
+    // check with > 1 hidden column regions
+    // where some columns are in the hidden regions
+    ColumnSelection cs2 = new ColumnSelection();
+    cs2.hideColumns(5, 10);
+    cs2.hideColumns(20, 27);
+    cs2.hideColumns(40, 44);
+
+    // hiding columns 5-10 and 20-27 moves column 8 to column 4
+    assertEquals(4, cs2.findColumnPosition(8));
+
+    // and moves column 24 to 13
+    assertEquals(13, cs2.findColumnPosition(24));
+
+    // and moves column 28 to 14
+    assertEquals(14, cs2.findColumnPosition(28));
+
+    // and moves column 40 to 25
+    assertEquals(25, cs2.findColumnPosition(40));
+
+    // check when hidden columns start at 0 that the visible column
+    // is returned as 0
+    ColumnSelection cs3 = new ColumnSelection();
+    cs3.hideColumns(0, 4);
+    assertEquals(0, cs3.findColumnPosition(2));
+
+  }
+
+  /**
+   * Test the method that finds the visible column position a given distance
+   * before another column
+   */
+  @Test(groups = { "Functional" })
+  public void testFindColumnNToLeft()
+  {
+    ColumnSelection cs = new ColumnSelection();
+
+    // test that without hidden columns, findColumnNToLeft returns
+    // position n to left of provided position
+    int pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to left returns negative number
+    pos = cs.subtractVisibleColumns(3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden columns to left of result column
+    // behaviour is the same as above
+    cs.hideColumns(1, 3);
+
+    // position n to left of provided position
+    pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    cs.revealAllHiddenColumns();
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
   }
 
   /**
index cae3536..7795988 100644 (file)
@@ -49,7 +49,7 @@ public class HiddenSequencesTest
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  static int SEQ_COUNT = 10;
+  static int SEQ_COUNT = 25;
 
   SequenceI[] seqs;
 
@@ -62,8 +62,9 @@ public class HiddenSequencesTest
     seqs = new SequenceI[SEQ_COUNT];
     for (int i = 0; i < SEQ_COUNT; i++)
     {
-      // sequence lengths are 1, 2, ... 10
-      seqs[i] = new Sequence("Seq" + i, "abcdefghijk".substring(0, i + 1));
+      // sequence lengths are 1, 2, ... 25
+      seqs[i] = new Sequence("Seq" + i,
+              "abcdefghijklmnopqrstuvwxy".substring(0, i + 1));
     }
   }
 
@@ -89,7 +90,7 @@ public class HiddenSequencesTest
     /*
      * alignment is now seq0/2/3/4/7/8/9
      */
-    assertEquals(7, al.getHeight());
+    assertEquals(SEQ_COUNT - 3, al.getHeight());
     assertEquals(0, hs.adjustForHiddenSeqs(0));
     assertEquals(2, hs.adjustForHiddenSeqs(1));
     assertEquals(3, hs.adjustForHiddenSeqs(2));
@@ -193,7 +194,7 @@ public class HiddenSequencesTest
     /*
      * alignment is now seq0/2/3/4/7/8/9
      */
-    assertEquals(7, al.getHeight());
+    assertEquals(SEQ_COUNT - 3, al.getHeight());
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(0));
     assertEquals(0, hs.findIndexWithoutHiddenSeqs(1));
     assertEquals(1, hs.findIndexWithoutHiddenSeqs(2));
@@ -207,6 +208,76 @@ public class HiddenSequencesTest
   }
 
   /**
+   * Test the method that finds the visible row position a given distance before
+   * another row
+   */
+  @Test(groups = { "Functional" })
+  public void testFindIndexNFromRow()
+  {
+    AlignmentI al = new Alignment(seqs);
+    HiddenSequences hs = new HiddenSequences(al);
+
+    // test that without hidden rows, findIndexNFromRow returns
+    // position n above provided position
+    int pos = hs.subtractVisibleRows(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = hs.subtractVisibleRows(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to top returns negative number
+    pos = hs.subtractVisibleRows(3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden rows above result row
+    // behaviour is the same as above
+    hs.hideSequence(seqs[1]);
+    hs.hideSequence(seqs[2]);
+    hs.hideSequence(seqs[3]);
+
+    // position n above provided position
+    pos = hs.subtractVisibleRows(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = hs.subtractVisibleRows(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden rows between start and required position
+    hs.hideSequence(seqs[12]);
+    hs.hideSequence(seqs[13]);
+    hs.hideSequence(seqs[14]);
+    hs.hideSequence(seqs[15]);
+    pos = hs.subtractVisibleRows(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden rows between start and required position
+    hs.hideSequence(seqs[20]);
+    hs.hideSequence(seqs[21]);
+    pos = hs.subtractVisibleRows(8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    hs.showAll(null);
+
+    // test with one set of hidden rows between start and required position
+    hs.hideSequence(seqs[12]);
+    hs.hideSequence(seqs[13]);
+    hs.hideSequence(seqs[14]);
+    hs.hideSequence(seqs[15]);
+    pos = hs.subtractVisibleRows(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden rows between start and required position
+    hs.hideSequence(seqs[20]);
+    hs.hideSequence(seqs[21]);
+    pos = hs.subtractVisibleRows(8, 23);
+    assertEquals(9, pos);
+
+  }
+
+  /**
    * Test the method that reconstructs (sort of) the full alignment including
    * hidden sequences
    */
@@ -289,7 +360,7 @@ public class HiddenSequencesTest
     assertTrue(al.getSequences().contains(seqs[1]));
     HiddenSequences hs = al.getHiddenSequences();
     assertEquals(0, hs.getSize());
-    assertEquals(10, al.getHeight());
+    assertEquals(SEQ_COUNT, al.getHeight());
 
     /*
      * hide the second sequence in the alignment
@@ -299,7 +370,7 @@ public class HiddenSequencesTest
     assertTrue(hs.isHidden(seqs[1]));
     assertFalse(al.getSequences().contains(seqs[1]));
     assertEquals(1, hs.getSize());
-    assertEquals(9, al.getHeight());
+    assertEquals(SEQ_COUNT - 1, al.getHeight());
     assertSame(seqs[2], al.getSequenceAt(1));
 
     /*
@@ -312,7 +383,7 @@ public class HiddenSequencesTest
     assertFalse(al.getSequences().contains(seqs[1]));
     assertFalse(al.getSequences().contains(seqs[2]));
     assertEquals(2, hs.getSize());
-    assertEquals(8, al.getHeight());
+    assertEquals(SEQ_COUNT - 2, al.getHeight());
 
     /*
      * perform 'reveal' on what is now the second sequence in the alignment
@@ -323,7 +394,54 @@ public class HiddenSequencesTest
     assertTrue(revealed.contains(seqs[1]));
     assertTrue(revealed.contains(seqs[2]));
     assertEquals(0, hs.getSize());
-    assertEquals(10, al.getHeight());
+    assertEquals(SEQ_COUNT, al.getHeight());
+  }
+
+  /**
+   * Test the method that adds a sequence to the hidden sequences and deletes it
+   * from the alignment, and its converse, where the first hidden sequences are
+   * at the bottom of the alignment (JAL-2437)
+   */
+  @Test(groups = "Functional")
+  public void testHideShowLastSequences()
+  {
+    AlignmentI al = new Alignment(seqs);
+    assertTrue(al.getSequences().contains(seqs[1]));
+    HiddenSequences hs = al.getHiddenSequences();
+    assertEquals(0, hs.getSize());
+    assertEquals(SEQ_COUNT, al.getHeight());
+
+    /*
+     * hide the last sequence in the alignment
+     */
+    hs.hideSequence(seqs[SEQ_COUNT - 1]);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 2]));
+    assertTrue(hs.isHidden(seqs[SEQ_COUNT - 1]));
+    assertFalse(al.getSequences().contains(seqs[SEQ_COUNT - 1]));
+    assertEquals(1, hs.getSize());
+    assertEquals(SEQ_COUNT - 1, al.getHeight());
+
+    /*
+     * hide the third last sequence in the alignment
+     */
+    hs.hideSequence(seqs[SEQ_COUNT - 3]);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 2]));
+    assertTrue(hs.isHidden(seqs[SEQ_COUNT - 3]));
+    assertFalse(al.getSequences().contains(seqs[SEQ_COUNT - 3]));
+    assertEquals(2, hs.getSize());
+    assertEquals(SEQ_COUNT - 2, al.getHeight());
+
+    /*
+     * reveal all the sequences, which should be reinstated in the same order as they started in
+     */
+    hs.showAll(null);
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 3]));
+    assertFalse(hs.isHidden(seqs[SEQ_COUNT - 1]));
+    assertEquals(seqs[SEQ_COUNT - 3], al.getSequences().get(SEQ_COUNT - 3));
+    assertEquals(seqs[SEQ_COUNT - 2], al.getSequences().get(SEQ_COUNT - 2));
+    assertEquals(seqs[SEQ_COUNT - 1], al.getSequences().get(SEQ_COUNT - 1));
+    assertEquals(0, hs.getSize());
+    assertEquals(SEQ_COUNT, al.getHeight());
   }
 
   @Test(groups = "Functional")
index 65549f2..6e1c2db 100644 (file)
@@ -10,6 +10,8 @@ import static org.testng.Assert.fail;
 
 import jalview.schemes.NucleotideColourScheme;
 
+import junit.extensions.PA;
+
 import org.testng.annotations.Test;
 
 public class SequenceGroupTest
@@ -111,6 +113,21 @@ public class SequenceGroupTest
       // expected
       assertNull(sg3.getContext());
     }
+
+    /*
+     * use PrivilegedAccessor to 'force' a SequenceGroup with
+     * a circular context reference
+     */
+    PA.setValue(sg2, "context", sg2);
+    try
+    {
+      sg3.setContext(sg2); // circular reference in sg2
+      fail("Expected exception");
+    } catch (IllegalArgumentException e)
+    {
+      // expected
+      assertNull(sg3.getContext());
+    }
   }
 
   @Test(groups = { "Functional" })
index 439e188..3309adf 100644 (file)
  */
 package jalview.ext.jmol;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
+import jalview.schemes.JalviewColourScheme;
+import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
+import java.util.HashMap;
+
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -58,6 +66,77 @@ public class JmolCommandsTest
     // need some mappings!
 
     StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, null, al);
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetColourBySequenceCommands_hiddenColumns()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    StructureSelectionManager ssm = new StructureSelectionManager();
+  
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+  
+    StructureMappingcommandSet[] commands = JmolCommands
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+    assertEquals(commands.length, 2);
+    assertEquals(commands[0].commands.length, 1);
+
+    String chainACommand = commands[0].commands[0];
+    // M colour is #82827d == (130, 130, 125) (see strand.html help page)
+    assertTrue(chainACommand
+            .contains(";select 21:A/1.1;color[130,130,125]"));
+    // H colour is #60609f == (96, 96, 159)
+    assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(chainACommand
+            .contains(";select 23-25:A/1.1;color[128,128,128]"));
+    // S and G are both coloured #4949b6 == (73, 73, 182)
+    assertTrue(chainACommand
+            .contains(";select 26-30:A/1.1;color[73,73,182]"));
+
+    String chainBCommand = commands[1].commands[0];
+    // M colour is #82827d == (130, 130, 125)
+    assertTrue(chainBCommand
+            .contains(";select 21:B/2.1;color[130,130,125]"));
+    // V colour is #ffff00 == (255, 255, 0)
+    assertTrue(chainBCommand
+.contains(";select 22:B/2.1;color[255,255,0]"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(chainBCommand
+            .contains(";select 23-25:B/2.1;color[128,128,128]"));
+    // S and G are both coloured #4949b6 == (73, 73, 182)
+    assertTrue(chainBCommand
+            .contains(";select 26-30:B/2.1;color[73,73,182]"));
   }
 }
index fb442e3..2c973ca 100644 (file)
@@ -23,7 +23,18 @@ package jalview.ext.rbvi.chimera;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
+import jalview.gui.SequenceRenderer;
+import jalview.schemes.JalviewColourScheme;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.util.HashMap;
@@ -90,7 +101,7 @@ public class ChimeraCommandsTest
      * feature name gets a jv_ namespace prefix
      * feature value is quoted in case it contains spaces
      */
-    assertEquals(commands.get(0), "setattr r jv_chain \"X\" #0:8-20.A");
+    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A");
 
     // add same feature value, overlapping range
     ChimeraCommands.addColourRange(featureValues, "X", 0, 3, 9, "A");
@@ -98,7 +109,7 @@ public class ChimeraCommandsTest
     ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "A");
     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
     assertEquals(1, commands.size());
-    assertEquals(commands.get(0), "setattr r jv_chain \"X\" #0:3-25.A");
+    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
 
     // same feature value and model, different chain
     ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "B");
@@ -107,7 +118,7 @@ public class ChimeraCommandsTest
     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
     assertEquals(1, commands.size());
     assertEquals(commands.get(0),
-            "setattr r jv_chain \"X\" #0:3-25.A,21-25.B|#1:26-30.A");
+            "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
 
     // same feature, different value
     ChimeraCommands.addColourRange(featureValues, "Y", 0, 40, 50, "A");
@@ -116,18 +127,20 @@ public class ChimeraCommandsTest
     // commands are ordered by feature type but not by value
     // so use contains to test for the expected command:
     assertTrue(commands
-            .contains("setattr r jv_chain \"X\" #0:3-25.A,21-25.B|#1:26-30.A"));
-    assertTrue(commands.contains("setattr r jv_chain \"Y\" #0:40-50.A"));
+            .contains("setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
+    assertTrue(commands.contains("setattr r jv_chain 'Y' #0:40-50.A"));
 
     featuresMap.clear();
     featureValues.clear();
     featuresMap.put("side-chain binding!", featureValues);
-    ChimeraCommands.addColourRange(featureValues, "metal ion!", 0, 7, 15,
+    ChimeraCommands.addColourRange(featureValues,
+            "<html>metal <a href=\"http:a.b.c/x\"> 'ion!", 0, 7, 15,
             "A");
-    // feature names are sanitised to change space or hyphen to underscore
+    // feature names are sanitised to change non-alphanumeric to underscore
+    // feature values are sanitised to encode single quote characters
     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
     assertTrue(commands
-            .contains("setattr r jv_side_chain_binding_ \"metal ion!\" #0:7-15.A"));
+            .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
   }
 
   /**
@@ -149,4 +162,59 @@ public class ChimeraCommandsTest
     assertEquals(ChimeraCommands.makeAttributeName("helixColor"),
             "jv_helixColor_");
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetColourBySequenceCommands_hiddenColumns()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    StructureSelectionManager ssm = new StructureSelectionManager();
+
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+
+    StructureMappingcommandSet[] commands = ChimeraCommands
+            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+    assertEquals(1, commands.length);
+    assertEquals(1, commands[0].commands.length);
+    String theCommand = commands[0].commands[0];
+    // M colour is #82827d (see strand.html help page)
+    assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
+    // H colour is #60609f
+    assertTrue(theCommand.contains("color #60609f #0:22.A"));
+    // V colour is #ffff00
+    assertTrue(theCommand.contains("color #ffff00 #1:22.B"));
+    // hidden columns are Gray (128, 128, 128)
+    assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B"));
+    // S and G are both coloured #4949b6
+    assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
+  }
 }
diff --git a/test/jalview/gui/AlignmentPanelTest.java b/test/jalview/gui/AlignmentPanelTest.java
new file mode 100644 (file)
index 0000000..b228ba1
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.viewmodel.ViewportRanges;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class AlignmentPanelTest
+{
+  SequenceI seq1 = new Sequence(
+          "Seq1",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq2 = new Sequence(
+          "Seq2",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq3 = new Sequence(
+          "Seq3",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq4 = new Sequence(
+          "Seq4",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq5 = new Sequence(
+          "Seq5",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq6 = new Sequence(
+          "Seq6",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq7 = new Sequence(
+          "Seq7",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq8 = new Sequence(
+          "Seq8",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq9 = new Sequence(
+          "Seq9",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq10 = new Sequence(
+          "Seq10",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq11 = new Sequence(
+          "Seq11",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq12 = new Sequence(
+          "Seq12",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq13 = new Sequence(
+          "Seq13",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq14 = new Sequence(
+          "Seq14",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq15 = new Sequence(
+          "Seq15",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq16 = new Sequence(
+          "Seq16",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq17 = new Sequence(
+          "Seq17",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq18 = new Sequence(
+          "Seq18",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq19 = new Sequence(
+          "Seq19",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq20 = new Sequence(
+          "Seq20",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq21 = new Sequence(
+          "Seq21",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq22 = new Sequence(
+          "Seq22",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  SequenceI seq23 = new Sequence(
+          "Seq23",
+          "ABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBACABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+  AlignFrame af;
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp()
+  {
+    Jalview.main(new String[] { "-nonews", "-props",
+        "test/jalview/testProps.jvprops" });
+
+    Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+            Boolean.TRUE.toString());
+    af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
+            DataSourceType.FILE);
+
+    /*
+     * wait for Consensus thread to complete
+     */
+    synchronized (this)
+    {
+      while (af.getViewport().getConsensusSeq() == null)
+      {
+        try
+        {
+          wait(50);
+        } catch (InterruptedException e)
+        {
+        }
+      }
+    }
+  }
+
+
+  /**
+   * Test side effect that end residue is set correctly by setScrollValues, with
+   * or without hidden columns
+   */
+  @Test(groups = "Functional")
+  public void TestSetScrollValues()
+  {
+    ViewportRanges ranges = af.getViewport().getRanges();
+    af.alignPanel.setScrollValues(0, 0);
+
+    int oldres = ranges.getEndRes();
+    af.alignPanel.setScrollValues(-1, 5);
+
+    // setting -ve x value does not change residue
+    assertEquals(ranges.getEndRes(), oldres);
+
+    af.alignPanel.setScrollValues(0, 5);
+
+    // setting 0 as x value does not change residue
+    assertEquals(ranges.getEndRes(), oldres);
+
+    af.alignPanel.setScrollValues(5, 5);
+    // setting x value to 5 extends endRes by 5 residues
+    assertEquals(ranges.getEndRes(), oldres + 5);
+
+    // scroll to position after hidden columns sets endres to oldres (width) +
+    // position
+    int scrollpos = 60;
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), oldres + scrollpos);
+
+    // scroll to position within hidden columns, still sets endres to oldres +
+    // position
+    // not sure if this is actually correct behaviour but this is what Jalview
+    // currently does
+    scrollpos = 40;
+    af.getViewport().showAllHiddenColumns();
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), oldres + scrollpos);
+
+    // scroll to position within <width> distance of the end of the alignment
+    // endRes should be set to width of alignment - 1
+    scrollpos = 130;
+    af.getViewport().showAllHiddenColumns();
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), af.getViewport()
+            .getAlignment().getWidth() - 1);
+
+    // now hide some columns, and scroll to position within <width>
+    // distance of the end of the alignment
+    // endRes should be set to width of alignment - 1 - the number of hidden
+    // columns
+    af.getViewport().hideColumns(30, 50);
+    af.alignPanel.setScrollValues(scrollpos, 5);
+    assertEquals(ranges.getEndRes(), af.getViewport()
+            .getAlignment().getWidth() - 1 - 21); // 21 is the number of hidden
+                                                  // columns
+  }
+}
diff --git a/test/jalview/gui/AnnotationRowFilterTest.java b/test/jalview/gui/AnnotationRowFilterTest.java
new file mode 100644 (file)
index 0000000..69a41c5
--- /dev/null
@@ -0,0 +1,121 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.util.Vector;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for methods of base class of annotation column or colour chooser
+ */
+public class AnnotationRowFilterTest
+{
+  AlignFrame af;
+
+  private AnnotationRowFilter testee;
+
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty(
+            Preferences.SHOW_AUTOCALC_ABOVE, Boolean.TRUE.toString());
+    af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
+            DataSourceType.FILE);
+    testee = new AnnotationRowFilter(af.viewport, af.alignPanel)
+    {
+      @Override
+      public void valueChanged(boolean updateAllAnnotation)
+      {
+      }
+
+      @Override
+      public void updateView()
+      {
+      }
+
+      @Override
+      public void reset()
+      {
+      }
+    };
+  }
+
+  /**
+   * Test the method that builds the drop-down list of annotations to choose
+   * from for colour by annotation or select columns by annotation
+   */
+  @Test(groups = "Functional")
+  public void testGetAnnotationItems()
+  {
+    AlignmentI al = af.getViewport().getAlignment();
+    SequenceI seq1 = al.findSequenceMatch("FER_CAPAA")[0];
+    SequenceI seq2 = al.findSequenceMatch("FER_BRANA")[0];
+
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("ann1Label", "ann1",
+            null);
+    al.addAnnotation(ann1);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("Significance",
+            "ann2", null);
+    al.addAnnotation(ann2);
+    /*
+     * a second Significance alignment annotation
+     */
+    AlignmentAnnotation ann2a = new AlignmentAnnotation("Significance",
+            "ann2", null);
+    al.addAnnotation(ann2a);
+
+    AlignmentAnnotation ann3 = new AlignmentAnnotation("Jronn", "Jronn",
+            null);
+    ann3.setSequenceRef(seq1);
+    al.addAnnotation(ann3);
+    AlignmentAnnotation ann4 = new AlignmentAnnotation("Jronn", "Jronn",
+            null);
+    ann4.setSequenceRef(seq2);
+    al.addAnnotation(ann4);
+    AlignmentAnnotation ann5 = new AlignmentAnnotation("Jnet", "Jnet", null);
+    ann5.setSequenceRef(seq2);
+    al.addAnnotation(ann5);
+    /*
+     * a second Jnet annotation for FER_BRANA
+     */
+    AlignmentAnnotation ann6 = new AlignmentAnnotation("Jnet", "Jnet", null);
+    ann6.setSequenceRef(seq2);
+    al.addAnnotation(ann6);
+
+    /*
+     * drop-down items with 'Per-sequence only' not checked
+     */
+    Vector<String> items = testee.getAnnotationItems(false);
+    assertEquals(
+            items.toString(),
+            "[Conservation, Quality, Consensus, Occupancy, ann1Label, Significance, Significance_1, Jronn_FER_CAPAA, Jronn_FER_BRANA, Jnet_FER_BRANA, Jnet_FER_BRANA_2]");
+    assertEquals(testee.getAnnotationMenuLabel(ann1), "ann1Label");
+    assertEquals(testee.getAnnotationMenuLabel(ann2), "Significance");
+    assertEquals(testee.getAnnotationMenuLabel(ann2a), "Significance_1");
+    assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn_FER_CAPAA");
+    assertEquals(testee.getAnnotationMenuLabel(ann4), "Jronn_FER_BRANA");
+    assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet_FER_BRANA");
+    assertEquals(testee.getAnnotationMenuLabel(ann6), "Jnet_FER_BRANA_2");
+
+    /*
+     * drop-down items with 'Per-sequence only' checked
+     */
+    items = testee.getAnnotationItems(true);
+    assertEquals(items.toString(), "[Jronn, Jnet]");
+    // the first annotation of the type is associated with the menu item
+    assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn");
+    assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet");
+  }
+}
index 81289b0..29a9a52 100644 (file)
@@ -53,10 +53,10 @@ public class SequenceRendererTest
     av.setGlobalColourScheme(new ZappoColourScheme());
 
     // @see ResidueProperties.zappo
-    assertEquals(Color.pink, sr.getResidueColour(seq, 0, null)); // M
-    assertEquals(Color.green, sr.getResidueColour(seq, 2, null)); // T
-    assertEquals(Color.magenta, sr.getResidueColour(seq, 5, null)); // G
-    assertEquals(Color.orange, sr.getResidueColour(seq, 12, null)); // F
+    assertEquals(Color.pink, sr.getResidueBoxColour(seq, 0)); // M
+    assertEquals(Color.green, sr.getResidueBoxColour(seq, 2)); // T
+    assertEquals(Color.magenta, sr.getResidueBoxColour(seq, 5)); // G
+    assertEquals(Color.orange, sr.getResidueBoxColour(seq, 12)); // F
   }
   // TODO more tests for getResidueBoxColour covering groups, feature rendering,
   // gaps, overview...
index a8611cc..4e4abe9 100644 (file)
@@ -36,6 +36,7 @@ import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueColourScheme;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -90,6 +91,8 @@ public class JSONFileTest
 
   private JSONFile jf;
 
+  private AlignExportSettingI exportSettings;
+
   @BeforeTest(alwaysRun = true)
   public void setup() throws Exception
   {
@@ -193,7 +196,7 @@ public class JSONFileTest
     TEST_ANOT_HEIGHT = expectedAnnots.size();
     TEST_CS_HEIGHT = expectedColSel.getHiddenColumns().size();
 
-    AlignExportSettingI exportSettings = new AlignExportSettingI()
+    exportSettings = new AlignExportSettingI()
     {
       @Override
       public boolean isExportHiddenSequences()
@@ -338,6 +341,50 @@ public class JSONFileTest
   }
 
   @Test(groups = { "Functional" })
+  /**
+   * Test for bug JAL-2489, NPE when exporting BioJSON with global colour scheme set as 'None'
+   */
+  public void testBioJSONRoundTripWithGlobalColourSchemeSetAsNone()
+  {
+    AppletFormatAdapter formatAdapter = new AppletFormatAdapter();
+
+    Alignment _alignment;
+    try
+    {
+      // load example BioJSON file
+      _alignment = (Alignment) formatAdapter.readFile(TEST_JSON_FILE,
+              DataSourceType.FILE, FileFormat.Json);
+      JSONFile bioJsonFile = (JSONFile) formatAdapter.getAlignFile();
+      AlignFrame alignFrame = new AlignFrame(_alignment,
+              bioJsonFile.getHiddenSequences(),
+              bioJsonFile.getColumnSelection(), AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT);
+      // Change colour scheme to 'None' and perform round trip
+      ColourSchemeI cs = ColourSchemeMapper.getJalviewColourScheme(
+              ResidueColourScheme.NONE, _alignment);
+      alignFrame.changeColour(cs);
+      alignFrame.getViewport().setFeaturesDisplayed(
+              bioJsonFile.getDisplayedFeatures());
+      formatAdapter = new AppletFormatAdapter(alignFrame.alignPanel,
+              exportSettings);
+      // export BioJSON string
+      String jsonOutput = formatAdapter.formatSequences(FileFormat.Json,
+              alignFrame.alignPanel.getAlignment(), false);
+      // read back Alignment from BioJSON string
+      formatAdapter = new AppletFormatAdapter();
+      formatAdapter.readFile(jsonOutput, DataSourceType.PASTE,
+              FileFormat.Json);
+      // assert 'None' colour scheme is retained after round trip
+      JSONFile _bioJsonFile = (JSONFile) formatAdapter.getAlignFile();
+      Assert.assertEquals(_bioJsonFile.getGlobalColourScheme(),
+              ResidueColourScheme.NONE);
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+    }
+  }
+
+  @Test(groups = { "Functional" })
   public void isShowSeqFeaturesSet()
   {
     Assert.assertTrue(testJsonFile.isShowSeqFeatures(),
diff --git a/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java b/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
new file mode 100644 (file)
index 0000000..59566ed
--- /dev/null
@@ -0,0 +1,448 @@
+package jalview.renderer.seqfeatures;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.FeatureRenderer;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.schemes.FeatureColour;
+
+import java.awt.Color;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for feature colour determination, including but not limited to
+ * <ul>
+ * <li>gap position</li>
+ * <li>no features present</li>
+ * <li>features present but show features turned off</li>
+ * <li>features displayed but selected feature turned off</li>
+ * <li>features displayed but feature group turned off</li>
+ * <li>feature displayed but none at the specified position</li>
+ * <li>multiple features at position, with no transparency</li>
+ * <li>multiple features at position, with transparency</li>
+ * <li>score graduated feature colour</li>
+ * <li>contact feature start at the selected position</li>
+ * <li>contact feature end at the selected position</li>
+ * <li>contact feature straddling the selected position (not shown)</li>
+ * </ul>
+ */
+public class FeatureColourFinderTest
+{
+  private AlignViewport av;
+
+  private SequenceI seq;
+
+  private FeatureColourFinder finder;
+
+  private AlignFrame af;
+
+  private FeatureRenderer fr;
+
+  @BeforeTest(alwaysRun = true)
+  public void setUp()
+  {
+    // aligned column 8 is sequence position 6
+    String s = ">s1\nABCDE---FGHIJKLMNOPQRSTUVWXYZ\n";
+    af = new FileLoader().LoadFileWaitTillLoaded(s,
+            DataSourceType.PASTE);
+    av = af.getViewport();
+    seq = av.getAlignment().getSequenceAt(0);
+    fr = af.getFeatureRenderer();
+    finder = new FeatureColourFinder(fr);
+  }
+
+  /**
+   * Clear down any sequence features before each test (not as easy as it
+   * sounds...)
+   */
+  @BeforeMethod(alwaysRun = true)
+  public void setUpBeforeTest()
+  {
+    SequenceFeature[] sfs = seq.getSequenceFeatures();
+    if (sfs != null)
+    {
+      for (SequenceFeature sf : sfs)
+      {
+        seq.deleteFeature(sf);
+      }
+    }
+    fr.findAllFeatures(true);
+
+    /*
+     * reset all feature groups to visible
+     */
+    for (String group : fr.getGroups(false))
+    {
+      fr.setGroupVisibility(group, true);
+    }
+
+    fr.clearRenderOrder();
+    av.setShowSequenceFeatures(true);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_noFeatures()
+  {
+    av.setShowSequenceFeatures(false);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    av.setShowSequenceFeatures(true);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_noFeaturesShown()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(false);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_singleFeatureAtPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_gapPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, 0f,
+            null));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(null, seq, 6);
+    assertEquals(c, Color.white);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_multipleFeaturesAtPositionNoTransparency()
+  {
+    /*
+     * featuresAdded -> FeatureRendererModel.updateRenderOrder which adds any
+     * new features 'on top' (but reverses the order of any added features)
+     */
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
+            Float.NaN, "DomainGroup"));
+    FeatureColour green = new FeatureColour(Color.green);
+    fr.setColour("Domain", green);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * expect Domain (green) to be rendered above Metal (red)
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.green);
+
+    /*
+     * now promote Metal above Domain
+     * - currently no way other than mimicking reordering of
+     * table in Feature Settings
+     */
+    Object[][] data = new Object[2][];
+    data[0] = new Object[] { "Metal", red, true };
+    data[1] = new Object[] { "Domain", green, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * ..and turn off display of Metal
+     */
+    data[0][2] = false;
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.green);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_singleFeatureNotAtPosition()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 8, 12,
+            Float.NaN, "MetalGroup"));
+    fr.setColour("Metal", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    // column 2 = sequence position 3
+    Color c = finder.findFeatureColour(Color.blue, seq, 2);
+    assertEquals(c, Color.blue);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_featureTypeNotDisplayed()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * turn off display of Metal - is this the easiest way to do it??
+     */
+    Object[][] data = new Object[1][];
+    data[0] = new Object[] { "Metal", red, false };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    /*
+     * turn display of Metal back on
+     */
+    data[0] = new Object[] { "Metal", red, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_featureGroupNotDisplayed()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+
+    /*
+     * turn off display of MetalGroup
+     */
+    fr.setGroupVisibility("MetalGroup", false);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+
+    /*
+     * turn display of MetalGroup back on
+     */
+    fr.setGroupVisibility("MetalGroup", true);
+    c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_contactFeature()
+  {
+    /*
+     * currently contact feature == type "Disulphide Bond" or "Disulfide Bond" !!
+     */
+    seq.addSequenceFeature(new SequenceFeature("Disulphide Bond",
+            "Contact", 2, 12, Float.NaN, "Disulphide"));
+    fr.setColour("Disulphide Bond", new FeatureColour(Color.red));
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * Contact positions are residues 2 and 12
+     * which are columns 1 and 14
+     * positions in between don't count for a contact feature!
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 10);
+    assertEquals(c, Color.blue);
+    c = finder.findFeatureColour(Color.blue, seq, 8);
+    assertEquals(c, Color.blue);
+    c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, Color.red);
+    c = finder.findFeatureColour(Color.blue, seq, 14);
+    assertEquals(c, Color.red);
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_graduatedFeatureColour()
+  {
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2,
+            2, 0f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4,
+            4, 5f, "KdGroup"));
+    seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7,
+            7, 10f, "KdGroup"));
+
+    /*
+     * graduated colour from 0 to 10
+     */
+    Color min = new Color(100, 50, 150);
+    Color max = new Color(200, 0, 100);
+    FeatureColourI fc = new FeatureColour(min, max, 0, 10);
+    fr.setColour("kd", fc);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+
+    /*
+     * position 2, column 1, score 0 - minimum colour in range
+     */
+    Color c = finder.findFeatureColour(Color.blue, seq, 1);
+    assertEquals(c, min);
+
+    /*
+     * position 7, column 9, score 10 - maximum colour in range
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 9);
+    assertEquals(c, max);
+
+    /*
+     * position 4, column 3, score 5 - half way from min to max
+     */
+    c = finder.findFeatureColour(Color.blue, seq, 3);
+    assertEquals(c, new Color(150, 25, 125));
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_transparencySingleFeature()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+  
+    /*
+     * the FeatureSettings transparency slider has range 0-70 which
+     * corresponds to a transparency value of 1 - 0.3
+     * A value of 0.4 gives a combination of
+     * 0.4 * red(255, 0, 0) + 0.6 * cyan(0, 255, 255) = (102, 153, 153)
+     */
+    fr.setTransparency(0.4f);
+    Color c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(102, 153, 153));
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatureColour_transparencyTwoFeatures()
+  {
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
+            Float.NaN, "DomainGroup"));
+    FeatureColour green = new FeatureColour(Color.green);
+    fr.setColour("Domain", green);
+    fr.featuresAdded();
+    av.setShowSequenceFeatures(true);
+  
+    /*
+     * Domain (green) rendered above Metal (red) above background (cyan)
+     * 1) 0.6 * red(255, 0, 0) + 0.4 * cyan(0, 255, 255) = (153, 102, 102)
+     * 2) 0.6* green(0, 255, 0) + 0.4 * (153, 102, 102) = (61, 194, 41) rounded
+     */
+    fr.setTransparency(0.6f);
+    Color c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(61, 194, 41));
+  
+    /*
+     * now promote Metal above Domain
+     * - currently no way other than mimicking reordering of
+     * table in Feature Settings
+     * Metal (red) rendered above Domain (green) above background (cyan)
+     * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102)
+     * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded
+     */
+    Object[][] data = new Object[2][];
+    data[0] = new Object[] { "Metal", red, true };
+    data[1] = new Object[] { "Domain", green, true };
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.cyan, seq, 10);
+    assertEquals(c, new Color(153, 102, 41));
+  
+    /*
+     * ..and turn off display of Metal
+     * Domain (green) above background (pink)
+     * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70)
+     */
+    data[0][2] = false;
+    fr.setFeaturePriority(data);
+    c = finder.findFeatureColour(Color.pink, seq, 10);
+    assertEquals(c, new Color(102, 223, 70));
+  }
+
+  @Test(groups = "Functional")
+  public void testNoFeaturesDisplayed()
+  {
+    /*
+     * no features on alignment to render
+     */
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * add a feature
+     * it will be automatically set visible but we leave
+     * the viewport configured not to show features
+     */
+    av.setShowSequenceFeatures(false);
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
+            Float.NaN, "MetalGroup"));
+    FeatureColour red = new FeatureColour(Color.red);
+    fr.setColour("Metal", red);
+    fr.featuresAdded();
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * turn on feature display
+     */
+    av.setShowSequenceFeatures(true);
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * turn off display of Metal
+     */
+    Object[][] data = new Object[1][];
+    data[0] = new Object[] { "Metal", red, false };
+    fr.setFeaturePriority(data);
+    assertTrue(finder.noFeaturesDisplayed());
+
+    /*
+     * turn display of Metal back on
+     */
+    fr.setVisible("Metal");
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * turn off MetalGroup - has no effect here since the group of a
+     * sequence feature instance is independent of its type
+     */
+    fr.setGroupVisibility("MetalGroup", false);
+    assertFalse(finder.noFeaturesDisplayed());
+
+    /*
+     * a finder with no feature renderer
+     */
+    FeatureColourFinder finder2 = new FeatureColourFinder(null);
+    assertTrue(finder2.noFeaturesDisplayed());
+  }
+}
diff --git a/test/jalview/schemes/AnnotationColourGradientTest.java b/test/jalview/schemes/AnnotationColourGradientTest.java
new file mode 100644 (file)
index 0000000..1c93856
--- /dev/null
@@ -0,0 +1,300 @@
+package jalview.schemes;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.awt.Color;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AnnotationColourGradientTest
+{
+  final static int WIDTH = 11;
+
+  final static int THRESHOLD_FIVE = 5;
+
+  private AlignmentAnnotation ann;
+
+  private SequenceI seq;
+
+  private AlignmentI al;
+
+  Color minColour = new Color(50, 200, 150);
+
+  Color maxColour = new Color(150, 100, 250);
+
+  /**
+   * Setup creates an annotation over 11 columns with values 0-10 and threshold
+   * 5
+   */
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Annotation[] anns = new Annotation[WIDTH];
+    /*
+     * set annotations with values 0-10, graded colours
+     */
+    for (int col = 0; col < WIDTH; col++)
+    {
+      int hue = col * 20;
+      Color colour = new Color(hue, hue, hue);
+      anns[col] = new Annotation("a", "a", 'a', col, colour);
+    }
+
+    seq = new Sequence("", "");
+    al = new Alignment(new SequenceI[]{ seq});
+    
+    /*
+     * AlignmentAnnotation constructor works out min-max range
+     */
+    ann = new AlignmentAnnotation("", "", anns);
+    ann.setThreshold(new GraphLine(THRESHOLD_FIVE, "", Color.RED));
+    seq.addAlignmentAnnotation(ann);
+  }
+
+  @Test(groups = "Functional")
+  public void testShadeCalculation_noThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * column <n> is n/10 of the way from minCol to maxCol
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour above threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testShadeCalculation_aboveThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * colour is derived regardless of the threshold value 
+       * (the renderer will suppress colouring if above/below threshold)
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    /*
+     * now make 6-10 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < THRESHOLD_FIVE; col++)
+    {
+      /*
+       * colours below the threshold are computed as before
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+    for (int col = THRESHOLD_FIVE; col < WIDTH; col++)
+    {
+      /*
+       * colours for values >= threshold are graduated
+       * range is 6-10 so steps of 100/5 = 20
+       */
+      int factor = col - THRESHOLD_FIVE;
+      Color expected = new Color(50 + 20 * factor, 200 - 20 * factor,
+              150 + 20 * factor);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour below threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testShadeCalculation_belowThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.shadeCalculation(ann, col);
+      /*
+       * colour is derived regardless of the threshold value 
+       * (the renderer will suppress colouring if above/below threshold)
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    /*
+     * now make 0-5 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = THRESHOLD_FIVE + 1; col < WIDTH; col++)
+    {
+      /*
+       * colours above the threshold are computed as before
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+
+    for (int col = 0; col <= THRESHOLD_FIVE; col++)
+    {
+      /*
+       * colours for values <= threshold are graduated
+       * range is 0-5 so steps of 100/5 = 20
+       */
+      Color expected = new Color(50 + 20 * col, 200 - 20 * col,
+              150 + 20 * col);
+      Color result = testee.shadeCalculation(ann, col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour above threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testFindColour_aboveThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.ABOVE_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      /*
+       * expect white below threshold of 5
+       */
+      Color expected = col < 5 ? Color.white : new Color(50 + 10 * col,
+              200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  
+    /*
+     * now make 6-10 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      /*
+       * colours for values >= threshold are graduated
+       * range is 6-10 so steps of 100/5 = 20
+       */
+      int factor = col - THRESHOLD_FIVE;
+      Color expected = col < 5 ? Color.white : new Color(50 + 20 * factor,
+              200 - 20 * factor,
+              150 + 20 * factor);
+      Color result = testee.findColour('a', col, seq);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  /**
+   * Test the 'colour below threshold' case
+   */
+  @Test(groups = "Functional")
+  public void testFindColour_belowThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.BELOW_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+  
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      Color expected = col > 5 ? Color.white : new Color(50 + 10 * col,
+              200 - 10 * col, 150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  
+    /*
+     * now make 0-5 the span of the colour range
+     * (annotation value == column number in this test)
+     */
+    testee.setThresholdIsMinMax(true);
+    for (int col = 0; col < WIDTH; col++)
+    {
+      /*
+       * colours for values <= threshold are graduated
+       * range is 0-5 so steps of 100/5 = 20
+       */
+      Color expected = col > 5 ? Color.white : new Color(50 + 20 * col,
+              200 - 20 * col, 150 + 20 * col);
+      Color result = testee.findColour('a', col, seq);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testFindColour_noThreshold()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    for (int col = 0; col < WIDTH; col++)
+    {
+      Color result = testee.findColour('a', col, seq);
+      /*
+       * column <n> is n/10 of the way from minCol to maxCol
+       */
+      Color expected = new Color(50 + 10 * col, 200 - 10 * col,
+              150 + 10 * col);
+      assertEquals(result, expected, "for column " + col);
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testFindColour_originalColours()
+  {
+    AnnotationColourGradient testee = new AnnotationColourGradient(ann,
+            minColour, maxColour, AnnotationColourGradient.NO_THRESHOLD);
+    testee = (AnnotationColourGradient) testee.getInstance(al, null);
+
+    /*
+     * flag corresponding to 'use original colours' checkbox
+     * - just use the individual annotation colours
+     */
+    testee.setPredefinedColours(true);
+
+    /*
+     * the annotation colour is returned, except for column 0 where it is
+     * black - in this case the colour scheme colour overrides it
+     */
+    for (int col = 0; col < WIDTH; col++)
+    {
+      int hue = col * 20;
+      Color c = col == 0 ? minColour : new Color(hue, hue, hue);
+      assertEquals(testee.findColour('a', col, seq), c, "for column " + col);
+    }
+  }
+}
index 4618ed7..0aaa38c 100644 (file)
@@ -228,9 +228,9 @@ public class ColourSchemesTest
      * set and check Taylor colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Taylor.toString());
-    Color taylor1 = sr.getResidueBoxColour(seq, 88); // E 255,0,102
-    Color taylor2 = sr.getResidueBoxColour(seq, 89); // A 204,255,0
-    Color taylor3 = sr.getResidueBoxColour(seq, 90); // G 255,153,0
+    Color taylor1 = sr.getResidueColour(seq, 88, null); // E 255,0,102
+    Color taylor2 = sr.getResidueColour(seq, 89, null); // A 204,255,0
+    Color taylor3 = sr.getResidueColour(seq, 90, null); // G 255,153,0
     assertEquals(taylor1, new Color(255, 0, 102));
     assertEquals(taylor2, new Color(204, 255, 0));
     assertEquals(taylor3, new Color(255, 153, 0));
@@ -239,9 +239,9 @@ public class ColourSchemesTest
      * set and check Zappo colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Zappo.toString());
-    Color zappo1 = sr.getResidueBoxColour(seq, 88); // E red
-    Color zappo2 = sr.getResidueBoxColour(seq, 89); // A pink
-    Color zappo3 = sr.getResidueBoxColour(seq, 90); // G magenta
+    Color zappo1 = sr.getResidueColour(seq, 88, null); // E red
+    Color zappo2 = sr.getResidueColour(seq, 89, null); // A pink
+    Color zappo3 = sr.getResidueColour(seq, 90, null); // G magenta
     assertEquals(zappo1, Color.red);
     assertEquals(zappo2, Color.pink);
     assertEquals(zappo3, Color.magenta);
@@ -250,9 +250,9 @@ public class ColourSchemesTest
      * set 'stripy' colours - odd columns are Taylor and even are Zappo 
      */
     af.changeColour_actionPerformed("stripy");
-    Color stripy1 = sr.getResidueBoxColour(seq, 88);
-    Color stripy2 = sr.getResidueBoxColour(seq, 89);
-    Color stripy3 = sr.getResidueBoxColour(seq, 90);
+    Color stripy1 = sr.getResidueColour(seq, 88, null);
+    Color stripy2 = sr.getResidueColour(seq, 89, null);
+    Color stripy3 = sr.getResidueColour(seq, 90, null);
     assertEquals(stripy1, zappo1);
     assertEquals(stripy2, taylor2);
     assertEquals(stripy3, zappo3);
@@ -261,9 +261,9 @@ public class ColourSchemesTest
      * set and check Clustal colours
      */
     af.changeColour_actionPerformed(JalviewColourScheme.Clustal.toString());
-    Color clustal1 = sr.getResidueBoxColour(seq, 88);
-    Color clustal2 = sr.getResidueBoxColour(seq, 89);
-    Color clustal3 = sr.getResidueBoxColour(seq, 90);
+    Color clustal1 = sr.getResidueColour(seq, 88, null);
+    Color clustal2 = sr.getResidueColour(seq, 89, null);
+    Color clustal3 = sr.getResidueColour(seq, 90, null);
     assertEquals(clustal1, ClustalColour.MAGENTA.colour);
     assertEquals(clustal2, ClustalColour.BLUE.colour);
     assertEquals(clustal3, ClustalColour.ORANGE.colour);
@@ -272,9 +272,9 @@ public class ColourSchemesTest
      * set 'MyClustal' colours - uses AWT colour equivalents
      */
     af.changeColour_actionPerformed("MyClustal");
-    Color myclustal1 = sr.getResidueBoxColour(seq, 88);
-    Color myclustal2 = sr.getResidueBoxColour(seq, 89);
-    Color myclustal3 = sr.getResidueBoxColour(seq, 90);
+    Color myclustal1 = sr.getResidueColour(seq, 88, null);
+    Color myclustal2 = sr.getResidueColour(seq, 89, null);
+    Color myclustal3 = sr.getResidueColour(seq, 90, null);
     assertEquals(myclustal1, Color.MAGENTA);
     assertEquals(myclustal2, Color.BLUE);
     assertEquals(myclustal3, Color.ORANGE);
index 0422537..7ba22b4 100644 (file)
@@ -44,6 +44,7 @@ import jalview.structures.models.AAStructureBindingModel.SuperposeData;
 
 import java.awt.Color;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -169,9 +170,10 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      public void superposeStructures(AlignmentI[] als, int[] alm,
+      public String superposeStructures(AlignmentI[] als, int[] alm,
               ColumnSelection[] alc)
       {
+        return null;
       }
 
       @Override
@@ -181,14 +183,7 @@ public class AAStructureBindingModelTest
 
       @Override
       protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, FeatureRenderer fr,
-              AlignmentI alignment)
-      {
-        return null;
-      }
-
-      @Override
-      public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
+              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
       {
         return null;
       }
@@ -215,6 +210,13 @@ public class AAStructureBindingModelTest
       public void colourByCharge()
       {
       }
+
+      @Override
+      public FeatureRenderer getFeatureRenderer(
+              AlignmentViewPanel alignment)
+      {
+        return null;
+      }
     };
   }
 
@@ -234,11 +236,14 @@ public class AAStructureBindingModelTest
       structs[i] = testee.new SuperposeData(al.getWidth());
     }
     /*
-     * initialise array of 'superposable columns' to true (would be false for
+     * initialise BitSet of 'superposable columns' to true (would be false for
      * hidden columns)
      */
-    boolean[] matched = new boolean[al.getWidth()];
-    Arrays.fill(matched, true);
+    BitSet matched = new BitSet();
+    for (int i = 0; i < al.getWidth(); i++)
+    {
+      matched.set(i);
+    }
 
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
@@ -248,12 +253,12 @@ public class AAStructureBindingModelTest
     /*
      * only ungapped, structure-mapped columns are superposable
      */
-    assertFalse(matched[0]); // gap in first sequence
-    assertTrue(matched[1]);
-    assertFalse(matched[2]); // gap in third sequence
-    assertFalse(matched[3]); // gap in fourth sequence
-    assertTrue(matched[4]);
-    assertTrue(matched[5]); // gap in second sequence
+    assertFalse(matched.get(0)); // gap in first sequence
+    assertTrue(matched.get(1));
+    assertFalse(matched.get(2)); // gap in third sequence
+    assertFalse(matched.get(3)); // gap in fourth sequence
+    assertTrue(matched.get(4));
+    assertTrue(matched.get(5)); // gap in second sequence
 
     assertEquals("1YCS", structs[0].pdbId);
     assertEquals("3A6S", structs[1].pdbId);
@@ -278,13 +283,17 @@ public class AAStructureBindingModelTest
       structs[i] = testee.new SuperposeData(al.getWidth());
     }
     /*
-     * initialise array of 'superposable columns' to true (would be false for
+     * initialise BitSet of 'superposable columns' to true (would be false for
      * hidden columns)
      */
-    boolean[] matched = new boolean[al.getWidth()];
-    Arrays.fill(matched, true);
+    BitSet matched = new BitSet();
+    for (int i = 0; i < al.getWidth(); i++)
+    {
+      matched.set(i);
+    }
+
     // treat column 5 of the alignment as hidden
-    matched[4] = false;
+    matched.clear(4);
 
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
@@ -292,21 +301,11 @@ public class AAStructureBindingModelTest
     assertEquals(0, refStructure);
 
     // only ungapped, structure-mapped columns are not superposable
-    assertFalse(matched[0]);
-    assertTrue(matched[1]);
-    assertFalse(matched[2]);
-    assertFalse(matched[3]);
-    assertFalse(matched[4]); // superposable, but hidden, column
-    assertTrue(matched[5]);
-  }
-
-  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    return null;
-  }
-
-  public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
-  {
-    return null;
+    assertFalse(matched.get(0));
+    assertTrue(matched.get(1));
+    assertFalse(matched.get(2));
+    assertFalse(matched.get(3));
+    assertFalse(matched.get(4)); // superposable, but hidden, column
+    assertTrue(matched.get(5));
   }
 }
diff --git a/test/jalview/viewmodel/OverviewDimensionsTest.java b/test/jalview/viewmodel/OverviewDimensionsTest.java
new file mode 100644 (file)
index 0000000..398fec3
--- /dev/null
@@ -0,0 +1,1037 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.viewmodel;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceCollectionI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+
+import java.util.Hashtable;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class OverviewDimensionsTest
+{
+  AlignmentI al;
+  OverviewDimensions od;
+
+  // cached widths and heights
+  int boxWidth;
+  int boxHeight;
+  int viewHeight;
+  int viewWidth;
+  int alheight;
+  int alwidth;
+
+  ViewportRanges vpranges;
+
+  Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+
+  ColumnSelection hiddenCols = new ColumnSelection();
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    al = gen.generate(157, 525, 123, 5, 5);
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp()
+  {
+    if (!hiddenRepSequences.isEmpty())
+    {
+      al.getHiddenSequences().showAll(hiddenRepSequences);
+    }
+    hiddenCols.revealAllHiddenColumns();
+    
+    vpranges = new ViewportRanges(al);
+    vpranges.setStartRes(0);
+    vpranges.setEndRes(62);
+    vpranges.setStartSeq(0);
+    vpranges.setEndSeq(17);
+
+    viewHeight = vpranges.getEndSeq() - vpranges.getStartSeq() + 1;
+    viewWidth = vpranges.getEndRes() - vpranges.getStartRes() + 1;
+
+    ColumnSelection hiddenCols = new ColumnSelection();
+
+    od = new OverviewDimensions(vpranges, true);
+    // Initial box sizing - default path through code
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+
+    mouseClick(od, 0, 0);
+    moveViewport(0, 0);
+
+    // calculate before hidden columns so we get absolute values
+    alheight = vpranges.getAbsoluteAlignmentHeight();
+    alwidth = vpranges.getAbsoluteAlignmentWidth();
+
+    boxWidth = Math.round((float) (vpranges.getEndRes()
+            - vpranges.getStartRes() + 1)
+            * od.getWidth() / alwidth);
+    boxHeight = Math.round((float) (vpranges.getEndSeq()
+            - vpranges.getStartSeq() + 1)
+            * od.getSequencesHeight() / alheight);
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void cleanUp()
+  {
+    al = null;
+  }
+
+  /**
+   * Test that the OverviewDimensions constructor sets width and height
+   * correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testConstructor()
+  {
+    SequenceI seqa = new Sequence("Seq1", "ABC");
+    SequenceI seqb = new Sequence("Seq2", "ABC");
+    SequenceI seqc = new Sequence("Seq3", "ABC");
+    SequenceI seqd = new Sequence("Seq4", "ABC");
+    SequenceI seqe = new Sequence("Seq5",
+            "ABCABCABCABCABCABCABCABCBACBACBACBAC");
+
+    int defaultGraphHeight = 20;
+    int maxWidth = 400;
+    int minWidth = 120;
+    int maxSeqHeight = 300;
+    int minSeqHeight = 40;
+
+    // test for alignment with width > height
+    SequenceI[] seqs1 = new SequenceI[] { seqa, seqb };
+    Alignment al1 = new Alignment(seqs1);
+    ViewportRanges props = new ViewportRanges(al1);
+
+    OverviewDimensions od = new OverviewDimensions(props, true);
+    int scaledHeight = 267;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), scaledHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), scaledHeight + defaultGraphHeight);
+
+    // test for alignment with width < height
+    SequenceI[] seqs2 = new SequenceI[] { seqa, seqb, seqc, seqd };
+    Alignment al2 = new Alignment(seqs2);
+    props = new ViewportRanges(al2);
+
+    od = new OverviewDimensions(props, true);
+    int scaledWidth = 300;
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), scaledWidth);
+    assertEquals(od.getHeight(), scaledWidth + defaultGraphHeight);
+
+    // test for alignment with width > height and sequence height scaled below
+    // min value
+    SequenceI[] seqs3 = new SequenceI[] { seqe };
+    Alignment al3 = new Alignment(seqs3);
+    props = new ViewportRanges(al3);
+
+    od = new OverviewDimensions(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), minSeqHeight);
+    assertEquals(od.getWidth(), maxWidth);
+    assertEquals(od.getHeight(), minSeqHeight + defaultGraphHeight);
+
+    // test for alignment with width < height and width scaled below min value
+    SequenceI[] seqs4 = new SequenceI[] { seqa, seqb, seqc, seqd, seqa,
+        seqb, seqc, seqd, seqa, seqb, seqc, seqd, seqa, seqb, seqc, seqd };
+    Alignment al4 = new Alignment(seqs4);
+    props = new ViewportRanges(al4);
+
+    od = new OverviewDimensions(props, true);
+    assertEquals(od.getGraphHeight(), defaultGraphHeight);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight + defaultGraphHeight);
+
+    Alignment al5 = new Alignment(seqs4);
+    props = new ViewportRanges(al5);
+
+    od = new OverviewDimensions(props, false);
+    assertEquals(od.getGraphHeight(), 0);
+    assertEquals(od.getSequencesHeight(), maxSeqHeight);
+    assertEquals(od.getWidth(), minWidth);
+    assertEquals(od.getHeight(), maxSeqHeight);
+  }
+
+  /**
+   * Test that validation after mouse adjustments to boxX and boxY sets box
+   * dimensions and scroll values correctly, when there are no hidden rows or
+   * columns.
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromMouseClick()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // negative boxX value reset to 0
+    mouseClick(od, -5, 10);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollRow(),
+            Math.round((float) 10 * alheight / od.getSequencesHeight()));
+    assertEquals(od.getScrollCol(), 0);
+
+    // negative boxY value reset to 0
+    mouseClick(od, 6, -2);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) 6 * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+
+    // overly large boxX value reset to width-boxWidth
+    mouseClick(od, 100, 6);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 6);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // overly large boxY value reset to sequenceHeight - boxHeight
+    mouseClick(od, 10, 520);
+    assertEquals(od.getBoxX(), 10);
+    assertEquals(od.getBoxY(), od.getSequencesHeight() - od.getBoxHeight());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+
+    // here (float) od.getBoxY() * alheight / od.getSequencesHeight() = 507.5
+    // and round rounds to 508; however we get 507 working with row values
+    // hence the subtraction of 1
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()) - 1);
+
+    // click past end of alignment, as above
+    mouseClick(od, 3000, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // move viewport so startRes non-zero and then mouseclick
+    moveViewportH(50);
+
+    // click at viewport position
+    int oldboxx = od.getBoxX();
+    int oldboxy = od.getBoxY();
+    mouseClick(od, od.getBoxX() + 5, od.getBoxY() + 2);
+    assertEquals(od.getBoxX(), oldboxx + 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getBoxY(), oldboxy + 2);
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+    // click at top corner
+    mouseClick(od, 0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getScrollRow(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // hide cols at start and check updated box position is correct
+    // changes boxX but not boxwidth
+    int lastHiddenCol = 30;
+    hiddenCols.hideColumns(0, lastHiddenCol);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                    / alwidth));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // try to click in hidden cols, check box does not move
+    int xpos = 10;
+    mouseClick(od, xpos, 0);
+    assertEquals(
+            od.getBoxX(),
+            Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                    / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollRow(), 0);
+    assertEquals(od.getScrollCol(), 0);
+
+    // click to right of hidden columns, box moves to click point
+    testBoxIsAtClickPoint(40, 0);
+    assertEquals(od.getScrollRow(), 0);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) 40 * alwidth / od.getWidth())
+                    - (lastHiddenCol + 1));
+
+    // click to right of hidden columns such that box runs over right hand side
+    // of alignment
+    // box position is adjusted away from the edge
+    // overly large boxX value reset to width-boxWidth
+    xpos = 100;
+    mouseClick(od, xpos, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth())
+                    - (lastHiddenCol + 1));
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols in the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+    
+    // hide columns 63-73, no change to box position or dimensions
+    int firstHidden = 63;
+    int lastHidden = 73;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // move box so that it overlaps with hidden cols on one side
+    // box width changes, boxX and scrollCol as for unhidden case
+    int xpos = 55 - boxWidth; // 55 is position in overview approx halfway
+                              // between cols 60 and 70
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round(xpos * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+
+    // move box so that it completely covers hidden cols
+    // box width changes, boxX and scrollCol as for hidden case
+    xpos = 33;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+
+    // move box so boxX is in hidden cols, box overhangs at right
+    // boxX and scrollCol at left of hidden area, box width extends across
+    // hidden region
+    xpos = 50;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((float) (lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(), firstHidden - 1);
+    assertEquals(od.getScrollRow(), 0);
+
+    // move box so boxX is to right of hidden cols, but does not go beyond full
+    // width of alignment
+    // box width, boxX and scrollCol all as for non-hidden case
+    xpos = 75;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(od.getScrollRow(), 0);
+    assertEquals(od.getScrollCol(),
+            Math.round(xpos * alwidth / od.getWidth())
+                    - (lastHidden - firstHidden + 1));
+    
+    // move box so it goes beyond full width of alignment
+    // boxX, scrollCol adjusted back, box width normal
+    xpos = 3000;
+    mouseClick(od, xpos, 5);
+    assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+    assertEquals(od.getBoxY(), 5);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round(((float) od.getBoxX() * alwidth / od.getWidth())
+                    - (lastHidden - firstHidden + 1)));
+    assertEquals(od.getScrollRow(),
+            Math.round((float) od.getBoxY() * alheight
+                    / od.getSequencesHeight()));
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden cols at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenColsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // hide columns 140-164, no change to box position or dimensions
+    int firstHidden = 140;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // click to left of hidden cols, without overlapping
+    // boxX, scrollCol and width as normal
+    int xpos = 5;
+    testBoxIsAtClickPoint(xpos, 0);
+    assertEquals(od.getScrollRow(), 0);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) xpos * alwidth / od.getWidth()));
+
+    // click to left of hidden cols, with overlap
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = Math.round((float) 145 * od.getWidth() / alwidth) - boxWidth;
+    mouseClick(od, xpos, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+
+    // click in hidden cols
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = 115;
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+
+    // click off end of alignment
+    // boxX and scrollCol adjusted for hidden cols, width normal
+    xpos = 3000;
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                    - boxWidth + 1);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(),
+            Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+    assertEquals(od.getScrollRow(), 0);
+  }
+
+  /**
+   * Test that the box position is set correctly when set from the viewport,
+   * with no hidden rows or columns
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewport()
+  {
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right
+    moveViewportH(70);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport down
+    moveViewportV(100);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 70 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round(100 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to bottom right
+    moveViewport(98, 508);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 98 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(),
+            Math.round((float) 508 * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport to start of alignment
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (lastHidden + 1) * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment - need to make startRes by removing
+    // hidden cols because of how viewport/overview are implemented
+    moveViewport(98 - lastHidden - 1, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 98 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * in the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsInMiddle()
+  {
+    int firstHidden = 68;
+    int lastHidden = 78;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    System.out.println(od.getBoxWidth());
+    assertEquals(od.getBoxWidth(), boxWidth);
+    System.out.println(od.getBoxWidth());
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to left of hidden columns with overlap
+    moveViewport(10, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 10 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((float) (lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden columns
+    moveViewport(63, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 63 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(
+            od.getBoxWidth(),
+            boxWidth
+                    + Math.round((lastHidden - firstHidden + 1)
+                            * od.getWidth() / alwidth));
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to right of hidden columns, no overlap
+    moveViewport(80 - (lastHidden - firstHidden + 1), 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 80 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden columns
+   * at the end
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenColsAtEnd()
+  {
+    int firstHidden = 152;
+    int lastHidden = 164;
+    hiddenCols.hideColumns(firstHidden, lastHidden);
+
+    // move viewport before hidden columns
+    moveViewport(3, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) 3 * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to hidden columns
+    // viewport can't actually extend into hidden cols,
+    // so move to the far right edge of the viewport
+    moveViewport(firstHidden - viewWidth, 0);
+    assertEquals(od.getBoxX(),
+            Math.round((float) (firstHidden - viewWidth)
+                    * od.getWidth() / alwidth));
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the start
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtStart()
+  {
+    int firstHidden = 0;
+    int lastHidden = 20;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box moves to below hidden rows, height remains same
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) (lastHidden + 1) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    moveViewport(0, 525 - viewHeight - lastHidden - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (525 - viewHeight) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows in
+   * the middle
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsInMiddle()
+  {
+    int firstHidden = 200;
+    int lastHidden = 210;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to straddle hidden rows
+    moveViewport(0, 198);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), Math.round ((float)198 * od.getSequencesHeight()
+            / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            Math.round((float) (viewHeight + lastHidden - firstHidden + 1)
+                    * od.getSequencesHeight() / alheight));
+  }
+
+  /**
+   * Test that the box position is set correctly when there are hidden rows at
+   * the bottom
+   */
+  @Test(groups = { "Functional" })
+  public void testSetBoxFromViewportHiddenRowsAtEnd()
+  {
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    // move viewport to start of alignment:
+    // box, height etc as in non-hidden case
+    moveViewport(0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // move viewport to end of alignment
+    // viewport sits above hidden rows and does not include them
+    moveViewport(0, firstHidden - viewHeight - 1);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (firstHidden - viewHeight - 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the start
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtStart()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // hide rows at start and check updated box position is correct
+    // changes boxY but not boxheight
+    int lastHiddenRow = 30;
+    hideSequences(0, lastHiddenRow);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) (lastHiddenRow + 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click in hidden rows - same result
+    mouseClick(od, 0, 0);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (lastHiddenRow + 1)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click below hidden rows
+    mouseClick(od, 0, 150);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 150);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the middle
+   * of the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsInMiddle()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // hide rows in middle and check updated box position is correct
+    // no changes
+    int firstHiddenRow = 50;
+    int lastHiddenRow = 54;
+    hideSequences(firstHiddenRow, lastHiddenRow);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows, so that box overlaps
+    int ypos = 35; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            boxHeight
+                    + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                            * od.getSequencesHeight() / alheight));
+
+    // click so that box straddles hidden rows
+    ypos = 44; // column value in residues
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(
+            od.getBoxHeight(),
+            boxHeight
+                    + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                            * od.getSequencesHeight() / alheight));
+  }
+
+  /**
+   * Test setting of the box position, when there are hidden rows at the end of
+   * the alignment
+   */
+  @Test(groups = { "Functional" })
+  public void testFromMouseWithHiddenRowsAtEnd()
+  {
+    od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+    assertEquals(od.getScrollCol(), 0);
+    assertEquals(od.getScrollRow(), 0);
+
+    // hide rows at end and check updated box position is correct
+    // no changes
+    int firstHidden = 500;
+    int lastHidden = 524;
+    hideSequences(firstHidden, lastHidden);
+
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(), 0);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows
+    int ypos = 40; // row 40
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(od.getBoxY(),
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click above hidden rows so box overlaps
+    // boxY moved upwards, boxHeight remains same
+    ypos = 497; // row 497
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((float) (firstHidden - viewHeight)
+                    * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+    // click within hidden rows
+    ypos = 505;
+    mouseClick(od, 0,
+            Math.round((float) ypos * od.getSequencesHeight() / alheight));
+    assertEquals(od.getBoxX(), 0);
+    assertEquals(
+            od.getBoxY(),
+            Math.round((firstHidden - viewHeight) * od.getSequencesHeight()
+                    / alheight));
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+  }
+
+  /*
+   * Move viewport horizontally: startRes + previous width gives new horizontal extent. Vertical extent stays the same.
+   */
+  private void moveViewportH(int startRes)
+  {
+    vpranges.setStartRes(startRes);
+    vpranges.setEndRes(startRes + viewWidth - 1);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+  }
+
+  /*
+   * Move viewport vertically: startSeq and endSeq give new vertical extent. Horizontal extent stays the same.
+   */
+  private void moveViewportV(int startSeq)
+  {
+    vpranges.setStartSeq(startSeq);
+    vpranges.setEndSeq(startSeq + viewHeight - 1);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+  }
+
+  /*
+   * Move viewport horizontally and vertically.
+   */
+  private void moveViewport(int startRes, int startSeq)
+  {
+    vpranges.setStartRes(startRes);
+    vpranges.setEndRes(startRes + viewWidth - 1);
+    vpranges.setStartSeq(startSeq);
+    vpranges.setEndSeq(startSeq + viewHeight - 1);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+  }
+
+  /*
+   * Mouse click as position x,y in overview window
+   */
+  private void mouseClick(OverviewDimensions od, int x, int y)
+  {
+    od.updateViewportFromMouse(x, y, al.getHiddenSequences(), hiddenCols,
+            vpranges);
+
+    // updates require an OverviewPanel to exist which it doesn't here
+    // so call setBoxPosition() as it would be called by the AlignmentPanel
+    // normally
+
+    vpranges.setStartRes(od.getScrollCol());
+    vpranges.setEndRes(od.getScrollCol() + viewWidth - 1);
+    vpranges.setStartSeq(od.getScrollRow());
+    vpranges.setEndSeq(od.getScrollRow() + viewHeight - 1);
+    od.setBoxPosition(al.getHiddenSequences(), hiddenCols, vpranges);
+  }
+  
+  /*
+   * Test that the box is positioned with the top left corner at xpos, ypos
+   * and with the original width and height
+   */
+  private void testBoxIsAtClickPoint(int xpos, int ypos)
+  {
+    mouseClick(od, xpos, ypos);
+    assertEquals(od.getBoxX(), xpos);
+    assertEquals(od.getBoxY(), ypos);
+    assertEquals(od.getBoxWidth(), boxWidth);
+    assertEquals(od.getBoxHeight(), boxHeight);
+
+  }
+
+  /*
+   * Hide sequences between start and end
+   */
+  private void hideSequences(int start, int end)
+  {
+    SequenceI[] allseqs = al.getSequencesArray();
+    SequenceGroup theseSeqs = new SequenceGroup();
+    
+    for (int i = start; i <= end; i++)
+    {
+      theseSeqs.addSequence(allseqs[i], false);
+      al.getHiddenSequences().hideSequence(allseqs[i]);
+    }
+
+    hiddenRepSequences.put(allseqs[start], theseSeqs);
+  }
+}
diff --git a/test/jalview/viewmodel/ViewportRangesTest.java b/test/jalview/viewmodel/ViewportRangesTest.java
new file mode 100644 (file)
index 0000000..cfd03cd
--- /dev/null
@@ -0,0 +1,100 @@
+package jalview.viewmodel;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.datamodel.AlignmentI;
+
+import org.testng.annotations.Test;
+
+public class ViewportRangesTest {
+
+  AlignmentGenerator gen = new AlignmentGenerator(false);
+
+  AlignmentI al = gen.generate(20, 30, 1, 5, 5);
+
+  @Test
+  public void testViewportRanges() 
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    
+    assertEquals(vr.getStartRes(),0);
+    assertEquals(vr.getEndRes(), al.getWidth()-1);
+    assertEquals(vr.getStartSeq(), 0);
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+  }
+
+  @Test
+  public void testGetAbsoluteAlignmentHeight()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+
+    assertEquals(vr.getAbsoluteAlignmentHeight(), al.getHeight());
+
+    al.getHiddenSequences().hideSequence(al.getSequenceAt(3));
+    assertEquals(vr.getAbsoluteAlignmentHeight(), al.getHeight() + 1);
+  }
+
+  @Test
+  public void testGetAbsoluteAlignmentWidth()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    assertEquals(vr.getAbsoluteAlignmentWidth(), al.getWidth());
+  }
+
+  @Test
+  public void testSetEndRes()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setEndRes(-1);
+    assertEquals(vr.getEndRes(), 0);
+
+    vr.setEndRes(al.getWidth());
+    assertEquals(vr.getEndRes(), al.getWidth() - 1);
+
+    vr.setEndRes(al.getWidth() - 1);
+    assertEquals(vr.getEndRes(), al.getWidth() - 1);
+  }
+
+  @Test
+  public void testSetEndSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setEndSeq(-1);
+    assertEquals(vr.getEndSeq(), 0);
+
+    vr.setEndSeq(al.getHeight());
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+
+    vr.setEndRes(al.getHeight() - 1);
+    assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+  }
+
+  @Test
+  public void testSetStartRes()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartRes(-1);
+    assertEquals(vr.getStartRes(), 0);
+
+    vr.setStartRes(al.getWidth());
+    assertEquals(vr.getStartRes(), al.getWidth() - 1);
+
+    vr.setStartRes(al.getWidth() - 1);
+    assertEquals(vr.getStartRes(), al.getWidth() - 1);
+  }
+
+  @Test
+  public void testSetStartSeq()
+  {
+    ViewportRanges vr = new ViewportRanges(al);
+    vr.setStartSeq(-1);
+    assertEquals(vr.getStartSeq(), 0);
+
+    vr.setStartSeq(al.getHeight());
+    assertEquals(vr.getStartSeq(), al.getHeight() - 1);
+
+    vr.setStartSeq(al.getHeight() - 1);
+    assertEquals(vr.getStartSeq(), al.getHeight() - 1);
+  }
+}
index 2d317e4..2714d6c 100644 (file)
@@ -44,7 +44,11 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-@Test(groups = { "External" })
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class DisorderAnnotExportImport
 {
 
@@ -65,12 +69,19 @@ public class DisorderAnnotExportImport
 
   public static jalview.gui.AlignFrame af = null;
 
-  @BeforeClass(inheritGroups = true)
+  @BeforeClass(alwaysRun = true)
   public static void setUpBeforeClass() throws Exception
   {
     Cache.loadProperties("test/jalview/io/testProps.jvprops");
     Cache.initLogger();
     disc = JalviewJabawsTestUtils.getJabawsDiscoverer();
+
+    while (disc.isRunning())
+    {
+      // don't get services until discoverer has finished
+      Thread.sleep(100);
+    }
+
     iupreds = new ArrayList<Jws2Instance>();
     for (Jws2Instance svc : disc.getServices())
     {
@@ -100,7 +111,7 @@ public class DisorderAnnotExportImport
   /**
    * test for patches to JAL-1294
    */
-  @Test
+  @Test(groups = { "External", "Network" })
   public void testDisorderAnnotExport()
   {
     disorderClient = new AADisorderClient(iupreds.get(0), af, null, null);
index f1430f6..4e9741e 100644 (file)
@@ -55,6 +55,11 @@ import org.testng.annotations.Test;
 import compbio.metadata.Argument;
 import compbio.metadata.WrongParameterException;
 
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class RNAStructExportImport
 {
 
@@ -84,6 +89,12 @@ public class RNAStructExportImport
     Cache.initLogger();
     disc = JalviewJabawsTestUtils.getJabawsDiscoverer(false);
 
+    while (disc.isRunning())
+    {
+      // don't get services until discoverer has finished
+      Thread.sleep(100);
+    }
+
     for (Jws2Instance svc : disc.getServices())
     {
 
@@ -139,7 +150,7 @@ public class RNAStructExportImport
     }
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRNAAliFoldValidStructure()
   {
 
@@ -172,7 +183,7 @@ public class RNAStructExportImport
     }
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRNAStructExport()
   {
 
@@ -192,11 +203,11 @@ public class RNAStructExportImport
 
     AlignmentI orig_alig = af.getViewport().getAlignment();
 
-    testAnnotationFileIO("Testing RNAalifold Annotation IO", orig_alig);
+    verifyAnnotationFileIO("Testing RNAalifold Annotation IO", orig_alig);
 
   }
 
-  public static void testAnnotationFileIO(String testname, AlignmentI al)
+  static void verifyAnnotationFileIO(String testname, AlignmentI al)
   {
     try
     {
@@ -242,7 +253,7 @@ public class RNAStructExportImport
             + "\nCouldn't complete Annotation file roundtrip input/output/input test.");
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testRnaalifoldSettingsRecovery()
   {
     List<Argument> opts = new ArrayList<Argument>();
index 0662e5b..e859cb0 100644 (file)
@@ -42,6 +42,11 @@ import compbio.metadata.Preset;
 import compbio.metadata.PresetManager;
 import compbio.metadata.WrongParameterException;
 
+/*
+ * All methods in this class are set to the Network group because setUpBeforeClass will fail
+ * if there is no network.
+ */
+@Test(singleThreaded = true)
 public class ParameterUtilsTest
 {
 
@@ -129,7 +134,7 @@ public class ParameterUtilsTest
             || serviceTests.contains(service.serviceType.toLowerCase());
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testCopyOption()
   {
     for (Jws2Instance service : disc.getServices())
@@ -153,7 +158,7 @@ public class ParameterUtilsTest
 
   /**
    */
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testCopyParameter()
   {
     for (Jws2Instance service : disc.getServices())
index 98ca303..f1dafcb 100644 (file)
  */
 package jalview.ws.seqfetcher;
 
+import static org.testng.Assert.assertTrue;
+
+import jalview.bin.Cache;
 import jalview.gui.JvOptionPane;
 
-import org.testng.AssertJUnit;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -36,15 +38,12 @@ public class DasSequenceFetcher
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Network" })
   public void testDasRegistryContact()
   {
-    jalview.bin.Cache.getDasSourceRegistry().refreshSources();
-    AssertJUnit
-            .assertTrue(
-                    "Expected to find at least one DAS source at the registry. Check config.",
-                    jalview.bin.Cache.getDasSourceRegistry().getSources()
-                            .size() > 0);
+    Cache.getDasSourceRegistry().refreshSources();
+    assertTrue(Cache.getDasSourceRegistry().getSources().isEmpty(),
+            "Expected to find no DAS sources at the registry. Check config.");
   }
 
 }
index d805e47..7f8adc9 100644 (file)
@@ -38,6 +38,8 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 import org.testng.Assert;
 import org.testng.FileAssert;
@@ -280,7 +282,19 @@ public class SiftsClientTest
               "A", testSeq, null);
       Assert.assertEquals(testSeq.getStart(), 1);
       Assert.assertEquals(testSeq.getEnd(), 147);
-      Assert.assertEquals(actualMapping, expectedMapping);
+      // Can't do Assert.assertEquals(actualMapping, expectedMapping);
+      // because this fails in our version of TestNG
+      Assert.assertEquals(actualMapping.size(), expectedMapping.size());
+      Iterator<Map.Entry<Integer, int[]>> it = expectedMapping.entrySet()
+              .iterator();
+      while (it.hasNext())
+      {
+        Map.Entry<Integer, int[]> pair = it.next();
+        Assert.assertTrue(actualMapping.containsKey(pair.getKey()));
+        Assert.assertEquals(actualMapping.get(pair.getKey()),
+                pair.getValue());
+      }
+
     } catch (Exception e)
     {
       e.printStackTrace();
@@ -399,7 +413,21 @@ groups = { "Network" },
 
     Assert.assertEquals(strucMapping.getMappingDetailsOutput(),
             expectedMappingOutput);
-    Assert.assertEquals(strucMapping.getMapping(), expectedMapping);
+
+    // Can't do Assert.assertEquals(strucMapping.getMapping(), expectedMapping);
+    // because this fails in our version of TestNG
+    Assert.assertEquals(strucMapping.getMapping().size(),
+            expectedMapping.size());
+    Iterator<Map.Entry<Integer, int[]>> it = expectedMapping.entrySet()
+            .iterator();
+    while (it.hasNext())
+    {
+      Map.Entry<Integer, int[]> pair = it.next();
+      Assert.assertTrue(strucMapping.getMapping()
+              .containsKey(pair.getKey()));
+      Assert.assertEquals(strucMapping.getMapping().get(pair.getKey()),
+              pair.getValue());
+    }
   }
 
   @Test(groups = { "Network" })
diff --git a/test/junit/extensions/PA.java b/test/junit/extensions/PA.java
new file mode 100644 (file)
index 0000000..57c873f
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package junit.extensions;
+
+import java.util.Collection;
+
+/**
+ * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax
+ * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
+ * <p>
+ * a.k.a. The "ObjectMolester"
+ * <p>
+ * Here is an example of using this to access a private member: <br>
+ * Given the following class <code>MyClass</code>: <br>
+ * 
+ * <pre>
+ * public class MyClass {
+ *    private String name; // private attribute
+ * 
+ *    // private constructor
+ *    private MyClass() {
+ *       super();
+ *    }
+ * 
+ *    // private method
+ *    private void setName(String newName) {
+ *       this.name = newName;
+ *    }
+ * }
+ * </pre>
+ * 
+ * We now want to access the class: <br>
+ * 
+ * <pre>
+ * MyClass myObj = PA.instantiate(MyClass.class);
+ * PA.invokeMethod(myObj, &quot;setName(java.lang.String)&quot;, &quot;myNewName&quot;);
+ * String name = PA.getValue(myObj, &quot;name&quot;);
+ * </pre>
+ * 
+ * This class extends {@link PrivilegedAccessor} by re-throwing checked {@link Exception}s as {@link RuntimeException}s.
+ * 
+ * 
+ * @see PrivilegedAccessor
+ * 
+ * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
+ * @author Lubos Bistak (lubos@bistak.sk)
+ */
+public class PA {
+   private final Object instanceOrClass;
+
+   /**
+    * Private constructor to make it impossible to instantiate this class from outside of PA.
+    * 
+    * @param instanceOrClass
+    */
+   private PA(Object instanceOrClass) {
+      this.instanceOrClass = instanceOrClass;
+   }
+
+   /**
+    * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
+    * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
+    * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
+    * 
+    * @param instanceOrClass the object or class to get a string representation of
+    * @return a string representation of the given object
+    * 
+    * @see PrivilegedAccessor#toString(Object)
+    */
+   public static String toString(final Object instanceOrClass) {
+      return PrivilegedAccessor.toString(instanceOrClass);
+   }
+
+   /**
+    * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
+    * fields (public, private, protected, default) of all its super classes.
+    * 
+    * @param instanceOrClass the instance or class to get the fields of
+    * @return the collection of field names of the given instance or class
+    * 
+    * @see PrivilegedAccessor#getFieldNames(Object)
+    */
+   public static Collection<String> getFieldNames(final Object instanceOrClass) {
+      return PrivilegedAccessor.getFieldNames(instanceOrClass);
+   }
+
+   /**
+    * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
+    * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
+    * 
+    * @param instanceOrClass the instance or class to get the method signatures of
+    * @return the collection of method signatures of the given instance or class
+    * 
+    * @see PrivilegedAccessor#getMethodSignatures(Object)
+    */
+   public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
+      return PrivilegedAccessor.getMethodSignatures(instanceOrClass);
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws IllegalArgumentException if the field does not exist
+    * 
+    * @see PrivilegedAccessor#getValue(Object,String)
+    */
+   public static Object getValue(final Object instanceOrClass, final String fieldName) {
+      try {
+         return PrivilegedAccessor.getValue(instanceOrClass, fieldName);
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't get value of " + fieldName + " from " + instanceOrClass, e);
+      }
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object.
+    * 
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws IllegalArgumentException if the field does not exist
+    * 
+    * @see PA#getValue(Object,String)
+    */
+   public Object getValue(final String fieldName) {
+      return PA.getValue(instanceOrClass, fieldName);
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
+    * member class, you must provide the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param arguments the arguments to pass to the constructor
+    * @param argumentTypes the fully qualified types of the arguments of the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
+    *            parameters differ; if an unwrapping conversion for primitive arguments fails; if, after possible unwrapping, a
+    *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
+    *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
+    *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
+    *            underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.instantiate(fromClass, argumentTypes, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
+      }
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
+    * the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param arguments the arguments to pass to the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
+    *            parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a
+    *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
+    *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
+    *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
+    *            underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.instantiate(fromClass, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
+      }
+   }
+
+   /**
+    * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
+    * primitives.
+    * 
+    * @param instanceOrClass the instance or class to invoke the method on
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
+    *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
+    *            an argument couldn't be converted to match the expected type
+    * 
+    * @see PrivilegedAccessor#invokeMethod(Object,String,Object[])
+    */
+   public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object... arguments) {
+      try {
+         return PrivilegedAccessor.invokeMethod(instanceOrClass, methodSignature, correctVarargs(arguments));
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't invoke method " + methodSignature + " on " + instanceOrClass + " with arguments "
+            + arguments, e);
+      }
+   }
+
+   /**
+    * Calls a method with the given arguments. Arguments can be object types or representations for primitives.
+    * 
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
+    *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
+    *            an argument couldn't be converted to match the expected type
+    * @see PA#invokeMethod(Object, String, Object...)
+    */
+   public Object invokeMethod(final String methodSignature, final Object... arguments) {
+      return PA.invokeMethod(instanceOrClass, methodSignature, arguments);
+   }
+
+   /**
+    * Corrects varargs to their initial form. If you call a method with an object-array as last argument the Java varargs mechanism
+    * converts this array in single arguments. This method returns an object array if the arguments are all of the same type.
+    * 
+    * @param arguments the possibly converted arguments of a vararg method
+    * @return arguments possibly converted
+    */
+   private static Object[] correctVarargs(final Object... arguments) {
+      if ((arguments == null) || changedByVararg(arguments)) return new Object[] {arguments};
+      return arguments;
+   }
+
+   /**
+    * Tests if the arguments were changed by vararg. Arguments are changed by vararg if they are of a non primitive array type. E.g.
+    * arguments[] = Object[String[]] is converted to String[] while e.g. arguments[] = Object[int[]] is not converted and stays
+    * Object[int[]]
+    * 
+    * Unfortunately we can't detect the difference for arg = Object[primitive] since arguments[] = Object[Object[primitive]] which is
+    * converted to Object[primitive] and arguments[] = Object[primitive] which stays Object[primitive]
+    * 
+    * and we can't detect the difference for arg = Object[non primitive] since arguments[] = Object[Object[non primitive]] is converted
+    * to Object[non primitive] and arguments[] = Object[non primitive] stays Object[non primitive]
+    * 
+    * @param parameters the parameters
+    * @return true if parameters were changes by varargs, false otherwise
+    */
+   private static boolean changedByVararg(final Object[] parameters) {
+      if ((parameters.length == 0) || (parameters[0] == null)) return false;
+
+      if (parameters.getClass() == Object[].class) return false;
+
+      return true;
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param instanceOrClass the instance or class to set the field
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
+    *            <code>fieldName</code> can be found; or if the field was final
+    * 
+    * @see PrivilegedAccessor.setValue(Object,String,Object)
+    */
+   public static PA setValue(final Object instanceOrClass, final String fieldName, final Object value) {
+      try {
+         PrivilegedAccessor.setValue(instanceOrClass, fieldName, value);
+      } catch (Exception e) {
+         throw new IllegalArgumentException("Can't set value " + value + " at " + fieldName + " in " + instanceOrClass, e);
+      }
+      return new PA(instanceOrClass);
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
+    *            <code>fieldName</code> can be found; or if the field was final
+    * 
+    * @see PA.setValue(Object,String,Object)
+    */
+   public PA setValue(final String fieldName, final Object value) {
+      PA.setValue(instanceOrClass, fieldName, value);
+      return this;
+   }
+}
diff --git a/test/junit/extensions/PrivilegedAccessor.java b/test/junit/extensions/PrivilegedAccessor.java
new file mode 100644 (file)
index 0000000..23f1c6e
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package junit.extensions;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax
+ * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
+ * <p>
+ * a.k.a. The "ObjectMolester"
+ * <p>
+ * Here is an example of using this to access a private member: <br>
+ * <code>myObject</code> is an object of type <code>MyClass</code>. <code>setName(String)</code> is a private method of
+ * <code>MyClass</code>.
+ * 
+ * <pre>
+ * PrivilegedAccessor.invokeMethod(myObject, &quot;setName(java.lang.String)&quot;, &quot;newName&quot;);
+ * </pre>
+ * 
+ * @author Charlie Hubbard (chubbard@iss.net)
+ * @author Prashant Dhokte (pdhokte@iss.net)
+ * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
+ * 
+ * @deprecated use PA instead. PA improves the functionality of PrivilegedAccessor by introducing support for varargs and removal of
+ *             the necessity to catch exceptions.
+ */
+@Deprecated
+final class PrivilegedAccessor
+{
+   /**
+    * Private constructor to make it impossible to instantiate this class.
+    */
+   private PrivilegedAccessor() {
+      assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
+   }
+
+   /**
+    * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
+    * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
+    * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
+    * 
+    * @param instanceOrClass the object or class to get a string representation of
+    * @return a string representation of the given object
+    */
+   public static String toString(final Object instanceOrClass) {
+      Collection<String> fields = getFieldNames(instanceOrClass);
+
+      if (fields.isEmpty())
+      {
+        return getClass(instanceOrClass).getName();
+      }
+
+      StringBuffer stringBuffer = new StringBuffer();
+
+      stringBuffer.append(getClass(instanceOrClass).getName() + " {");
+
+      for (String fieldName : fields) {
+         try {
+            stringBuffer.append(fieldName + "=" + getValue(instanceOrClass, fieldName) + ", ");
+         } catch (NoSuchFieldException e) {
+            assert false : "It should always be possible to get a field that was just here";
+         }
+      }
+
+      stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}");
+      return stringBuffer.toString();
+   }
+
+   /**
+    * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
+    * fields (public, private, protected, default) of all its super classes.
+    * 
+    * @param instanceOrClass the instance or class to get the fields of
+    * @return the collection of field names of the given instance or class
+    */
+   public static Collection<String> getFieldNames(final Object instanceOrClass) {
+      if (instanceOrClass == null)
+      {
+        return Collections.EMPTY_LIST;
+      }
+
+      Class<?> clazz = getClass(instanceOrClass);
+      Field[] fields = clazz.getDeclaredFields();
+      Collection<String> fieldNames = new ArrayList<String>(fields.length);
+
+      for (Field field : fields) {
+         fieldNames.add(field.getName());
+      }
+      fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
+
+      return fieldNames;
+   }
+
+   /**
+    * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
+    * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
+    * 
+    * @param instanceOrClass the instance or class to get the method signatures of
+    * @return the collection of method signatures of the given instance or class
+    */
+   public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
+      if (instanceOrClass == null)
+      {
+        return Collections.EMPTY_LIST;
+      }
+
+      Class<?> clazz = getClass(instanceOrClass);
+      Method[] methods = clazz.getDeclaredMethods();
+      Collection<String> methodSignatures = new ArrayList<String>(methods.length + Object.class.getDeclaredMethods().length);
+
+      for (Method method : methods) {
+         methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")");
+      }
+      methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
+
+      return methodSignatures;
+   }
+
+   /**
+    * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field
+    * @return an object representing the value of the field
+    * @throws NoSuchFieldException if the field does not exist
+    */
+   public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException {
+      Field field = getField(instanceOrClass, fieldName);
+      try {
+         return field.get(instanceOrClass);
+      } catch (IllegalAccessException e) {
+         assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place";
+         return null;
+      }
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
+    * the object it is a member of as first argument.
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param args the arguments to pass to the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
+    *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
+    *            parameter type by a method invocation conversion.
+    * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
+    *            inaccessible.
+    * @throws InvocationTargetException if the underlying constructor throws an exception.
+    * @throws NoSuchMethodException if the constructor could not be found
+    * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Object[] args) throws IllegalArgumentException,
+      InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+      return instantiate(fromClass, getParameterTypes(args), args);
+   }
+
+   /**
+    * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
+    * member class, you must provide the object it is a member of as first argument.
+    * 
+    * 
+    * @param fromClass the class to instantiate an object from
+    * @param args the arguments to pass to the constructor
+    * @param argumentTypes the fully qualified types of the arguments of the constructor
+    * @return an object of the given type
+    * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
+    *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
+    *            parameter type by a method invocation conversion.
+    * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
+    *            inaccessible.
+    * @throws InvocationTargetException if the underlying constructor throws an exception.
+    * @throws NoSuchMethodException if the constructor could not be found
+    * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
+    * 
+    * @see PrivilegedAccessor#instantiate(Class,Object[])
+    */
+   public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object[] args)
+      throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
+      NoSuchMethodException {
+      return getConstructor(fromClass, argumentTypes).newInstance(args);
+   }
+
+   /**
+    * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
+    * primitives.
+    * 
+    * @param instanceOrClass the instance or class to invoke the method on
+    * @param methodSignature the name of the method and the parameters <br>
+    *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
+    * @param arguments an array of objects to pass as arguments
+    * @return the return value of this method or null if void
+    * @throws IllegalAccessException if the method is inaccessible
+    * @throws InvocationTargetException if the underlying method throws an exception.
+    * @throws NoSuchMethodException if no method with the given <code>methodSignature</code> could be found
+    * @throws IllegalArgumentException if an argument couldn't be converted to match the expected type
+    */
+   public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object[] arguments)
+      throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+      if ((methodSignature.indexOf('(') == -1) || (methodSignature.indexOf('(') >= methodSignature.indexOf(')')))
+      {
+        throw new NoSuchMethodException(methodSignature);
+      }
+      Class<?>[] parameterTypes = getParameterTypes(methodSignature);
+      return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass,
+         getCorrectedArguments(parameterTypes, arguments));
+   }
+
+   /**
+    * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be
+    * mistaken by varargs.
+    * 
+    * @param parameterTypes the method signatue the given arguments should match
+    * @param arguments the arguments that should be corrected
+    * @return the corrected arguments
+    */
+   private static Object[] getCorrectedArguments(Class<?>[] parameterTypes, Object[] arguments) {
+      if (arguments == null)
+      {
+        return arguments;
+      }
+      if (parameterTypes.length > arguments.length)
+      {
+        return arguments;
+      }
+      if (parameterTypes.length < arguments.length)
+      {
+        return getCorrectedArguments(parameterTypes, new Object[] {arguments});
+      }
+
+      Object[] correctedArguments = new Object[arguments.length];
+      int currentArgument = 0;
+      for (Class<?> parameterType : parameterTypes) {
+         correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]);
+         currentArgument++;
+      }
+      return correctedArguments;
+   }
+
+   /**
+    * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be
+    * mistaken by varargs.
+    * 
+    * @param parameterType the type to match the given argument upon
+    * @param argument the argument to match the given parameterType
+    * @return the corrected argument
+    */
+   private static Object getCorrectedArgument(Class<?> parameterType, Object argument) {
+      if (!parameterType.isArray() || (argument == null)) {
+         return argument; // normal argument for normal parameterType
+      }
+
+      if (!argument.getClass().isArray()) {
+         return new Object[] {argument};
+      }
+
+      if (parameterType.equals(argument.getClass()))
+       {
+        return argument; // no need to cast
+      }
+
+      // (typed) array argument for (object) array parameterType, elements need to be casted
+      Object correctedArrayArgument = Array.newInstance(parameterType.getComponentType(), Array.getLength(argument));
+      for (int index = 0; index < Array.getLength(argument); index++) {
+         if (parameterType.getComponentType().isPrimitive()) { // rely on autoboxing
+            Array.set(correctedArrayArgument, index, Array.get(argument, index));
+         } else { // cast to expected type
+            try {
+               Array.set(correctedArrayArgument, index, parameterType.getComponentType().cast(Array.get(argument, index)));
+            } catch (ClassCastException e) {
+               throw new IllegalArgumentException("Argument " + argument + " of type " + argument.getClass()
+                  + " does not match expected argument type " + parameterType + ".");
+            }
+         }
+      }
+      return correctedArrayArgument;
+   }
+
+   /**
+    * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
+    * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
+    * at other times than instantiation can have unpredictable effects.<br/>
+    * <br/>
+    * Example:<br/>
+    * <br/>
+    * <code>
+    * String myString = "Test"; <br/>
+    * <br/>
+    * //setting the private field value<br/>
+    * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
+    * <br/>
+    * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
+    * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
+    * <br/>
+    * </code>
+    * 
+    * @param instanceOrClass the instance or class to set the field
+    * @param fieldName the name of the field
+    * @param value the new value of the field
+    * @throws NoSuchFieldException if no field with the given <code>fieldName</code> can be found
+    * @throws IllegalAccessException possibly if the field was final
+    */
+   public static void setValue(final Object instanceOrClass, final String fieldName, final Object value) throws NoSuchFieldException,
+      IllegalAccessException {
+      Field field = getField(instanceOrClass, fieldName);
+      if (Modifier.isFinal(field.getModifiers())) {
+         PrivilegedAccessor.setValue(field, "modifiers", field.getModifiers() ^ Modifier.FINAL);
+      }
+      field.set(instanceOrClass, value);
+   }
+
+   /**
+    * Gets the class with the given className.
+    * 
+    * @param className the name of the class to get
+    * @return the class for the given className
+    * @throws ClassNotFoundException if the class could not be found
+    */
+   private static Class<?> getClassForName(final String className) throws ClassNotFoundException {
+      if (className.indexOf('[') > -1) {
+         Class<?> clazz = getClassForName(className.substring(0, className.indexOf('[')));
+         return Array.newInstance(clazz, 0).getClass();
+      }
+
+      if (className.indexOf("...") > -1) {
+         Class<?> clazz = getClassForName(className.substring(0, className.indexOf("...")));
+         return Array.newInstance(clazz, 0).getClass();
+      }
+
+      try {
+         return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+         return getSpecialClassForName(className);
+      }
+   }
+
+   /**
+    * Maps string representation of primitives to their corresponding classes.
+    */
+   private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(8);
+
+   /**
+    * Fills the map with all java primitives and their corresponding classes.
+    */
+   static {
+      PRIMITIVE_MAPPER.put("int", Integer.TYPE);
+      PRIMITIVE_MAPPER.put("float", Float.TYPE);
+      PRIMITIVE_MAPPER.put("double", Double.TYPE);
+      PRIMITIVE_MAPPER.put("short", Short.TYPE);
+      PRIMITIVE_MAPPER.put("long", Long.TYPE);
+      PRIMITIVE_MAPPER.put("byte", Byte.TYPE);
+      PRIMITIVE_MAPPER.put("char", Character.TYPE);
+      PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE);
+   }
+
+   /**
+    * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String)
+    * 
+    * @param className the name of the class to get
+    * @return the class for the given className
+    * @throws ClassNotFoundException if the class could not be found
+    */
+   private static Class<?> getSpecialClassForName(final String className) throws ClassNotFoundException {
+      if (PRIMITIVE_MAPPER.containsKey(className))
+      {
+        return PRIMITIVE_MAPPER.get(className);
+      }
+
+      if (missesPackageName(className))
+      {
+        return getStandardClassForName(className);
+      }
+
+      throw new ClassNotFoundException(className);
+   }
+
+   /**
+    * Gets a 'standard' java class for the given className.
+    * 
+    * @param className the className
+    * @return the class for the given className (if any)
+    * @throws ClassNotFoundException of no 'standard' java class was found for the given className
+    */
+   private static Class<?> getStandardClassForName(String className) throws ClassNotFoundException {
+      try {
+         return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+         try {
+            return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader());
+         } catch (ClassNotFoundException e1) {
+            throw new ClassNotFoundException(className);
+         }
+      }
+   }
+
+   /**
+    * Tests if the given className possibly misses its package name.
+    * 
+    * @param className the className
+    * @return true if the className might miss its package name, otherwise false
+    */
+   private static boolean missesPackageName(String className) {
+      if (className.contains("."))
+      {
+        return false;
+      }
+      if (className.startsWith(className.substring(0, 1).toUpperCase()))
+      {
+        return true;
+      }
+      return false;
+   }
+
+   /**
+    * Gets the constructor for a given class with the given parameters.
+    * 
+    * @param type the class to instantiate
+    * @param parameterTypes the types of the parameters
+    * @return the constructor
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static <T> Constructor<T> getConstructor(final Class<T> type, final Class<?>[] parameterTypes) throws NoSuchMethodException {
+      Constructor<T> constructor = type.getDeclaredConstructor(parameterTypes);
+      constructor.setAccessible(true);
+      return constructor;
+   }
+
+   /**
+    * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class.
+    * 
+    * @param instanceOrClass the instance or class to get the field from
+    * @param fieldName the name of the field to get
+    * @return the field
+    * @throws NoSuchFieldException if no such field can be found
+    * @throws InvalidParameterException if instanceOrClass was null
+    */
+   private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException,
+      InvalidParameterException {
+      if (instanceOrClass == null)
+      {
+        throw new InvalidParameterException("Can't get field on null object/class");
+      }
+
+      Class<?> type = getClass(instanceOrClass);
+
+      try {
+         Field field = type.getDeclaredField(fieldName);
+         field.setAccessible(true);
+         return field;
+      } catch (NoSuchFieldException e) {
+         if (type.getSuperclass() == null)
+        {
+          throw e;
+        }
+         return getField(type.getSuperclass(), fieldName);
+      }
+   }
+
+   /**
+    * Gets the class of the given parameter. If the parameter is a class, it is returned, if it is an object, its class is returned
+    * 
+    * @param instanceOrClass the instance or class to get the class of
+    * @return the class of the given parameter
+    */
+   private static Class<?> getClass(final Object instanceOrClass) {
+      if (instanceOrClass instanceof Class)
+      {
+        return (Class<?>) instanceOrClass;
+      }
+
+      return instanceOrClass.getClass();
+   }
+
+   /**
+    * Return the named method with a method signature matching classTypes from the given class.
+    * 
+    * @param type the class to get the method from
+    * @param methodName the name of the method to get
+    * @param parameterTypes the parameter-types of the method to get
+    * @return the method
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static Method getMethod(final Class<?> type, final String methodName, final Class<?>[] parameterTypes)
+      throws NoSuchMethodException {
+      try {
+         return type.getDeclaredMethod(methodName, parameterTypes);
+      } catch (NoSuchMethodException e) {
+         if (type.getSuperclass() == null)
+        {
+          throw e;
+        }
+         return getMethod(type.getSuperclass(), methodName, parameterTypes);
+      }
+   }
+
+   /**
+    * Gets the method with the given name and parameters from the given instance or class. If instanceOrClass is a class, then we get a
+    * static method.
+    * 
+    * @param instanceOrClass the instance or class to get the method of
+    * @param methodName the name of the method
+    * @param parameterTypes the parameter-types of the method to get
+    * @return the method
+    * @throws NoSuchMethodException if the method could not be found
+    */
+   private static Method getMethod(final Object instanceOrClass, final String methodName, final Class<?>[] parameterTypes)
+      throws NoSuchMethodException {
+      Class<?> type;
+
+      type = getClass(instanceOrClass);
+
+      Method accessMethod = getMethod(type, methodName, parameterTypes);
+      accessMethod.setAccessible(true);
+      return accessMethod;
+   }
+
+   /**
+    * Gets the name of a method.
+    * 
+    * @param methodSignature the signature of the method
+    * @return the name of the method
+    */
+   private static String getMethodName(final String methodSignature) {
+      try {
+         return methodSignature.substring(0, methodSignature.indexOf('(')).trim();
+      } catch (StringIndexOutOfBoundsException e) {
+         assert false : "Signature must have been checked before this method was called";
+         return null;
+      }
+   }
+
+   /**
+    * Gets the types of the parameters.
+    * 
+    * @param parameters the parameters
+    * @return the class-types of the arguments
+    */
+   private static Class<?>[] getParameterTypes(final Object[] parameters) {
+      if (parameters == null)
+      {
+        return new Class[0];
+      }
+
+      Class<?>[] typesOfParameters = new Class[parameters.length];
+
+      for (int i = 0; i < parameters.length; i++) {
+         typesOfParameters[i] = parameters[i].getClass();
+      }
+      return typesOfParameters;
+   }
+
+   /**
+    * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is
+    * thrown.
+    * 
+    * @param methodSignature the signature of the method
+    * @return the parameter types as class[]
+    * @throws NoSuchMethodException if the method could not be found
+    * @throws IllegalArgumentException if one of the given parameters doesn't math the given methodSignature
+    */
+   private static Class<?>[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException {
+      String signature = getSignatureWithoutBraces(methodSignature);
+
+      StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
+      Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
+
+      for (int x = 0; tokenizer.hasMoreTokens(); x++) {
+         String className = tokenizer.nextToken();
+         try {
+            typesInSignature[x] = getClassForName(className);
+         } catch (ClassNotFoundException e) {
+            NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature);
+            noSuchMethodException.initCause(e);
+            throw noSuchMethodException;
+         }
+      }
+      return typesInSignature;
+   }
+
+   /**
+    * Gets the parameter types as a string.
+    * 
+    * @param classTypes the types to get as names.
+    * @return the parameter types as a string
+    * 
+    * @see java.lang.Class#argumentTypesToString(Class[])
+    */
+   private static String getParameterTypesAsString(final Class<?>[] classTypes) {
+      assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
+      if (classTypes.length == 0)
+      {
+        return "";
+      }
+
+      StringBuilder parameterTypes = new StringBuilder();
+      for (Class<?> clazz : classTypes) {
+         assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
+         parameterTypes.append(clazz.getName()).append(", ");
+      }
+
+      return parameterTypes.substring(0, parameterTypes.length() - 2);
+   }
+
+   /**
+    * Removes the braces around the methods signature.
+    * 
+    * @param methodSignature the signature with braces
+    * @return the signature without braces
+    */
+   private static String getSignatureWithoutBraces(final String methodSignature) {
+      try {
+         return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')'));
+      } catch (IndexOutOfBoundsException e) {
+         assert false : "signature must have been checked before this method";
+         return null;
+      }
+   }
+
+}