From 9e926ac4305fd9dff38b6e079e55b4f50664d544 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Wed, 22 Nov 2017 15:36:06 +0000 Subject: [PATCH] JAL-2069 spike updated with latest (FeatureTypeSettings) --- help/helpTOC.xml | 1 - help/html/calculations/sorting.html | 3 +- help/html/features/viewingpdbs.html | 2 +- help/html/releases.html | 261 +++- help/html/whatsNew.html | 35 +- resources/lang/Messages.properties | 31 +- resources/lang/Messages_es.properties | 46 +- src/jalview/api/FeatureColourI.java | 12 - src/jalview/appletgui/AnnotationPanel.java | 9 + src/jalview/appletgui/FeatureColourChooser.java | 8 +- src/jalview/appletgui/FeatureSettings.java | 19 +- src/jalview/appletgui/IdCanvas.java | 9 + src/jalview/appletgui/ScalePanel.java | 4 +- src/jalview/appletgui/SeqCanvas.java | 30 +- src/jalview/appletgui/SeqPanel.java | 61 +- src/jalview/datamodel/Alignment.java | 30 +- src/jalview/datamodel/AlignmentAnnotation.java | 68 + src/jalview/datamodel/ContiguousI.java | 20 + src/jalview/datamodel/Range.java | 20 + src/jalview/datamodel/Sequence.java | 87 +- src/jalview/datamodel/SequenceCursor.java | 20 + src/jalview/datamodel/SequenceGroup.java | 45 +- src/jalview/datamodel/SequenceI.java | 7 +- .../datamodel/features/FeatureAttributes.java | 45 +- .../datamodel/features/FeatureLocationI.java | 20 + src/jalview/datamodel/features/FeatureStore.java | 20 + src/jalview/datamodel/features/NCList.java | 20 + src/jalview/datamodel/features/NCNode.java | 20 + .../datamodel/features/RangeComparator.java | 20 + .../datamodel/features/SequenceFeatures.java | 20 + .../datamodel/features/SequenceFeaturesI.java | 20 + .../datamodel/xdb/uniprot/UniprotFeature.java | 20 + src/jalview/gui/AnnotationPanel.java | 9 + src/jalview/gui/CalculationChooser.java | 2 +- src/jalview/gui/FeatureColourChooser.java | 1006 ------------- src/jalview/gui/FeatureRenderer.java | 44 +- src/jalview/gui/FeatureSettings.java | 1143 ++++++--------- src/jalview/gui/FeatureTypeSettings.java | 1548 ++++++++++++++++++++ src/jalview/gui/IdCanvas.java | 9 + src/jalview/gui/JalviewDialog.java | 2 +- src/jalview/gui/JvSwingUtils.java | 7 +- src/jalview/gui/ScalePanel.java | 4 +- src/jalview/gui/SeqCanvas.java | 485 +++--- src/jalview/gui/SeqPanel.java | 52 +- src/jalview/gui/SequenceRenderer.java | 25 +- src/jalview/gui/StructureChooser.java | 2 +- src/jalview/schemes/FeatureColour.java | 28 +- src/jalview/util/IntRangeComparator.java | 20 + .../viewmodel/OverviewDimensionsHideHidden.java | 4 +- .../viewmodel/OverviewDimensionsShowHidden.java | 3 +- src/jalview/viewmodel/ViewportRanges.java | 201 ++- .../seqfeatures/FeatureRendererModel.java | 34 +- src/jalview/ws/dbsources/Uniprot.java | 15 +- test/jalview/datamodel/SequenceTest.java | 78 + .../datamodel/features/FeatureAttributesTest.java | 83 +- test/jalview/io/vcf/VCFLoaderTest.java | 65 +- .../renderer/seqfeatures/FeatureRendererTest.java | 185 +++ test/jalview/schemes/FeatureColourTest.java | 13 +- test/jalview/viewmodel/ViewportRangesTest.java | 169 ++- test/jalview/ws/dbsources/UniprotTest.java | 8 +- utils/InstallAnywhere/Jalview.iap_xml | 8 +- utils/MessageBundleChecker.java | 74 +- 62 files changed, 3988 insertions(+), 2371 deletions(-) delete mode 100644 src/jalview/gui/FeatureColourChooser.java create mode 100644 src/jalview/gui/FeatureTypeSettings.java diff --git a/help/helpTOC.xml b/help/helpTOC.xml index 7ba4ee5..20dd8db 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -24,7 +24,6 @@ - diff --git a/help/html/calculations/sorting.html b/help/html/calculations/sorting.html index aeb461a..a0412d0 100755 --- a/help/html/calculations/sorting.html +++ b/help/html/calculations/sorting.html @@ -46,8 +46,7 @@
  • Sort by Pairwise Identity

    -

    Places pairs of sequences together that align with the - greatest fraction of conserved residues.

    +

    Sorts sequences in the selection or alignment according to percent identity with respect to the first sequence in the view.

  • Sort by Tree Order diff --git a/help/html/features/viewingpdbs.html b/help/html/features/viewingpdbs.html index 0fcbbf9..45d979f 100755 --- a/help/html/features/viewingpdbs.html +++ b/help/html/features/viewingpdbs.html @@ -56,7 +56,7 @@

  • Viewing Cached Structures
    If previously downloaded structures are available for your sequences, the structure chooser will automatically offer them - via the Cached PDB Entries view. If you wish + via the Cached Structures view. If you wish to download new structures, select one of the PDBe selection criteria from the drop-down menu.
  • diff --git a/help/html/releases.html b/help/html/releases.html index 6396313..1a48340 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -70,8 +70,7 @@ li:before {
    - 2.10.3
    - 14/11/2017
    + 2.10.3
    17/11/2017
    @@ -89,83 +88,227 @@ li:before { Structure views don't get updated unless their colours have changed -
  • All linked sequences are highlighted for a structure mousover (Jmol) or selection (Chimera)
  • -
  • 'Cancel' button in progress bar for JABAWS AACon, RNAAliFold and Disorder prediction jobs +
  • + All linked sequences are highlighted for + a structure mousover (Jmol) or selection (Chimera) +
  • +
  • + 'Cancel' button in progress bar for + JABAWS AACon, RNAAliFold and Disorder prediction jobs +
  • +
  • + Stop codons are excluded in CDS/Protein + view from Ensembl locus cross-references +
  • +
  • + Start/End limits are shown in Pairwise + Alignment report +
  • +
  • + Sequence fetcher's Free text 'autosearch' + feature can be disabled +
  • +
  • + Retrieve IDs tab added for UniProt and + PDB easier retrieval of sequences for lists of IDs +
  • +
  • + Short names for sequences retrieved from + Uniprot
  • - -
  • Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references
  • -
  • Start/End limits are shown in Pairwise Alignment report
  • -
  • Sequence fetcher's Free text 'autosearch' feature can be disabled
  • -
  • Retrieve IDs tab added for UniProt and PDB easier retrieval of sequences for lists of IDs
  • - Scripting
      -
    • Groovy interpreter updated to 2.4.12
    • -
    • Example groovy script for generating a matrix of percent identity scores for current alignment.
    • +
    • Groovy interpreter updated to 2.4.12
    • +
    • Example groovy script for generating a matrix of + percent identity scores for current alignment.
    Testing and Deployment -
    • Test to catch memory leaks in Jalview UI
    -
    - +
      +
    • + Test to catch memory leaks in Jalview UI +
    • +
    +
    General
      -
    • Pressing tab after updating the colour threshold text field doesn't trigger an update to the alignment view
    • -
    • Race condition when parsing sequence ID strings in parallel
    • -
    • Overview windows are also closed when alignment window is closed
    • -
    • Export of features doesn't always respect group visibility
    • +
    • + Pressing tab after updating the colour + threshold text field doesn't trigger an update to the + alignment view +
    • +
    • + Race condition when parsing sequence ID + strings in parallel +
    • +
    • + Overview windows are also closed when + alignment window is closed +
    • +
    • + Export of features doesn't always respect + group visibility +
    • +
    • + Jumping from column 1 to column 100,000 + takes a long time in Cursor mode +
    Desktop
      -
    • Structures with whitespace chainCode cannot be viewed in Chimera
    • -
    • Protein annotation panel too high in CDS/Protein view -
    • -
    • Can't edit the query after the server error warning icon is shown in Uniprot and PDB Free Text Search Dialogs -
    • -
    • Slow EnsemblGenome ID lookup
    • -
    • Revised Ensembl REST API CDNA query
    • -
    • Hidden column marker in last column not rendered when switching back from Wrapped to normal view
    • -
    • Annotation display corrupted when scrolling right in unwapped alignment view
    • -
    • Existing features on subsequence incorrectly relocated when full sequence retrieved from database
    • -
    • Last reported memory still shown when Desktop->Show Memory is unticked (OSX only)
    • -
    • Amend Features dialog doesn't allow features of same type and group to be selected for amending
    • -
    • Jalview becomes sluggish in wide alignments when hidden columns are present
    • -
    • Jalview freezes when loading and displaying several structures
    • -
    • Black outlines left after resizing or moving a window
    • -
    • Unable to minimise windows within the Jalview desktop on OSX
    • -
    • Mouse wheel doesn't scroll vertically when in wrapped alignment mode
    • -
    • Scale mark not shown when close to right hand end of alignment
    • -
    • Pairwise alignment only aligns selected regions of each selected sequence
    • -
    • Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog
    • -
    • Show cross-references not enabled after restoring project until a new view is created
    • -
    • Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)
    • -
    • Overview redraws whole window when box position is adjusted
    • -
    • Structure viewer doesn't map all chains in a multi-chain structure when viewing alignment involving more than one chain (since 2.10)
    • -
    - Applet
    -
      -
    • Concurrent modification exception when closing alignment panel
    • +
    • + Structures with whitespace chainCode + cannot be viewed in Chimera +
    • +
    • + Protein annotation panel too high in + CDS/Protein view +
    • +
    • + Can't edit the query after the server + error warning icon is shown in Uniprot and PDB Free Text + Search Dialogs +
    • +
    • + Slow EnsemblGenome ID lookup +
    • +
    • + Revised Ensembl REST API CDNA query +
    • +
    • + Hidden column marker in last column not + rendered when switching back from Wrapped to normal view +
    • +
    • + Annotation display corrupted when + scrolling right in unwapped alignment view +
    • +
    • + Existing features on subsequence + incorrectly relocated when full sequence retrieved from + database +
    • +
    • + Last reported memory still shown when + Desktop->Show Memory is unticked (OSX only) +
    • +
    • + Amend Features dialog doesn't allow + features of same type and group to be selected for + amending +
    • +
    • + Jalview becomes sluggish in wide + alignments when hidden columns are present +
    • +
    • + Jalview freezes when loading and + displaying several structures +
    • +
    • + Black outlines left after resizing or + moving a window +
    • +
    • + Unable to minimise windows + within the Jalview desktop on OSX +
    • +
    • + Mouse wheel doesn't scroll vertically + when in wrapped alignment mode +
    • +
    • + Scale mark not shown when close to right + hand end of alignment +
    • +
    • + Pairwise alignment of selected regions of + each selected sequence do not have correct start/end + positions +
    • +
    • + Alignment ruler height set incorrectly + after canceling the Alignment Window's Font dialog +
    • +
    • + Show cross-references not enabled after + restoring project until a new view is created +
    • +
    • + Warning popup about use of SEQUENCE_ID in + URL links appears when only default EMBL-EBI link is + configured (since 2.10.2b2) +
    • +
    • + Overview redraws whole window when box + position is adjusted +
    • +
    • + Structure viewer doesn't map all chains + in a multi-chain structure when viewing alignment + involving more than one chain (since 2.10) +
    • +
    • + Double residue highlights in cursor mode + if new selection moves alignment window +
    • +
    • + Alignment vanishes when using + arrow key in cursor mode to pass hidden column marker +
    • +
    • + Ensembl Genomes example ID changed to one + that produces correctly annotated transcripts and products +
    • +
    • + Toggling a feature group after first time + doesn't update associated structure view +
    - BioJSON
    + Applet
      -
    • - BioJSON export does not preserve non-positional features -
    • +
    • + Concurrent modification exception when + closing alignment panel +
    - Known Java 9 Issues + BioJSON
      -
    • Groovy Console very slow to open and is - not responsive when entering characters (Webstart, Java 9.01, - OSX 10.10) +
    • + BioJSON export does not preserve + non-positional features
    - New Known Issues + New Known Issues
      -
    • +
    • + Delete/Cut selection doesn't relocate + sequence features correctly (for many previous versions of + Jalview) +
    • +
    • + Cursor mode unexpectedly scrolls when + using cursor in wrapped panel other than top +
    • +
    • + Select columns containing feature ignores + graduated colour threshold +
    • +
    • + Edit sequence operation doesn't + always preserve numbering and sequence features +
    -
    - + Known Java 9 Issues +
      +
    • + Groovy Console very slow to open and is + not responsive when entering characters (Webstart, Java + 9.01, OSX 10.10) +
    • +
    + diff --git a/help/html/whatsNew.html b/help/html/whatsNew.html index 9cf1044..4bf1cec 100755 --- a/help/html/whatsNew.html +++ b/help/html/whatsNew.html @@ -27,27 +27,32 @@ What's new in Jalview 2.10.3 ?

    - Version 2.10.3 was released in November 2017. The full list of - bug fixes and new features can be found in the 2.10.3 Release Notes, but - the highlights are below. + Version 2.10.3 was released in November 2017. The major focus was to + improve Jalview's sequence features datamodel and the scalability of + the alignment rendering system. The full list of bug fixes and new + features can be found in the 2.10.3 + Release Notes. Key improvements include:

      -
    • Faster import and more responsive UI when working with wide alignments and handling hundreds and thousands of sequence features
    • -
    • -
    • Improved usability with PDB and - UniProt Free Text Search - dialog, and new tab for retrieval of sequences for lists of IDs.
    • +
    • Faster and more responsive UI when importing and working + with wide alignments and handling hundreds and thousands of + sequence features
    • +
    • Improved usability with PDB and UniProt Free Text + Search dialog, and new tab for retrieval of sequences for lists of + IDs. +
    • +
    • Short names assigned to sequences retrieved from UniProt
    • +
    • Groovy console upgraded to 2.4.12 (improved support for Java 9)

    Experimental Features

    - This release of Jalview introduces an Experimental Features - option in the Jalview Desktop's Tools menu that allows you - to try out features that are still in development. To access the - experimental features below - first enable the Tools→Enable - Experimental Features option, and then restart Jalview. + Remember, please enable the Experimental Features option in + the Jalview Desktop's Tools menu, and then restart Jalview + if you want to try out features below:

    • Annotation transfer between Chimera and Jalview
      Two @@ -55,6 +60,6 @@ the Chimera viewer's Chimera menu allow positional annotation to be exchanged between Chimera and Jalview.
    - + diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 9f1c71b..a592282 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -242,7 +242,6 @@ label.documentation = Documentation label.about = About... label.show_sequence_limits = Show Sequence Limits action.feature_settings = Feature Settings... -label.feature_settings = Feature Settings label.all_columns = All Columns label.all_sequences = All Sequences label.selected_columns = Selected Columns @@ -282,9 +281,9 @@ label.selection = Selection label.group_colour = Group Colour label.sequence = Sequence label.view_pdb_structure = View PDB Structure -label.min_value = Min value: -label.max_value = Max value: -label.no_value = No value: +label.min_value = Min value +label.max_value = Max value +label.no_value = No value label.new_feature = New Feature label.match_case = Match Case label.view_alignment_editor = View in alignment editor @@ -533,7 +532,6 @@ label.threshold_feature_above_threshold = Above Threshold label.threshold_feature_below_threshold = Below Threshold label.adjust_threshold = Adjust threshold label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold. -label.colour_by_label_tip = Display features of the same type with a different label using a different colour. (e.g. domain features) label.select_colour_minimum_value = Select Colour for Minimum Value label.select_colour_maximum_value = Select Colour for Maximum Value label.open_url_param = Open URL {0} @@ -871,7 +869,7 @@ label.msa_service_is_unknown = The Multiple Sequence Alignment Service named {0} label.service_called_is_not_seq_search_service = The Service called \n{0}\nis not a \nSequence Search Service\! label.seq_search_service_is_unknown = The Sequence Search Service named {0} is unknown label.feature_type = Feature Type -label.display = Display +label.show = Show label.service_url = Service URL label.copied_sequences = Copied sequences label.cut_sequences = Cut Sequences @@ -1336,20 +1334,23 @@ label.matchCondition_le = <= label.matchCondition_gt = > label.matchCondition_ge = >= label.numeric_required = The value should be numeric -label.no_attributes = No attributes known -label.no_numeric_attributes = No numeric attributes known +label.filter = Filter label.filters = Filters -label.match_condition = Match condition label.join_conditions = Join conditions with -label.feature_to_filter = Feature to filter -label.colour_by_value = Colour by value -label.colour_by_text = Colour by text label.score = Score -label.attribute = Attribute label.colour_by_label = Colour by label label.variable_colour = Variable colour -label.select_new_colour = Select new colour -label.no_feature_attributes = No feature attributes found +label.select_colour = Select colour option.enable_disable_autosearch = When ticked, search is performed automatically. option.autosearch = Autosearch label.retrieve_ids = Retrieve IDs +label.display_settings_for = Display settings for {0} features +label.simple = Simple +label.simple_colour = Simple Colour +label.colour_by_text = Colour by text +label.graduated_colour = Graduated Colour +label.by_text_of = By text of +label.by_range_of = By range of +label.filters_tooltip = Click to set or amend filters +label.or = Or +label.and = And \ No newline at end of file diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index a7fff8e..31c3a86 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -226,7 +226,6 @@ label.automatic_scrolling = Desplazamiento autom label.documentation = Documentación label.about = Acerca de... label.show_sequence_limits = Mostrar los límites de la secuencia -label.feature_settings = Ajustar funciones... label.all_columns = Todas las columnas label.all_sequences = Todas las secuencias label.selected_columns = Columnas seleccionadas @@ -243,6 +242,7 @@ label.apply_all_groups = Aplicar a todos los grupos label.autocalculated_annotation = Anotación autocalculada label.min_colour = Color mínimo label.max_colour = Color máximo +label.no_colour = Sin color label.use_original_colours = Usar colores originales label.threshold_minmax = El umbral es mín/máx label.represent_group_with = Representar al grupo con @@ -250,8 +250,9 @@ label.selection = Seleccionar label.group_colour = Color del grupo label.sequence = Secuencia label.view_pdb_structure = Ver estructura PDB -label.min = Mín: -label.max = Máx: +label.max_value = Valor máximo +label.min_value = Valor mínimo +label.no_value = Sin valor label.colour_by_label = Color por etiquetas label.new_feature = Nueva función label.match_case = Hacer corresponder mayúsculas y minúsculas @@ -456,6 +457,10 @@ label.settings_for_type = Ajustes para {0} label.view_full_application = Ver en la aplicación completa label.load_associated_tree = Cargar árbol asociado ... label.load_features_annotations = Cargar características/anotaciones ... +label.load_vcf = Cargar variantes SNP desde fichero VCF texto o tab-indexado +label.load_vcf_file = Cargar fichero VCF +label.searching_vcf = Cargando variantes VCF... +label.added_vcf= {0} variantes VCF añadidas a {1} secuencia(s) label.export_features = Exportar características... label.export_annotations = Exportar anotaciones ... label.to_upper_case = Pasar a mayúsculas @@ -489,7 +494,6 @@ label.threshold_feature_above_threshold = Por encima del umbral label.threshold_feature_below_threshold = Por debajo del umbral label.adjust_threshold = Ajustar umbral label.toggle_absolute_relative_display_threshold = Cambiar entre mostrar el umbral absoluto y el relativo. -label.display_features_same_type_different_label_using_different_colour = Mostrar las características del mismo tipo con una etiqueta diferente y empleando un color distinto (p.e. características del dominio) label.select_colour_minimum_value = Seleccionar el color para el valor mínimo label.select_colour_maximum_value = Seleccionar el color para el valor máximo label.open_url_param = Abrir URL {0} @@ -791,7 +795,7 @@ label.msa_service_is_unknown = El Servicio de Alineamiento M label.service_called_is_not_seq_search_service = El Servicio llamando \n{0}\nno es un \nServicio de B\u00FAsqueda de Secuencias\! label.seq_search_service_is_unknown = El Servicio de Búsqueda de Sencuencias llamado {0} es desconocido label.feature_type = Tipo de característisca -label.display = Representación +label.show = Mostrar label.service_url = URL del servicio label.copied_sequences = Secuencias copiadas label.cut_sequences = Cortar secuencias @@ -1319,3 +1323,35 @@ label.select_hidden_colour = Seleccionar color de las regiones ocultas label.overview = Resumen label.reset_to_defaults = Restablecen a los predeterminados label.oview_calc = Recalculando resumen +label.feature_details = Detalles de característica +label.matchCondition_contains = Contiene +label.matchCondition_notcontains = No contiene +label.matchCondition_matches = Es igual a +label.matchCondition_notmatches = No es igual a +label.matchCondition_eq = = +label.matchCondition_ne = not = +label.matchCondition_lt = < +label.matchCondition_le = <= +label.matchCondition_gt = > +label.matchCondition_ge = >= +label.numeric_required = Valor numérico requerido +label.filter = Filtro +label.filters = Filtros +label.join_conditions = Combinar condiciones con +label.score = Puntuación +label.colour_by_label = Colorear por texto +label.variable_colour = Color variable +label.select_colour = Seleccionar color +option.enable_disable_autosearch = Marque para buscar automáticamente +option.autosearch = Búsqueda automática +label.retrieve_ids = Recuperar IDs +label.display_settings_for = Visualización de características {0} +label.simple = Simple +label.simple_colour = Color simple +label.colour_by_text = Colorear por texto +label.graduated_colour = Color graduado +label.by_text_of = Por texto de +label.by_range_of = Por rango de +label.filters_tooltip = Haga clic para configurar o modificar los filtros +label.or = O +label.and = Y \ No newline at end of file diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 93773cc..0780271 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -101,18 +101,6 @@ public interface FeatureColourI void setAboveThreshold(boolean b); /** - * Answers true if the threshold is the minimum value (when - * isAboveThreshold()) or maximum value (when isBelowThreshold()) of the - * colour range; only applicable when isGraduatedColour and either - * isAboveThreshold() or isBelowThreshold() answers true - * - * @return - */ - boolean isThresholdMinMax(); - - void setThresholdMinMax(boolean b); - - /** * Returns the threshold value (if any), else zero * * @return diff --git a/src/jalview/appletgui/AnnotationPanel.java b/src/jalview/appletgui/AnnotationPanel.java index c06f7b1..50a9e33 100755 --- a/src/jalview/appletgui/AnnotationPanel.java +++ b/src/jalview/appletgui/AnnotationPanel.java @@ -778,5 +778,14 @@ public class AnnotationPanel extends Panel { fastPaint((int) evt.getNewValue() - (int) evt.getOldValue()); } + else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(((int[]) evt.getNewValue())[0] + - ((int[]) evt.getOldValue())[0]); + } + else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) + { + repaint(); + } } } diff --git a/src/jalview/appletgui/FeatureColourChooser.java b/src/jalview/appletgui/FeatureColourChooser.java index e9c377a..d9eae11 100644 --- a/src/jalview/appletgui/FeatureColourChooser.java +++ b/src/jalview/appletgui/FeatureColourChooser.java @@ -58,6 +58,8 @@ public class FeatureColourChooser extends Panel implements ActionListener, */ private static final int SCALE_FACTOR_1K = 1000; + private static final String COLON = ":"; + private JVDialog frame; private Frame owner; @@ -198,8 +200,10 @@ public class FeatureColourChooser extends Panel implements ActionListener, private void jbInit() throws Exception { - Label minLabel = new Label(MessageManager.getString("label.min_value")); - Label maxLabel = new Label(MessageManager.getString("label.max_value")); + Label minLabel = new Label( + MessageManager.getString("label.min_value") + COLON); + Label maxLabel = new Label( + MessageManager.getString("label.max_value") + COLON); minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 9a67499..39a2747 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -20,6 +20,10 @@ */ package jalview.appletgui; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN; + import jalview.api.FeatureColourI; import jalview.api.FeatureSettingsControllerI; import jalview.datamodel.AlignmentI; @@ -584,18 +588,23 @@ public class FeatureSettings extends Panel Component[] comps = featurePanel.getComponents(); int cSize = comps.length; - Object[][] tmp = new Object[cSize][3]; + /* + * temporary! leave column[2] empty - used for Filter in + * gui.FeatureSettings + */ + int columnCount = 4; + Object[][] tmp = new Object[cSize][columnCount]; int tmpSize = 0; for (int i = 0; i < cSize; i++) { MyCheckbox check = (MyCheckbox) comps[i]; - tmp[tmpSize][0] = check.type; - tmp[tmpSize][1] = fr.getFeatureStyle(check.type); - tmp[tmpSize][2] = new Boolean(check.getState()); + tmp[tmpSize][TYPE_COLUMN /* 0 */] = check.type; + tmp[tmpSize][COLOUR_COLUMN /* 1 */] = fr.getFeatureStyle(check.type); + tmp[tmpSize][SHOW_COLUMN /* 3 */] = new Boolean(check.getState()); tmpSize++; } - Object[][] data = new Object[tmpSize][3]; + Object[][] data = new Object[tmpSize][columnCount]; System.arraycopy(tmp, 0, data, 0, tmpSize); fr.setFeaturePriority(data); diff --git a/src/jalview/appletgui/IdCanvas.java b/src/jalview/appletgui/IdCanvas.java index 5eddc4f..f5ea12e 100755 --- a/src/jalview/appletgui/IdCanvas.java +++ b/src/jalview/appletgui/IdCanvas.java @@ -448,5 +448,14 @@ public class IdCanvas extends Panel implements ViewportListenerI { fastPaint((int) evt.getNewValue() - (int) evt.getOldValue()); } + else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(((int[]) evt.getNewValue())[1] + - ((int[]) evt.getOldValue())[1]); + } + else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT)) + { + repaint(); + } } } diff --git a/src/jalview/appletgui/ScalePanel.java b/src/jalview/appletgui/ScalePanel.java index 7d4150d..04fb22b 100755 --- a/src/jalview/appletgui/ScalePanel.java +++ b/src/jalview/appletgui/ScalePanel.java @@ -468,7 +468,9 @@ public class ScalePanel extends Panel // Here we only want to fastpaint on a scroll, with resize using a normal // paint, so scroll events are identified as changes to the horizontal or // vertical start value. - if (evt.getPropertyName().equals(ViewportRanges.STARTRES)) + if (evt.getPropertyName().equals(ViewportRanges.STARTRES) + || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ) + || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) { // scroll event, repaint panel repaint(); diff --git a/src/jalview/appletgui/SeqCanvas.java b/src/jalview/appletgui/SeqCanvas.java index f59967d..2420cf7 100755 --- a/src/jalview/appletgui/SeqCanvas.java +++ b/src/jalview/appletgui/SeqCanvas.java @@ -889,15 +889,37 @@ public class SeqCanvas extends Panel implements ViewportListenerI { String eventName = evt.getPropertyName(); + if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED)) + { + fastPaint = true; + repaint(); + return; + } + else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT)) + { + fastPaint = false; + repaint(); + return; + } + if (!av.getWrapAlignment()) { int scrollX = 0; - if (eventName.equals(ViewportRanges.STARTRES)) + if (eventName.equals(ViewportRanges.STARTRES) + || eventName.equals(ViewportRanges.STARTRESANDSEQ)) { // Make sure we're not trying to draw a panel // larger than the visible window + if (eventName.equals(ViewportRanges.STARTRES)) + { + scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); + } + else + { + scrollX = ((int[]) evt.getNewValue())[0] + - ((int[]) evt.getOldValue())[0]; + } ViewportRanges vpRanges = av.getRanges(); - scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); int range = vpRanges.getEndRes() - vpRanges.getStartRes(); if (scrollX > range) { @@ -924,6 +946,10 @@ public class SeqCanvas extends Panel implements ViewportListenerI // scroll fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); } + else if (eventName.equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(scrollX, 0); + } } } diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index 9a61f5f..d74bbb7 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -43,7 +43,6 @@ import jalview.util.Comparison; import jalview.util.MappingUtils; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; -import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Font; @@ -148,13 +147,13 @@ public class SeqPanel extends Panel implements MouseMotionListener, void setCursorRow() { seqCanvas.cursorY = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorColumn() { seqCanvas.cursorX = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorRowAndColumn() @@ -167,7 +166,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, { seqCanvas.cursorX = getKeyboardNo1() - 1; seqCanvas.cursorY = getKeyboardNo2() - 1; - scrollToVisible(); + scrollToVisible(true); } } @@ -176,7 +175,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1; - scrollToVisible(); + scrollToVisible(true); } void moveCursor(int dx, int dy) @@ -202,10 +201,16 @@ public class SeqPanel extends Panel implements MouseMotionListener, seqCanvas.cursorX = original; } } - scrollToVisible(); + scrollToVisible(false); } - void scrollToVisible() + /** + * Scroll to make the cursor visible in the viewport. + * + * @param jump + * just jump to the location rather than scrolling + */ + void scrollToVisible(boolean jump) { if (seqCanvas.cursorX < 0) { @@ -226,44 +231,34 @@ public class SeqPanel extends Panel implements MouseMotionListener, } endEditing(); - if (av.getWrapAlignment()) + + boolean repaintNeeded = true; + if (jump) { - av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX); + // only need to repaint if the viewport did not move, as otherwise it will + // get a repaint + repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX, + seqCanvas.cursorY); } else { - ViewportRanges ranges = av.getRanges(); - HiddenColumns hidden = av.getAlignment().getHiddenColumns(); - while (seqCanvas.cursorY < ranges.getStartSeq()) + if (av.getWrapAlignment()) { - ranges.scrollUp(true); + av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX); } - while (seqCanvas.cursorY > ranges.getEndSeq()) - { - ranges.scrollUp(false); - } - while (seqCanvas.cursorX < hidden - .adjustForHiddenColumns(ranges.getStartRes())) - { - - if (!ranges.scrollRight(false)) - { - break; - } - } - while (seqCanvas.cursorX > hidden - .adjustForHiddenColumns(ranges.getEndRes())) + else { - if (!ranges.scrollRight(true)) - { - break; - } + av.getRanges().scrollToVisible(seqCanvas.cursorX, + seqCanvas.cursorY); } } setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY), seqCanvas.cursorX, seqCanvas.cursorY); - seqCanvas.repaint(); + if (repaintNeeded) + { + seqCanvas.repaint(); + } } void setSelectionAreaAtCursor(boolean topLeft) diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index 8c5e4ac..f268d37 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -28,6 +28,7 @@ import jalview.util.LinkedIdentityHashSet; import jalview.util.MessageManager; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; @@ -1617,40 +1618,21 @@ public class Alignment implements AlignmentI @Override public Iterable findAnnotation(String calcId) { - List aa = new ArrayList<>(); AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation(); if (alignmentAnnotation != null) { - for (AlignmentAnnotation a : alignmentAnnotation) - { - if (a.getCalcId() == calcId || (a.getCalcId() != null - && calcId != null && a.getCalcId().equals(calcId))) - { - aa.add(a); - } - } + return AlignmentAnnotation.findAnnotation( + Arrays.asList(getAlignmentAnnotation()), calcId); } - return aa; + return Arrays.asList(new AlignmentAnnotation[] {}); } @Override public Iterable findAnnotations(SequenceI seq, String calcId, String label) { - ArrayList aa = new ArrayList<>(); - for (AlignmentAnnotation ann : getAlignmentAnnotation()) - { - if ((calcId == null || (ann.getCalcId() != null - && ann.getCalcId().equals(calcId))) - && (seq == null || (ann.sequenceRef != null - && ann.sequenceRef == seq)) - && (label == null - || (ann.label != null && ann.label.equals(label)))) - { - aa.add(ann); - } - } - return aa; + return AlignmentAnnotation.findAnnotations( + Arrays.asList(getAlignmentAnnotation()), seq, calcId, label); } @Override diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index 09facbf..f7bf4d8 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -24,6 +24,7 @@ import jalview.analysis.Rna; import jalview.analysis.SecStrConsensus.SimpleBP; import jalview.analysis.WUSSParseException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -1471,4 +1472,71 @@ public class AlignmentAnnotation { return graphMin < graphMax; } + + public static Iterable findAnnotations( + Iterable list, SequenceI seq, String calcId, + String label) + { + + ArrayList aa = new ArrayList<>(); + for (AlignmentAnnotation ann : list) + { + if ((calcId == null || (ann.getCalcId() != null + && ann.getCalcId().equals(calcId))) + && (seq == null || (ann.sequenceRef != null + && ann.sequenceRef == seq)) + && (label == null + || (ann.label != null && ann.label.equals(label)))) + { + aa.add(ann); + } + } + return aa; + } + + /** + * Answer true if any annotation matches the calcId passed in (if not null). + * + * @param list + * annotation to search + * @param calcId + * @return + */ + public static boolean hasAnnotation(List list, + String calcId) + { + + if (calcId != null && !"".equals(calcId)) + { + for (AlignmentAnnotation a : list) + { + if (a.getCalcId() == calcId) + { + return true; + } + } + } + return false; + } + + public static Iterable findAnnotation( + List list, String calcId) + { + + List aa = new ArrayList<>(); + if (calcId == null) + { + return aa; + } + for (AlignmentAnnotation a : list) + { + + if (a.getCalcId() == calcId || (a.getCalcId() != null + && calcId != null && a.getCalcId().equals(calcId))) + { + aa.add(a); + } + } + return aa; + } } diff --git a/src/jalview/datamodel/ContiguousI.java b/src/jalview/datamodel/ContiguousI.java index f2ae4b7..a9b1372 100644 --- a/src/jalview/datamodel/ContiguousI.java +++ b/src/jalview/datamodel/ContiguousI.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel; public interface ContiguousI diff --git a/src/jalview/datamodel/Range.java b/src/jalview/datamodel/Range.java index 7886713..8b6f617 100644 --- a/src/jalview/datamodel/Range.java +++ b/src/jalview/datamodel/Range.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel; /** diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 9e81d81..441d8d0 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -1217,7 +1217,7 @@ public class Sequence extends ASequence implements SequenceI } @Override - public void deleteChars(int i, int j) + public void deleteChars(final int i, final int j) { int newstart = start, newend = end; if (i >= sequence.length || i < 0) @@ -1229,62 +1229,75 @@ public class Sequence extends ASequence implements SequenceI boolean createNewDs = false; // TODO: take a (second look) at the dataset creation validation method for // the very large sequence case - int eindex = -1, sindex = -1; - boolean ecalc = false, scalc = false; + int startIndex = findIndex(start) - 1; + int endIndex = findIndex(end) - 1; + int startDeleteColumn = -1; // for dataset sequence deletions + int deleteCount = 0; + for (int s = i; s < j; s++) { - if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23) + if (Comparison.isGap(sequence[s])) + { + continue; + } + deleteCount++; + if (startDeleteColumn == -1) { - if (createNewDs) + startDeleteColumn = findPosition(s) - start; + } + if (createNewDs) + { + newend--; + } + else + { + if (startIndex == s) { - newend--; + /* + * deleting characters from start of sequence; new start is the + * sequence position of the next column (position to the right + * if the column position is gapped) + */ + newstart = findPosition(j); + break; } else { - if (!scalc) - { - sindex = findIndex(start) - 1; - scalc = true; - } - if (sindex == s) + if (endIndex < j) { - // delete characters including start of sequence - newstart = findPosition(j); - break; // don't need to search for any more residue characters. + /* + * deleting characters at end of sequence; new end is the sequence + * position of the column before the deletion; subtract 1 if this is + * gapped since findPosition returns the next sequence position + */ + newend = findPosition(i - 1); + if (Comparison.isGap(sequence[i - 1])) + { + newend--; + } + break; } else { - // delete characters after start. - if (!ecalc) - { - eindex = findIndex(end) - 1; - ecalc = true; - } - if (eindex < j) - { - // delete characters at end of sequence - newend = findPosition(i - 1); - break; // don't need to search for any more residue characters. - } - else - { - createNewDs = true; - newend--; // decrease end position by one for the deleted residue - // and search further - } + createNewDs = true; + newend--; } } } } - // deletion occured in the middle of the sequence + if (createNewDs && this.datasetSequence != null) { - // construct a new sequence + /* + * if deletion occured in the middle of the sequence, + * construct a new dataset sequence and delete the residues + * that were deleted from the aligned sequence + */ Sequence ds = new Sequence(datasetSequence); + ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount); + datasetSequence = ds; // TODO: remove any non-inheritable properties ? // TODO: create a sequence mapping (since there is a relation here ?) - ds.deleteChars(i, j); - datasetSequence = ds; } start = newstart; end = newend; diff --git a/src/jalview/datamodel/SequenceCursor.java b/src/jalview/datamodel/SequenceCursor.java index b5929bf..24752bf 100644 --- a/src/jalview/datamodel/SequenceCursor.java +++ b/src/jalview/datamodel/SequenceCursor.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel; /** diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index e2f15e1..6b797d7 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -30,6 +30,7 @@ import java.awt.Color; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -1316,39 +1317,16 @@ public class SequenceGroup implements AnnotatedCollectionI @Override public Iterable findAnnotation(String calcId) { - List aa = new ArrayList<>(); - if (calcId == null) - { - return aa; - } - for (AlignmentAnnotation a : getAlignmentAnnotation()) - { - if (calcId.equals(a.getCalcId())) - { - aa.add(a); - } - } - return aa; + return AlignmentAnnotation.findAnnotation( + Arrays.asList(getAlignmentAnnotation()), calcId); } @Override public Iterable findAnnotations(SequenceI seq, String calcId, String label) { - ArrayList aa = new ArrayList<>(); - for (AlignmentAnnotation ann : getAlignmentAnnotation()) - { - if ((calcId == null || (ann.getCalcId() != null - && ann.getCalcId().equals(calcId))) - && (seq == null || (ann.sequenceRef != null - && ann.sequenceRef == seq)) - && (label == null - || (ann.label != null && ann.label.equals(label)))) - { - aa.add(ann); - } - } - return aa; + return AlignmentAnnotation.findAnnotations( + Arrays.asList(getAlignmentAnnotation()), seq, calcId, label); } /** @@ -1359,17 +1337,8 @@ public class SequenceGroup implements AnnotatedCollectionI */ public boolean hasAnnotation(String calcId) { - if (calcId != null && !"".equals(calcId)) - { - for (AlignmentAnnotation a : getAlignmentAnnotation()) - { - if (a.getCalcId() == calcId) - { - return true; - } - } - } - return false; + return AlignmentAnnotation + .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId); } /** diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index c064373..fb723e6 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -193,13 +193,14 @@ public interface SequenceI extends ASequenceI public int findIndex(int pos); /** - * Returns the sequence position for an alignment position. + * Returns the sequence position for an alignment (column) position. If at a + * gap, returns the position of the next residue to the right. If beyond the + * end of the sequence, returns 1 more than the last residue position. * * @param i * column index in alignment (from 0.. atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null) + { + return attData.getType(); + } + } + return null; + } } diff --git a/src/jalview/datamodel/features/FeatureLocationI.java b/src/jalview/datamodel/features/FeatureLocationI.java index e651c13..378b8db 100644 --- a/src/jalview/datamodel/features/FeatureLocationI.java +++ b/src/jalview/datamodel/features/FeatureLocationI.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index 51bee57..02ce1c5 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/NCList.java b/src/jalview/datamodel/features/NCList.java index b8160d3..ae58a69 100644 --- a/src/jalview/datamodel/features/NCList.java +++ b/src/jalview/datamodel/features/NCList.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/NCNode.java b/src/jalview/datamodel/features/NCNode.java index 007f3b1..b991750 100644 --- a/src/jalview/datamodel/features/NCNode.java +++ b/src/jalview/datamodel/features/NCNode.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/RangeComparator.java b/src/jalview/datamodel/features/RangeComparator.java index 26ffee1..b7d702d 100644 --- a/src/jalview/datamodel/features/RangeComparator.java +++ b/src/jalview/datamodel/features/RangeComparator.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index 8d5ba58..fcf1b53 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.ContiguousI; diff --git a/src/jalview/datamodel/features/SequenceFeaturesI.java b/src/jalview/datamodel/features/SequenceFeaturesI.java index 58beca2..80c4f9a 100644 --- a/src/jalview/datamodel/features/SequenceFeaturesI.java +++ b/src/jalview/datamodel/features/SequenceFeaturesI.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; diff --git a/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java b/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java index 4a359ff..b1ed275 100644 --- a/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java +++ b/src/jalview/datamodel/xdb/uniprot/UniprotFeature.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.xdb.uniprot; /** diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 12d1369..438e81b 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -1185,5 +1185,14 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { fastPaint((int) evt.getNewValue() - (int) evt.getOldValue()); } + else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(((int[]) evt.getNewValue())[0] + - ((int[]) evt.getOldValue())[0]); + } + else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) + { + repaint(); + } } } diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index 886762a..f674c7e 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -169,7 +169,7 @@ public class CalculationChooser extends JPanel JPanel treePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); treePanel.setOpaque(false); - JvSwingUtils.createItalicTitledBorder(treePanel, + JvSwingUtils.createTitledBorder(treePanel, MessageManager.getString("label.tree"), true); // then copy the inset dimensions for the border-less PCA panel diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java deleted file mode 100644 index da3819c..0000000 --- a/src/jalview/gui/FeatureColourChooser.java +++ /dev/null @@ -1,1006 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.gui; - -import jalview.api.FeatureColourI; -import jalview.datamodel.GraphLine; -import jalview.datamodel.features.FeatureAttributes; -import jalview.schemes.FeatureColour; -import jalview.util.MessageManager; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JSlider; -import javax.swing.JTextField; -import javax.swing.border.LineBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class FeatureColourChooser extends JalviewDialog -{ - private static final String COLON = ":"; - - private static final int MAX_TOOLTIP_LENGTH = 50; - - private static int NO_COLOUR_OPTION = 0; - - private static int MIN_COLOUR_OPTION = 1; - - private static int MAX_COLOUR_OPTION = 2; - - private FeatureRenderer fr; - - private FeatureColourI cs; - - private FeatureColourI oldcs; - - private AlignmentPanel ap; - - private boolean adjusting = false; - - private float min; - - private float max; - - private float scaleFactor; - - private String type = null; - - private JPanel minColour = new JPanel(); - - private JPanel maxColour = new JPanel(); - - private Color noColour; - - private JComboBox threshold = new JComboBox<>(); - - private JSlider slider = new JSlider(); - - private JTextField thresholdValue = new JTextField(20); - - private JCheckBox thresholdIsMin = new JCheckBox(); - - private GraphLine threshline; - - private Color oldmaxColour; - - private Color oldminColour; - - private Color oldNoColour; - - private ActionListener colourEditor = null; - - /* - * radio buttons to select what to colour by - * label, attribute text, score, attribute value - */ - private JRadioButton byDescription = new JRadioButton(); - - private JRadioButton byAttributeText = new JRadioButton(); - - private JRadioButton byScore = new JRadioButton(); - - private JRadioButton byAttributeValue = new JRadioButton(); - - private ActionListener changeColourAction; - - private ActionListener changeMinMaxAction; - - /* - * choice of option for 'colour for no value' - */ - private JComboBox noValueCombo; - - /* - * choice of attribute (if any) for 'colour by text' - */ - private JComboBox textAttributeCombo; - - /* - * choice of attribute (if any) for 'colour by value' - */ - private JComboBox valueAttributeCombo; - - /** - * Constructor - * - * @param frender - * @param theType - */ - public FeatureColourChooser(FeatureRenderer frender, String theType) - { - this(frender, false, theType); - } - - /** - * Constructor, with option to make a blocking dialog (has to complete in the - * AWT event queue thread). Currently this option is always set to false. - * - * @param frender - * @param blocking - * @param theType - */ - FeatureColourChooser(FeatureRenderer frender, boolean blocking, - String theType) - { - this.fr = frender; - this.type = theType; - ap = fr.ap; - String title = MessageManager.formatMessage("label.variable_color_for", - new String[] { theType }); - initDialogFrame(this, true, blocking, title, 470, 300); - - slider.addChangeListener(new ChangeListener() - { - @Override - public void stateChanged(ChangeEvent evt) - { - if (!adjusting) - { - thresholdValue.setText((slider.getValue() / scaleFactor) + ""); - sliderValueChanged(); - } - } - }); - slider.addMouseListener(new MouseAdapter() - { - @Override - public void mouseReleased(MouseEvent evt) - { - /* - * only update Overview and/or structure colouring - * when threshold slider drag ends (mouse up) - */ - if (ap != null) - { - ap.paintAlignment(true, true); - } - } - }); - - // todo move all threshold setup inside a method - float mm[] = fr.getMinMax().get(theType)[0]; - min = mm[0]; - max = mm[1]; - - /* - * ensure scale factor allows a scaled range with - * 10 integer divisions ('ticks'); if we have got here, - * we should expect that max != min - */ - scaleFactor = (max == min) ? 1f : 100f / (max - min); - - oldcs = fr.getFeatureColours().get(theType); - if (!oldcs.isSimpleColour()) - { - if (oldcs.isAutoScaled()) - { - // update the scale - cs = new FeatureColour((FeatureColour) oldcs, min, max); - } - else - { - cs = new FeatureColour((FeatureColour) oldcs); - } - } - else - { - /* - * promote original simple color to a graduated color - * - by score if there is a score range, else by label - */ - Color bl = oldcs.getColour(); - if (bl == null) - { - bl = Color.BLACK; - } - // original colour becomes the maximum colour - cs = new FeatureColour(Color.white, bl, mm[0], mm[1]); - cs.setColourByLabel(mm[0] == mm[1]); - } - minColour.setBackground(oldminColour = cs.getMinColour()); - maxColour.setBackground(oldmaxColour = cs.getMaxColour()); - noColour = cs.getNoColour(); - - adjusting = true; - - try - { - jbInit(); - } catch (Exception ex) - { - ex.printStackTrace(); - return; - } - - /* - * set the initial state of options on screen - */ - if (cs.isColourByLabel()) - { - if (cs.isColourByAttribute()) - { - byAttributeText.setSelected(true); - textAttributeCombo.setEnabled(true); - String[] attributeName = cs.getAttributeName(); - textAttributeCombo - .setSelectedItem(String.join(COLON, attributeName)); - } - else - { - byDescription.setSelected(true); - textAttributeCombo.setEnabled(false); - } - } - else - { - if (cs.isColourByAttribute()) - { - byAttributeValue.setSelected(true); - String[] attributeName = cs.getAttributeName(); - valueAttributeCombo - .setSelectedItem(String.join(COLON, attributeName)); - valueAttributeCombo.setEnabled(true); - updateMinMax(); - } - else - { - byScore.setSelected(true); - valueAttributeCombo.setEnabled(false); - } - } - - if (noColour == null) - { - noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); - } - else if (noColour.equals(oldminColour)) - { - noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); - } - else if (noColour.equals(oldmaxColour)) - { - noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); - } - - threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black); - threshline.value = cs.getThreshold(); - - if (cs.hasThreshold()) - { - // initialise threshold slider and selector - threshold.setSelectedIndex(cs.isAboveThreshold() ? 1 : 2); - slider.setEnabled(true); - slider.setValue((int) (cs.getThreshold() * scaleFactor)); - thresholdValue.setEnabled(true); - } - - adjusting = false; - - changeColour(false); - waitForInput(); - } - - /** - * Configures the initial layout - */ - private void jbInit() - { - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - this.setBackground(Color.white); - - changeColourAction = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - changeColour(true); - } - }; - - changeMinMaxAction = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - updateMinMax(); - changeColour(true); - } - }; - - /* - * this panel - * detailsPanel - * colourByTextPanel - * colourByScorePanel - * okCancelPanel - */ - JPanel detailsPanel = new JPanel(); - detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS)); - - JPanel colourByTextPanel = initColourByTextPanel(); - detailsPanel.add(colourByTextPanel); - - JPanel colourByValuePanel = initColourByValuePanel(); - detailsPanel.add(colourByValuePanel); - - /* - * 4 radio buttons select between colour by description, by - * attribute text, by score, or by attribute value - */ - ButtonGroup bg = new ButtonGroup(); - bg.add(byDescription); - bg.add(byAttributeText); - bg.add(byScore); - bg.add(byAttributeValue); - - JPanel okCancelPanel = initOkCancelPanel(); - - this.add(detailsPanel); - this.add(okCancelPanel); - } - - /** - * Updates the min-max range for a change in choice of Colour by Score, or - * Colour by Attribute (value) - */ - protected void updateMinMax() - { - float[] minMax = null; - if (byScore.isSelected()) - { - minMax = fr.getMinMax().get(type)[0]; - } - else if (byAttributeValue.isSelected()) - { - String attName = (String) valueAttributeCombo.getSelectedItem(); - String[] attNames = attName.split(COLON); - minMax = FeatureAttributes.getInstance().getMinMax(type, attNames); - } - if (minMax != null) - { - min = minMax[0]; - max = minMax[1]; - scaleFactor = (max == min) ? 1f : 100f / (max - min); - slider.setValue((int) (min * scaleFactor)); - } - } - - /** - * Lay out fields for graduated colour by value - * - * @return - */ - protected JPanel initColourByValuePanel() - { - JPanel byValuePanel = new JPanel(); - byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS)); - JvSwingUtils.createItalicTitledBorder(byValuePanel, - MessageManager.getString("label.colour_by_value"), true); - byValuePanel.setBackground(Color.white); - - /* - * first row - choose colour by score or by attribute, choose attribute - */ - JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - byWhatPanel.setBackground(Color.white); - byValuePanel.add(byWhatPanel); - - byScore.setText(MessageManager.getString("label.score")); - byWhatPanel.add(byScore); - byScore.addActionListener(changeMinMaxAction); - - byAttributeValue.setText(MessageManager.getString("label.attribute")); - byAttributeValue.addActionListener(changeMinMaxAction); - byWhatPanel.add(byAttributeValue); - - List attNames = FeatureAttributes.getInstance() - .getAttributes(type); - valueAttributeCombo = populateAttributesDropdown(type, attNames, true); - - /* - * if no numeric atttibutes found, disable colour by attribute value - */ - if (valueAttributeCombo.getItemCount() == 0) - { - byAttributeValue.setEnabled(false); - } - - byWhatPanel.add(valueAttributeCombo); - - /* - * second row - min/max/no colours - */ - JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - colourRangePanel.setBackground(Color.white); - byValuePanel.add(colourRangePanel); - - minColour.setFont(JvSwingUtils.getLabelFont()); - minColour.setBorder(BorderFactory.createLineBorder(Color.black)); - minColour.setPreferredSize(new Dimension(40, 20)); - minColour.setToolTipText(MessageManager.getString("label.min_colour")); - minColour.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent e) - { - if (minColour.isEnabled()) - { - minColour_actionPerformed(); - } - } - }); - - maxColour.setFont(JvSwingUtils.getLabelFont()); - maxColour.setBorder(BorderFactory.createLineBorder(Color.black)); - maxColour.setPreferredSize(new Dimension(40, 20)); - maxColour.setToolTipText(MessageManager.getString("label.max_colour")); - maxColour.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent e) - { - if (maxColour.isEnabled()) - { - maxColour_actionPerformed(); - } - } - }); - maxColour.setBorder(new LineBorder(Color.black)); - - noValueCombo = new JComboBox<>(); - noValueCombo.addItem(MessageManager.getString("label.no_colour")); - noValueCombo.addItem(MessageManager.getString("label.min_colour")); - noValueCombo.addItem(MessageManager.getString("label.max_colour")); - noValueCombo.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - setNoValueColour(); - } - }); - - JLabel minText = new JLabel(MessageManager.getString("label.min_value")); - minText.setFont(JvSwingUtils.getLabelFont()); - JLabel maxText = new JLabel(MessageManager.getString("label.max_value")); - maxText.setFont(JvSwingUtils.getLabelFont()); - JLabel noText = new JLabel(MessageManager.getString("label.no_value")); - noText.setFont(JvSwingUtils.getLabelFont()); - - colourRangePanel.add(minText); - colourRangePanel.add(minColour); - colourRangePanel.add(maxText); - colourRangePanel.add(maxColour); - colourRangePanel.add(noText); - colourRangePanel.add(noValueCombo); - - /* - * third row - threshold options and value - */ - JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - thresholdPanel.setBackground(Color.white); - byValuePanel.add(thresholdPanel); - - threshold.addActionListener(changeColourAction); - threshold.setToolTipText(MessageManager - .getString("label.threshold_feature_display_by_score")); - threshold.addItem(MessageManager - .getString("label.threshold_feature_no_threshold")); // index 0 - threshold.addItem(MessageManager - .getString("label.threshold_feature_above_threshold")); // index 1 - threshold.addItem(MessageManager - .getString("label.threshold_feature_below_threshold")); // index 2 - - thresholdValue.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - thresholdValue_actionPerformed(); - } - }); - thresholdValue.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - thresholdValue_actionPerformed(); - } - }); - slider.setPaintLabels(false); - slider.setPaintTicks(true); - slider.setBackground(Color.white); - slider.setEnabled(false); - slider.setOpaque(false); - slider.setPreferredSize(new Dimension(100, 32)); - slider.setToolTipText(MessageManager - .getString("label.adjust_threshold")); - thresholdValue.setEnabled(false); - thresholdValue.setColumns(7); - - thresholdPanel.add(threshold); - thresholdPanel.add(slider); - thresholdPanel.add(thresholdValue); - - /* - * 4th row - threshold is min / max - */ - JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - isMinMaxPanel.setBackground(Color.white); - byValuePanel.add(isMinMaxPanel); - thresholdIsMin.setBackground(Color.white); - thresholdIsMin.setText(MessageManager - .getString("label.threshold_minmax")); - thresholdIsMin.setToolTipText(MessageManager - .getString("label.toggle_absolute_relative_display_threshold")); - thresholdIsMin.addActionListener(changeColourAction); - isMinMaxPanel.add(thresholdIsMin); - - return byValuePanel; - } - - /** - * Action on user choice of no / min / max colour to use when there is no - * value to colour by - */ - protected void setNoValueColour() - { - int i = noValueCombo.getSelectedIndex(); - if (i == NO_COLOUR_OPTION) - { - noColour = null; - } - else if (i == MIN_COLOUR_OPTION) - { - noColour = minColour.getBackground(); - } - else if (i == MAX_COLOUR_OPTION) - { - noColour = maxColour.getBackground(); - } - changeColour(true); - } - - /** - * Lay out OK and Cancel buttons - * - * @return - */ - protected JPanel initOkCancelPanel() - { - JPanel okCancelPanel = new JPanel(); - okCancelPanel.setBackground(Color.white); - okCancelPanel.add(ok); - okCancelPanel.add(cancel); - return okCancelPanel; - } - - /** - * Lay out Colour by Label and attribute choice elements - * - * @return - */ - protected JPanel initColourByTextPanel() - { - JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - byTextPanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(byTextPanel, - MessageManager.getString("label.colour_by_text"), true); - - byDescription.setText(MessageManager.getString("label.label")); - byDescription.setToolTipText(MessageManager - .getString("label.colour_by_label_tip")); - byDescription.addActionListener(changeColourAction); - byTextPanel.add(byDescription); - - byAttributeText.setText(MessageManager.getString("label.attribute")); - byAttributeText.addActionListener(changeColourAction); - byTextPanel.add(byAttributeText); - - List attNames = FeatureAttributes.getInstance() - .getAttributes(type); - textAttributeCombo = populateAttributesDropdown(type, attNames, false); - byTextPanel.add(textAttributeCombo); - - /* - * disable colour by attribute if no attributes - */ - if (attNames.isEmpty()) - { - byAttributeText.setEnabled(false); - } - - return byTextPanel; - } - - /** - * Action on clicking the 'minimum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void minColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_minimum_value"), - minColour.getBackground()); - if (col != null) - { - minColour.setBackground(col); - minColour.setForeground(col); - } - minColour.repaint(); - changeColour(true); - } - - /** - * Action on clicking the 'maximum colour' - open a colour chooser dialog, and - * set the selected colour (if the user does not cancel out of the dialog) - */ - protected void maxColour_actionPerformed() - { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_colour_maximum_value"), - maxColour.getBackground()); - if (col != null) - { - maxColour.setBackground(col); - maxColour.setForeground(col); - } - maxColour.repaint(); - changeColour(true); - } - - /** - * Constructs and sets the selected colour options as the colour for the - * feature type, and repaints the alignment, and optionally the Overview - * and/or structure viewer if open - * - * @param updateStructsAndOverview - */ - void changeColour(boolean updateStructsAndOverview) - { - // Check if combobox is still adjusting - if (adjusting) - { - return; - } - - boolean aboveThreshold = false; - boolean belowThreshold = false; - if (threshold.getSelectedIndex() == 1) - { - aboveThreshold = true; - } - else if (threshold.getSelectedIndex() == 2) - { - belowThreshold = true; - } - boolean hasThreshold = aboveThreshold || belowThreshold; - - slider.setEnabled(true); - thresholdValue.setEnabled(true); - - /* - * make the feature colour - */ - FeatureColourI acg; - if (cs.isColourByLabel()) - { - acg = new FeatureColour(oldminColour, oldmaxColour, min, max); - } - else - { - acg = new FeatureColour(oldminColour = minColour.getBackground(), - oldmaxColour = maxColour.getBackground(), - oldNoColour = noColour, min, max); - } - String attribute = null; - textAttributeCombo.setEnabled(false); - valueAttributeCombo.setEnabled(false); - if (byAttributeText.isSelected()) - { - attribute = (String) textAttributeCombo.getSelectedItem(); - textAttributeCombo.setEnabled(true); - acg.setAttributeName(attribute.split(COLON)); - } - else if (byAttributeValue.isSelected()) - { - attribute = (String) valueAttributeCombo.getSelectedItem(); - valueAttributeCombo.setEnabled(true); - acg.setAttributeName(attribute.split(COLON)); - } - else - { - acg.setAttributeName((String) null); - } - - if (!hasThreshold) - { - slider.setEnabled(false); - thresholdValue.setEnabled(false); - thresholdValue.setText(""); - thresholdIsMin.setEnabled(false); - } - else if (threshline == null) - { - /* - * todo not yet implemented: visual indication of feature threshold - */ - threshline = new GraphLine((max - min) / 2f, "Threshold", - Color.black); - } - - if (hasThreshold) - { - adjusting = true; - acg.setThreshold(threshline.value); - - float range = (max - min) * scaleFactor; - - slider.setMinimum((int) (min * scaleFactor)); - slider.setMaximum((int) (max * scaleFactor)); - // slider.setValue((int) (threshline.value * scaleFactor)); - slider.setValue(Math.round(threshline.value * scaleFactor)); - thresholdValue.setText(threshline.value + ""); - slider.setMajorTickSpacing((int) (range / 10f)); - slider.setEnabled(true); - thresholdValue.setEnabled(true); - thresholdIsMin.setEnabled(!byDescription.isSelected()); - adjusting = false; - } - - acg.setAboveThreshold(aboveThreshold); - acg.setBelowThreshold(belowThreshold); - if (thresholdIsMin.isSelected() && hasThreshold) - { - acg.setAutoScaled(false); - if (aboveThreshold) - { - acg = new FeatureColour((FeatureColour) acg, threshline.value, max); - } - else - { - acg = new FeatureColour((FeatureColour) acg, min, threshline.value); - } - } - else - { - acg.setAutoScaled(true); - } - acg.setColourByLabel(byDescription.isSelected() - || byAttributeText.isSelected()); - - if (acg.isColourByLabel()) - { - maxColour.setEnabled(false); - minColour.setEnabled(false); - noValueCombo.setEnabled(false); - maxColour.setBackground(this.getBackground()); - maxColour.setForeground(this.getBackground()); - minColour.setBackground(this.getBackground()); - minColour.setForeground(this.getBackground()); - } - else - { - maxColour.setEnabled(true); - minColour.setEnabled(true); - noValueCombo.setEnabled(true); - maxColour.setBackground(oldmaxColour); - maxColour.setForeground(oldmaxColour); - minColour.setBackground(oldminColour); - minColour.setForeground(oldminColour); - noColour = oldNoColour; - } - - /* - * save the colour, and repaint stuff - */ - fr.setColour(type, acg); - cs = acg; - ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); - } - - @Override - protected void raiseClosed() - { - if (this.colourEditor != null) - { - colourEditor.actionPerformed(new ActionEvent(this, 0, "CLOSED")); - } - } - - @Override - public void okPressed() - { - changeColour(false); - } - - @Override - public void cancelPressed() - { - reset(); - } - - /** - * Action when the user cancels the dialog. All previous settings should be - * restored and rendered on the alignment, and any linked Overview window or - * structure. - */ - void reset() - { - fr.setColour(type, oldcs); - ap.paintAlignment(true, true); - cs = null; - } - - /** - * Action on text entry of a threshold value - */ - protected void thresholdValue_actionPerformed() - { - try - { - float f = Float.parseFloat(thresholdValue.getText()); - slider.setValue((int) (f * scaleFactor)); - threshline.value = f; - - /* - * force repaint of any Overview window or structure - */ - ap.paintAlignment(true, true); - } catch (NumberFormatException ex) - { - } - } - - /** - * Action on change of threshold slider value. This may be done interactively - * (by moving the slider), or programmatically (to update the slider after - * manual input of a threshold value). - */ - protected void sliderValueChanged() - { - /* - * squash rounding errors by forcing min/max of slider to - * actual min/max of feature score range - */ - int value = slider.getValue(); - threshline.value = value == slider.getMaximum() ? max - : (value == slider.getMinimum() ? min : value / scaleFactor); - cs.setThreshold(threshline.value); - - /* - * repaint alignment, but not Overview or structure, - * to avoid overload while dragging the slider - */ - changeColour(false); - } - - void addActionListener(ActionListener graduatedColorEditor) - { - if (colourEditor != null) - { - System.err.println( - "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); - } - colourEditor = graduatedColorEditor; - } - - /** - * Answers the last colour setting selected by user - either oldcs (which may - * be a java.awt.Color) or the new GraduatedColor - * - * @return - */ - FeatureColourI getLastColour() - { - if (cs == null) - { - return oldcs; - } - return cs; - } - - /** - * A helper method to build the drop-down choice of attributes for a feature. - * Where metadata is available with a description for an attribute, that is - * added as a tooltip. The list may optionally be restricted to attributes for - * which we hold a range of numerical values (so suitable candidates for a - * graduated colour scheme). - *

    - * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ", - * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele. - * - * @param featureType - * @param attNames - * @param withNumericRange - */ - protected JComboBox populateAttributesDropdown( - String featureType, List attNames, - boolean withNumericRange) - { - List validAtts = new ArrayList<>(); - List tooltips = new ArrayList<>(); - - FeatureAttributes fa = FeatureAttributes.getInstance(); - for (String[] attName : attNames) - { - if (withNumericRange) - { - float[] minMax = fa.getMinMax(featureType, attName); - if (minMax == null) - { - continue; - } - } - validAtts.add(String.join(COLON, attName)); - String desc = fa.getDescription(featureType, attName); - if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) - { - desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; - } - tooltips.add(desc == null ? "" : desc); - } - - JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( - validAtts, tooltips); - - attCombo.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - changeMinMaxAction.actionPerformed(null); - } - }); - - if (validAtts.isEmpty()) - { - attCombo.setToolTipText(MessageManager - .getString(withNumericRange ? "label.no_numeric_attributes" - : "label.no_attributes")); - } - - return attCombo; - } - -} diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index 9c4b009..46f574e 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -180,15 +180,15 @@ public class FeatureRenderer final JSpinner end = new JSpinner(); start.setPreferredSize(new Dimension(80, 20)); end.setPreferredSize(new Dimension(80, 20)); - final FeatureRenderer me = this; final JLabel colour = new JLabel(); colour.setOpaque(true); // colour.setBorder(BorderFactory.createEtchedBorder()); colour.setMaximumSize(new Dimension(30, 16)); colour.addMouseListener(new MouseAdapter() { - FeatureColourChooser fcc = null; - + /* + * open colour chooser on click in colour panel + */ @Override public void mousePressed(MouseEvent evt) { @@ -205,28 +205,26 @@ public class FeatureRenderer } else { - if (fcc == null) + /* + * variable colour dialog - on OK, refetch the updated + * feature colour and update this display + */ + final String ft = features.get(featureIndex).getType(); + final String type = ft == null ? lastFeatureAdded : ft; + FeatureTypeSettings fcc = new FeatureTypeSettings( + FeatureRenderer.this, type); + fcc.setRequestFocusEnabled(true); + fcc.requestFocus(); + fcc.addActionListener(new ActionListener() { - final String ft = features.get(featureIndex).getType(); - final String type = ft == null ? lastFeatureAdded : ft; - fcc = new FeatureColourChooser(me, type); - fcc.setRequestFocusEnabled(true); - fcc.requestFocus(); - - fcc.addActionListener(new ActionListener() + @Override + public void actionPerformed(ActionEvent e) { - - @Override - public void actionPerformed(ActionEvent e) - { - fcol = fcc.getLastColour(); - fcc = null; - setColour(type, fcol); - updateColourButton(mainPanel, colour, fcol); - } - }); - - } + fcol = FeatureRenderer.this.getFeatureStyle(ft); + setColour(type, fcol); + updateColourButton(mainPanel, colour, fcol); + } + }); } } }); diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index ed98830..fedfe3f 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -20,12 +20,16 @@ */ package jalview.gui; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.COLOUR_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.FILTER_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.SHOW_COLUMN; +import static jalview.viewmodel.seqfeatures.FeatureRendererModel.TYPE_COLUMN; + import jalview.api.FeatureColourI; import jalview.api.FeatureSettingsControllerI; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; -import jalview.datamodel.features.FeatureAttributes; import jalview.gui.Help.HelpId; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; @@ -35,10 +39,6 @@ import jalview.util.Format; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.util.QuickSort; -import jalview.util.ReverseListIterator; -import jalview.util.matcher.Condition; -import jalview.util.matcher.KeyedMatcher; -import jalview.util.matcher.KeyedMatcherI; import jalview.util.matcher.KeyedMatcherSet; import jalview.util.matcher.KeyedMatcherSetI; import jalview.viewmodel.AlignmentViewport; @@ -49,16 +49,13 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; -import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.GridLayout; -import java.awt.LayoutManager; +import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; @@ -72,7 +69,6 @@ import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Hashtable; @@ -85,14 +81,11 @@ import java.util.Vector; import javax.help.HelpSetException; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JColorChooser; -import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JInternalFrame; import javax.swing.JLabel; @@ -100,26 +93,24 @@ import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSlider; -import javax.swing.JTabbedPane; import javax.swing.JTable; -import javax.swing.JTextArea; -import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; public class FeatureSettings extends JPanel implements FeatureSettingsControllerI { + private static final int COLUMN_COUNT = 4; + private static final String COLON = ":"; private static final int MIN_WIDTH = 400; @@ -156,7 +147,7 @@ public class FeatureSettings extends JPanel JPanel groupPanel; JSlider transparency = new JSlider(); - + /* * when true, constructor is still executing - so ignore UI events */ @@ -182,29 +173,6 @@ public class FeatureSettings extends JPanel */ Map typeWidth = null; - /* - * fields of the feature filters tab - */ - private JPanel filtersPane; - - private JPanel chooseFiltersPanel; - - private JComboBox filteredFeatureChoice; - - private JRadioButton andFilters; - - private JRadioButton orFilters; - - /* - * filters for the currently selected feature type - */ - private List filters; - - private JTextArea filtersAsText; - - // set white normally, black to debug layout - private Color debugBorderColour = Color.white; - /** * Constructor * @@ -235,25 +203,48 @@ public class FeatureSettings extends JPanel @Override public String getToolTipText(MouseEvent e) { - if (table.columnAtPoint(e.getPoint()) == 0) + String tip = null; + int column = table.columnAtPoint(e.getPoint()); + switch (column) { - /* - * Tooltip for feature name only - */ - return JvSwingUtils.wrapTooltip(true, MessageManager + case TYPE_COLUMN: + tip = JvSwingUtils.wrapTooltip(true, MessageManager .getString("label.feature_settings_click_drag")); + break; + case FILTER_COLUMN: + int row = table.rowAtPoint(e.getPoint()); + KeyedMatcherSet o = (KeyedMatcherSet) table.getValueAt(row, + column); + tip = o.isEmpty() + ? MessageManager.getString("label.filters_tooltip") + : o.toString(); + break; + default: + break; } - return null; + return tip; } }; table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12)); table.setFont(new Font("Verdana", Font.PLAIN, 12)); - table.setDefaultRenderer(Color.class, new ColorRenderer()); - - table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultRenderer(Color.class, new ColorRenderer()); + // table.setDefaultEditor(Color.class, new ColorEditor(this)); + // table.setDefaultEditor(FeatureColour.class, new ColorEditor(this)); table.setDefaultRenderer(FeatureColour.class, new ColorRenderer()); + + table.setDefaultEditor(KeyedMatcherSet.class, new FilterEditor(this)); + table.setDefaultRenderer(KeyedMatcherSet.class, new FilterRenderer()); + + TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75, + new ColorRenderer(), new ColorEditor(this)); + table.addColumn(colourColumn); + + TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75, + new FilterRenderer(), new FilterEditor(this)); + table.addColumn(filterColumn); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.addMouseListener(new MouseAdapter() @@ -262,11 +253,12 @@ public class FeatureSettings extends JPanel public void mousePressed(MouseEvent evt) { selectedRow = table.rowAtPoint(evt.getPoint()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } else if (evt.getClickCount() == 2) { @@ -274,8 +266,7 @@ public class FeatureSettings extends JPanel boolean toggleSelection = Platform.isControlDown(evt); boolean extendSelection = evt.isShiftDown(); fr.ap.alignFrame.avc.markColumnsContainingFeatures( - invertSelection, extendSelection, toggleSelection, - (String) table.getValueAt(selectedRow, 0)); + invertSelection, extendSelection, toggleSelection, type); } } @@ -286,9 +277,10 @@ public class FeatureSettings extends JPanel selectedRow = table.rowAtPoint(evt.getPoint()); if (evt.isPopupTrigger()) { - popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0), - table.getValueAt(selectedRow, 1), fr.getMinMax(), - evt.getX(), evt.getY()); + String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN); + Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN); + popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(), + evt.getY()); } } }); @@ -344,8 +336,8 @@ public class FeatureSettings extends JPanel if (!fs.resettingTable && !fs.handlingUpdate) { fs.handlingUpdate = true; - fs.resetTable(null); // new groups may be added with new seuqence - // feature types only + fs.resetTable(null); + // new groups may be added with new sequence feature types only fs.handlingUpdate = false; } } @@ -383,7 +375,7 @@ public class FeatureSettings extends JPanel inConstruction = false; } - protected void popupSort(final int selectedRow, final String type, + protected void popupSort(final int rowSelected, final String type, final Object typeCol, final Map minmax, int x, int y) { @@ -443,15 +435,17 @@ public class FeatureSettings extends JPanel { if (featureColour.isSimpleColour()) { - FeatureColourChooser fc = new FeatureColourChooser(me.fr, type); + FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type); fc.addActionListener(this); } else { // bring up simple color chooser colorChooser = new JColorChooser(); + String title = MessageManager + .getString("label.select_colour"); JDialog dialog = JColorChooser.createDialog(me, - "Select new Colour", true, // modal + title, true, // modal colorChooser, this, // OK button handler null); // no CANCEL button handler colorChooser.setColor(featureColour.getMaxColour()); @@ -460,20 +454,25 @@ public class FeatureSettings extends JPanel } else { - if (e.getSource() instanceof FeatureColourChooser) + if (e.getSource() instanceof FeatureTypeSettings) { - FeatureColourChooser fc = (FeatureColourChooser) e.getSource(); - table.setValueAt(fc.getLastColour(), selectedRow, 1); + /* + * update after OK in feature colour dialog; the updated + * colour will have already been set in the FeatureRenderer + */ + FeatureColourI fci = fr.getFeatureColours().get(type); + table.setValueAt(fci, rowSelected, 1); table.validate(); } else { // probably the color chooser! table.setValueAt(new FeatureColour(colorChooser.getColor()), - selectedRow, 1); + rowSelected, 1); table.validate(); me.updateFeatureRenderer( - ((FeatureTableModel) table.getModel()).getData(), false); + ((FeatureTableModel) table.getModel()).getData(), + false); } } } @@ -548,8 +547,6 @@ public class FeatureSettings extends JPanel } } - populateFilterableFeatures(); - resetTable(null); validate(); @@ -654,7 +651,8 @@ public class FeatureSettings extends JPanel } } - Object[][] data = new Object[displayableTypes.size()][3]; + int columnCount = COLUMN_COUNT; + Object[][] data = new Object[displayableTypes.size()][columnCount]; int dataIndex = 0; if (fr.hasRenderOrder()) @@ -677,9 +675,13 @@ public class FeatureSettings extends JPanel continue; } - data[dataIndex][0] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - data[dataIndex][2] = new Boolean( + data[dataIndex][TYPE_COLUMN] = type; + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new KeyedMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean( af.getViewport().getFeaturesDisplayed().isVisible(type)); dataIndex++; displayableTypes.remove(type); @@ -693,27 +695,31 @@ public class FeatureSettings extends JPanel while (!displayableTypes.isEmpty()) { String type = displayableTypes.iterator().next(); - data[dataIndex][0] = type; + data[dataIndex][TYPE_COLUMN] = type; - data[dataIndex][1] = fr.getFeatureStyle(type); - if (data[dataIndex][1] == null) + data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); + if (data[dataIndex][COLOUR_COLUMN] == null) { // "Colour has been updated in another view!!" fr.clearRenderOrder(); return; } - - data[dataIndex][2] = new Boolean(true); + KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + data[dataIndex][FILTER_COLUMN] = featureFilter == null + ? new KeyedMatcherSet() + : featureFilter; + data[dataIndex][SHOW_COLUMN] = new Boolean(true); dataIndex++; displayableTypes.remove(type); } if (originalData == null) { - originalData = new Object[data.length][3]; + int size = data[0].length; + originalData = new Object[data.length][size]; for (int i = 0; i < data.length; i++) { - System.arraycopy(data[i], 0, originalData[i], 0, 3); + System.arraycopy(data[i], 0, originalData[i], 0, size); } } else @@ -734,8 +740,8 @@ public class FeatureSettings extends JPanel } /** - * Updates 'originalData' (used for restore on Cancel) if we detect that - * changes have been made outwith this dialog + * Updates 'originalData' (used for restore on Cancel) if we detect that changes + * have been made outwith this dialog *

      *
    • a new feature type added (and made visible)
    • *
    • a feature colour changed (in the Amend Features dialog)
    • @@ -751,27 +757,27 @@ public class FeatureSettings extends JPanel .getData(); for (Object[] row : foundData) { - String type = (String) row[0]; + String type = (String) row[TYPE_COLUMN]; boolean found = false; for (Object[] current : currentData) { - if (type.equals(current[0])) + if (type.equals(current[TYPE_COLUMN])) { found = true; /* * currently dependent on object equality here; * really need an equals method on FeatureColour */ - if (!row[1].equals(current[1])) + if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN])) { /* * feature colour has changed externally - update originalData */ for (Object[] original : originalData) { - if (type.equals(original[0])) + if (type.equals(original[TYPE_COLUMN])) { - original[1] = row[1]; + original[COLOUR_COLUMN] = row[COLOUR_COLUMN]; break; } } @@ -784,10 +790,11 @@ public class FeatureSettings extends JPanel /* * new feature detected - add to original data (on top) */ - Object[][] newData = new Object[originalData.length + 1][3]; + int size = currentData[0].length; + Object[][] newData = new Object[originalData.length + 1][size]; for (int i = 0; i < originalData.length; i++) { - System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3); + System.arraycopy(originalData[i], 0, newData[i + 1], 0, size); } newData[0] = row; originalData = newData; @@ -797,8 +804,8 @@ public class FeatureSettings extends JPanel /** * Remove from the groups panel any checkboxes for groups that are not in the - * foundGroups set. This enables removing a group from the display when the - * last feature in that group is deleted. + * foundGroups set. This enables removing a group from the display when the last + * feature in that group is deleted. * * @param foundGroups */ @@ -1004,9 +1011,9 @@ public class FeatureSettings extends JPanel { for (int i = 0; i < table.getRowCount(); i++) { - Boolean value = (Boolean) table.getValueAt(i, 2); + Boolean value = (Boolean) table.getValueAt(i, SHOW_COLUMN); - table.setValueAt(new Boolean(!value.booleanValue()), i, 2); + table.setValueAt(new Boolean(!value.booleanValue()), i, SHOW_COLUMN); } } @@ -1020,17 +1027,16 @@ public class FeatureSettings extends JPanel float[] width = new float[data.length]; float[] awidth; float max = 0; - int num = 0; + for (int i = 0; i < data.length; i++) { - awidth = typeWidth.get(data[i][0]); + awidth = typeWidth.get(data[i][TYPE_COLUMN]); if (awidth[0] > 0) { width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better // weight - but have to make per // sequence, too (awidth[2]) // if (width[i]==1) // hack to distinguish single width sequences. - num++; } else { @@ -1047,16 +1053,17 @@ public class FeatureSettings extends JPanel // awidth = (float[]) typeWidth.get(data[i][0]); if (width[i] == 0) { - width[i] = fr.getOrder(data[i][0].toString()); + width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString()); if (width[i] < 0) { - width[i] = fr.setOrder(data[i][0].toString(), i / data.length); + width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(), + i / data.length); } } else { width[i] /= max; // normalize - fr.setOrder(data[i][0].toString(), width[i]); // store for later + fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later } if (i > 0) { @@ -1090,8 +1097,8 @@ public class FeatureSettings extends JPanel } /** - * Update the priority order of features; only repaint if this changed the - * order of visible features + * Update the priority order of features; only repaint if this changed the order + * of visible features * * @param data * @param visibleNew @@ -1111,8 +1118,6 @@ public class FeatureSettings extends JPanel JPanel settingsPane = new JPanel(); settingsPane.setLayout(new BorderLayout()); - filtersPane = new JPanel(); - dasSettingsPane.setLayout(new BorderLayout()); JPanel bigPanel = new JPanel(); @@ -1257,7 +1262,7 @@ public class FeatureSettings extends JPanel if (!inConstruction) { fr.setTransparency((100 - transparency.getValue()) / 100f); - af.alignPanel.paintAlignment(true,true); + af.alignPanel.paintAlignment(true, true); } } }); @@ -1298,15 +1303,6 @@ public class FeatureSettings extends JPanel } }); - JTabbedPane tabbedPane = new JTabbedPane(); - this.add(tabbedPane, BorderLayout.CENTER); - tabbedPane.addTab(MessageManager.getString("label.feature_settings"), - settingsPane); - tabbedPane.addTab(MessageManager.getString("label.filters"), - filtersPane); - // tabbedPane.addTab(MessageManager.getString("label.das_settings"), - // dasSettingsPane); - JPanel transPanel = new JPanel(new GridLayout(1, 2)); bigPanel.add(transPanel, BorderLayout.SOUTH); @@ -1331,444 +1327,7 @@ public class FeatureSettings extends JPanel dasButtonPanel.add(saveDAS); settingsPane.add(bigPanel, BorderLayout.CENTER); settingsPane.add(buttonPanel, BorderLayout.SOUTH); - - initFiltersTab(); - } - - /** - * Populates initial layout of the feature attribute filters panel - */ - protected void initFiltersTab() - { - filters = new ArrayList<>(); - - /* - * choose feature type - */ - JPanel chooseTypePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - chooseTypePanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(chooseTypePanel, - MessageManager.getString("label.feature_type"), true); - filteredFeatureChoice = new JComboBox<>(); - filteredFeatureChoice.addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - refreshFiltersDisplay(); - } - }); - chooseTypePanel.add(new JLabel(MessageManager - .getString("label.feature_to_filter"))); - chooseTypePanel.add(filteredFeatureChoice); - populateFilterableFeatures(); - - /* - * the panel with the filters for the selected feature type - */ - JPanel filtersPanel = new JPanel(); - filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); - filtersPanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(filtersPanel, - MessageManager.getString("label.filters"), true); - - /* - * add AND or OR radio buttons - */ - JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - andOrPanel.setBackground(Color.white); - andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour)); - andFilters = new JRadioButton("And"); - orFilters = new JRadioButton("Or"); - ActionListener actionListener = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - filtersChanged(); - } - }; - andFilters.addActionListener(actionListener); - orFilters.addActionListener(actionListener); - ButtonGroup andOr = new ButtonGroup(); - andOr.add(andFilters); - andOr.add(orFilters); - andFilters.setSelected(true); - andOrPanel.add(new JLabel(MessageManager - .getString("label.join_conditions"))); - andOrPanel.add(andFilters); - andOrPanel.add(orFilters); - filtersPanel.add(andOrPanel); - - /* - * panel with filters - populated by refreshFiltersDisplay - */ - chooseFiltersPanel = new JPanel(); - LayoutManager box = new BoxLayout(chooseFiltersPanel, - BoxLayout.Y_AXIS); - chooseFiltersPanel.setLayout(box); - filtersPanel.add(chooseFiltersPanel); - - /* - * a read-only text view of the current filters - */ - JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5)); - showFiltersPanel.setBackground(Color.white); - JvSwingUtils.createItalicTitledBorder(showFiltersPanel, - MessageManager.getString("label.match_condition"), true); - filtersAsText = new JTextArea(); - filtersAsText.setLineWrap(true); - filtersAsText.setWrapStyleWord(true); - showFiltersPanel.add(filtersAsText); - - filtersPane.setLayout(new BorderLayout()); - filtersPane.add(chooseTypePanel, BorderLayout.NORTH); - filtersPane.add(filtersPanel, BorderLayout.CENTER); - filtersPane.add(showFiltersPanel, BorderLayout.SOUTH); - - /* - * update display for initial feature type selection - */ - refreshFiltersDisplay(); - } - - /** - * Adds entries to the 'choose feature to filter' drop-down choice. Only - * feature types which have known attributes (so can be filtered) are - * included, so recall this method to update the list (check for newly added - * attributes). - */ - protected void populateFilterableFeatures() - { - /* - * suppress action handler while updating the list - */ - ItemListener listener = filteredFeatureChoice.getItemListeners()[0]; - filteredFeatureChoice.removeItemListener(listener); - - filteredFeatureChoice.removeAllItems(); - ReverseListIterator types = new ReverseListIterator<>( - fr.getRenderOrder()); - - boolean found = false; - while (types.hasNext()) - { - String type = types.next(); - if (FeatureAttributes.getInstance().hasAttributes(type)) - { - filteredFeatureChoice.addItem(type); - found = true; - } - } - if (!found) - { - filteredFeatureChoice.addItem(MessageManager - .getString("label.no_feature_attributes")); - filteredFeatureChoice.setEnabled(false); - } - - filteredFeatureChoice.addItemListener(listener); - } - - /** - * Refreshes the display to show any filters currently configured for the - * selected feature type (editable, with 'remove' option), plus one extra row - * for adding a condition. This should be called on change of selected feature - * type, or after a filter has been removed, added or amended. - */ - protected void refreshFiltersDisplay() - { - /* - * clear the panel and list of filter conditions - */ - chooseFiltersPanel.removeAll(); - filters.clear(); - - /* - * look up attributes known for feature type - */ - String selectedType = (String) filteredFeatureChoice.getSelectedItem(); - List attNames = FeatureAttributes.getInstance() - .getAttributes(selectedType); - - /* - * if this feature type has filters set, load them first - */ - KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType); - filtersAsText.setText(""); - if (featureFilters != null) - { - filtersAsText.setText(featureFilters.toString()); - if (!featureFilters.isAnded()) - { - orFilters.setSelected(true); - } - featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); - } - - /* - * and an empty filter for the user to populate (add) - */ - KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "", - (String) null); - filters.add(noFilter); - - /* - * render the conditions in rows, each in its own JPanel - */ - int filterIndex = 0; - for (KeyedMatcherI filter : filters) - { - String[] attName = filter.getKey(); - Condition condition = filter.getMatcher() - .getCondition(); - String pattern = filter.getMatcher().getPattern(); - JPanel row = addFilter(attName, attNames, condition, pattern, filterIndex); - row.setBorder(BorderFactory.createLineBorder(debugBorderColour)); - chooseFiltersPanel.add(row); - filterIndex++; - } - // chooseFiltersPanel.add(Box.createVerticalGlue()); - - filtersPane.validate(); - filtersPane.repaint(); - } - - /** - * A helper method that constructs a panel with one filter condition: - *
        - *
      • a drop-down list of attribute names to choose from
      • - *
      • a drop-down list of conditions to choose from
      • - *
      • a text field for input of a match pattern
      • - *
      • optionally, a 'remove' button
      • - *
      - * If attribute, condition or pattern are not null, they are set as defaults for - * the input fields. The 'remove' button is added unless the pattern is null or - * empty (incomplete filter condition). - * - * @param attName - * @param attNames - * @param cond - * @param pattern - * @param filterIndex - * @return - */ - protected JPanel addFilter(String[] attName, List attNames, - Condition cond, String pattern, int filterIndex) - { - JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); - filterRow.setBackground(Color.white); - - /* - * drop-down choice of attribute, with description as a tooltip - * if we can obtain it - */ - String featureType = (String) filteredFeatureChoice.getSelectedItem(); - final JComboBox attCombo = populateAttributesDropdown( - featureType, attNames); - JComboBox condCombo = new JComboBox<>(); - JTextField patternField = new JTextField(8); - - /* - * action handlers that validate and (if valid) apply changes - */ - ActionListener actionListener = new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - if (attCombo.getSelectedItem() != null) - { - if (validateFilter(patternField, condCombo)) - { - updateFilter(attCombo, condCombo, patternField, filterIndex); - filtersChanged(); - } - } - } - }; - ItemListener itemListener = new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) - { - actionListener.actionPerformed(null); - } - }; - - if (attName == null) // the 'add a condition' row - { - attCombo.setSelectedItem(null); - } - else - { - attCombo.setSelectedItem(String.join(COLON, attName)); - } - attCombo.addItemListener(itemListener); - - filterRow.add(attCombo); - - /* - * drop-down choice of test condition - */ - for (Condition c : Condition.values()) - { - condCombo.addItem(c); - } - if (cond != null) - { - condCombo.setSelectedItem(cond); - } - condCombo.addItemListener(itemListener); - filterRow.add(condCombo); - - /* - * pattern to match against - */ - patternField.setText(pattern); - patternField.addActionListener(actionListener); - patternField.addFocusListener(new FocusAdapter() - { - @Override - public void focusLost(FocusEvent e) - { - actionListener.actionPerformed(null); - } - }); - filterRow.add(patternField); - - /* - * add remove button if filter is populated (non-empty pattern) - */ - if (pattern != null && pattern.trim().length() > 0) - { - // todo: gif for button drawing '-' or 'x' - JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); - removeCondition.setToolTipText(MessageManager - .getString("label.delete_row")); - removeCondition.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - filters.remove(filterIndex); - filtersChanged(); - } - }); - filterRow.add(removeCondition); - } - - return filterRow; - } - - /** - * A helper method to build the drop-down choice of attributes for a feature. - * Where metadata is available with a description for an attribute, that is - * added as a tooltip. - * - * @param featureType - * @param attNames - */ - protected JComboBox populateAttributesDropdown( - String featureType, List attNames) - { - List displayNames = new ArrayList<>(); - List tooltips = new ArrayList<>(); - FeatureAttributes fa = FeatureAttributes.getInstance(); - for (String[] attName : attNames) - { - String desc = fa.getDescription(featureType, attName); - if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) - { - desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; - } - displayNames.add(String.join(COLON, attName)); - tooltips.add(desc == null ? "" : desc); - } - - JComboBox attCombo = JvSwingUtils.buildComboWithTooltips( - displayNames, tooltips); - if (attNames.isEmpty()) - { - attCombo.setToolTipText(MessageManager - .getString("label.no_attributes")); - } - return attCombo; - } - - /** - * Action on any change to feature filtering, namely - *
        - *
      • change of selected attribute
      • - *
      • change of selected condition
      • - *
      • change of match pattern
      • - *
      • removal of a condition
      • - *
      - * The action should be to - *
        - *
      • parse and validate the filters
      • - *
      • if valid, update the filter text box
      • - *
      • and apply the filters to the viewport
      • - *
      - */ - protected void filtersChanged() - { - /* - * update the filter conditions for the feature type - */ - String featureType = (String) filteredFeatureChoice.getSelectedItem(); - boolean anded = andFilters.isSelected(); - KeyedMatcherSetI combined = new KeyedMatcherSet(); - - for (KeyedMatcherI filter : filters) - { - String pattern = filter.getMatcher().getPattern(); - if (pattern.trim().length() > 0) - { - if (anded) - { - combined.and(filter); - } - else - { - combined.or(filter); - } - } - } - - /* - * save the filter conditions in the FeatureRenderer - * (note this might now be an empty filter with no conditions) - */ - fr.setFeatureFilter(featureType, combined); - - filtersAsText.setText(combined.toString()); - - refreshFiltersDisplay(); - - af.alignPanel.paintAlignment(true, true); - } - - /** - * Constructs a filter condition from the given input fields, and replaces the - * condition at filterIndex with the new one - * - * @param attCombo - * @param condCombo - * @param valueField - * @param filterIndex - */ - protected void updateFilter(JComboBox attCombo, - JComboBox condCombo, JTextField valueField, - int filterIndex) - { - String attName = (String) attCombo.getSelectedItem(); - Condition cond = (Condition) condCombo.getSelectedItem(); - String pattern = valueField.getText(); - KeyedMatcherI km = new KeyedMatcher(cond, pattern, - attName.split(COLON)); - - filters.set(filterIndex, km); + this.add(settingsPane); } public void fetchDAS_actionPerformed(ActionEvent e) @@ -1928,68 +1487,25 @@ public class FeatureSettings extends JPanel JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE); } - /** - * Answers true unless a numeric condition has been selected with a - * non-numeric value. Sets the value field to RED with a tooltip if in error. - *

      - * If the pattern entered is empty, this method returns false, but does not - * mark the field as invalid. This supports selecting an attribute for a new - * condition before a match pattern has been entered. - * - * @param value - * @param condCombo - */ - protected boolean validateFilter(JTextField value, - JComboBox condCombo) - { - if (value == null || condCombo == null) - { - return true; // fields not populated - } - - Condition cond = (Condition) condCombo.getSelectedItem(); - value.setBackground(Color.white); - value.setToolTipText(""); - String v1 = value.getText().trim(); - if (v1.length() == 0) - { - return false; - } - - if (cond.isNumeric()) - { - try - { - Float.valueOf(v1); - } catch (NumberFormatException e) - { - value.setBackground(Color.red); - value.setToolTipText(MessageManager - .getString("label.numeric_required")); - return false; - } - } - - return true; - } - // /////////////////////////////////////////////////////////////////////// // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html // /////////////////////////////////////////////////////////////////////// class FeatureTableModel extends AbstractTableModel { - FeatureTableModel(Object[][] data) - { - this.data = data; - } private String[] columnNames = { MessageManager.getString("label.feature_type"), MessageManager.getString("action.colour"), - MessageManager.getString("label.display") }; + MessageManager.getString("label.filter"), + MessageManager.getString("label.show") }; private Object[][] data; + FeatureTableModel(Object[][] data) + { + this.data = data; + } + public Object[][] getData() { return data; @@ -2029,10 +1545,14 @@ public class FeatureSettings extends JPanel return data[row][col]; } + /** + * Answers the class of the object in column c of the first row of the table + */ @Override - public Class getColumnClass(int c) + public Class getColumnClass(int c) { - return getValueAt(0, c).getClass(); + Object v = getValueAt(0, c); + return v == null ? null : v.getClass(); } @Override @@ -2109,6 +1629,54 @@ public class FeatureSettings extends JPanel } } + class FilterRenderer extends JLabel implements TableCellRenderer + { + javax.swing.border.Border unselectedBorder = null; + + javax.swing.border.Border selectedBorder = null; + + public FilterRenderer() + { + setOpaque(true); // MUST do this for background to show up. + setHorizontalTextPosition(SwingConstants.CENTER); + setVerticalTextPosition(SwingConstants.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable tbl, + Object filter, boolean isSelected, boolean hasFocus, int row, + int column) + { + KeyedMatcherSetI theFilter = (KeyedMatcherSetI) filter; + setOpaque(true); + String asText = theFilter.toString(); + setBackground(tbl.getBackground()); + this.setText(asText); + this.setIcon(null); + + if (isSelected) + { + if (selectedBorder == null) + { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else + { + if (unselectedBorder == null) + { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + tbl.getBackground()); + } + setBorder(unselectedBorder); + } + + return this; + } + } + /** * update comp using rendering settings from gcol * @@ -2196,11 +1764,250 @@ public class FeatureSettings extends JPanel } else { - comp.setToolTipText(tt.append(" ").append(comp.getToolTipText()) - .toString()); + comp.setToolTipText( + tt.append(" ").append(comp.getToolTipText()).toString()); } } } + + class ColorEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + FeatureColourI currentColor; + + FeatureTypeSettings chooser; + + String type; + + JButton button; + + JColorChooser colorChooser; + + JDialog dialog; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public ColorEditor(FeatureSettings me) + { + this.me = me; + // Set up the editor (from the table's point of view), + // which is a button. + // This button brings up the color chooser dialog, + // which is the editor from the user's point of view. + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + // Set up the dialog that the button brings up. + colorChooser = new JColorChooser(); + dialog = JColorChooser.createDialog(button, + MessageManager.getString("label.select_colour"), true, // modal + colorChooser, this, // OK button handler + null); // no CANCEL button handler + } + + /** + * Handles events from the editor button and from the dialog's OK button. + */ + @Override + public void actionPerformed(ActionEvent e) + { + // todo test e.getSource() instead here + if (EDIT.equals(e.getActionCommand())) + { + // The user has clicked the cell, so + // bring up the dialog. + if (currentColor.isSimpleColour()) + { + // bring up simple color chooser + button.setBackground(currentColor.getColour()); + colorChooser.setColor(currentColor.getColour()); + dialog.setVisible(true); + } + else + { + // bring up graduated chooser. + chooser = new FeatureTypeSettings(me.fr, type); + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + chooser.addActionListener(this); + chooser.showTab(true); + } + // Make the renderer reappear. + fireEditingStopped(); + + } + else + { + if (currentColor.isSimpleColour()) + { + /* + * read off colour picked in colour chooser after OK pressed + */ + currentColor = new FeatureColour(colorChooser.getColor()); + me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN); + } + else + { + /* + * after OK in variable colour dialog, any changes to colour + * (or filters!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + currentColor = fr.getFeatureColours().get(type); + KeyedMatcherSetI currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new KeyedMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + } + fireEditingStopped(); + me.table.validate(); + } + } + + // Implement the one CellEditor method that AbstractCellEditor doesn't. + @Override + public Object getCellEditorValue() + { + return currentColor; + } + + // Implement the one method defined by TableCellEditor. + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentColor = (FeatureColourI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + if (!currentColor.isSimpleColour()) + { + JLabel btn = new JLabel(); + btn.setSize(button.getSize()); + FeatureSettings.renderGraduatedColor(btn, currentColor); + button.setBackground(btn.getBackground()); + button.setIcon(btn.getIcon()); + button.setText(btn.getText()); + } + else + { + button.setText(""); + button.setIcon(null); + button.setBackground(currentColor.getColour()); + } + return button; + } + } + + /** + * The cell editor for the Filter column. It displays the text of any filters + * for the feature type in that row (in full as a tooltip, possible abbreviated + * as display text). On click in the cell, opens the Feature Display Settings + * dialog at the Filters tab. + */ + class FilterEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener + { + FeatureSettings me; + + KeyedMatcherSetI currentFilter; + + Point lastLocation; + + String type; + + JButton button; + + protected static final String EDIT = "edit"; + + int rowSelected = 0; + + public FilterEditor(FeatureSettings me) + { + this.me = me; + button = new JButton(); + button.setActionCommand(EDIT); + button.addActionListener(this); + button.setBorderPainted(false); + } + + /** + * Handles events from the editor button + */ + @Override + public void actionPerformed(ActionEvent e) + { + if (button == e.getSource()) + { + FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type); + chooser.addActionListener(this); + chooser.setRequestFocusEnabled(true); + chooser.requestFocus(); + if (lastLocation != null) + { + // todo open at its last position on screen + chooser.setBounds(lastLocation.x, lastLocation.y, + chooser.getWidth(), chooser.getHeight()); + chooser.validate(); + } + chooser.showTab(false); + fireEditingStopped(); + } + else if (e.getSource() instanceof Component) + { + + /* + * after OK in variable colour dialog, any changes to filter + * (or colours!) are already set in FeatureRenderer, so just + * update table data without triggering updateFeatureRenderer + */ + FeatureColourI currentColor = fr.getFeatureColours().get(type); + currentFilter = me.fr.getFeatureFilter(type); + if (currentFilter == null) + { + currentFilter = new KeyedMatcherSet(); + } + Object[] data = ((FeatureTableModel) table.getModel()) + .getData()[rowSelected]; + data[COLOUR_COLUMN] = currentColor; + data[FILTER_COLUMN] = currentFilter; + fireEditingStopped(); + me.table.validate(); + } + } + + @Override + public Object getCellEditorValue() + { + return currentFilter; + } + + @Override + public Component getTableCellEditorComponent(JTable theTable, Object value, + boolean isSelected, int row, int column) + { + currentFilter = (KeyedMatcherSetI) value; + this.rowSelected = row; + type = me.table.getValueAt(row, TYPE_COLUMN).toString(); + button.setOpaque(true); + button.setBackground(me.getBackground()); + button.setText(currentFilter.toString()); + button.setToolTipText(currentFilter.toString()); + button.setIcon(null); + return button; + } + } } class FeatureIcon implements Icon @@ -2284,125 +2091,3 @@ class FeatureIcon implements Icon } } } - -class ColorEditor extends AbstractCellEditor - implements TableCellEditor, ActionListener -{ - FeatureSettings me; - - FeatureColourI currentColor; - - FeatureColourChooser chooser; - - String type; - - JButton button; - - JColorChooser colorChooser; - - JDialog dialog; - - protected static final String EDIT = "edit"; - - int selectedRow = 0; - - public ColorEditor(FeatureSettings me) - { - this.me = me; - // Set up the editor (from the table's point of view), - // which is a button. - // This button brings up the color chooser dialog, - // which is the editor from the user's point of view. - button = new JButton(); - button.setActionCommand(EDIT); - button.addActionListener(this); - button.setBorderPainted(false); - // Set up the dialog that the button brings up. - colorChooser = new JColorChooser(); - dialog = JColorChooser.createDialog(button, - MessageManager.getString("label.select_new_colour"), true, // modal - colorChooser, this, // OK button handler - null); // no CANCEL button handler - } - - /** - * Handles events from the editor button and from the dialog's OK button. - */ - @Override - public void actionPerformed(ActionEvent e) - { - - if (EDIT.equals(e.getActionCommand())) - { - // The user has clicked the cell, so - // bring up the dialog. - if (currentColor.isSimpleColour()) - { - // bring up simple color chooser - button.setBackground(currentColor.getColour()); - colorChooser.setColor(currentColor.getColour()); - dialog.setVisible(true); - } - else - { - // bring up graduated chooser. - chooser = new FeatureColourChooser(me.fr, type); - chooser.setRequestFocusEnabled(true); - chooser.requestFocus(); - chooser.addActionListener(this); - } - // Make the renderer reappear. - fireEditingStopped(); - - } - else - { // User pressed dialog's "OK" button. - if (currentColor.isSimpleColour()) - { - currentColor = new FeatureColour(colorChooser.getColor()); - } - else - { - currentColor = chooser.getLastColour(); - } - me.table.setValueAt(getCellEditorValue(), selectedRow, 1); - fireEditingStopped(); - me.table.validate(); - } - } - - // Implement the one CellEditor method that AbstractCellEditor doesn't. - @Override - public Object getCellEditorValue() - { - return currentColor; - } - - // Implement the one method defined by TableCellEditor. - @Override - public Component getTableCellEditorComponent(JTable table, Object value, - boolean isSelected, int row, int column) - { - currentColor = (FeatureColourI) value; - this.selectedRow = row; - type = me.table.getValueAt(row, 0).toString(); - button.setOpaque(true); - button.setBackground(me.getBackground()); - if (!currentColor.isSimpleColour()) - { - JLabel btn = new JLabel(); - btn.setSize(button.getSize()); - FeatureSettings.renderGraduatedColor(btn, currentColor); - button.setBackground(btn.getBackground()); - button.setIcon(btn.getIcon()); - button.setText(btn.getText()); - } - else - { - button.setText(""); - button.setIcon(null); - button.setBackground(currentColor.getColour()); - } - return button; - } -} diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java new file mode 100644 index 0000000..e280091 --- /dev/null +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -0,0 +1,1548 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.gui; + +import jalview.api.AlignmentViewPanel; +import jalview.api.FeatureColourI; +import jalview.datamodel.GraphLine; +import jalview.datamodel.features.FeatureAttributes; +import jalview.datamodel.features.FeatureAttributes.Datatype; +import jalview.schemes.FeatureColour; +import jalview.util.ColorUtils; +import jalview.util.MessageManager; +import jalview.util.matcher.Condition; +import jalview.util.matcher.KeyedMatcher; +import jalview.util.matcher.KeyedMatcherI; +import jalview.util.matcher.KeyedMatcherSet; +import jalview.util.matcher.KeyedMatcherSetI; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JSlider; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.LineBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicArrowButton; + +/** + * A dialog where the user can configure colour scheme, and any filters, for one + * feature type + *

      + * (Was FeatureColourChooser prior to Jalview 1.11, renamed with the addition of + * filter options) + */ +public class FeatureTypeSettings extends JalviewDialog +{ + private static final int RADIO_WIDTH = 130; + + private static final String COLON = ":"; + + private static final int MAX_TOOLTIP_LENGTH = 50; + + private static final int NO_COLOUR_OPTION = 0; + + private static final int MIN_COLOUR_OPTION = 1; + + private static final int MAX_COLOUR_OPTION = 2; + + private static final int ABOVE_THRESHOLD_OPTION = 1; + + private static final int BELOW_THRESHOLD_OPTION = 2; + + /* + * FeatureRenderer holds colour scheme and filters for feature types + */ + private final FeatureRenderer fr; // todo refactor to allow interface type here + + /* + * the view panel to update when settings change + */ + private final AlignmentViewPanel ap; + + private final String featureType; + + /* + * the colour and filters to reset to on Cancel + */ + private final FeatureColourI originalColour; + + private final KeyedMatcherSetI originalFilter; + + /* + * set flag to true when setting values programmatically, + * to avoid invocation of action handlers + */ + private boolean adjusting = false; + + private float min; + + private float max; + + private float scaleFactor; + + /* + * radio button group, to select what to colour by: + * simple colour, by category (text), or graduated + */ + private JRadioButton simpleColour = new JRadioButton(); + + private JRadioButton byCategory = new JRadioButton(); + + private JRadioButton graduatedColour = new JRadioButton(); + + private JPanel singleColour = new JPanel(); + + private JPanel minColour = new JPanel(); + + private JPanel maxColour = new JPanel(); + + private JComboBox threshold = new JComboBox<>(); + + private JSlider slider = new JSlider(); + + private JTextField thresholdValue = new JTextField(20); + + private JCheckBox thresholdIsMin = new JCheckBox(); + + private GraphLine threshline; + + private ActionListener featureSettings = null; + + private ActionListener changeColourAction; + + /* + * choice of option for 'colour for no value' + */ + private JComboBox noValueCombo; + + /* + * choice of what to colour by text (Label or attribute) + */ + private JComboBox colourByTextCombo; + + /* + * choice of what to colour by range (Score or attribute) + */ + private JComboBox colourByRangeCombo; + + private JRadioButton andFilters; + + private JRadioButton orFilters; + + /* + * filters for the currently selected feature type + */ + private List filters; + + // set white normally, black to debug layout + private Color debugBorderColour = Color.white; + + private JPanel chooseFiltersPanel; + + private JTabbedPane tabbedPane; + + /** + * Constructor + * + * @param frender + * @param theType + */ + public FeatureTypeSettings(FeatureRenderer frender, String theType) + { + this(frender, false, theType); + } + + /** + * Constructor, with option to make a blocking dialog (has to complete in the + * AWT event queue thread). Currently this option is always set to false. + * + * @param frender + * @param blocking + * @param theType + */ + FeatureTypeSettings(FeatureRenderer frender, boolean blocking, + String theType) + { + this.fr = frender; + this.featureType = theType; + ap = fr.ap; + originalFilter = fr.getFeatureFilter(theType); + originalColour = fr.getFeatureColours().get(theType); + + adjusting = true; + + try + { + initialise(); + } catch (Exception ex) + { + ex.printStackTrace(); + return; + } + + updateColoursTab(); + + updateFiltersTab(); + + adjusting = false; + + colourChanged(false); + + String title = MessageManager + .formatMessage("label.display_settings_for", new String[] + { theType }); + initDialogFrame(this, true, blocking, title, 600, 360); + + waitForInput(); + } + + /** + * Configures the widgets on the Colours tab according to the current feature + * colour scheme + */ + private void updateColoursTab() + { + FeatureColourI fc = fr.getFeatureColours().get(featureType); + + /* + * suppress action handling while updating values programmatically + */ + adjusting = true; + try + { + /* + * single colour + */ + if (fc.isSimpleColour()) + { + simpleColour.setSelected(true); + singleColour.setBackground(fc.getColour()); + singleColour.setForeground(fc.getColour()); + } + + /* + * colour by text (Label or attribute text) + */ + if (fc.isColourByLabel()) + { + byCategory.setSelected(true); + colourByTextCombo.setEnabled(colourByTextCombo.getItemCount() > 1); + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByTextCombo + .setSelectedItem(toAttributeDisplayName(attributeName)); + } + else + { + colourByTextCombo + .setSelectedItem(MessageManager.getString("label.label")); + } + } + else + { + colourByTextCombo.setEnabled(false); + } + + if (!fc.isGraduatedColour()) + { + colourByRangeCombo.setEnabled(false); + minColour.setEnabled(false); + maxColour.setEnabled(false); + noValueCombo.setEnabled(false); + threshold.setEnabled(false); + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + return; + } + + /* + * Graduated colour, by score or attribute value range + */ + graduatedColour.setSelected(true); + colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1); + minColour.setEnabled(true); + maxColour.setEnabled(true); + noValueCombo.setEnabled(true); + threshold.setEnabled(true); + minColour.setBackground(fc.getMinColour()); + maxColour.setBackground(fc.getMaxColour()); + + if (fc.isColourByAttribute()) + { + String[] attributeName = fc.getAttributeName(); + colourByRangeCombo + .setSelectedItem(toAttributeDisplayName(attributeName)); + } + else + { + colourByRangeCombo + .setSelectedItem(MessageManager.getString("label.score")); + } + Color noColour = fc.getNoColour(); + if (noColour == null) + { + noValueCombo.setSelectedIndex(NO_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMinColour())) + { + noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION); + } + else if (noColour.equals(fc.getMaxColour())) + { + noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION); + } + + /* + * update min-max scaling if there is a range to work with, + * else disable the widgets (this shouldn't happen if only + * valid options are offered in the combo box) + */ + scaleFactor = (max == min) ? 1f : 100f / (max - min); + float range = (max - min) * scaleFactor; + slider.setMinimum((int) (min * scaleFactor)); + slider.setMaximum((int) (max * scaleFactor)); + slider.setMajorTickSpacing((int) (range / 10f)); + + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + threshline.value = fc.getThreshold(); + + if (fc.hasThreshold()) + { + threshold.setSelectedIndex( + fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION + : BELOW_THRESHOLD_OPTION); + slider.setEnabled(true); + slider.setValue((int) (fc.getThreshold() * scaleFactor)); + thresholdValue.setText(String.valueOf(getRoundedSliderValue())); + thresholdValue.setEnabled(true); + thresholdIsMin.setEnabled(true); + } + else + { + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdIsMin.setEnabled(false); + } + thresholdIsMin.setSelected(!fc.isAutoScaled()); + } finally + { + adjusting = false; + } + } + + /** + * Configures the initial layout + */ + private void initialise() + { + this.setLayout(new BorderLayout()); + tabbedPane = new JTabbedPane(); + this.add(tabbedPane, BorderLayout.CENTER); + + /* + * an ActionListener that applies colour changes + */ + changeColourAction = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + colourChanged(true); + } + }; + + /* + * first tab: colour options + */ + JPanel coloursPanel = initialiseColoursPanel(); + tabbedPane.addTab(MessageManager.getString("action.colour"), + coloursPanel); + + /* + * second tab: filter options + */ + JPanel filtersPanel = initialiseFiltersPanel(); + tabbedPane.addTab(MessageManager.getString("label.filters"), + filtersPanel); + + JPanel okCancelPanel = initialiseOkCancelPanel(); + + this.add(okCancelPanel, BorderLayout.SOUTH); + } + + /** + * Updates the min-max range if Colour By selected item is Score, or an + * attribute, with a min-max range + */ + protected void updateMinMax() + { + if (!graduatedColour.isSelected()) + { + return; + } + + float[] minMax = null; + String colourBy = (String) colourByRangeCombo.getSelectedItem(); + if (MessageManager.getString("label.score").equals(colourBy)) + { + minMax = fr.getMinMax().get(featureType)[0]; + } + else + { + // colour by attribute range + String[] attNames = fromAttributeDisplayName(colourBy); + minMax = FeatureAttributes.getInstance().getMinMax(featureType, + attNames); + } + + if (minMax != null) + { + min = minMax[0]; + max = minMax[1]; + } + } + + /** + * Lay out fields for graduated colour (by score or attribute value) + * + * @return + */ + private JPanel initialiseGraduatedColourPanel() + { + JPanel graduatedColourPanel = new JPanel(); + graduatedColourPanel.setLayout( + new BoxLayout(graduatedColourPanel, BoxLayout.Y_AXIS)); + JvSwingUtils.createTitledBorder(graduatedColourPanel, + MessageManager.getString("label.graduated_colour"), true); + graduatedColourPanel.setBackground(Color.white); + + /* + * first row: graduated colour radio button, score/attribute drop-down + */ + JPanel graduatedChoicePanel = new JPanel( + new FlowLayout(FlowLayout.LEFT)); + graduatedChoicePanel.setBackground(Color.white); + graduatedColour = new JRadioButton( + MessageManager.getString("label.by_range_of") + COLON); + graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + graduatedColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (graduatedColour.isSelected()) + { + colourChanged(true); + } + } + }); + graduatedChoicePanel.add(graduatedColour); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByRangeCombo = populateAttributesDropdown(attNames, true, false); + colourByRangeCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + /* + * disable graduated colour option if no range found + */ + graduatedColour.setEnabled(colourByRangeCombo.getItemCount() > 0); + + graduatedChoicePanel.add(colourByRangeCombo); + graduatedColourPanel.add(graduatedChoicePanel); + + /* + * second row - min/max/no colours + */ + JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + colourRangePanel.setBackground(Color.white); + graduatedColourPanel.add(colourRangePanel); + + minColour.setFont(JvSwingUtils.getLabelFont()); + minColour.setBorder(BorderFactory.createLineBorder(Color.black)); + minColour.setPreferredSize(new Dimension(40, 20)); + minColour.setToolTipText(MessageManager.getString("label.min_colour")); + minColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (minColour.isEnabled()) + { + showColourChooser(minColour, "label.select_colour_minimum_value"); + } + } + }); + + maxColour.setFont(JvSwingUtils.getLabelFont()); + maxColour.setBorder(BorderFactory.createLineBorder(Color.black)); + maxColour.setPreferredSize(new Dimension(40, 20)); + maxColour.setToolTipText(MessageManager.getString("label.max_colour")); + maxColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (maxColour.isEnabled()) + { + showColourChooser(maxColour, "label.select_colour_maximum_value"); + } + } + }); + maxColour.setBorder(new LineBorder(Color.black)); + + /* + * default max colour to current colour (if a plain colour), + * or to Black if colour by label; make min colour a pale + * version of max colour + */ + FeatureColourI fc = fr.getFeatureColours().get(featureType); + Color bg = fc.isSimpleColour() ? fc.getColour() : Color.BLACK; + maxColour.setBackground(bg); + minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f)); + + noValueCombo = new JComboBox<>(); + noValueCombo.addItem(MessageManager.getString("label.no_colour")); + noValueCombo.addItem(MessageManager.getString("label.min_colour")); + noValueCombo.addItem(MessageManager.getString("label.max_colour")); + noValueCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + + JLabel minText = new JLabel( + MessageManager.getString("label.min_value") + COLON); + minText.setFont(JvSwingUtils.getLabelFont()); + JLabel maxText = new JLabel( + MessageManager.getString("label.max_value") + COLON); + maxText.setFont(JvSwingUtils.getLabelFont()); + JLabel noText = new JLabel( + MessageManager.getString("label.no_value") + COLON); + noText.setFont(JvSwingUtils.getLabelFont()); + + colourRangePanel.add(minText); + colourRangePanel.add(minColour); + colourRangePanel.add(maxText); + colourRangePanel.add(maxColour); + colourRangePanel.add(noText); + colourRangePanel.add(noValueCombo); + + /* + * third row - threshold options and value + */ + JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + thresholdPanel.setBackground(Color.white); + graduatedColourPanel.add(thresholdPanel); + + threshold.addActionListener(changeColourAction); + threshold.setToolTipText(MessageManager + .getString("label.threshold_feature_display_by_score")); + threshold.addItem(MessageManager + .getString("label.threshold_feature_no_threshold")); // index 0 + threshold.addItem(MessageManager + .getString("label.threshold_feature_above_threshold")); // index 1 + threshold.addItem(MessageManager + .getString("label.threshold_feature_below_threshold")); // index 2 + + thresholdValue.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + thresholdValue_actionPerformed(); + } + }); + thresholdValue.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + thresholdValue_actionPerformed(); + } + }); + slider.setPaintLabels(false); + slider.setPaintTicks(true); + slider.setBackground(Color.white); + slider.setEnabled(false); + slider.setOpaque(false); + slider.setPreferredSize(new Dimension(100, 32)); + slider.setToolTipText( + MessageManager.getString("label.adjust_threshold")); + + slider.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent evt) + { + if (!adjusting) + { + thresholdValue + .setText(String.valueOf(slider.getValue() / scaleFactor)); + sliderValueChanged(); + } + } + }); + slider.addMouseListener(new MouseAdapter() + { + @Override + public void mouseReleased(MouseEvent evt) + { + /* + * only update Overview and/or structure colouring + * when threshold slider drag ends (mouse up) + */ + if (ap != null) + { + ap.paintAlignment(true, true); + } + } + }); + + thresholdValue.setEnabled(false); + thresholdValue.setColumns(7); + + thresholdPanel.add(threshold); + thresholdPanel.add(slider); + thresholdPanel.add(thresholdValue); + + thresholdIsMin.setBackground(Color.white); + thresholdIsMin + .setText(MessageManager.getString("label.threshold_minmax")); + thresholdIsMin.setToolTipText(MessageManager + .getString("label.toggle_absolute_relative_display_threshold")); + thresholdIsMin.addActionListener(changeColourAction); + thresholdPanel.add(thresholdIsMin); + + return graduatedColourPanel; + } + + /** + * Lay out OK and Cancel buttons + * + * @return + */ + private JPanel initialiseOkCancelPanel() + { + JPanel okCancelPanel = new JPanel(); + // okCancelPanel.setBackground(Color.white); + okCancelPanel.add(ok); + okCancelPanel.add(cancel); + return okCancelPanel; + } + + /** + * Lay out Colour options panel, containing + *

        + *
      • plain colour, with colour picker
      • + *
      • colour by text, with choice of Label or other attribute
      • + *
      • colour by range, of score or other attribute, when available
      • + *
      + * + * @return + */ + private JPanel initialiseColoursPanel() + { + JPanel colourByPanel = new JPanel(); + colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS)); + + /* + * simple colour radio button and colour picker + */ + JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + simpleColourPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(simpleColourPanel, + MessageManager.getString("label.simple"), true); + colourByPanel.add(simpleColourPanel); + + simpleColour = new JRadioButton( + MessageManager.getString("label.simple_colour")); + simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + simpleColour.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (simpleColour.isSelected() && !adjusting) + { + showColourChooser(singleColour, "label.select_colour"); + } + } + + }); + + singleColour.setFont(JvSwingUtils.getLabelFont()); + singleColour.setBorder(BorderFactory.createLineBorder(Color.black)); + singleColour.setPreferredSize(new Dimension(40, 20)); + singleColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + if (simpleColour.isSelected()) + { + showColourChooser(singleColour, "label.select_colour"); + } + } + }); + simpleColourPanel.add(simpleColour); // radio button + simpleColourPanel.add(singleColour); // colour picker button + + /* + * colour by text (category) radio button and drop-down choice list + */ + JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + byTextPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(byTextPanel, + MessageManager.getString("label.colour_by_text"), true); + colourByPanel.add(byTextPanel); + byCategory = new JRadioButton( + MessageManager.getString("label.by_text_of") + COLON); + byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + byCategory.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + if (byCategory.isSelected()) + { + colourChanged(true); + } + } + }); + byTextPanel.add(byCategory); + + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + colourByTextCombo = populateAttributesDropdown(attNames, false, true); + colourByTextCombo.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + colourChanged(true); + } + }); + byTextPanel.add(colourByTextCombo); + + /* + * graduated colour panel + */ + JPanel graduatedColourPanel = initialiseGraduatedColourPanel(); + colourByPanel.add(graduatedColourPanel); + + /* + * 3 radio buttons select between simple colour, + * by category (text), or graduated + */ + ButtonGroup bg = new ButtonGroup(); + bg.add(simpleColour); + bg.add(byCategory); + bg.add(graduatedColour); + + return colourByPanel; + } + + private void showColourChooser(JPanel colourPanel, String key) + { + Color col = JColorChooser.showDialog(this, + MessageManager.getString(key), colourPanel.getBackground()); + if (col != null) + { + colourPanel.setBackground(col); + colourPanel.setForeground(col); + } + colourPanel.repaint(); + colourChanged(true); + } + + /** + * Constructs and sets the selected colour options as the colour for the feature + * type, and repaints the alignment, and optionally the Overview and/or + * structure viewer if open + * + * @param updateStructsAndOverview + */ + void colourChanged(boolean updateStructsAndOverview) + { + if (adjusting) + { + /* + * ignore action handlers while setting values programmatically + */ + return; + } + + /* + * ensure min-max range is for the latest choice of + * 'graduated colour by' + */ + updateMinMax(); + + FeatureColourI acg = makeColourFromInputs(); + + /* + * save the colour, and repaint stuff + */ + fr.setColour(featureType, acg); + ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); + + updateColoursTab(); + } + + /** + * Converts the input values into an instance of FeatureColour + * + * @return + */ + private FeatureColourI makeColourFromInputs() + { + /* + * easiest case - a single colour + */ + if (simpleColour.isSelected()) + { + return new FeatureColour(singleColour.getBackground()); + } + + /* + * next easiest case - colour by Label, or attribute text + */ + if (byCategory.isSelected()) + { + Color c = this.getBackground(); + FeatureColourI fc = new FeatureColour(c, c, null, 0f, 0f); + fc.setColourByLabel(true); + String byWhat = (String) colourByTextCombo.getSelectedItem(); + if (!MessageManager.getString("label.label").equals(byWhat)) + { + fc.setAttributeName(fromAttributeDisplayName(byWhat)); + } + return fc; + } + + /* + * remaining case - graduated colour by score, or attribute value + */ + Color noColour = null; + if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION) + { + noColour = minColour.getBackground(); + } + else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION) + { + noColour = maxColour.getBackground(); + } + + float thresh = 0f; + try + { + thresh = Float.valueOf(thresholdValue.getText()); + } catch (NumberFormatException e) + { + // invalid inputs are already handled on entry + } + + /* + * min-max range is to (or from) threshold value if + * 'threshold is min/max' is selected + */ + float minValue = min; + float maxValue = max; + final int thresholdOption = threshold.getSelectedIndex(); + if (thresholdIsMin.isSelected() + && thresholdOption == ABOVE_THRESHOLD_OPTION) + { + minValue = thresh; + } + if (thresholdIsMin.isSelected() + && thresholdOption == BELOW_THRESHOLD_OPTION) + { + maxValue = thresh; + } + + /* + * make the graduated colour + */ + FeatureColourI fc = new FeatureColour(minColour.getBackground(), + maxColour.getBackground(), noColour, minValue, maxValue); + + /* + * set attribute to colour by if selected + */ + String byWhat = (String) colourByRangeCombo.getSelectedItem(); + if (!MessageManager.getString("label.score").equals(byWhat)) + { + fc.setAttributeName(fromAttributeDisplayName(byWhat)); + } + + /* + * set threshold options and 'autoscaled' which is + * false if 'threshold is min/max' is selected + * else true (colour range is on actual range of values) + */ + fc.setThreshold(thresh); + fc.setAutoScaled(!thresholdIsMin.isSelected()); + fc.setAboveThreshold(thresholdOption == ABOVE_THRESHOLD_OPTION); + fc.setBelowThreshold(thresholdOption == BELOW_THRESHOLD_OPTION); + + if (threshline == null) + { + /* + * todo not yet implemented: visual indication of feature threshold + */ + threshline = new GraphLine((max - min) / 2f, "Threshold", + Color.black); + } + + return fc; + } + + /** + * A helper method that converts a 'compound' attribute name from its display + * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attribute + * @return + */ + private String[] fromAttributeDisplayName(String attribute) + { + return attribute == null ? null : attribute.split(COLON); + } + + /** + * A helper method that converts a 'compound' attribute name to its display + * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attName + * @return + */ + private String toAttributeDisplayName(String[] attName) + { + return attName == null ? "" : String.join(COLON, attName); + } + + @Override + protected void raiseClosed() + { + if (this.featureSettings != null) + { + featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED")); + } + } + + /** + * Action on OK is just to dismiss the dialog - any changes have already been + * applied + */ + @Override + public void okPressed() + { + } + + /** + * Action on Cancel is to restore colour scheme and filters as they were when + * the dialog was opened + */ + @Override + public void cancelPressed() + { + fr.setColour(featureType, originalColour); + fr.setFeatureFilter(featureType, originalFilter); + ap.paintAlignment(true, true); + } + + /** + * Action on text entry of a threshold value + */ + protected void thresholdValue_actionPerformed() + { + try + { + adjusting = true; + float f = Float.parseFloat(thresholdValue.getText()); + slider.setValue((int) (f * scaleFactor)); + threshline.value = f; + thresholdValue.setBackground(Color.white); // ok + + /* + * force repaint of any Overview window or structure + */ + ap.paintAlignment(true, true); + } catch (NumberFormatException ex) + { + thresholdValue.setBackground(Color.red); // not ok + } finally + { + adjusting = false; + } + } + + /** + * Action on change of threshold slider value. This may be done interactively + * (by moving the slider), or programmatically (to update the slider after + * manual input of a threshold value). + */ + protected void sliderValueChanged() + { + threshline.value = getRoundedSliderValue(); + + /* + * repaint alignment, but not Overview or structure, + * to avoid overload while dragging the slider + */ + colourChanged(false); + } + + /** + * Converts the slider value to its absolute value by dividing by the + * scaleFactor. Rounding errors are squashed by forcing min/max of slider range + * to the actual min/max of feature score range + * + * @return + */ + private float getRoundedSliderValue() + { + int value = slider.getValue(); + float f = value == slider.getMaximum() ? max + : (value == slider.getMinimum() ? min : value / scaleFactor); + return f; + } + + void addActionListener(ActionListener listener) + { + if (featureSettings != null) + { + System.err.println( + "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser"); + } + featureSettings = listener; + } + + /** + * A helper method to build the drop-down choice of attributes for a feature. If + * 'withRange' is true, then Score, and any attributes with a min-max range, are + * added. If 'withText' is true, Label and any known attributes are added. This + * allows 'categorical numerical' attributes e.g. codon position to be coloured + * by text. + *

      + * Where metadata is available with a description for an attribute, that is + * added as a tooltip. + *

      + * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ", + * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele. + *

      + * This method does not add any ActionListener to the JComboBox. + * + * @param attNames + * @param withRange + * @param withText + */ + protected JComboBox populateAttributesDropdown( + List attNames, boolean withRange, boolean withText) + { + List displayAtts = new ArrayList<>(); + List tooltips = new ArrayList<>(); + + if (withText) + { + displayAtts.add(MessageManager.getString("label.label")); + tooltips.add(MessageManager.getString("label.description")); + } + if (withRange) + { + float[][] minMax = fr.getMinMax().get(featureType); + if (minMax != null && minMax[0][0] != minMax[0][1]) + { + displayAtts.add(MessageManager.getString("label.score")); + tooltips.add(MessageManager.getString("label.score")); + } + } + + FeatureAttributes fa = FeatureAttributes.getInstance(); + for (String[] attName : attNames) + { + float[] minMax = fa.getMinMax(featureType, attName); + boolean hasRange = minMax != null && minMax[0] != minMax[1]; + if (!withText && !hasRange) + { + continue; + } + displayAtts.add(toAttributeDisplayName(attName)); + String desc = fa.getDescription(featureType, attName); + if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) + { + desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "..."; + } + tooltips.add(desc == null ? "" : desc); + } + + JComboBox attCombo = JvSwingUtils + .buildComboWithTooltips(displayAtts, tooltips); + + return attCombo; + } + + /** + * Populates initial layout of the feature attribute filters panel + */ + private JPanel initialiseFiltersPanel() + { + filters = new ArrayList<>(); + + JPanel filtersPanel = new JPanel(); + filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS)); + filtersPanel.setBackground(Color.white); + JvSwingUtils.createTitledBorder(filtersPanel, + MessageManager.getString("label.filters"), true); + + JPanel andOrPanel = initialiseAndOrPanel(); + filtersPanel.add(andOrPanel); + + /* + * panel with filters - populated by refreshFiltersDisplay + */ + chooseFiltersPanel = new JPanel(); + LayoutManager box = new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS); + chooseFiltersPanel.setLayout(box); + filtersPanel.add(chooseFiltersPanel); + + return filtersPanel; + } + + /** + * Lays out the panel with radio buttons to AND or OR filter conditions + * + * @return + */ + private JPanel initialiseAndOrPanel() + { + JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + andOrPanel.setBackground(Color.white); + andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour)); + andFilters = new JRadioButton(MessageManager.getString("label.and")); + orFilters = new JRadioButton(MessageManager.getString("label.or")); + ActionListener actionListener = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + filtersChanged(); + } + }; + andFilters.addActionListener(actionListener); + orFilters.addActionListener(actionListener); + ButtonGroup andOr = new ButtonGroup(); + andOr.add(andFilters); + andOr.add(orFilters); + andFilters.setSelected(true); + andOrPanel.add( + new JLabel(MessageManager.getString("label.join_conditions"))); + andOrPanel.add(andFilters); + andOrPanel.add(orFilters); + return andOrPanel; + } + + /** + * Refreshes the display to show any filters currently configured for the + * selected feature type (editable, with 'remove' option), plus one extra row + * for adding a condition. This should be called after a filter has been + * removed, added or amended. + */ + private void updateFiltersTab() + { + /* + * clear the panel and list of filter conditions + */ + chooseFiltersPanel.removeAll(); + filters.clear(); + + /* + * look up attributes known for feature type + */ + List attNames = FeatureAttributes.getInstance() + .getAttributes(featureType); + + /* + * if this feature type has filters set, load them first + */ + KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType); + if (featureFilters != null) + { + if (!featureFilters.isAnded()) + { + orFilters.setSelected(true); + } + featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); + } + + /* + * and an empty filter for the user to populate (add) + */ + KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "", + (String) null); + filters.add(noFilter); + + /* + * render the conditions in rows, each in its own JPanel + */ + int filterIndex = 0; + for (KeyedMatcherI filter : filters) + { + String[] attName = filter.getKey(); + Condition condition = filter.getMatcher().getCondition(); + String pattern = filter.getMatcher().getPattern(); + JPanel row = addFilter(attName, attNames, condition, pattern, + filterIndex); + row.setBorder(BorderFactory.createLineBorder(debugBorderColour)); + chooseFiltersPanel.add(row); + filterIndex++; + } + + this.validate(); + this.repaint(); + } + + /** + * A helper method that constructs a row (panel) with one filter condition: + *

        + *
      • a drop-down list of attribute names to choose from
      • + *
      • a drop-down list of conditions to choose from
      • + *
      • a text field for input of a match pattern
      • + *
      • optionally, a 'remove' button
      • + *
      + * If attribute, condition or pattern are not null, they are set as defaults for + * the input fields. The 'remove' button is added unless the pattern is null or + * empty (incomplete filter condition). + * + * @param attName + * @param attNames + * @param cond + * @param pattern + * @param filterIndex + * @return + */ + protected JPanel addFilter(String[] attName, List attNames, + Condition cond, String pattern, int filterIndex) + { + JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); + filterRow.setBackground(Color.white); + + /* + * drop-down choice of attribute, with description as a tooltip + * if we can obtain it + */ + final JComboBox attCombo = populateAttributesDropdown(attNames, + true, true); + JComboBox condCombo = new JComboBox<>(); + JTextField patternField = new JTextField(8); + + /* + * action handlers that validate and (if valid) apply changes + */ + ActionListener actionListener = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + if (attCombo.getSelectedItem() != null) + { + if (validateFilter(patternField, condCombo)) + { + updateFilter(attCombo, condCombo, patternField, filterIndex); + filtersChanged(); + } + } + } + }; + ItemListener itemListener = new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + actionListener.actionPerformed(null); + } + }; + + if (attName == null) // the 'add a condition' row + { + attCombo.setSelectedIndex(0); + } + else + { + attCombo.setSelectedItem(toAttributeDisplayName(attName)); + } + attCombo.addItemListener(itemListener); + + filterRow.add(attCombo); + + /* + * drop-down choice of test condition + */ + populateConditions((String) attCombo.getSelectedItem(), cond, + condCombo); + condCombo.addItemListener(itemListener); + filterRow.add(condCombo); + + /* + * pattern to match against + */ + patternField.setText(pattern); + patternField.addActionListener(actionListener); + patternField.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + actionListener.actionPerformed(null); + } + }); + filterRow.add(patternField); + + /* + * add remove button if filter is populated (non-empty pattern) + */ + if (pattern != null && pattern.trim().length() > 0) + { + // todo: gif for button drawing '-' or 'x' + JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); + removeCondition + .setToolTipText(MessageManager.getString("label.delete_row")); + removeCondition.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + filters.remove(filterIndex); + filtersChanged(); + } + }); + filterRow.add(removeCondition); + } + + return filterRow; + } + + /** + * Populates the drop-down list of comparison conditions for the given attribute + * name. The conditions added depend on the datatype of the attribute values. + * The supplied condition is set as the selected item in the list, provided it + * is in the list. + * + * @param attName + * @param cond + * @param condCombo + */ + private void populateConditions(String attName, Condition cond, + JComboBox condCombo) + { + Datatype type = FeatureAttributes.getInstance().getDatatype(featureType, + attName); + if (MessageManager.getString("label.label").equals(attName)) + { + type = Datatype.Character; + } + else if (MessageManager.getString("label.score").equals(attName)) + { + type = Datatype.Number; + } + + for (Condition c : Condition.values()) + { + if ((c.isNumeric() && type != Datatype.Character) + || (!c.isNumeric() && type != Datatype.Number)) + { + condCombo.addItem(c); + } + } + + /* + * set the selected condition (does nothing if not in the list) + */ + if (cond != null) + { + condCombo.setSelectedItem(cond); + } + } + + /** + * Answers true unless a numeric condition has been selected with a non-numeric + * value. Sets the value field to RED with a tooltip if in error. + *

      + * If the pattern entered is empty, this method returns false, but does not mark + * the field as invalid. This supports selecting an attribute for a new + * condition before a match pattern has been entered. + * + * @param value + * @param condCombo + */ + protected boolean validateFilter(JTextField value, + JComboBox condCombo) + { + if (value == null || condCombo == null) + { + return true; // fields not populated + } + + Condition cond = (Condition) condCombo.getSelectedItem(); + value.setBackground(Color.white); + value.setToolTipText(""); + String v1 = value.getText().trim(); + if (v1.length() == 0) + { + return false; + } + + if (cond.isNumeric()) + { + try + { + Float.valueOf(v1); + } catch (NumberFormatException e) + { + value.setBackground(Color.red); + value.setToolTipText( + MessageManager.getString("label.numeric_required")); + return false; + } + } + + return true; + } + + /** + * Constructs a filter condition from the given input fields, and replaces the + * condition at filterIndex with the new one + * + * @param attCombo + * @param condCombo + * @param valueField + * @param filterIndex + */ + protected void updateFilter(JComboBox attCombo, + JComboBox condCombo, JTextField valueField, + int filterIndex) + { + String attName = (String) attCombo.getSelectedItem(); + Condition cond = (Condition) condCombo.getSelectedItem(); + String pattern = valueField.getText(); + KeyedMatcherI km = new KeyedMatcher(cond, pattern, + fromAttributeDisplayName(attName)); + + filters.set(filterIndex, km); + } + + /** + * Makes the dialog visible, at the Feature Colour tab or at the Filters tab + * + * @param coloursTab + */ + public void showTab(boolean coloursTab) + { + setVisible(true); + tabbedPane.setSelectedIndex(coloursTab ? 0 : 1); + } + + /** + * Action on any change to feature filtering, namely + *

        + *
      • change of selected attribute
      • + *
      • change of selected condition
      • + *
      • change of match pattern
      • + *
      • removal of a condition
      • + *
      + * The inputs are parsed into a combined filter and this is set for the feature + * type, and the alignment redrawn. + */ + protected void filtersChanged() + { + /* + * update the filter conditions for the feature type + */ + boolean anded = andFilters.isSelected(); + KeyedMatcherSetI combined = new KeyedMatcherSet(); + + for (KeyedMatcherI filter : filters) + { + String pattern = filter.getMatcher().getPattern(); + if (pattern.trim().length() > 0) + { + if (anded) + { + combined.and(filter); + } + else + { + combined.or(filter); + } + } + } + + /* + * save the filter conditions in the FeatureRenderer + * (note this might now be an empty filter with no conditions) + */ + fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined); + ap.paintAlignment(true, true); + + updateFiltersTab(); + } +} diff --git a/src/jalview/gui/IdCanvas.java b/src/jalview/gui/IdCanvas.java index a7dff86..085b259 100755 --- a/src/jalview/gui/IdCanvas.java +++ b/src/jalview/gui/IdCanvas.java @@ -564,5 +564,14 @@ public class IdCanvas extends JPanel implements ViewportListenerI { fastPaint((int) evt.getNewValue() - (int) evt.getOldValue()); } + else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(((int[]) evt.getNewValue())[1] + - ((int[]) evt.getOldValue())[1]); + } + else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT)) + { + repaint(); + } } } diff --git a/src/jalview/gui/JalviewDialog.java b/src/jalview/gui/JalviewDialog.java index 1008203..1d7bf3d 100644 --- a/src/jalview/gui/JalviewDialog.java +++ b/src/jalview/gui/JalviewDialog.java @@ -136,8 +136,8 @@ public abstract class JalviewDialog extends JPanel { try { - frame.dispose(); raiseClosed(); + frame.dispose(); } catch (Exception ex) { } diff --git a/src/jalview/gui/JvSwingUtils.java b/src/jalview/gui/JvSwingUtils.java index 79e0cef..4658668 100644 --- a/src/jalview/gui/JvSwingUtils.java +++ b/src/jalview/gui/JvSwingUtils.java @@ -356,12 +356,13 @@ public final class JvSwingUtils /** * Adds a titled border to the component in the default font and position (top - * left) + * left), optionally witht italic text * * @param comp * @param title + * @param italic */ - public static void createItalicTitledBorder(JComponent comp, + public static TitledBorder createTitledBorder(JComponent comp, String title, boolean italic) { Font font = comp.getFont(); @@ -374,6 +375,8 @@ public final class JvSwingUtils title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, font); comp.setBorder(titledBorder); + + return titledBorder; } } diff --git a/src/jalview/gui/ScalePanel.java b/src/jalview/gui/ScalePanel.java index e677769..798c833 100755 --- a/src/jalview/gui/ScalePanel.java +++ b/src/jalview/gui/ScalePanel.java @@ -549,7 +549,9 @@ public class ScalePanel extends JPanel // Here we only want to fastpaint on a scroll, with resize using a normal // paint, so scroll events are identified as changes to the horizontal or // vertical start value. - if (evt.getPropertyName().equals(ViewportRanges.STARTRES)) + if (evt.getPropertyName().equals(ViewportRanges.STARTRES) + || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ) + || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) { // scroll event, repaint panel repaint(); diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index 2a9c704..433d2ec 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -296,47 +296,48 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int transX = 0; int transY = 0; - gg.copyArea(horizontal * charWidth, vertical * charHeight, - img.getWidth(), img.getHeight(), -horizontal * charWidth, - -vertical * charHeight); + gg.copyArea(horizontal * charWidth, vertical * charHeight, + img.getWidth(), img.getHeight(), -horizontal * charWidth, + -vertical * charHeight); - if (horizontal > 0) // scrollbar pulled right, image to the left - { - transX = (endRes - startRes - horizontal) * charWidth; - startRes = endRes - horizontal; - } - else if (horizontal < 0) - { - endRes = startRes - horizontal; - } - else if (vertical > 0) // scroll down - { - startSeq = endSeq - vertical; - - if (startSeq < ranges.getStartSeq()) - { // ie scrolling too fast, more than a page at a time - startSeq = ranges.getStartSeq(); + if (horizontal > 0) // scrollbar pulled right, image to the left + { + transX = (endRes - startRes - horizontal) * charWidth; + startRes = endRes - horizontal; } - else + else if (horizontal < 0) { - transY = img.getHeight() - ((vertical + 1) * charHeight); + endRes = startRes - horizontal; } - } - else if (vertical < 0) - { - endSeq = startSeq - vertical; - if (endSeq > ranges.getEndSeq()) + if (vertical > 0) // scroll down + { + startSeq = endSeq - vertical; + + if (startSeq < ranges.getStartSeq()) + { // ie scrolling too fast, more than a page at a time + startSeq = ranges.getStartSeq(); + } + else + { + transY = img.getHeight() - ((vertical + 1) * charHeight); + } + } + else if (vertical < 0) { - endSeq = ranges.getEndSeq(); + endSeq = startSeq - vertical; + + if (endSeq > ranges.getEndSeq()) + { + endSeq = ranges.getEndSeq(); + } } - } - gg.translate(transX, transY); - drawPanel(gg, startRes, endRes, startSeq, endSeq, 0); - gg.translate(-transX, -transY); + gg.translate(transX, transY); + drawPanel(gg, startRes, endRes, startSeq, endSeq, 0); + gg.translate(-transX, -transY); - repaint(); + repaint(); } finally { fastpainting = false; @@ -410,6 +411,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // lcimg is a local *copy* of img which we'll draw selectImage on top of BufferedImage lcimg = buildLocalImage(selectImage); g.drawImage(lcimg, 0, 0, this); + + } + + if (av.cursorMode) + { + drawCursor(g, ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq()); } } @@ -508,6 +516,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); g2d.drawImage(selectImage, 0, 0, this); } + g2d.dispose(); return lcimg; @@ -771,8 +780,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI * white fill the region to be drawn (so incremental fast paint doesn't * scribble over an existing image) */ - gg.setColor(Color.white); - gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth, + g.setColor(Color.white); + g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth, wrappedRepeatHeightPx); drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1, @@ -912,9 +921,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int canvasWidth, int canvasHeight, int startRes) { - int charHeight = av.getCharHeight(); - int charWidth = av.getCharWidth(); - + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + // height gap above each panel int hgap = charHeight; if (av.getScaleAboveWrapped()) @@ -1141,13 +1150,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } } } - - if (av.cursorMode && cursorY == i && cursorX >= startRes - && cursorX <= endRes) - { - seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth, - offset + ((i - startSeq) * charHeight)); - } } if (av.getSelectionGroup() != null @@ -1245,6 +1247,94 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return selectionImage; } + /** + * Draw the cursor as a separate image and overlay + * + * @param startRes + * start residue of area to draw cursor in + * @param endRes + * end residue of area to draw cursor in + * @param startSeq + * start sequence of area to draw cursor in + * @param endSeq + * end sequence of are to draw cursor in + * @return a transparent image of the same size as the sequence canvas, with + * the cursor drawn on it, if any + */ + private void drawCursor(Graphics g, int startRes, int endRes, + int startSeq, + int endSeq) + { + // convert the cursorY into a position on the visible alignment + int cursor_ypos = cursorY; + + // don't do work unless we have to + if (cursor_ypos >= startSeq && cursor_ypos <= endSeq) + { + int yoffset = 0; + int xoffset = 0; + int startx = startRes; + int endx = endRes; + + // convert the cursorX into a position on the visible alignment + int cursor_xpos = av.getAlignment().getHiddenColumns() + .findColumnPosition(cursorX); + + if (av.getAlignment().getHiddenColumns().isVisible(cursorX)) + { + + if (av.getWrapAlignment()) + { + // work out the correct offsets for the cursor + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + int canvasWidth = getWidth(); + int canvasHeight = getHeight(); + + // height gap above each panel + int hgap = charHeight; + if (av.getScaleAboveWrapped()) + { + hgap += charHeight; + } + + int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) + / charWidth; + int cHeight = av.getAlignment().getHeight() * charHeight; + + endx = startx + cWidth - 1; + int ypos = hgap; // vertical offset + + // iterate down the wrapped panels + while ((ypos <= canvasHeight) && (endx < cursor_xpos)) + { + // update vertical offset + ypos += cHeight + getAnnotationHeight() + hgap; + + // update horizontal offset + startx += cWidth; + endx = startx + cWidth - 1; + } + yoffset = ypos; + xoffset = labelWidthWest; + } + + // now check if cursor is within range for x values + if (cursor_xpos >= startx && cursor_xpos <= endx) + { + // get the character the cursor is drawn at + SequenceI seq = av.getAlignment().getSequenceAt(cursorY); + char s = seq.getCharAt(cursorX); + + seqRdr.drawCursor(g, s, + xoffset + (cursor_xpos - startx) * av.getCharWidth(), + yoffset + (cursor_ypos - startSeq) * av.getCharHeight()); + } + } + } + } + + /* * Set up graphics for selection group */ @@ -1276,8 +1366,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group, int startRes, int endRes, int startSeq, int endSeq, int offset) { - int charWidth = av.getCharWidth(); - + int charWidth = av.getCharWidth(); + if (!av.hasHiddenColumns()) { drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq, @@ -1339,9 +1429,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int startRes, int endRes, int startSeq, int endSeq, int verticalOffset) { - int charHeight = av.getCharHeight(); - int charWidth = av.getCharWidth(); - + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); int visWidth = (endRes - startRes + 1) * charWidth; int oldY = -1; @@ -1349,140 +1438,141 @@ public class SeqCanvas extends JComponent implements ViewportListenerI boolean inGroup = false; int top = -1; int bottom = -1; - - int sx = -1; int sy = -1; - int xwidth = -1; - for (i = startSeq; i <= endSeq; i++) - { - // position of start residue of group relative to startRes, in pixels - sx = (group.getStartRes() - startRes) * charWidth; + List seqs = group.getSequences(null); - // width of group in pixels - xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - - 1; + // position of start residue of group relative to startRes, in pixels + int sx = (group.getStartRes() - startRes) * charWidth; - sy = verticalOffset + (i - startSeq) * charHeight; + // width of group in pixels + int xwidth = (((group.getEndRes() + 1) - group.getStartRes()) + * charWidth) - 1; - if (sx + xwidth < 0 || sx > visWidth) - { - continue; - } - - if ((sx <= (endRes - startRes) * charWidth) - && group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i))) + if (!(sx + xwidth < 0 || sx > visWidth)) + { + for (i = startSeq; i <= endSeq; i++) { - if ((bottom == -1) && !group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i + 1))) - { - bottom = sy + charHeight; - } - - if (!inGroup) - { - if (((top == -1) && (i == 0)) || !group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i - 1))) - { - top = sy; - } + sy = verticalOffset + (i - startSeq) * charHeight; - oldY = sy; - inGroup = true; - } - } - else - { - if (inGroup) + if ((sx <= (endRes - startRes) * charWidth) + && seqs.contains(av.getAlignment().getSequenceAt(i))) { - // if start position is visible, draw vertical line to left of - // group - if (sx >= 0 && sx < visWidth) + if ((bottom == -1) + && !seqs.contains(av.getAlignment().getSequenceAt(i + 1))) { - g.drawLine(sx, oldY, sx, sy); + bottom = sy + charHeight; } - // if end position is visible, draw vertical line to right of - // group - if (sx + xwidth < visWidth) - { - g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); - } - - if (sx < 0) - { - xwidth += sx; - sx = 0; - } - - // don't let width extend beyond current block, or group extent - // fixes JAL-2672 - if (sx + xwidth >= (endRes - startRes + 1) * charWidth) - { - xwidth = (endRes - startRes + 1) * charWidth - sx; - } - - // draw horizontal line at top of group - if (top != -1) + if (!inGroup) { - g.drawLine(sx, top, sx + xwidth, top); - top = -1; - } + if (((top == -1) && (i == 0)) || !seqs + .contains(av.getAlignment().getSequenceAt(i - 1))) + { + top = sy; + } - // draw horizontal line at bottom of group - if (bottom != -1) - { - g.drawLine(sx, bottom, sx + xwidth, bottom); - bottom = -1; + oldY = sy; + inGroup = true; } + } + else if (inGroup) + { + drawVerticals(g, sx, xwidth, visWidth, oldY, sy); + drawHorizontals(g, sx, xwidth, visWidth, top, bottom); + // reset top and bottom + top = -1; + bottom = -1; inGroup = false; } } - } - - if (inGroup) - { - sy = verticalOffset + ((i - startSeq) * charHeight); - if (sx >= 0 && sx < visWidth) + if (inGroup) { - g.drawLine(sx, oldY, sx, sy); + sy = verticalOffset + ((i - startSeq) * charHeight); + drawVerticals(g, sx, xwidth, visWidth, oldY, sy); + drawHorizontals(g, sx, xwidth, visWidth, top, bottom); } + } + } - if (sx + xwidth < visWidth) - { - g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); - } + /** + * Draw horizontal selection group boundaries at top and bottom positions + * + * @param g + * graphics object to draw on + * @param sx + * start x position + * @param xwidth + * width of gap + * @param visWidth + * visWidth maximum available width + * @param top + * position to draw top of group at + * @param bottom + * position to draw bottom of group at + */ + private void drawHorizontals(Graphics2D g, int sx, int xwidth, + int visWidth, int top, int bottom) + { + int width = xwidth; + int startx = sx; + if (startx < 0) + { + width += startx; + startx = 0; + } - if (sx < 0) - { - xwidth += sx; - sx = 0; - } + // don't let width extend beyond current block, or group extent + // fixes JAL-2672 + if (startx + width >= visWidth) + { + width = visWidth - startx; + } - if (sx + xwidth > visWidth) - { - xwidth = visWidth; - } - else if (sx + xwidth >= (endRes - startRes + 1) * charWidth) - { - xwidth = (endRes - startRes + 1) * charWidth; - } + if (top != -1) + { + g.drawLine(startx, top, startx + width, top); + } - if (top != -1) - { - g.drawLine(sx, top, sx + xwidth, top); - top = -1; - } + if (bottom != -1) + { + g.drawLine(startx, bottom - 1, startx + width, bottom - 1); + } + } - if (bottom != -1) - { - g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1); - bottom = -1; - } + /** + * Draw vertical lines at sx and sx+xwidth providing they lie within + * [0,visWidth) + * + * @param g + * graphics object to draw on + * @param sx + * start x position + * @param xwidth + * width of gap + * @param visWidth + * visWidth maximum available width + * @param oldY + * top y value + * @param sy + * bottom y value + */ + private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth, + int oldY, int sy) + { + // if start position is visible, draw vertical line to left of + // group + if (sx >= 0 && sx < visWidth) + { + g.drawLine(sx, oldY, sx, sy); + } - inGroup = false; + // if end position is visible, draw vertical line to right of + // group + if (sx + xwidth < visWidth) + { + g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); } } @@ -1569,7 +1659,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI */ protected boolean drawMappedPositions(SearchResultsI results) { - if (results == null) + if ((results == null) || (gg == null)) // JAL-2784 check gg is not null { return false; } @@ -1660,15 +1750,31 @@ public class SeqCanvas extends JComponent implements ViewportListenerI repaint(); return; } + else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT)) + { + fastPaint = false; + repaint(); + return; + } int scrollX = 0; - if (eventName.equals(ViewportRanges.STARTRES)) + if (eventName.equals(ViewportRanges.STARTRES) + || eventName.equals(ViewportRanges.STARTRESANDSEQ)) { // Make sure we're not trying to draw a panel // larger than the visible window + if (eventName.equals(ViewportRanges.STARTRES)) + { + scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); + } + else + { + scrollX = ((int[]) evt.getNewValue())[0] + - ((int[]) evt.getOldValue())[0]; + } ViewportRanges vpRanges = av.getRanges(); - scrollX = (int) evt.getNewValue() - (int) evt.getOldValue(); - int range = vpRanges.getViewportWidth(); + + int range = vpRanges.getEndRes() - vpRanges.getStartRes(); if (scrollX > range) { scrollX = range; @@ -1677,30 +1783,43 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { scrollX = -range; } - } - - // Both scrolling and resizing change viewport ranges: scrolling changes - // both start and end points, but resize only changes end values. - // Here we only want to fastpaint on a scroll, with resize using a normal - // paint, so scroll events are identified as changes to the horizontal or - // vertical start value. - // scroll - startres and endres both change - if (eventName.equals(ViewportRanges.STARTRES)) - { - if (av.getWrapAlignment()) + // Both scrolling and resizing change viewport ranges: scrolling changes + // both start and end points, but resize only changes end values. + // Here we only want to fastpaint on a scroll, with resize using a normal + // paint, so scroll events are identified as changes to the horizontal or + // vertical start value. + if (eventName.equals(ViewportRanges.STARTRES)) { - fastPaintWrapped(scrollX); + if (av.getWrapAlignment()) + { + fastPaintWrapped(scrollX); + } + else + { + fastPaint(scrollX, 0); + } } - else + else if (eventName.equals(ViewportRanges.STARTSEQ)) { - fastPaint(scrollX, 0); + // scroll + fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); + } + else if (eventName.equals(ViewportRanges.STARTRESANDSEQ)) + { + if (av.getWrapAlignment()) + { + fastPaintWrapped(scrollX); + } + else + { + fastPaint(scrollX, 0); + } + // bizarrely, we only need to scroll on the x value here as fastpaint + // copies the full height of the image anyway. Passing in the y value + // causes nasty repaint artefacts, which only disappear on a full + // repaint. } - } - else if (eventName.equals(ViewportRanges.STARTSEQ)) - { - // scroll - fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue()); } } @@ -1717,7 +1836,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { ViewportRanges ranges = av.getRanges(); - if (Math.abs(scrollX) > ranges.getViewportWidth()) + // if (Math.abs(scrollX) > ranges.getViewportWidth()) + // JAL-2836, 2836 temporarily removed wrapped fastpaint for release 2.10.3 + if (true) { /* * shift of more than one view width is @@ -1967,7 +2088,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI */ protected boolean drawMappedPositionsWrapped(SearchResultsI results) { - if (results == null) + if ((results == null) || (gg == null)) // JAL-2784 check gg is not null { return false; } diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 29f68c1..d5a13f3 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -316,13 +316,13 @@ public class SeqPanel extends JPanel void setCursorRow() { seqCanvas.cursorY = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorColumn() { seqCanvas.cursorX = getKeyboardNo1() - 1; - scrollToVisible(); + scrollToVisible(true); } void setCursorRowAndColumn() @@ -335,7 +335,7 @@ public class SeqPanel extends JPanel { seqCanvas.cursorX = getKeyboardNo1() - 1; seqCanvas.cursorY = getKeyboardNo2() - 1; - scrollToVisible(); + scrollToVisible(true); } } @@ -344,7 +344,7 @@ public class SeqPanel extends JPanel SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1()) - 1; - scrollToVisible(); + scrollToVisible(true); } void moveCursor(int dx, int dy) @@ -372,10 +372,16 @@ public class SeqPanel extends JPanel } } - scrollToVisible(); + scrollToVisible(false); } - void scrollToVisible() + /** + * Scroll to make the cursor visible in the viewport. + * + * @param jump + * just jump to the location rather than scrolling + */ + void scrollToVisible(boolean jump) { if (seqCanvas.cursorX < 0) { @@ -396,20 +402,44 @@ public class SeqPanel extends JPanel } endEditing(); - if (av.getWrapAlignment()) + + boolean repaintNeeded = true; + if (jump) { - av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX); + // only need to repaint if the viewport did not move, as otherwise it will + // get a repaint + repaintNeeded = !av.getRanges().setViewportLocation(seqCanvas.cursorX, + seqCanvas.cursorY); } else { - av.getRanges().scrollToVisible(seqCanvas.cursorX, seqCanvas.cursorY); + if (av.getWrapAlignment()) + { + // scrollToWrappedVisible expects x-value to have hidden cols subtracted + int x = av.getAlignment().getHiddenColumns() + .findColumnPosition(seqCanvas.cursorX); + av.getRanges().scrollToWrappedVisible(x); + } + else + { + av.getRanges().scrollToVisible(seqCanvas.cursorX, + seqCanvas.cursorY); + } } - setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY), + + if (av.getAlignment().getHiddenColumns().isVisible(seqCanvas.cursorX)) + { + setStatusMessage(av.getAlignment().getSequenceAt(seqCanvas.cursorY), seqCanvas.cursorX, seqCanvas.cursorY); + } - seqCanvas.repaint(); + if (repaintNeeded) + { + seqCanvas.repaint(); + } } + void setSelectionAreaAtCursor(boolean topLeft) { SequenceI sequence = av.getAlignment().getSequenceAt(seqCanvas.cursorY); diff --git a/src/jalview/gui/SequenceRenderer.java b/src/jalview/gui/SequenceRenderer.java index 0a1e8ef..81b394b 100755 --- a/src/jalview/gui/SequenceRenderer.java +++ b/src/jalview/gui/SequenceRenderer.java @@ -481,21 +481,30 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer } } - public void drawCursor(SequenceI seq, int res, int x1, int y1) + /** + * Draw a sequence canvas cursor + * + * @param g + * graphics context to draw on + * @param s + * character to draw at cursor + * @param x1 + * x position of cursor in graphics context + * @param y1 + * y position of cursor in graphics context + */ + public void drawCursor(Graphics g, char s, int x1, int y1) { int pady = av.getCharHeight() / 5; int charOffset = 0; - graphics.setColor(Color.black); - graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight()); + g.setColor(Color.black); + g.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight()); if (av.isValidCharWidth()) { - graphics.setColor(Color.white); - - char s = seq.getCharAt(res); - + g.setColor(Color.white); charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2; - graphics.drawString(String.valueOf(s), charOffset + x1, + g.drawString(String.valueOf(s), charOffset + x1, (y1 + av.getCharHeight()) - pady); } diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 37632ef..7c386f1 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -553,7 +553,7 @@ public class StructureChooser extends GStructureChooser if (cachedPDBExists) { - FilterOption cachedOption = new FilterOption("Cached PDB Entries", + FilterOption cachedOption = new FilterOption("Cached Structures", "-", VIEWS_LOCAL_PDB, false); cmb_filterOption.addItem(cachedOption); cmb_filterOption.setSelectedItem(cachedOption); diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index 71a89b0..aa0b640 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -49,7 +49,7 @@ import java.util.StringTokenizer; */ public class FeatureColour implements FeatureColourI { - static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY; + static final Color DEFAULT_NO_COLOUR = null; private static final String BAR = "|"; @@ -93,8 +93,6 @@ public class FeatureColour implements FeatureColourI private boolean aboveThreshold; - private boolean thresholdIsMinOrMax; - private boolean isHighToLow; private boolean autoScaled; @@ -392,6 +390,15 @@ public class FeatureColour implements FeatureColourI updateBounds(min, max); } + /** + * Constructor for a graduated colour + * + * @param low + * @param high + * @param noValueColour + * @param min + * @param max + */ public FeatureColour(Color low, Color high, Color noValueColour, float min, float max) { @@ -524,18 +531,6 @@ public class FeatureColour implements FeatureColourI } @Override - public boolean isThresholdMinMax() - { - return thresholdIsMinOrMax; - } - - @Override - public void setThresholdMinMax(boolean b) - { - thresholdIsMinOrMax = b; - } - - @Override public float getThreshold() { return threshold; @@ -607,8 +602,7 @@ public class FeatureColour implements FeatureColourI /* * graduated colour case, optionally with threshold * may be based on feature score on an attribute value - * Float.NaN is assigned minimum visible score colour - * no such attribute is assigned the 'no value' colour + * Float.NaN, or no value, is assigned the 'no value' colour */ float scr = feature.getScore(); if (attributeName != null) diff --git a/src/jalview/util/IntRangeComparator.java b/src/jalview/util/IntRangeComparator.java index cb32a0e..a0a29f2 100644 --- a/src/jalview/util/IntRangeComparator.java +++ b/src/jalview/util/IntRangeComparator.java @@ -1,3 +1,23 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.util; import java.util.Comparator; diff --git a/src/jalview/viewmodel/OverviewDimensionsHideHidden.java b/src/jalview/viewmodel/OverviewDimensionsHideHidden.java index c525bc6..c158ce7 100644 --- a/src/jalview/viewmodel/OverviewDimensionsHideHidden.java +++ b/src/jalview/viewmodel/OverviewDimensionsHideHidden.java @@ -132,9 +132,7 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions } } - // update viewport - ranges.setStartRes(xAsRes); - ranges.setStartSeq(yAsSeq); + ranges.setStartResAndSeq(xAsRes, yAsSeq); } @Override diff --git a/src/jalview/viewmodel/OverviewDimensionsShowHidden.java b/src/jalview/viewmodel/OverviewDimensionsShowHidden.java index 0bda56e..9dde16e 100644 --- a/src/jalview/viewmodel/OverviewDimensionsShowHidden.java +++ b/src/jalview/viewmodel/OverviewDimensionsShowHidden.java @@ -176,8 +176,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions } // update viewport - ranges.setStartRes(visXAsRes); - ranges.setStartSeq(visYAsSeq); + ranges.setStartResAndSeq(visXAsRes, visYAsSeq); } /** diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java index 24ff57f..c7a3fa1 100644 --- a/src/jalview/viewmodel/ViewportRanges.java +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -24,11 +24,10 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.HiddenColumns; /** - * Slightly less embryonic class which: Supplies and updates viewport properties - * relating to position such as: start and end residues and sequences; ideally - * will serve hidden columns/rows too. Intention also to support calculations - * for positioning, scrolling etc. such as finding the middle of the viewport, - * checking for scrolls off screen + * Supplies and updates viewport properties relating to position such as: start + * and end residues and sequences; ideally will serve hidden columns/rows too. + * Intention also to support calculations for positioning, scrolling etc. such + * as finding the middle of the viewport, checking for scrolls off screen */ public class ViewportRanges extends ViewportProperties { @@ -40,6 +39,10 @@ public class ViewportRanges extends ViewportProperties public static final String ENDSEQ = "endseq"; + public static final String STARTRESANDSEQ = "startresandseq"; + + public static final String MOVE_VIEWPORT = "move_viewport"; + private boolean wrappedMode = false; // start residue of viewport @@ -130,6 +133,31 @@ public class ViewportRanges extends ViewportProperties */ public void setStartEndRes(int start, int end) { + int[] oldvalues = updateStartEndRes(start, end); + int oldstartres = oldvalues[0]; + int oldendres = oldvalues[1]; + + changeSupport.firePropertyChange(STARTRES, oldstartres, startRes); + if (oldstartres == startRes) + { + // event won't be fired if start positions are same + // fire an event for the end positions in case they changed + changeSupport.firePropertyChange(ENDRES, oldendres, endRes); + } + } + + /** + * Update start and end residue values, adjusting for width constraints if + * necessary + * + * @param start + * start residue + * @param end + * end residue + * @return array containing old start and end residue values + */ + private int[] updateStartEndRes(int start, int end) + { int oldstartres = this.startRes; /* @@ -162,14 +190,7 @@ public class ViewportRanges extends ViewportProperties { endRes = end; } - - changeSupport.firePropertyChange(STARTRES, oldstartres, startRes); - if (oldstartres == startRes) - { - // event won't be fired if start positions are same - // fire an event for the end positions in case they changed - changeSupport.firePropertyChange(ENDRES, oldendres, endRes); - } + return new int[] { oldstartres, oldendres }; } /** @@ -203,6 +224,31 @@ public class ViewportRanges extends ViewportProperties */ public void setStartEndSeq(int start, int end) { + int[] oldvalues = updateStartEndSeq(start, end); + int oldstartseq = oldvalues[0]; + int oldendseq = oldvalues[1]; + + changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq); + if (oldstartseq == startSeq) + { + // event won't be fired if start positions are the same + // fire in case the end positions changed + changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq); + } + } + + /** + * Update start and end sequence values, adjusting for height constraints if + * necessary + * + * @param start + * start sequence + * @param end + * end sequence + * @return array containing old start and end sequence values + */ + private int[] updateStartEndSeq(int start, int end) + { int oldstartseq = this.startSeq; int visibleHeight = getVisibleAlignmentHeight(); if (start > visibleHeight - 1) @@ -231,14 +277,7 @@ public class ViewportRanges extends ViewportProperties { endSeq = end; } - - changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq); - if (oldstartseq == startSeq) - { - // event won't be fired if start positions are the same - // fire in case the end positions changed - changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq); - } + return new int[] { oldstartseq, oldendseq }; } /** @@ -255,6 +294,34 @@ public class ViewportRanges extends ViewportProperties } /** + * Set start residue and start sequence together (fires single event). The + * event supplies a pair of old values and a pair of new values: [old start + * residue, old start sequence] and [new start residue, new start sequence] + * + * @param res + * the start residue + * @param seq + * the start sequence + */ + public void setStartResAndSeq(int res, int seq) + { + int width = getViewportWidth(); + int[] oldresvalues = updateStartEndRes(res, res + width - 1); + + int startseq = seq; + int height = getViewportHeight(); + if (startseq + height - 1 > getVisibleAlignmentHeight() - 1) + { + startseq = getVisibleAlignmentHeight() - height; + } + int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1); + + int[] old = new int[] { oldresvalues[0], oldseqvalues[0] }; + int[] newresseq = new int[] { startRes, startSeq }; + changeSupport.firePropertyChange(STARTRESANDSEQ, old, newresseq); + } + + /** * Get start residue of viewport */ public int getStartRes() @@ -477,18 +544,33 @@ public class ViewportRanges extends ViewportProperties * the startRes changed, else false. * * @param res - * residue position to scroll to + * residue position to scroll to NB visible position not absolute + * alignment position * @return */ public boolean scrollToWrappedVisible(int res) { - int oldStartRes = startRes; - int width = getViewportWidth(); - - if (res >= oldStartRes && res < oldStartRes + width) + int newStartRes = calcWrappedStartResidue(res); + if (newStartRes == startRes) { return false; } + setStartRes(newStartRes); + + return true; + } + + /** + * Calculate wrapped start residue from visible start residue + * + * @param res + * visible start residue + * @return left column of panel res will be located in + */ + private int calcWrappedStartResidue(int res) + { + int oldStartRes = startRes; + int width = getViewportWidth(); boolean up = res < oldStartRes; int widthsToScroll = Math.abs((res - oldStartRes) / width); @@ -504,19 +586,16 @@ public class ViewportRanges extends ViewportProperties { newStartRes = 0; } - - setStartRes(newStartRes); - - return true; + return newStartRes; } /** * Scroll so that (x,y) is visible. Fires a property change event. * * @param x - * x position in alignment + * x position in alignment (absolute position) * @param y - * y position in alignment + * y position in alignment (absolute position) */ public void scrollToVisible(int x, int y) { @@ -528,7 +607,7 @@ public class ViewportRanges extends ViewportProperties { scrollUp(false); } - + HiddenColumns hidden = al.getHiddenColumns(); while (x < hidden.adjustForHiddenColumns(startRes)) { @@ -547,6 +626,62 @@ public class ViewportRanges extends ViewportProperties } /** + * Set the viewport location so that a position is visible + * + * @param x + * column to be visible: absolute position in alignment + * @param y + * row to be visible: absolute position in alignment + */ + public boolean setViewportLocation(int x, int y) + { + boolean changedLocation = false; + + // convert the x,y location to visible coordinates + int visX = al.getHiddenColumns().findColumnPosition(x); + int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y); + + // if (vis_x,vis_y) is already visible don't do anything + if (startRes > visX || visX > endRes + || startSeq > visY && visY > endSeq) + { + int[] old = new int[] { startRes, startSeq }; + int[] newresseq; + if (wrappedMode) + { + int newstartres = calcWrappedStartResidue(visX); + setStartRes(newstartres); + newresseq = new int[] { startRes, startSeq }; + } + else + { + // set the viewport x location to contain vis_x + int newstartres = visX; + int width = getViewportWidth(); + if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1) + { + newstartres = getVisibleAlignmentWidth() - width; + } + updateStartEndRes(newstartres, newstartres + width - 1); + + // set the viewport y location to contain vis_y + int newstartseq = visY; + int height = getViewportHeight(); + if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1) + { + newstartseq = getVisibleAlignmentHeight() - height; + } + updateStartEndSeq(newstartseq, newstartseq + height - 1); + + newresseq = new int[] { startRes, startSeq }; + } + changedLocation = true; + changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq); + } + return changedLocation; + } + + /** * Adjust sequence position for page up. Fires a property change event. */ public void pageUp() diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 28fceec..4797675 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -49,6 +49,17 @@ import java.util.concurrent.ConcurrentHashMap; public abstract class FeatureRendererModel implements jalview.api.FeatureRenderer { + /* + * column indices of fields in Feature Settings table + * todo: transfer valuers as data beans instead of Object[][] + */ + public static final int TYPE_COLUMN = 0; + + public static final int COLOUR_COLUMN = 1; + + public static final int FILTER_COLUMN = 2; + + public static final int SHOW_COLUMN = 3; /* * global transparency for feature @@ -297,9 +308,13 @@ public abstract class FeatureRendererModel List features = sequence.findFeatures(column, column, visibleTypes); + /* + * include features unless their feature group is not displayed, or + * they are hidden (have no colour) based on a filter or colour threshold + */ for (SequenceFeature sf : features) { - if (!featureGroupNotShown(sf)) + if (!featureGroupNotShown(sf) && getColour(sf) != null) { result.add(sf); } @@ -716,9 +731,9 @@ public abstract class FeatureRendererModel { for (int i = 0; i < data.length; i++) { - String type = data[i][0].toString(); - setColour(type, (FeatureColourI) data[i][1]); - if (((Boolean) data[i][2]).booleanValue()) + String type = data[i][TYPE_COLUMN].toString(); + setColour(type, (FeatureColourI) data[i][COLOUR_COLUMN]); + if (((Boolean) data[i][SHOW_COLUMN]).booleanValue()) { av_featuresdisplayed.setVisible(type); } @@ -993,7 +1008,7 @@ public abstract class FeatureRendererModel for (SequenceFeature sf : features) { - if (!featureGroupNotShown(sf)) + if (!featureGroupNotShown(sf) && getColour(sf) != null) { result.add(sf); } @@ -1120,8 +1135,13 @@ public abstract class FeatureRendererModel protected boolean featureMatchesFilters(SequenceFeature sf) { KeyedMatcherSetI filter = featureFilters.get(sf.getType()); - return filter == null ? true : filter.matches(key -> sf - .getValueAsString(key)); + // TODO temporary fudge for Score and Label + return filter == null ? true + : filter.matches( + key -> "Label".equals(key[0]) ? sf.getDescription() + : ("Score".equals(key[0]) + ? String.valueOf(sf.getScore()) + : sf.getValueAsString(key))); } } diff --git a/src/jalview/ws/dbsources/Uniprot.java b/src/jalview/ws/dbsources/Uniprot.java index c9beb8e..73775cf 100644 --- a/src/jalview/ws/dbsources/Uniprot.java +++ b/src/jalview/ws/dbsources/Uniprot.java @@ -310,23 +310,18 @@ public class Uniprot extends DbSourceProxyImpl /** * * @param entry - * UniportEntry + * UniprotEntry * @return The accession id(s) and name(s) delimited by '|'. */ public static String getUniprotEntryId(UniprotEntry entry) { StringBuilder name = new StringBuilder(32); - // name.append("UniProt/Swiss-Prot"); - // use 'canonicalised' name for optimal id matching - name.append(DBRefSource.UNIPROT); - for (String accessionId : entry.getAccession()) - { - name.append(BAR_DELIMITER); - name.append(accessionId); - } for (String n : entry.getName()) { - name.append(BAR_DELIMITER); + if (name.length() > 0) + { + name.append(BAR_DELIMITER); + } name.append(n); } return name.toString(); diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index c0cb09c..a084a8e 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -1803,4 +1803,82 @@ public class SequenceTest sq.checkValidRange(); assertEquals(22, sq.getEnd()); } + + @Test(groups = { "Functional" }) + public void testDeleteChars_withGaps() + { + /* + * delete gaps only + */ + SequenceI sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString()); + sq.deleteChars(1, 2); // delete first gap + assertEquals("AB-C", sq.getSequenceAsString()); + assertEquals(8, sq.getStart()); + assertEquals(10, sq.getEnd()); + assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString()); + + /* + * delete gaps and residues at start (no new dataset sequence) + */ + sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + sq.deleteChars(0, 3); // delete A-B + assertEquals("-C", sq.getSequenceAsString()); + assertEquals(10, sq.getStart()); + assertEquals(10, sq.getEnd()); + assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString()); + + /* + * delete gaps and residues at end (no new dataset sequence) + */ + sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + sq.deleteChars(2, 5); // delete B-C + assertEquals("A-", sq.getSequenceAsString()); + assertEquals(8, sq.getStart()); + assertEquals(8, sq.getEnd()); + assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString()); + + /* + * delete gaps and residues internally (new dataset sequence) + * first delete from gap to residue + */ + sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + sq.deleteChars(1, 3); // delete -B + assertEquals("A-C", sq.getSequenceAsString()); + assertEquals(8, sq.getStart()); + assertEquals(9, sq.getEnd()); + assertEquals("AC", sq.getDatasetSequence().getSequenceAsString()); + assertEquals(8, sq.getDatasetSequence().getStart()); + assertEquals(9, sq.getDatasetSequence().getEnd()); + + /* + * internal delete from gap to gap + */ + sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + sq.deleteChars(1, 4); // delete -B- + assertEquals("AC", sq.getSequenceAsString()); + assertEquals(8, sq.getStart()); + assertEquals(9, sq.getEnd()); + assertEquals("AC", sq.getDatasetSequence().getSequenceAsString()); + assertEquals(8, sq.getDatasetSequence().getStart()); + assertEquals(9, sq.getDatasetSequence().getEnd()); + + /* + * internal delete from residue to residue + */ + sq = new Sequence("test/8-10", "A-B-C"); + sq.createDatasetSequence(); + sq.deleteChars(2, 3); // delete B + assertEquals("A--C", sq.getSequenceAsString()); + assertEquals(8, sq.getStart()); + assertEquals(9, sq.getEnd()); + assertEquals("AC", sq.getDatasetSequence().getSequenceAsString()); + assertEquals(8, sq.getDatasetSequence().getStart()); + assertEquals(9, sq.getDatasetSequence().getEnd()); + } } diff --git a/test/jalview/datamodel/features/FeatureAttributesTest.java b/test/jalview/datamodel/features/FeatureAttributesTest.java index e464326..4b7a435 100644 --- a/test/jalview/datamodel/features/FeatureAttributesTest.java +++ b/test/jalview/datamodel/features/FeatureAttributesTest.java @@ -1,18 +1,35 @@ package jalview.datamodel.features; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; -import java.util.Comparator; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.features.FeatureAttributes.Datatype; -import junit.extensions.PA; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import junit.extensions.PA; + public class FeatureAttributesTest { /** + * clear down attributes map after tests + */ + @AfterMethod + public void tearDown() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + ((Map) PA.getValue(fa, "attributes")).clear(); + } + + /** * Test the method that keeps attribute names in non-case-sensitive order, * including handling of 'compound' names */ @@ -38,4 +55,66 @@ public class FeatureAttributesTest assertTrue(comp.compare(new String[] { "CSQ", "ac" }, new String[] { "csq", "AF" }) < 0); } + + @Test + public void testGetMinMax() + { + SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20, + "group"); + FeatureAttributes fa = FeatureAttributes.getInstance(); + assertNull(fa.getMinMax("Pfam", "kd")); + sf.setValue("domain", "xyz"); + assertNull(fa.getMinMax("Pfam", "kd")); + sf.setValue("kd", "some text"); + assertNull(fa.getMinMax("Pfam", "kd")); + sf.setValue("kd", "1.3"); + assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { 1.3f, 1.3f }); + sf.setValue("kd", "-2.6"); + assertEquals(fa.getMinMax("Pfam", "kd"), new float[] { -2.6f, 1.3f }); + Map csq = new HashMap<>(); + csq.put("AF", "-3"); + sf.setValue("CSQ", csq); + assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"), + new float[] + { -3f, -3f }); + csq.put("AF", "4"); + sf.setValue("CSQ", csq); + assertEquals(fa.getMinMax("Pfam", "CSQ", "AF"), + new float[] + { -3f, 4f }); + } + + /** + * Test the method that returns an attribute description, provided it is + * recorded and unique + */ + @Test + public void testGetDescription() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + // with no description returns null + assertNull(fa.getDescription("Pfam", "kd")); + // with a unique description, returns that value + fa.addDescription("Pfam", "desc1", "kd"); + assertEquals(fa.getDescription("Pfam", "kd"), "desc1"); + // with ambiguous description, returns null + fa.addDescription("Pfam", "desc2", "kd"); + assertNull(fa.getDescription("Pfam", "kd")); + } + + @Test + public void testDatatype() + { + FeatureAttributes fa = FeatureAttributes.getInstance(); + assertNull(fa.getDatatype("Pfam", "kd")); + SequenceFeature sf = new SequenceFeature("Pfam", "desc", 10, 20, + "group"); + sf.setValue("kd", "-1"); + sf.setValue("domain", "Metal"); + sf.setValue("phase", "1"); + sf.setValue("phase", "reverse"); + assertEquals(fa.getDatatype("Pfam", "kd"), Datatype.Number); + assertEquals(fa.getDatatype("Pfam", "domain"), Datatype.Character); + assertEquals(fa.getDatatype("Pfam", "phase"), Datatype.Mixed); + } } diff --git a/test/jalview/io/vcf/VCFLoaderTest.java b/test/jalview/io/vcf/VCFLoaderTest.java index 5607b4b..246337d 100644 --- a/test/jalview/io/vcf/VCFLoaderTest.java +++ b/test/jalview/io/vcf/VCFLoaderTest.java @@ -1,8 +1,8 @@ package jalview.io.vcf; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.Mapping; @@ -21,7 +21,9 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.List; +import java.util.Map; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class VCFLoaderTest @@ -64,6 +66,17 @@ public class VCFLoaderTest // insertion G/GA is transferred to nucleotide but not to peptide "17\t45051613\t.\tG\tGA,C\t1666.64\tRF\tAC=15;AF=3.0e-03,2.0e-03" }; + @BeforeClass + public void setUp() + { + /* + * configure to capture all available VCF and VEP (CSQ) fields + */ + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Cache.setProperty("VCF_FIELDS", ".*"); + Cache.setProperty("VEP_FIELDS", ".*"); + } + @Test(groups = "Functional") public void testDoLoad() throws IOException { @@ -432,49 +445,56 @@ public class VCFLoaderTest assertEquals(sf.getScore(), 0.1f, DELTA); assertEquals(sf.getValue("alleles"), "C,A"); // gene features include Consequence for all transcripts - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + Map map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(1); assertEquals(sf.getBegin(), 5); assertEquals(sf.getEnd(), 5); assertEquals(sf.getScore(), 0.2f, DELTA); assertEquals(sf.getValue("alleles"), "C,T"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(2); assertEquals(sf.getBegin(), 9); assertEquals(sf.getEnd(), 11); // deletion over 3 positions assertEquals(sf.getScore(), 0.3f, DELTA); assertEquals(sf.getValue("alleles"), "CGG,C"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(3); assertEquals(sf.getBegin(), 13); assertEquals(sf.getEnd(), 13); assertEquals(sf.getScore(), 0.5f, DELTA); assertEquals(sf.getValue("alleles"), "C,T"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(4); assertEquals(sf.getBegin(), 13); assertEquals(sf.getEnd(), 13); assertEquals(sf.getScore(), 0.4f, DELTA); assertEquals(sf.getValue("alleles"), "C,G"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(5); assertEquals(sf.getBegin(), 17); assertEquals(sf.getEnd(), 17); assertEquals(sf.getScore(), 0.7f, DELTA); assertEquals(sf.getValue("alleles"), "A,G"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); sf = geneFeatures.get(6); assertEquals(sf.getBegin(), 17); assertEquals(sf.getEnd(), 17); // insertion assertEquals(sf.getScore(), 0.6f, DELTA); assertEquals(sf.getValue("alleles"), "A,AC"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 2); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); /* * verify variant feature(s) added to transcript3 @@ -492,24 +512,25 @@ public class VCFLoaderTest assertEquals(sf.getScore(), 0.2f, DELTA); assertEquals(sf.getValue("alleles"), "C,T"); // transcript features only have Consequence for that transcripts - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript3")); + map = (Map) sf.getValue("CSQ"); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); sf = transcriptFeatures.get(1); assertEquals(sf.getBegin(), 11); assertEquals(sf.getEnd(), 11); assertEquals(sf.getScore(), 0.7f, DELTA); assertEquals(sf.getValue("alleles"), "A,G"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript3")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); sf = transcriptFeatures.get(2); assertEquals(sf.getBegin(), 11); assertEquals(sf.getEnd(), 11); assertEquals(sf.getScore(), 0.6f, DELTA); assertEquals(sf.getValue("alleles"), "A,AC"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript3")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript3"); /* * verify variants computed on protein product for transcript3 @@ -548,31 +569,31 @@ public class VCFLoaderTest assertEquals(sf.getEnd(), 7); assertEquals(sf.getScore(), 0.5f, DELTA); assertEquals(sf.getValue("alleles"), "C,T"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript4")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); sf = transcriptFeatures.get(1); assertEquals(sf.getBegin(), 7); assertEquals(sf.getEnd(), 7); assertEquals(sf.getScore(), 0.4f, DELTA); assertEquals(sf.getValue("alleles"), "C,G"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript4")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); sf = transcriptFeatures.get(2); assertEquals(sf.getBegin(), 11); assertEquals(sf.getEnd(), 11); assertEquals(sf.getScore(), 0.7f, DELTA); assertEquals(sf.getValue("alleles"), "A,G"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript4")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); sf = transcriptFeatures.get(3); assertEquals(sf.getBegin(), 11); assertEquals(sf.getEnd(), 11); assertEquals(sf.getScore(), 0.6f, DELTA); assertEquals(sf.getValue("alleles"), "A,AC"); - assertEquals(((String) sf.getValue("CSQ")).split(",").length, 1); - assertTrue(sf.getValue("CSQ").toString().contains("transcript4")); + assertEquals(map.size(), 9); + assertEquals(sf.getValueAsString("CSQ", "Feature"), "transcript4"); } } diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index 438feba..745eec3 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -2,6 +2,7 @@ package jalview.renderer.seqfeatures; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.api.AlignViewportI; @@ -12,10 +13,15 @@ import jalview.gui.AlignFrame; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; +import jalview.util.matcher.Condition; +import jalview.util.matcher.KeyedMatcher; +import jalview.util.matcher.KeyedMatcherSet; +import jalview.util.matcher.KeyedMatcherSetI; import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -252,6 +258,37 @@ public class FeatureRendererTest features = fr.findFeaturesAtColumn(seq, 5); assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); + + /* + * give "Type3" features a graduated colour scheme + * - first with no threshold + */ + FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f, + 10f); + fr.getFeatureColours().put("Type3", gc); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with threshold > 2f - feature score of 1f is excluded + gc.setAboveThreshold(true); + gc.setThreshold(2f); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + + /* + * make "Type3" graduated colour by attribute "AF" + * - first with no attribute held - feature should be excluded + */ + gc.setAttributeName("AF"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + // now with the attribute above threshold - should be included + sf4.setValue("AF", "2.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with the attribute below threshold - should be excluded + sf4.setValue("AF", "1.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); } @Test(groups = "Functional") @@ -319,4 +356,152 @@ public class FeatureRendererTest assertFalse(features.contains(sf2) && features.contains(sf3)); assertTrue(features.contains(sf5)); } + + @Test(groups = "Functional") + public void testGetColour() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n", + DataSourceType.PASTE); + AlignViewportI av = af.getViewport(); + FeatureRenderer fr = new FeatureRenderer(av); + + /* + * simple colour, feature type and group displayed + */ + FeatureColourI fc = new FeatureColour(Color.red); + fr.getFeatureColours().put("Cath", fc); + SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, + "group1"); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * hide feature type, then unhide + */ + Object[][] data = new Object[1][]; + data[0] = new Object[] { "Cath", fc, false }; + fr.setFeaturePriority(data); + assertNull(fr.getColour(sf1)); + data[0] = new Object[] { "Cath", fc, true }; + fr.setFeaturePriority(data); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * hide feature group, then unhide + */ + fr.setGroupVisibility("group1", false); + assertNull(fr.getColour(sf1)); + fr.setGroupVisibility("group1", true); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * graduated colour by score, no threshold, no score + * + */ + FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, + Color.green, 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + assertEquals(fr.getColour(sf1), Color.green); + + /* + * graduated colour by score, no threshold, with score value + */ + SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f, + "group1"); + // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0) + Color expected = new Color(255, 128, 0); + assertEquals(fr.getColour(sf2), expected); + + /* + * above threshold, score is above threshold - no change + */ + gc.setAboveThreshold(true); + gc.setThreshold(5f); + assertEquals(fr.getColour(sf2), expected); + + /* + * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11 + * or from yellow(255, 255, 0) to red(255, 0, 0) + */ + gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAutoScaled(false); // this does little other than save a checkbox setting! + assertEquals(fr.getColour(sf2), new Color(255, 213, 0)); + + /* + * feature score is below threshold - no colour + */ + gc.setAboveThreshold(true); + gc.setThreshold(7f); + assertNull(fr.getColour(sf2)); + + /* + * feature score is above threshold - no colour + */ + gc.setBelowThreshold(true); + gc.setThreshold(3f); + assertNull(fr.getColour(sf2)); + + /* + * colour by feature attribute value + * first with no value held + */ + gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAttributeName("AF"); + assertEquals(fr.getColour(sf2), Color.green); + + // with non-numeric attribute value + sf2.setValue("AF", "Five"); + assertEquals(fr.getColour(sf2), Color.green); + + // with numeric attribute value + sf2.setValue("AF", "6"); + assertEquals(fr.getColour(sf2), expected); + + // with numeric value outwith threshold + gc.setAboveThreshold(true); + gc.setThreshold(10f); + assertNull(fr.getColour(sf2)); + + // with filter on AF < 4 + gc.setAboveThreshold(false); + assertEquals(fr.getColour(sf2), expected); + KeyedMatcherSetI filter = new KeyedMatcherSet(); + filter.and(new KeyedMatcher(Condition.LT, 4f, "AF")); + fr.setFeatureFilter("Cath", filter); + assertNull(fr.getColour(sf2)); + + // with filter on 'Consequence contains missense' + filter = new KeyedMatcherSet(); + filter.and(new KeyedMatcher(Condition.Contains, "missense", + "Consequence")); + fr.setFeatureFilter("Cath", filter); + // if feature has no Consequence attribute, no colour + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + sf2.setValue("Consequence", "Synonymous"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + sf2.setValue("Consequence", "Missense variant"); + assertEquals(fr.getColour(sf2), expected); + + // with filter on CSQ.Feature contains "ENST01234" + filter = new KeyedMatcherSet(); + filter.and(new KeyedMatcher(Condition.Matches, "ENST01234", "CSQ", + "Feature")); + fr.setFeatureFilter("Cath", filter); + // if feature has no CSQ data, no colour + assertNull(fr.getColour(sf2)); + // if CSQ data does not include Feature, no colour + Map csqData = new HashMap<>(); + csqData.put("BIOTYPE", "Transcript"); + sf2.setValue("CSQ", csqData); + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + csqData.put("Feature", "ENST9876"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + csqData.put("Feature", "ENST01234"); + assertEquals(fr.getColour(sf2), expected); + } } diff --git a/test/jalview/schemes/FeatureColourTest.java b/test/jalview/schemes/FeatureColourTest.java index db4fe40..72c29d3 100644 --- a/test/jalview/schemes/FeatureColourTest.java +++ b/test/jalview/schemes/FeatureColourTest.java @@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; +import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; import jalview.datamodel.SequenceFeature; import jalview.gui.JvOptionPane; @@ -33,11 +34,11 @@ import jalview.util.Format; import java.awt.Color; -import junit.extensions.PA; - import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import junit.extensions.PA; + public class FeatureColourTest { @@ -121,7 +122,7 @@ public class FeatureColourTest assertTrue(fc1.isColourByLabel()); assertFalse(fc1.isGraduatedColour()); assertTrue(fc1.isColourByAttribute()); - assertEquals("AF", fc1.getAttributeName()); + assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName()); /* * colour by attribute (value) @@ -134,7 +135,7 @@ public class FeatureColourTest assertTrue(fc1.isGraduatedColour()); assertFalse(fc1.isColourByLabel()); assertTrue(fc1.isColourByAttribute()); - assertEquals("AF", fc1.getAttributeName()); + assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName()); assertTrue(fc1.isAboveThreshold()); assertEquals(12f, fc1.getThreshold()); assertEquals(Color.gray, fc1.getMinColour()); @@ -190,7 +191,7 @@ public class FeatureColourTest assertTrue(fc1.isGraduatedColour()); assertFalse(fc1.isColourByLabel()); assertTrue(fc1.isColourByAttribute()); - assertEquals("AF", fc1.getAttributeName()); + assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName()); assertEquals(13f, fc1.getMin()); assertEquals(36f, fc1.getMax()); assertFalse((boolean) PA.getValue(fc1, "isHighToLow")); @@ -226,7 +227,7 @@ public class FeatureColourTest assertFalse(fc1.isGraduatedColour()); assertTrue(fc1.isColourByLabel()); assertTrue(fc1.isColourByAttribute()); - assertEquals("AC", fc1.getAttributeName()); + assertArrayEquals(new String[] { "AC" }, fc1.getAttributeName()); assertEquals(13f, fc1.getMin()); assertEquals(36f, fc1.getMax()); } diff --git a/test/jalview/viewmodel/ViewportRangesTest.java b/test/jalview/viewmodel/ViewportRangesTest.java index c0cb4ba..41a313f 100644 --- a/test/jalview/viewmodel/ViewportRangesTest.java +++ b/test/jalview/viewmodel/ViewportRangesTest.java @@ -85,7 +85,6 @@ public class ViewportRangesTest { vr.setEndSeq(al.getHeight()); assertEquals(vr.getEndSeq(), al.getHeight() - 1); - // vr.setEndRes(al.getHeight() - 1); vr.setEndSeq(al.getHeight() - 1); assertEquals(vr.getEndSeq(), al.getHeight() - 1); } @@ -169,6 +168,24 @@ public class ViewportRangesTest { } @Test(groups = { "Functional" }) + public void testSetStartResAndSeq() + { + ViewportRanges vr = new ViewportRanges(al); + vr.setViewportHeight(10); + vr.setStartResAndSeq(3, 6); + assertEquals(vr.getStartRes(), 3); + assertEquals(vr.getStartSeq(), 6); + assertEquals(vr.getEndRes(), 3 + vr.getViewportWidth() - 1); + assertEquals(vr.getEndSeq(), 6 + vr.getViewportHeight() - 1); + + vr.setStartResAndSeq(10, 25); + assertEquals(vr.getStartRes(), 10); + assertEquals(vr.getStartSeq(), 19); + assertEquals(vr.getEndRes(), 10 + vr.getViewportWidth() - 1); + assertEquals(vr.getEndSeq(), 19 + vr.getViewportHeight() - 1); + } + + @Test(groups = { "Functional" }) public void testSetViewportHeight() { ViewportRanges vr = new ViewportRanges(al); @@ -385,14 +402,13 @@ public class ViewportRangesTest { assertEquals(vr.getEndRes(), 52); } - // leave until JAL-2388 is merged and we can do without viewport - /*@Test(groups = { "Functional" }) + @Test(groups = { "Functional" }) public void testScrollToVisible() { ViewportRanges vr = new ViewportRanges(al); vr.setViewportStartAndWidth(12,5); vr.setViewportStartAndHeight(10,6); - vr.scrollToVisible(13,14) + vr.scrollToVisible(13, 14); // no change assertEquals(vr.getStartRes(), 12); @@ -403,7 +419,15 @@ public class ViewportRangesTest { assertEquals(vr.getStartSeq(), 6); // test for hidden columns too - }*/ + al.getHiddenColumns().hideColumns(1, 3); + vr.scrollToVisible(13, 3); + assertEquals(vr.getStartRes(), 6); + assertEquals(vr.getStartSeq(), 3); + + vr.scrollToVisible(2, 9); + assertEquals(vr.getStartRes(), 0); + assertEquals(vr.getStartSeq(), 4); + } @Test(groups = { "Functional" }) public void testEventFiring() @@ -418,7 +442,7 @@ public class ViewportRangesTest { // one event fired when startRes is called with new value vr.setStartRes(4); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); l.reset(); // no event fired for same value @@ -427,7 +451,7 @@ public class ViewportRangesTest { l.reset(); vr.setStartSeq(4); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.setStartSeq(4); @@ -435,7 +459,7 @@ public class ViewportRangesTest { l.reset(); vr.setEndSeq(10); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.setEndSeq(10); @@ -443,7 +467,7 @@ public class ViewportRangesTest { l.reset(); vr.setStartEndRes(2, 15); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); l.reset(); vr.setStartEndRes(2, 15); @@ -452,16 +476,18 @@ public class ViewportRangesTest { // check new value fired by event is corrected startres vr.setStartEndRes(-1, 5); - assertTrue(l.verify(1, Arrays.asList("startres"), Arrays.asList(0))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES), + Arrays.asList(0))); l.reset(); // check new value fired by event is corrected endres vr.setStartEndRes(0, -1); - assertTrue(l.verify(1, Arrays.asList("endres"), Arrays.asList(0))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDRES), + Arrays.asList(0))); l.reset(); vr.setStartEndSeq(2, 15); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.setStartEndSeq(2, 15); @@ -474,12 +500,14 @@ public class ViewportRangesTest { // check new value fired by event is corrected startseq vr.setStartEndSeq(-1, 5); - assertTrue(l.verify(1, Arrays.asList("startseq"), Arrays.asList(0))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ), + Arrays.asList(0))); l.reset(); // check new value fired by event is corrected endseq vr.setStartEndSeq(0, -1); - assertTrue(l.verify(1, Arrays.asList("endseq"), Arrays.asList(0))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ), + Arrays.asList(0))); l.reset(); // reset for later tests @@ -488,51 +516,52 @@ public class ViewportRangesTest { // test viewport height and width setting triggers event vr.setViewportHeight(10); - assertTrue(l.verify(1, Arrays.asList("endseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ))); l.reset(); vr.setViewportWidth(18); - assertTrue(l.verify(1, Arrays.asList("endres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDRES))); l.reset(); // already has seq start set to 2, so triggers endseq vr.setViewportStartAndHeight(2, 16); - assertTrue(l.verify(1, Arrays.asList("endseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.ENDSEQ))); l.reset(); vr.setViewportStartAndWidth(1, 14); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); l.reset(); // test page up/down triggers event vr.pageUp(); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.pageDown(); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); // test scrolling triggers event vr.scrollUp(true); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.scrollUp(false); - assertTrue(l.verify(1, Arrays.asList("startseq"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTSEQ))); l.reset(); vr.scrollRight(true); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); l.reset(); vr.scrollRight(false); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); l.reset(); vr.scrollToVisible(10, 10); assertTrue(l.verify(4, - Arrays.asList("startseq", "startseq", "startseq", "startseq"))); + Arrays.asList(ViewportRanges.STARTSEQ, ViewportRanges.STARTSEQ, + ViewportRanges.STARTSEQ, ViewportRanges.STARTSEQ))); l.reset(); /* @@ -544,7 +573,15 @@ public class ViewportRangesTest { l.reset(); vr.scrollToWrappedVisible(25); - assertTrue(l.verify(1, Arrays.asList("startres"))); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRES))); + l.reset(); + + // test setStartResAndSeq triggers one event + vr.setStartResAndSeq(5, 7); + assertTrue(l.verify(1, Arrays.asList(ViewportRanges.STARTRESANDSEQ), + Arrays.asList(5, 7))); + + l.reset(); } @Test(groups = { "Functional" }) @@ -823,6 +860,76 @@ public class ViewportRangesTest { assertEquals(vr.getStartSeq(), 1); assertEquals(vr.getStartRes(), 43); } + + @Test(groups = { "Functional" }) + public void testSetViewportLocation() + { + AlignmentI al2 = gen.generate(60, 80, 1, 0, 0); + + ViewportRanges vr = new ViewportRanges(al2); + + // start with viewport on 5-14 + vr.setViewportStartAndWidth(5, 10); + assertEquals(vr.getStartRes(), 5); + assertEquals(vr.getEndRes(), 14); + + vr.setViewportStartAndHeight(3, 13); + assertEquals(vr.getStartSeq(), 3); + assertEquals(vr.getEndSeq(), 15); + + // set location to (8,5) - no change + vr.setViewportLocation(8, 5); + assertEquals(vr.getStartRes(), 5); + assertEquals(vr.getEndRes(), 14); + assertEquals(vr.getStartSeq(), 3); + assertEquals(vr.getEndSeq(), 15); + + // set location to (40,50) - change to top left (40,50) + vr.setViewportLocation(40, 50); + assertEquals(vr.getStartRes(), 40); + assertEquals(vr.getEndRes(), 49); + assertEquals(vr.getStartSeq(), 50); + assertEquals(vr.getEndSeq(), 62); + + // set location past end of alignment - resets to leftmost pos + vr.setViewportLocation(63, 85); + assertEquals(vr.getStartRes(), 50); + assertEquals(vr.getEndRes(), 59); + assertEquals(vr.getStartSeq(), 67); + assertEquals(vr.getEndSeq(), 79); + + // hide some columns + al2.getHiddenColumns().hideColumns(20, 50); + vr.setViewportLocation(55, 4); + assertEquals(vr.getStartRes(), 19); + assertEquals(vr.getEndRes(), 28); + assertEquals(vr.getStartSeq(), 4); + assertEquals(vr.getEndSeq(), 16); + + // hide some sequences + al2.getHiddenSequences().hideSequence(al2.getSequenceAt(3)); + al2.getHiddenSequences().hideSequence(al2.getSequenceAt(4)); + vr.setViewportLocation(17, 5); + assertEquals(vr.getStartRes(), 17); + assertEquals(vr.getEndRes(), 26); + assertEquals(vr.getStartSeq(), 3); + assertEquals(vr.getEndSeq(), 15); + + // set wrapped mode + vr.setWrappedMode(true); + vr.setViewportLocation(1, 8); + assertEquals(vr.getStartRes(), 0); + assertEquals(vr.getEndRes(), 9); + assertEquals(vr.getStartSeq(), 3); + assertEquals(vr.getEndSeq(), 15); + + // try further down the alignment + vr.setViewportLocation(57, 5); + assertEquals(vr.getStartRes(), 20); + assertEquals(vr.getEndRes(), 29); + assertEquals(vr.getStartSeq(), 3); + assertEquals(vr.getEndSeq(), 15); + } } // mock listener for property change events @@ -844,7 +951,15 @@ class MockPropChangeListener implements ViewportListenerI { firecount++; events.add(evt.getPropertyName()); - newvalues.add((Integer) evt.getNewValue()); + if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)) + { + newvalues.add(((int[]) evt.getNewValue())[0]); + newvalues.add(((int[]) evt.getNewValue())[1]); + } + else + { + newvalues.add((Integer) evt.getNewValue()); + } } public boolean verify(int count, List eventslist, diff --git a/test/jalview/ws/dbsources/UniprotTest.java b/test/jalview/ws/dbsources/UniprotTest.java index 2d4be71..f98ef85 100644 --- a/test/jalview/ws/dbsources/UniprotTest.java +++ b/test/jalview/ws/dbsources/UniprotTest.java @@ -163,11 +163,11 @@ public class UniprotTest new StringReader(UNIPROT_XML)).get(0); /* - * name formatted as source | accession ids | names - * source database converted to Jalview canonical name + * name formatted with Uniprot Entry name */ - String expectedName = "UNIPROT|A9CKP4|A9CKP5|A9CKP4_AGRT5|A9CKP4_AGRT6"; - assertEquals(expectedName, Uniprot.getUniprotEntryId(entry)); + String expectedName = "A9CKP4_AGRT5|A9CKP4_AGRT6"; + assertEquals(expectedName, + Uniprot.getUniprotEntryId(entry)); } /** diff --git a/utils/InstallAnywhere/Jalview.iap_xml b/utils/InstallAnywhere/Jalview.iap_xml index abe9d4b..a649cb4 100755 --- a/utils/InstallAnywhere/Jalview.iap_xml +++ b/utils/InstallAnywhere/Jalview.iap_xml @@ -2025,7 +2025,7 @@ and any path to a file to save to the file]]> - + false @@ -2043,7 +2043,7 @@ and any path to a file to save to the file]]>
      true - + 6149494 @@ -5270,7 +5270,7 @@ Press "Done" to quit the installer.]]>
      - + @@ -5366,7 +5366,7 @@ Press "Done" to quit the installer.]]>
      - + diff --git a/utils/MessageBundleChecker.java b/utils/MessageBundleChecker.java index 4489a93..c870f6d 100644 --- a/utils/MessageBundleChecker.java +++ b/utils/MessageBundleChecker.java @@ -24,6 +24,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.Properties; +import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; @@ -89,7 +90,9 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner private int javaCount; - private HashSet invalidKeys; + private Set invalidKeys; + + private Set dynamicKeys; /** * Runs the scan given the path to the root of Java source directories @@ -125,7 +128,7 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner private void doMain(String srcPath) throws IOException { System.out.println("Scanning " + srcPath - + " for calls to MessageManager"); + + " for calls to MessageManager\n"); sourcePath = srcPath; loadMessages(); File dir = new File(srcPath); @@ -134,7 +137,10 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner System.out.println(srcPath + " not found"); return; } - invalidKeys = new HashSet(); + + invalidKeys = new HashSet<>(); + dynamicKeys = new HashSet<>(); + if (dir.isDirectory()) { scanDirectory(dir); @@ -152,17 +158,60 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner private void reportResults() { System.out.println("\nScanned " + javaCount + " source files"); - System.out.println("Message.properties has " + messages.size() + System.out.println( + "Messages.properties has " + messages.size() + " keys"); - System.out.println("Found " + invalidKeys.size() - + " possibly invalid parameter calls"); + if (!invalidKeys.isEmpty()) + { + System.out.println("Found " + invalidKeys.size() + + " possibly invalid parameter call" + + (invalidKeys.size() > 1 ? "s" : "")); + } - System.out.println(messageKeys.size() - + " keys not found, either unused or constructed dynamically"); + System.out.println("Keys not found, assumed constructed dynamically:"); + int dynamicCount = 0; for (String key : messageKeys) { - System.out.println(" " + key); + if (isDynamic(key)) + { + System.out.println(" " + key); + dynamicCount++; + } + } + + if (dynamicCount < messageKeys.size()) + { + System.out.println((messageKeys.size() - dynamicCount) + + " keys not found, possibly unused"); + for (String key : messageKeys) + { + if (!isDynamic(key)) + { + System.out.println(" " + key); + } + } + } + System.out + .println("(Run i18nAnt.xml to compare other message bundles)"); + } + + /** + * Answers true if the key starts with one of the recorded dynamic key stubs, + * else false + * + * @param key + * @return + */ + private boolean isDynamic(String key) + { + for (String dynamic : dynamicKeys) + { + if (key.startsWith(dynamic)) + { + return true; + } } + return false; } /** @@ -275,14 +324,17 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner continue; } + String messageKey = getMessageKey(method, methodArgs); + if (METHOD3 == method) { System.out.println(String.format("Dynamic key at %s line %s %s", path.substring(sourcePath.length()), lineNos, line)); + String key = messageKey.substring(1, messageKey.length() - 1); + dynamicKeys.add(key); continue; } - String messageKey = getMessageKey(method, methodArgs); if (messageKey == null) { System.out.println(String.format("Trouble parsing %s line %s %s", @@ -370,7 +422,7 @@ public class MessageBundleChecker implements BufferedLineReader.LineCleaner messages.load(reader); reader.close(); - messageKeys = new TreeSet(); + messageKeys = new TreeSet<>(); for (Object key : messages.keySet()) { messageKeys.add((String) key); -- 1.7.10.2