Merge remote-tracking branch 'origin/develop' into bug/JAL-2491
authorkiramt <k.mourao@dundee.ac.uk>
Mon, 15 May 2017 10:42:01 +0000 (11:42 +0100)
committerkiramt <k.mourao@dundee.ac.uk>
Mon, 15 May 2017 10:42:01 +0000 (11:42 +0100)
(not working yet)

Conflicts:
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/SeqPanel.java
src/jalview/viewmodel/OverviewDimensions.java
src/jalview/viewmodel/ViewportRanges.java
test/jalview/viewmodel/OverviewDimensionsTest.java

27 files changed:
1  2 
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/viewmodel/OverviewDimensions.java
src/jalview/viewmodel/OverviewDimensionsHideHidden.java
src/jalview/viewmodel/OverviewDimensionsShowHidden.java
src/jalview/viewmodel/ViewportRanges.java
test/jalview/viewmodel/OverviewDimensionsHideHiddenTest.java
test/jalview/viewmodel/OverviewDimensionsShowHiddenTest.java

Simple merge
@@@ -684,74 -685,109 +685,75 @@@ public class AlignmentPanel extends Pan
      }
      else
      {
 -      if (vextent + vscroll.getValue() >= av.getAlignment().getHeight())
 -      {
 -        return false;
 -      }
 -      setScrollValues(hscroll.getValue(), vscroll.getValue() + 1);
 -    }
 -
 -    repaint();
 -    return true;
 -  }
 +      int width = av.getAlignment().getWidth();
 +      int height = av.getAlignment().getHeight();
  
 -  public boolean scrollRight(boolean right)
 -  {
 -    if (!right)
 -    {
 -      if (hscroll.getValue() < 1)
 +      if (av.hasHiddenColumns())
        {
-         width = av.getColumnSelection().findColumnPosition(width);
 -        return false;
++        width = av.getAlignment().getHiddenColumns()
++                .findColumnPosition(width);
        }
 -      setScrollValues(hscroll.getValue() - 1, vscroll.getValue());
 -    }
 -    else
 -    {
 -      if (hextent + hscroll.getValue() >= av.getAlignment().getWidth())
 +      if (x < 0)
        {
 -        return false;
 +        x = 0;
        }
 -      setScrollValues(hscroll.getValue() + 1, vscroll.getValue());
 -    }
  
 -    repaint();
 -    return true;
 -  }
 -
 -  public void setScrollValues(int x, int y)
 -  {
 -    int width = av.getAlignment().getWidth();
 -    int height = av.getAlignment().getHeight();
 -
 -    if (av.hasHiddenColumns())
 -    {
 -      width = av.getAlignment().getHiddenColumns()
 -              .findColumnPosition(width);
 -    }
 -    if (x < 0)
 -    {
 -      x = 0;
 -    }
 +      hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
 +      vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
  
 +      if (hextent > width)
 +      {
 +        hextent = width;
 +      }
  
 -    hextent = seqPanel.seqCanvas.getSize().width / av.getCharWidth();
 -    vextent = seqPanel.seqCanvas.getSize().height / av.getCharHeight();
 -
 -    if (hextent > width)
 -    {
 -      hextent = width;
 -    }
 +      if (vextent > height)
 +      {
 +        vextent = height;
 +      }
  
 -    if (vextent > height)
 -    {
 -      vextent = height;
 -    }
 +      if ((hextent + x) > width)
 +      {
 +        System.err.println("hextent was " + hextent + " and x was " + x);
  
 -    if ((hextent + x) > width)
 -    {
 -      // System.err.println("hextent was " + hextent + " and x was " + x);
 -      //
 -      x = width - hextent;
 -    }
 +        x = width - hextent;
 +      }
  
 -    if ((vextent + y) > height)
 -    {
 -      y = height - vextent;
 -    }
 +      if ((vextent + y) > height)
 +      {
 +        y = height - vextent;
 +      }
  
 -    if (y < 0)
 -    {
 -      y = 0;
 -    }
 +      if (y < 0)
 +      {
 +        y = 0;
 +      }
  
 -    if (x < 0)
 -    {
 -      System.err.println("x was " + x);
 -      x = 0;
 -    }
 +      if (x < 0)
 +      {
 +        System.err.println("x was " + x);
 +        x = 0;
 +      }
  
 -    vpRanges.setStartSeq(y);
 -    vpRanges.setEndSeq(y + vextent);
 -    vpRanges.setStartRes(x);
 -    vpRanges.setEndRes((x + (seqPanel.seqCanvas.getSize().width / av
 -            .getCharWidth())) - 1);
 +      hscroll.setValues(x, hextent, 0, width);
 +      vscroll.setValues(y, vextent, 0, height);
  
 -    hscroll.setValues(x, hextent, 0, width);
 -    vscroll.setValues(y, vextent, 0, height);
 +      // AWT scrollbar does not fire adjustmentValueChanged for setValues
 +      // so also call adjustment code!
 +      adjustHorizontal(x);
 +      adjustVertical(y);
  
 -    if (overviewPanel != null)
 -    {
 -      overviewPanel.setBoxPosition();
 +      sendViewPosition();
      }
 -    sendViewPosition();
 -
    }
  
 +  /**
 +   * Respond to adjustment event when horizontal or vertical scrollbar is
 +   * changed
 +   * 
 +   * @param evt
 +   *          adjustment event encoding whether apvscroll, hscroll or vscroll
 +   *          changed
 +   */
    @Override
    public void adjustmentValueChanged(AdjustmentEvent evt)
    {
  
    }
  
 +  /*
 +   * Set vertical scroll bar parameters for wrapped panel
 +   * @param res 
 +   *    the residue to scroll to
 +   */
 +  private void setScrollingForWrappedPanel(int res)
 +  {
 +    // get the width of the alignment in residues
 +    int maxwidth = av.getAlignment().getWidth();
 +    if (av.hasHiddenColumns())
 +    {
-       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
++      maxwidth = av.getAlignment().getHiddenColumns()
++              .findColumnPosition(maxwidth) - 1;
 +    }
 +
 +    // get the width of the canvas in residues
 +    int canvasWidth = seqPanel.seqCanvas
 +            .getWrappedCanvasWidth(seqPanel.seqCanvas.getSize().width);
 +    if (canvasWidth > 0)
 +    {
 +      // position we want to scroll to is number of canvasWidth's to get there
 +      int current = res / canvasWidth;
 +
 +      // max scroll position: add one because extent is 1 and scrollbar value
 +      // can only be set to at most max - extent
 +      int max = maxwidth / canvasWidth + 1;
 +      vscroll.setUnitIncrement(1);
 +      vscroll.setValues(current, 1, 0, max);
 +    }
 +  }
 +
    protected Panel sequenceHolderPanel = new Panel();
  
    protected Scrollbar vscroll = new Scrollbar();
Simple merge
Simple merge
   */
  package jalview.appletgui;
  
- import jalview.datamodel.SequenceI;
- import jalview.renderer.seqfeatures.FeatureColourFinder;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import jalview.viewmodel.OverviewDimensions;
+ import jalview.viewmodel.OverviewDimensionsHideHidden;
+ import jalview.viewmodel.OverviewDimensionsShowHidden;
 +import jalview.viewmodel.ViewportListenerI;
  
- import java.awt.Color;
+ import java.awt.BorderLayout;
+ import java.awt.CheckboxMenuItem;
  import java.awt.Dimension;
- import java.awt.Frame;
- import java.awt.Graphics;
- import java.awt.Image;
  import java.awt.Panel;
+ import java.awt.PopupMenu;
  import java.awt.event.ComponentAdapter;
  import java.awt.event.ComponentEvent;
+ import java.awt.event.InputEvent;
+ import java.awt.event.ItemEvent;
+ import java.awt.event.ItemListener;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.awt.event.MouseMotionListener;
@@@ -70,22 -60,15 +62,18 @@@ public class OverviewPanel extends Pane
      this.av = alPanel.av;
      this.ap = alPanel;
      setLayout(null);
-     nullFrame = new Frame();
-     nullFrame.addNotify();
  
-     sr = new SequenceRenderer(av);
-     sr.graphics = nullFrame.getGraphics();
-     sr.renderGaps = false;
-     sr.forOverview = true;
-     fr = new FeatureRenderer(av);
-     od = new OverviewDimensions(av.getRanges(),
+     od = new OverviewDimensionsShowHidden(av.getRanges(),
              (av.isShowAnnotation() && av.getSequenceConsensusHash() != null));
  
+     oviewCanvas = new OverviewCanvas(od, av);
+     setLayout(new BorderLayout());
+     add(oviewCanvas, BorderLayout.CENTER);
      setSize(new Dimension(od.getWidth(), od.getHeight()));
 +
 +    av.getRanges().addPropertyChangeListener(this);
 +
      addComponentListener(new ComponentAdapter()
      {
  
  
    private void mouseAction(MouseEvent evt)
    {
-     od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
-             .getHiddenSequences(), av.getColumnSelection(), av
-             .getRanges());
-     ap.paintAlignment(false);
+     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     {
+       if (!Platform.isAMac())
+       {
+         showPopupMenu(evt);
+       }
+     }
+     else
+     {
+       od.updateViewportFromMouse(evt.getX(), evt.getY(), av.getAlignment()
+               .getHiddenSequences(), av.getAlignment().getHiddenColumns());
 -      ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
+       ap.paintAlignment(false);
+     }
    }
  
    /**
     * changed
     * 
     */
 -  public void setBoxPosition()
 +  private void setBoxPosition()
    {
      od.setBoxPosition(av.getAlignment()
-             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+ .getHiddenSequences(), av
+             .getAlignment().getHiddenColumns());
      repaint();
    }
  
-   @Override
-   public void update(Graphics g)
-   {
-     paint(g);
-   }
-   @Override
-   public void paint(Graphics g)
+   /*
+    * Displays the popup menu and acts on user input
+    */
+   private void showPopupMenu(MouseEvent e)
    {
-     Graphics og = offscreen.getGraphics();
-     if (miniMe != null)
+     PopupMenu popup = new PopupMenu();
+     ItemListener menuListener = new ItemListener()
      {
-       og.drawImage(miniMe, 0, 0, this);
-       og.setColor(Color.red);
-       od.drawBox(og);
-       g.drawImage(offscreen, 0, 0, this);
-     }
+       @Override
+       public void itemStateChanged(ItemEvent e)
+       {
+         toggleHiddenColumns();
+       }
+     };
+     CheckboxMenuItem item = new CheckboxMenuItem(
+             MessageManager.getString("label.togglehidden"));
+     item.setState(showHidden);
+     popup.add(item);
+     item.addItemListener(menuListener);
+     this.add(popup);
+     popup.show(this, e.getX(), e.getY());
    }
  
 +  @Override
 +  public void propertyChange(PropertyChangeEvent evt)
 +  {
 +    setBoxPosition();
 +  }
 +
+   /*
+    * Toggle overview display between showing hidden columns and hiding hidden columns
+    */
+   private void toggleHiddenColumns()
+   {
+     if (showHidden)
+     {
+       showHidden = false;
+       od = new OverviewDimensionsHideHidden(av.getRanges(),
+               (av.isShowAnnotation() && av
+                       .getAlignmentConservationAnnotation() != null));
+     }
+     else
+     {
+       showHidden = true;
+       od = new OverviewDimensionsShowHidden(av.getRanges(),
+               (av.isShowAnnotation() && av
+                       .getAlignmentConservationAnnotation() != null));
+     }
+     oviewCanvas.resetOviewDims(od);
+     updateOverviewImage();
+   }
  }
Simple merge
Simple merge
@@@ -228,27 -232,28 +232,28 @@@ public class SeqPanel extends Panel imp
      else
      {
        ViewportRanges ranges = av.getRanges();
+       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
        while (seqCanvas.cursorY < ranges.getStartSeq())
        {
 -        ap.scrollUp(true);
 +        ranges.scrollUp(true);
        }
        while (seqCanvas.cursorY + 1 > ranges.getEndSeq())
        {
 -        ap.scrollUp(false);
 +        ranges.scrollUp(false);
        }
-       while (seqCanvas.cursorX < av.getColumnSelection()
-               .adjustForHiddenColumns(ranges.getStartRes()))
+       while (seqCanvas.cursorX < hidden.adjustForHiddenColumns(ranges
+               .getStartRes()))
        {
  
 -        if (!ap.scrollRight(false))
 +        if (!ranges.scrollRight(false))
          {
            break;
          }
        }
-       while (seqCanvas.cursorX > av.getColumnSelection()
-               .adjustForHiddenColumns(ranges.getEndRes()))
+       while (seqCanvas.cursorX > hidden.adjustForHiddenColumns(ranges
+               .getEndRes()))
        {
 -        if (!ap.scrollRight(true))
 +        if (!ranges.scrollRight(true))
          {
            break;
          }
Simple merge
Simple merge
@@@ -660,59 -707,62 +669,59 @@@ public class AlignmentPanel extends GAl
      {
        return;
      }
 -    int width = av.getAlignment().getWidth();
 -    int height = av.getAlignment().getHeight();
  
 -    if (av.hasHiddenColumns())
 +    if (av.getWrapAlignment())
      {
 -      // reset the width to exclude hidden columns
 -      width = av.getAlignment().getHiddenColumns()
 -              .findColumnPosition(width);
 +      setScrollingForWrappedPanel(x);
      }
 +    else
 +    {
 +      int width = av.getAlignment().getWidth();
 +      int height = av.getAlignment().getHeight();
  
 -    hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 -    vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 +      if (av.hasHiddenColumns())
 +      {
 +        // reset the width to exclude hidden columns
-         width = av.getColumnSelection().findColumnPosition(width);
++        width = av.getAlignment().getHiddenColumns().findColumnPosition(width);
 +      }
  
 -    if (hextent > width)
 -    {
 -      hextent = width;
 -    }
 +      hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 +      vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
  
 -    if (vextent > height)
 -    {
 -      vextent = height;
 -    }
 +      if (hextent > width)
 +      {
 +        hextent = width;
 +      }
  
 -    if ((hextent + x) > width)
 -    {
 -      x = width - hextent;
 -    }
 +      if (vextent > height)
 +      {
 +        vextent = height;
 +      }
  
 -    if ((vextent + y) > height)
 -    {
 -      y = height - vextent;
 -    }
 +      if ((hextent + x) > width)
 +      {
 +        x = width - hextent;
 +      }
  
 -    if (y < 0)
 -    {
 -      y = 0;
 -    }
 +      if ((vextent + y) > height)
 +      {
 +        y = height - vextent;
 +      }
  
 -    if (x < 0)
 -    {
 -      x = 0;
 -    }
 +      if (y < 0)
 +      {
 +        y = 0;
 +      }
  
 -    // update endRes after x has (possibly) been adjusted
 -    vpRanges.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av
 -            .getCharWidth())) - 1);
 +      if (x < 0)
 +      {
 +        x = 0;
 +      }
  
 -    /*
 -     * each scroll adjustment triggers adjustmentValueChanged, which resets the
 -     * 'do not scroll complement' flag; ensure it is the same for both
 -     * operations
 -     */
 -    boolean flag = isDontScrollComplement();
 -    hscroll.setValues(x, hextent, 0, width);
 -    setDontScrollComplement(flag);
 -    vscroll.setValues(y, vextent, 0, height);
 +      // update the scroll values
 +      hscroll.setValues(x, hextent, 0, width);
 +      vscroll.setValues(y, vextent, 0, height);
 +    }
    }
  
    /**
      validate();
  
      /*
 -     * set scroll bar positions; first suppress this being 'followed' in any
 -     * complementary split pane
 +     * set scroll bar positions
       */
 -    setDontScrollComplement(true);
 +    setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
 +  }
  
 -    if (av.getWrapAlignment())
 +  /*
 +   * Set vertical scroll bar parameters for wrapped panel
 +   * @param res 
 +   *    the residue to scroll to
 +   */
 +  private void setScrollingForWrappedPanel(int res)
 +  {
 +    // get the width of the alignment in residues
 +    int maxwidth = av.getAlignment().getWidth();
 +    if (av.hasHiddenColumns())
      {
-       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
 -      int maxwidth = av.getAlignment().getWidth();
 -
 -      if (av.hasHiddenColumns())
 -      {
+         maxwidth = av.getAlignment().getHiddenColumns()
+                 .findColumnPosition(maxwidth) - 1;
 -      }
 -
 -      int canvasWidth = getSeqPanel().seqCanvas
 -              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
 -      if (canvasWidth > 0)
 -      {
 -        int max = maxwidth
 -                / getSeqPanel().seqCanvas
 -                        .getWrappedCanvasWidth(getSeqPanel().seqCanvas
 -                                .getWidth()) + 1;
 -        vscroll.setMaximum(max);
 -        vscroll.setUnitIncrement(1);
 -        vscroll.setVisibleAmount(1);
 -      }
      }
 -    else
 +
 +    // get the width of the canvas in residues
 +    int canvasWidth = getSeqPanel().seqCanvas
 +            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
 +    if (canvasWidth > 0)
      {
 -      setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
 +      // position we want to scroll to is number of canvasWidth's to get there
 +      int current = res / canvasWidth;
 +
 +      // max scroll position: add one because extent is 1 and scrollbar value
 +      // can only be set to at most max - extent
 +      int max = maxwidth / canvasWidth + 1;
 +      vscroll.setUnitIncrement(1);
 +      vscroll.setValues(current, 1, 0, max);
      }
    }
  
      }
    }
  
 +  @Override
 +  /**
 +   * Property change event fired when a change is made to the viewport ranges 
 +   * object associated with this alignment panel's viewport
 +   */
 +  public void propertyChange(PropertyChangeEvent evt)
 +  {
 +    // update this panel's scroll values based on the new viewport ranges values
 +    int x = vpRanges.getStartRes();
 +    int y = vpRanges.getStartSeq();
 +    setScrollValues(x, y);
 +
 +    // now update any complementary alignment (its viewport ranges object
 +    // is different so does not get automatically updated)
 +    if (isSetToScrollComplementPanel())
 +    {
 +      setToScrollComplementPanel(false);
 +      av.scrollComplementaryAlignment();
 +      setToScrollComplementPanel(true);
 +    }
 +  }
++
+   /**
+    * Set the reference to the PCA/Tree chooser dialog for this panel. This
+    * reference should be nulled when the dialog is closed.
+    * 
+    * @param calculationChooser
+    */
+   public void setCalculationDialog(CalculationChooser calculationChooser)
+   {
+     calculationDialog = calculationChooser;
+   }
+   /**
+    * Returns the reference to the PCA/Tree chooser dialog for this panel (null
+    * if none is open)
+    */
+   public CalculationChooser getCalculationDialog()
+   {
+     return calculationDialog;
+   }
  }
Simple merge
Simple merge
Simple merge
Simple merge
   */
  package jalview.gui;
  
- import jalview.datamodel.SequenceI;
- import jalview.renderer.AnnotationRenderer;
- import jalview.renderer.seqfeatures.FeatureColourFinder;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import jalview.viewmodel.OverviewDimensions;
+ import jalview.viewmodel.OverviewDimensionsHideHidden;
+ import jalview.viewmodel.OverviewDimensionsShowHidden;
 +import jalview.viewmodel.ViewportListenerI;
  
- import java.awt.Color;
+ import java.awt.BorderLayout;
  import java.awt.Dimension;
- import java.awt.Graphics;
+ import java.awt.event.ActionEvent;
+ import java.awt.event.ActionListener;
  import java.awt.event.ComponentAdapter;
  import java.awt.event.ComponentEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseMotionAdapter;
- import java.awt.image.BufferedImage;
 +import java.beans.PropertyChangeEvent;
  
+ import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JPanel;
+ import javax.swing.JPopupMenu;
+ import javax.swing.SwingUtilities;
  
  /**
   * Panel displaying an overview of the full alignment, with an interactive box
   * @author $author$
   * @version $Revision$
   */
 -public class OverviewPanel extends JPanel implements Runnable
 +public class OverviewPanel extends JPanel implements Runnable,
 +        ViewportListenerI
  {
-   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
-   private final AnnotationRenderer renderer = new AnnotationRenderer();
    private OverviewDimensions od;
  
-   private BufferedImage miniMe;
-   private BufferedImage lastMiniMe = null;
+   private OverviewCanvas oviewCanvas;
  
    private AlignViewport av;
  
              (av.isShowAnnotation() && av
                      .getAlignmentConservationAnnotation() != null));
  
+     oviewCanvas = new OverviewCanvas(od, av);
+     setLayout(new BorderLayout());
+     add(oviewCanvas, BorderLayout.CENTER);
 +    av.getRanges().addPropertyChangeListener(this);
 +
      addComponentListener(new ComponentAdapter()
      {
        @Override
        @Override
        public void mouseDragged(MouseEvent evt)
        {
-         if (!av.getWrapAlignment())
+         if (!SwingUtilities.isRightMouseButton(evt)
+                 && !av.getWrapAlignment())
          {
            od.updateViewportFromMouse(evt.getX(), evt.getY(), av
-                   .getAlignment().getHiddenSequences(), av
-                   .getColumnSelection(), av.getRanges());
-           // TODO set via ViewportRanges in overview dimensions once JAL-2388 is
-           // merged
+                   .getAlignment().getHiddenSequences(), av.getAlignment()
+                   .getHiddenColumns());
 -          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
++
          }
        }
      });
        @Override
        public void mousePressed(MouseEvent evt)
        {
-         if (!av.getWrapAlignment())
+         if (SwingUtilities.isRightMouseButton(evt))
+         {
+           if (!Platform.isAMac())
+           {
+             showPopupMenu(evt);
+           }
+         }
+         else if (!av.getWrapAlignment())
          {
            od.updateViewportFromMouse(evt.getX(), evt.getY(), av
-                   .getAlignment().getHiddenSequences(), av
-                   .getColumnSelection(), av.getRanges());
-           // TODO set via ViewportRanges in overview dimensions once JAL-2388 is
-           // merged
+                   .getAlignment().getHiddenSequences(), av.getAlignment()
+                   .getHiddenColumns());
 -          ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
+         }
+       }
+       @Override
+       public void mouseClicked(MouseEvent evt)
+       {
+         if (SwingUtilities.isRightMouseButton(evt))
+         {
+           showPopupMenu(evt);
          }
        }
      });
    }
  
    /*
-    * Build the overview panel image
+    * Toggle overview display between showing hidden columns and hiding hidden columns
     */
-   private void buildImage(float sampleRow, float sampleCol)
+   private void toggleHiddenColumns()
    {
-     int lastcol = -1;
-     int lastrow = -1;
-     int rgbColour = Color.white.getRGB();
-     SequenceI seq = null;
-     FeatureColourFinder finder = new FeatureColourFinder(fr);
-     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++)
+     if (showHidden)
      {
-       boolean doCopy = true;
-       int currentrow = (int) (row * sampleRow);
-       if (currentrow != lastrow)
-       {
-         doCopy = false;
-         lastrow = currentrow;
-         // 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);
-       }
-       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
-       {
-         if (doCopy)
-         {
-           rgbColour = miniMe.getRGB(col, row - 1);
-         }
-         else if ((int) (col * sampleCol) != lastcol
-                 || (int) (row * sampleRow) != lastrow)
-         {
-           lastcol = (int) (col * sampleCol);
-           rgbColour = getColumnColourFromSequence(seq, hiddenRow,
-                   hasHiddenCols, lastcol, finder);
-         }
-         // else we just use the color we already have , so don't need to set it
-         miniMe.setRGB(col, row, rgbColour);
-       }
+       showHidden = false;
+       od = new OverviewDimensionsHideHidden(av.getRanges(),
+               (av.isShowAnnotation() && av
+                       .getAlignmentConservationAnnotation() != null));
+     }
+     else
+     {
+       showHidden = true;
+       od = new OverviewDimensionsShowHidden(av.getRanges(),
+               (av.isShowAnnotation() && av
+                       .getAlignmentConservationAnnotation() != null));
      }
+     oviewCanvas.resetOviewDims(od);
+     updateOverviewImage();
++    setBoxPosition();
    }
  
-   /*
-    * Find the colour of a sequence at a specified column position
+   /**
+    * Updates the overview image when the related alignment panel is updated
     */
-   private int getColumnColourFromSequence(
-           jalview.datamodel.SequenceI seq,
-           boolean hiddenRow, boolean hasHiddenCols, int lastcol,
-           FeatureColourFinder finder)
+   public void updateOverviewImage()
    {
-     Color color = Color.white;
-     if ((seq != null) && (seq.getLength() > lastcol))
+     if ((getWidth() > 0) && (getHeight() > 0))
      {
-       color = sr.getResidueColour(seq, lastcol, finder);
+       od.setWidth(getWidth());
+       od.setHeight(getHeight());
      }
+     
+     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
  
-     if (hiddenRow
-             || (hasHiddenCols && !av.getColumnSelection()
-                     .isVisible(lastcol)))
+     if (oviewCanvas.restartDraw())
      {
-       color = color.darker().darker();
+       return;
      }
  
-     return color.getRGB();
+     Thread thread = new Thread(this);
+     thread.start();
+     repaint();
+   }
+   @Override
+   public void run()
+   {
+     oviewCanvas.draw(av.isShowSequenceFeatures(),
+             (av.isShowAnnotation() && av
+                     .getAlignmentConservationAnnotation() != null), ap
+                     .getSeqPanel().seqCanvas.getFeatureRenderer());
+     setBoxPosition();
    }
  
    /**
     * changed
     * 
     */
 -  public void setBoxPosition()
 +  private void setBoxPosition()
    {
-     od.setBoxPosition(av.getAlignment()
-             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
+     od.setBoxPosition(av.getAlignment().getHiddenSequences(), av
+             .getAlignment().getHiddenColumns());
      repaint();
    }
 +
-   @Override
-   public void paintComponent(Graphics g)
-   {
-     if (resizing || resizeAgain)
-     {
-       if (lastMiniMe == null)
-       {
-         g.setColor(Color.white);
-         g.fillRect(0, 0, getWidth(), getHeight());
-       }
-       else
-       {
-         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
-       }
-       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(TRANS_GREY);
-         g.fillRect(0, 0, getWidth(), getHeight());
-       }
-     }
-     g.setColor(Color.red);
-     od.drawBox(g);
-   }
 +  @Override
 +  public void propertyChange(PropertyChangeEvent evt)
 +  {
 +    setBoxPosition();
 +  }
  }
Simple merge
Simple merge
Simple merge
@@@ -25,49 -28,27 +28,44 @@@ import jalview.datamodel.HiddenSequence
  
  import java.awt.Graphics;
  
- public class OverviewDimensions
+ public abstract class OverviewDimensions
  {
-   // Default width and height values
-   private static final int DEFAULT_GRAPH_HEIGHT = 20;
+   protected static final int MAX_WIDTH = 400;
++
+   protected static final int MIN_WIDTH = 120;
 +
-   private static final int MAX_WIDTH = 400;
+   protected static final int MIN_SEQ_HEIGHT = 40;
 +
-   private static final int MIN_WIDTH = 120;
+   protected static final int MAX_SEQ_HEIGHT = 300;
  
-   private static final int MIN_SEQ_HEIGHT = 40;
+   private static final int DEFAULT_GRAPH_HEIGHT = 20;
  
-   private static final int MAX_SEQ_HEIGHT = 300;
+   protected int width;
 +
-   // width of the overview panel
-   private int width;
+   protected int sequencesHeight;
 +
-   // height of sequences part of the overview panel
-   private int sequencesHeight;
+   protected int graphHeight = DEFAULT_GRAPH_HEIGHT;
 +
-   // height of the graphs part of the overview panel
-   private int graphHeight = DEFAULT_GRAPH_HEIGHT;
+   protected int boxX = -1;
 +
-   // dimensions of box outlining current extent of view in alignment panel
-   // location of left side of box
-   private int boxX = -1;
+   protected int boxY = -1;
 +
-   // location of bottom of box
-   private int boxY = -1;
+   protected int boxWidth = -1;
 +
-   // width of box
-   private int boxWidth = -1;
+   protected int boxHeight = -1;
 -  protected int scrollCol = -1;
 -  protected int scrollRow = -1;
 +
-   // height of box
-   private int boxHeight = -1;
+   protected int alwidth;
++
+   protected int alheight;
  
 +  /**
 +   * Create an OverviewDimensions object
 +   * 
 +   * @param ranges
 +   *          positional properties of the viewport
 +   * @param showAnnotationPanel
 +   *          true if the annotation panel is to be shown, false otherwise
 +   */
    public OverviewDimensions(ViewportRanges ranges,
            boolean showAnnotationPanel)
    {
      g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
    }
  
-   // TODO should be removed, when unit test has mock Graphics object available
-   // to check boxX/boxY
 -  public int getScrollCol()
 -  {
 -    return scrollCol;
 -  }
 -
 -  public int getScrollRow()
 -  {
 -    return scrollRow;
 -  }
 -
    public int getBoxX()
    {
      return boxX;
index 0000000,b03f9ac..9236151
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,137 +1,137 @@@
+ package jalview.viewmodel;
+ import jalview.api.AlignmentColsCollectionI;
+ import jalview.api.AlignmentRowsCollectionI;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.HiddenSequences;
+ import jalview.datamodel.VisibleColsCollection;
+ import jalview.datamodel.VisibleRowsCollection;
+ public class OverviewDimensionsHideHidden extends OverviewDimensions
+ {
+   private ViewportRanges ranges;
+   public OverviewDimensionsHideHidden(ViewportRanges vpranges,
+           boolean showAnnotationPanel)
+   {
+     super(vpranges, showAnnotationPanel);
+     ranges = vpranges;
+     resetAlignmentDims();
+   }
+   @Override
+   public void updateViewportFromMouse(int mousex, int mousey,
+           HiddenSequences hiddenSeqs, HiddenColumns hiddenCols)
+   {
+     resetAlignmentDims();
+     int x = mousex;
+     int y = mousey;
+     if (x < 0)
+     {
+       x = 0;
+     }
+     if (y < 0)
+     {
+       y = 0;
+     }
+     //
+     // Convert x value to residue position
+     //
+     // need to determine where scrollCol should be, given x
+     // to do this also need to know width of viewport, and some hidden column
+     // correction
+     // convert x to residues - this is an absolute position
+     int xAsRes = Math.round((float) x * alwidth / width);
+     // get viewport width in residues
 -    int vpwidth = ranges.getEndRes() - ranges.getStartRes() + 1;
++    int vpwidth = ranges.getViewportWidth();
+     if (xAsRes + vpwidth > alwidth)
+     {
+       // went past the end of the alignment, adjust backwards
+       // if last position was before the end of the alignment, need to update
 -      if ((scrollCol + vpwidth - 1) < alwidth)
++      if ((ranges.getEndRes() + vpwidth - 1) < alwidth)
+       {
+         xAsRes = alwidth - vpwidth;
+       }
+       else
+       {
 -        xAsRes = scrollCol;
++        xAsRes = ranges.getEndRes();
+       }
+     }
+     //
+     // Convert y value to sequence position
+     //
+     // convert y to residues
+     int yAsSeq = Math.round((float) y * alheight / sequencesHeight);
+     // get viewport height in sequences
+     // add 1 because height includes both endSeq and startSeq
 -    int vpheight = ranges.getEndSeq() - ranges.getStartSeq() + 1;
++    int vpheight = ranges.getViewportHeight();
+     if (yAsSeq + vpheight > alheight)
+     {
+       // went past the end of the alignment, adjust backwards
 -      if ((scrollRow + vpheight - 1) < alheight)
++      if ((ranges.getEndSeq() + vpheight - 1) < alheight)
+       {
+         yAsSeq = alheight - vpheight;
+       }
+       else
+       {
 -        yAsSeq = scrollRow;
++        yAsSeq = ranges.getEndSeq();
+       }
+     }
 -    // update scroll values
 -    scrollCol = xAsRes;
 -    scrollRow = yAsSeq;
++    // update viewport
++    ranges.setStartRes(xAsRes);
++    ranges.setStartSeq(yAsSeq);
+   }
+   @Override
+   public void setBoxPosition(HiddenSequences hiddenSeqs,
+           HiddenColumns hiddenCols)
+   {
+     // work with visible values of startRes and endRes
+     int startRes = ranges.getStartRes();
+     int endRes = ranges.getEndRes();
+     // work with visible values of startSeq and endSeq
+     int startSeq = ranges.getStartSeq();
+     int endSeq = ranges.getEndSeq();
+     setBoxPosition(startRes, endRes, startSeq, endSeq);
+   }
+   @Override
+   public AlignmentColsCollectionI getColumns(AlignmentI al)
+   {
+     return new VisibleColsCollection(0,
+             ranges.getAbsoluteAlignmentWidth() - 1, al);
+   }
+   @Override
+   public AlignmentRowsCollectionI getRows(AlignmentI al)
+   {
+     return new VisibleRowsCollection(0,
+             ranges.getAbsoluteAlignmentHeight() - 1, al);
+   }
+   @Override
+   protected void resetAlignmentDims()
+   {
+     alwidth = ranges.getVisibleAlignmentWidth();
+     alheight = ranges.getVisibleAlignmentHeight();
+   }
+ }
index 0000000,b897189..4f7d6c2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,211 +1,210 @@@
+ /*
+  * 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.viewmodel;
+ import jalview.api.AlignmentColsCollectionI;
+ import jalview.api.AlignmentRowsCollectionI;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.AllColsCollection;
+ import jalview.datamodel.AllRowsCollection;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.HiddenSequences;
+ public class OverviewDimensionsShowHidden extends OverviewDimensions
+ {
+   private ViewportRanges ranges;
+   /**
+    * Create an OverviewDimensions object
+    * 
+    * @param ranges
+    *          positional properties of the viewport
+    * @param showAnnotationPanel
+    *          true if the annotation panel is to be shown, false otherwise
+    */
+   public OverviewDimensionsShowHidden(ViewportRanges vpranges,
+           boolean showAnnotationPanel)
+   {
+     super(vpranges, showAnnotationPanel);
+     ranges = vpranges;
+     resetAlignmentDims();
+   }
+   /**
+    * Check box dimensions and scroll positions and correct if necessary
+    * 
+    * @param mousex
+    *          x position in overview panel
+    * @param mousey
+    *          y position in overview panel
+    * @param hiddenSeqs
+    *          hidden sequences
+    * @param hiddenCols
+    *          hidden columns
+    * @param ranges
+    *          viewport position properties
+    */
+   @Override
+   public void updateViewportFromMouse(int mousex, int mousey,
+           HiddenSequences hiddenSeqs, HiddenColumns hiddenCols)
+   {
+     int x = mousex;
+     int y = mousey;
+     resetAlignmentDims();
+     if (x < 0)
+     {
+       x = 0;
+     }
+     if (y < 0)
+     {
+       y = 0;
+     }
+     //
+     // Convert x value to residue position
+     //
+     // need to determine where scrollCol should be, given x
+     // to do this also need to know width of viewport, and some hidden column
+     // correction
+     // convert x to residues - this is an absolute position
+     int xAsRes = Math.round((float) x * alwidth / width);
+     // get viewport width in residues
 -    int vpwidth = ranges.getEndRes() - ranges.getStartRes() + 1;
++    int vpwidth = ranges.getViewportWidth();
+     // get where x should be when accounting for hidden cols
+     // if x is in a hidden col region, shift to left - but we still need
+     // absolute position
+     // so convert back after getting visible region position
+     int visXAsRes = hiddenCols.findColumnPosition(xAsRes);
+     // check in case we went off the edge of the alignment
+     int visAlignWidth = hiddenCols.findColumnPosition(alwidth - 1);
+     if (visXAsRes + vpwidth - 1 > visAlignWidth)
+     {
+       // went past the end of the alignment, adjust backwards
+       // if last position was before the end of the alignment, need to update
 -      if ((scrollCol + vpwidth - 1) < visAlignWidth)
++      if ((ranges.getEndRes() + vpwidth - 1) < visAlignWidth)
+       {
+         visXAsRes = hiddenCols.findColumnPosition(hiddenCols
+                 .subtractVisibleColumns(vpwidth - 1, alwidth - 1));
+       }
+       else
+       {
 -        visXAsRes = scrollCol;
++        visXAsRes = ranges.getEndRes();
+       }
+     }
+     //
+     // Convert y value to sequence position
+     //
+     // convert y to residues
+     int yAsSeq = Math.round((float) y * alheight / sequencesHeight);
+     // get viewport height in sequences
 -    // add 1 because height includes both endSeq and startSeq
 -    int vpheight = ranges.getEndSeq() - ranges.getStartSeq() + 1;
++    int vpheight = ranges.getViewportHeight();
+     // get where y should be when accounting for hidden rows
+     // if y is in a hidden row region, shift up - but we still need absolute
+     // position,
+     // so convert back after getting visible region position
+     yAsSeq = hiddenSeqs.adjustForHiddenSeqs(hiddenSeqs
+             .findIndexWithoutHiddenSeqs(yAsSeq));
+     // check in case we went off the edge of the alignment
+     int visAlignHeight = hiddenSeqs.findIndexWithoutHiddenSeqs(alheight);
+     int visYAsSeq = hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq);
+     if (visYAsSeq + vpheight - 1 > visAlignHeight)
+     {
+       // went past the end of the alignment, adjust backwards
 -      if ((scrollRow + vpheight - 1) < visAlignHeight)
++      if ((ranges.getEndSeq() + vpheight - 1) < visAlignHeight)
+       {
+         visYAsSeq = hiddenSeqs.findIndexWithoutHiddenSeqs(hiddenSeqs
+                 .subtractVisibleRows(vpheight - 1, alheight - 1));
+       }
+       else
+       {
 -        visYAsSeq = scrollRow;
++        visYAsSeq = ranges.getEndSeq();
+       }
+     }
 -    // update scroll values
 -    scrollCol = visXAsRes;
 -    scrollRow = visYAsSeq;
++    // update viewport
++    ranges.setStartRes(xAsRes);
++    ranges.setStartSeq(yAsSeq);
+   }
+   /**
+    * Update the overview panel box when the associated alignment panel is
+    * changed
+    * 
+    * @param hiddenSeqs
+    *          hidden sequences
+    * @param hiddenCols
+    *          hidden columns
+    * @param ranges
+    *          viewport position properties
+    */
+   @Override
+   public void setBoxPosition(HiddenSequences hiddenSeqs,
+           HiddenColumns hiddenCols)
+   {
+     // work with absolute values of startRes and endRes
+     int startRes = hiddenCols
+             .adjustForHiddenColumns(ranges.getStartRes());
+     int endRes = hiddenCols.adjustForHiddenColumns(ranges.getEndRes());
+     // work with absolute values of startSeq and endSeq
+     int startSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getStartSeq());
+     int endSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getEndSeq());
+     setBoxPosition(startRes, endRes, startSeq, endSeq);
+   }
+   @Override
+   public AlignmentColsCollectionI getColumns(AlignmentI al)
+   {
+     return new AllColsCollection(0,
+             ranges.getAbsoluteAlignmentWidth() - 1, al);
+   }
+   @Override
+   public AlignmentRowsCollectionI getRows(AlignmentI al)
+   {
+     return new AllRowsCollection(0,
+             ranges.getAbsoluteAlignmentHeight() - 1,
+             al);
+   }
+   @Override
+   protected void resetAlignmentDims()
+   {
+     alwidth = ranges.getAbsoluteAlignmentWidth();
+     alheight = ranges.getAbsoluteAlignmentHeight();
+   }
+ }
   */
  package jalview.viewmodel;
  
 +import jalview.api.AlignViewportI;
  import jalview.datamodel.AlignmentI;
++import jalview.datamodel.HiddenColumns;
  
  /**
 - * Embryonic class which: Supplies and updates viewport properties relating to
 - * position such as: start and end residues and sequences; ideally will serve
 - * hidden columns/rows too. Intention also to support calculations for
 - * positioning, scrolling etc. such as finding the middle of the viewport,
 + * Slightly less embryonic class which: Supplies and updates viewport properties
 + * relating to position such as: start and end residues and sequences; ideally
 + * will serve hidden columns/rows too. Intention also to support calculations
 + * for positioning, scrolling etc. such as finding the middle of the viewport,
   * checking for scrolls off screen
   */
  public class ViewportRanges extends ViewportProperties
    }
  
    /**
+    * Get alignment width in cols, excluding hidden cols
+    */
+   public int getVisibleAlignmentWidth()
+   {
+     return al.getWidth() - al.getHiddenColumns().getSize();
+   }
+   /**
+    * Get alignment height in rows, excluding hidden rows
+    */
+   public int getVisibleAlignmentHeight()
+   {
+     return al.getHeight();
+   }
+   /**
 -   * Set first residue visible in the viewport
 +   * Set first residue visible in the viewport, and retain the current width.
 +   * Fires a property change event.
     * 
     * @param res
     *          residue position
    {
      return endSeq;
    }
 +
 +  /**
 +   * Set viewport width in residues, without changing startRes. Use in
 +   * preference to calculating endRes from the width, to avoid out by one
 +   * errors! Fires a property change event.
 +   * 
 +   * @param w
 +   *          width in residues
 +   */
 +  public void setViewportWidth(int w)
 +  {
 +    setStartEndRes(startRes, startRes + w - 1);
 +  }
 +
 +  /**
 +   * Set viewport height in residues, without changing startSeq. Use in
 +   * preference to calculating endSeq from the height, to avoid out by one
 +   * errors! Fires a property change event.
 +   * 
 +   * @param h
 +   *          height in sequences
 +   */
 +  public void setViewportHeight(int h)
 +  {
 +    setStartEndSeq(startSeq, startSeq + h - 1);
 +  }
 +
 +  /**
 +   * Set viewport horizontal start position and width. Use in preference to
 +   * calculating endRes from the width, to avoid out by one errors! Fires a
 +   * property change event.
 +   * 
 +   * @param start
 +   *          start residue
 +   * @param w
 +   *          width in residues
 +   */
 +  public void setViewportStartAndWidth(int start, int w)
 +  {
 +    int vpstart = start;
 +    if (vpstart < 0)
 +    {
 +      vpstart = 0;
 +    }
 +    else if (vpstart + w - 1 > al.getWidth() - 1)
 +    {
 +      vpstart = al.getWidth() - 1;
 +    }
 +    setStartEndRes(vpstart, vpstart + w - 1);
 +  }
 +
 +  /**
 +   * Set viewport vertical start position and height. Use in preference to
 +   * calculating endSeq from the height, to avoid out by one errors! Fires a
 +   * property change event.
 +   * 
 +   * @param start
 +   *          start sequence
 +   * @param h
 +   *          height in sequences
 +   */
 +  public void setViewportStartAndHeight(int start, int h)
 +  {
 +    int vpstart = start;
 +    if (vpstart < 0)
 +    {
 +      vpstart = 0;
 +    }
 +    else if (vpstart + h - 1 > al.getHeight() - 1)
 +    {
 +      vpstart = al.getHeight() - h;
 +    }
 +    setStartEndSeq(vpstart, vpstart + h - 1);
 +  }
 +
 +  /**
 +   * Get width of viewport in residues
 +   * 
 +   * @return width of viewport
 +   */
 +  public int getViewportWidth()
 +  {
 +    return (endRes - startRes + 1);
 +  }
 +
 +  /**
 +   * Get height of viewport in residues
 +   * 
 +   * @return height of viewport
 +   */
 +  public int getViewportHeight()
 +  {
 +    return (endSeq - startSeq + 1);
 +  }
 +
 +  /**
 +   * Scroll the viewport range vertically. Fires a property change event.
 +   * 
 +   * @param up
 +   *          true if scrolling up, false if down
 +   * 
 +   * @return true if the scroll is valid
 +   */
 +  public boolean scrollUp(boolean up)
 +  {
 +    if (up)
 +    {
 +      if (startSeq < 1)
 +      {
 +        return false;
 +      }
 +
 +      setStartSeq(startSeq - 1);
 +    }
 +    else
 +    {
 +      if (endSeq >= al.getHeight() - 1)
 +      {
 +        return false;
 +      }
 +
 +      setStartSeq(startSeq + 1);
 +    }
 +    return true;
 +  }
 +
 +  /**
 +   * Scroll the viewport range horizontally. Fires a property change event.
 +   * 
 +   * @param right
 +   *          true if scrolling right, false if left
 +   * 
 +   * @return true if the scroll is valid
 +   */
 +  public boolean scrollRight(boolean right)
 +  {
 +    if (!right)
 +    {
 +      if (startRes < 1)
 +      {
 +        return false;
 +      }
 +
 +      setStartRes(startRes - 1);
 +    }
 +    else
 +    {
 +      if (endRes > al.getWidth() - 1)
 +      {
 +        return false;
 +      }
 +
 +      setStartRes(startRes + 1);
 +    }
 +
 +    return true;
 +  }
 +
 +  /**
 +   * Scroll a wrapped alignment so that the specified residue is visible. Fires
 +   * a property change event.
 +   * 
 +   * @param res
 +   *          residue position to scroll to
 +   */
 +  public void scrollToWrappedVisible(int res)
 +  {
 +    // get the start residue of the wrapped row which res is in
 +    // and set that as our start residue
 +    int width = getViewportWidth();
 +    setStartRes((res / width) * width);
 +  }
 +
 +  /**
 +   * Scroll so that (x,y) is visible. Fires a property change event.
 +   * 
 +   * @param x
 +   *          x position in alignment
 +   * @param y
 +   *          y position in alignment
 +   * @param av
 +   *          viewport to be visible in. Here until hidden columns JAL-2388
 +   *          merged, then use alignment to get hidden cols
 +   */
 +  public void scrollToVisible(int x, int y, AlignViewportI av)
 +  {
 +    while (y < startSeq)
 +    {
 +      scrollUp(true);
 +    }
 +    while (y > endSeq)
 +    {
 +      scrollUp(false);
 +    }
 +
-     while (x < av.getColumnSelection().adjustForHiddenColumns(startRes))
++    HiddenColumns hidden = al.getHiddenColumns();
++    while (x < hidden.adjustForHiddenColumns(startRes))
 +    {
 +      if (!scrollRight(false))
 +      {
 +        break;
 +      }
 +    }
-     while (x > av.getColumnSelection().adjustForHiddenColumns(endRes))
++    while (x > hidden.adjustForHiddenColumns(endRes))
 +    {
 +      if (!scrollRight(true))
 +      {
 +        break;
 +      }
 +    }
 +  }
 +  
 +  /**
 +   * Adjust sequence position for page up. Fires a property change event.
 +   */
 +  public void pageUp()
 +  {
 +    setViewportStartAndHeight(2 * startSeq - endSeq, getViewportHeight());
 +  }
 +  
 +  /**
 +   * Adjust sequence position for page down. Fires a property change event.
 +   */
 +  public void pageDown()
 +  {
 +    setViewportStartAndHeight(endSeq, getViewportHeight());
 +  }
  }
index 0000000,2bd9c47..0e931eb
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,989 +1,982 @@@
+ /*
+  * 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.viewmodel;
+ import static org.testng.Assert.assertEquals;
+ import jalview.analysis.AlignmentGenerator;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceCollectionI;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import java.util.Hashtable;
+ import org.testng.annotations.AfterClass;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ @Test(singleThreaded = true)
+ public class OverviewDimensionsHideHiddenTest
+ {
+   AlignmentI al;
+   OverviewDimensionsHideHidden od;
+   // cached widths and heights
+   int boxWidth;
+   int boxHeight;
+   int viewHeight;
+   int viewWidth;
+   int alheight;
+   int alwidth;
+   ViewportRanges vpranges;
+   Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+   HiddenColumns hiddenCols = new HiddenColumns();
+   @BeforeClass(alwaysRun = true)
+   public void setUpAlignment()
+   {
+     // create random alignment
+     AlignmentGenerator gen = new AlignmentGenerator(false);
+     al = gen.generate(157, 525, 123, 5, 5);
+   }
+   @BeforeMethod(alwaysRun = true)
+   public void setUp()
+   {
+     if (!hiddenRepSequences.isEmpty())
+     {
+       al.getHiddenSequences().showAll(hiddenRepSequences);
+     }
+     ColumnSelection colsel = new ColumnSelection();
+     hiddenCols.revealAllHiddenColumns(colsel);
+     
+     vpranges = new ViewportRanges(al);
 -    vpranges.setStartRes(0);
 -    vpranges.setEndRes(62);
 -    vpranges.setStartSeq(0);
 -    vpranges.setEndSeq(17);
++    vpranges.setViewportStartAndHeight(0, 18);
++    vpranges.setViewportStartAndWidth(0, 63);
+     viewHeight = vpranges.getEndSeq() - vpranges.getStartSeq() + 1;
+     viewWidth = vpranges.getEndRes() - vpranges.getStartRes() + 1;
+     HiddenColumns hiddenCols = new HiddenColumns();
+     od = new OverviewDimensionsHideHidden(vpranges, true);
+     // Initial box sizing - default path through code
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     mouseClick(od, 0, 0);
+     moveViewport(0, 0);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+   }
+   @AfterClass(alwaysRun = true)
+   public void cleanUp()
+   {
+     al = null;
+   }
+   /**
+    * Test that the OverviewDimensions constructor sets width and height
+    * correctly
+    */
+   @Test(groups = { "Functional" })
+   public void testConstructor()
+   {
+     SequenceI seqa = new Sequence("Seq1", "ABC");
+     SequenceI seqb = new Sequence("Seq2", "ABC");
+     SequenceI seqc = new Sequence("Seq3", "ABC");
+     SequenceI seqd = new Sequence("Seq4", "ABC");
+     SequenceI seqe = new Sequence("Seq5",
+             "ABCABCABCABCABCABCABCABCBACBACBACBAC");
+     int defaultGraphHeight = 20;
+     int maxWidth = 400;
+     int minWidth = 120;
+     int maxSeqHeight = 300;
+     int minSeqHeight = 40;
+     // test for alignment with width > height
+     SequenceI[] seqs1 = new SequenceI[] { seqa, seqb };
+     Alignment al1 = new Alignment(seqs1);
+     ViewportRanges props = new ViewportRanges(al1);
+     OverviewDimensions od = new OverviewDimensionsHideHidden(props, true);
+     int scaledHeight = 267;
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), scaledHeight);
+     assertEquals(od.getWidth(), maxWidth);
+     assertEquals(od.getHeight(), scaledHeight + defaultGraphHeight);
+     // test for alignment with width < height
+     SequenceI[] seqs2 = new SequenceI[] { seqa, seqb, seqc, seqd };
+     Alignment al2 = new Alignment(seqs2);
+     props = new ViewportRanges(al2);
+     od = new OverviewDimensionsHideHidden(props, true);
+     int scaledWidth = 300;
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), scaledWidth);
+     assertEquals(od.getHeight(), scaledWidth + defaultGraphHeight);
+     // test for alignment with width > height and sequence height scaled below
+     // min value
+     SequenceI[] seqs3 = new SequenceI[] { seqe };
+     Alignment al3 = new Alignment(seqs3);
+     props = new ViewportRanges(al3);
+     od = new OverviewDimensionsHideHidden(props, true);
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), minSeqHeight);
+     assertEquals(od.getWidth(), maxWidth);
+     assertEquals(od.getHeight(), minSeqHeight + defaultGraphHeight);
+     // test for alignment with width < height and width scaled below min value
+     SequenceI[] seqs4 = new SequenceI[] { seqa, seqb, seqc, seqd, seqa,
+         seqb, seqc, seqd, seqa, seqb, seqc, seqd, seqa, seqb, seqc, seqd };
+     Alignment al4 = new Alignment(seqs4);
+     props = new ViewportRanges(al4);
+     od = new OverviewDimensionsHideHidden(props, true);
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), minWidth);
+     assertEquals(od.getHeight(), maxSeqHeight + defaultGraphHeight);
+     Alignment al5 = new Alignment(seqs4);
+     props = new ViewportRanges(al5);
+     od = new OverviewDimensionsHideHidden(props, false);
+     assertEquals(od.getGraphHeight(), 0);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), minWidth);
+     assertEquals(od.getHeight(), maxSeqHeight);
+   }
+   /**
+    * Test that validation after mouse adjustments to boxX and boxY sets box
+    * dimensions and scroll values correctly, when there are no hidden rows or
+    * columns.
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromMouseClick()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // negative boxX value reset to 0
+     mouseClick(od, -5, 10);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollRow(),
++    assertEquals(vpranges.getStartSeq(),
+             Math.round((float) 10 * alheight / od.getSequencesHeight()));
 -    assertEquals(od.getScrollCol(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
+     // negative boxY value reset to 0
+     mouseClick(od, 6, -2);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) 6 * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // overly large boxX value reset to width-boxWidth
+     mouseClick(od, 100, 6);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxY(), 6);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // overly large boxY value reset to sequenceHeight - boxHeight
+     mouseClick(od, 10, 520);
+     assertEquals(od.getBoxX(), 10);
+     assertEquals(od.getBoxY(), od.getSequencesHeight() - od.getBoxHeight());
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+     // here (float) od.getBoxY() * alheight / od.getSequencesHeight() = 507.5
+     // and round rounds to 508; however we get 507 working with row values
+     // hence the subtraction of 1
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()) - 1);
+     // click past end of alignment, as above
+     mouseClick(od, 3000, 5);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // move viewport so startRes non-zero and then mouseclick
+     moveViewportH(50);
+     // click at viewport position
+     int oldboxx = od.getBoxX();
+     int oldboxy = od.getBoxY();
+     mouseClick(od, od.getBoxX() + 5, od.getBoxY() + 2);
+     assertEquals(od.getBoxX(), oldboxx + 5);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+     assertEquals(od.getBoxY(), oldboxy + 2);
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // click at top corner
+     mouseClick(od, 0, 0);
+     assertEquals(od.getBoxX(), 0);
 -    assertEquals(od.getScrollCol(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
+     assertEquals(od.getBoxY(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols at the start
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsAtStart()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide cols at start and check updated box position is correct
+     int lastHiddenCol = 30;
+     hiddenCols.hideColumns(0, lastHiddenCol);
+     testBoxIsAtClickPoint(0, 0);
+     // click to right of hidden columns, box moves to click point
+     testBoxIsAtClickPoint(40, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) 40 * alwidth / od.getWidth()));
+     // click to right of hidden columns such that box runs over right hand side
+     // of alignment
+     // box position is adjusted away from the edge
+     // overly large boxX value reset to width-boxWidth
+     int xpos = 100;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols in the middle
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsInMiddle()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     testBoxIsAtClickPoint(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     
+     // hide columns 63-73, no change to box position or dimensions
+     int firstHidden = 63;
+     int lastHidden = 73;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     testBoxIsAtClickPoint(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so that it overlaps with hidden cols on one side
+     // box width, boxX and scrollCol as for unhidden case
+     int xpos = 55 - boxWidth; // 55 is position in overview approx halfway
+                               // between cols 60 and 70
+     mouseClick(od, xpos, 0);
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round(xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so that it completely covers hidden cols
+     // box width, boxX and scrollCol as for unhidden case
+     xpos = 33;
+     mouseClick(od, xpos, 0);
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so boxX is in hidden cols, box overhangs at right
+     // boxX and scrollCol at left of hidden area, box width unchanged
+     xpos = 50;
+     mouseClick(od, xpos, 0);
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so boxX is to right of hidden cols, but does not go beyond full
+     // width of alignment
+     // box width, boxX and scrollCol all as for non-hidden case
+     xpos = 75;
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round(xpos * alwidth / od.getWidth()));
+     
+     // move box so it goes beyond full width of alignment
+     // boxX, scrollCol adjusted back, box width normal
+     xpos = 3000;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols at the end of
+    * the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsAtEnd()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide columns 140-164, no change to box position or dimensions
+     int firstHidden = 140;
+     int lastHidden = 164;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // click to left of hidden cols, without overlapping
+     // boxX, scrollCol and width as normal
+     int xpos = 5;
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
+     // click to left of hidden cols, with overlap
+     // boxX and scrollCol adjusted for hidden cols, width normal
+     xpos = Math.round((float) 145 * od.getWidth() / alwidth) - boxWidth;
+     mouseClick(od, xpos, 0);
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // click off end of alignment
+     // boxX and scrollCol adjusted backwards, width normal
+     xpos = 3000;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(), Math.round(od.getWidth()) - boxWidth);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+   }
+   /**
+    * Test that the box position is set correctly when set from the viewport,
+    * with no hidden rows or columns
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewport()
+   {
+     // move viewport to start of alignment
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to right
+     moveViewportH(70);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 70 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport down
+     moveViewportV(100);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 70 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(),
+             Math.round(100 * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to bottom right
+     moveViewport(98, 508);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 98 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(),
+             Math.round((float) 508 * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * at the start
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsAtStart()
+   {
+     int firstHidden = 0;
+     int lastHidden = 20;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport to start of alignment
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment - need to make startRes by removing
+     // hidden cols because of how viewport/overview are implemented
+     moveViewport(98 - lastHidden - 1, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (98 - lastHidden - 1) * od.getWidth()
+                     / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * in the middle
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsInMiddle()
+   {
+     int firstHidden = 68;
+     int lastHidden = 78;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport before hidden columns
+     moveViewport(3, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 3 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to left of hidden columns with overlap
+     moveViewport(10, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 10 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to straddle hidden columns
+     moveViewport(63, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 63 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to right of hidden columns, no overlap
+     moveViewport(80 - (lastHidden - firstHidden + 1), 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (80 - (lastHidden - firstHidden + 1))
+                     * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * at the end
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsAtEnd()
+   {
+     int firstHidden = 152;
+     int lastHidden = 164;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport before hidden columns
+     moveViewport(3, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 3 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to hidden columns
+     // viewport can't actually extend into hidden cols,
+     // so move to the far right edge of the viewport
+     moveViewport(firstHidden - viewWidth, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - viewWidth)
+                     * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows at
+    * the start
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsAtStart()
+   {
+     int firstHidden = 0;
+     int lastHidden = 20;
+     hideSequences(firstHidden, lastHidden);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     // move viewport to start of alignment:
+     // box moves to below hidden rows, height remains same
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment
+     moveViewport(0, 525 - viewHeight - lastHidden - 1);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (525 - viewHeight - lastHidden - 1)
+                     * od.getSequencesHeight()
+                     / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows in
+    * the middle
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsInMiddle()
+   {
+     int firstHidden = 200;
+     int lastHidden = 210;
+     hideSequences(firstHidden, lastHidden);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     // move viewport to start of alignment:
+     // box, height etc as in non-hidden case
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to straddle hidden rows
+     moveViewport(0, 198);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), Math.round ((float)198 * od.getSequencesHeight()
+             / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows at
+    * the bottom
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsAtEnd()
+   {
+     int firstHidden = 500;
+     int lastHidden = 524;
+     hideSequences(firstHidden, lastHidden);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     // move viewport to start of alignment:
+     // box, height etc as in non-hidden case
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment
+     // viewport sits above hidden rows and does not include them
+     moveViewport(0, firstHidden - viewHeight - 1);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (firstHidden - viewHeight - 1)
+                     * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the start
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsAtStart()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows at start and check updated box position is correct
+     int lastHiddenRow = 30;
+     hideSequences(0, lastHiddenRow);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click below hidden rows
+     mouseClick(od, 0, 150);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 150);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the middle
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsInMiddle()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows in middle and check updated box position is correct
+     // no changes
+     int firstHiddenRow = 50;
+     int lastHiddenRow = 54;
+     hideSequences(firstHiddenRow, lastHiddenRow);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows, so that box overlaps
+     int ypos = 35; // column value in residues
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click so that box straddles hidden rows
+     ypos = 44; // column value in residues
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the end of
+    * the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsAtEnd()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows at end and check updated box position is correct
+     // no changes
+     int firstHidden = 500;
+     int lastHidden = 524;
+     hideSequences(firstHidden, lastHidden);
+     // calculate with visible values
+     alheight = vpranges.getVisibleAlignmentHeight();
+     alwidth = vpranges.getVisibleAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows
+     int ypos = 40; // row 40
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows so box overlaps
+     // boxY, boxHeight remains same
+     ypos = 497; // row 497
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) firstHidden * od.getSequencesHeight()
+                     / alheight)
+                     - boxHeight);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /*
+    * Move viewport horizontally: startRes + previous width gives new horizontal extent. Vertical extent stays the same.
+    */
+   private void moveViewportH(int startRes)
+   {
 -    vpranges.setStartRes(startRes);
 -    vpranges.setEndRes(startRes + viewWidth - 1);
++    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Move viewport vertically: startSeq and endSeq give new vertical extent. Horizontal extent stays the same.
+    */
+   private void moveViewportV(int startSeq)
+   {
 -    vpranges.setStartSeq(startSeq);
 -    vpranges.setEndSeq(startSeq + viewHeight - 1);
++    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Move viewport horizontally and vertically.
+    */
+   private void moveViewport(int startRes, int startSeq)
+   {
 -    vpranges.setStartRes(startRes);
 -    vpranges.setEndRes(startRes + viewWidth - 1);
 -    vpranges.setStartSeq(startSeq);
 -    vpranges.setEndSeq(startSeq + viewHeight - 1);
++    vpranges.setViewportStartAndWidth(startRes, viewWidth);
++    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Mouse click as position x,y in overview window
+    */
 -  private void mouseClick(OverviewDimensionsHideHidden od, int x, int y)
++  private void mouseClick(OverviewDimensions od, int x, int y)
+   {
+     od.updateViewportFromMouse(x, y, al.getHiddenSequences(), hiddenCols);
+     // updates require an OverviewPanel to exist which it doesn't here
+     // so call setBoxPosition() as it would be called by the AlignmentPanel
+     // normally
 -
 -    vpranges.setStartRes(od.getScrollCol());
 -    vpranges.setEndRes(od.getScrollCol() + viewWidth - 1);
 -    vpranges.setStartSeq(od.getScrollRow());
 -    vpranges.setEndSeq(od.getScrollRow() + viewHeight - 1);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   
+   /*
+    * Test that the box is positioned with the top left corner at xpos, ypos
+    * and with the original width and height
+    */
+   private void testBoxIsAtClickPoint(int xpos, int ypos)
+   {
+     mouseClick(od, xpos, ypos);
+     assertEquals(od.getBoxX(), xpos);
+     assertEquals(od.getBoxY(), ypos);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /*
+    * Hide sequences between start and end
+    */
+   private void hideSequences(int start, int end)
+   {
+     SequenceI[] allseqs = al.getSequencesArray();
+     SequenceGroup theseSeqs = new SequenceGroup();
+     
+     for (int i = start; i <= end; i++)
+     {
+       theseSeqs.addSequence(allseqs[i], false);
+       al.getHiddenSequences().hideSequence(allseqs[i]);
+     }
+     hiddenRepSequences.put(allseqs[start], theseSeqs);
+   }
+ }
index 0000000,8297159..1bc3bfa
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1029 +1,1025 @@@
+ /*
+  * 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.viewmodel;
+ import static org.testng.Assert.assertEquals;
+ import jalview.analysis.AlignmentGenerator;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceCollectionI;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import java.util.Hashtable;
+ import org.testng.annotations.AfterClass;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ @Test(singleThreaded = true)
+ public class OverviewDimensionsShowHiddenTest
+ {
+   AlignmentI al;
+   OverviewDimensionsShowHidden od;
+   // cached widths and heights
+   int boxWidth;
+   int boxHeight;
+   int viewHeight;
+   int viewWidth;
+   int alheight;
+   int alwidth;
+   ViewportRanges vpranges;
+   Hashtable<SequenceI, SequenceCollectionI> hiddenRepSequences = new Hashtable<SequenceI, SequenceCollectionI>();
+   HiddenColumns hiddenCols = new HiddenColumns();
+   @BeforeClass(alwaysRun = true)
+   public void setUpAlignment()
+   {
+     // create random alignment
+     AlignmentGenerator gen = new AlignmentGenerator(false);
+     al = gen.generate(157, 525, 123, 5, 5);
+   }
+   @BeforeMethod(alwaysRun = true)
+   public void setUp()
+   {
+     if (!hiddenRepSequences.isEmpty())
+     {
+       al.getHiddenSequences().showAll(hiddenRepSequences);
+     }
+     ColumnSelection colsel = new ColumnSelection();
+     hiddenCols.revealAllHiddenColumns(colsel);
+     
+     vpranges = new ViewportRanges(al);
 -    vpranges.setStartRes(0);
 -    vpranges.setEndRes(62);
 -    vpranges.setStartSeq(0);
 -    vpranges.setEndSeq(17);
++    vpranges.setViewportStartAndHeight(0, 18);
++    vpranges.setViewportStartAndWidth(0, 63);
+     viewHeight = vpranges.getEndSeq() - vpranges.getStartSeq() + 1;
+     viewWidth = vpranges.getEndRes() - vpranges.getStartRes() + 1;
+     HiddenColumns hiddenCols = new HiddenColumns();
+     od = new OverviewDimensionsShowHidden(vpranges, true);
+     // Initial box sizing - default path through code
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     mouseClick(od, 0, 0);
+     moveViewport(0, 0);
+     // calculate before hidden columns so we get absolute values
+     alheight = vpranges.getAbsoluteAlignmentHeight();
+     alwidth = vpranges.getAbsoluteAlignmentWidth();
+     boxWidth = Math.round((float) (vpranges.getEndRes()
+             - vpranges.getStartRes() + 1)
+             * od.getWidth() / alwidth);
+     boxHeight = Math.round((float) (vpranges.getEndSeq()
+             - vpranges.getStartSeq() + 1)
+             * od.getSequencesHeight() / alheight);
+   }
+   @AfterClass(alwaysRun = true)
+   public void cleanUp()
+   {
+     al = null;
+   }
+   /**
+    * Test that the OverviewDimensions constructor sets width and height
+    * correctly
+    */
+   @Test(groups = { "Functional" })
+   public void testConstructor()
+   {
+     SequenceI seqa = new Sequence("Seq1", "ABC");
+     SequenceI seqb = new Sequence("Seq2", "ABC");
+     SequenceI seqc = new Sequence("Seq3", "ABC");
+     SequenceI seqd = new Sequence("Seq4", "ABC");
+     SequenceI seqe = new Sequence("Seq5",
+             "ABCABCABCABCABCABCABCABCBACBACBACBAC");
+     int defaultGraphHeight = 20;
+     int maxWidth = 400;
+     int minWidth = 120;
+     int maxSeqHeight = 300;
+     int minSeqHeight = 40;
+     // test for alignment with width > height
+     SequenceI[] seqs1 = new SequenceI[] { seqa, seqb };
+     Alignment al1 = new Alignment(seqs1);
+     ViewportRanges props = new ViewportRanges(al1);
+     OverviewDimensions od = new OverviewDimensionsShowHidden(props, true);
+     int scaledHeight = 267;
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), scaledHeight);
+     assertEquals(od.getWidth(), maxWidth);
+     assertEquals(od.getHeight(), scaledHeight + defaultGraphHeight);
+     // test for alignment with width < height
+     SequenceI[] seqs2 = new SequenceI[] { seqa, seqb, seqc, seqd };
+     Alignment al2 = new Alignment(seqs2);
+     props = new ViewportRanges(al2);
+     od = new OverviewDimensionsShowHidden(props, true);
+     int scaledWidth = 300;
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), scaledWidth);
+     assertEquals(od.getHeight(), scaledWidth + defaultGraphHeight);
+     // test for alignment with width > height and sequence height scaled below
+     // min value
+     SequenceI[] seqs3 = new SequenceI[] { seqe };
+     Alignment al3 = new Alignment(seqs3);
+     props = new ViewportRanges(al3);
+     od = new OverviewDimensionsShowHidden(props, true);
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), minSeqHeight);
+     assertEquals(od.getWidth(), maxWidth);
+     assertEquals(od.getHeight(), minSeqHeight + defaultGraphHeight);
+     // test for alignment with width < height and width scaled below min value
+     SequenceI[] seqs4 = new SequenceI[] { seqa, seqb, seqc, seqd, seqa,
+         seqb, seqc, seqd, seqa, seqb, seqc, seqd, seqa, seqb, seqc, seqd };
+     Alignment al4 = new Alignment(seqs4);
+     props = new ViewportRanges(al4);
+     od = new OverviewDimensionsShowHidden(props, true);
+     assertEquals(od.getGraphHeight(), defaultGraphHeight);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), minWidth);
+     assertEquals(od.getHeight(), maxSeqHeight + defaultGraphHeight);
+     Alignment al5 = new Alignment(seqs4);
+     props = new ViewportRanges(al5);
+     od = new OverviewDimensionsShowHidden(props, false);
+     assertEquals(od.getGraphHeight(), 0);
+     assertEquals(od.getSequencesHeight(), maxSeqHeight);
+     assertEquals(od.getWidth(), minWidth);
+     assertEquals(od.getHeight(), maxSeqHeight);
+   }
+   /**
+    * Test that validation after mouse adjustments to boxX and boxY sets box
+    * dimensions and scroll values correctly, when there are no hidden rows or
+    * columns.
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromMouseClick()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // negative boxX value reset to 0
+     mouseClick(od, -5, 10);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollRow(),
++    assertEquals(vpranges.getStartSeq(),
+             Math.round((float) 10 * alheight / od.getSequencesHeight()));
 -    assertEquals(od.getScrollCol(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
+     // negative boxY value reset to 0
+     mouseClick(od, 6, -2);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) 6 * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // overly large boxX value reset to width-boxWidth
+     mouseClick(od, 100, 6);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxY(), 6);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // overly large boxY value reset to sequenceHeight - boxHeight
+     mouseClick(od, 10, 520);
+     assertEquals(od.getBoxX(), 10);
+     assertEquals(od.getBoxY(), od.getSequencesHeight() - od.getBoxHeight());
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+     // here (float) od.getBoxY() * alheight / od.getSequencesHeight() = 507.5
+     // and round rounds to 508; however we get 507 working with row values
+     // hence the subtraction of 1
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()) - 1);
+     // click past end of alignment, as above
+     mouseClick(od, 3000, 5);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // move viewport so startRes non-zero and then mouseclick
+     moveViewportH(50);
+     // click at viewport position
+     int oldboxx = od.getBoxX();
+     int oldboxy = od.getBoxY();
+     mouseClick(od, od.getBoxX() + 5, od.getBoxY() + 2);
+     assertEquals(od.getBoxX(), oldboxx + 5);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
+     assertEquals(od.getBoxY(), oldboxy + 2);
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+     // click at top corner
+     mouseClick(od, 0, 0);
+     assertEquals(od.getBoxX(), 0);
 -    assertEquals(od.getScrollCol(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
+     assertEquals(od.getBoxY(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols at the start
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsAtStart()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide cols at start and check updated box position is correct
+     // changes boxX but not boxwidth
+     int lastHiddenCol = 30;
+     hiddenCols.hideColumns(0, lastHiddenCol);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                     / alwidth));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // try to click in hidden cols, check box does not move
+     int xpos = 10;
+     mouseClick(od, xpos, 0);
+     assertEquals(
+             od.getBoxX(),
+             Math.round((float) (lastHiddenCol + 1) * od.getWidth()
+                     / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
+     // click to right of hidden columns, box moves to click point
+     testBoxIsAtClickPoint(40, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) 40 * alwidth / od.getWidth())
+                     - (lastHiddenCol + 1));
+     // click to right of hidden columns such that box runs over right hand side
+     // of alignment
+     // box position is adjusted away from the edge
+     // overly large boxX value reset to width-boxWidth
+     xpos = 100;
+     mouseClick(od, xpos, 5);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxY(), 5);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth())
+                     - (lastHiddenCol + 1));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols in the middle
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsInMiddle()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     
+     // hide columns 63-73, no change to box position or dimensions
+     int firstHidden = 63;
+     int lastHidden = 73;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so that it overlaps with hidden cols on one side
+     // box width changes, boxX and scrollCol as for unhidden case
+     int xpos = 55 - boxWidth; // 55 is position in overview approx halfway
+                               // between cols 60 and 70
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(), xpos);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(
+             od.getBoxWidth(),
+             Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                     * od.getWidth() / alwidth));
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round(xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so that it completely covers hidden cols
+     // box width changes, boxX and scrollCol as for hidden case
+     xpos = 33;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(), xpos);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(
+             od.getBoxWidth(),
+             Math.round(boxWidth + (float) (lastHidden - firstHidden + 1)
+                     * od.getWidth() / alwidth));
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so boxX is in hidden cols, box overhangs at right
+     // boxX and scrollCol at left of hidden area, box width extends across
+     // hidden region
+     xpos = 50;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(
+             od.getBoxWidth(),
+             boxWidth
+                     + Math.round((float) (lastHidden - firstHidden + 1)
+                             * od.getWidth() / alwidth));
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(), firstHidden - 1);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), firstHidden - 1);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // move box so boxX is to right of hidden cols, but does not go beyond full
+     // width of alignment
+     // box width, boxX and scrollCol all as for non-hidden case
+     xpos = 75;
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round(xpos * alwidth / od.getWidth())
+                     - (lastHidden - firstHidden + 1));
+     
+     // move box so it goes beyond full width of alignment
+     // boxX, scrollCol adjusted back, box width normal
+     xpos = 3000;
+     mouseClick(od, xpos, 5);
+     assertEquals(od.getBoxX(), od.getWidth() - od.getBoxWidth());
+     assertEquals(od.getBoxY(), 5);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(
++            vpranges.getStartRes(),
+             Math.round(((float) od.getBoxX() * alwidth / od.getWidth())
+                     - (lastHidden - firstHidden + 1)));
 -    assertEquals(od.getScrollRow(),
++    assertEquals(
++            vpranges.getStartSeq(),
+             Math.round((float) od.getBoxY() * alheight
+                     / od.getSequencesHeight()));
+   }
+   /**
+    * Test setting of the box position, when there are hidden cols at the end of
+    * the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenColsAtEnd()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide columns 140-164, no change to box position or dimensions
+     int firstHidden = 140;
+     int lastHidden = 164;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // click to left of hidden cols, without overlapping
+     // boxX, scrollCol and width as normal
+     int xpos = 5;
+     testBoxIsAtClickPoint(xpos, 0);
 -    assertEquals(od.getScrollRow(), 0);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartSeq(), 0);
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) xpos * alwidth / od.getWidth()));
+     // click to left of hidden cols, with overlap
+     // boxX and scrollCol adjusted for hidden cols, width normal
+     xpos = Math.round((float) 145 * od.getWidth() / alwidth) - boxWidth;
+     mouseClick(od, xpos, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                     - boxWidth + 1);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // click in hidden cols
+     // boxX and scrollCol adjusted for hidden cols, width normal
+     xpos = 115;
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                     - boxWidth + 1);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // click off end of alignment
+     // boxX and scrollCol adjusted for hidden cols, width normal
+     xpos = 3000;
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - 1) * od.getWidth() / alwidth)
+                     - boxWidth + 1);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(),
++    assertEquals(vpranges.getStartRes(),
+             Math.round((float) od.getBoxX() * alwidth / od.getWidth()));
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+   }
+   /**
+    * Test that the box position is set correctly when set from the viewport,
+    * with no hidden rows or columns
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewport()
+   {
+     // move viewport to start of alignment
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to right
+     moveViewportH(70);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 70 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport down
+     moveViewportV(100);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 70 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(),
+             Math.round(100 * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to bottom right
+     moveViewport(98, 508);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 98 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(),
+             Math.round((float) 508 * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * at the start
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsAtStart()
+   {
+     int firstHidden = 0;
+     int lastHidden = 20;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport to start of alignment
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (lastHidden + 1) * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment - need to make startRes by removing
+     // hidden cols because of how viewport/overview are implemented
+     moveViewport(98 - lastHidden - 1, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 98 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * in the middle
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsInMiddle()
+   {
+     int firstHidden = 68;
+     int lastHidden = 78;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport before hidden columns
+     moveViewport(3, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 3 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to left of hidden columns with overlap
+     moveViewport(10, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 10 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(
+             od.getBoxWidth(),
+             boxWidth
+                     + Math.round((float) (lastHidden - firstHidden + 1)
+                             * od.getWidth() / alwidth));
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to straddle hidden columns
+     moveViewport(63, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 63 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(
+             od.getBoxWidth(),
+             boxWidth
+                     + Math.round((lastHidden - firstHidden + 1)
+                             * od.getWidth() / alwidth));
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to right of hidden columns, no overlap
+     moveViewport(80 - (lastHidden - firstHidden + 1), 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 80 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden columns
+    * at the end
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenColsAtEnd()
+   {
+     int firstHidden = 152;
+     int lastHidden = 164;
+     hiddenCols.hideColumns(firstHidden, lastHidden);
+     // move viewport before hidden columns
+     moveViewport(3, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) 3 * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to hidden columns
+     // viewport can't actually extend into hidden cols,
+     // so move to the far right edge of the viewport
+     moveViewport(firstHidden - viewWidth, 0);
+     assertEquals(od.getBoxX(),
+             Math.round((float) (firstHidden - viewWidth)
+                     * od.getWidth() / alwidth));
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows at
+    * the start
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsAtStart()
+   {
+     int firstHidden = 0;
+     int lastHidden = 20;
+     hideSequences(firstHidden, lastHidden);
+     // move viewport to start of alignment:
+     // box moves to below hidden rows, height remains same
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) (lastHidden + 1) * od.getSequencesHeight()
+                     / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment
+     moveViewport(0, 525 - viewHeight - lastHidden - 1);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (525 - viewHeight) * od.getSequencesHeight()
+                     / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows in
+    * the middle
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsInMiddle()
+   {
+     int firstHidden = 200;
+     int lastHidden = 210;
+     hideSequences(firstHidden, lastHidden);
+     // move viewport to start of alignment:
+     // box, height etc as in non-hidden case
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to straddle hidden rows
+     moveViewport(0, 198);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), Math.round ((float)198 * od.getSequencesHeight()
+             / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(
+             od.getBoxHeight(),
+             Math.round((float) (viewHeight + lastHidden - firstHidden + 1)
+                     * od.getSequencesHeight() / alheight));
+   }
+   /**
+    * Test that the box position is set correctly when there are hidden rows at
+    * the bottom
+    */
+   @Test(groups = { "Functional" })
+   public void testSetBoxFromViewportHiddenRowsAtEnd()
+   {
+     int firstHidden = 500;
+     int lastHidden = 524;
+     hideSequences(firstHidden, lastHidden);
+     // move viewport to start of alignment:
+     // box, height etc as in non-hidden case
+     moveViewport(0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // move viewport to end of alignment
+     // viewport sits above hidden rows and does not include them
+     moveViewport(0, firstHidden - viewHeight - 1);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (firstHidden - viewHeight - 1)
+                     * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the start
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsAtStart()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     assertEquals(od.getBoxWidth(), boxWidth);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows at start and check updated box position is correct
+     // changes boxY but not boxheight
+     int lastHiddenRow = 30;
+     hideSequences(0, lastHiddenRow);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) (lastHiddenRow + 1)
+                     * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click in hidden rows - same result
+     mouseClick(od, 0, 0);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (lastHiddenRow + 1)
+                     * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click below hidden rows
+     mouseClick(od, 0, 150);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 150);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the middle
+    * of the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsInMiddle()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows in middle and check updated box position is correct
+     // no changes
+     int firstHiddenRow = 50;
+     int lastHiddenRow = 54;
+     hideSequences(firstHiddenRow, lastHiddenRow);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows, so that box overlaps
+     int ypos = 35; // column value in residues
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(
+             od.getBoxHeight(),
+             boxHeight
+                     + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                             * od.getSequencesHeight() / alheight));
+     // click so that box straddles hidden rows
+     ypos = 44; // column value in residues
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(
+             od.getBoxHeight(),
+             boxHeight
+                     + Math.round((float) (lastHiddenRow - firstHiddenRow + 1)
+                             * od.getSequencesHeight() / alheight));
+   }
+   /**
+    * Test setting of the box position, when there are hidden rows at the end of
+    * the alignment
+    */
+   @Test(groups = { "Functional" })
+   public void testFromMouseWithHiddenRowsAtEnd()
+   {
+     od.updateViewportFromMouse(0, 0, al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
 -    assertEquals(od.getScrollCol(), 0);
 -    assertEquals(od.getScrollRow(), 0);
++    assertEquals(vpranges.getStartRes(), 0);
++    assertEquals(vpranges.getStartSeq(), 0);
+     // hide rows at end and check updated box position is correct
+     // no changes
+     int firstHidden = 500;
+     int lastHidden = 524;
+     hideSequences(firstHidden, lastHidden);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(), 0);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows
+     int ypos = 40; // row 40
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(od.getBoxY(),
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click above hidden rows so box overlaps
+     // boxY moved upwards, boxHeight remains same
+     ypos = 497; // row 497
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((float) (firstHidden - viewHeight)
+                     * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+     // click within hidden rows
+     ypos = 505;
+     mouseClick(od, 0,
+             Math.round((float) ypos * od.getSequencesHeight() / alheight));
+     assertEquals(od.getBoxX(), 0);
+     assertEquals(
+             od.getBoxY(),
+             Math.round((firstHidden - viewHeight) * od.getSequencesHeight()
+                     / alheight));
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /*
+    * Move viewport horizontally: startRes + previous width gives new horizontal extent. Vertical extent stays the same.
+    */
+   private void moveViewportH(int startRes)
+   {
 -    vpranges.setStartRes(startRes);
 -    vpranges.setEndRes(startRes + viewWidth - 1);
++    vpranges.setViewportStartAndWidth(startRes, viewWidth);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Move viewport vertically: startSeq and endSeq give new vertical extent. Horizontal extent stays the same.
+    */
+   private void moveViewportV(int startSeq)
+   {
 -    vpranges.setStartSeq(startSeq);
 -    vpranges.setEndSeq(startSeq + viewHeight - 1);
++    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Move viewport horizontally and vertically.
+    */
+   private void moveViewport(int startRes, int startSeq)
+   {
 -    vpranges.setStartRes(startRes);
 -    vpranges.setEndRes(startRes + viewWidth - 1);
 -    vpranges.setStartSeq(startSeq);
 -    vpranges.setEndSeq(startSeq + viewHeight - 1);
++    vpranges.setViewportStartAndWidth(startRes, viewWidth);
++    vpranges.setViewportStartAndHeight(startSeq, viewHeight);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   /*
+    * Mouse click as position x,y in overview window
+    */
 -  private void mouseClick(OverviewDimensionsShowHidden od, int x, int y)
++  private void mouseClick(OverviewDimensions od, int x, int y)
+   {
+     od.updateViewportFromMouse(x, y, al.getHiddenSequences(), hiddenCols);
+     // updates require an OverviewPanel to exist which it doesn't here
+     // so call setBoxPosition() as it would be called by the AlignmentPanel
+     // normally
 -
 -    vpranges.setStartRes(od.getScrollCol());
 -    vpranges.setEndRes(od.getScrollCol() + viewWidth - 1);
 -    vpranges.setStartSeq(od.getScrollRow());
 -    vpranges.setEndSeq(od.getScrollRow() + viewHeight - 1);
+     od.setBoxPosition(al.getHiddenSequences(), hiddenCols);
+   }
+   
+   /*
+    * Test that the box is positioned with the top left corner at xpos, ypos
+    * and with the original width and height
+    */
+   private void testBoxIsAtClickPoint(int xpos, int ypos)
+   {
+     mouseClick(od, xpos, ypos);
+     assertEquals(od.getBoxX(), xpos);
+     assertEquals(od.getBoxY(), ypos);
+     assertEquals(od.getBoxWidth(), boxWidth);
+     assertEquals(od.getBoxHeight(), boxHeight);
+   }
+   /*
+    * Hide sequences between start and end
+    */
+   private void hideSequences(int start, int end)
+   {
+     SequenceI[] allseqs = al.getSequencesArray();
+     SequenceGroup theseSeqs = new SequenceGroup();
+     
+     for (int i = start; i <= end; i++)
+     {
+       theseSeqs.addSequence(allseqs[i], false);
+       al.getHiddenSequences().hideSequence(allseqs[i]);
+     }
+     hiddenRepSequences.put(allseqs[start], theseSeqs);
+   }
+ }