+ * Does a minimal update of the image for a scroll movement. This method
+ * handles scroll movements of up to one width of the wrapped alignment (one
+ * click in the vertical scrollbar). Larger movements (for example after a
+ * scroll to highlight a mapped position) trigger a full redraw instead.
+ *
+ * @param scrollX
+ * number of positions scrolled (right if positive, left if negative)
+ */
+ protected void fastPaintWrapped(int scrollX)
+ {
+ if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
+ {
+ /*
+ * shift of more than one view width is
+ * too complicated to handle in this method
+ */
+ fastPaint = false;
+ repaint();
+ return;
+ }
+
+ fastPaint = true;
+
+ /*
+ * relocate the regions of the alignment that are still visible
+ */
+ shiftWrappedAlignment(-scrollX);
+
+ /*
+ * add new columns (scale above, 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);
+ }
+ else
+ {
+ fastPaintWrappedAddRight(scrollX);
+ }
+
+ repaint();
+ }
+
+ /**
+ * 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).
+ *
+ * @param columns
+ */
+ protected void fastPaintWrappedAddRight(int columns)
+ {
+ if (columns == 0)
+ {
+ 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
+ */
+ ViewportRanges ranges = av.getRanges();
+ int visibleAlignmentWidth = ranges.getVisibleAlignmentWidth();
+ int viewportWidth = ranges.getViewportWidth();
+ int maxWidths = visibleAlignmentWidth / viewportWidth;
+ if (visibleAlignmentWidth % viewportWidth > 0)
+ {
+ maxWidths++;
+ }
+ visibleWidths = Math.min(visibleWidths, maxWidths);
+ int widthInColumns = (getWidth() - labelWidthEast - labelWidthWest)
+ / charWidth;
+
+ /**
+ * draw full height alignment in the second last row, last columns, if the
+ * last row was not full height
+ */
+ if (lastWidthPartHeight)
+ {
+ int widthsAbove = visibleWidths - 2;
+ int ypos = repeatHeight * widthsAbove + hgap;
+ 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.setColor(Color.white);
+ gg.fillRect(labelWidthWest, ypos,
+ (endRes - startRes + 1) * charWidth, repeatHeight);
+
+ drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
+ ypos);
+ gg.translate(-xOffset, 0);
+ }
+
+ /*
+ * y-offset for drawing is height of widths above,
+ * plus one gap row
+ */
+ int widthsAbove = visibleWidths - 1;
+ int ypos = repeatHeight * widthsAbove + hgap;
+ int endRes = ranges.getEndRes();
+ endRes += widthsAbove * viewportWidth;
+ endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
+
+ /*
+ * draw one extra column than strictly needed - this is a (harmless)
+ * fudge to ensure scale marks get drawn (JAL-2636)
+ */
+ 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 = getWidth() - labelWidthWest - xOffset;
+ gg.fillRect(labelWidthWest, ypos - hgap, width, repeatHeight);
+
+ 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);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param positions
+ */
+ protected void shiftWrappedAlignment(int positions)
+ {
+ if (positions == 0)
+ {
+ return;
+ }
+
+ int repeatHeight = getRepeatHeightWrapped();
+ ViewportRanges ranges = av.getRanges();
+ int xMax = ranges.getVisibleAlignmentWidth();
+ 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++;
+ }
+
+ if (positions > 0)
+ {
+ /*
+ * shift right (after scroll left)
+ * for each wrapped width (starting with the last), copy (width-positions)
+ * columns from the left margin to the right margin, and copy positions
+ * 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
+ */
+ int y = canvasHeight / repeatHeight * repeatHeight;
+ int copyFromLeftStart = labelWidthWest;
+ int copyFromRightStart = copyFromLeftStart + widthToCopy;
+
+ while (y >= 0)
+ {
+ gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
+ positions * charWidth, 0);
+ if (y > 0)
+ {
+ gg.copyArea(copyFromRightStart, y - repeatHeight, positions
+ * charWidth, repeatHeight, -widthToCopy, repeatHeight);
+ }
+
+ 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;
+ }
+ }
+ else
+ {
+ /*
+ * shift left (after scroll right)
+ * for each wrapped width (starting with the first), copy (width-positions)
+ * columns from the right margin to the left margin, and copy positions
+ * 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 copyFromRightStart = labelWidthWest - positions * charWidth;
+
+ while (y < canvasHeight)
+ {
+ gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
+ positions * charWidth, 0);
+ if (y + repeatHeight < canvasHeight - repeatHeight
+ && (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);
+ }
+
+ y += repeatHeight;
+ xpos += viewportWidth;
+ }
+ }
+ }
+
+ /**
+ * Redraws any positions in the search results in the visible region of a
+ * wrapped alignment. Any highlights are drawn depending on the search results
+ * set on the Viewport, not the <code>results</code> argument. This allows
+ * this method to be called either to clear highlights (passing the previous
+ * search results), or to draw new highlights.