Merge branch 'bug/JAL-2691_nomultimermapping' into documentation/JAL-2675_release2102b1
authorJim Procter <jprocter@issues.jalview.org>
Sun, 3 Sep 2017 17:06:24 +0000 (18:06 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Sun, 3 Sep 2017 17:06:24 +0000 (18:06 +0100)
39 files changed:
.ant-targets-build.xml [new file with mode: 0644]
help/help.jhm
help/helpTOC.xml
help/html/features/overview.html
help/html/features/preferences.html
help/html/releases.html
help/html/whatsNew.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/AlignmentColsCollectionI.java
src/jalview/api/AlignmentRowsCollectionI.java
src/jalview/appletgui/OverviewCanvas.java
src/jalview/appletgui/SequenceRenderer.java
src/jalview/datamodel/AllColsCollection.java
src/jalview/datamodel/AllRowsCollection.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/VisibleColsCollection.java
src/jalview/datamodel/VisibleRowsCollection.java
src/jalview/datamodel/xdb/embl/EmblEntry.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/Preferences.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceRenderer.java
src/jalview/jbgui/GPreferences.java
src/jalview/renderer/OverviewRenderer.java
src/jalview/renderer/OverviewResColourFinder.java [new file with mode: 0644]
src/jalview/renderer/ResidueColourFinder.java [new file with mode: 0644]
src/jalview/schemes/ColourSchemeI.java
src/jalview/schemes/ResidueColourScheme.java
src/jalview/schemes/UserColourScheme.java
src/jalview/ws/dbsources/EmblXmlSource.java
src/jalview/ws/seqfetcher/ASequenceFetcher.java
test/jalview/gui/SequenceRendererTest.java [deleted file]
test/jalview/io/CrossRef2xmlTests.java
test/jalview/renderer/OverviewResColourFinderTest.java [new file with mode: 0644]
test/jalview/renderer/ResidueColourFinderTest.java [new file with mode: 0644]

diff --git a/.ant-targets-build.xml b/.ant-targets-build.xml
new file mode 100644 (file)
index 0000000..15432a1
--- /dev/null
@@ -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
index c12436f..8e4961f 100755 (executable)
    <mapID target="printSetupIcon" url="icons/setup.png" />
    
    <mapID target="overview" url="html/features/overview.html" />
+   <mapID target="overviewprefs" url="html/features/preferences.html#overview" />
 </map>
index f3311a7..4636ea3 100755 (executable)
@@ -28,6 +28,7 @@
                                <tocitem text="Groovy Features Counter example" target="groovy.featurescounter"/>
                                <tocitem text="Custom Colourschemes in Groovy" target="groovy.colours"/>
                                <tocitem text="Omit hidden regions in Overview" target="overview"/>
+                               <tocitem text="Show gaps as grey in overview" target="overviewprefs"/>
                                <tocitem text="identifers.org for URL Links" target="linksprefs" />
                                <tocitem text="New features in Split Frame View" target="splitframe.mirrorfonts" />
                </tocitem>
index ac5aecb..2a656a3 100755 (executable)
     <strong>View&#8594;Overview window</strong>
   </p>
   <p>Select the overview window menu item to get a navigable image
-    of the whole alignment.</p>
+    of the whole alignment. By default, gaps are shown as dark grey, in
+    the overview (since 2.10.2b1).</p>
   <p>The red box indicates the currently viewed region of the
     alignment, this may be moved by clicking and dragging with the
     mouse.</p>
   <p>Click anywhere else in the overview to centre the view on that
     position</p>
-    <p></p>
+  <p></p>
   <p>
     <strong>Hiding hidden regions in the overview</strong>
   </p>
     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 <strong>Right-clicking</strong>
-    (or CMD-Clicking) to open the overview's popup menu.<br />
-    <br /> <em>The option to include/exclude hidden regions in the
+    (or CMD-Clicking) to open the overview's popup menu.<br /> <br />
+    <em>The option to include/exclude hidden regions in the
       overview was introduced in Jalview 2.10.2</em>.
   
   <p>
     <img src="overview.gif" width="407" height="137">
   </p>
   <p>&nbsp;</p>
+  <em>Previous to 2.10.2b1, gaps were white, and sequences shown as
+    dark grey unless coloured.</em>
 </body>
 </html>
index acd7ba6..b29b66b 100755 (executable)
@@ -41,6 +41,9 @@
         Preferences</a> tab allows you to configure default colourschemes
       for a new alignment window.
     </li>
+    <li>The <a href="#overview"><strong>&quot;Overview&quot;</strong>
+        Preferences</a> tab configures defaults for the overview window.
+    </li>
     <li>The <a href="#structure"><strong>&quot;Structure&quot;</strong>
         Preferences</a> tab allows you to configure options for obtaining
       and displaying structure information.
       by Annotation...</a> is selected from the alignment window's colours
     menu.
   </p>
+   <p>
+    <a name="overview"><strong>&quot;Overview&quot;
+        Preferences tab</strong>
+  </p>
+  <p>
+    <em>Use legacy gap colouring (gaps are white)</em> - when enabled,
+    Jalview's overview shows gaps as white, and sequences with no
+    colourscheme applied as grey.
+  </p>
+  <p>
+    <em>Show Hidden regions when opening overview</em> - default setting
+    for inclusion of hidden regions.
+  </p>
+  <p>
+    <em>Gap Colour</em> - When legacy gap colouring is not enabled, this
+    configures the default colour for gaps in the overview.
+  </p>
+  <p>
+    <em>Hidden Colour</em> - colour used to highlight regions in the
+    overview that are hidden in the alignment.
+  </p>
+  <p>
+    <em>Gap Colour</em> - The default colour scheme for a new alignment
+    window. If the chosen option is &quot;User Defined&quot; then the
+    last User Defined Colour loaded or saved via the User Defined
+    Colours panel will be loaded.
+  </p>
   <p>
     <a name="structure"><strong>&quot;Structure&quot;
         Preferences tab</strong></a><em> added in Jalview 2.8.2</em>
index c331ae1..a0efc10 100755 (executable)
@@ -77,11 +77,38 @@ li:before {
       <td><div align="left">
           <em></em>
           <ul>
+            <li>
+              <!-- JAL-2588 -->Show gaps in overview window by colouring
+              in grey (sequences used to be coloured grey, and gaps were
+              white)
+            </li>
+            <li><!-- JAL-2588,JAL-2527 -->Overview tab in Jalview Desktop Preferences</li>
           </ul>
         </div></td>
       <td><div align="left">
           <em></em>
           <ul>
+            <li>
+              <!-- JAL-2664 -->Overview window redraws every hidden
+              column region row by row
+            </li>
+            <li>
+              <!-- JAL-2681 -->duplicate protein sequences shown after
+              retrieving Ensembl crossrefs for sequences from Uniprot
+            </li>
+            <li>
+              <!-- JAL-2603 -->Overview window throws NPE if show boxes
+              format setting is unticked
+            </li>
+            <li>
+              <!-- JAL-2610 -->Groups are coloured wrongly in overview
+              if group has show boxes format setting unticked
+            </li>
+            <li>
+              <!-- JAL-2672,JAL-2665 -->Redraw problems when autoscrolling whilst
+              dragging current selection group to include sequences and
+              columns not currently displayed
+            </li>
           </ul>
         </div></td>
     
index c002eb8..169e7fe 100755 (executable)
@@ -30,6 +30,9 @@
     This is patch release for 2.10.2. See the <a
       href="releases.html#Jalview.2.10.2b1">release notes</a>.
   </p>
+  <ul>
+    <li>Gaps are now rendered as dark grey in overview window</li>
+  </ul>
   <p>
     <strong>What's new in Jalview 2.10.2 ?</strong>
   </p>
index 162f10f..28fbfd5 100644 (file)
@@ -1311,3 +1311,12 @@ 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
\ No newline at end of file
index 8385142..b5b5704 100644 (file)
@@ -1311,3 +1311,12 @@ 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 = <html>Utilizar el color heredado de huecos<br>(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
\ No newline at end of file
index 3a3f660..06b1675 100644 (file)
@@ -30,4 +30,11 @@ public interface AlignmentColsCollectionI extends Iterable<Integer>
    * @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();
 }
index 24affc5..fbf6ceb 100644 (file)
@@ -34,6 +34,13 @@ public interface AlignmentRowsCollectionI extends Iterable<Integer>
   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
index a0466d3..9597b44 100644 (file)
@@ -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());
 
index a382c60..e55f2a5 100755 (executable)
@@ -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;
index 4ac4f83..e216c46 100644 (file)
@@ -50,4 +50,10 @@ public class AllColsCollection implements AlignmentColsCollectionI
   {
     return !hidden.isVisible(c);
   }
+
+  @Override
+  public boolean hasHidden()
+  {
+    return hidden.hasHiddenColumns();
+  }
 }
index 9a33094..10c47f0 100644 (file)
@@ -59,4 +59,10 @@ public class AllRowsCollection implements AlignmentRowsCollectionI
   {
     return alignment.getSequenceAtAbsoluteIndex(seq);
   }
+
+  @Override
+  public boolean hasHidden()
+  {
+    return (hidden.getSize() > 0);
+  }
 }
index 6964b53..e2f15e1 100755 (executable)
@@ -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());
     }
   }
 
index bc32fac..e9437a7 100644 (file)
@@ -51,4 +51,10 @@ public class VisibleColsCollection implements AlignmentColsCollectionI
     return false;
   }
 
+  @Override
+  public boolean hasHidden()
+  {
+    return false;
+  }
+
 }
index ee0557f..fd7cf47 100644 (file)
@@ -56,4 +56,10 @@ public class VisibleRowsCollection implements AlignmentRowsCollectionI
   {
     return alignment.getSequenceAtAbsoluteIndex(seq);
   }
+
+  @Override
+  public boolean hasHidden()
+  {
+    return false;
+  }
 }
index fe3f6ef..2de100b 100644 (file)
@@ -279,7 +279,7 @@ public class EmblEntry
     String translation = null;
     String proteinName = "";
     String proteinId = null;
-    Map<String, String> vals = new Hashtable<String, String>();
+    Map<String, String> vals = new Hashtable<>();
 
     /*
      * codon_start 1/2/3 in EMBL corresponds to phase 0/1/2 in CDS
index 922e481..fe216c0 100644 (file)
@@ -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)
index 27f9c3f..ed1497a 100644 (file)
@@ -21,7 +21,9 @@
 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.Color;
@@ -57,6 +59,8 @@ public class OverviewCanvas extends JComponent
 
   private AlignViewportI av;
 
+  private OverviewResColourFinder cf;
+
   public OverviewCanvas(OverviewDimensions overviewDims,
           AlignViewportI alignvp)
   {
@@ -65,8 +69,14 @@ public class OverviewCanvas extends JComponent
 
     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,
+            Preferences.OVERVIEW_DEFAULT_GAP);
+    Color hiddenCol = Cache.getDefaultColour(Preferences.HIDDEN_COLOUR,
+            Preferences.OVERVIEW_DEFAULT_HIDDEN);
+    cf = new OverviewResColourFinder(useLegacy, gapCol, hiddenCol);
   }
 
   /**
@@ -126,7 +136,8 @@ 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);
     miniMe = or.draw(od.getRows(av.getAlignment()),
             od.getColumns(av.getAlignment()));
 
index 28de801..3c94f26 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.bin.Cache;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.viewmodel.OverviewDimensions;
@@ -79,9 +80,20 @@ 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());
 
@@ -158,7 +170,6 @@ public class OverviewPanel extends JPanel
           }
         }
         else
-        // if (!av.getWrapAlignment())
         {
           if (!od.isPositionInBox(evt.getX(), evt.getY()))
           {
index c3c9239..7ceb04e 100755 (executable)
@@ -109,10 +109,25 @@ 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;
 
+  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();
+
   /**
    * Holds name and link separated with | character. Sequence ID must be
    * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
@@ -155,7 +170,7 @@ public class Preferences extends GPreferences
      * .properties file as '|' separated strings
      */
 
-    groupURLLinks = new ArrayList<String>();
+    groupURLLinks = new ArrayList<>();
   }
 
   JInternalFrame frame;
@@ -309,6 +324,20 @@ public class Preferences extends GPreferences
             Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red));
 
     /*
+     * Set overview panel defaults
+     */
+    gapColour.setBackground(
+            Cache.getDefaultColour(GAP_COLOUR, OVERVIEW_DEFAULT_GAP));
+    hiddenColour.setBackground(
+            Cache.getDefaultColour(HIDDEN_COLOUR,
+                    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 +660,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 +1074,60 @@ 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(OVERVIEW_DEFAULT_LEGACY_GAP);
+    }
+    else
+    {
+      gapColour.setBackground(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(OVERVIEW_DEFAULT_HIDDEN);
+  }
+
+  @Override
   protected void userIdWidth_actionPerformed()
   {
     try
index a052ae3..3d8b8aa 100755 (executable)
@@ -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))
       {
index 056a602..328dfdc 100644 (file)
@@ -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);
@@ -1940,8 +1953,6 @@ public class SeqPanel extends JPanel
     {
       scrollThread.setEvent(evt);
     }
-
-    seqCanvas.repaint();
   }
 
   void scrollCanvas(MouseEvent evt)
index 4498f88..5be7f55 100755 (executable)
@@ -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
index 633d2b8..1ca0802 100755 (executable)
@@ -95,11 +95,11 @@ public class GPreferences extends JPanel
 
   protected JCheckBox rightAlign = new JCheckBox();
 
-  protected JComboBox<String> fontSizeCB = new JComboBox<String>();
+  protected JComboBox<String> fontSizeCB = new JComboBox<>();
 
-  protected JComboBox<String> fontStyleCB = new JComboBox<String>();
+  protected JComboBox<String> fontStyleCB = new JComboBox<>();
 
-  protected JComboBox<String> fontNameCB = new JComboBox<String>();
+  protected JComboBox<String> fontNameCB = new JComboBox<>();
 
   protected JCheckBox showOccupancy = new JCheckBox();
 
@@ -111,15 +111,15 @@ public class GPreferences extends JPanel
 
   protected JCheckBox scaleProteinToCdna = new JCheckBox();
 
-  protected JComboBox<String> gapSymbolCB = new JComboBox<String>();
+  protected JComboBox<String> gapSymbolCB = new JComboBox<>();
 
   protected JCheckBox wrap = new JCheckBox();
 
-  protected JComboBox<String> sortby = new JComboBox<String>();
+  protected JComboBox<String> sortby = new JComboBox<>();
 
-  protected JComboBox<String> sortAnnBy = new JComboBox<String>();
+  protected JComboBox<String> sortAnnBy = new JComboBox<>();
 
-  protected JComboBox<String> sortAutocalc = new JComboBox<String>();
+  protected JComboBox<String> sortAutocalc = new JComboBox<>();
 
   protected JCheckBox startupCheckbox = new JCheckBox();
 
@@ -159,7 +159,7 @@ public class GPreferences extends JPanel
 
   protected JCheckBox addTempFactor = new JCheckBox();
 
-  protected JComboBox<String> structViewer = new JComboBox<String>();
+  protected JComboBox<String> structViewer = new JComboBox<>();
 
   protected JTextField chimeraPath = new JTextField();
 
@@ -176,9 +176,22 @@ public class GPreferences extends JPanel
 
   protected JPanel maxColour = new JPanel();
 
-  protected JComboBox<String> protColour = new JComboBox<String>();
+  protected JComboBox<String> protColour = new JComboBox<>();
 
-  protected JComboBox<String> nucColour = new JComboBox<String>();
+  protected JComboBox<String> 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<Object> epsRendering = new JComboBox<Object>();
+  protected JComboBox<Object> 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!
    * 
index 0f8cda6..992a1b2 100644 (file)
@@ -23,21 +23,28 @@ package jalview.renderer;
 import jalview.api.AlignmentColsCollectionI;
 import jalview.api.AlignmentRowsCollectionI;
 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;
 
 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();
 
-  private jalview.api.SequenceRenderer sr;
+  private FeatureColourFinder finder;
 
   // image to render on
   private BufferedImage miniMe;
@@ -51,11 +58,22 @@ public class OverviewRenderer
   // 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();
@@ -85,15 +103,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 +122,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)
           {
@@ -125,7 +143,7 @@ public class OverviewRenderer
               miniMe.setRGB(col, row, rgbcolor);
             }
           }
-
+    
           pixelCol = endCol + 1;
         }
         colIndex++;
@@ -133,28 +151,146 @@ public class OverviewRenderer
       pixelRow = endRow + 1;
       seqIndex++;
     }
+
+    overlayHiddenRegions(rows, cols);
     return miniMe;
   }
 
-  /*
+  /**
    * 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;
   }
 
   /**
@@ -222,6 +358,13 @@ public class OverviewRenderer
     }
   }
 
+  /**
+   * 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)
diff --git a/src/jalview/renderer/OverviewResColourFinder.java b/src/jalview/renderer/OverviewResColourFinder.java
new file mode 100644 (file)
index 0000000..b606ba3
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.renderer;
+
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.Preferences;
+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;
+
+  /**
+   * Constructor without colour settings (used by applet)
+   */
+  public OverviewResColourFinder()
+  {
+    this(false, Preferences.OVERVIEW_DEFAULT_GAP,
+            Preferences.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 (file)
index 0000000..2da7233
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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;
+  }
+
+}
index f16ca21..d70b4e2 100755 (executable)
@@ -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();
 }
index 34a5daa..2f7a5e0 100755 (executable)
@@ -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;
+  }
 }
index b86250a..bf62e45 100755 (executable)
@@ -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<Color, List<String>> colours = new HashMap<Color, List<String>>();
+    Map<Color, List<String>> 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<String> residueColours = new ArrayList<String>();
+    List<String> residueColours = new ArrayList<>();
     for (Entry<Color, List<String>> 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);
+  }
 }
index 21b226b..ca90d60 100644 (file)
@@ -91,7 +91,7 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
           File reply) throws Exception
   {
     EmblFile efile = null;
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceI> 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: <acc> display type is either not supported or entry is not found.
      */
-    List<SequenceI> peptides = new ArrayList<SequenceI>();
+    List<SequenceI> peptides = new ArrayList<>();
     if (efile != null && efile.getEntries() != null)
     {
       for (EmblEntry entry : efile.getEntries())
index 977f9da..9284f82 100644 (file)
@@ -126,15 +126,16 @@ public class ASequenceFetcher
    */
   public SequenceI[] getSequences(List<DBRefEntry> refs, boolean dna)
   {
-    Vector<SequenceI> rseqs = new Vector<SequenceI>();
-    Hashtable<String, List<String>> queries = new Hashtable<String, List<String>>();
+    Vector<SequenceI> rseqs = new Vector<>();
+    Hashtable<String, List<String>> 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<String>());
+        queries.put(canonical, new ArrayList<String>());
       }
-      List<String> qset = queries.get(ref.getSource());
+      List<String> qset = queries.get(canonical);
       if (!qset.contains(ref.getAccessionId()))
       {
         qset.add(ref.getAccessionId());
@@ -154,14 +155,14 @@ public class ASequenceFetcher
         continue;
       }
 
-      Stack<String> queriesLeft = new Stack<String>();
+      Stack<String> queriesLeft = new Stack<>();
       queriesLeft.addAll(query);
 
       List<DbSourceProxy> proxies = getSourceProxy(db);
       for (DbSourceProxy fetcher : proxies)
       {
-        List<String> queriesMade = new ArrayList<String>();
-        HashSet<String> queriesFound = new HashSet<String>();
+        List<String> queriesMade = new ArrayList<>();
+        HashSet<String> queriesFound = new HashSet<>();
         try
         {
           if (fetcher.isDnaCoding() != dna)
@@ -306,13 +307,13 @@ public class ASequenceFetcher
     Map<String, DbSourceProxy> dblist = fetchableDbs.get(db);
     if (dblist == null)
     {
-      return new ArrayList<DbSourceProxy>();
+      return new ArrayList<>();
     }
 
     /*
      * sort so that primary sources precede secondary
      */
-    List<DbSourceProxy> dbs = new ArrayList<DbSourceProxy>(dblist.values());
+    List<DbSourceProxy> dbs = new ArrayList<>(dblist.values());
     Collections.sort(dbs, proxyComparator);
     return dbs;
   }
@@ -357,14 +358,14 @@ public class ASequenceFetcher
     {
       if (fetchableDbs == null)
       {
-        fetchableDbs = new Hashtable<String, Map<String, DbSourceProxy>>();
+        fetchableDbs = new Hashtable<>();
       }
       Map<String, DbSourceProxy> slist = fetchableDbs
               .get(proxy.getDbSource());
       if (slist == null)
       {
         fetchableDbs.put(proxy.getDbSource(),
-                slist = new Hashtable<String, DbSourceProxy>());
+                slist = new Hashtable<>());
       }
       slist.put(proxy.getDbName(), proxy);
     }
@@ -391,7 +392,7 @@ public class ASequenceFetcher
       return null;
     }
     String[] sources = null;
-    Vector<String> src = new Vector<String>();
+    Vector<String> src = new Vector<>();
     Enumeration<String> dbs = fetchableDbs.keys();
     while (dbs.hasMoreElements())
     {
@@ -413,7 +414,7 @@ public class ASequenceFetcher
 
   public DbSourceProxy[] getDbSourceProxyInstances(Class class1)
   {
-    List<DbSourceProxy> prlist = new ArrayList<DbSourceProxy>();
+    List<DbSourceProxy> 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 (file)
index c80b830..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- * 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...
-
-}
index ec5855f..0715857 100644 (file)
@@ -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<String> failedDBRetr = new ArrayList<String>();
-    List<String> failedXrefMenuItems = new ArrayList<String>();
-    List<String> failedProjectRecoveries = new ArrayList<String>();
-
+    List<String> failedDBRetr = new ArrayList<>();
+    List<String> failedXrefMenuItems = new ArrayList<>();
+    List<String> failedProjectRecoveries = new ArrayList<>();
+    // only search for ensembl or Uniprot crossrefs
+    List<String> 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<String, String> dbtoviewBit = new HashMap<String, String>();
-    List<String> keyseq = new ArrayList<String>();
-    HashMap<String, File> savedProjects = new HashMap<String, File>();
+    HashMap<String, String> dbtoviewBit = new HashMap<>();
+    List<String> keyseq = new ArrayList<>();
+    HashMap<String, File> 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<AlignmentViewPanel> cra_views = new ArrayList<AlignmentViewPanel>();
+            List<AlignmentViewPanel> cra_views = new ArrayList<>();
             CrossRefAction cra = null;
 
             if (pass2 == 0)
@@ -237,7 +242,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
 
               }
             }
-            HashMap<String, List<String>> xrptypes = new HashMap<String, List<String>>();
+            HashMap<String, List<String>> 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<AlignmentViewPanel> cra_views2 = new ArrayList<AlignmentViewPanel>();
+                  List<AlignmentViewPanel> 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<String> ptypes, List<String> 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<SequenceI> nonType = new ArrayList<SequenceI>();
+    List<SequenceI> 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 (file)
index 0000000..2016087
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.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.gui.Preferences;
+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<SequenceI> 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(Preferences.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(Preferences.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 (file)
index 0000000..81fb2c0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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...
+}