From: Jim Procter Date: Thu, 27 Apr 2017 10:11:48 +0000 (+0100) Subject: Merge branch 'develop' into features/JAL-1933_occupancy X-Git-Tag: Release_2_10_2~3^2~124^2~7 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=5a74603b846dae238eaca8c1a8aa5bab64ab3c2c;hp=529dfc85c7c4f85717d9778e49b2e61b5dfddd85;p=jalview.git Merge branch 'develop' into features/JAL-1933_occupancy --- diff --git a/help/html/colourSchemes/user.html b/help/html/colourSchemes/user.html index fb6c356..c2fde1c 100755 --- a/help/html/colourSchemes/user.html +++ b/help/html/colourSchemes/user.html @@ -27,7 +27,7 @@ User Defined Colours

- +

You may define any number of new colour schemes, each with a unique @@ -41,7 +41,7 @@ The Case Sensitive option allows you to choose distinct colours for upper and lower case residue codes.

- The Lower Case Colour option allows you to apply a selected colour + The Colour All Lower Case option allows you to apply a selected colour to all lower case residues.

Click Apply or OK to set your new @@ -56,11 +56,5 @@
Any saved colour schemes will be automatically loaded the next time you use Jalview.
-
- 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). -

diff --git a/help/html/colourSchemes/userDefined_java6.gif b/help/html/colourSchemes/userDefined_java6.gif deleted file mode 100644 index d737e80..0000000 Binary files a/help/html/colourSchemes/userDefined_java6.gif and /dev/null differ diff --git a/help/html/colourSchemes/userDefined_java7.gif b/help/html/colourSchemes/userDefined_java7.gif index f0ced11..de80b84 100644 Binary files a/help/html/colourSchemes/userDefined_java7.gif and b/help/html/colourSchemes/userDefined_java7.gif differ diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 138cf89..4ebca62 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -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.

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 diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index d408fee..bdd61fe 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -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.

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 diff --git a/src/MCview/AppletPDBCanvas.java b/src/MCview/AppletPDBCanvas.java index aac796c..3ae0650 100644 --- a/src/MCview/AppletPDBCanvas.java +++ b/src/MCview/AppletPDBCanvas.java @@ -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); } - } } } diff --git a/src/MCview/PDBCanvas.java b/src/MCview/PDBCanvas.java index 292de91..08bca8c 100644 --- a/src/MCview/PDBCanvas.java +++ b/src/MCview/PDBCanvas.java @@ -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); } } diff --git a/src/jalview/analysis/AAFrequency.java b/src/jalview/analysis/AAFrequency.java index ffa413b..ee16f94 100755 --- a/src/jalview/analysis/AAFrequency.java +++ b/src/jalview/analysis/AAFrequency.java @@ -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); } diff --git a/src/jalview/api/AlignViewportI.java b/src/jalview/api/AlignViewportI.java index 634521c..8b07340 100644 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@ -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 diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index f54231e..7123b8c 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ * Returns null if there is no visible feature at the position. + *

+ * 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(); } diff --git a/src/jalview/api/SequenceRenderer.java b/src/jalview/api/SequenceRenderer.java index d708902..54f7fb6 100644 --- a/src/jalview/api/SequenceRenderer.java +++ b/src/jalview/api/SequenceRenderer.java @@ -21,14 +21,14 @@ 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); } diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index 6a0b390..2646ede 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -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); diff --git a/src/jalview/appletgui/AlignViewport.java b/src/jalview/appletgui/AlignViewport.java index fc087c6..065c503 100644 --- a/src/jalview/appletgui/AlignViewport.java +++ b/src/jalview/appletgui/AlignViewport.java @@ -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) diff --git a/src/jalview/appletgui/AlignmentPanel.java b/src/jalview/appletgui/AlignmentPanel.java index e97c347..3ae0394 100644 --- a/src/jalview/appletgui/AlignmentPanel.java +++ b/src/jalview/appletgui/AlignmentPanel.java @@ -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(); diff --git a/src/jalview/appletgui/AnnotationColourChooser.java b/src/jalview/appletgui/AnnotationColourChooser.java index 487b75c..f516bc9 100644 --- a/src/jalview/appletgui/AnnotationColourChooser.java +++ b/src/jalview/appletgui/AnnotationColourChooser.java @@ -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 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 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(); 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 list = new Vector(); - 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 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 getAnnotationItems() + { + // TODO remove duplication with gui.AnnotationRowFilter + // TODO add 'per sequence only' option / parameter + + annotationLabels = new HashMap(); + Vector list = new Vector(); + 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 diff --git a/src/jalview/appletgui/AnnotationLabels.java b/src/jalview/appletgui/AnnotationLabels.java index b28ccc7..ad74b25 100755 --- a/src/jalview/appletgui/AnnotationLabels.java +++ b/src/jalview/appletgui/AnnotationLabels.java @@ -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(); diff --git a/src/jalview/appletgui/AnnotationPanel.java b/src/jalview/appletgui/AnnotationPanel.java index 6012c1a..0ec7adf 100755 --- a/src/jalview/appletgui/AnnotationPanel.java +++ b/src/jalview/appletgui/AnnotationPanel.java @@ -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 { diff --git a/src/jalview/appletgui/AppletJmolBinding.java b/src/jalview/appletgui/AppletJmolBinding.java index f938cad..9b8a235 100644 --- a/src/jalview/appletgui/AppletJmolBinding.java +++ b/src/jalview/appletgui/AppletJmolBinding.java @@ -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 diff --git a/src/jalview/appletgui/ExtJmol.java b/src/jalview/appletgui/ExtJmol.java index 189fe88..b369318 100644 --- a/src/jalview/appletgui/ExtJmol.java +++ b/src/jalview/appletgui/ExtJmol.java @@ -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 { diff --git a/src/jalview/appletgui/FeatureRenderer.java b/src/jalview/appletgui/FeatureRenderer.java index 67ca8e9..b88a1dc 100644 --- a/src/jalview/appletgui/FeatureRenderer.java +++ b/src/jalview/appletgui/FeatureRenderer.java @@ -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', ' '); diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 2c454a4..1b9fbf9 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -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 diff --git a/src/jalview/appletgui/IdCanvas.java b/src/jalview/appletgui/IdCanvas.java index d72e91f..abcbd70 100755 --- a/src/jalview/appletgui/IdCanvas.java +++ b/src/jalview/appletgui/IdCanvas.java @@ -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); diff --git a/src/jalview/appletgui/IdPanel.java b/src/jalview/appletgui/IdPanel.java index b03a638..e47c50a 100755 --- a/src/jalview/appletgui/IdPanel.java +++ b/src/jalview/appletgui/IdPanel.java @@ -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) diff --git a/src/jalview/appletgui/OverviewPanel.java b/src/jalview/appletgui/OverviewPanel.java index 9b2be4c..3ef2936 100755 --- a/src/jalview/appletgui/OverviewPanel.java +++ b/src/jalview/appletgui/OverviewPanel.java @@ -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); } } diff --git a/src/jalview/appletgui/ScalePanel.java b/src/jalview/appletgui/ScalePanel.java index ed07b63..15d82a5 100755 --- a/src/jalview/appletgui/ScalePanel.java +++ b/src/jalview/appletgui/ScalePanel.java @@ -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); } diff --git a/src/jalview/appletgui/SeqCanvas.java b/src/jalview/appletgui/SeqCanvas.java index 5d6bb07..89df11f 100755 --- a/src/jalview/appletgui/SeqCanvas.java +++ b/src/jalview/appletgui/SeqCanvas.java @@ -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); diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index 1352fe9..0e12703 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -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); } /** diff --git a/src/jalview/appletgui/SequenceRenderer.java b/src/jalview/appletgui/SequenceRenderer.java index 86d1f98..78ed4a3 100755 --- a/src/jalview/appletgui/SequenceRenderer.java +++ b/src/jalview/appletgui/SequenceRenderer.java @@ -20,10 +20,10 @@ */ 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; } diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 48c1ee9..da3cb92 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -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) diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index a6f2bf4..2dafc0c 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -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( diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index bbd3ce4..6117baf 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -20,10 +20,6 @@ */ 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++) diff --git a/src/jalview/datamodel/AlignmentI.java b/src/jalview/datamodel/AlignmentI.java index 5d185cd..2abb1f8 100755 --- a/src/jalview/datamodel/AlignmentI.java +++ b/src/jalview/datamodel/AlignmentI.java @@ -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 hiddenCols); + } diff --git a/src/jalview/datamodel/ColumnSelection.java b/src/jalview/datamodel/ColumnSelection.java index 98a7fe2..97bc5a3 100644 --- a/src/jalview/datamodel/ColumnSelection.java +++ b/src/jalview/datamodel/ColumnSelection.java @@ -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) diff --git a/src/jalview/datamodel/HiddenSequences.java b/src/jalview/datamodel/HiddenSequences.java index 9e2cf72..6950c28 100755 --- a/src/jalview/datamodel/HiddenSequences.java +++ b/src/jalview/datamodel/HiddenSequences.java @@ -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 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) diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index e37c55e..1246d23 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -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"); diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index bf80831..94df99a 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -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); } /** diff --git a/src/jalview/ext/jmol/JmolCommands.java b/src/jalview/ext/jmol/JmolCommands.java index d5676c5..23e0a6f 100644 --- a/src/jalview/ext/jmol/JmolCommands.java +++ b/src/jalview/ext/jmol/JmolCommands.java @@ -20,16 +20,21 @@ */ 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 cset = new ArrayList(); + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + FeatureColourFinder finder = new FeatureColourFinder(fr); + AlignViewportI viewport = viewPanel.getAlignViewport(); + ColumnSelection cs = viewport.getColumnSelection(); + AlignmentI al = viewport.getAlignment(); + List cset = new ArrayList(); for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { @@ -74,9 +83,9 @@ public class JmolCommands for (int sp, m = 0; m < mapping.length; m++) { if (mapping[m].getSequence() == sequence[pdbfnum][s] - && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1) + && (sp = al.findIndex(sequence[pdbfnum][s])) > -1) { - SequenceI asp = alignment.getSequenceAt(sp); + SequenceI asp = al.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { // no mapping to gaps in sequence @@ -93,12 +102,17 @@ public class JmolCommands lastPos = pos; - Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r); + Color col = sr.getResidueColour(sequence[pdbfnum][s], r, + finder); - if (fr != null) + /* + * shade hidden regions darker + */ + if (!cs.isVisible(r)) { - col = fr.findFeatureColour(col, sequence[pdbfnum][s], r); + col = Color.GRAY; } + String newSelcom = (mapping[m].getChain() != " " ? ":" + mapping[m].getChain() : "") + "/" diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index c4aa2e7..95757fd 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -20,11 +20,15 @@ */ 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 colourMap = buildColoursMap(ssm, files, - sequence, sr, fr, alignment); + sequence, sr, viewPanel); List colourCommands = buildColourCommands(colourMap); @@ -173,9 +177,8 @@ public class ChimeraCommands /** *

-   * 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 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 colourMap = new LinkedHashMap();
     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> featureMap = buildFeaturesMap(
-            ssm, files, seqs, fr, alignment);
+            ssm, files, seqs, viewPanel);
 
     List 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> buildFeaturesMap(
           StructureSelectionManager ssm, String[] files,
-          SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment)
+          SequenceI[][] seqs, AlignmentViewPanel viewPanel)
   {
     Map> theMap = new LinkedHashMap>();
 
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    if (fr == null)
+    {
+      return theMap;
+    }
+
     List 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("\\'", "'");
+        sb.append("setattr r ").append(attributeName).append(" '")
+                .append(featureValue).append("' ");
         sb.append(values.get(value).getAtomSpec());
         commands.add(sb.toString());
       }
diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
index 2042ac4..ffce90c 100644
--- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
+++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
@@ -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 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);
 
       /*
diff --git a/src/jalview/ext/varna/VarnaCommands.java b/src/jalview/ext/varna/VarnaCommands.java
index a41e10e..d65f1d5 100644
--- a/src/jalview/ext/varna/VarnaCommands.java
+++ b/src/jalview/ext/varna/VarnaCommands.java
@@ -20,10 +20,10 @@
  */
 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 str = new ArrayList();
@@ -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() : "")
                       + "/"
diff --git a/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java b/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
index 27bfca8..a23df4c 100644
--- a/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
+++ b/src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
@@ -157,14 +157,14 @@ public class UniProtFTSRestClient extends FTSRestClient
     if (foundDataRow != null && foundDataRow.length > 0)
     {
       result = new ArrayList();
-      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);
diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java
index b5fc817..d4c87d8 100644
--- a/src/jalview/gui/AlignFrame.java
+++ b/src/jalview/gui/AlignFrame.java
@@ -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 alignPanels = new ArrayList();
@@ -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);
   }
 }
 
diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java
index e0efa7c..602e3a1 100644
--- a/src/jalview/gui/AlignViewport.java
+++ b/src/jalview/gui/AlignViewport.java
@@ -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());
   }
 
diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java
index e61b042..8ade5d6 100644
--- a/src/jalview/gui/AlignmentPanel.java
+++ b/src/jalview/gui/AlignmentPanel.java
@@ -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();
+    }
+  }
 }
diff --git a/src/jalview/gui/AnnotationColourChooser.java b/src/jalview/gui/AnnotationColourChooser.java
index f6352d7..8500888 100644
--- a/src/jalview/gui/AnnotationColourChooser.java
+++ b/src/jalview/gui/AnnotationColourChooser.java
@@ -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 oldgroupColours;
+  private JButton defColours;
 
-  /**
-   * enabled if the user is dragging the slider - try to keep updates to a
-   * minimun
-   */
+  private Hashtable oldgroupColours;
 
-  JComboBox 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 threshold = new JComboBox();
+  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(
-            getAnnotationItems(seqAssociated.isSelected()));
+    Vector annotItems = getAnnotationItems(seqAssociated
+            .isSelected());
+    annotations = new JComboBox(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 getThreshold()
+  public void originalColours_actionPerformed()
   {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox 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;
+  }
+
 }
diff --git a/src/jalview/gui/AnnotationColumnChooser.java b/src/jalview/gui/AnnotationColumnChooser.java
index 1290d70..637eb30 100644
--- a/src/jalview/gui/AnnotationColumnChooser.java
+++ b/src/jalview/gui/AnnotationColumnChooser.java
@@ -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 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 threshold = new JComboBox();
-
   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 getThreshold()
-  {
-    return threshold;
-  }
-
-  public void setThreshold(JComboBox threshold)
-  {
-    this.threshold = threshold;
-  }
-
-  public JComboBox getAnnotations()
-  {
-    return annotations;
-  }
-
-  public void setAnnotations(JComboBox 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();
   }
diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java
index 4b774d6..c9535d0 100755
--- a/src/jalview/gui/AnnotationLabels.java
+++ b/src/jalview/gui/AnnotationLabels.java
@@ -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);
diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java
index b1f0edb..84f3e6c 100755
--- a/src/jalview/gui/AnnotationPanel.java
+++ b/src/jalview/gui/AnnotationPanel.java
@@ -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
diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java
index 7705bc3..c2bf41b 100644
--- a/src/jalview/gui/AnnotationRowFilter.java
+++ b/src/jalview/gui/AnnotationRowFilter.java
@@ -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 threshold = new JComboBox();
+
+  protected JComboBox annotations;
+
+  /*
+   * map from annotation to its menu item display label
+   * - so we know which item to pre-select on restore
+   */
+  private Map 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 getAnnotationItems(boolean isSeqAssociated)
   {
+    annotationLabels = new HashMap();
+
     Vector list = new Vector();
     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 threshold)
+  protected void populateThresholdComboBox(JComboBox 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 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 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 getThreshold()
   {
-    return currentAnnotation;
+    return threshold;
   }
 
-  public void setCurrentAnnotation(
-          jalview.datamodel.AlignmentAnnotation currentAnnotation)
+  public void setThreshold(JComboBox thresh)
   {
-    this.currentAnnotation = currentAnnotation;
+    this.threshold = thresh;
   }
 
-  public abstract void valueChanged(boolean updateAllAnnotation);
-
-  public abstract void updateView();
+  public JComboBox getAnnotations()
+  {
+    return annotations;
+  }
 
-  public abstract void reset();
+  public void setAnnotations(JComboBox anns)
+  {
+    this.annotations = anns;
+  }
 }
diff --git a/src/jalview/gui/AppJmolBinding.java b/src/jalview/gui/AppJmolBinding.java
index 75e0c5e..f822358 100644
--- a/src/jalview/gui/AppJmolBinding.java
+++ b/src/jalview/gui/AppJmolBinding.java
@@ -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;
+  }
 }
diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java
index 67eddca..ec9feb7 100644
--- a/src/jalview/gui/ChimeraViewFrame.java
+++ b/src/jalview/gui/ChimeraViewFrame.java
@@ -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;
   }
 }
diff --git a/src/jalview/gui/ColourMenuHelper.java b/src/jalview/gui/ColourMenuHelper.java
index 31780d6..b2b9574 100644
--- a/src/jalview/gui/ColourMenuHelper.java
+++ b/src/jalview/gui/ColourMenuHelper.java
@@ -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
    * 
  • Clustal
  • *
  • ...other 'built-in' colours
  • *
  • ...any user-defined colours
  • - *
  • User Defined..
  • + *
  • User Defined..(only for AlignFrame menu)
  • * * * @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 colourSchemes = ColourSchemes.getInstance() @@ -103,14 +110,14 @@ public class ColourMenuHelper /* * user-defined colour scheme loaded on startup or during the * Jalview session; right-click on this offers the option to - * remove it as a colour choice + * remove it as a colour choice (unless currently selected) */ radioItem.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { - if (evt.isPopupTrigger()) // Mac + if (evt.isPopupTrigger() && !radioItem.isSelected()) // Mac { offerRemoval(); } @@ -119,7 +126,7 @@ public class ColourMenuHelper @Override public void mouseReleased(MouseEvent evt) { - if (evt.isPopupTrigger()) // Windows + if (evt.isPopupTrigger() && !radioItem.isSelected()) // Windows { offerRemoval(); } @@ -170,6 +177,7 @@ public class ColourMenuHelper final String label = MessageManager.getString("action.user_defined"); JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem( label); + userDefinedColour.setName(ResidueColourScheme.USER_DEFINED); userDefinedColour.addActionListener(new ActionListener() { @Override @@ -186,20 +194,22 @@ public class ColourMenuHelper } /** - * Marks as selected the colour menu item matching the given name, or the - * first item ('None') if no match is found + * Marks as selected the colour menu item matching the given colour scheme, or + * the first item ('None') if no match is found. If the colour scheme is a + * user defined scheme, but not in the menu (this arises if a new scheme is + * defined and applied but not saved to file), then menu option + * "User Defined.." is selected. * * @param colourMenu - * @param colourName + * @param cs */ - public static void setColourSelected(JMenu colourMenu, String colourName) + public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs) { - if (colourName == null) - { - return; - } + String colourName = cs == null ? ResidueColourScheme.NONE : cs + .getSchemeName(); JRadioButtonMenuItem none = null; + JRadioButtonMenuItem userDefined = null; /* * select the radio button whose name matches the colour name @@ -209,38 +219,39 @@ public class ColourMenuHelper { if (menuItem instanceof JRadioButtonMenuItem) { - String buttonName = ((JRadioButtonMenuItem) menuItem).getName(); - if (colourName.equals(buttonName)) + JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) menuItem; + String buttonName = radioButton.getName(); + if (buttonName.equals(colourName)) { - ((JRadioButtonMenuItem) menuItem).setSelected(true); + radioButton.setSelected(true); return; } if (ResidueColourScheme.NONE.equals(buttonName)) { - none = (JRadioButtonMenuItem) menuItem; + none = radioButton; + } + if (ResidueColourScheme.USER_DEFINED.equals(buttonName)) + { + userDefined = radioButton; } } } - if (none != null) + + /* + * no match by name; select User Defined.. if current scheme is a + * user defined one, else select None + */ + if (cs instanceof UserColourScheme && userDefined != null) + { + userDefined.setSelected(true); + } + else if (none != null) { none.setSelected(true); } } /** - * Marks as selected the colour menu item matching the given colour scheme, or - * the first item ('None') if no match is found - * - * @param colourMenu - * @param cs - */ - public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs) - { - setColourSelected(colourMenu, cs == null ? ResidueColourScheme.NONE - : cs.getSchemeName()); - } - - /** * Updates the USER_DEFINE_COLOURS preference to remove any de-registered * colour scheme */ @@ -258,7 +269,7 @@ public class ColourMenuHelper { try { - UserColourScheme ucs = ColourSchemes.loadColourScheme(file); + UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file); if (ucs != null && ColourSchemes.getInstance().nameExists(ucs.getName())) { diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index dc16a57..7d0eb7f 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -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; { diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index ed6a3c5..f519f99 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -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", " "); diff --git a/src/jalview/gui/IdCanvas.java b/src/jalview/gui/IdCanvas.java index 37be8bc..aad0776 100755 --- a/src/jalview/gui/IdCanvas.java +++ b/src/jalview/gui/IdCanvas.java @@ -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); diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java index 6ae19f0..2074900 100755 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@ -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) diff --git a/src/jalview/gui/JDatabaseTree.java b/src/jalview/gui/JDatabaseTree.java index 2a3d788..d92f6c0 100644 --- a/src/jalview/gui/JDatabaseTree.java +++ b/src/jalview/gui/JDatabaseTree.java @@ -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); diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 3ac453f..c19f005 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -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 userColours = new ArrayList(); 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 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; diff --git a/src/jalview/gui/Jalview2XML_V1.java b/src/jalview/gui/Jalview2XML_V1.java index e751a2c..6235cbe 100755 --- a/src/jalview/gui/Jalview2XML_V1.java +++ b/src/jalview/gui/Jalview2XML_V1.java @@ -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()); diff --git a/src/jalview/gui/JalviewChimeraBindingModel.java b/src/jalview/gui/JalviewChimeraBindingModel.java index a1f05bd..c9b35d8 100644 --- a/src/jalview/gui/JalviewChimeraBindingModel.java +++ b/src/jalview/gui/JalviewChimeraBindingModel.java @@ -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 diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 1c48690..c530fdc 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -20,7 +20,10 @@ */ 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); } } diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 81c3d4f..660c651 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -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(); diff --git a/src/jalview/gui/ScalePanel.java b/src/jalview/gui/ScalePanel.java index 8961f21..de21be6 100755 --- a/src/jalview/gui/ScalePanel.java +++ b/src/jalview/gui/ScalePanel.java @@ -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, diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index d015292..64e5fdc 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -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); diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 37b4852..db7aa36 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -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); } diff --git a/src/jalview/gui/SequenceRenderer.java b/src/jalview/gui/SequenceRenderer.java index 95c3261..1c0420d 100755 --- a/src/jalview/gui/SequenceRenderer.java +++ b/src/jalview/gui/SequenceRenderer.java @@ -20,10 +20,10 @@ */ 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; } diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 44a429c..d7f7c31 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -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); diff --git a/src/jalview/gui/TextColourChooser.java b/src/jalview/gui/TextColourChooser.java index 49fdaf7..91e05c6 100644 --- a/src/jalview/gui/TextColourChooser.java +++ b/src/jalview/gui/TextColourChooser.java @@ -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 groupColour1; + + Map groupColour2; + + Map 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(); + groupColour2 = new HashMap(); + groupThreshold = new HashMap(); + + 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(); } } diff --git a/src/jalview/gui/TreeCanvas.java b/src/jalview/gui/TreeCanvas.java index 54eed1a..9a38d4c 100755 --- a/src/jalview/gui/TreeCanvas.java +++ b/src/jalview/gui/TreeCanvas.java @@ -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); diff --git a/src/jalview/gui/UserDefinedColours.java b/src/jalview/gui/UserDefinedColours.java index 83a8d24..f75a0a3 100755 --- a/src/jalview/gui/UserDefinedColours.java +++ b/src/jalview/gui/UserDefinedColours.java @@ -20,15 +20,14 @@ */ 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 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 upperCaseButtons; List 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(); @@ -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. + *
      + *
    • Open a file chooser to browse for files with extension .jc
    • + *
    • Load in the colour scheme and transfer it to this panel's buttons
    • + *
    • Register the loaded colour scheme
    • + *
    */ @Override - protected void loadbutton_actionPerformed(ActionEvent e) + protected void loadbutton_actionPerformed() { upperCaseButtons = new ArrayList(); lowerCaseButtons = new ArrayList(); @@ -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. + *
      + *
    • Check a name has been entered
    • + *
    • Warn if the name already exists, remove any existing scheme of the same + * name if overwriting
    • + *
    • Do the standard file chooser thing to write with extension .jc
    • + *
    • 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
    • + *
    • 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
    • + *
    + * 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); diff --git a/src/jalview/io/JSONFile.java b/src/jalview/io/JSONFile.java index 27ebe5a..583bbc0 100644 --- a/src/jalview/io/JSONFile.java +++ b/src/jalview/io/JSONFile.java @@ -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(); diff --git a/src/jalview/javascript/MouseOverStructureListener.java b/src/jalview/javascript/MouseOverStructureListener.java index 4f833bc..7580222 100644 --- a/src/jalview/javascript/MouseOverStructureListener.java +++ b/src/jalview/javascript/MouseOverStructureListener.java @@ -221,8 +221,8 @@ public class MouseOverStructureListener extends JSFunctionExec implements ArrayList ccomands = new ArrayList(); ArrayList pdbfn = new ArrayList(); 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; diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index b39f4a8..b759d64 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -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 diff --git a/src/jalview/jbgui/GStructureViewer.java b/src/jalview/jbgui/GStructureViewer.java index 5c7a31a..d8f3f61 100644 --- a/src/jalview/jbgui/GStructureViewer.java +++ b/src/jalview/jbgui/GStructureViewer.java @@ -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) { diff --git a/src/jalview/jbgui/GUserDefinedColours.java b/src/jalview/jbgui/GUserDefinedColours.java index aa5319c..5384cc0 100755 --- a/src/jalview/jbgui/GUserDefinedColours.java +++ b/src/jalview/jbgui/GUserDefinedColours.java @@ -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() { } diff --git a/src/jalview/renderer/AnnotationRenderer.java b/src/jalview/renderer/AnnotationRenderer.java index 6f84a2e..3a27c7d 100644 --- a/src/jalview/renderer/AnnotationRenderer.java +++ b/src/jalview/renderer/AnnotationRenderer.java @@ -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 index 0000000..1db2004 --- /dev/null +++ b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java @@ -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. + *

    + * 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; + } +} diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 9e0089f..72ac2c8 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -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; + } } diff --git a/src/jalview/schemes/AnnotationColourGradient.java b/src/jalview/schemes/AnnotationColourGradient.java index 1a3e9ef..220d3ab 100755 --- a/src/jalview/schemes/AnnotationColourGradient.java +++ b/src/jalview/schemes/AnnotationColourGradient.java @@ -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 seqannot = null; + private IdentityHashMap 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 index 0000000..8660f3e --- /dev/null +++ b/src/jalview/schemes/ColourSchemeLoader.java @@ -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; + } + +} diff --git a/src/jalview/schemes/ColourSchemes.java b/src/jalview/schemes/ColourSchemes.java index 817fb01..269811b 100644 --- a/src/jalview/schemes/ColourSchemes.java +++ b/src/jalview/schemes/ColourSchemes.java @@ -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 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()); } } diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index 7519538..3ab642f 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -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 atoms) { - SearchResults results = new SearchResults(); + SearchResultsI results = new SearchResults(); for (AtomSpec atom : atoms) { SequenceI lastseq = null; diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index fda08fd..84475fe 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -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); } diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 94d0dd1..47dceec 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -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 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 index 0000000..43680b5 --- /dev/null +++ b/src/jalview/viewmodel/OverviewDimensions.java @@ -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 . + * 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 index 0000000..246806e --- /dev/null +++ b/src/jalview/viewmodel/ViewportProperties.java @@ -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 . + * 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 index 0000000..c91d2d9 --- /dev/null +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -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 . + * 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; + } +} diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 8468329..84c9477 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -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 getGroups(boolean visible) { if (featureGroups != null) { diff --git a/src/jalview/workers/ConservationThread.java b/src/jalview/workers/ConservationThread.java index e71c4f5..571234c 100644 --- a/src/jalview/workers/ConservationThread.java +++ b/src/jalview/workers/ConservationThread.java @@ -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); diff --git a/src/jalview/ws/DasSequenceFeatureFetcher.java b/src/jalview/ws/DasSequenceFeatureFetcher.java index 676a4b6..4d3dd2f 100644 --- a/src/jalview/ws/DasSequenceFeatureFetcher.java +++ b/src/jalview/ws/DasSequenceFeatureFetcher.java @@ -617,8 +617,8 @@ public class DasSequenceFeatureFetcher } af.getFeatureRenderer().featuresAdded(); - int start = af.getViewport().getStartSeq(); - int end = af.getViewport().getEndSeq(); + int start = af.getViewport().getRanges().getStartSeq(); + int end = af.getViewport().getRanges().getEndSeq(); int index; for (index = start; index < end; index++) { diff --git a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java b/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java index b184ff2..e0f7f70 100644 --- a/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java +++ b/src/jalview/ws/dbsources/das/datamodel/DasSourceRegistry.java @@ -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(); } } diff --git a/src/jalview/ws/jws2/Jws2Discoverer.java b/src/jalview/ws/jws2/Jws2Discoverer.java index 1b2c708..12a08a0 100644 --- a/src/jalview/ws/jws2/Jws2Discoverer.java +++ b/src/jalview/ws/jws2/Jws2Discoverer.java @@ -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 invalidServiceUrls = null, urlsWithoutServices = null, - validServiceUrls = null; + private Vector invalidServiceUrls = null; - boolean running = false, aborted = false; + private Vector urlsWithoutServices = null; - Thread oldthread = null; + private Vector 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()); 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(), services); } /** @@ -321,7 +329,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI service.hasParameters(); if (validServiceUrls == null) { - validServiceUrls = new Vector(); + validServiceUrls = new Vector(); } 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(), 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 hostLabels = new ArrayList(); Hashtable lasthostFor = new Hashtable(); Hashtable> hosts = new Hashtable>(); @@ -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); diff --git a/src/jalview/ws/sifts/SiftsClient.java b/src/jalview/ws/sifts/SiftsClient.java index ea65125..c69581f 100644 --- a/src/jalview/ws/sifts/SiftsClient.java +++ b/src/jalview/ws/sifts/SiftsClient.java @@ -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; } diff --git a/test/jalview/datamodel/AlignmentTest.java b/test/jalview/datamodel/AlignmentTest.java index 7af77f5..c5d09c1 100644 --- a/test/jalview/datamodel/AlignmentTest.java +++ b/test/jalview/datamodel/AlignmentTest.java @@ -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); + } } diff --git a/test/jalview/datamodel/ColumnSelectionTest.java b/test/jalview/datamodel/ColumnSelectionTest.java index 1d819c9..4d3f611 100644 --- a/test/jalview/datamodel/ColumnSelectionTest.java +++ b/test/jalview/datamodel/ColumnSelectionTest.java @@ -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); + } /** diff --git a/test/jalview/datamodel/HiddenSequencesTest.java b/test/jalview/datamodel/HiddenSequencesTest.java index cae3536..7795988 100644 --- a/test/jalview/datamodel/HiddenSequencesTest.java +++ b/test/jalview/datamodel/HiddenSequencesTest.java @@ -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") diff --git a/test/jalview/datamodel/SequenceGroupTest.java b/test/jalview/datamodel/SequenceGroupTest.java index 65549f2..6e1c2db 100644 --- a/test/jalview/datamodel/SequenceGroupTest.java +++ b/test/jalview/datamodel/SequenceGroupTest.java @@ -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" }) diff --git a/test/jalview/ext/jmol/JmolCommandsTest.java b/test/jalview/ext/jmol/JmolCommandsTest.java index 439e188..3309adf 100644 --- a/test/jalview/ext/jmol/JmolCommandsTest.java +++ b/test/jalview/ext/jmol/JmolCommandsTest.java @@ -20,16 +20,24 @@ */ 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 map = new HashMap(); + 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]")); } } diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java index fb442e3..2c973ca 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java @@ -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, + "metal '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_ 'metal '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 map = new HashMap(); + 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 index 0000000..b228ba1 --- /dev/null +++ b/test/jalview/gui/AlignmentPanelTest.java @@ -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 . + * 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 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 + // 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 index 0000000..69a41c5 --- /dev/null +++ b/test/jalview/gui/AnnotationRowFilterTest.java @@ -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 items = testee.getAnnotationItems(false); + assertEquals( + items.toString(), + "[Conservation, Quality, Consensus, Occupancy, ann1Label, Significance, Significance_1, Jronn_FER_CAPAA, Jronn_FER_BRANA, Jnet_FER_BRANA, Jnet_FER_BRANA_2]"); + assertEquals(testee.getAnnotationMenuLabel(ann1), "ann1Label"); + assertEquals(testee.getAnnotationMenuLabel(ann2), "Significance"); + assertEquals(testee.getAnnotationMenuLabel(ann2a), "Significance_1"); + assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn_FER_CAPAA"); + assertEquals(testee.getAnnotationMenuLabel(ann4), "Jronn_FER_BRANA"); + assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet_FER_BRANA"); + assertEquals(testee.getAnnotationMenuLabel(ann6), "Jnet_FER_BRANA_2"); + + /* + * drop-down items with 'Per-sequence only' checked + */ + items = testee.getAnnotationItems(true); + assertEquals(items.toString(), "[Jronn, Jnet]"); + // the first annotation of the type is associated with the menu item + assertEquals(testee.getAnnotationMenuLabel(ann3), "Jronn"); + assertEquals(testee.getAnnotationMenuLabel(ann5), "Jnet"); + } +} diff --git a/test/jalview/gui/SequenceRendererTest.java b/test/jalview/gui/SequenceRendererTest.java index 81289b0..29a9a52 100644 --- a/test/jalview/gui/SequenceRendererTest.java +++ b/test/jalview/gui/SequenceRendererTest.java @@ -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... diff --git a/test/jalview/io/JSONFileTest.java b/test/jalview/io/JSONFileTest.java index a8611cc..4e4abe9 100644 --- a/test/jalview/io/JSONFileTest.java +++ b/test/jalview/io/JSONFileTest.java @@ -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 index 0000000..59566ed --- /dev/null +++ b/test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java @@ -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 + *

    + */ +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 index 0000000..1c93856 --- /dev/null +++ b/test/jalview/schemes/AnnotationColourGradientTest.java @@ -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 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 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); + } + } +} diff --git a/test/jalview/schemes/ColourSchemesTest.java b/test/jalview/schemes/ColourSchemesTest.java index 4618ed7..0aaa38c 100644 --- a/test/jalview/schemes/ColourSchemesTest.java +++ b/test/jalview/schemes/ColourSchemesTest.java @@ -228,9 +228,9 @@ public class ColourSchemesTest * set and check Taylor colours */ af.changeColour_actionPerformed(JalviewColourScheme.Taylor.toString()); - Color taylor1 = sr.getResidueBoxColour(seq, 88); // E 255,0,102 - Color taylor2 = sr.getResidueBoxColour(seq, 89); // A 204,255,0 - Color taylor3 = sr.getResidueBoxColour(seq, 90); // G 255,153,0 + Color taylor1 = sr.getResidueColour(seq, 88, null); // E 255,0,102 + Color taylor2 = sr.getResidueColour(seq, 89, null); // A 204,255,0 + Color taylor3 = sr.getResidueColour(seq, 90, null); // G 255,153,0 assertEquals(taylor1, new Color(255, 0, 102)); assertEquals(taylor2, new Color(204, 255, 0)); assertEquals(taylor3, new Color(255, 153, 0)); @@ -239,9 +239,9 @@ public class ColourSchemesTest * set and check Zappo colours */ af.changeColour_actionPerformed(JalviewColourScheme.Zappo.toString()); - Color zappo1 = sr.getResidueBoxColour(seq, 88); // E red - Color zappo2 = sr.getResidueBoxColour(seq, 89); // A pink - Color zappo3 = sr.getResidueBoxColour(seq, 90); // G magenta + Color zappo1 = sr.getResidueColour(seq, 88, null); // E red + Color zappo2 = sr.getResidueColour(seq, 89, null); // A pink + Color zappo3 = sr.getResidueColour(seq, 90, null); // G magenta assertEquals(zappo1, Color.red); assertEquals(zappo2, Color.pink); assertEquals(zappo3, Color.magenta); @@ -250,9 +250,9 @@ public class ColourSchemesTest * set 'stripy' colours - odd columns are Taylor and even are Zappo */ af.changeColour_actionPerformed("stripy"); - Color stripy1 = sr.getResidueBoxColour(seq, 88); - Color stripy2 = sr.getResidueBoxColour(seq, 89); - Color stripy3 = sr.getResidueBoxColour(seq, 90); + Color stripy1 = sr.getResidueColour(seq, 88, null); + Color stripy2 = sr.getResidueColour(seq, 89, null); + Color stripy3 = sr.getResidueColour(seq, 90, null); assertEquals(stripy1, zappo1); assertEquals(stripy2, taylor2); assertEquals(stripy3, zappo3); @@ -261,9 +261,9 @@ public class ColourSchemesTest * set and check Clustal colours */ af.changeColour_actionPerformed(JalviewColourScheme.Clustal.toString()); - Color clustal1 = sr.getResidueBoxColour(seq, 88); - Color clustal2 = sr.getResidueBoxColour(seq, 89); - Color clustal3 = sr.getResidueBoxColour(seq, 90); + Color clustal1 = sr.getResidueColour(seq, 88, null); + Color clustal2 = sr.getResidueColour(seq, 89, null); + Color clustal3 = sr.getResidueColour(seq, 90, null); assertEquals(clustal1, ClustalColour.MAGENTA.colour); assertEquals(clustal2, ClustalColour.BLUE.colour); assertEquals(clustal3, ClustalColour.ORANGE.colour); @@ -272,9 +272,9 @@ public class ColourSchemesTest * set 'MyClustal' colours - uses AWT colour equivalents */ af.changeColour_actionPerformed("MyClustal"); - Color myclustal1 = sr.getResidueBoxColour(seq, 88); - Color myclustal2 = sr.getResidueBoxColour(seq, 89); - Color myclustal3 = sr.getResidueBoxColour(seq, 90); + Color myclustal1 = sr.getResidueColour(seq, 88, null); + Color myclustal2 = sr.getResidueColour(seq, 89, null); + Color myclustal3 = sr.getResidueColour(seq, 90, null); assertEquals(myclustal1, Color.MAGENTA); assertEquals(myclustal2, Color.BLUE); assertEquals(myclustal3, Color.ORANGE); diff --git a/test/jalview/structures/models/AAStructureBindingModelTest.java b/test/jalview/structures/models/AAStructureBindingModelTest.java index 0422537..7ba22b4 100644 --- a/test/jalview/structures/models/AAStructureBindingModelTest.java +++ b/test/jalview/structures/models/AAStructureBindingModelTest.java @@ -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 index 0000000..398fec3 --- /dev/null +++ b/test/jalview/viewmodel/OverviewDimensionsTest.java @@ -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 . + * 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 hiddenRepSequences = new Hashtable(); + + 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 index 0000000..cfd03cd --- /dev/null +++ b/test/jalview/viewmodel/ViewportRangesTest.java @@ -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); + } +} diff --git a/test/jalview/ws/jabaws/DisorderAnnotExportImport.java b/test/jalview/ws/jabaws/DisorderAnnotExportImport.java index 2d317e4..2714d6c 100644 --- a/test/jalview/ws/jabaws/DisorderAnnotExportImport.java +++ b/test/jalview/ws/jabaws/DisorderAnnotExportImport.java @@ -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(); 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); diff --git a/test/jalview/ws/jabaws/RNAStructExportImport.java b/test/jalview/ws/jabaws/RNAStructExportImport.java index f1430f6..4e9741e 100644 --- a/test/jalview/ws/jabaws/RNAStructExportImport.java +++ b/test/jalview/ws/jabaws/RNAStructExportImport.java @@ -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 opts = new ArrayList(); diff --git a/test/jalview/ws/jws2/ParameterUtilsTest.java b/test/jalview/ws/jws2/ParameterUtilsTest.java index 0662e5b..e859cb0 100644 --- a/test/jalview/ws/jws2/ParameterUtilsTest.java +++ b/test/jalview/ws/jws2/ParameterUtilsTest.java @@ -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()) diff --git a/test/jalview/ws/seqfetcher/DasSequenceFetcher.java b/test/jalview/ws/seqfetcher/DasSequenceFetcher.java index 98ca303..f1dafcb 100644 --- a/test/jalview/ws/seqfetcher/DasSequenceFetcher.java +++ b/test/jalview/ws/seqfetcher/DasSequenceFetcher.java @@ -20,9 +20,11 @@ */ 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."); } } diff --git a/test/jalview/ws/sifts/SiftsClientTest.java b/test/jalview/ws/sifts/SiftsClientTest.java index d805e47..7f8adc9 100644 --- a/test/jalview/ws/sifts/SiftsClientTest.java +++ b/test/jalview/ws/sifts/SiftsClientTest.java @@ -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> it = expectedMapping.entrySet() + .iterator(); + while (it.hasNext()) + { + Map.Entry 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> it = expectedMapping.entrySet() + .iterator(); + while (it.hasNext()) + { + Map.Entry 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 index 0000000..57c873f --- /dev/null +++ b/test/junit/extensions/PA.java @@ -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. + *

    + * a.k.a. The "ObjectMolester" + *

    + * Here is an example of using this to access a private member:
    + * Given the following class MyClass:
    + * + *

    + * public class MyClass {
    + *    private String name; // private attribute
    + * 
    + *    // private constructor
    + *    private MyClass() {
    + *       super();
    + *    }
    + * 
    + *    // private method
    + *    private void setName(String newName) {
    + *       this.name = newName;
    + *    }
    + * }
    + * 
    + * + * We now want to access the class:
    + * + *
    + * MyClass myObj = PA.instantiate(MyClass.class);
    + * PA.invokeMethod(myObj, "setName(java.lang.String)", "myNewName");
    + * String name = PA.getValue(myObj, "name");
    + * 
    + * + * 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: " {}" + * whereas is a comma separated list with = 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 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 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 instantiate(final Class 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 instantiate(final Class 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
    + * (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 methodSignature 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
    + * (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 methodSignature 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.
    + *
    + * Example:
    + *
    + * + * String myString = "Test";
    + *
    + * //setting the private field value
    + * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
    + *
    + * //setting the static final field serialVersionUID - MIGHT FAIL
    + * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
    + *
    + *
    + * + * @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 + * fieldName 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.
    + *
    + * Example:
    + *
    + * + * String myString = "Test";
    + *
    + * //setting the private field value
    + * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
    + *
    + * //setting the static final field serialVersionUID - MIGHT FAIL
    + * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
    + *
    + *
    + * + * @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 + * fieldName 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 index 0000000..23f1c6e --- /dev/null +++ b/test/junit/extensions/PrivilegedAccessor.java @@ -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. + *

    + * a.k.a. The "ObjectMolester" + *

    + * Here is an example of using this to access a private member:
    + * myObject is an object of type MyClass. setName(String) is a private method of + * MyClass. + * + *

    + * PrivilegedAccessor.invokeMethod(myObject, "setName(java.lang.String)", "newName");
    + * 
    + * + * @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: " {}" + * whereas is a comma separated list with = 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 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 getFieldNames(final Object instanceOrClass) { + if (instanceOrClass == null) + { + return Collections.EMPTY_LIST; + } + + Class clazz = getClass(instanceOrClass); + Field[] fields = clazz.getDeclaredFields(); + Collection fieldNames = new ArrayList(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 getMethodSignatures(final Object instanceOrClass) { + if (instanceOrClass == null) + { + return Collections.EMPTY_LIST; + } + + Class clazz = getClass(instanceOrClass); + Method[] methods = clazz.getDeclaredMethods(); + Collection methodSignatures = new ArrayList(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 instantiate(final Class 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 instantiate(final Class 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
    + * (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 methodSignature 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.
    + *
    + * Example:
    + *
    + * + * String myString = "Test";
    + *
    + * //setting the private field value
    + * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});
    + *
    + * //setting the static final field serialVersionUID - MIGHT FAIL
    + * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);
    + *
    + *
    + * + * @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 fieldName 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> PRIMITIVE_MAPPER = new HashMap>(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 Constructor getConstructor(final Class type, final Class[] parameterTypes) throws NoSuchMethodException { + Constructor 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; + } + } + +}