From: Jim Procter Date: Mon, 4 Sep 2017 20:39:38 +0000 (+0100) Subject: Merge branch 'releases/Release_2_10_2b1_Branch' X-Git-Tag: Release_2_10_2b1~11 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=b7b29fb6c0b015ac4798ec4718ab0cfbdd79b20a;hp=7750a4b02d54025e18a216183957fa70a91e3682;p=jalview.git Merge branch 'releases/Release_2_10_2b1_Branch' --- diff --git a/.ant-targets-build.xml b/.ant-targets-build.xml new file mode 100644 index 0000000..15432a1 --- /dev/null +++ b/.ant-targets-build.xml @@ -0,0 +1,31 @@ +build +buildPropertiesFile +buildTests +buildextclients +buildindices +castorbinding +clean +compileApplet +distclean +help +init +linkcheck +makeApplet +makedist +makefulldist +obfuscate +packageApplet +prepare +prepareTests +preparejnlp +prepubapplet_1 +pubapplet +runenv +signApplet +sourcedist +sourcedoc +sourcescrub +testclean +testng +usage +writejnlpf diff --git a/RELEASE b/RELEASE index 6dffc29..e6b0cf8 100644 --- a/RELEASE +++ b/RELEASE @@ -1,2 +1,2 @@ -jalview.release=releases/Release_2_10_2_Branch -jalview.version=2.10.2 +jalview.release=releases/Release_2_10_2b1_Branch +jalview.version=2.10.2b1 diff --git a/build.xml b/build.xml index 9e21593..eb30ef0 100755 --- a/build.xml +++ b/build.xml @@ -451,8 +451,8 @@ - - j2se version="9+" + + j2se version="1.9+" diff --git a/help/help.jhm b/help/help.jhm index cb0c4c4..8e4961f 100755 --- a/help/help.jhm +++ b/help/help.jhm @@ -22,7 +22,7 @@ - + @@ -163,4 +163,5 @@ + diff --git a/help/helpTOC.xml b/help/helpTOC.xml index f3311a7..4636ea3 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -28,6 +28,7 @@ + diff --git a/help/html/features/overview.html b/help/html/features/overview.html index ac5aecb..2a656a3 100755 --- a/help/html/features/overview.html +++ b/help/html/features/overview.html @@ -27,13 +27,14 @@ View→Overview window

Select the overview window menu item to get a navigable image - of the whole alignment.

+ of the whole alignment. By default, gaps are shown as dark grey, in + the overview (since 2.10.2b1).

The red box indicates the currently viewed region of the alignment, this may be moved by clicking and dragging with the mouse.

Click anywhere else in the overview to centre the view on that position

-

+

Hiding hidden regions in the overview

@@ -41,13 +42,15 @@ Hidden sequences and columns are by default shown as dark-grey rows and columns in the overview. Hidden regions can also be omitted entirely (to make the Overview 'WYSIWIG') by Right-clicking - (or CMD-Clicking) to open the overview's popup menu.
-
The option to include/exclude hidden regions in the + (or CMD-Clicking) to open the overview's popup menu.

+ The option to include/exclude hidden regions in the overview was introduced in Jalview 2.10.2.

 

+ Previous to 2.10.2b1, gaps were white, and sequences shown as + dark grey unless coloured. diff --git a/help/html/features/preferences.html b/help/html/features/preferences.html index acd7ba6..b29b66b 100755 --- a/help/html/features/preferences.html +++ b/help/html/features/preferences.html @@ -41,6 +41,9 @@ Preferences tab allows you to configure default colourschemes for a new alignment window. +
  • The "Overview" + Preferences tab configures defaults for the overview window. +
  • The "Structure" Preferences tab allows you to configure options for obtaining and displaying structure information. @@ -181,6 +184,33 @@ by Annotation... is selected from the alignment window's colours menu.

    +

    + "Overview" + Preferences tab +

    +

    + Use legacy gap colouring (gaps are white) - when enabled, + Jalview's overview shows gaps as white, and sequences with no + colourscheme applied as grey. +

    +

    + Show Hidden regions when opening overview - default setting + for inclusion of hidden regions. +

    +

    + Gap Colour - When legacy gap colouring is not enabled, this + configures the default colour for gaps in the overview. +

    +

    + Hidden Colour - colour used to highlight regions in the + overview that are hidden in the alignment. +

    +

    + Gap Colour - The default colour scheme for a new alignment + window. If the chosen option is "User Defined" then the + last User Defined Colour loaded or saved via the User Defined + Colours panel will be loaded. +

    "Structure" Preferences tab added in Jalview 2.8.2 diff --git a/help/html/releases.html b/help/html/releases.html index 471e12a..5a1e324 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -70,6 +70,78 @@ li:before {

    + 2.10.2b1
    + 5/9/2017
    +
    + +
    + +
      +
    • + Show gaps in overview window by colouring + in grey (sequences used to be coloured grey, and gaps were + white) +
    • +
    • + Overview tab in Jalview Desktop + Preferences +
    • +
    • + Overview updates immediately on increase + in size and progress bar shown as higher resolution + overview is recalculated +
    • + +
    +
    +
    + +
      +
    • + Overview window redraws every hidden + column region row by row +
    • +
    • + duplicate protein sequences shown after + retrieving Ensembl crossrefs for sequences from Uniprot +
    • +
    • + Overview window throws NPE if show boxes + format setting is unticked +
    • +
    • + Groups are coloured wrongly in overview + if group has show boxes format setting unticked +
    • +
    • + Redraw problems when + autoscrolling whilst dragging current selection group to + include sequences and columns not currently displayed +
    • +
    • + Not all chains are mapped when multimeric + assemblies are imported via CIF file +
    • +
    • + Gap colour in custom colourscheme is not + displayed when threshold or conservation colouring is also + enabled. +
    • +
    • + JABAWS 2.2 services report wrong JABAWS + server version +
    • +
    • + Jalview continues to scroll after + dragging a selected region off the visible region of the + alignment +
    • +
    +
    + + + +
    2.10.2
    17/8/2017
    @@ -210,7 +282,9 @@ li:before { Updated JABAWS client to v2.2
  • - Filter non-standard amino acids and nucleotides when submitting to AACon and other MSA Analysis services + Filter non-standard amino acids and + nucleotides when submitting to AACon and other MSA + Analysis services
  • URLs for viewing database @@ -272,25 +346,23 @@ li:before { matrix - C->R should be '-3'
    Old matrix restored with this one-line groovy script:
    jalview.analysis.scoremodels.ScoreModels.instance.BLOSUM62.@matrix[4][1]=3
  • -
  • - Fixed Jalview's treatment of gaps in PCA - and substitution matrix based Tree calculations.

    In - earlier versions of Jalview, gaps matching gaps were - penalised, and gaps matching non-gaps penalised even more. - In the PCA calculation, gaps were actually treated as - non-gaps - so different costs were applied, which meant - Jalview's PCAs were different to those produced by - SeqSpace.
    Jalview now treats gaps in the same way as - SeqSpace (ie it scores them as 0).

    Enter - the following in the Groovy console to restore pre-2.10.2 - behaviour:
    +
  • Fixed + Jalview's treatment of gaps in PCA and substitution matrix + based Tree calculations.

    In earlier versions + of Jalview, gaps matching gaps were penalised, and gaps + matching non-gaps penalised even more. In the PCA + calculation, gaps were actually treated as non-gaps - so + different costs were applied, which meant Jalview's PCAs + were different to those produced by SeqSpace.
    Jalview + now treats gaps in the same way as SeqSpace (ie it scores + them as 0).

    Enter the following in the + Groovy console to restore pre-2.10.2 behaviour:
    jalview.analysis.scoremodels.ScoreMatrix.scoreGapAsAny=true // for 2.10.1 mode
    jalview.analysis.scoremodels.ScoreMatrix.scoreGapAsAny=false // to restore 2.10.2 mode

    Note: these settings will affect all subsequent tree and PCA - calculations (not recommended) -
  • + calculations (not recommended)
  • Fixed off-by-one bug that affected scaling of branch lengths for trees computed using @@ -643,7 +715,8 @@ li:before { doesn't always add secondary structure annotation.
  • - + +
    2.10.1
    29/11/2016
    diff --git a/help/html/whatsNew.html b/help/html/whatsNew.html index 90f6832..297233d 100755 --- a/help/html/whatsNew.html +++ b/help/html/whatsNew.html @@ -24,15 +24,27 @@

    + Jalview 2.10.2b1 bugfix release +

    +

    + This is patch release for 2.10.2. See the release notes for full + details bugs addressed in this version, which also introduces + additional improvements to the overview panel, and patches for + several minor issues including the ability to correctly recover + cross-references for Uniprot protein sequences from Ensembl. +

    +

    What's new in Jalview 2.10.2 ?

    - This August 2017 release of Jalview introduces new user interface - features, improved and more extensible tree and PCA analysis, more - robust 3D structure viewing with UCSF Chimera and an updated service - client for JABAWS. The full list of bug fixes and new features can - be found in the 2.10.2 - Release Notes, but the highlights are below. + Version 2.10.2 was released in August 2017, and introduced new user + interface features, improved and more extensible tree and PCA + analysis, more robust 3D structure viewing with UCSF Chimera and an + updated service client for JABAWS. The full list of bug fixes and + new features can be found in the 2.10.2 Release Notes, but + the highlights are below.

    • New dialog and faster and more diff --git a/lib/jabaws-min-client-2.2.0.jar b/lib/jabaws-min-client-2.2.0.jar index bf1e8b1..37426c3 100644 Binary files a/lib/jabaws-min-client-2.2.0.jar and b/lib/jabaws-min-client-2.2.0.jar differ diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 162f10f..9f3ab70 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1311,3 +1311,13 @@ label.occupancy_descr = Number of aligned positions label.show_experimental = Enable experimental features label.show_experimental_tip = Enable any new and currently 'experimental' features (see Latest Release Notes for details) label.warning_hidden = Warning: {0} {1} is currently hidden +label.overview_settings = Overview settings +label.ov_legacy_gap = Use legacy gap colouring (gaps are white) +label.gap_colour = Gap colour: +label.ov_show_hide_default = Show hidden regions when opening overview +label.hidden_colour = Hidden colour: +label.select_gap_colour = Select gap colour +label.select_hidden_colour = Select hidden colour +label.overview = Overview +label.reset_to_defaults = Reset to defaults +label.oview_calc = Recalculating overview... diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 8385142..1e3a391 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -1311,3 +1311,13 @@ label.togglehidden = Mostrar regiones ocultas label.show_experimental = Habilitar funciones experimentales label.show_experimental_tip = Habilitar funciones nuevas y experimentales (ver Latest Release Notes para más detalles) label.warning_hidden = Advertencia: {0} {1} está actualmente oculto +label.overview_settings = Ajustes para la ventana resumen +label.ov_legacy_gap = Utilizar el color heredado de huecos
      (los huecos son blancos) +label.gap_colour = Color de huecos: +label.ov_show_hide_default = Mostrar regiones ocultas al abrir el resumen +label.hidden_colour = Color de las regiones ocultas: +label.select_gap_colour = Seleccionar color de huecos +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 diff --git a/src/jalview/api/AlignmentColsCollectionI.java b/src/jalview/api/AlignmentColsCollectionI.java index 3a3f660..06b1675 100644 --- a/src/jalview/api/AlignmentColsCollectionI.java +++ b/src/jalview/api/AlignmentColsCollectionI.java @@ -30,4 +30,11 @@ public interface AlignmentColsCollectionI extends Iterable * @return true if the column at the position is hidden */ public boolean isHidden(int c); + + /** + * Answers if any column in this collection is hidden + * + * @return true if there is at least 1 hidden column + */ + public boolean hasHidden(); } diff --git a/src/jalview/api/AlignmentRowsCollectionI.java b/src/jalview/api/AlignmentRowsCollectionI.java index 24affc5..fbf6ceb 100644 --- a/src/jalview/api/AlignmentRowsCollectionI.java +++ b/src/jalview/api/AlignmentRowsCollectionI.java @@ -34,6 +34,13 @@ public interface AlignmentRowsCollectionI extends Iterable public boolean isHidden(int r); /** + * Answers if any row in this collection is hidden + * + * @return true if there is at least 1 hidden row + */ + public boolean hasHidden(); + + /** * Answers the sequence at the given position in the alignment * * @param r diff --git a/src/jalview/api/RendererListenerI.java b/src/jalview/api/RendererListenerI.java new file mode 100644 index 0000000..0ce4116 --- /dev/null +++ b/src/jalview/api/RendererListenerI.java @@ -0,0 +1,28 @@ +/* + * 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.api; + +import java.beans.PropertyChangeListener; + +public interface RendererListenerI extends PropertyChangeListener +{ + +} diff --git a/src/jalview/appletgui/OverviewCanvas.java b/src/jalview/appletgui/OverviewCanvas.java index a0466d3..9597b44 100644 --- a/src/jalview/appletgui/OverviewCanvas.java +++ b/src/jalview/appletgui/OverviewCanvas.java @@ -21,6 +21,7 @@ package jalview.appletgui; import jalview.renderer.OverviewRenderer; +import jalview.renderer.OverviewResColourFinder; import jalview.viewmodel.OverviewDimensions; import java.awt.Color; @@ -48,10 +49,6 @@ public class OverviewCanvas extends Component private AlignViewport av; - // Can set different properties in this seqCanvas than - // main visible SeqCanvas - private SequenceRenderer sr; - private jalview.renderer.seqfeatures.FeatureRenderer fr; private Frame nullFrame; @@ -65,10 +62,6 @@ public class OverviewCanvas extends Component nullFrame = new Frame(); nullFrame.addNotify(); - sr = new SequenceRenderer(av); - sr.graphics = nullFrame.getGraphics(); - sr.renderGaps = false; - sr.forOverview = true; fr = new jalview.renderer.seqfeatures.FeatureRenderer(av); } @@ -119,7 +112,8 @@ public class OverviewCanvas extends Component setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); - or = new OverviewRenderer(sr, fr, od); + or = new OverviewRenderer(fr, od, av.getAlignment(), + av.getResidueShading(), new OverviewResColourFinder()); miniMe = nullFrame.createImage(od.getWidth(), od.getHeight()); offscreen = nullFrame.createImage(od.getWidth(), od.getHeight()); diff --git a/src/jalview/appletgui/OverviewPanel.java b/src/jalview/appletgui/OverviewPanel.java index be552c0..e74e1cd 100755 --- a/src/jalview/appletgui/OverviewPanel.java +++ b/src/jalview/appletgui/OverviewPanel.java @@ -148,11 +148,12 @@ public class OverviewPanel extends Panel implements Runnable, } else { + // don't do anything if the mouse press is in the overview's box + // (wait to see if it's a drag instead) + // otherwise update the viewport if (!od.isPositionInBox(evt.getX(), evt.getY())) - { - // don't do anything if the mouse press is in the overview's box - // (wait to see if it's a drag instead) - // otherwise update the viewport + { + draggingBox = false; od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment().getHiddenSequences(), av.getAlignment().getHiddenColumns()); @@ -170,10 +171,6 @@ public class OverviewPanel extends Panel implements Runnable, @Override public void mouseReleased(MouseEvent evt) { - if (draggingBox) - { - draggingBox = false; - } } @Override diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index be4f9e6..57bfa68 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -1451,7 +1451,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, { if (scrollThread != null) { - scrollThread.running = false; + scrollThread.threadRunning = false; scrollThread = null; } @@ -1701,7 +1701,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, mouseExited(evt); } - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { scrollThread.setEvent(evt); } @@ -1717,9 +1717,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, oldSeq = 0; } - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { - scrollThread.running = false; + scrollThread.stopScrolling(); scrollThread = null; } } @@ -1742,9 +1742,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, { if (evt == null) { - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { - scrollThread.running = false; + scrollThread.stopScrolling(); scrollThread = null; } mouseDragging = false; @@ -1767,7 +1767,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, { MouseEvent evt; - boolean running = false; + private volatile boolean threadRunning = true; public ScrollThread() { @@ -1781,14 +1781,18 @@ public class SeqPanel extends Panel implements MouseMotionListener, public void stopScrolling() { - running = false; + threadRunning = false; + } + + public boolean isRunning() + { + return threadRunning; } @Override public void run() { - running = true; - while (running) + while (threadRunning) { if (evt != null) @@ -1797,23 +1801,23 @@ public class SeqPanel extends Panel implements MouseMotionListener, if (mouseDragging && evt.getY() < 0 && av.getRanges().getStartSeq() > 0) { - running = av.getRanges().scrollUp(true); + av.getRanges().scrollUp(true); } if (mouseDragging && evt.getY() >= getSize().height && av .getAlignment().getHeight() > av.getRanges().getEndSeq()) { - running = av.getRanges().scrollUp(false); + av.getRanges().scrollUp(false); } if (mouseDragging && evt.getX() < 0) { - running = av.getRanges().scrollRight(false); + av.getRanges().scrollRight(false); } else if (mouseDragging && evt.getX() >= getSize().width) { - running = av.getRanges().scrollRight(true); + av.getRanges().scrollRight(true); } } diff --git a/src/jalview/appletgui/SequenceRenderer.java b/src/jalview/appletgui/SequenceRenderer.java index a382c60..e55f2a5 100755 --- a/src/jalview/appletgui/SequenceRenderer.java +++ b/src/jalview/appletgui/SequenceRenderer.java @@ -22,7 +22,7 @@ package jalview.appletgui; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; -import jalview.renderer.ResidueShaderI; +import jalview.renderer.ResidueColourFinder; import jalview.renderer.seqfeatures.FeatureColourFinder; import java.awt.Color; @@ -40,19 +40,18 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer boolean renderGaps = true; - SequenceGroup currentSequenceGroup = null; - SequenceGroup[] allGroups = null; Color resBoxColour; Graphics graphics; - boolean forOverview = false; + ResidueColourFinder resColourFinder; public SequenceRenderer(AlignViewport av) { this.av = av; + resColourFinder = new ResidueColourFinder(); } /** @@ -69,25 +68,6 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer this.renderGaps = renderGaps; } - protected Color getResidueBoxColour(SequenceI seq, int i) - { - allGroups = av.getAlignment().findAllGroups(seq); - - if (inCurrentSequenceGroup(i)) - { - if (currentSequenceGroup.getDisplayBoxes()) - { - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, i); - } - } - else if (av.getShowBoxes()) - { - getBoxColour(av.getResidueShading(), seq, i); - } - - return resBoxColour; - } - /** * Get the residue colour at the given sequence position - as determined by * the sequence group colour (if any), else the colour scheme, possibly @@ -104,31 +84,9 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer { // TODO replace 8 or so code duplications with calls to this method // (refactored as needed) - Color col = getResidueBoxColour(seq, position); - - if (finder != null) - { - col = finder.findFeatureColour(col, seq, position); - } - return col; - } - - void getBoxColour(ResidueShaderI shader, SequenceI seq, int i) - { - if (shader.getColourScheme() != null) - { - resBoxColour = shader.findColour(seq.getCharAt(i), i, seq); - } - else if (forOverview - && !jalview.util.Comparison.isGap(seq.getCharAt(i))) - { - resBoxColour = Color.lightGray; - } - else - { - resBoxColour = Color.white; - } - + return resColourFinder.getResidueColour(av.getShowBoxes(), + av.getResidueShading(), + allGroups, seq, position, finder); } public Color findSequenceColour(SequenceI seq, int i) @@ -165,23 +123,28 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer int curWidth = av.getCharWidth(), avCharWidth = av.getCharWidth(), avCharHeight = av.getCharHeight(); + Color resBoxColour = Color.white; Color tempColour = null; while (i <= end) { resBoxColour = Color.white; if (i < length) { - if (inCurrentSequenceGroup(i)) + SequenceGroup currentSequenceGroup = resColourFinder + .getCurrentSequenceGroup(allGroups, i); + if (currentSequenceGroup != null) { if (currentSequenceGroup.getDisplayBoxes()) { - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, + resBoxColour = resColourFinder.getBoxColour( + currentSequenceGroup.getGroupColourScheme(), seq, i); } } else if (av.getShowBoxes()) { - getBoxColour(av.getResidueShading(), seq, i); + resBoxColour = resColourFinder + .getBoxColour(av.getResidueShading(), seq, i); } } @@ -245,7 +208,9 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer continue; } - if (inCurrentSequenceGroup(i)) + SequenceGroup currentSequenceGroup = resColourFinder + .getCurrentSequenceGroup(allGroups, i); + if (currentSequenceGroup != null) { if (!currentSequenceGroup.getDisplayText()) { @@ -254,7 +219,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer if (currentSequenceGroup.getColourText()) { - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, i); + resBoxColour = resColourFinder.getBoxColour( + currentSequenceGroup.getGroupColourScheme(), seq, i); graphics.setColor(resBoxColour.darker()); } if (currentSequenceGroup.getShowNonconserved()) @@ -271,7 +237,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer if (av.getColourText()) { - getBoxColour(av.getResidueShading(), seq, i); + resBoxColour = resColourFinder + .getBoxColour(av.getResidueShading(), seq, i); if (av.getShowBoxes()) { graphics.setColor(resBoxColour.darker()); @@ -358,28 +325,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer return sequenceChar; } - boolean inCurrentSequenceGroup(int res) - { - if (allGroups == null) - { - return false; - } - - for (int i = 0; i < allGroups.length; i++) - { - if (allGroups[i].getStartRes() <= res - && allGroups[i].getEndRes() >= res) - { - currentSequenceGroup = allGroups[i]; - return true; - } - } - - return false; - } - - public void drawHighlightedText(SequenceI seq, int start, int end, int x1, - int y1) + public void drawHighlightedText(SequenceI seq, int start, int end, + int x1, int y1) { int avCharWidth = av.getCharWidth(), avCharHeight = av.getCharHeight(); int pady = avCharHeight / 5; diff --git a/src/jalview/commands/RemoveGapColCommand.java b/src/jalview/commands/RemoveGapColCommand.java index 3266874..32e5fb4 100644 --- a/src/jalview/commands/RemoveGapColCommand.java +++ b/src/jalview/commands/RemoveGapColCommand.java @@ -20,25 +20,6 @@ */ package jalview.commands; -/* - * Jalview - A Sequence Alignment Editor and Viewer Copyright (C) 2007 AM - * Waterhouse, J Procter, G Barton, M Clamp, S Searle - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; @@ -110,6 +91,7 @@ public class RemoveGapColCommand extends EditCommand performEdit(0, null); } + @Override public int getSize() { // We're interested in the number of columns deleted, diff --git a/src/jalview/commands/RemoveGapsCommand.java b/src/jalview/commands/RemoveGapsCommand.java index c5c35f1..27831dd 100644 --- a/src/jalview/commands/RemoveGapsCommand.java +++ b/src/jalview/commands/RemoveGapsCommand.java @@ -20,25 +20,6 @@ */ package jalview.commands; -/* - * Jalview - A Sequence Alignment Editor and Viewer Copyright (C) 2007 AM - * Waterhouse, J Procter, G Barton, M Clamp, S Searle - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; diff --git a/src/jalview/datamodel/AlignmentI.java b/src/jalview/datamodel/AlignmentI.java index 1b5207f..084b80e 100755 --- a/src/jalview/datamodel/AlignmentI.java +++ b/src/jalview/datamodel/AlignmentI.java @@ -5,16 +5,16 @@ * 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 License + * 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 License for more details. + * PURPOSE. See the GNU General Public License for more details. * - * You should have received a copy of the GNU General License + * 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. */ diff --git a/src/jalview/datamodel/AllColsCollection.java b/src/jalview/datamodel/AllColsCollection.java index 4ac4f83..e216c46 100644 --- a/src/jalview/datamodel/AllColsCollection.java +++ b/src/jalview/datamodel/AllColsCollection.java @@ -50,4 +50,10 @@ public class AllColsCollection implements AlignmentColsCollectionI { return !hidden.isVisible(c); } + + @Override + public boolean hasHidden() + { + return hidden.hasHiddenColumns(); + } } diff --git a/src/jalview/datamodel/AllRowsCollection.java b/src/jalview/datamodel/AllRowsCollection.java index 9a33094..10c47f0 100644 --- a/src/jalview/datamodel/AllRowsCollection.java +++ b/src/jalview/datamodel/AllRowsCollection.java @@ -59,4 +59,10 @@ public class AllRowsCollection implements AlignmentRowsCollectionI { return alignment.getSequenceAtAbsoluteIndex(seq); } + + @Override + public boolean hasHidden() + { + return (hidden.getSize() > 0); + } } diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 6964b53..e2f15e1 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -27,6 +27,8 @@ import jalview.renderer.ResidueShaderI; import jalview.schemes.ColourSchemeI; import java.awt.Color; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -39,6 +41,26 @@ import java.util.Map; */ public class SequenceGroup implements AnnotatedCollectionI { + // TODO ideally this event notification functionality should be separated into + // a + // subclass of ViewportProperties similarly to ViewportRanges. Done here as + // quick fix for JAL-2665 + public static final String SEQ_GROUP_CHANGED = "Sequence group changed"; + + protected PropertyChangeSupport changeSupport = new PropertyChangeSupport( + this); + + public void addPropertyChangeListener(PropertyChangeListener listener) + { + changeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) + { + changeSupport.removePropertyChangeListener(listener); + } + // end of event notification functionality initialisation + String groupName; String description; @@ -496,6 +518,8 @@ public class SequenceGroup implements AnnotatedCollectionI if (s != null && !sequences.contains(s)) { sequences.add(s); + changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, + sequences.size() - 1, sequences.size()); } if (recalc) @@ -693,6 +717,8 @@ public class SequenceGroup implements AnnotatedCollectionI synchronized (sequences) { sequences.remove(s); + changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, + sequences.size() + 1, sequences.size()); if (recalc) { @@ -729,7 +755,9 @@ public class SequenceGroup implements AnnotatedCollectionI */ public void setStartRes(int i) { + int before = startRes; startRes = i; + changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes); } /** @@ -739,7 +767,9 @@ public class SequenceGroup implements AnnotatedCollectionI */ public void setEndRes(int i) { + int before = endRes; endRes = i; + changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes); } /** @@ -1349,7 +1379,10 @@ public class SequenceGroup implements AnnotatedCollectionI { synchronized (sequences) { + int before = sequences.size(); sequences.clear(); + changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, + sequences.size()); } } diff --git a/src/jalview/datamodel/VisibleColsCollection.java b/src/jalview/datamodel/VisibleColsCollection.java index bc32fac..e9437a7 100644 --- a/src/jalview/datamodel/VisibleColsCollection.java +++ b/src/jalview/datamodel/VisibleColsCollection.java @@ -51,4 +51,10 @@ public class VisibleColsCollection implements AlignmentColsCollectionI return false; } + @Override + public boolean hasHidden() + { + return false; + } + } diff --git a/src/jalview/datamodel/VisibleRowsCollection.java b/src/jalview/datamodel/VisibleRowsCollection.java index ee0557f..fd7cf47 100644 --- a/src/jalview/datamodel/VisibleRowsCollection.java +++ b/src/jalview/datamodel/VisibleRowsCollection.java @@ -56,4 +56,10 @@ public class VisibleRowsCollection implements AlignmentRowsCollectionI { return alignment.getSequenceAtAbsoluteIndex(seq); } + + @Override + public boolean hasHidden() + { + return false; + } } diff --git a/src/jalview/datamodel/xdb/embl/EmblEntry.java b/src/jalview/datamodel/xdb/embl/EmblEntry.java index fe3f6ef..2de100b 100644 --- a/src/jalview/datamodel/xdb/embl/EmblEntry.java +++ b/src/jalview/datamodel/xdb/embl/EmblEntry.java @@ -279,7 +279,7 @@ public class EmblEntry String translation = null; String proteinName = ""; String proteinId = null; - Map vals = new Hashtable(); + Map vals = new Hashtable<>(); /* * codon_start 1/2/3 in EMBL corresponds to phase 0/1/2 in CDS diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index 922e481..fe216c0 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -1103,8 +1103,8 @@ public class AlignmentPanel extends GAlignmentPanel implements * single graphics context), then reset to (0, scale height) */ alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight); - getSeqPanel().seqCanvas.drawPanel(alignmentGraphics, startRes, endRes, - startSeq, endSeq, 0); + getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes, + endRes, startSeq, endSeq); alignmentGraphics.translate(-alignmentGraphicsOffset, 0); if (av.isShowAnnotation() && (endSeq == alignmentHeight)) @@ -1231,7 +1231,7 @@ public class AlignmentPanel extends GAlignmentPanel implements pg.translate(idWidth, 0); - getSeqPanel().seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, + getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(pg, pwidth - idWidth, totalHeight, 0); if ((pi * pheight) < totalHeight) diff --git a/src/jalview/gui/FontChooser.java b/src/jalview/gui/FontChooser.java index 5271e4f..6cddcca 100755 --- a/src/jalview/gui/FontChooser.java +++ b/src/jalview/gui/FontChooser.java @@ -327,22 +327,17 @@ public class FontChooser extends GFontChooser ap.fontChanged(); /* - * adjust other half of split frame if any, if either same - * font, or proportionate scaling, is selected + * adjust other half of split frame if present, whether or not same font or + * scale to cDNA is selected, because a font change may affect character + * width, and this is kept the same in both panels */ - if (fontAsCdna.isEnabled()) + if (fontAsCdna.isVisible()) { if (fontAsCdna.isSelected()) { - /* - * copy the font - */ ap.av.getCodingComplement().setFont(newFont, true); } - /* - * adjust layout for font change / reset / sizing - */ SplitFrame splitFrame = (SplitFrame) ap.alignFrame .getSplitViewContainer(); splitFrame.adjustLayout(); diff --git a/src/jalview/gui/OverviewCanvas.java b/src/jalview/gui/OverviewCanvas.java index 27f9c3f..64bf15c 100644 --- a/src/jalview/gui/OverviewCanvas.java +++ b/src/jalview/gui/OverviewCanvas.java @@ -21,18 +21,29 @@ package jalview.gui; import jalview.api.AlignViewportI; +import jalview.bin.Cache; import jalview.renderer.OverviewRenderer; +import jalview.renderer.OverviewResColourFinder; import jalview.viewmodel.OverviewDimensions; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import javax.swing.JComponent; +import javax.swing.Timer; public class OverviewCanvas extends JComponent { + private static final long RUNNING_TIME = 1000; + + private static final int SPEED = 40; + private static final Color TRANS_GREY = new Color(100, 100, 100, 25); // This is set true if the alignment view changes whilst @@ -41,10 +52,14 @@ public class OverviewCanvas extends JComponent private volatile boolean updaterunning = false; + private boolean dispose = false; + private BufferedImage miniMe; private BufferedImage lastMiniMe = null; + private BufferedImage veryLastMiniMe = null; + // Can set different properties in this seqCanvas than // main visible SeqCanvas private SequenceRenderer sr; @@ -57,16 +72,67 @@ public class OverviewCanvas extends JComponent private AlignViewportI av; + + private OverviewResColourFinder cf; + + private float alpha = 0f; + + private long startTime = -1; + + private final Timer timer; + + private ProgressPanel progressPanel; + public OverviewCanvas(OverviewDimensions overviewDims, - AlignViewportI alignvp) + AlignViewportI alignvp, ProgressPanel pp) { od = overviewDims; av = alignvp; + progressPanel = pp; sr = new SequenceRenderer(av); sr.renderGaps = false; - sr.forOverview = true; fr = new jalview.renderer.seqfeatures.FeatureRenderer(av); + + boolean useLegacy = Cache.getDefault(Preferences.USE_LEGACY_GAP, false); + Color gapCol = Cache.getDefaultColour(Preferences.GAP_COLOUR, + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP); + Color hiddenCol = Cache.getDefaultColour(Preferences.HIDDEN_COLOUR, + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN); + cf = new OverviewResColourFinder(useLegacy, gapCol, hiddenCol); + + setSize(od.getWidth(), od.getHeight()); + + timer = new Timer(SPEED, new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + if (startTime < 0) + { + startTime = System.currentTimeMillis(); + } + else + { + + long time = System.currentTimeMillis(); + long duration = time - startTime; + if (duration >= RUNNING_TIME) + { + startTime = -1; + ((Timer) e.getSource()).stop(); + alpha = 0f; + } + else + { + alpha = 1f - ((float) duration / (float) RUNNING_TIME); + } + repaint(); + } + } + }); + } /** @@ -118,6 +184,7 @@ public class OverviewCanvas extends JComponent FeatureRenderer transferRenderer) { miniMe = null; + veryLastMiniMe = lastMiniMe; if (showSequenceFeatures) { @@ -126,7 +193,11 @@ public class OverviewCanvas extends JComponent setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); - or = new OverviewRenderer(sr, fr, od); + or = new OverviewRenderer(fr, od, av.getAlignment(), + av.getResidueShading(), cf); + + or.addPropertyChangeListener(progressPanel); + miniMe = or.draw(od.getRows(av.getAlignment()), od.getColumns(av.getAlignment())); @@ -142,15 +213,22 @@ public class OverviewCanvas extends JComponent } System.gc(); + or.removePropertyChangeListener(progressPanel); + or = null; if (restart) { restart = false; - draw(showSequenceFeatures, showAnnotation, transferRenderer); + if (!dispose) + { + draw(showSequenceFeatures, showAnnotation, transferRenderer); + } } else { updaterunning = false; lastMiniMe = miniMe; + alpha = 1f; + timer.start(); } } @@ -173,16 +251,88 @@ public class OverviewCanvas extends JComponent } else if (lastMiniMe != null) { - g.drawImage(lastMiniMe, 0, 0, this); - if (lastMiniMe != miniMe) + // is this a resize? + if ((getWidth() > 0) && (getHeight() > 0) + && ((getWidth() != od.getWidth()) + || (getHeight() != od.getHeight()))) { - g.setColor(TRANS_GREY); - g.fillRect(0, 0, getWidth(), getHeight()); + // if there is annotation, scale the alignment and annotation separately + if (od.getGraphHeight() > 0) + { + BufferedImage topImage = lastMiniMe.getSubimage(0, 0, + od.getWidth(), od.getSequencesHeight()); + BufferedImage bottomImage = lastMiniMe.getSubimage(0, + od.getSequencesHeight(), od.getWidth(), + od.getGraphHeight()); + + // must be done at this point as we rely on using old width/height + // above, and new width/height below + od.setWidth(getWidth()); + od.setHeight(getHeight()); + + // stick the images back together so lastMiniMe is consistent in the + // event of a repaint - BUT probably not thread safe + lastMiniMe = new BufferedImage(od.getWidth(), od.getHeight(), + BufferedImage.TYPE_INT_RGB); + Graphics lg = lastMiniMe.getGraphics(); + lg.drawImage(topImage, 0, 0, od.getWidth(), + od.getSequencesHeight(), null); + lg.drawImage(bottomImage, 0, od.getSequencesHeight(), + od.getWidth(), od.getGraphHeight(), this); + lg.dispose(); + } + else + { + od.setWidth(getWidth()); + od.setHeight(getHeight()); + } + + // scale lastMiniMe to the new size + g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); + + // make sure the box is in the right place + od.setBoxPosition(av.getAlignment().getHiddenSequences(), + av.getAlignment().getHiddenColumns()); + } + else // not a resize + { + if (alpha != 0) // this is a timer triggered dissolve + { + Graphics2D g2d = (Graphics2D) g.create(); + + // draw the original image + g2d.drawImage(veryLastMiniMe, 0, 0, getWidth(), getHeight(), + this); + + // draw the new image on top with varying degrees of transparency + g2d.setComposite(AlphaComposite.SrcOver.derive(1f - alpha)); + g2d.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); + + g2d.dispose(); + } + else + { + // fall back to normal behaviour + g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this); + } } } + // draw the box g.setColor(Color.red); od.drawBox(g); } + public void dispose() + { + dispose = true; + synchronized (this) + { + restart = true; + if (or != null) + { + or.setRedraw(true); + } + } + } } diff --git a/src/jalview/gui/OverviewPanel.java b/src/jalview/gui/OverviewPanel.java index 28de801..51d7a84 100755 --- a/src/jalview/gui/OverviewPanel.java +++ b/src/jalview/gui/OverviewPanel.java @@ -20,6 +20,8 @@ */ package jalview.gui; +import jalview.bin.Cache; +import jalview.renderer.OverviewRenderer; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.viewmodel.OverviewDimensions; @@ -68,6 +70,8 @@ public class OverviewPanel extends JPanel private boolean draggingBox = false; + private ProgressPanel progressPanel; + /** * Creates a new OverviewPanel object. * @@ -79,30 +83,62 @@ public class OverviewPanel extends JPanel this.av = alPanel.av; this.ap = alPanel; - od = new OverviewDimensionsShowHidden(av.getRanges(), + showHidden = Cache.getDefault(Preferences.SHOW_OV_HIDDEN_AT_START, + true); + if (showHidden) + { + od = new OverviewDimensionsShowHidden(av.getRanges(), (av.isShowAnnotation() && av.getAlignmentConservationAnnotation() != null)); + } + else + { + od = new OverviewDimensionsHideHidden(av.getRanges(), + (av.isShowAnnotation() + && av.getAlignmentConservationAnnotation() != null)); + } - setSize(od.getWidth(), od.getHeight()); - - oviewCanvas = new OverviewCanvas(od, av); setLayout(new BorderLayout()); + progressPanel = new ProgressPanel(OverviewRenderer.UPDATE, + MessageManager.getString("label.oview_calc"), getWidth()); + this.add(progressPanel, BorderLayout.SOUTH); + oviewCanvas = new OverviewCanvas(od, av, progressPanel); + add(oviewCanvas, BorderLayout.CENTER); av.getRanges().addPropertyChangeListener(this); + // without this the overview window does not size to fit the overview canvas + setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); + addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { - if ((getWidth() != od.getWidth()) - || (getHeight() != (od.getHeight()))) + // Resize is called on the initial display of the overview. + // This code adjusts sizes to account for the progress bar if it has not + // already been accounted for, which triggers another resize call for + // the correct sizing, at which point the overview image is updated. + // (This avoids a double recalculation of the image.) + if (getWidth() == od.getWidth() && getHeight() == od.getHeight() + + progressPanel.getHeight()) { updateOverviewImage(); - setBoxPosition(); + } + else + { + if ((getWidth() > 0) && (getHeight() > 0)) + { + od.setWidth(getWidth()); + od.setHeight(getHeight() - progressPanel.getHeight()); + } + + setPreferredSize(new Dimension(od.getWidth(), + od.getHeight() + progressPanel.getHeight())); } } + }); addMouseMotionListener(new MouseMotionAdapter() @@ -158,13 +194,13 @@ public class OverviewPanel extends JPanel } } else - // if (!av.getWrapAlignment()) { + // don't do anything if the mouse press is in the overview's box + // (wait to see if it's a drag instead) + // otherwise update the viewport if (!od.isPositionInBox(evt.getX(), evt.getY())) { - // don't do anything if the mouse press is in the overview's box - // (wait to see if it's a drag instead) - // otherwise update the viewport + draggingBox = false; od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment().getHiddenSequences(), av.getAlignment().getHiddenColumns()); @@ -180,15 +216,6 @@ public class OverviewPanel extends JPanel } @Override - public void mouseReleased(MouseEvent evt) - { - if (draggingBox) - { - draggingBox = false; - } - } - - @Override public void mouseClicked(MouseEvent evt) { if (SwingUtilities.isRightMouseButton(evt)) @@ -197,8 +224,6 @@ public class OverviewPanel extends JPanel } } }); - - updateOverviewImage(); } /* @@ -266,10 +291,11 @@ public class OverviewPanel extends JPanel if ((getWidth() > 0) && (getHeight() > 0)) { od.setWidth(getWidth()); - od.setHeight(getHeight()); + od.setHeight(getHeight() - progressPanel.getHeight()); } - - setPreferredSize(new Dimension(od.getWidth(), od.getHeight())); + + setPreferredSize(new Dimension(od.getWidth(), + od.getHeight() + progressPanel.getHeight())); if (oviewCanvas.restartDraw()) { @@ -279,16 +305,21 @@ public class OverviewPanel extends JPanel Thread thread = new Thread(this); thread.start(); repaint(); + + } @Override public void run() { - oviewCanvas.draw(av.isShowSequenceFeatures(), - (av.isShowAnnotation() - && av.getAlignmentConservationAnnotation() != null), - ap.getSeqPanel().seqCanvas.getFeatureRenderer()); - setBoxPosition(); + if (oviewCanvas != null) + { + oviewCanvas.draw(av.isShowSequenceFeatures(), + (av.isShowAnnotation() + && av.getAlignmentConservationAnnotation() != null), + ap.getSeqPanel().seqCanvas.getFeatureRenderer()); + setBoxPosition(); + } } /** @@ -298,9 +329,12 @@ public class OverviewPanel extends JPanel */ private void setBoxPosition() { - od.setBoxPosition(av.getAlignment().getHiddenSequences(), - av.getAlignment().getHiddenColumns()); - repaint(); + if (od != null) + { + od.setBoxPosition(av.getAlignment().getHiddenSequences(), + av.getAlignment().getHiddenColumns()); + repaint(); + } } @Override @@ -317,8 +351,10 @@ public class OverviewPanel extends JPanel try { av.getRanges().removePropertyChangeListener(this); + oviewCanvas.dispose(); } finally { + progressPanel = null; av = null; oviewCanvas = null; ap = null; diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 3e2eba9..2ef71cc 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -455,8 +455,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener buildGroupURLMenu(sg, groupLinks); } // Add a 'show all structures' for the current selection - Hashtable pdbe = new Hashtable(), - reppdb = new Hashtable(); + Hashtable pdbe = new Hashtable<>(), reppdb = new Hashtable<>(); + SequenceI sqass = null; for (SequenceI sq : ap.av.getSequenceSelection()) { @@ -523,7 +523,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener void addFeatureLinks(final SequenceI seq, List links) { JMenu linkMenu = new JMenu(MessageManager.getString("action.link")); - Map> linkset = new LinkedHashMap>(); + Map> linkset = new LinkedHashMap<>(); for (String link : links) { @@ -609,8 +609,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * the insertion order, which is the order of the annotations on the * alignment. */ - Map>> shownTypes = new LinkedHashMap>>(); - Map>> hiddenTypes = new LinkedHashMap>>(); + Map>> shownTypes = new LinkedHashMap<>(); + Map>> hiddenTypes = new LinkedHashMap<>(); AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes, AlignmentAnnotationUtils.asList(annotations), forSequences); @@ -716,7 +716,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener SequenceI[] seqs = ap.av.getSelectionAsNewSequence(); String[][] idandseqs = GroupUrlLink.formStrings(seqs); - Hashtable commonDbrefs = new Hashtable(); + Hashtable commonDbrefs = new Hashtable<>(); for (int sq = 0; sq < seqs.length; sq++) { @@ -1377,8 +1377,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener * Temporary store to hold distinct calcId / type pairs for the tooltip. * Using TreeMap means calcIds are shown in alphabetical order. */ - SortedMap tipEntries = new TreeMap(); - final Map> candidates = new LinkedHashMap>(); + SortedMap tipEntries = new TreeMap<>(); + final Map> candidates = new LinkedHashMap<>(); AlignmentI al = this.ap.av.getAlignment(); AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries, candidates, al); @@ -1561,7 +1561,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener void refresh() { ap.updateAnnotation(); - ap.paintAlignment(true); + // removed paintAlignment(true) here: + // updateAnnotation calls paintAlignment already, so don't need to call + // again PaintRefresher.Refresh(this, ap.av.getSequenceSetId()); } @@ -1929,8 +1931,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener return; } - List seqs = new ArrayList(); - List features = new ArrayList(); + List seqs = new ArrayList<>(); + List features = new ArrayList<>(); /* * assemble dataset sequences, and template new sequence features, diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index c3c9239..6635dbe 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -109,6 +109,14 @@ public class Preferences extends GPreferences public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY"; + public static final String SHOW_OV_HIDDEN_AT_START = "SHOW_OV_HIDDEN_AT_START"; + + public static final String USE_LEGACY_GAP = "USE_LEGACY_GAP"; + + public static final String GAP_COLOUR = "GAP_COLOUR"; + + public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR"; + private static final int MIN_FONT_SIZE = 1; private static final int MAX_FONT_SIZE = 30; @@ -155,7 +163,7 @@ public class Preferences extends GPreferences * .properties file as '|' separated strings */ - groupURLLinks = new ArrayList(); + groupURLLinks = new ArrayList<>(); } JInternalFrame frame; @@ -309,6 +317,21 @@ public class Preferences extends GPreferences Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red)); /* + * Set overview panel defaults + */ + gapColour.setBackground( + Cache.getDefaultColour(GAP_COLOUR, + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP)); + hiddenColour.setBackground( + Cache.getDefaultColour(HIDDEN_COLOUR, + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN)); + useLegacyGap.setSelected(Cache.getDefault(USE_LEGACY_GAP, false)); + gapLabel.setEnabled(!useLegacyGap.isSelected()); + gapColour.setEnabled(!useLegacyGap.isSelected()); + showHiddenAtStart + .setSelected(Cache.getDefault(SHOW_OV_HIDDEN_AT_START, true)); + + /* * Set Structure tab defaults. */ final boolean structSelected = Cache.getDefault(STRUCT_FROM_PDB, false); @@ -631,6 +654,16 @@ public class Preferences extends GPreferences maxColour.getBackground()); /* + * Save Overview settings + */ + Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground()); + Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground()); + Cache.applicationProperties.setProperty(USE_LEGACY_GAP, + Boolean.toString(useLegacyGap.isSelected())); + Cache.applicationProperties.setProperty(SHOW_OV_HIDDEN_AT_START, + Boolean.toString(showHiddenAtStart.isSelected())); + + /* * Save Structure settings */ Cache.applicationProperties.setProperty(ADD_TEMPFACT_ANN, @@ -1035,6 +1068,63 @@ public class Preferences extends GPreferences } @Override + public void gapColour_actionPerformed(JPanel gap) + { + if (!useLegacyGap.isSelected()) + { + Color col = JColorChooser.showDialog(this, + MessageManager.getString("label.select_gap_colour"), + gapColour.getBackground()); + if (col != null) + { + gap.setBackground(col); + } + gap.repaint(); + } + } + + @Override + public void hiddenColour_actionPerformed(JPanel hidden) + { + Color col = JColorChooser.showDialog(this, + MessageManager.getString("label.select_hidden_colour"), + hiddenColour.getBackground()); + if (col != null) + { + hidden.setBackground(col); + } + hidden.repaint(); + } + + @Override + protected void useLegacyGaps_actionPerformed(ActionEvent e) + { + boolean enabled = useLegacyGap.isSelected(); + if (enabled) + { + gapColour.setBackground( + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_LEGACY_GAP); + } + else + { + gapColour.setBackground( + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP); + } + gapColour.setEnabled(!enabled); + gapLabel.setEnabled(!enabled); + } + + @Override + protected void resetOvDefaults_actionPerformed(ActionEvent e) + { + useLegacyGap.setSelected(false); + useLegacyGaps_actionPerformed(null); + showHiddenAtStart.setSelected(true); + hiddenColour.setBackground( + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN); + } + + @Override protected void userIdWidth_actionPerformed() { try diff --git a/src/jalview/gui/ProgressPanel.java b/src/jalview/gui/ProgressPanel.java new file mode 100644 index 0000000..170e9eb --- /dev/null +++ b/src/jalview/gui/ProgressPanel.java @@ -0,0 +1,148 @@ +/* + * 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.RendererListenerI; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.beans.PropertyChangeEvent; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.border.EmptyBorder; + +/** + * A class to manage a panel containing a label and progress bar updated by an + * event firing + * + * @author kmourao + * + */ +public class ProgressPanel extends JPanel implements RendererListenerI +{ + // max value of progress bar: values expected to be %s + private final int MAXVALUE = 100; + + private final String VISIBLE = "VISIBLE"; + + private final String INVISIBLE = "INVISIBLE"; + + // name of event property which updates the progress bar + private String eventName; + + private JProgressBar progressBar; + + private JLabel progressLabel; + + private JPanel labelPanel = new JPanel(); + + private CardLayout labelLayout = new CardLayout(); + + private JPanel barPanel = new JPanel(); + + private CardLayout barLayout = new CardLayout(); + + /** + * Construct a JPanel containing a progress bar and a label. + * + * @param eventPropertyName + * The name of the event property to update the progress bar + * @param label + * The label to place next to the progress bar + */ + public ProgressPanel(String eventPropertyName, String label, int maxwidth) + { + super(new BorderLayout(10, 0)); + setBorder(new EmptyBorder(0, 3, 0, 0)); + + eventName = eventPropertyName; + String labelText = label; + + final int w = maxwidth; + + progressBar = new JProgressBar() + { + @Override + public Dimension getMaximumSize() + { + return new Dimension(w, 1); + } + }; + progressBar.setMinimum(0); + progressBar.setPreferredSize(progressBar.getMaximumSize()); + progressLabel = new JLabel(labelText); + progressLabel.setFont(new java.awt.Font("Verdana", 0, 11)); + + // Use a CardLayout to stop the progress bar panel moving around when + // changing visibility + labelPanel.setLayout(labelLayout); + barPanel.setLayout(barLayout); + + labelPanel.add(progressLabel, VISIBLE); + labelPanel.add(new JPanel(), INVISIBLE); + barPanel.add(progressBar, VISIBLE); + barPanel.add(new JPanel(), INVISIBLE); + + labelLayout.show(labelPanel, VISIBLE); + barLayout.show(barPanel, VISIBLE); + + add(labelPanel, BorderLayout.WEST); + add(barPanel, BorderLayout.CENTER); + add(new JLabel(" "), BorderLayout.EAST); + + setBorder(BorderFactory.createLineBorder(Color.black)); + // setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); + } + + @Override + /** + * Update the progress bar in response to the event. Expects the value + * supplied by the event to be in the range 0-100 i.e. a percentage + */ + public void propertyChange(PropertyChangeEvent evt) + { + if (evt.getPropertyName().equals(eventName)) + { + int progress = (int) evt.getNewValue(); + progressBar.setValue(progress); + + // switch progress bar to visible if it is not visible and current + // progress is less than MAXVALUE + // switch progress bar to invisible if it is visible and we reached + // MAXVALUE + if (progress < MAXVALUE && !progressBar.isVisible()) + { + labelLayout.show(labelPanel, VISIBLE); + barLayout.show(barPanel, VISIBLE); + } + if (progress >= MAXVALUE) + { + labelLayout.show(labelPanel, INVISIBLE); + barLayout.show(barPanel, INVISIBLE); + } + } + } +} diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index a052ae3..3d8b8aa 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -30,6 +30,7 @@ import jalview.renderer.ScaleRenderer.ScaleMark; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; @@ -54,16 +55,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { final FeatureRenderer fr; - final SequenceRenderer sr; + final SequenceRenderer seqRdr; BufferedImage img; Graphics2D gg; - int imgWidth; - - int imgHeight; - AlignViewport av; boolean fastPaint = false; @@ -76,6 +73,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int cursorY = 0; + int charHeight = 0; + + int charWidth = 0; + + boolean fastpainting = false; + + AnnotationPanel annotations; + /** * Creates a new SeqCanvas object. * @@ -87,7 +92,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI this.av = ap.av; updateViewport(); fr = new FeatureRenderer(ap); - sr = new SequenceRenderer(av); + seqRdr = new SequenceRenderer(av); setLayout(new BorderLayout()); PaintRefresher.Register(this, av.getSequenceSetId()); setBackground(Color.white); @@ -97,7 +102,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI public SequenceRenderer getSequenceRenderer() { - return sr; + return seqRdr; } public FeatureRenderer getFeatureRenderer() @@ -105,8 +110,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return fr; } - int charHeight = 0, charWidth = 0; - private void updateViewport() { charHeight = av.getCharHeight(); @@ -266,7 +269,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } } - boolean fastpainting = false; /** * need to make this thread safe move alignment rendering in response to @@ -295,8 +297,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int transX = 0; int transY = 0; - gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth, - imgHeight, -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 { @@ -317,7 +320,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } else { - transY = imgHeight - ((vertical + 1) * charHeight); + transY = img.getHeight() - ((vertical + 1) * charHeight); } } else if (vertical < 0) @@ -351,73 +354,201 @@ public class SeqCanvas extends JComponent implements ViewportListenerI @Override public void paintComponent(Graphics g) { - updateViewport(); - BufferedImage lcimg = img; // take reference since other threads may null - // img and call later. super.paintComponent(g); - if (lcimg != null && (fastPaint + updateViewport(); + + ViewportRanges ranges = av.getRanges(); + + int width = getWidth(); + int height = getHeight(); + + width -= (width % charWidth); + height -= (height % charHeight); + + // selectImage is the selection group outline image + BufferedImage selectImage = drawSelectionGroup( + ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq()); + + if ((img != null) && (fastPaint || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g.getClipBounds().height))) { + BufferedImage lcimg = buildLocalImage(selectImage); g.drawImage(lcimg, 0, 0, this); fastPaint = false; - return; } + else if ((width > 0) && (height > 0)) + { + // img is a cached version of the last view we drew, if any + // if we have no img or the size has changed, make a new one + if (img == null || width != img.getWidth() + || height != img.getHeight()) + { + img = setupImage(); + if (img == null) + { + return; + } + gg = (Graphics2D) img.getGraphics(); + gg.setFont(av.getFont()); + } - // this draws the whole of the alignment - imgWidth = getWidth(); - imgHeight = getHeight(); + if (av.antiAlias) + { + gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } - imgWidth -= (imgWidth % charWidth); - imgHeight -= (imgHeight % charHeight); + gg.setColor(Color.white); + gg.fillRect(0, 0, img.getWidth(), img.getHeight()); - if ((imgWidth < 1) || (imgHeight < 1)) - { - return; + if (av.getWrapAlignment()) + { + drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes()); + } + else + { + drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq(), 0); + } + + // 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); } + } + + /** + * Draw an alignment panel for printing + * + * @param g1 + * Graphics object to draw with + * @param startRes + * start residue of print area + * @param endRes + * end residue of print area + * @param startSeq + * start sequence of print area + * @param endSeq + * end sequence of print area + */ + public void drawPanelForPrinting(Graphics g1, int startRes, int endRes, + int startSeq, int endSeq) + { + BufferedImage selectImage = drawSelectionGroup(startRes, endRes, + startSeq, endSeq); + drawPanel(g1, startRes, endRes, startSeq, endSeq, 0); + ((Graphics2D) g1).setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + g1.drawImage(selectImage, 0, 0, this); + } - if (lcimg == null || imgWidth != lcimg.getWidth() - || imgHeight != lcimg.getHeight()) + /** + * Draw a wrapped alignment panel for printing + * + * @param g + * Graphics object to draw with + * @param canvasWidth + * width of drawing area + * @param canvasHeight + * height of drawing area + * @param startRes + * start residue of print area + */ + public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth, + int canvasHeight, int startRes) + { + SequenceGroup group = av.getSelectionGroup(); + + drawWrappedPanel(g, canvasWidth, canvasHeight, startRes); + + if (group != null) { + BufferedImage selectImage = null; try { - lcimg = img = new BufferedImage(imgWidth, imgHeight, - BufferedImage.TYPE_INT_RGB); - gg = (Graphics2D) img.getGraphics(); - gg.setFont(av.getFont()); + selectImage = new BufferedImage(canvasWidth, canvasHeight, + BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works } catch (OutOfMemoryError er) { System.gc(); - System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er); - new OOMWarning("Creating alignment image for display", er); - - return; + System.err.println("Print image OutOfMemory Error.\n" + er); + new OOMWarning("Creating wrapped alignment image for printing", er); + } + if (selectImage != null) + { + Graphics2D g2 = selectImage.createGraphics(); + setupSelectionGroup(g2, selectImage); + drawWrappedSelection(g2, group, canvasWidth, canvasHeight, + startRes); + + g2.setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + g.drawImage(selectImage, 0, 0, this); + g2.dispose(); } } + } - if (av.antiAlias) + /* + * Make a local image by combining the cached image img + * with any selection + */ + private BufferedImage buildLocalImage(BufferedImage selectImage) + { + // clone the cached image + BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(), + img.getType()); + Graphics2D g2d = lcimg.createGraphics(); + g2d.drawImage(img, 0, 0, null); + + // overlay selection group on lcimg + if (selectImage != null) { - gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + g2d.drawImage(selectImage, 0, 0, this); } + g2d.dispose(); - gg.setColor(Color.white); - gg.fillRect(0, 0, imgWidth, imgHeight); + return lcimg; + } - ViewportRanges ranges = av.getRanges(); - if (av.getWrapAlignment()) + /* + * Set up a buffered image of the correct height and size for the sequence canvas + */ + private BufferedImage setupImage() + { + BufferedImage lcimg = null; + + int width = getWidth(); + int height = getHeight(); + + width -= (width % charWidth); + height -= (height % charHeight); + + if ((width < 1) || (height < 1)) { - drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes()); + return null; } - else + + try { - drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(), - ranges.getStartSeq(), ranges.getEndSeq(), 0); - } + lcimg = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works + } catch (OutOfMemoryError er) + { + System.gc(); + System.err.println( + "Group image OutOfMemory Redraw Error.\n" + er); + new OOMWarning("Creating alignment image for display", er); - g.drawImage(lcimg, 0, 0, this); + return null; + } + return lcimg; } /** @@ -486,7 +617,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI * @param startRes * DOCUMENT ME! */ - public void drawWrappedPanel(Graphics g, int canvasWidth, + private void drawWrappedPanel(Graphics g, int canvasWidth, int canvasHeight, int startRes) { updateViewport(); @@ -625,7 +756,61 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } } - AnnotationPanel annotations; + /* + * Draw a selection group over a wrapped alignment + */ + private void drawWrappedSelection(Graphics2D g, SequenceGroup group, + int canvasWidth, + int canvasHeight, int startRes) + { + // height gap above each panel + int hgap = charHeight; + if (av.getScaleAboveWrapped()) + { + hgap += charHeight; + } + + int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth; + int cHeight = av.getAlignment().getHeight() * charHeight; + + int startx = startRes; + int endx; + int ypos = hgap; // vertical offset + int maxwidth = av.getAlignment().getWidth(); + + if (av.hasHiddenColumns()) + { + maxwidth = av.getAlignment().getHiddenColumns() + .findColumnPosition(maxwidth); + } + + // chop the wrapped alignment extent up into panel-sized blocks and treat + // each block as if it were a block from an unwrapped alignment + while ((ypos <= canvasHeight) && (startx < maxwidth)) + { + // set end value to be start + width, or maxwidth, whichever is smaller + endx = startx + cWidth - 1; + + if (endx > maxwidth) + { + endx = maxwidth; + } + + g.translate(LABEL_WEST, 0); + + drawUnwrappedSelection(g, group, startx, endx, 0, + av.getAlignment().getHeight() - 1, + ypos); + + g.translate(-LABEL_WEST, 0); + + // update vertical offset + ypos += cHeight + getAnnotationHeight() + hgap; + + // update horizontal offset + startx += cWidth; + } + } int getAnnotationHeight() { @@ -642,24 +827,24 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return annotations.adjustPanelHeight(); } - /** - * DOCUMENT ME! + /* + * Draw an alignment panel for printing * * @param g1 - * DOCUMENT ME! + * Graphics object to draw with * @param startRes - * DOCUMENT ME! + * start residue of print area * @param endRes - * DOCUMENT ME! + * end residue of print area * @param startSeq - * DOCUMENT ME! + * start sequence of print area * @param endSeq - * DOCUMENT ME! + * end sequence of print area * @param offset - * DOCUMENT ME! + * vertical offset */ - public void drawPanel(Graphics g1, int startRes, int endRes, int startSeq, - int endSeq, int offset) + private void drawPanel(Graphics g1, int startRes, int endRes, + int startSeq, int endSeq, int offset) { updateViewport(); if (!av.hasHiddenColumns()) @@ -723,13 +908,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } - // int startRes, int endRes, int startSeq, int endSeq, int x, int y, - // int x1, int x2, int y1, int y2, int startx, int starty, private void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq, int offset) { g.setFont(av.getFont()); - sr.prepare(g, av.isRenderGaps()); + seqRdr.prepare(g, av.isRenderGaps()); SequenceI nextSeq; @@ -744,7 +927,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // empty continue; } - sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq), + seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq), startRes, endRes, offset + ((i - startSeq) * charHeight)); if (av.isShowSequenceFeatures()) @@ -763,10 +946,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { for (int r = 0; r < visibleResults.length; r += 2) { - sr.drawHighlightedText(nextSeq, visibleResults[r], - visibleResults[r + 1], - (visibleResults[r] - startRes) * charWidth, - offset + ((i - startSeq) * charHeight)); + seqRdr.drawHighlightedText(nextSeq, visibleResults[r], + visibleResults[r + 1], (visibleResults[r] - startRes) + * charWidth, offset + + ((i - startSeq) * charHeight)); } } } @@ -774,7 +957,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.cursorMode && cursorY == i && cursorX >= startRes && cursorX <= endRes) { - sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth, + seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth, offset + ((i - startSeq) * charHeight)); } } @@ -795,15 +978,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // /////////////////////////////////// // Now outline any areas if necessary // /////////////////////////////////// - SequenceGroup group = av.getSelectionGroup(); - int sx = -1; - int sy = -1; - int ex = -1; + SequenceGroup group = null; int groupIndex = -1; - int visWidth = (endRes - startRes + 1) * charWidth; - if ((group == null) && (av.getAlignment().getGroups().size() > 0)) + if (av.getAlignment().getGroups().size() > 0) { group = av.getAlignment().getGroups().get(0); groupIndex = 0; @@ -811,168 +990,309 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (group != null) { + g.setStroke(new BasicStroke()); + g.setColor(group.getOutlineColour()); + do { - int oldY = -1; - int i = 0; - boolean inGroup = false; - int top = -1; - int bottom = -1; + drawPartialGroupOutline(g, group, startRes, endRes, startSeq, + endSeq, offset); + + groupIndex++; + + g.setStroke(new BasicStroke()); + + if (groupIndex >= av.getAlignment().getGroups().size()) + { + break; + } + + group = av.getAlignment().getGroups().get(groupIndex); + + } while (groupIndex < av.getAlignment().getGroups().size()); + + } + + } + + + /* + * Draw the selection group as a separate image and overlay + */ + private BufferedImage drawSelectionGroup(int startRes, int endRes, + int startSeq, int endSeq) + { + // get a new image of the correct size + BufferedImage selectionImage = setupImage(); + + if (selectionImage == null) + { + return null; + } + + SequenceGroup group = av.getSelectionGroup(); + if (group == null) + { + // nothing to draw + return null; + } + + // set up drawing colour + Graphics2D g = (Graphics2D) selectionImage.getGraphics(); + + setupSelectionGroup(g, selectionImage); + + if (!av.getWrapAlignment()) + { + drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq, + 0); + } + else + { + drawWrappedSelection(g, group, getWidth(), getHeight(), + av.getRanges().getStartRes()); + } + + g.dispose(); + return selectionImage; + } + + /* + * Set up graphics for selection group + */ + private void setupSelectionGroup(Graphics2D g, + BufferedImage selectionImage) + { + // set background to transparent + g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); + g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight()); + + // set up foreground to draw red dashed line + g.setComposite(AlphaComposite.Src); + g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_ROUND, 3f, new float[] + { 5f, 3f }, 0f)); + g.setColor(Color.RED); + } + + /* + * Draw a selection group over an unwrapped alignment + * @param g graphics object to draw with + * @param group selection group + * @param startRes start residue of area to draw + * @param endRes end residue of area to draw + * @param startSeq start sequence of area to draw + * @param endSeq end sequence of area to draw + * @param offset vertical offset (used when called from wrapped alignment code) + */ + private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group, + int startRes, int endRes, int startSeq, int endSeq, int offset) + { + if (!av.hasHiddenColumns()) + { + drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq, + offset); + } + else + { + // package into blocks of visible columns + int screenY = 0; + int blockStart = startRes; + int blockEnd = endRes; + + for (int[] region : av.getAlignment().getHiddenColumns() + .getHiddenColumnsCopy()) + { + int hideStart = region[0]; + int hideEnd = region[1]; - for (i = startSeq; i <= endSeq; i++) + if (hideStart <= blockStart) { - sx = (group.getStartRes() - startRes) * charWidth; - sy = offset + ((i - startSeq) * charHeight); - ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - - 1; + blockStart += (hideEnd - hideStart) + 1; + continue; + } - if (sx + ex < 0 || sx > visWidth) - { - continue; - } + blockEnd = hideStart - 1; - if ((sx <= (endRes - startRes) * charWidth) - && group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i))) - { - if ((bottom == -1) && !group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i + 1))) - { - bottom = sy + charHeight; - } - - if (!inGroup) - { - if (((top == -1) && (i == 0)) || !group.getSequences(null) - .contains(av.getAlignment().getSequenceAt(i - 1))) - { - top = sy; - } - - oldY = sy; - inGroup = true; - - if (group == av.getSelectionGroup()) - { - g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_ROUND, 3f, new float[] - { 5f, 3f }, 0f)); - g.setColor(Color.RED); - } - else - { - g.setStroke(new BasicStroke()); - g.setColor(group.getOutlineColour()); - } - } - } - else + g.translate(screenY * charWidth, 0); + drawPartialGroupOutline(g, group, + blockStart, blockEnd, startSeq, endSeq, offset); + + g.translate(-screenY * charWidth, 0); + screenY += blockEnd - blockStart + 1; + blockStart = hideEnd + 1; + + if (screenY > (endRes - startRes)) + { + // already rendered last block + break; + } + } + + if (screenY <= (endRes - startRes)) + { + // remaining visible region to render + blockEnd = blockStart + (endRes - startRes) - screenY; + g.translate(screenY * charWidth, 0); + drawPartialGroupOutline(g, group, + blockStart, blockEnd, startSeq, endSeq, offset); + + g.translate(-screenY * charWidth, 0); + } + } + } + + /* + * Draw the selection group as a separate image and overlay + */ + private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group, + int startRes, int endRes, int startSeq, int endSeq, + int verticalOffset) + { + int visWidth = (endRes - startRes + 1) * charWidth; + + int oldY = -1; + int i = 0; + 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; + + // width of group in pixels + xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) + - 1; + + sy = verticalOffset + (i - startSeq) * charHeight; + + if (sx + xwidth < 0 || sx > visWidth) + { + continue; + } + + if ((sx <= (endRes - startRes) * charWidth) + && group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i))) + { + if ((bottom == -1) && !group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i + 1))) + { + bottom = sy + charHeight; + } + + if (!inGroup) + { + if (((top == -1) && (i == 0)) || !group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i - 1))) { - if (inGroup) - { - if (sx >= 0 && sx < visWidth) - { - g.drawLine(sx, oldY, sx, sy); - } - - if (sx + ex < visWidth) - { - g.drawLine(sx + ex, oldY, sx + ex, sy); - } - - if (sx < 0) - { - ex += sx; - sx = 0; - } - - if (sx + ex > visWidth) - { - ex = visWidth; - } - - else if (sx + ex >= (endRes - startRes + 1) * charWidth) - { - ex = (endRes - startRes + 1) * charWidth; - } - - if (top != -1) - { - g.drawLine(sx, top, sx + ex, top); - top = -1; - } - - if (bottom != -1) - { - g.drawLine(sx, bottom, sx + ex, bottom); - bottom = -1; - } - - inGroup = false; - } + top = sy; } - } + oldY = sy; + inGroup = true; + } + } + else + { if (inGroup) { - sy = offset + ((i - startSeq) * charHeight); + // if start position is visible, draw vertical line to left of + // group if (sx >= 0 && sx < visWidth) { g.drawLine(sx, oldY, sx, sy); } - if (sx + ex < visWidth) + // if end position is visible, draw vertical line to right of + // group + if (sx + xwidth < visWidth) { - g.drawLine(sx + ex, oldY, sx + ex, sy); + g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); } if (sx < 0) { - ex += sx; + xwidth += sx; sx = 0; } - if (sx + ex > visWidth) + // don't let width extend beyond current block, or group extent + // fixes JAL-2672 + if (sx + xwidth >= (endRes - startRes + 1) * charWidth) { - ex = visWidth; + xwidth = (endRes - startRes + 1) * charWidth - sx; } - else if (sx + ex >= (endRes - startRes + 1) * charWidth) - { - ex = (endRes - startRes + 1) * charWidth; - } - + + // draw horizontal line at top of group if (top != -1) { - g.drawLine(sx, top, sx + ex, top); + g.drawLine(sx, top, sx + xwidth, top); top = -1; } + // draw horizontal line at bottom of group if (bottom != -1) { - g.drawLine(sx, bottom - 1, sx + ex, bottom - 1); + g.drawLine(sx, bottom, sx + xwidth, bottom); bottom = -1; } inGroup = false; } + } + } - groupIndex++; + if (inGroup) + { + sy = verticalOffset + ((i - startSeq) * charHeight); + if (sx >= 0 && sx < visWidth) + { + g.drawLine(sx, oldY, sx, sy); + } - g.setStroke(new BasicStroke()); + if (sx + xwidth < visWidth) + { + g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); + } - if (groupIndex >= av.getAlignment().getGroups().size()) - { - break; - } + if (sx < 0) + { + xwidth += sx; + sx = 0; + } - group = av.getAlignment().getGroups().get(groupIndex); + if (sx + xwidth > visWidth) + { + xwidth = visWidth; + } + else if (sx + xwidth >= (endRes - startRes + 1) * charWidth) + { + xwidth = (endRes - startRes + 1) * charWidth; + } - } while (groupIndex < av.getAlignment().getGroups().size()); + if (top != -1) + { + g.drawLine(sx, top, sx + xwidth, top); + top = -1; + } - } + if (bottom != -1) + { + g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1); + bottom = -1; + } + inGroup = false; + } } - + /** * DOCUMENT ME! * @@ -993,7 +1313,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { String eventName = evt.getPropertyName(); - if (av.getWrapAlignment()) + if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED)) + { + fastPaint = true; + repaint(); + } + else if (av.getWrapAlignment()) { if (eventName.equals(ViewportRanges.STARTRES)) { diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 056a602..c3aa15f 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -1095,7 +1095,7 @@ public class SeqPanel extends JPanel } mouseDragging = true; - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { scrollThread.setEvent(evt); } @@ -1521,9 +1521,9 @@ public class SeqPanel extends JPanel oldSeq = 0; } - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { - scrollThread.running = false; + scrollThread.stopScrolling(); scrollThread = null; } } @@ -1542,7 +1542,7 @@ public class SeqPanel extends JPanel return; } - if (mouseDragging) + if (mouseDragging && scrollThread == null) { scrollThread = new ScrollThread(); } @@ -1710,43 +1710,53 @@ public class SeqPanel extends JPanel if (stretchGroup == null) { - // Only if left mouse button do we want to change group sizes + createStretchGroup(res, sequence); + } - // define a new group here - SequenceGroup sg = new SequenceGroup(); - sg.setStartRes(res); - sg.setEndRes(res); - sg.addSequence(sequence, false); - av.setSelectionGroup(sg); - stretchGroup = sg; + if (stretchGroup != null) + { + stretchGroup.addPropertyChangeListener(seqCanvas); + } - if (av.getConservationSelected()) - { - SliderPanel.setConservationSlider(ap, av.getResidueShading(), - ap.getViewName()); - } + seqCanvas.repaint(); + } - if (av.getAbovePIDThreshold()) - { - SliderPanel.setPIDSliderSource(ap, av.getResidueShading(), - ap.getViewName()); - } - // TODO: stretchGroup will always be not null. Is this a merge error ? - if ((stretchGroup != null) && (stretchGroup.getEndRes() == res)) - { - // Edit end res position of selected group - changeEndRes = true; - } - else if ((stretchGroup != null) - && (stretchGroup.getStartRes() == res)) - { - // Edit end res position of selected group - changeStartRes = true; - } - stretchGroup.getWidth(); + private void createStretchGroup(int res, SequenceI sequence) + { + // Only if left mouse button do we want to change group sizes + // define a new group here + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(res); + sg.setEndRes(res); + sg.addSequence(sequence, false); + av.setSelectionGroup(sg); + stretchGroup = sg; + + if (av.getConservationSelected()) + { + SliderPanel.setConservationSlider(ap, av.getResidueShading(), + ap.getViewName()); } - seqCanvas.repaint(); + if (av.getAbovePIDThreshold()) + { + SliderPanel.setPIDSliderSource(ap, av.getResidueShading(), + ap.getViewName()); + } + // TODO: stretchGroup will always be not null. Is this a merge error ? + // or is there a threading issue here? + if ((stretchGroup != null) && (stretchGroup.getEndRes() == res)) + { + // Edit end res position of selected group + changeEndRes = true; + } + else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res)) + { + // Edit end res position of selected group + changeStartRes = true; + } + stretchGroup.getWidth(); + } /** @@ -1795,6 +1805,9 @@ public class SeqPanel extends JPanel { return; } + + stretchGroup.removePropertyChangeListener(seqCanvas); + // always do this - annotation has own state // but defer colourscheme update until hidden sequences are passed in boolean vischange = stretchGroup.recalcConservation(true); @@ -1936,21 +1949,19 @@ public class SeqPanel extends JPanel mouseDragging = true; - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { scrollThread.setEvent(evt); } - - seqCanvas.repaint(); } void scrollCanvas(MouseEvent evt) { if (evt == null) { - if (scrollThread != null) + if ((scrollThread != null) && (scrollThread.isRunning())) { - scrollThread.running = false; + scrollThread.stopScrolling(); scrollThread = null; } mouseDragging = false; @@ -1973,7 +1984,7 @@ public class SeqPanel extends JPanel { MouseEvent evt; - boolean running = false; + private volatile boolean threadRunning = true; public ScrollThread() { @@ -1987,37 +1998,40 @@ public class SeqPanel extends JPanel public void stopScrolling() { - running = false; + threadRunning = false; + } + + public boolean isRunning() + { + return threadRunning; } @Override public void run() { - running = true; - - while (running) + while (threadRunning) { if (evt != null) { if (mouseDragging && (evt.getY() < 0) && (av.getRanges().getStartSeq() > 0)) { - running = av.getRanges().scrollUp(true); + av.getRanges().scrollUp(true); } if (mouseDragging && (evt.getY() >= getHeight()) && (av .getAlignment().getHeight() > av.getRanges().getEndSeq())) { - running = av.getRanges().scrollUp(false); + av.getRanges().scrollUp(false); } if (mouseDragging && (evt.getX() < 0)) { - running = av.getRanges().scrollRight(false); + av.getRanges().scrollRight(false); } else if (mouseDragging && (evt.getX() >= getWidth())) { - running = av.getRanges().scrollRight(true); + av.getRanges().scrollRight(true); } } diff --git a/src/jalview/gui/SequenceRenderer.java b/src/jalview/gui/SequenceRenderer.java index 4498f88..5be7f55 100755 --- a/src/jalview/gui/SequenceRenderer.java +++ b/src/jalview/gui/SequenceRenderer.java @@ -23,9 +23,8 @@ package jalview.gui; import jalview.api.AlignViewportI; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; -import jalview.renderer.ResidueShaderI; +import jalview.renderer.ResidueColourFinder; import jalview.renderer.seqfeatures.FeatureColourFinder; -import jalview.util.Comparison; import java.awt.Color; import java.awt.FontMetrics; @@ -41,17 +40,15 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer boolean renderGaps = true; - SequenceGroup currentSequenceGroup = null; - SequenceGroup[] allGroups = null; - Color resBoxColour; + // Color resBoxColour; Graphics graphics; boolean monospacedFont; - boolean forOverview = false; + ResidueColourFinder resColourFinder; /** * Creates a new SequenceRenderer object @@ -61,6 +58,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer public SequenceRenderer(AlignViewportI viewport) { this.av = viewport; + resColourFinder = new ResidueColourFinder(); } /** @@ -83,26 +81,6 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer this.renderGaps = renderGaps; } - protected Color getResidueBoxColour(SequenceI seq, int i) - { - // rate limiting step when rendering overview for lots of groups - allGroups = av.getAlignment().findAllGroups(seq); - - if (inCurrentSequenceGroup(i)) - { - if (currentSequenceGroup.getDisplayBoxes()) - { - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, i); - } - } - else if (av.getShowBoxes()) - { - getBoxColour(av.getResidueShading(), seq, i); - } - - return resBoxColour; - } - /** * Get the residue colour at the given sequence position - as determined by * the sequence group colour (if any), else the colour scheme, possibly @@ -117,39 +95,10 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer public Color getResidueColour(final SequenceI seq, int position, FeatureColourFinder finder) { - Color col = getResidueBoxColour(seq, position); - - if (finder != null) - { - col = finder.findFeatureColour(col, seq, position); - } - return col; - } - - /** - * DOCUMENT ME! - * - * @param shader - * DOCUMENT ME! - * @param seq - * DOCUMENT ME! - * @param i - * DOCUMENT ME! - */ - void getBoxColour(ResidueShaderI shader, SequenceI seq, int i) - { - if (shader.getColourScheme() != null) - { - resBoxColour = shader.findColour(seq.getCharAt(i), i, seq); - } - else if (forOverview && !Comparison.isGap(seq.getCharAt(i))) - { - resBoxColour = Color.lightGray; - } - else - { - resBoxColour = Color.white; - } + return resColourFinder.getResidueColour(av.getShowBoxes(), + av.getResidueShading(), + allGroups, seq, position, + finder); } /** @@ -208,6 +157,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer public synchronized void drawBoxes(SequenceI seq, int start, int end, int y1) { + Color resBoxColour = Color.white; + if (seq == null) { return; // fix for racecondition @@ -227,17 +178,22 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer if (i < length) { - if (inCurrentSequenceGroup(i)) + SequenceGroup currentSequenceGroup = resColourFinder + .getCurrentSequenceGroup( + allGroups, i); + if (currentSequenceGroup != null) { if (currentSequenceGroup.getDisplayBoxes()) { - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, + resBoxColour = resColourFinder.getBoxColour( + currentSequenceGroup.getGroupColourScheme(), seq, i); } } else if (av.getShowBoxes()) { - getBoxColour(av.getResidueShading(), seq, i); + resBoxColour = resColourFinder + .getBoxColour(av.getResidueShading(), seq, i); } } @@ -317,10 +273,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer boolean srep = av.isDisplayReferenceSeq(); boolean getboxColour = false; boolean isarep = av.getAlignment().getSeqrep() == seq; - boolean isgrep = currentSequenceGroup != null - ? currentSequenceGroup.getSeqrep() == seq - : false; - char sr_c; + Color resBoxColour = Color.white; + for (int i = start; i <= end; i++) { @@ -333,7 +287,10 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer continue; } - if (inCurrentSequenceGroup(i)) + SequenceGroup currentSequenceGroup = resColourFinder + .getCurrentSequenceGroup( + allGroups, i); + if (currentSequenceGroup != null) { if (!currentSequenceGroup.getDisplayText()) { @@ -344,7 +301,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer || currentSequenceGroup.getColourText()) { getboxColour = true; - getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, + resBoxColour = resColourFinder.getBoxColour( + currentSequenceGroup.getGroupColourScheme(), seq, i); if (currentSequenceGroup.getColourText()) @@ -366,6 +324,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer { graphics.setColor(currentSequenceGroup.textColour); } + boolean isgrep = currentSequenceGroup != null + ? currentSequenceGroup.getSeqrep() == seq : false; if (!isarep && !isgrep && currentSequenceGroup.getShowNonconserved()) // todo // optimize @@ -386,7 +346,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer if (av.getColourText()) { getboxColour = true; - getBoxColour(av.getResidueShading(), seq, i); + resBoxColour = resColourFinder + .getBoxColour(av.getResidueShading(), seq, i); if (av.getShowBoxes()) { @@ -402,7 +363,8 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer { if (!getboxColour) { - getBoxColour(av.getResidueShading(), seq, i); + resBoxColour = resColourFinder + .getBoxColour(av.getResidueShading(), seq, i); } if (resBoxColour.getRed() + resBoxColour.getBlue() @@ -473,35 +435,6 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer /** * DOCUMENT ME! * - * @param res - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - boolean inCurrentSequenceGroup(int res) - { - if (allGroups == null) - { - return false; - } - - for (int i = 0; i < allGroups.length; i++) - { - if ((allGroups[i].getStartRes() <= res) - && (allGroups[i].getEndRes() >= res)) - { - currentSequenceGroup = allGroups[i]; - - return true; - } - } - - return false; - } - - /** - * DOCUMENT ME! - * * @param seq * DOCUMENT ME! * @param start diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 633d2b8..1ca0802 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -95,11 +95,11 @@ public class GPreferences extends JPanel protected JCheckBox rightAlign = new JCheckBox(); - protected JComboBox fontSizeCB = new JComboBox(); + protected JComboBox fontSizeCB = new JComboBox<>(); - protected JComboBox fontStyleCB = new JComboBox(); + protected JComboBox fontStyleCB = new JComboBox<>(); - protected JComboBox fontNameCB = new JComboBox(); + protected JComboBox fontNameCB = new JComboBox<>(); protected JCheckBox showOccupancy = new JCheckBox(); @@ -111,15 +111,15 @@ public class GPreferences extends JPanel protected JCheckBox scaleProteinToCdna = new JCheckBox(); - protected JComboBox gapSymbolCB = new JComboBox(); + protected JComboBox gapSymbolCB = new JComboBox<>(); protected JCheckBox wrap = new JCheckBox(); - protected JComboBox sortby = new JComboBox(); + protected JComboBox sortby = new JComboBox<>(); - protected JComboBox sortAnnBy = new JComboBox(); + protected JComboBox sortAnnBy = new JComboBox<>(); - protected JComboBox sortAutocalc = new JComboBox(); + protected JComboBox sortAutocalc = new JComboBox<>(); protected JCheckBox startupCheckbox = new JCheckBox(); @@ -159,7 +159,7 @@ public class GPreferences extends JPanel protected JCheckBox addTempFactor = new JCheckBox(); - protected JComboBox structViewer = new JComboBox(); + protected JComboBox structViewer = new JComboBox<>(); protected JTextField chimeraPath = new JTextField(); @@ -176,9 +176,22 @@ public class GPreferences extends JPanel protected JPanel maxColour = new JPanel(); - protected JComboBox protColour = new JComboBox(); + protected JComboBox protColour = new JComboBox<>(); - protected JComboBox nucColour = new JComboBox(); + protected JComboBox nucColour = new JComboBox<>(); + + /* + * Overview tab components + */ + protected JPanel gapColour = new JPanel(); + + protected JPanel hiddenColour = new JPanel(); + + protected JCheckBox useLegacyGap; + + protected JCheckBox showHiddenAtStart; + + protected JLabel gapLabel; /* * Connections tab components @@ -216,7 +229,7 @@ public class GPreferences extends JPanel /* * Output tab components */ - protected JComboBox epsRendering = new JComboBox(); + protected JComboBox epsRendering = new JComboBox<>(); protected JLabel userIdWidthlabel = new JLabel(); @@ -294,6 +307,9 @@ public class GPreferences extends JPanel tabbedPane.add(initColoursTab(), MessageManager.getString("label.colours")); + tabbedPane.add(initOverviewTab(), + MessageManager.getString("label.overview")); + tabbedPane.add(initStructureTab(), MessageManager.getString("label.structure")); @@ -963,6 +979,151 @@ public class GPreferences extends JPanel } /** + * Initialises the Overview tabbed panel. + * + * @return + */ + private JPanel initOverviewTab() + { + JPanel overviewPanel = new JPanel(); + overviewPanel.setBorder(new TitledBorder( + MessageManager.getString("label.overview_settings"))); + + gapColour.setFont(LABEL_FONT); + // fixing the border colours stops apparent colour bleed from the panel + gapColour.setBorder( + BorderFactory.createEtchedBorder(Color.white, Color.lightGray)); + gapColour.setPreferredSize(new Dimension(40, 20)); + gapColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + gapColour_actionPerformed(gapColour); + } + }); + + hiddenColour.setFont(LABEL_FONT); + // fixing the border colours stops apparent colour bleed from the panel + hiddenColour.setBorder( + BorderFactory.createEtchedBorder(Color.white, Color.lightGray)); + hiddenColour.setPreferredSize(new Dimension(40, 20)); + hiddenColour.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent e) + { + hiddenColour_actionPerformed(hiddenColour); + } + }); + + useLegacyGap = new JCheckBox( + MessageManager.getString("label.ov_legacy_gap")); + useLegacyGap.setFont(LABEL_FONT); + useLegacyGap.setHorizontalAlignment(SwingConstants.LEFT); + useLegacyGap.setVerticalTextPosition(SwingConstants.TOP); + gapLabel = new JLabel( + MessageManager.getString("label.gap_colour")); + gapLabel.setFont(LABEL_FONT); + gapLabel.setHorizontalAlignment(SwingConstants.LEFT); + gapLabel.setVerticalTextPosition(SwingConstants.TOP); + showHiddenAtStart = new JCheckBox( + MessageManager.getString("label.ov_show_hide_default")); + showHiddenAtStart.setFont(LABEL_FONT); + showHiddenAtStart.setHorizontalAlignment(SwingConstants.LEFT); + showHiddenAtStart.setVerticalTextPosition(SwingConstants.TOP); + JLabel hiddenLabel = new JLabel( + MessageManager.getString("label.hidden_colour")); + hiddenLabel.setFont(LABEL_FONT); + hiddenLabel.setHorizontalAlignment(SwingConstants.LEFT); + hiddenLabel.setVerticalTextPosition(SwingConstants.TOP); + + useLegacyGap.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + useLegacyGaps_actionPerformed(e); + } + }); + + overviewPanel.setLayout(new GridBagLayout()); + GridBagConstraints c1 = new GridBagConstraints(); + + c1.fill = GridBagConstraints.HORIZONTAL; + c1.gridx = 0; + c1.gridy = 0; + c1.weightx = 1; + c1.ipady = 20; + c1.anchor = GridBagConstraints.FIRST_LINE_START; + overviewPanel.add(useLegacyGap, c1); + + GridBagConstraints c2 = new GridBagConstraints(); + c2.fill = GridBagConstraints.HORIZONTAL; + c2.gridx = 1; + c2.gridy = 0; + c2.insets = new Insets(0, 15, 0, 10); + overviewPanel.add(gapLabel, c2); + + GridBagConstraints c3 = new GridBagConstraints(); + c3.fill = GridBagConstraints.HORIZONTAL; + c3.gridx = 2; + c3.gridy = 0; + c3.insets = new Insets(0, 0, 0, 15); + overviewPanel.add(gapColour, c3); + + GridBagConstraints c4 = new GridBagConstraints(); + c4.fill = GridBagConstraints.HORIZONTAL; + c4.gridx = 0; + c4.gridy = 1; + c4.weightx = 1; + overviewPanel.add(showHiddenAtStart, c4); + + GridBagConstraints c5 = new GridBagConstraints(); + c5.fill = GridBagConstraints.HORIZONTAL; + c5.gridx = 1; + c5.gridy = 1; + c5.insets = new Insets(0, 15, 0, 10); + overviewPanel.add(hiddenLabel, c5); + + GridBagConstraints c6 = new GridBagConstraints(); + c6.fill = GridBagConstraints.HORIZONTAL; + c6.gridx = 2; + c6.gridy = 1; + c6.insets = new Insets(0, 0, 0, 15); + overviewPanel.add(hiddenColour, c6); + + JButton resetButton = new JButton( + MessageManager.getString("label.reset_to_defaults")); + + resetButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + resetOvDefaults_actionPerformed(e); + } + }); + + GridBagConstraints c7 = new GridBagConstraints(); + c7.fill = GridBagConstraints.NONE; + c7.gridx = 0; + c7.gridy = 2; + c7.insets = new Insets(10, 0, 0, 0); + c7.anchor = GridBagConstraints.WEST; + overviewPanel.add(resetButton, c7); + + // Add padding so the panel doesn't look ridiculous + JPanel spacePanel = new JPanel(); + overviewPanel.add(spacePanel, + new GridBagConstraints(0, 3, 1, 1, 1.0, 1.0, + GridBagConstraints.WEST, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 5), 0, 0)); + + return overviewPanel; + } + + /** * Initialises the Structure tabbed panel. * * @return @@ -1494,12 +1655,28 @@ public class GPreferences extends JPanel { } + protected void gapColour_actionPerformed(JPanel panel) + { + } + + protected void hiddenColour_actionPerformed(JPanel panel) + { + } + protected void showunconserved_actionPerformed(ActionEvent e) { // TODO Auto-generated method stub } + protected void useLegacyGaps_actionPerformed(ActionEvent e) + { + } + + protected void resetOvDefaults_actionPerformed(ActionEvent e) + { + } + /** * DOCUMENT ME! * diff --git a/src/jalview/jbgui/GStructureChooser.java b/src/jalview/jbgui/GStructureChooser.java index 3e6cd2a..7c4672a 100644 --- a/src/jalview/jbgui/GStructureChooser.java +++ b/src/jalview/jbgui/GStructureChooser.java @@ -165,6 +165,9 @@ public abstract class GStructureChooser extends JPanel protected static final String VIEWS_ENTER_ID = "VIEWS_ENTER_ID"; + /** + * 'cached' structure view + */ protected static final String VIEWS_LOCAL_PDB = "VIEWS_LOCAL_PDB"; protected JTable tbl_local_pdb = new JTable(); diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index 0f8cda6..1c50aab 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -22,22 +22,38 @@ package jalview.renderer; import jalview.api.AlignmentColsCollectionI; import jalview.api.AlignmentRowsCollectionI; +import jalview.api.RendererListenerI; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.viewmodel.OverviewDimensions; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; +import java.beans.PropertyChangeSupport; public class OverviewRenderer { - private FeatureColourFinder finder; + // transparency of hidden cols/seqs overlay + private final float TRANSPARENCY = 0.5f; + + private final Color HIDDEN_COLOUR = Color.DARK_GRAY.darker(); + + public static final String UPDATE = "OverviewUpdate"; + + private static final int MAX_PROGRESS = 100; - private jalview.api.SequenceRenderer sr; + private PropertyChangeSupport changeSupport = new PropertyChangeSupport( + this); + + private FeatureColourFinder finder; // image to render on private BufferedImage miniMe; @@ -48,17 +64,32 @@ public class OverviewRenderer // raw number of pixels to allocate to each row private float pixelsPerSeq; + // height in pixels of graph + private int graphHeight; + // flag to indicate whether to halt drawing private volatile boolean redraw = false; - public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer, - FeatureRenderer fr, OverviewDimensions od) + // reference to alignment, needed to get sequence groups + private AlignmentI al; + + private ResidueShaderI shader; + + private OverviewResColourFinder resColFinder; + + public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od, + AlignmentI alignment, + ResidueShaderI resshader, OverviewResColourFinder colFinder) { - sr = seqRenderer; finder = new FeatureColourFinder(fr); + resColFinder = colFinder; + + al = alignment; + shader = resshader; pixelsPerCol = od.getPixelsPerCol(); pixelsPerSeq = od.getPixelsPerSeq(); + graphHeight = od.getGraphHeight(); miniMe = new BufferedImage(od.getWidth(), od.getHeight(), BufferedImage.TYPE_INT_RGB); } @@ -78,6 +109,12 @@ public class OverviewRenderer int rgbcolor = Color.white.getRGB(); int seqIndex = 0; int pixelRow = 0; + int alignmentHeight = miniMe.getHeight() - graphHeight; + int totalPixels = miniMe.getWidth() * alignmentHeight; + + int lastRowUpdate = 0; + int lastUpdate = 0; + changeSupport.firePropertyChange(UPDATE, -1, 0); for (int alignmentRow : rows) { @@ -85,15 +122,17 @@ public class OverviewRenderer { break; } - + // get details of this alignment row - boolean hidden = rows.isHidden(alignmentRow); SequenceI seq = rows.getSequence(alignmentRow); + // rate limiting step when rendering overview for lots of groups + SequenceGroup[] allGroups = al.findAllGroups(seq); + // calculate where this row extends to in pixels int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1, miniMe.getHeight() - 1); - + int colIndex = 0; int pixelCol = 0; for (int alignmentCol : cols) @@ -102,21 +141,19 @@ public class OverviewRenderer { break; } - + // calculate where this column extends to in pixels int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1, miniMe.getWidth() - 1); - + // don't do expensive colour determination if we're not going to use it // NB this is important to avoid performance issues in the overview // panel if (pixelCol <= endCol) { - // determine the colour based on the sequence and column position - rgbcolor = getColumnColourFromSequence(seq, - hidden || cols.isHidden(alignmentCol), alignmentCol, - finder); - + rgbcolor = getColumnColourFromSequence(allGroups, seq, + alignmentCol, finder); + // fill in the appropriate number of pixels for (int row = pixelRow; row <= endRow; ++row) { @@ -126,35 +163,193 @@ public class OverviewRenderer } } + // store last update value + lastUpdate = sendProgressUpdate( + (pixelCol + 1) * (endRow - pixelRow), totalPixels, + lastRowUpdate, lastUpdate); + pixelCol = endCol + 1; } colIndex++; } - pixelRow = endRow + 1; + + if (pixelRow != endRow + 1) + { + // store row offset and last update value + lastRowUpdate = sendProgressUpdate(endRow + 1, alignmentHeight, 0, + lastUpdate); + lastUpdate = lastRowUpdate; + pixelRow = endRow + 1; + } seqIndex++; } + + overlayHiddenRegions(rows, cols); + // final update to progress bar if present + if (redraw) + { + sendProgressUpdate(pixelRow - 1, alignmentHeight, 0, 0); + } + else + { + sendProgressUpdate(alignmentHeight, miniMe.getHeight(), 0, 0); + } return miniMe; } /* + * Calculate progress update value and fire event + * @param rowOffset number of rows to offset calculation by + * @return new rowOffset - return value only to be used when at end of a row + */ + private int sendProgressUpdate(int position, int maximum, int rowOffset, + int lastUpdate) + { + int newUpdate = rowOffset + + Math.round(MAX_PROGRESS * ((float) position / maximum)); + if (newUpdate > lastUpdate) + { + changeSupport.firePropertyChange(UPDATE, rowOffset, newUpdate); + return newUpdate; + } + return newUpdate; + } + + /* * Find the colour of a sequence at a specified column position + * + * @param seq + * sequence to get colour for + * @param lastcol + * column position to get colour for + * @param fcfinder + * FeatureColourFinder to use + * @return colour of sequence at this position, as RGB */ - private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq, - boolean isHidden, int lastcol, FeatureColourFinder fcfinder) + private int getColumnColourFromSequence(SequenceGroup[] allGroups, + jalview.datamodel.SequenceI seq, + int lastcol, FeatureColourFinder fcfinder) { Color color = Color.white; if ((seq != null) && (seq.getLength() > lastcol)) { - color = sr.getResidueColour(seq, lastcol, fcfinder); + color = resColFinder.getResidueColour(true, shader, allGroups, seq, + lastcol, + fcfinder); } - if (isHidden) + return color.getRGB(); + } + + /** + * Overlay the hidden regions on the overview image + * + * @param rows + * collection of rows the overview is built over + * @param cols + * collection of columns the overview is built over + */ + private void overlayHiddenRegions(AlignmentRowsCollectionI rows, + AlignmentColsCollectionI cols) + { + if (cols.hasHidden() || rows.hasHidden()) { - color = color.darker().darker(); + BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(), + miniMe.getHeight()); + + Graphics2D g = (Graphics2D) miniMe.getGraphics(); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + TRANSPARENCY)); + g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null); } + } - return color.getRGB(); + /** + * Build a masking image of hidden columns and rows to be applied on top of + * the main overview image. + * + * @param rows + * collection of rows the overview is built over + * @param cols + * collection of columns the overview is built over + * @param width + * width of overview in pixels + * @param height + * height of overview in pixels + * @return BufferedImage containing mask of hidden regions + */ + private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows, + AlignmentColsCollectionI cols, int width, int height) + { + // new masking image + BufferedImage hiddenImage = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + + int colIndex = 0; + int pixelCol = 0; + + Color hidden = resColFinder.getHiddenColour(); + + Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics(); + + // set background to transparent + g2d.setComposite(AlphaComposite.Clear); + g2d.fillRect(0, 0, width, height); + + // set next colour to opaque + g2d.setComposite(AlphaComposite.Src); + + for (int alignmentCol : cols) + { + if (redraw) + { + break; + } + + // calculate where this column extends to in pixels + int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1, + hiddenImage.getWidth() - 1); + + if (pixelCol <= endCol) + { + // determine the colour based on the sequence and column position + if (cols.isHidden(alignmentCol)) + { + g2d.setColor(hidden); + g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height); + } + + pixelCol = endCol + 1; + } + colIndex++; + + } + + int seqIndex = 0; + int pixelRow = 0; + for (int alignmentRow : rows) + { + if (redraw) + { + break; + } + + // calculate where this row extends to in pixels + int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1, + miniMe.getHeight() - 1); + + // get details of this alignment row + if (rows.isHidden(alignmentRow)) + { + g2d.setColor(hidden); + g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1); + } + pixelRow = endRow + 1; + seqIndex++; + } + + return hiddenImage; } /** @@ -185,8 +380,10 @@ public class OverviewRenderer { if (redraw) { + changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, 0); break; } + if (alignmentCol >= annotations.length) { break; // no more annotations to draw here @@ -216,12 +413,22 @@ public class OverviewRenderer g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height); } + pixelCol = endCol + 1; colIndex++; } } + changeSupport.firePropertyChange(UPDATE, MAX_PROGRESS - 1, + MAX_PROGRESS); } + /** + * Allows redraw flag to be set + * + * @param b + * value to set redraw to: true = redraw is occurring, false = no + * redraw + */ public void setRedraw(boolean b) { synchronized (this) @@ -229,4 +436,14 @@ public class OverviewRenderer redraw = b; } } + + public void addPropertyChangeListener(RendererListenerI listener) + { + changeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(RendererListenerI listener) + { + changeSupport.removePropertyChangeListener(listener); + } } diff --git a/src/jalview/renderer/OverviewResColourFinder.java b/src/jalview/renderer/OverviewResColourFinder.java new file mode 100644 index 0000000..a497d92 --- /dev/null +++ b/src/jalview/renderer/OverviewResColourFinder.java @@ -0,0 +1,143 @@ +/* + * 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.renderer; + +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.util.Comparison; + +import java.awt.Color; + +public class OverviewResColourFinder extends ResidueColourFinder +{ + final Color GAP_COLOUR; // default colour to use at gaps + + final Color RESIDUE_COLOUR; // default colour to use at residues + + final Color HIDDEN_COLOUR; // colour for hidden regions + + boolean useLegacy = false; + + public static final Color OVERVIEW_DEFAULT_GAP = Color.lightGray; + + public static final Color OVERVIEW_DEFAULT_LEGACY_GAP = Color.white; + + public static final Color OVERVIEW_DEFAULT_HIDDEN = Color.darkGray + .darker(); + + /** + * Constructor without colour settings (used by applet) + */ + public OverviewResColourFinder() + { + this(false, OVERVIEW_DEFAULT_GAP, OVERVIEW_DEFAULT_HIDDEN); + } + + /** + * Constructor with colour settings + * + * @param useLegacyColouring + * whether to use legacy gap colouring (white gaps, grey residues) + * @param gapCol + * gap colour if not legacy + * @param hiddenCol + * hidden region colour (transparency applied by rendering code) + */ + public OverviewResColourFinder(boolean useLegacyColouring, Color gapCol, + Color hiddenCol) + { + if (useLegacyColouring) + { + GAP_COLOUR = Color.white; + RESIDUE_COLOUR = Color.lightGray; + HIDDEN_COLOUR = hiddenCol; + } + else + { + GAP_COLOUR = gapCol; + RESIDUE_COLOUR = Color.white; + HIDDEN_COLOUR = hiddenCol; + } + } + + @Override + public Color getBoxColour(ResidueShaderI shader, SequenceI seq, int i) + { + Color resBoxColour = RESIDUE_COLOUR; + char currentChar = seq.getCharAt(i); + + // In the overview window, gaps are coloured grey, unless the colour scheme + // specifies a gap colour, in which case gaps honour the colour scheme + // settings + if (shader.getColourScheme() != null) + { + if (Comparison.isGap(currentChar) + && (!shader.getColourScheme().hasGapColour())) + { + resBoxColour = GAP_COLOUR; + } + else + { + resBoxColour = shader.findColour(currentChar, i, seq); + } + } + else if (Comparison.isGap(currentChar)) + { + resBoxColour = GAP_COLOUR; + } + + return resBoxColour; + } + + /** + * {@inheritDoc} In the overview, the showBoxes setting is ignored, as the + * overview displays the colours regardless. + */ + @Override + protected Color getResidueBoxColour(boolean showBoxes, + ResidueShaderI shader, + SequenceGroup[] allGroups, SequenceI seq, int i) + { + ResidueShaderI currentShader; + SequenceGroup currentSequenceGroup = getCurrentSequenceGroup(allGroups, + i); + if (currentSequenceGroup != null) + { + currentShader = currentSequenceGroup.getGroupColourScheme(); + } + else + { + currentShader = shader; + } + + return getBoxColour(currentShader, seq, i); + } + + /** + * Supply hidden colour + * + * @return colour of hidden regions + */ + protected Color getHiddenColour() + { + return HIDDEN_COLOUR; + } +} diff --git a/src/jalview/renderer/ResidueColourFinder.java b/src/jalview/renderer/ResidueColourFinder.java new file mode 100644 index 0000000..2da7233 --- /dev/null +++ b/src/jalview/renderer/ResidueColourFinder.java @@ -0,0 +1,156 @@ +/* + * 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.renderer; + +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; + +import java.awt.Color; + +public class ResidueColourFinder +{ + public ResidueColourFinder() + { + } + + /** + * Get the colour of a residue in a sequence + * + * @param showBoxes + * true if the viewport's Show Boxes setting is true + * @param shader + * the viewport's colour scheme + * @param allGroups + * all the groups which seq participates in + * @param seq + * the sequence containing the residue + * @param position + * the position of the residue in the sequence + * @param finder + * FeatureColourFinder for the viewport + * @return colour of the residue + */ + public Color getResidueColour(boolean showBoxes, ResidueShaderI shader, + SequenceGroup[] allGroups, + final SequenceI seq, int position, FeatureColourFinder finder) + { + Color col = getResidueBoxColour(showBoxes, shader, allGroups, seq, + position); + + // if there's a FeatureColourFinder we might override the residue colour + // here with feature colouring + if (finder != null) + { + col = finder.findFeatureColour(col, seq, position); + } + return col; + } + + /** + * Get the residue colour without accounting for any features + * + * @param showBoxes + * true if the viewport's Show Boxes setting is true + * @param shader + * the viewport's colour scheme + * @param allGroups + * all the groups which seq participates in + * @param seq + * the sequence containing the residue + * @param i + * the position of the residue in the sequence + * @return + */ + protected Color getResidueBoxColour(boolean showBoxes, + ResidueShaderI shader, + SequenceGroup[] allGroups, + SequenceI seq, int i) + { + SequenceGroup currentSequenceGroup = getCurrentSequenceGroup(allGroups, + i); + if (currentSequenceGroup != null) + { + if (currentSequenceGroup.getDisplayBoxes()) + { + return getBoxColour(currentSequenceGroup.getGroupColourScheme(), + seq, i); + } + } + else if (showBoxes) + { + return getBoxColour(shader, seq, i); + } + + return Color.white; + } + + /** + * Search all the groups for a sequence to find the one which a given res + * falls into + * + * @param allGroups + * all the groups a sequence participates in + * @param res + * the residue to search for + * @return a sequence group for res, or null if no sequence group applies + */ + public SequenceGroup getCurrentSequenceGroup(SequenceGroup[] allGroups, + int res) + { + if (allGroups == null) + { + return null; + } + + for (int i = 0; i < allGroups.length; i++) + { + if ((allGroups[i].getStartRes() <= res) + && (allGroups[i].getEndRes() >= res)) + { + return (allGroups[i]); + } + } + + return null; + } + + /** + * DOCUMENT ME! + * + * @param shader + * the viewport's colour scheme + * @param seq + * the sequence containing the residue + * @param i + * the position of the residue in the sequence + */ + public Color getBoxColour(ResidueShaderI shader, SequenceI seq, int i) + { + Color resBoxColour = Color.white; + if (shader.getColourScheme() != null) + { + resBoxColour = shader.findColour(seq.getCharAt(i), i, seq); + } + return resBoxColour; + } + +} diff --git a/src/jalview/renderer/ResidueShader.java b/src/jalview/renderer/ResidueShader.java index 7e4f211..c031170 100644 --- a/src/jalview/renderer/ResidueShader.java +++ b/src/jalview/renderer/ResidueShader.java @@ -235,6 +235,11 @@ public class ResidueShader implements ResidueShaderI @Override public Color findColour(char symbol, int position, SequenceI seq) { + if (colourScheme == null) + { + return Color.white; // Colour is 'None' + } + /* * get 'base' colour */ @@ -243,14 +248,16 @@ public class ResidueShader implements ResidueShaderI : profile.getModalResidue(); float pid = profile == null ? 0f : profile.getPercentageIdentity(ignoreGaps); - Color colour = colourScheme == null ? Color.white - : colourScheme.findColour(symbol, position, seq, modalResidue, - pid); + Color colour = colourScheme.findColour(symbol, position, seq, + modalResidue, pid); /* * apply PID threshold and consensus fading if in force */ - colour = adjustColour(symbol, position, colour); + if (!Comparison.isGap(symbol)) + { + colour = adjustColour(symbol, position, colour); + } return colour; } diff --git a/src/jalview/schemes/ColourSchemeI.java b/src/jalview/schemes/ColourSchemeI.java index f16ca21..d70b4e2 100755 --- a/src/jalview/schemes/ColourSchemeI.java +++ b/src/jalview/schemes/ColourSchemeI.java @@ -98,4 +98,11 @@ public interface ColourSchemeI * @return */ boolean isSimple(); + + /** + * Answers true if the colour scheme has a colour specified for gaps. + * + * @return + */ + boolean hasGapColour(); } diff --git a/src/jalview/schemes/ResidueColourScheme.java b/src/jalview/schemes/ResidueColourScheme.java index 34a5daa..2f7a5e0 100755 --- a/src/jalview/schemes/ResidueColourScheme.java +++ b/src/jalview/schemes/ResidueColourScheme.java @@ -208,4 +208,14 @@ public abstract class ResidueColourScheme implements ColourSchemeI { return true; } + + /** + * Default method returns false. Override this to return true in colour + * schemes that have a colour associated with gap residues. + */ + @Override + public boolean hasGapColour() + { + return false; + } } diff --git a/src/jalview/schemes/UserColourScheme.java b/src/jalview/schemes/UserColourScheme.java index b86250a..bf62e45 100755 --- a/src/jalview/schemes/UserColourScheme.java +++ b/src/jalview/schemes/UserColourScheme.java @@ -287,7 +287,7 @@ public class UserColourScheme extends ResidueColourScheme /* * step 1: build a map from colours to the symbol(s) that have the colour */ - Map> colours = new HashMap>(); + Map> colours = new HashMap<>(); for (char symbol = 'A'; symbol <= 'Z'; symbol++) { @@ -320,7 +320,7 @@ public class UserColourScheme extends ResidueColourScheme /* * step 2: make a list of { A,G,R=12f9d6 } residues/colour specs */ - List residueColours = new ArrayList(); + List residueColours = new ArrayList<>(); for (Entry> cols : colours.entrySet()) { boolean first = true; @@ -350,4 +350,10 @@ public class UserColourScheme extends ResidueColourScheme Collections.sort(residueColours); return StringUtils.listToDelimitedString(residueColours, ";"); } + + @Override + public boolean hasGapColour() + { + return (findColour(' ') != null); + } } diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index fbfa486..b973f45 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -340,7 +340,9 @@ public class StructureSelectionManager * - one or more sequences to be mapped to pdbFile * @param targetChainIds * - optional chain specification for mapping each sequence to pdb - * (may be nill, individual elements may be nill) + * (may be nill, individual elements may be nill) - JBPNote: JAL-2693 + * - this should be List>, empty lists indicate no + * predefined mappings * @param pdbFile * - structure data resource * @param sourceType diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index 9f4cea0..2528286 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -142,7 +142,6 @@ public abstract class AAStructureBindingModel * @param ssm * @param pdbentry * @param sequenceIs - * @param chains * @param protocol */ public AAStructureBindingModel(StructureSelectionManager ssm, @@ -154,8 +153,60 @@ public abstract class AAStructureBindingModel this.nucleotide = Comparison.isNucleotide(sequenceIs); this.pdbEntry = pdbentry; this.protocol = protocol; + resolveChains(); } + private boolean resolveChains() + { + /** + * final count of chain mappings discovered + */ + int chainmaps = 0; + // JBPNote: JAL-2693 - this should be a list of chain mappings per + // [pdbentry][sequence] + String[][] newchains = new String[pdbEntry.length][]; + int pe = 0; + for (PDBEntry pdb : pdbEntry) + { + SequenceI[] seqsForPdb = sequence[pe]; + if (seqsForPdb != null) + { + newchains[pe] = new String[seqsForPdb.length]; + int se = 0; + for (SequenceI asq : seqsForPdb) + { + String chain = (chains != null && chains[pe] != null) + ? chains[pe][se] + : null; + SequenceI sq = (asq.getDatasetSequence() == null) ? asq + : asq.getDatasetSequence(); + if (sq.getAllPDBEntries() != null) + { + for (PDBEntry pdbentry : sq.getAllPDBEntries()) + { + if (pdb.getFile() != null && pdbentry.getFile() != null + && pdb.getFile().equals(pdbentry.getFile())) + { + String chaincode = pdbentry.getChainCode(); + if (chaincode != null && chaincode.length() > 0) + { + chain = chaincode; + chainmaps++; + break; + } + } + } + } + newchains[pe][se] = chain; + se++; + } + pe++; + } + } + + chains = newchains; + return chainmaps > 0; + } public StructureSelectionManager getSsm() { return ssm; diff --git a/src/jalview/ws/dbsources/EmblXmlSource.java b/src/jalview/ws/dbsources/EmblXmlSource.java index 21b226b..ca90d60 100644 --- a/src/jalview/ws/dbsources/EmblXmlSource.java +++ b/src/jalview/ws/dbsources/EmblXmlSource.java @@ -91,7 +91,7 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy File reply) throws Exception { EmblFile efile = null; - List seqs = new ArrayList(); + List seqs = new ArrayList<>(); if (reply != null && reply.exists()) { @@ -107,7 +107,7 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy * EmbFile reads something like (e.g.) this ungrammatical phrase * Entry: display type is either not supported or entry is not found. */ - List peptides = new ArrayList(); + List peptides = new ArrayList<>(); if (efile != null && efile.getEntries() != null) { for (EmblEntry entry : efile.getEntries()) diff --git a/src/jalview/ws/seqfetcher/ASequenceFetcher.java b/src/jalview/ws/seqfetcher/ASequenceFetcher.java index 977f9da..9284f82 100644 --- a/src/jalview/ws/seqfetcher/ASequenceFetcher.java +++ b/src/jalview/ws/seqfetcher/ASequenceFetcher.java @@ -126,15 +126,16 @@ public class ASequenceFetcher */ public SequenceI[] getSequences(List refs, boolean dna) { - Vector rseqs = new Vector(); - Hashtable> queries = new Hashtable>(); + Vector rseqs = new Vector<>(); + Hashtable> queries = new Hashtable<>(); for (DBRefEntry ref : refs) { - if (!queries.containsKey(ref.getSource())) + String canonical = DBRefUtils.getCanonicalName(ref.getSource()); + if (!queries.containsKey(canonical)) { - queries.put(ref.getSource(), new ArrayList()); + queries.put(canonical, new ArrayList()); } - List qset = queries.get(ref.getSource()); + List qset = queries.get(canonical); if (!qset.contains(ref.getAccessionId())) { qset.add(ref.getAccessionId()); @@ -154,14 +155,14 @@ public class ASequenceFetcher continue; } - Stack queriesLeft = new Stack(); + Stack queriesLeft = new Stack<>(); queriesLeft.addAll(query); List proxies = getSourceProxy(db); for (DbSourceProxy fetcher : proxies) { - List queriesMade = new ArrayList(); - HashSet queriesFound = new HashSet(); + List queriesMade = new ArrayList<>(); + HashSet queriesFound = new HashSet<>(); try { if (fetcher.isDnaCoding() != dna) @@ -306,13 +307,13 @@ public class ASequenceFetcher Map dblist = fetchableDbs.get(db); if (dblist == null) { - return new ArrayList(); + return new ArrayList<>(); } /* * sort so that primary sources precede secondary */ - List dbs = new ArrayList(dblist.values()); + List dbs = new ArrayList<>(dblist.values()); Collections.sort(dbs, proxyComparator); return dbs; } @@ -357,14 +358,14 @@ public class ASequenceFetcher { if (fetchableDbs == null) { - fetchableDbs = new Hashtable>(); + fetchableDbs = new Hashtable<>(); } Map slist = fetchableDbs .get(proxy.getDbSource()); if (slist == null) { fetchableDbs.put(proxy.getDbSource(), - slist = new Hashtable()); + slist = new Hashtable<>()); } slist.put(proxy.getDbName(), proxy); } @@ -391,7 +392,7 @@ public class ASequenceFetcher return null; } String[] sources = null; - Vector src = new Vector(); + Vector src = new Vector<>(); Enumeration dbs = fetchableDbs.keys(); while (dbs.hasMoreElements()) { @@ -413,7 +414,7 @@ public class ASequenceFetcher public DbSourceProxy[] getDbSourceProxyInstances(Class class1) { - List prlist = new ArrayList(); + List prlist = new ArrayList<>(); for (String fetchable : getSupportedDb()) { for (DbSourceProxy pr : getSourceProxy(fetchable)) diff --git a/test/jalview/gui/SequenceRendererTest.java b/test/jalview/gui/SequenceRendererTest.java deleted file mode 100644 index c80b830..0000000 --- a/test/jalview/gui/SequenceRendererTest.java +++ /dev/null @@ -1,82 +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 static org.testng.AssertJUnit.assertEquals; - -import jalview.datamodel.Alignment; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.Sequence; -import jalview.datamodel.SequenceI; -import jalview.schemes.ZappoColourScheme; - -import java.awt.Color; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -public class SequenceRendererTest -{ - - @BeforeClass(alwaysRun = true) - public void setUpJvOptionPane() - { - JvOptionPane.setInteractiveMode(false); - JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); - } - - @Test(groups = { "Functional" }) - public void testGetResidueBoxColour_zappo() - { - SequenceI seq = new Sequence("name", "MATVLGSPRAPAFF"); // FER1_MAIZE... - AlignmentI al = new Alignment(new SequenceI[] { seq }); - final AlignViewport av = new AlignViewport(al); - SequenceRenderer sr = new SequenceRenderer(av); - av.setGlobalColourScheme(new ZappoColourScheme()); - - // @see ResidueProperties.zappo - assertEquals(Color.pink, sr.getResidueBoxColour(seq, 0)); // M - assertEquals(Color.green, sr.getResidueBoxColour(seq, 2)); // T - assertEquals(Color.magenta, sr.getResidueBoxColour(seq, 5)); // G - assertEquals(Color.orange, sr.getResidueBoxColour(seq, 12)); // F - } - - @Test(groups = { "Functional" }) - public void testGetResidueBoxColour_none() - { - SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF"); - AlignmentI al = new Alignment(new SequenceI[] { seq }); - final AlignViewport av = new AlignViewport(al); - SequenceRenderer sr = new SequenceRenderer(av); - - assertEquals(Color.white, sr.getResidueBoxColour(seq, 0)); - assertEquals(Color.white, sr.getResidueBoxColour(seq, 2)); - - // set for overview - sr.forOverview = true; - assertEquals(Color.lightGray, sr.getResidueBoxColour(seq, 0)); - assertEquals(Color.white, sr.getResidueBoxColour(seq, 2)); - } - - // TODO more tests for getResidueBoxColour covering groups, feature rendering, - // gaps, overview... - -} diff --git a/test/jalview/io/CrossRef2xmlTests.java b/test/jalview/io/CrossRef2xmlTests.java index ec5855f..0715857 100644 --- a/test/jalview/io/CrossRef2xmlTests.java +++ b/test/jalview/io/CrossRef2xmlTests.java @@ -31,10 +31,12 @@ import jalview.gui.CrossRefAction; import jalview.gui.Desktop; import jalview.gui.Jalview2XML; import jalview.gui.JvOptionPane; +import jalview.util.DBRefUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -65,10 +67,13 @@ public class CrossRef2xmlTests extends Jalview2xmlBase public void testRetrieveAndShowCrossref() throws Exception { - List failedDBRetr = new ArrayList(); - List failedXrefMenuItems = new ArrayList(); - List failedProjectRecoveries = new ArrayList(); - + List failedDBRetr = new ArrayList<>(); + List failedXrefMenuItems = new ArrayList<>(); + List failedProjectRecoveries = new ArrayList<>(); + // only search for ensembl or Uniprot crossrefs + List limit=Arrays.asList(new String[] { + DBRefUtils.getCanonicalName("ENSEMBL"), + DBRefUtils.getCanonicalName("Uniprot")}); // for every set of db queries // retrieve db query // verify presence of expected xrefs @@ -85,12 +90,11 @@ public class CrossRef2xmlTests extends Jalview2xmlBase // . codonframes // // - HashMap dbtoviewBit = new HashMap(); - List keyseq = new ArrayList(); - HashMap savedProjects = new HashMap(); + HashMap dbtoviewBit = new HashMap<>(); + List keyseq = new ArrayList<>(); + HashMap savedProjects = new HashMap<>(); - for (String[] did : new String[][] { { "ENSEMBL", "ENSG00000157764" }, - { "UNIPROT", "P01731" } }) + for (String[] did : new String[][] { { "UNIPROT", "P00338" } }) { // pass counters - 0 - first pass, 1 means retrieve project rather than // perform action @@ -163,7 +167,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase ptypes = (seqs == null || seqs.length == 0) ? null : new CrossRef( seqs, dataset).findXrefSourcesForSequences(dna); - + filterDbRefs(ptypes, limit); + // start of pass2: retrieve each cross-ref for fetched or restored // project. do // first cross ref and recover crossref loop @@ -176,7 +181,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase // build next key so we an retrieve all views String nextxref = first + " -> " + db + "{" + firstcr_ap + "}"; // perform crossref action, or retrieve stored project - List cra_views = new ArrayList(); + List cra_views = new ArrayList<>(); CrossRefAction cra = null; if (pass2 == 0) @@ -237,7 +242,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase } } - HashMap> xrptypes = new HashMap>(); + HashMap> xrptypes = new HashMap<>(); // first save/verify views. for (AlignmentViewPanel avp : cra_views) { @@ -274,7 +279,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}"; for (String xrefdb : xrptypes.get(nextxref)) { - List cra_views2 = new ArrayList(); + List cra_views2 = new ArrayList<>(); int q = 0; String nextnextxref = nextxref + " -> " + xrefdb + "{" + q + "}"; @@ -437,6 +442,25 @@ public class CrossRef2xmlTests extends Jalview2xmlBase } } + private void filterDbRefs(List ptypes, List limit) + { + if (limit != null) + { + int p = 0; + while (ptypes.size() > p) + { + if (!limit.contains(ptypes.get(p))) + { + ptypes.remove(p); + } + else + { + p++; + } + } + } + } + /** * wrapper to trap known defect for AH002001 testcase * @@ -480,7 +504,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase private void assertType(boolean expectProtein, AlignmentViewPanel alignmentViewPanel, String message) { - List nonType = new ArrayList(); + List nonType = new ArrayList<>(); for (SequenceI sq : alignmentViewPanel.getAlignViewport() .getAlignment().getSequences()) { diff --git a/test/jalview/renderer/OverviewResColourFinderTest.java b/test/jalview/renderer/OverviewResColourFinderTest.java new file mode 100644 index 0000000..1687516 --- /dev/null +++ b/test/jalview/renderer/OverviewResColourFinderTest.java @@ -0,0 +1,299 @@ +/* + * 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.renderer; + +import static org.testng.AssertJUnit.assertEquals; + +import jalview.bin.Cache; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignViewport; +import jalview.gui.JvOptionPane; +import jalview.schemes.ColourSchemeI; +import jalview.schemes.UserColourScheme; +import jalview.schemes.ZappoColourScheme; + +import java.awt.Color; +import java.util.ArrayList; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class OverviewResColourFinderTest +{ + + @BeforeClass(alwaysRun = true) + public void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + } + + @Test(groups = { "Functional" }) + public void testGetResidueBoxColour_none() + { + SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF"); + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new OverviewResColourFinder(); + + // gaps are grey, residues white + assertEquals(Color.white, rcf.getResidueColour(true, + av.getResidueShading(), + null, seq, 0, null)); + assertEquals(Color.lightGray, rcf + .getResidueColour(true, av.getResidueShading(), null, seq, 2, + null)); + + // unaffected by showBoxes setting + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 0, null)); + assertEquals(Color.lightGray, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 2, null)); + } + + @Test(groups = { "Functional" }) + public void testGetResidueBoxColour_zappo() + { + SequenceI seq = new Sequence("name", "MAT--GSPRAPAFF"); // FER1_MAIZE... + a + // gap + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new OverviewResColourFinder(); + av.setGlobalColourScheme(new ZappoColourScheme()); + + // @see ResidueProperties.zappo + assertEquals(Color.pink, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 0, null)); // M + assertEquals(Color.green, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 2, null)); // T + assertEquals(Color.magenta, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 5, null)); // G + assertEquals(Color.orange, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 12, null)); // F + + // gap colour not specified so gaps are lightGray + assertEquals(Color.lightGray, rcf + .getResidueColour(true, av.getResidueShading(), null, seq, 3, + null)); + + // unaffected by showBoxes setting + assertEquals(Color.pink, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 0, null)); // M + assertEquals(Color.green, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 2, null)); // T + assertEquals(Color.magenta, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 5, null)); // G + assertEquals(Color.orange, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 12, null)); // F + + // gap colour not specified so gaps are lightGray + assertEquals(Color.lightGray, rcf + .getResidueColour(false, av.getResidueShading(), null, seq, 3, + null)); + + } + + @Test(groups = { "Functional" }) + public void testGetResidueBoxColour_userdef() + { + SequenceI seq = new Sequence("name", "MAT--GSPRAPAFF"); // FER1_MAIZE... + a + // gap + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new OverviewResColourFinder(); + + Color[] newColours = new Color[24]; + for (int i = 0; i < 24; i++) + { + newColours[i] = null; + } + + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour not specified so gaps are lightGray + assertEquals(Color.lightGray, rcf + .getResidueColour(true, av.getResidueShading(), null, seq, 3, + null)); + + newColours[23] = Color.pink; + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour specified as pink + assertEquals(Color.pink, rcf.getResidueColour(true, + av.getResidueShading(), + null, seq, 3, null)); + + // unaffected by showBoxes setting + // gap colour not specified so gaps are lightGray + newColours[23] = null; + assertEquals(Color.lightGray, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 3, null)); + + newColours[23] = Color.pink; + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour specified as pink + assertEquals(Color.pink, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 3, null)); + } + + @Test + public void testGetResidueBoxColour_group() + { + SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF"); + AlignmentI al = new Alignment(new SequenceI[] { seq }); + + ColourSchemeI cs = new ZappoColourScheme(); + ArrayList seqlist = new ArrayList<>(); + seqlist.add(seq); + SequenceGroup sg = new SequenceGroup(seqlist, "testgroup", cs, true, + true, true, 5, 9); + al.addGroup(sg); + SequenceGroup[] groups = new SequenceGroup[1]; + groups[0] = sg; + + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new OverviewResColourFinder(); + + // G in group specified as magenta in Zappo + assertEquals(Color.magenta, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 7, null)); + + // Residue outside group coloured white + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 0, null)); + + // Gap outside group coloured lightgray + assertEquals(Color.lightGray, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 2, null)); + + // use legacy colouring + rcf = new OverviewResColourFinder(true, Color.blue, Color.red); + + // G in group specified as magenta in Zappo + assertEquals(Color.magenta, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 7, null)); + + // Residue outside group coloured lightgray + assertEquals(Color.lightGray, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 0, null)); + + // Gap outside group coloured white + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 2, null)); + + // use new colouring + rcf = new OverviewResColourFinder(false, Color.blue, Color.red); + + // G in group specified as magenta in Zappo + assertEquals(Color.magenta, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 7, null)); + + // Residue outside group coloured white + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 0, null)); + + // Gap outside group coloured blue + assertEquals(Color.blue, rcf.getResidueColour(false, + av.getResidueShading(), groups, seq, 2, null)); + } + + @Test + public void testGetBoxColour() + { + SequenceI seq = new Sequence("name", "MAT--GSPRAPAFF"); // FER1_MAIZE... + a + // gap + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + + // non-legacy colouring + ResidueColourFinder rcf = new OverviewResColourFinder(); + ResidueShaderI shader = new ResidueShader(); + + // residues white + Color c = rcf.getBoxColour(shader, seq, 0); + assertEquals(Color.white, c); + + // gaps gap colour + c = rcf.getBoxColour(shader, seq, 3); + assertEquals( + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP, + c); + + // legacy colouring set explicitly via constructor + rcf = new OverviewResColourFinder(true, Color.blue, Color.red); + shader = new ResidueShader(); + + // residues light gray + c = rcf.getBoxColour(shader, seq, 0); + assertEquals(Color.lightGray, c); + + // gaps white + c = rcf.getBoxColour(shader, seq, 3); + assertEquals(Color.white, c); + + // legacy colouring off + rcf = new OverviewResColourFinder(); + shader = new ResidueShader(); + + // residues white + c = rcf.getBoxColour(shader, seq, 0); + assertEquals(Color.white, c); + + // gaps gap colour + c = rcf.getBoxColour(shader, seq, 3); + assertEquals( + jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP, + c); + + // non legacy colouring with colour scheme + rcf = new OverviewResColourFinder(false, Color.blue, Color.red); + shader = new ResidueShader(new ZappoColourScheme()); + + // M residue pink + c = rcf.getBoxColour(shader, seq, 0); + assertEquals(Color.pink, c); + + // gaps blue + c = rcf.getBoxColour(shader, seq, 3); + assertEquals(Color.blue, c); + + // legacy colouring with colour scheme + rcf = new OverviewResColourFinder(true, Color.blue, Color.red); + + // M residue pink + c = rcf.getBoxColour(shader, seq, 0); + assertEquals(Color.pink, c); + + // gaps white + c = rcf.getBoxColour(shader, seq, 3); + assertEquals(Color.white, c); + } +} diff --git a/test/jalview/renderer/ResidueColourFinderTest.java b/test/jalview/renderer/ResidueColourFinderTest.java new file mode 100644 index 0000000..81fb2c0 --- /dev/null +++ b/test/jalview/renderer/ResidueColourFinderTest.java @@ -0,0 +1,151 @@ +/* + * 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.renderer; + +import static org.testng.AssertJUnit.assertEquals; + +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignViewport; +import jalview.gui.JvOptionPane; +import jalview.schemes.UserColourScheme; +import jalview.schemes.ZappoColourScheme; + +import java.awt.Color; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class ResidueColourFinderTest +{ + + @BeforeClass(alwaysRun = true) + public void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + } + + @Test(groups = { "Functional" }) + public void testGetResidueColour_zappo() + { + SequenceI seq = new Sequence("name", "MATVLGSPRAPAFF"); // FER1_MAIZE... + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new ResidueColourFinder(); + av.setGlobalColourScheme(new ZappoColourScheme()); + + // @see ResidueProperties.zappo + assertEquals(Color.pink, + rcf.getResidueColour(true, av.getResidueShading(), null, seq, 0, + null)); // M + assertEquals(Color.green, + rcf.getResidueColour(true, av.getResidueShading(), null, seq, 2, + null)); // T + assertEquals(Color.magenta, + rcf.getResidueColour(true, av.getResidueShading(), null, seq, 5, + null)); // G + assertEquals(Color.orange, + rcf.getResidueColour(true, av.getResidueShading(), null, seq, + 12, + null)); // F + + // everything is white if showBoxes is false + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 0, null)); // M + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 2, null)); // T + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 5, null)); // G + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 12, null)); // F + } + + @Test(groups = { "Functional" }) + public void testGetResidueColour_none() + { + SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF"); + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new ResidueColourFinder(); + + assertEquals(Color.white, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 0, null)); + assertEquals(Color.white, + rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 2, null)); + + // no change if showBoxes is false + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 0, null)); + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 2, null)); + } + + @Test(groups = { "Functional" }) + public void testGetResidueColour_userdef() + { + SequenceI seq = new Sequence("name", "MAT--GSPRAPAFF"); // FER1_MAIZE... + a + // gap + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + ResidueColourFinder rcf = new ResidueColourFinder(); + + Color[] newColours = new Color[24]; + for (int i = 0; i < 24; i++) + { + newColours[i] = null; + } + + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour not specified so gap colour is null + // this is consistent with previous behaviour, but may not be correct? + assertEquals(null, rcf.getResidueColour(true, av.getResidueShading(), + null, seq, 3, null)); + + newColours[23] = Color.pink; + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour specified as pink + assertEquals(Color.pink, rcf.getResidueColour(true, + av.getResidueShading(), + null, seq, 3, null)); + + // everything is white if showBoxes is false + newColours[23] = null; + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), + null, seq, 3, null)); + + newColours[23] = Color.pink; + av.setGlobalColourScheme(new UserColourScheme(newColours)); + + // gap colour specified as pink + assertEquals(Color.white, rcf.getResidueColour(false, + av.getResidueShading(), null, seq, 3, null)); + } + + // TODO more tests for getResidueColour covering groups, feature rendering... +} diff --git a/test/jalview/renderer/ResidueShaderTest.java b/test/jalview/renderer/ResidueShaderTest.java index 76fd9b4..eba5f59 100644 --- a/test/jalview/renderer/ResidueShaderTest.java +++ b/test/jalview/renderer/ResidueShaderTest.java @@ -8,9 +8,15 @@ import jalview.analysis.Conservation; import jalview.datamodel.Profile; import jalview.datamodel.ProfileI; import jalview.datamodel.Profiles; +import jalview.datamodel.ProfilesI; +import jalview.datamodel.ResidueCount; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; +import jalview.schemes.ColourSchemeI; import jalview.schemes.PIDColourScheme; +import jalview.schemes.ResidueProperties; +import jalview.schemes.UserColourScheme; +import jalview.schemes.ZappoColourScheme; import java.awt.Color; import java.util.Collections; @@ -163,4 +169,165 @@ public class ResidueShaderTest assertEquals(Color.WHITE, ccs.applyConservation(colour, 12)); } + @Test(groups = "Functional") + public void testFindColour_gapColour() + { + /* + * normally, a gap is coloured white + */ + ResidueShader rs = new ResidueShader(new ZappoColourScheme()); + assertEquals(Color.white, rs.findColour(' ', 7, null)); + + /* + * a User Colour Scheme may specify a bespoke gap colour + */ + Color[] colours = new Color[ResidueProperties.maxProteinIndex + 1]; + colours[5] = Color.blue; // Q colour + colours[23] = Color.red; // gap colour + ColourSchemeI cs = new UserColourScheme(colours); + rs = new ResidueShader(cs); + + assertEquals(Color.red, rs.findColour(' ', 7, null)); + assertEquals(Color.blue, rs.findColour('Q', 7, null)); + + /* + * stub Conservation to return a given consensus string + */ + final String consSequence = "0123456789+*-"; + Conservation cons = new Conservation(null, + Collections. emptyList(), 0, 0) + { + @Override + public SequenceI getConsSequence() + { + return new Sequence("seq", consSequence); + } + }; + rs.setConservation(cons); + + /* + * with 0% threshold, there should be no fading + */ + rs.setConservationInc(0); + assertEquals(Color.red, rs.findColour(' ', 7, null)); + assertEquals(Color.blue, rs.findColour('Q', 7, null)); + + /* + * with 40% threshold, 'fade factor' is + * (11-score)/10 * 40/20 = (11-score)/5 + * so position 7, score 7 fades 80% of the way to white (255, 255, 255) + */ + rs.setConservationInc(40); + + /* + * gap colour is unchanged for Conservation + */ + assertEquals(Color.red, rs.findColour(' ', 7, null)); + assertEquals(Color.red, rs.findColour('-', 7, null)); + assertEquals(Color.red, rs.findColour('.', 7, null)); + + /* + * residue colour is faded 80% of the way from + * blue(0, 0, 255) to white(255, 255, 255) + * making (204, 204, 255) + */ + assertEquals(new Color(204, 204, 255), rs.findColour('Q', 7, null)); + + /* + * turn off By Conservation, apply Above Identity Threshold + * providing a stub Consensus that has modal residue "Q" with pid 60% + */ + rs.setConservationApplied(false); + ProfilesI consensus = getStubConsensus("Q", 60f); + rs.setConsensus(consensus); + + // with consensus pid (60) above threshold (50), colours are unchanged + rs.setThreshold(50, false); + assertEquals(Color.blue, rs.findColour('Q', 7, null)); + assertEquals(Color.red, rs.findColour('-', 7, null)); + + // with consensus pid (60) below threshold (70), + // residue colour becomes white, gap colour is unchanged + rs.setThreshold(70, false); + assertEquals(Color.white, rs.findColour('Q', 7, null)); + assertEquals(Color.red, rs.findColour('-', 7, null)); + } + + /** + * @param modalResidue + * @param pid + * @return + */ + protected ProfilesI getStubConsensus(final String modalResidue, + final float pid) + { + ProfilesI consensus = new ProfilesI() { + + @Override + public ProfileI get(int i) + { + return new ProfileI() { + @Override + public void setCounts(ResidueCount residueCounts) + { + } + + @Override + public float getPercentageIdentity(boolean ignoreGaps) + { + return pid; + } + + @Override + public ResidueCount getCounts() + { + return null; + } + + @Override + public int getHeight() + { + return 0; + } + + @Override + public int getGapped() + { + return 0; + } + + @Override + public int getMaxCount() + { + return 0; + } + + @Override + public String getModalResidue() + { + return modalResidue; + } + + @Override + public int getNonGapped() + { + return 0; + }}; + } + + @Override + public int getStartColumn() + { + return 0; + } + + @Override + public int getEndColumn() + { + return 0; + } + + }; + return consensus; + } } diff --git a/test/jalview/structures/models/AAStructureBindingModelTest.java b/test/jalview/structures/models/AAStructureBindingModelTest.java index c125ef6..aea3687 100644 --- a/test/jalview/structures/models/AAStructureBindingModelTest.java +++ b/test/jalview/structures/models/AAStructureBindingModelTest.java @@ -20,8 +20,8 @@ */ package jalview.structures.models; +import static org.testng.Assert.assertFalse; import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import jalview.api.AlignmentViewPanel; @@ -36,6 +36,7 @@ import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import jalview.gui.JvOptionPane; import jalview.io.DataSourceType; +import jalview.io.FileFormats; import jalview.schemes.ColourSchemeI; import jalview.structure.AtomSpec; import jalview.structure.StructureMappingcommandSet; @@ -43,6 +44,7 @@ import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel.SuperposeData; import java.awt.Color; +import java.io.IOException; import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -98,6 +100,150 @@ public class AAStructureBindingModelTest + "ATOM 33 CA ALA A 10 26.790 4.320 3.172 1.00 11.98 C \n" + "ATOM 39 CA AVAL A 12 24.424 3.853 6.106 0.50 13.83 C \n"; + /** + * Multichain PDB with identical sequences imported - Binding should correctly + * recover chain mappings for each derived sequence + */ + private static final String PDB_4_MC = "HEADER HYDROLASE 09-SEP-09 3A6S \n" + + "ATOM 2 CA MET A 1 15.366 -11.648 24.854 1.00 32.05 C \n" + + "ATOM 10 CA LYS A 2 16.846 -9.215 22.340 1.00 25.68 C \n" + + "ATOM 19 CA LYS A 3 15.412 -6.335 20.343 1.00 19.42 C \n" + + "ATOM 28 CA LEU A 4 15.629 -5.719 16.616 1.00 15.49 C \n" + + "ATOM 36 CA GLN A 5 14.412 -2.295 15.567 1.00 12.19 C \n" + + "ATOM 1030 CA MET B 1 18.869 -7.572 3.432 1.00 31.52 C \n" + + "ATOM 1038 CA LYS B 2 19.182 -10.025 6.313 1.00 26.41 C \n" + + "ATOM 1047 CA LYS B 3 17.107 -12.963 7.534 1.00 19.71 C \n" + + "ATOM 1056 CA LEU B 4 16.142 -13.579 11.164 1.00 14.81 C \n" + + "ATOM 1064 CA GLN B 5 14.648 -17.005 11.785 1.00 13.38 C \n"; + + // TODO: JAL-2227 - import mmCIF PISA assembly & identify master/copy chains + + @Test(groups= {"Functional"}) + public void testImportPDBPreservesChainMappings() throws IOException + { + AlignmentI importedAl = new jalview.io.FormatAdapter().readFile( + PDB_4_MC, DataSourceType.PASTE, FileFormats.getInstance() + .forName(jalview.io.FileFormat.PDB.toString())); + // ideally, we would match on the actual data for the 'File' handle for + // pasted files, + // see JAL-623 - pasting is still not correctly handled... + PDBEntry importedPDB = new PDBEntry("3A6S", "", Type.PDB, + "Paste"); + AAStructureBindingModel binder = new AAStructureBindingModel( + new StructureSelectionManager(), new PDBEntry[] + { importedPDB }, + new SequenceI[][] + { importedAl.getSequencesArray() }, null) + { + + @Override + public void updateColours(Object source) + { + // TODO Auto-generated method stub + + } + + @Override + public void releaseReferences(Object svl) + { + // TODO Auto-generated method stub + + } + + @Override + public String[] getStructureFiles() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String superposeStructures(AlignmentI[] alignments, + int[] structureIndices, HiddenColumns[] hiddenCols) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setJalviewColourScheme(ColourSchemeI cs) + { + // TODO Auto-generated method stub + + } + + @Override + public void setBackgroundColour(Color col) + { + // TODO Auto-generated method stub + + } + + @Override + public void highlightAtoms(List atoms) + { + // TODO Auto-generated method stub + + } + + @Override + public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment) + { + // TODO Auto-generated method stub + return null; + } + + @Override + protected StructureMappingcommandSet[] getColourBySequenceCommands( + String[] files, SequenceRenderer sr, AlignmentViewPanel avp) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getChainNames() + { + // TODO Auto-generated method stub + return null; + } + + @Override + protected void colourBySequence( + StructureMappingcommandSet[] colourBySequenceCommands) + { + // TODO Auto-generated method stub + + } + + @Override + public void colourByCharge() + { + // TODO Auto-generated method stub + + } + + @Override + public void colourByChain() + { + // TODO Auto-generated method stub + + } + }; + String[][] chains = binder.getChains(); + assertFalse(chains == null || chains[0] == null, + "No chains discovered by binding"); + assertEquals(2, chains[0].length); + assertEquals("A", chains[0][0]); + assertEquals("B", chains[0][1]); + } AAStructureBindingModel testee; AlignmentI al = null;