Merge remote-tracking branch 'origin/develop' into
authorkiramt <k.mourao@dundee.ac.uk>
Wed, 22 Mar 2017 12:36:24 +0000 (12:36 +0000)
committerkiramt <k.mourao@dundee.ac.uk>
Wed, 22 Mar 2017 12:36:24 +0000 (12:36 +0000)
bug/JAL-2436featureRendererThreading

Conflicts:
src/jalview/appletgui/OverviewPanel.java
src/jalview/gui/OverviewPanel.java

1  2 
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/SeqCanvas.java

@@@ -20,8 -20,8 +20,9 @@@
   */
  package jalview.appletgui;
  
- import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceI;
 +import jalview.renderer.seqfeatures.FeatureColourFinder;
+ import jalview.viewmodel.OverviewDimensions;
  
  import java.awt.Color;
  import java.awt.Dimension;
@@@ -38,38 -38,34 +39,34 @@@ import java.awt.event.MouseMotionListen
  public class OverviewPanel extends Panel implements Runnable,
          MouseMotionListener, MouseListener
  {
-   Image miniMe;
+   private OverviewDimensions od;
  
-   Image offscreen;
+   private Image miniMe;
  
-   AlignViewport av;
+   private Image offscreen;
  
-   AlignmentPanel ap;
+   private AlignViewport av;
  
-   float scalew = 1f;
+   private AlignmentPanel ap;
  
-   float scaleh = 1f;
+   private boolean resizing = false;
  
-   public int width, sequencesHeight;
-   int graphHeight = 20;
-   int boxX = -1, boxY = -1, boxWidth = -1, boxHeight = -1;
-   boolean resizing = false;
+   // This is set true if the user resizes whilst
+   // the overview is being calculated
+   private boolean resizeAgain = false;
  
    // Can set different properties in this seqCanvas than
    // main visible SeqCanvas
-   SequenceRenderer sr;
+   private SequenceRenderer sr;
  
-   FeatureRenderer fr;
+   private FeatureRenderer fr;
  
-   Frame nullFrame;
+   private Frame nullFrame;
  
-   public OverviewPanel(AlignmentPanel ap)
+   public OverviewPanel(AlignmentPanel alPanel)
    {
-     this.av = ap.av;
-     this.ap = ap;
+     this.av = alPanel.av;
+     this.ap = alPanel;
      setLayout(null);
      nullFrame = new Frame();
      nullFrame.addNotify();
      sr.forOverview = true;
      fr = new FeatureRenderer(av);
  
-     // scale the initial size of overviewpanel to shape of alignment
-     float initialScale = (float) av.getAlignment().getWidth()
-             / (float) av.getAlignment().getHeight();
-     if (av.getSequenceConsensusHash() == null)
-     {
-       graphHeight = 0;
-     }
+     od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
  
-     if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-     {
-       // wider
-       width = 400;
-       sequencesHeight = (int) (400f / initialScale);
-       if (sequencesHeight < 40)
-       {
-         sequencesHeight = 40;
-       }
-     }
-     else
-     {
-       // taller
-       width = (int) (400f * initialScale);
-       sequencesHeight = 300;
-       if (width < 120)
-       {
-         width = 120;
-       }
-     }
-     setSize(new Dimension(width, sequencesHeight + graphHeight));
+     setSize(new Dimension(od.getWidth(), od.getHeight()));
      addComponentListener(new ComponentAdapter()
      {
  
        @Override
        public void componentResized(ComponentEvent evt)
        {
-         if (getSize().width != width
-                 || getSize().height != sequencesHeight + graphHeight)
+         if ((getWidth() != od.getWidth())
+                 || (getHeight() != (od.getHeight())))
          {
            updateOverviewImage();
          }
    @Override
    public void mousePressed(MouseEvent evt)
    {
-     boxX = evt.getX();
-     boxY = evt.getY();
-     checkValid();
+     mouseAction(evt);
    }
  
    @Override
    public void mouseReleased(MouseEvent evt)
    {
-     boxX = evt.getX();
-     boxY = evt.getY();
-     checkValid();
+     mouseAction(evt);
    }
  
    @Override
    public void mouseDragged(MouseEvent evt)
    {
-     boxX = evt.getX();
-     boxY = evt.getY();
-     checkValid();
+     mouseAction(evt);
    }
  
-   void checkValid()
+   private void mouseAction(MouseEvent evt)
    {
-     if (boxY < 0)
-     {
-       boxY = 0;
-     }
-     if (boxY > (sequencesHeight - boxHeight))
-     {
-       boxY = sequencesHeight - boxHeight + 1;
-     }
-     if (boxX < 0)
-     {
-       boxX = 0;
-     }
-     if (boxX > (width - boxWidth))
-     {
-       if (av.hasHiddenColumns())
-       {
-         // Try smallest possible box
-         boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
-       }
-       boxX = width - boxWidth;
-     }
-     int col = (int) (boxX / scalew / av.getCharWidth());
-     int row = (int) (boxY / scaleh / av.getCharHeight());
-     if (av.hasHiddenColumns())
-     {
-       if (!av.getColumnSelection().isVisible(col))
-       {
-         return;
-       }
-       col = av.getColumnSelection().findColumnPosition(col);
-     }
-     if (av.hasHiddenRows())
-     {
-       row = av.getAlignment().getHiddenSequences()
-               .findIndexWithoutHiddenSeqs(row);
-     }
-     ap.setScrollValues(col, row);
+     od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
+             .getHiddenSequences(), av.getColumnSelection(), av
+             .getRanges());
+     ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
      ap.paintAlignment(false);
    }
  
    /**
-    * DOCUMENT ME!
+    * Updates the overview image when the related alignment panel is updated
     */
    public void updateOverviewImage()
    {
  
      if ((getSize().width > 0) && (getSize().height > 0))
      {
-       width = getSize().width;
-       sequencesHeight = getSize().height - graphHeight;
+       od.setWidth(getSize().width);
+       od.setHeight(getSize().height);
      }
-     setSize(new Dimension(width, sequencesHeight + graphHeight));
+     setSize(new Dimension(od.getWidth(), od.getHeight()));
  
      Thread thread = new Thread(this);
      thread.start();
      repaint();
    }
  
-   // This is set true if the user resizes whilst
-   // the overview is being calculated
-   boolean resizeAgain = false;
    @Override
    public void run()
    {
      miniMe = null;
-     int alwidth = av.getAlignment().getWidth();
-     int alheight = av.getAlignment().getHeight()
-             + av.getAlignment().getHiddenSequences().getSize();
  
      if (av.isShowSequenceFeatures())
      {
  
      if (getSize().width > 0 && getSize().height > 0)
      {
-       width = getSize().width;
-       sequencesHeight = getSize().height - graphHeight;
+       od.setWidth(getSize().width);
+       od.setHeight(getSize().height);
      }
  
-     setSize(new Dimension(width, sequencesHeight + graphHeight));
-     int fullsizeWidth = alwidth * av.getCharWidth();
-     int fullsizeHeight = alheight * av.getCharHeight();
-     scalew = (float) width / (float) fullsizeWidth;
-     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
+     setSize(new Dimension(od.getWidth(), od.getHeight()));
  
-     miniMe = nullFrame.createImage(width, sequencesHeight + graphHeight);
-     offscreen = nullFrame.createImage(width, sequencesHeight + graphHeight);
+     miniMe = nullFrame.createImage(od.getWidth(), od.getHeight());
+     offscreen = nullFrame.createImage(od.getWidth(), od.getHeight());
  
      Graphics mg = miniMe.getGraphics();
-     float sampleCol = (float) alwidth / (float) width;
-     float sampleRow = (float) alheight / (float) sequencesHeight;
-     int lastcol = 0, lastrow = 0;
-     int xstart = 0, ystart = 0;
-     Color color = Color.yellow;
-     int row, col, sameRow = 0, sameCol = 0;
-     jalview.datamodel.SequenceI seq;
-     final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-             .hasHiddenColumns();
-     boolean hiddenRow = false;
-     AlignmentI alignment = av.getAlignment();
-     FeatureColourFinder finder = new FeatureColourFinder(fr);
-     for (row = 0; row <= sequencesHeight; row++)
-     {
-       if (resizeAgain)
-       {
-         break;
-       }
-       if ((int) (row * sampleRow) == lastrow)
-       {
-         sameRow++;
-         continue;
-       }
-       hiddenRow = false;
-       if (hasHiddenRows)
-       {
-         seq = alignment.getHiddenSequences().getHiddenSequence(lastrow);
-         if (seq == null)
-         {
-           int index = alignment.getHiddenSequences()
-                   .findIndexWithoutHiddenSeqs(lastrow);
  
-           seq = alignment.getSequenceAt(index);
-         }
-         else
-         {
-           hiddenRow = true;
-         }
-       }
-       else
-       {
-         seq = alignment.getSequenceAt(lastrow);
-       }
-       for (col = 0; col < width; col++)
-       {
-         if ((int) (col * sampleCol) == lastcol
-                 && (int) (row * sampleRow) == lastrow)
-         {
-           sameCol++;
-           continue;
-         }
-         lastcol = (int) (col * sampleCol);
-         if (seq.getLength() > lastcol)
-         {
-           color = sr.getResidueColour(seq, lastcol, finder);
-         }
-         else
-         {
-           color = Color.white;
-         }
-         if (hiddenRow
-                 || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                         lastcol)))
-         {
-           color = color.darker().darker();
-         }
-         mg.setColor(color);
-         if (sameCol == 1 && sameRow == 1)
-         {
-           mg.drawLine(xstart, ystart, xstart, ystart);
-         }
-         else
-         {
-           mg.fillRect(xstart, ystart, sameCol, sameRow);
-         }
+     int alwidth = av.getAlignment().getWidth();
+     int alheight = av.getAlignment().getAbsoluteHeight();
+     float sampleCol = alwidth / (float) od.getWidth();
+     float sampleRow = alheight / (float) od.getSequencesHeight();
  
-         xstart = col;
-         sameCol = 1;
-       }
-       lastrow = (int) (row * sampleRow);
-       ystart = row;
-       sameRow = 1;
-     }
+     buildImage(sampleRow, sampleCol, mg);
  
-     if (av.getAlignmentConservationAnnotation() != null)
+     if (av.isShowAnnotation())
      {
-       for (col = 0; col < width; col++)
+       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
        {
-         if (resizeAgain)
-         {
-           break;
-         }
-         lastcol = (int) (col * sampleCol);
-         {
-           mg.translate(col, sequencesHeight);
-           ap.annotationPanel.renderer.drawGraph(mg,
-                   av.getAlignmentConservationAnnotation(),
-                   av.getAlignmentConservationAnnotation().annotations,
-                   (int) (sampleCol) + 1, graphHeight,
-                   (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-           mg.translate(-col, -sequencesHeight);
-         }
+         mg.translate(col, od.getSequencesHeight());
+         ap.annotationPanel.renderer.drawGraph(mg,
+                 av.getAlignmentConservationAnnotation(),
+                 av.getAlignmentConservationAnnotation().annotations,
+                 (int) (sampleCol) + 1, od.getGraphHeight(),
+                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+         mg.translate(-col, -od.getSequencesHeight());
        }
      }
      System.gc();
      }
    }
  
-   public void setBoxPosition()
+   /*
+    * Build the overview panel image
+    */
+   private void buildImage(float sampleRow, float sampleCol, Graphics mg)
    {
-     int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-     int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-             .getHiddenSequences().getSize())
-             * av.getCharHeight();
-     int startRes = av.getStartRes();
-     int endRes = av.getEndRes();
+     int lastcol = 0;
+     int lastrow = 0;
+     int xstart = 0;
+     int ystart = 0;
+     Color color = Color.yellow;
+     int sameRow = 0;
+     int sameCol = 0;
  
-     if (av.hasHiddenColumns())
-     {
-       startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-       endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-     }
+     SequenceI seq = null;
++    FeatureColourFinder finder = new FeatureColourFinder(fr);
  
-     int startSeq = av.startSeq;
-     int endSeq = av.endSeq;
+     final boolean hasHiddenCols = av.hasHiddenColumns();
+     boolean hiddenRow = false;
  
-     if (av.hasHiddenRows())
+     for (int row = 0; row <= od.getSequencesHeight() && !resizeAgain; row++)
      {
-       startSeq = av.getAlignment().getHiddenSequences()
-               .adjustForHiddenSeqs(startSeq);
-       endSeq = av.getAlignment().getHiddenSequences()
-               .adjustForHiddenSeqs(endSeq);
+       if ((int) (row * sampleRow) == lastrow)
+       {
+         sameRow++;
+       }
+       else
+       {
+         // get the sequence which would be at alignment index 'lastrow' if no
+         // columns were hidden, and determine whether it is hidden or not
+         hiddenRow = av.getAlignment().isHidden(lastrow);
+         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
  
+         for (int col = 0; col < od.getWidth(); col++)
+         {
+           if ((int) (col * sampleCol) == lastcol
+                   && (int) (row * sampleRow) == lastrow)
+           {
+             sameCol++;
+           }
+           else
+           {
+             lastcol = (int) (col * sampleCol);
+             color = getColumnColourFromSequence(seq, hiddenRow,
 -                    hasHiddenCols, lastcol);
++                    hasHiddenCols, lastcol, finder);
+             mg.setColor(color);
+             if (sameCol == 1 && sameRow == 1)
+             {
+               mg.drawLine(xstart, ystart, xstart, ystart);
+             }
+             else
+             {
+               mg.fillRect(xstart, ystart, sameCol, sameRow);
+             }
+             xstart = col;
+             sameCol = 1;
+           }
+         }
+         lastrow = (int) (row * sampleRow);
+         ystart = row;
+         sameRow = 1;
+       }
      }
+   }
  
-     scalew = (float) width / (float) fullsizeWidth;
-     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-     boxX = (int) (startRes * av.getCharWidth() * scalew);
-     boxY = (int) (startSeq * av.getCharHeight() * scaleh);
-     if (av.hasHiddenColumns())
+   /*
+    * Find the colour of a sequence at a specified column position
+    */
+   private Color getColumnColourFromSequence(
+           jalview.datamodel.SequenceI seq, boolean hiddenRow,
 -          boolean hasHiddenCols, int lastcol)
++          boolean hasHiddenCols, int lastcol, FeatureColourFinder finder)
+   {
 -    Color color;
++    Color color = Color.white;
+     if (seq.getLength() > lastcol)
      {
-       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
 -      color = sr.getResidueBoxColour(seq, lastcol);
 -
 -      if (av.isShowSequenceFeatures())
 -      {
 -        color = fr.findFeatureColour(color, seq, lastcol);
 -      }
 -    }
 -    else
 -    {
 -      color = Color.white;
++      color = sr.getResidueColour(seq, lastcol, finder);
      }
-     else
+     if (hiddenRow
+             || (hasHiddenCols && !av.getColumnSelection()
+                     .isVisible(lastcol)))
      {
-       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
+       color = color.darker().darker();
      }
+     return color;
+   }
  
-     boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
+   /**
+    * Update the overview panel box when the associated alignment panel is
+    * changed
+    * 
+    */
+   public void setBoxPosition()
+   {
+     od.setBoxPosition(av.getAlignment()
+             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
      repaint();
    }
  
      {
        og.drawImage(miniMe, 0, 0, this);
        og.setColor(Color.red);
-       og.drawRect(boxX, boxY, boxWidth, boxHeight);
-       og.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+       od.drawBox(og);
        g.drawImage(offscreen, 0, 0, this);
      }
    }
@@@ -27,6 -27,7 +27,7 @@@ import jalview.datamodel.SequenceI
  import jalview.renderer.ScaleRenderer;
  import jalview.renderer.ScaleRenderer.ScaleMark;
  import jalview.viewmodel.AlignmentViewport;
+ import jalview.viewmodel.ViewportRanges;
  
  import java.awt.Color;
  import java.awt.FontMetrics;
@@@ -211,17 -212,19 +212,19 @@@ public class SeqCanvas extends Pane
        return;
      }
  
+     ViewportRanges ranges = av.getRanges();
      updateViewport();
  
      // Its possible on certain browsers that the call to fastpaint
      // is faster than it can paint, so this check here catches
      // this possibility
-     if (lastsr + horizontal != av.startRes)
+     if (lastsr + horizontal != ranges.getStartRes())
      {
-       horizontal = av.startRes - lastsr;
+       horizontal = ranges.getStartRes() - lastsr;
      }
  
-     lastsr = av.startRes;
+     lastsr = ranges.getStartRes();
  
      fastPaint = true;
      gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
              imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
              -vertical * avcharHeight);
  
-     int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0;
+     int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
+             .getStartSeq(), es = ranges
+             .getEndSeq(), transX = 0, transY = 0;
  
      if (horizontal > 0) // scrollbar pulled right, image to the left
      {
      else if (vertical > 0) // scroll down
      {
        ss = es - vertical;
-       if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
+       if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
+                                      // at a
+                                  // time
        {
-         ss = av.startSeq;
+         ss = ranges.getStartSeq();
        }
        else
        {
-         transY = imgHeight - vertical * avcharHeight;
+         transY = imgHeight - ((vertical + 1) * avcharHeight);
        }
      }
      else if (vertical < 0)
      {
        es = ss - vertical;
-       if (es > av.endSeq)
+       if (es > ranges.getEndSeq())
        {
-         es = av.endSeq;
+         es = ranges.getEndSeq();
        }
      }
  
      gg.setColor(Color.white);
      gg.fillRect(0, 0, imgWidth, imgHeight);
  
+     ViewportRanges ranges = av.getRanges();
      if (av.getWrapAlignment())
      {
-       drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);
+       drawWrappedPanel(gg, imgWidth, imgHeight, ranges.getStartRes());
      }
      else
      {
-       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+       drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+               ranges.getStartSeq(), ranges.getEndSeq(), 0);
      }
  
      g.drawImage(img, 0, 0, this);
  
      av.setWrappedWidth(cWidth);
  
-     av.endRes = av.startRes + cWidth;
+     av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth);
  
      int endx;
      int ypos = hgap;
  
      // / First draw the sequences
      // ///////////////////////////
-     for (int i = startSeq; i < endSeq; i++)
+     for (int i = startSeq; i <= endSeq; i++)
      {
        nextSeq = av.getAlignment().getSequenceAt(i);
  
        if (av.isShowSequenceFeatures())
        {
          fr.drawSequence(g, nextSeq, startRes, endRes, offset
 -                + ((i - startSeq) * avcharHeight));
 +                + ((i - startSeq) * avcharHeight), false);
        }
  
        // / Highlight search Results once all sequences have been drawn
          int bottom = -1;
          int alHeight = av.getAlignment().getHeight() - 1;
  
-         for (i = startSeq; i < endSeq; i++)
+         for (i = startSeq; i <= endSeq; i++)
          {
            sx = (group.getStartRes() - startRes) * avcharWidth;
            sy = offset + ((i - startSeq) * avcharHeight);
   */
  package jalview.gui;
  
+ import jalview.datamodel.SequenceI;
  import jalview.renderer.AnnotationRenderer;
 +import jalview.renderer.seqfeatures.FeatureColourFinder;
+ import jalview.viewmodel.OverviewDimensions;
  
  import java.awt.Color;
  import java.awt.Dimension;
@@@ -36,102 -37,67 +38,67 @@@ import java.awt.image.BufferedImage
  import javax.swing.JPanel;
  
  /**
-  * DOCUMENT ME!
+  * Panel displaying an overview of the full alignment, with an interactive box
+  * representing the viewport onto the alignment.
   * 
   * @author $author$
   * @version $Revision$
   */
  public class OverviewPanel extends JPanel implements Runnable
  {
-   BufferedImage miniMe;
+   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
  
-   AlignViewport av;
+   private final AnnotationRenderer renderer = new AnnotationRenderer();
  
-   AlignmentPanel ap;
+   private OverviewDimensions od;
  
-   final AnnotationRenderer renderer = new AnnotationRenderer();
+   private BufferedImage miniMe;
  
-   float scalew = 1f;
-   float scaleh = 1f;
-   int width;
-   int sequencesHeight;
-   int graphHeight = 20;
-   int boxX = -1;
+   private BufferedImage lastMiniMe = null;
  
-   int boxY = -1;
+   private AlignViewport av;
  
-   int boxWidth = -1;
+   private AlignmentPanel ap;
  
-   int boxHeight = -1;
+   //
+   private boolean resizing = false;
  
-   boolean resizing = false;
+   // This is set true if the user resizes whilst
+   // the overview is being calculated
+   private boolean resizeAgain = false;
  
    // Can set different properties in this seqCanvas than
    // main visible SeqCanvas
-   SequenceRenderer sr;
+   private SequenceRenderer sr;
  
 -  private jalview.renderer.seqfeatures.FeatureRenderer fr;
 +  jalview.renderer.seqfeatures.FeatureRenderer fr;
  
    /**
     * Creates a new OverviewPanel object.
     * 
-    * @param ap
-    *          DOCUMENT ME!
+    * @param alPanel
+    *          The alignment panel which is shown in the overview panel
     */
-   public OverviewPanel(AlignmentPanel ap)
+   public OverviewPanel(AlignmentPanel alPanel)
    {
-     this.av = ap.av;
-     this.ap = ap;
+     this.av = alPanel.av;
+     this.ap = alPanel;
      setLayout(null);
  
      sr = new SequenceRenderer(av);
      sr.renderGaps = false;
      sr.forOverview = true;
 -    fr = new FeatureRenderer(alPanel);
 +    fr = new FeatureRenderer(ap);
  
-     // scale the initial size of overviewpanel to shape of alignment
-     float initialScale = (float) av.getAlignment().getWidth()
-             / (float) av.getAlignment().getHeight();
-     if (av.getAlignmentConservationAnnotation() == null)
-     {
-       graphHeight = 0;
-     }
-     if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
-     {
-       // wider
-       width = 400;
-       sequencesHeight = (int) (400f / initialScale);
-       if (sequencesHeight < 40)
-       {
-         sequencesHeight = 40;
-       }
-     }
-     else
-     {
-       // taller
-       width = (int) (400f * initialScale);
-       sequencesHeight = 300;
-       if (width < 120)
-       {
-         width = 120;
-       }
-     }
+     od = new OverviewDimensions(av.getRanges(), av.isShowAnnotation());
  
      addComponentListener(new ComponentAdapter()
      {
        @Override
        public void componentResized(ComponentEvent evt)
        {
-         if ((getWidth() != width)
-                 || (getHeight() != (sequencesHeight + graphHeight)))
+         if ((getWidth() != od.getWidth())
+                 || (getHeight() != (od.getHeight())))
          {
            updateOverviewImage();
          }
        {
          if (!av.getWrapAlignment())
          {
-           // TODO: feature: jv2.5 detect shift drag and update selection from
-           // it.
-           boxX = evt.getX();
-           boxY = evt.getY();
-           checkValid();
+           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                   .getAlignment().getHiddenSequences(), av
+                   .getColumnSelection(), av.getRanges());
+           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
          }
        }
      });
        {
          if (!av.getWrapAlignment())
          {
-           boxX = evt.getX();
-           boxY = evt.getY();
-           checkValid();
+           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
+                   .getAlignment().getHiddenSequences(), av
+                   .getColumnSelection(), av.getRanges());
+           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
          }
        }
      });
    }
  
    /**
-    * DOCUMENT ME!
-    */
-   void checkValid()
-   {
-     if (boxY < 0)
-     {
-       boxY = 0;
-     }
-     if (boxY > (sequencesHeight - boxHeight))
-     {
-       boxY = sequencesHeight - boxHeight + 1;
-     }
-     if (boxX < 0)
-     {
-       boxX = 0;
-     }
-     if (boxX > (width - boxWidth))
-     {
-       if (av.hasHiddenColumns())
-       {
-         // Try smallest possible box
-         boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
-       }
-       boxX = width - boxWidth;
-     }
-     int col = (int) (boxX / scalew / av.getCharWidth());
-     int row = (int) (boxY / scaleh / av.getCharHeight());
-     if (av.hasHiddenColumns())
-     {
-       if (!av.getColumnSelection().isVisible(col))
-       {
-         return;
-       }
-       col = av.getColumnSelection().findColumnPosition(col);
-     }
-     if (av.hasHiddenRows())
-     {
-       row = av.getAlignment().getHiddenSequences()
-               .findIndexWithoutHiddenSeqs(row);
-     }
-     ap.setScrollValues(col, row);
-   }
-   /**
-    * DOCUMENT ME!
+    * Updates the overview image when the related alignment panel is updated
     */
    public void updateOverviewImage()
    {
  
      if ((getWidth() > 0) && (getHeight() > 0))
      {
-       width = getWidth();
-       sequencesHeight = getHeight() - graphHeight;
+       od.setWidth(getWidth());
+       od.setHeight(getHeight());
      }
  
-     setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
+     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
  
      Thread thread = new Thread(this);
      thread.start();
      repaint();
    }
  
-   // This is set true if the user resizes whilst
-   // the overview is being calculated
-   boolean resizeAgain = false;
-   /**
-    * DOCUMENT ME!
-    */
    @Override
    public void run()
    {
        fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
      }
  
-     int alwidth = av.getAlignment().getWidth();
-     int alheight = av.getAlignment().getHeight()
-             + av.getAlignment().getHiddenSequences().getSize();
-     setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
-     int fullsizeWidth = alwidth * av.getCharWidth();
-     int fullsizeHeight = alheight * av.getCharHeight();
+     // why do we need to set preferred size again? was set in
+     // updateOverviewImage
+     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
  
-     scalew = (float) width / (float) fullsizeWidth;
-     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-     miniMe = new BufferedImage(width, sequencesHeight + graphHeight,
+     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
              BufferedImage.TYPE_INT_RGB);
  
      Graphics mg = miniMe.getGraphics();
      mg.setColor(Color.orange);
-     mg.fillRect(0, 0, width, miniMe.getHeight());
-     float sampleCol = (float) alwidth / (float) width;
-     float sampleRow = (float) alheight / (float) sequencesHeight;
-     int lastcol = -1, lastrow = -1;
-     Color color = Color.white;
-     int row, col;
-     jalview.datamodel.SequenceI seq;
-     final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
-             .hasHiddenColumns();
-     boolean hiddenRow = false;
-     // get hidden row and hidden column map once at beginning.
-     // clone featureRenderer settings to avoid race conditions... if state is
-     // updated just need to refresh again
-     FeatureColourFinder finder = new FeatureColourFinder(fr);
-     for (row = 0; row < sequencesHeight; row++)
-     {
-       if (resizeAgain)
-       {
-         break;
-       }
-       if ((int) (row * sampleRow) == lastrow)
-       {
-         // No need to recalculate the colours,
-         // Just copy from the row above
-         for (col = 0; col < width; col++)
-         {
-           if (resizeAgain)
-           {
-             break;
-           }
-           miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
-         }
-         continue;
-       }
-       lastrow = (int) (row * sampleRow);
-       hiddenRow = false;
-       if (hasHiddenRows)
-       {
-         seq = av.getAlignment().getHiddenSequences()
-                 .getHiddenSequence(lastrow);
-         if (seq == null)
-         {
-           int index = av.getAlignment().getHiddenSequences()
-                   .findIndexWithoutHiddenSeqs(lastrow);
-           seq = av.getAlignment().getSequenceAt(index);
-         }
-         else
-         {
-           hiddenRow = true;
-         }
-       }
-       else
-       {
-         seq = av.getAlignment().getSequenceAt(lastrow);
-       }
-       if (seq == null)
-       {
-         System.out.println(lastrow + " null");
-         continue;
-       }
-       for (col = 0; col < width; col++)
-       {
-         if (resizeAgain)
-         {
-           break;
-         }
-         if ((int) (col * sampleCol) == lastcol
-                 && (int) (row * sampleRow) == lastrow)
-         {
-           miniMe.setRGB(col, row, color.getRGB());
-           continue;
-         }
+     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
  
-         lastcol = (int) (col * sampleCol);
-         if (seq.getLength() > lastcol)
-         {
-           color = sr.getResidueColour(seq, lastcol, finder);
-         }
-         else
-         {
-           color = Color.WHITE;
-         }
+     // calculate sampleCol and sampleRow
+     // alignment width is max number of residues/bases
+     // alignment height is number of sequences
+     int alwidth = av.getAlignment().getWidth();
+     int alheight = av.getAlignment().getAbsoluteHeight();
  
-         if (hiddenRow
-                 || (hasHiddenCols && !av.getColumnSelection().isVisible(
-                         lastcol)))
-         {
-           color = color.darker().darker();
-         }
+     // sampleCol or sampleRow is the width/height allocated to each residue
+     // in particular, sometimes we may need more than one row/col of the
+     // BufferedImage allocated
+     // sampleCol is how much of a residue to assign to each pixel
+     // sampleRow is how many sequences to assign to each pixel
+     float sampleCol = alwidth / (float) od.getWidth();
+     float sampleRow = alheight / (float) od.getSequencesHeight();
  
-         miniMe.setRGB(col, row, color.getRGB());
+     buildImage(sampleRow, sampleCol);
  
-       }
-     }
-     if (av.getAlignmentConservationAnnotation() != null)
+     if (av.isShowAnnotation())
      {
        renderer.updateFromAlignViewport(av);
-       for (col = 0; col < width; col++)
+       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
        {
-         if (resizeAgain)
-         {
-           break;
-         }
-         lastcol = (int) (col * sampleCol);
-         {
-           mg.translate(col, sequencesHeight);
-           renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
-                   av.getAlignmentConservationAnnotation().annotations,
-                   (int) (sampleCol) + 1, graphHeight,
-                   (int) (col * sampleCol), (int) (col * sampleCol) + 1);
-           mg.translate(-col, -sequencesHeight);
-         }
+         mg.translate(col, od.getSequencesHeight());
+         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
+                 av.getAlignmentConservationAnnotation().annotations,
+                 (int) (sampleCol) + 1, od.getGraphHeight(),
+                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
+         mg.translate(-col, -od.getSequencesHeight());
        }
      }
      System.gc();
      setBoxPosition();
    }
  
-   /**
-    * DOCUMENT ME!
+   /*
+    * Build the overview panel image
     */
-   public void setBoxPosition()
+   private void buildImage(float sampleRow, float sampleCol)
    {
-     int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
-     int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
-             .getHiddenSequences().getSize())
-             * av.getCharHeight();
+     int lastcol = -1;
+     int lastrow = -1;
 -    int color = Color.white.getRGB();
++    int rgbColour = Color.white.getRGB();
  
-     int startRes = av.getStartRes();
-     int endRes = av.getEndRes();
+     SequenceI seq = null;
++    FeatureColourFinder finder = new FeatureColourFinder(fr);
  
-     if (av.hasHiddenColumns())
+     final boolean hasHiddenCols = av.hasHiddenColumns();
+     boolean hiddenRow = false;
+     // get hidden row and hidden column map once at beginning.
+     // clone featureRenderer settings to avoid race conditions... if state is
+     // updated just need to refresh again
+     for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
      {
-       startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
-       endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
-     }
+       boolean doCopy = true;
+       int currentrow = (int) (row * sampleRow);
+       if (currentrow != lastrow)
+       {
+         doCopy = false;
  
-     int startSeq = av.startSeq;
-     int endSeq = av.endSeq;
+         lastrow = currentrow;
  
-     if (av.hasHiddenRows())
-     {
-       startSeq = av.getAlignment().getHiddenSequences()
-               .adjustForHiddenSeqs(startSeq);
+         // get the sequence which would be at alignment index 'lastrow' if no
+         // rows were hidden, and determine whether it is hidden or not
+         hiddenRow = av.getAlignment().isHidden(lastrow);
+         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
+       }
  
-       endSeq = av.getAlignment().getHiddenSequences()
-               .adjustForHiddenSeqs(endSeq);
+       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
+       {
+         if (doCopy)
+         {
 -          color = miniMe.getRGB(col, row - 1);
++          rgbColour = miniMe.getRGB(col, row - 1);
+         }
+         else if ((int) (col * sampleCol) != lastcol
+                 || (int) (row * sampleRow) != lastrow)
+         {
+           lastcol = (int) (col * sampleCol);
 -          color = getColumnColourFromSequence(seq, hiddenRow, hasHiddenCols,
 -                  lastcol);
++          rgbColour = getColumnColourFromSequence(seq, hiddenRow,
++                  hasHiddenCols, lastcol, finder);
+         }
+         // else we just use the color we already have , so don't need to set it
  
 -        miniMe.setRGB(col, row, color);
++        miniMe.setRGB(col, row, rgbColour);
+       }
      }
+   }
  
-     scalew = (float) width / (float) fullsizeWidth;
-     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
-     boxX = (int) (startRes * av.getCharWidth() * scalew);
-     boxY = (int) (startSeq * av.getCharHeight() * scaleh);
+   /*
+    * Find the colour of a sequence at a specified column position
+    */
 -  private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
 -          boolean hiddenRow, boolean hasHiddenCols, int lastcol)
++  private int getColumnColourFromSequence(
++          jalview.datamodel.SequenceI seq,
++          boolean hiddenRow, boolean hasHiddenCols, int lastcol,
++          FeatureColourFinder finder)
+   {
 -    int color;
++    Color color = Color.white;
  
-     if (av.hasHiddenColumns())
 -    if (seq == null)
++    if ((seq != null) && (seq.getLength() > lastcol))
      {
-       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
 -      color = Color.white.getRGB();
 -    }
 -    else if (seq.getLength() > lastcol)
 -    {
 -      color = sr.getResidueBoxColour(seq, lastcol).getRGB();
 -
 -      if (av.isShowSequenceFeatures())
 -      {
 -        color = fr.findFeatureColour(color, seq, lastcol);
 -      }
 -    }
 -    else
 -    {
 -      color = Color.white.getRGB();
++      color = sr.getResidueColour(seq, lastcol, finder);
      }
-     else
+     if (hiddenRow
+             || (hasHiddenCols && !av.getColumnSelection()
+                     .isVisible(lastcol)))
      {
-       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
 -      color = new Color(color).darker().darker().getRGB();
++      color = color.darker().darker();
      }
  
-     boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
-     repaint();
 -    return color;
++    return color.getRGB();
    }
  
-   private BufferedImage lastMiniMe = null;
    /**
-    * DOCUMENT ME!
+    * Update the overview panel box when the associated alignment panel is
+    * changed
     * 
-    * @param g
-    *          DOCUMENT ME!
     */
+   public void setBoxPosition()
+   {
+     od.setBoxPosition(av.getAlignment()
+             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+     repaint();
+   }
    @Override
    public void paintComponent(Graphics g)
    {
        {
          g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
        }
-       g.setColor(new Color(100, 100, 100, 25));
+       g.setColor(TRANS_GREY);
        g.fillRect(0, 0, getWidth(), getHeight());
      }
      else if (lastMiniMe != null)
        g.drawImage(lastMiniMe, 0, 0, this);
        if (lastMiniMe != miniMe)
        {
-         g.setColor(new Color(100, 100, 100, 25));
+         g.setColor(TRANS_GREY);
          g.fillRect(0, 0, getWidth(), getHeight());
        }
      }
-     // TODO: render selected regions
      g.setColor(Color.red);
-     g.drawRect(boxX, boxY, boxWidth, boxHeight);
-     g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
+     od.drawBox(g);
    }
  }
@@@ -26,6 -26,7 +26,7 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.renderer.ScaleRenderer;
  import jalview.renderer.ScaleRenderer.ScaleMark;
+ import jalview.viewmodel.ViewportRanges;
  
  import java.awt.BasicStroke;
  import java.awt.BorderLayout;
@@@ -279,10 -280,11 +280,11 @@@ public class SeqCanvas extends JCompone
      gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
              imgHeight, -horizontal * charWidth, -vertical * charHeight);
  
-     int sr = av.startRes;
-     int er = av.endRes;
-     int ss = av.startSeq;
-     int es = av.endSeq;
+     ViewportRanges ranges = av.getRanges();
+     int sr = ranges.getStartRes();
+     int er = ranges.getEndRes();
+     int ss = ranges.getStartSeq();
+     int es = ranges.getEndSeq();
      int transX = 0;
      int transY = 0;
  
      {
        ss = es - vertical;
  
-       if (ss < av.startSeq)
+       if (ss < ranges.getStartSeq())
        { // ie scrolling too fast, more than a page at a time
-         ss = av.startSeq;
+         ss = ranges.getStartSeq();
        }
        else
        {
-         transY = imgHeight - (vertical * charHeight);
+         transY = imgHeight - ((vertical + 1) * charHeight);
        }
      }
      else if (vertical < 0)
      {
        es = ss - vertical;
  
-       if (es > av.endSeq)
+       if (es > ranges.getEndSeq())
        {
-         es = av.endSeq;
+         es = ranges.getEndSeq();
        }
      }
  
      gg.setColor(Color.white);
      gg.fillRect(0, 0, imgWidth, imgHeight);
  
+     ViewportRanges ranges = av.getRanges();
      if (av.getWrapAlignment())
      {
-       drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);
+       drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
      }
      else
      {
-       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
+       drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
+               ranges.getStartSeq(), ranges.getEndSeq(), 0);
      }
  
      g.drawImage(lcimg, 0, 0, this);
  
      av.setWrappedWidth(cWidth);
  
-     av.endRes = av.startRes + cWidth;
+     av.getRanges().setEndRes(av.getRanges().getStartRes() + cWidth);
  
      int endx;
      int ypos = hgap;
  
      // / First draw the sequences
      // ///////////////////////////
-     for (int i = startSeq; i < endSeq; i++)
+     for (int i = startSeq; i <= endSeq; i++)
      {
        nextSeq = av.getAlignment().getSequenceAt(i);
        if (nextSeq == null)
        if (av.isShowSequenceFeatures())
        {
          fr.drawSequence(g, nextSeq, startRes, endRes, offset
 -                + ((i - startSeq) * charHeight));
 +                + ((i - startSeq) * charHeight), false);
        }
  
        // / Highlight search Results once all sequences have been drawn
          int top = -1;
          int bottom = -1;
  
-         for (i = startSeq; i < endSeq; i++)
+         for (i = startSeq; i <= endSeq; i++)
          {
            sx = (group.getStartRes() - startRes) * charWidth;
            sy = offset + ((i - startSeq) * charHeight);