JAL-2609 revised design (always draw all scales, fast paint relocates
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 7 Aug 2017 13:52:05 +0000 (15:52 +0200)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 7 Aug 2017 13:52:05 +0000 (15:52 +0200)
sequences/annotations)

src/jalview/gui/SeqCanvas.java

index 576d6ca..efc6910 100755 (executable)
@@ -73,14 +73,29 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   boolean fastpainting = false;
 
-  int labelWidthWest;
-
-  int labelWidthEast;
-
   int cursorX = 0;
 
   int cursorY = 0;
 
+  int charHeight = 0;
+
+  int charWidth = 0;
+
+  private AnnotationPanel annotations;
+
+  /*
+   * measurements for drawing a wrapped alignment
+   */
+  int labelWidthWest; // label left width in pixels if shown
+
+  private int labelWidthEast; // label right width in pixels if shown
+
+  private int wrappedSpaceAboveAlignment; // gap between widths
+
+  private int wrappedRepeatHeightPx; // height in pixels of wrapped width
+
+  private int wrappedVisibleWidths; // number of wrapped widths displayed
+
   /**
    * Creates a new SeqCanvas object.
    * 
@@ -110,8 +125,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return fr;
   }
 
-  int charHeight = 0, charWidth = 0;
-
   private void updateViewport()
   {
     charHeight = av.getCharHeight();
@@ -135,6 +148,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
   {
     updateViewport();
+
+    /*
+     * white fill the scale space (for the fastPaint case)
+     */
+    g.setColor(Color.white);
+    g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
+            charHeight * 3 / 2 + 2);
+    g.setColor(Color.black);
+
     List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
             endx);
     for (ScaleMark mark : marks)
@@ -216,18 +238,20 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         }
       }
 
+      /*
+       * white fill the space for the scale
+       */
+      g.setColor(Color.white);
+      int y = (ypos + (i * charHeight)) - (charHeight / 5);
+      y -= charHeight; // fillRect: origin is top left of rectangle
+      int xpos = left ? 0 : labelWidthWest + charWidth
+              * av.getRanges().getViewportWidth();
+      g.fillRect(xpos, y, left ? labelWidthWest : labelWidthEast,
+              charHeight + 1);
+      y += charHeight; // drawString: origin is bottom left of text
+
       if (value != -1)
       {
-        /*
-         * white fill the space for the scale
-         */
-        g.setColor(Color.white);
-        int y = (ypos + (i * charHeight)) - (charHeight / 5);
-        y -= charHeight; // fillRect: origin is top left of rectangle
-        int xpos = left ? 0 : getWidth() - labelWidthEast;
-        g.fillRect(xpos, y, left ? labelWidthWest : labelWidthEast,
-                charHeight + 1);
-        y += charHeight; // drawString: origin is bottom left of text
 
         /*
          * draw scale value, right justified, with half a character width
@@ -471,143 +495,146 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    *          available width in pixels
    * @param canvasHeight
    *          available height in pixels
-   * @param startRes
-   *          the first visible column (0...) of the alignment to draw
+   * @param startColumn
+   *          the first column (0...) of the alignment to draw
    */
   public void drawWrappedPanel(Graphics g, int canvasWidth,
-          int canvasHeight, int startRes)
+          int canvasHeight, final int startColumn)
   {
     updateViewport();
-    int labelWidth = 0;
-    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
-    {
-      FontMetrics fm = getFontMetrics(av.getFont());
-      labelWidth = getLabelWidth(fm);
-    }
-
-    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
-    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
-
-    int hgap = charHeight;
-    if (av.getScaleAboveWrapped())
-    {
-      hgap += charHeight;
-    }
 
-    int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
+    int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
 
-    av.setWrappedWidth(cWidth);
-
-    av.getRanges().setViewportStartAndWidth(startRes, cWidth);
-
-    int ypos = hgap;
-    int maxwidth = av.getAlignment().getWidth();
-
-    if (av.hasHiddenColumns())
-    {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
-    }
+    av.setWrappedWidth(wrappedWidthInResidues);
 
-    int annotationHeight = getAnnotationHeight();
-    int sequencesHeight = av.getAlignment().getHeight() * charHeight;
+    ViewportRanges ranges = av.getRanges();
+    ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
 
     /*
      * draw one width at a time (including any scales or annotation shown),
-     * until we have run out of alignment or vertical space available
-     * (stop if not enough room left for at least one sequence)
+     * until we have run out of either alignment or vertical space available
      */
-    int yposMax = canvasHeight;// - hgap - charHeight + 1;
-    while ((ypos <= yposMax) && (startRes < maxwidth))
-    {
-      drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
+    int yposMax = canvasHeight;
+    // ensure room for at least one sequence
+    yposMax -= wrappedSpaceAboveAlignment - charHeight;
+    int ypos = wrappedSpaceAboveAlignment;
+    int maxWidth = ranges.getVisibleAlignmentWidth();
 
-      ypos += sequencesHeight + annotationHeight + hgap;
-
-      startRes += cWidth;
+    int start = startColumn;
+    while ((ypos <= yposMax) && (start < maxWidth))
+    {
+      int endColumn = Math
+              .min(maxWidth, start + wrappedWidthInResidues - 1);
+      drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
+      ypos += wrappedRepeatHeightPx;
+      start += wrappedWidthInResidues;
     }
+
+    drawWrappedDecorators(g, canvasHeight, startColumn);
   }
 
   /**
-   * Draws one width of a wrapped alignment, including scales left, right or
-   * above, and annnotations, if shown
+   * Calculates and saves values needed when rendering a wrapped alignment.
+   * These depend on many factors, including
+   * <ul>
+   * <li>canvas width and height</li>
+   * <li>number of visible sequences, and height of annotations if shown</li>
+   * <li>font and character width</li>
+   * <li>whether scales are shown left, right or above the alignment</li>
+   * </ul>
    * 
-   * @param g
-   * @param startRes
-   * @param canvasHeight
    * @param canvasWidth
-   * @param maxWidth
-   * @param ypos
+   * @param canvasHeight
+   * @return the number of residue columns in each width
    */
-  protected void drawWrappedWidth(Graphics g, int startRes,
-          int canvasHeight, int canvasWidth, int maxWidth, int ypos)
+  protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
   {
-    int endx;
-    endx = startRes + canvasWidth - 1;
-
-    if (endx > maxWidth)
+    /*
+     * width of labels in pixels left and right (if shown)
+     */
+    int labelWidth = 0;
+    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
     {
-      endx = maxWidth;
+      FontMetrics fm = getFontMetrics(av.getFont());
+      labelWidth = getLabelWidth(fm);
     }
+    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
+    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
 
-    g.setFont(av.getFont());
-    g.setColor(Color.black);
+    /*
+     * vertical space in pixels between wrapped widths of alignment
+     */
+    wrappedSpaceAboveAlignment = charHeight
+            * (av.getScaleAboveWrapped() ? 2 : 1);
 
-    if (av.getScaleLeftWrapped())
+    /*
+     * height in pixels of the wrapped widths
+     */
+    wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
+    // add sequences
+    wrappedRepeatHeightPx += av.getRanges().getViewportHeight() * charHeight;
+    // add annotations panel height if shown
+    wrappedRepeatHeightPx += getAnnotationHeight();
+
+    /*
+     * number of visible widths (the last one may be part height)
+     */
+    ViewportRanges ranges = av.getRanges();
+    int xMax = ranges.getVisibleAlignmentWidth();
+    wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
+    int remainder = canvasHeight % wrappedRepeatHeightPx;
+    if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
     {
-      drawVerticalScale(g, startRes, endx, ypos, true);
+      wrappedVisibleWidths++;
     }
 
-    if (av.getScaleRightWrapped())
+    /*
+     *  limit visibleWidths to not exceed width of alignment
+     */
+    int viewportWidth = ranges.getViewportWidth();
+    int maxWidths = (xMax - ranges.getStartRes()) / viewportWidth;
+    if (xMax % viewportWidth > 0)
     {
-      drawVerticalScale(g, startRes, endx, ypos, false);
+      maxWidths++;
     }
+    wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
 
-    drawWrappedRegion(g, startRes, endx, canvasHeight, canvasWidth, ypos);
+    /*
+     * number of whole width residue columns we can show in each row
+     */
+    int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
+            / charWidth;
+    return wrappedWidthInResidues;
   }
 
   /**
-   * Draws columns of a wrapped alignment from startRes to endRes, including
-   * scale above and annotations if shown, but not scale left or right.
+   * Draws one width of a wrapped alignment, including sequences and
+   * annnotations, if shown, but not scales or hidden column markers
    * 
    * @param g
-   * @param startRes
-   * @param endRes
-   * @param canvasHeight
-   * @param canvasWidth
    * @param ypos
+   * @param startColumn
+   * @param endColumn
+   * @param canvasHeight
    */
-  protected void drawWrappedRegion(Graphics g, int startRes, int endRes,
-          int canvasHeight, int canvasWidth, int ypos)
+  protected void drawWrappedWidth(Graphics g, int ypos,
+          int startColumn, int endColumn, int canvasHeight)
   {
-    g.translate(labelWidthWest, 0);
-
-    if (av.getScaleAboveWrapped())
-    {
-      drawNorthScale(g, startRes, endRes, ypos);
-    }
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
 
-    // todo can we let drawPanel() handle this?
-    if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
-    {
-      g.setColor(Color.blue);
-      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-      List<Integer> positions = hidden.findHiddenRegionPositions();
-      for (int pos : positions)
-      {
-        int res = pos - startRes;
+    int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
 
-        if (res < 0 || res > endRes - startRes)
-        {
-          continue;
-        }
-
-        gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
-            res * charWidth + charHeight / 4, res * charWidth }, new int[] {
-            ypos - (charHeight / 2), ypos - (charHeight / 2),
-            ypos - (charHeight / 2) + 8 }, 3);
-      }
-    }
+    /*
+     * move right before drawing by the width of the scale left (if any)
+     * plus column offset from left margin (usually zero, but may not be
+     * when fast painting draws just a few columns)
+     */
+    int xOffset = labelWidthWest
+            + ((startColumn - ranges.getStartRes()) % viewportWidth)
+            * charWidth;
+    g.translate(xOffset, 0);
 
     // When printing we have an extra clipped region,
     // the Printable page which we need to account for here
@@ -615,15 +642,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     if (clip == null)
     {
-      g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
+      g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
     }
     else
     {
-      g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
-              (int) clip.getBounds().getHeight());
+      g.setClip(0, (int) clip.getBounds().getY(),
+              viewportWidth * charWidth, (int) clip.getBounds().getHeight());
     }
 
-    drawPanel(g, startRes, endRes, 0, av.getAlignment().getHeight() - 1, ypos);
+    drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
+            ypos);
 
     int cHeight = av.getAlignment().getHeight() * charHeight;
 
@@ -635,15 +663,101 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         annotations = new AnnotationPanel(av);
       }
 
-      annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
-              endRes + 1);
+      annotations.renderer.drawComponent(annotations, av, g, -1,
+              startColumn, endx + 1);
       g.translate(0, -cHeight - ypos - 3);
     }
     g.setClip(clip);
-    g.translate(-labelWidthWest, 0);
+    g.translate(-xOffset, 0);
+  }
+
+  /**
+   * Draws scales left, right and above (if shown), and any hidden column
+   * markers, on the wrapped alignment
+   * 
+   * @param g
+   * @param ypos
+   * @param startColumn
+   * @param endColumn
+   */
+  protected void drawWrappedDecorators(Graphics g, int canvasHeight,
+          int startColumn)
+  {
+    g.setFont(av.getFont());
+    g.setColor(Color.black);
+
+    int ypos = wrappedSpaceAboveAlignment;
+    ViewportRanges ranges = av.getRanges();
+    int viewportWidth = ranges.getViewportWidth();
+    int maxWidth = ranges.getVisibleAlignmentWidth();
+    int widthsDrawn = 0;
+    while (widthsDrawn < wrappedVisibleWidths)
+    {
+      int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
+
+      if (av.getScaleLeftWrapped())
+      {
+        drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
+      }
+
+      if (av.getScaleRightWrapped())
+      {
+        drawVerticalScale(g, startColumn, endColumn, ypos, false);
+      }
+
+      g.translate(labelWidthWest, 0);
+
+      if (av.getScaleAboveWrapped())
+      {
+        drawNorthScale(g, startColumn, endColumn, ypos);
+      }
+
+      if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
+      {
+        drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
+      }
+
+      g.translate(-labelWidthWest, 0);
+
+      ypos += wrappedRepeatHeightPx;
+      startColumn += viewportWidth;
+      widthsDrawn++;
+    }
   }
 
-  AnnotationPanel annotations;
+  /**
+   * @param g
+   * @param ypos
+   * @param startColumn
+   * @param endColumn
+   */
+  protected void drawHiddenColumnMarkers(Graphics g, int ypos,
+          int startColumn, int endColumn)
+  {
+    g.setColor(Color.blue);
+    HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+    List<Integer> positions = hidden.findHiddenRegionPositions();
+    for (int pos : positions)
+    {
+      int res = pos - startColumn;
+
+      if (res < 0 || res > endColumn - startColumn)
+      {
+        continue;
+      }
+
+      /*
+       * draw a downward-pointing triangle at the hidden columns location
+       * (before the following visible column)
+       */
+      int xMiddle = res * charWidth;
+      int[] xPoints = new int[] { xMiddle - charHeight / 4,
+          xMiddle + charHeight / 4, xMiddle };
+      int yTop = ypos - (charHeight / 2);
+      int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
+      gg.fillPolygon(xPoints, yPoints, 3);
+    }
+  }
 
   int getAnnotationHeight()
   {
@@ -1243,7 +1357,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   protected void fastPaintWrapped(int scrollX)
   {
-    if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
+    ViewportRanges ranges = av.getRanges();
+
+    if (Math.abs(scrollX) > ranges.getViewportWidth())
     {
       /*
        * shift of more than one view width is 
@@ -1264,26 +1380,34 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     try
     {
+      calculateWrappedGeometry(getWidth(), getHeight());
+
       /*
        * relocate the regions of the alignment that are still visible
        */
       shiftWrappedAlignment(-scrollX);
 
       /*
-       * add new columns (scale above, sequence, annotation)
+       * add new columns (sequence, annotation)
        * - at top left if scrollX < 0 
        * - at right of last two widths if scrollX > 0
-       * also West scale top left or East scale bottom right if shown
        */
       if (scrollX < 0)
       {
-        fastPaintWrappedAddLeft(-scrollX);
+        int startRes = ranges.getStartRes();
+        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
+                - scrollX - 1, getHeight());
       }
       else
       {
         fastPaintWrappedAddRight(scrollX);
       }
 
+      /*
+       * draw all scales (if  shown) and hidden column markers
+       */
+      drawWrappedDecorators(gg, getHeight(), ranges.getStartRes());
+
       repaint();
     } finally
     {
@@ -1293,10 +1417,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   /**
    * Draws the specified number of columns at the 'end' (bottom right) of a
-   * wrapped alignment view, including scale above and right and annotations if
-   * shown. Also draws the same number of columns at the right hand end of the
-   * second last width shown, if the last width is not full height (so cannot
-   * simply be copied from the graphics image).
+   * wrapped alignment view, including sequences and annotations if shown, but
+   * not scales. Also draws the same number of columns at the right hand end of
+   * the second last width shown, if the last width is not full height (so
+   * cannot simply be copied from the graphics image).
    * 
    * @param columns
    */
@@ -1307,175 +1431,84 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       return;
     }
 
-    /*
-     * how many widths are visible? we will be adding
-     * columns to the last visible width, right hand end
-     */
-    int repeatHeight = getRepeatHeightWrapped();
-    int canvasHeight = getHeight();
-    int visibleWidths = canvasHeight / repeatHeight;
-    int remainder = canvasHeight % repeatHeight;
-    int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
-    boolean lastWidthPartHeight = false;
-    if (remainder >= (hgap + charHeight))
-    {
-      visibleWidths++;
-      lastWidthPartHeight = true;
-    }
-
-    /*
-     * limit visible widths to max widths of alignment, from the
-     * current start residue (we may be scrolled down)
-     */
     ViewportRanges ranges = av.getRanges();
-    int availableAlignmentWidth = ranges.getVisibleAlignmentWidth()
-            - ranges.getStartRes();
     int viewportWidth = ranges.getViewportWidth();
-    int maxWidths = availableAlignmentWidth / viewportWidth;
-    if (availableAlignmentWidth % viewportWidth > 0)
-    {
-      maxWidths++;
-    }
-    visibleWidths = Math.min(visibleWidths, maxWidths);
-    int canvasWidth = getWidth();
-    int widthInColumns = (canvasWidth - labelWidthEast - labelWidthWest)
-            / charWidth;
 
     /**
      * draw full height alignment in the second last row, last columns, if the
      * last row was not full height
      */
+    int visibleWidths = wrappedVisibleWidths;
+    int canvasHeight = getHeight();
+    boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
+
     if (lastWidthPartHeight)
     {
       int widthsAbove = visibleWidths - 2;
-      int ypos = repeatHeight * widthsAbove + hgap;
+      int ypos = wrappedRepeatHeightPx * widthsAbove
+              + wrappedSpaceAboveAlignment;
       int endRes = ranges.getEndRes();
       endRes += widthsAbove * viewportWidth;
       int startRes = endRes - columns;
       int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
               * charWidth;
-      gg.translate(xOffset, 0);
 
       /*
        * white fill first to erase annotations
        */
+      gg.translate(xOffset, 0);
       gg.setColor(Color.white);
       gg.fillRect(labelWidthWest, ypos,
-              (endRes - startRes + 1) * charWidth, repeatHeight);
-
-      drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
-              ypos);
+              (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
       gg.translate(-xOffset, 0);
+
+      drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
     }
 
     /*
-     * y-offset for drawing is height of widths above,
+     * y-offset for drawing last width is height of widths above,
      * plus one gap row
      */
     int widthsAbove = visibleWidths - 1;
-    int ypos = repeatHeight * widthsAbove + hgap;
+    int ypos = wrappedRepeatHeightPx * widthsAbove
+            + wrappedSpaceAboveAlignment;
     int endRes = ranges.getEndRes();
     endRes += widthsAbove * viewportWidth;
     endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
+    int startRes = endRes - columns + 1;
 
     /*
-     * draw one extra column than strictly needed - this is a (harmless)
-     * fudge to ensure scale marks get drawn (JAL-2636)
+     * white fill first to erase annotations
      */
-    int startRes = endRes - columns;
-
-    /*
-     * x-offset is x-start modulo viewport start residue;
-     * doesn't include label West (offset is applied in drawWrappedRegion)
-     */
-
-    int leftEndColumn = ranges.getStartRes() + widthsAbove
-            * ranges.getViewportWidth();
-    // startRes = Math.max(startRes - 0, leftEndColumn);
     int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
             * charWidth;
     gg.translate(xOffset, 0);
-
-    /*
-     * white fill the region to be drawn including scale left or above;
-     * extend to right hand margin so as to erase scale above when
-     * scrolling right beyond end of alignment
-     */
     gg.setColor(Color.white);
-    int width = canvasWidth - labelWidthWest - xOffset;
-    gg.fillRect(labelWidthWest, ypos - hgap, width, repeatHeight);
+    int width = viewportWidth * charWidth - xOffset;
+    gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
+    gg.translate(-xOffset, 0);
 
     gg.setFont(av.getFont());
     gg.setColor(Color.black);
 
-    drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
-            ypos);
-    gg.translate(-xOffset, 0);
-
-    /*
-     * draw scale right if shown, passing in the start/end columns
-     * for the whole line, not just the last few columns
-     */
-    if (av.getScaleRightWrapped())
-    {
-      drawVerticalScale(gg, leftEndColumn, endRes, ypos, false);
-    }
+    drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
 
     /*
      * and finally, white fill any space below the visible alignment
-     * (in case it has wrapped to just the top part of the panel)
      */
-    int heightBelow = canvasHeight - visibleWidths * repeatHeight;
+    int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
     if (heightBelow > 0)
     {
       gg.setColor(Color.white);
-      gg.fillRect(0, canvasHeight - heightBelow, canvasWidth, heightBelow);
+      gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
     }
   }
 
   /**
-   * Draws the specified number of columns at the 'start' (top left) of a
-   * wrapped alignment view, including scale above and left and annotations if
-   * shown
-   * 
-   * @param columns
-   */
-  protected void fastPaintWrappedAddLeft(int columns)
-  {
-    int startRes = av.getRanges().getStartRes();
-
-    /*
-     * draw one extra column than strictly needed - this is a (harmless)
-     * fudge to ensure scale marks get drawn (JAL-2636)
-     */
-    int endx = startRes + columns;
-    int ypos = 0;
-
-    /*
-     * white fill the region to be drawn including scale left or above
-     */
-    gg.setColor(Color.white);
-    int height = getRepeatHeightWrapped();
-    gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
-    ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
-
-    gg.setFont(av.getFont());
-    gg.setColor(Color.black);
-
-    if (av.getScaleLeftWrapped())
-    {
-      drawVerticalScale(gg, startRes, endx, ypos, true);
-    }
-
-    int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
-
-    drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
-  }
-
-  /**
    * Shifts the visible alignment by the specified number of columns - left if
-   * negative, right if positive. Includes scale above, left or right and
-   * annotations (if shown). Does not draw newly visible columns.
+   * negative, right if positive. Copies and moves sequences and annotations (if
+   * shown). Scales, hidden column markers and any newly visible columns must be
+   * drawn separately.
    * 
    * @param positions
    */
@@ -1486,34 +1519,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       return;
     }
 
-    int repeatHeight = getRepeatHeightWrapped();
+    int canvasHeight = getHeight();
     ViewportRanges ranges = av.getRanges();
-    int xMax = ranges.getVisibleAlignmentWidth();
+    int viewportWidth = ranges.getViewportWidth();
     int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
             * charWidth;
-    int canvasHeight = getHeight();
-    int visibleWidths = canvasHeight / repeatHeight;
-    if (canvasHeight % repeatHeight > 0)
-    {
-      visibleWidths++;
-    }
-    int viewportWidth = ranges.getViewportWidth();
-    int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
-
-    int remainder = canvasHeight % repeatHeight;
-    if (remainder >= (hgap + charHeight))
-    {
-      visibleWidths++;
-    }
-    /*
-     *  limit visibleWidths to not exceed width of alignment
-     */
-    int maxWidths = (xMax - ranges.getStartRes()) / viewportWidth;
-    if (xMax % viewportWidth > 0)
-    {
-      maxWidths++;
-    }
-    visibleWidths = Math.min(visibleWidths, maxWidths);
+    int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
+    int xMax = ranges.getVisibleAlignmentWidth();
 
     if (positions > 0)
     {
@@ -1524,38 +1536,27 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
        * columns from the right margin of the row above (if any) to the 
        * left margin of the current row
        */
-      int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
 
       /*
-       * get y-offset of last wrapped width
+       * get y-offset of last wrapped width, first row of sequences
        */
-      int y = canvasHeight / repeatHeight * repeatHeight;
+      int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
+      y += wrappedSpaceAboveAlignment;
       int copyFromLeftStart = labelWidthWest;
       int copyFromRightStart = copyFromLeftStart + widthToCopy;
 
       while (y >= 0)
       {
-        gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
+        gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
                 positions * charWidth, 0);
         if (y > 0)
         {
-          gg.copyArea(copyFromRightStart, y - repeatHeight, positions
-                  * charWidth, repeatHeight, -widthToCopy, repeatHeight);
+          gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
+                  positions * charWidth, heightToCopy, -widthToCopy,
+                  wrappedRepeatHeightPx);
         }
 
-        if (av.getScaleLeftWrapped())
-        {
-          drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
-                  true);
-        }
-        if (av.getScaleRightWrapped())
-        {
-          drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
-                  false);
-        }
-
-        y -= repeatHeight;
-        xpos -= viewportWidth;
+        y -= wrappedRepeatHeightPx;
       }
     }
     else
@@ -1567,31 +1568,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
        * columns from the left margin of the row below (if any) to the 
        * right margin of the current row
        */
-      int xpos = ranges.getStartRes();
-      int y = 0;
+      int xpos = av.getRanges().getStartRes();
+      int y = wrappedSpaceAboveAlignment;
       int copyFromRightStart = labelWidthWest - positions * charWidth;
 
       while (y < canvasHeight)
       {
-        gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
+        gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
                 positions * charWidth, 0);
-        if (y + repeatHeight < canvasHeight - repeatHeight
+        if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
                 && (xpos + viewportWidth <= xMax))
         {
-          gg.copyArea(labelWidthWest, y + repeatHeight, -positions
-                  * charWidth, repeatHeight, widthToCopy, -repeatHeight);
-        }
-
-        if (av.getScaleLeftWrapped())
-        {
-          drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
-        }
-        if (av.getScaleRightWrapped())
-        {
-          drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
+          gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
+                  * charWidth, heightToCopy, widthToCopy,
+                  -wrappedRepeatHeightPx);
         }
 
-        y += repeatHeight;
+        y += wrappedRepeatHeightPx;
         xpos += viewportWidth;
       }
     }