*/
package jalview.gui;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.VisibleContigsIterator;
-import jalview.renderer.ScaleRenderer;
-import jalview.renderer.ScaleRenderer.ScaleMark;
-import jalview.util.Comparison;
-import jalview.viewmodel.ViewportListenerI;
-import jalview.viewmodel.ViewportRanges;
-
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
+import java.awt.Rectangle;
import java.awt.RenderingHints;
-import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.util.Iterator;
import java.util.List;
-import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.VisibleContigsIterator;
+import jalview.renderer.ScaleRenderer;
+import jalview.renderer.ScaleRenderer.ScaleMark;
+import jalview.util.Comparison;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
/**
* The Swing component on which the alignment sequences, and annotations (if
* Wrapped mode, but not the scale above in Unwrapped mode.
*
*/
-public class SeqCanvas extends JComponent implements ViewportListenerI
+@SuppressWarnings("serial")
+public class SeqCanvas extends JPanel implements ViewportListenerI
{
/**
- * vertical gap in pixels between sequences and annotations when in wrapped mode
+ * vertical gap in pixels between sequences and annotations when in wrapped
+ * mode
*/
static final int SEQS_ANNOTATION_GAP = 3;
private final SequenceRenderer seqRdr;
- private boolean fastPaint = false;
+ boolean fastPaint = false;
private boolean fastpainting = false;
private int wrappedVisibleWidths; // number of wrapped widths displayed
- private Graphics2D gg;
+ // Don't do this! Graphics handles are supposed to be transient
+ // private Graphics2D gg;
/**
* Creates a new SeqCanvas object.
g.drawString(valueAsString, xOffset, y);
}
}
+
}
/**
* <li>scrolling by trackpad, middle mouse button, or other device</li>
* <li>by moving the box in the Overview window</li>
* <li>programmatically to make a highlighted position visible</li>
+ * <li>pasting a block of sequences</li>
* </ul>
*
* @param horizontal
*/
public void fastPaint(int horizontal, int vertical)
{
- if (fastpainting || gg == null || img == null)
+
+ // effectively:
+ // if (horizontal != 0 && vertical != 0)
+ // throw new InvalidArgumentException();
+ if (fastpainting || img == null)
{
return;
}
fastpainting = true;
fastPaint = true;
-
try
{
int charHeight = av.getCharHeight();
int charWidth = av.getCharWidth();
-
+
ViewportRanges ranges = av.getRanges();
int startRes = ranges.getStartRes();
int endRes = ranges.getEndRes();
int endSeq = ranges.getEndSeq();
int transX = 0;
int transY = 0;
-
- gg.copyArea(horizontal * charWidth, vertical * charHeight,
- img.getWidth(), img.getHeight(), -horizontal * charWidth,
- -vertical * charHeight);
if (horizontal > 0) // scrollbar pulled right, image to the left
{
}
}
+ // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
+ // + horizontal + " " + vertical + " " + startRes + " " + endRes
+ // + " " + startSeq + " " + endSeq);
+
+ Graphics gg = img.getGraphics();
+ gg.copyArea(horizontal * charWidth, vertical * charHeight,
+ img.getWidth(), img.getHeight(), -horizontal * charWidth,
+ -vertical * charHeight);
+
+ /** @j2sNative xxi = this.img */
+
gg.translate(transX, transY);
drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
gg.translate(-transX, -transY);
+ gg.dispose();
// Call repaint on alignment panel so that repaints from other alignment
// panel components can be aggregated. Otherwise performance of the
// overview window and others may be adversely affected.
+ // System.out.println("SeqCanvas fastPaint() repaint() request...");
av.getAlignPanel().repaint();
} finally
{
@Override
public void paintComponent(Graphics g)
{
- super.paintComponent(g);
-
+
int charHeight = av.getCharHeight();
int charWidth = av.getCharWidth();
-
- ViewportRanges ranges = av.getRanges();
-
+
int width = getWidth();
int height = getHeight();
-
+
width -= (width % charWidth);
height -= (height % charHeight);
-
- if ((img != null) && (fastPaint
- || (getVisibleRect().width != g.getClipBounds().width)
- || (getVisibleRect().height != g.getClipBounds().height)))
- {
- g.drawImage(img, 0, 0, this);
- drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
- ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+ // BH 2019 can't possibly fastPaint if either width or height is 0
+
+ if (width == 0 || height == 0)
+ {
+ return;
+ }
+ ViewportRanges ranges = av.getRanges();
+ int startRes = ranges.getStartRes();
+ int startSeq = ranges.getStartSeq();
+ int endRes = ranges.getEndRes();
+ int endSeq = ranges.getEndSeq();
+
+ // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
+ // repaint() requests in unpredictable ways. In this case, the issue was
+ // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
+ // repaint request preceded two full requests, thus resulting
+ // in a full request for paint. In constrast, in JavaScript, the three
+ // requests were bundled together into one, so the fastPaint flag was
+ // still present for the second and third request.
+ //
+ // This resulted in incomplete painting.
+ //
+ // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
+ // in PaintRefresher when the target to be painted is one of those two
+ // components.
+ //
+ // BH 2019.04.22
+ //
+ // An initial idea; can be removed once we determine this issue is closed:
+ // if (av.isFastPaintDisabled())
+ // {
+ // fastPaint = false;
+ // }
+
+ Rectangle vis, clip;
+ if (img != null
+ && (fastPaint
+ || (vis = getVisibleRect()).width != (clip = g
+ .getClipBounds()).width
+ || vis.height != clip.height))
+ {
+ g.drawImage(img, 0, 0, this);
+ drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
+ endSeq);
fastPaint = false;
}
- else if (width > 0 && height > 0)
+ else
{
- /*
- * img is a cached version of the last view we drew, if any
- * if we have no img or the size has changed, make a new one
- */
+ // img is a cached version of the last view we drew.
+ // If we have no img or the size has changed, make a new one.
+ //
if (img == null || width != img.getWidth()
|| height != img.getHeight())
{
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- gg = (Graphics2D) img.getGraphics();
- gg.setFont(av.getFont());
}
-
+
+ Graphics2D gg = (Graphics2D) img.getGraphics();
+ gg.setFont(av.getFont());
+
if (av.antiAlias)
{
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
-
+
gg.setColor(Color.white);
gg.fillRect(0, 0, img.getWidth(), img.getHeight());
-
+
if (av.getWrapAlignment())
{
drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
}
else
{
- drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
- ranges.getStartSeq(), ranges.getEndSeq(), 0);
+ drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
}
- drawSelectionGroup(gg, ranges.getStartRes(),
- ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+ drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
g.drawImage(img, 0, 0, this);
+ gg.dispose();
}
if (av.cursorMode)
{
- drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
- ranges.getStartSeq(), ranges.getEndSeq());
+ drawCursor(g, startRes, endRes, startSeq, endSeq);
}
}
-
+
/**
* Draw an alignment panel for printing
*
{
drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
- drawSelectionGroup((Graphics2D) g1, startRes, endRes,
- startSeq, endSeq);
+ drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
}
/**
if (group != null)
{
drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
- startRes);
+ startRes);
}
}
FontMetrics fm = getFontMetrics(av.getFont());
int labelWidth = 0;
-
+
if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
{
labelWidth = getLabelWidth(fm);
int currentWidth = 0;
while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
{
- int endColumn = Math
- .min(maxWidth, start + wrappedWidthInResidues - 1);
+ int endColumn = Math.min(maxWidth,
+ start + wrappedWidthInResidues - 1);
drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
ypos += wrappedRepeatHeightPx;
start += wrappedWidthInResidues;
* - start with space above plus sequences
*/
wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
- wrappedRepeatHeightPx += av.getAlignment().getHeight()
- * charHeight;
+ wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
/*
* add annotations panel height if shown
* compute width in residues; this also sets East and West label widths
*/
int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
-
+ av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
/*
* limit visibleWidths to not exceed width of alignment
*/
int charWidth = av.getCharWidth();
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
- Shape clip = g.getClip();
+ * charWidth;
- if (clip == null)
- {
- g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
- }
- else
- {
- g.setClip(0, (int) clip.getBounds().getY(),
- viewportWidth * charWidth, (int) clip.getBounds().getHeight());
- }
+ g.translate(xOffset, 0);
/*
* white fill the region to be drawn (so incremental fast paint doesn't
startColumn, endx + 1);
g.translate(0, -yShift);
}
- g.setClip(clip);
g.translate(-xOffset, 0);
}
int charWidth = av.getCharWidth();
g.setFont(av.getFont());
+
g.setColor(Color.black);
int ypos = wrappedSpaceAboveAlignment;
if (av.getScaleRightWrapped())
{
int x = labelWidthWest + viewportWidth * charWidth;
+
g.translate(x, 0);
drawVerticalScale(g, startCol, endColumn, ypos, false);
g.translate(-x, 0);
*/
g.translate(labelWidthWest, 0);
g.setColor(Color.white);
- g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
- * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
+ g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
+ viewportWidth * charWidth + labelWidthWest,
+ wrappedSpaceAboveAlignment);
g.setColor(Color.black);
g.translate(-labelWidthWest, 0);
* Draw a selection group over a wrapped alignment
*/
private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
- int canvasWidth,
- int canvasHeight, int startRes)
+ int canvasWidth, int canvasHeight, int startRes)
{
// chop the wrapped alignment extent up into panel-sized blocks and treat
// each block as if it were a block from an unwrapped alignment
g.translate(labelWidthWest, 0);
drawUnwrappedSelection(g, group, startx, endx, 0,
- av.getAlignment().getHeight() - 1,
- ypos);
+ av.getAlignment().getHeight() - 1, ypos);
g.translate(-labelWidthWest, 0);
ypos += wrappedRepeatHeightPx;
}
/**
- * Answers zero if annotations are not shown, otherwise recalculates and answers
- * the total height of all annotation rows in pixels
+ * Answers zero if annotations are not shown, otherwise recalculates and
+ * answers the total height of all annotation rows in pixels
*
* @return
*/
do
{
g.setColor(group.getOutlineColour());
-
drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
endSeq, offset);
groupIndex++;
-
if (groupIndex >= av.getAlignment().getGroups().size())
{
break;
}
-
group = av.getAlignment().getGroups().get(groupIndex);
-
} while (groupIndex < av.getAlignment().getGroups().size());
-
}
-
}
/**
* the cursor drawn on it, if any
*/
private void drawCursor(Graphics g, int startRes, int endRes,
- int startSeq,
- int endSeq)
+ int startSeq, int endSeq)
{
// convert the cursorY into a position on the visible alignment
int cursor_ypos = cursorY;
}
}
-
/**
* Draw a selection group over an unwrapped alignment
*
int startRes, int endRes, int startSeq, int endSeq, int offset)
{
int charWidth = av.getCharWidth();
-
+
if (!av.hasHiddenColumns())
{
drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
blockStart = region[0];
g.translate(screenY * charWidth, 0);
- drawPartialGroupOutline(g, group,
- blockStart, blockEnd, startSeq, endSeq, offset);
+ drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
+ endSeq, offset);
g.translate(-screenY * charWidth, 0);
screenY += blockEnd - blockStart + 1;
g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
}
}
-
+
+ /**
+ * Highlights search results in the visible region by rendering as white text
+ * on a black background. Any previous highlighting is removed. Answers true
+ * if any highlight was left on the visible alignment (so status bar should be
+ * set to match), else false. This method does _not_ set the 'fastPaint' flag,
+ * so allows the next repaint to update the whole display.
+ *
+ * @param results
+ * @return
+ */
+ public boolean highlightSearchResults(SearchResultsI results)
+ {
+ return highlightSearchResults(results, false);
+
+ }
+
/**
* Highlights search results in the visible region by rendering as white text
* on a black background. Any previous highlighting is removed. Answers true
* if any highlight was left on the visible alignment (so status bar should be
* set to match), else false.
* <p>
- * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
- * alignment had to be scrolled to show the highlighted region, then it should
- * be fully redrawn, otherwise a fast paint can be performed. This argument
- * could be removed if fast paint of scrolled wrapped alignment is coded in
- * future (JAL-2609).
+ * Optionally, set the 'fastPaint' flag for a faster redraw if only the
+ * highlighted regions are modified. This speeds up highlighting across linked
+ * alignments.
+ * <p>
+ * Currently fastPaint is not implemented for scrolled wrapped alignments. If
+ * a wrapped alignment had to be scrolled to show the highlighted region, then
+ * it should be fully redrawn, otherwise a fast paint can be performed. This
+ * argument could be removed if fast paint of scrolled wrapped alignment is
+ * coded in future (JAL-2609).
*
* @param results
- * @param noFastPaint
+ * @param doFastPaint
+ * if true, sets a flag so the next repaint only redraws the modified
+ * image
* @return
*/
public boolean highlightSearchResults(SearchResultsI results,
- boolean noFastPaint)
+ boolean doFastPaint)
{
if (fastpainting)
{
boolean wrapped = av.getWrapAlignment();
try
{
- fastPaint = !noFastPaint;
+ fastPaint = doFastPaint;
fastpainting = fastPaint;
/*
*/
protected boolean drawMappedPositions(SearchResultsI results)
{
- if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
+ if ((results == null) || (img == null)) // JAL-2784 check gg is not null
{
return false;
}
{
firstCol = alignment.getHiddenColumns()
.absoluteToVisibleColumn(firstCol);
- lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
+ lastCol = alignment.getHiddenColumns()
+ .absoluteToVisibleColumn(lastCol);
}
int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
+ Graphics gg = img.getGraphics();
gg.translate(transX, transY);
drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
gg.translate(-transX, -transY);
+ gg.dispose();
}
return matchFound;
public void propertyChange(PropertyChangeEvent evt)
{
String eventName = evt.getPropertyName();
-
+ // System.err.println(">>SeqCanvas propertyChange " + eventName);
if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
{
fastPaint = true;
else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
{
fastPaint = false;
+ // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
repaint();
return;
}
scrollX = -range;
}
}
- // Both scrolling and resizing change viewport ranges: scrolling changes
- // both start and end points, but resize only changes end values.
- // Here we only want to fastpaint on a scroll, with resize using a normal
- // paint, so scroll events are identified as changes to the horizontal or
- // vertical start value.
- if (eventName.equals(ViewportRanges.STARTRES))
+ // Both scrolling and resizing change viewport ranges: scrolling changes
+ // both start and end points, but resize only changes end values.
+ // Here we only want to fastpaint on a scroll, with resize using a normal
+ // paint, so scroll events are identified as changes to the horizontal or
+ // vertical start value.
+ if (eventName.equals(ViewportRanges.STARTRES))
+ {
+ if (av.getWrapAlignment())
{
- if (av.getWrapAlignment())
- {
- fastPaintWrapped(scrollX);
- }
- else
- {
- fastPaint(scrollX, 0);
- }
+ fastPaintWrapped(scrollX);
}
- else if (eventName.equals(ViewportRanges.STARTSEQ))
+ else
{
- // scroll
- fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+ fastPaint(scrollX, 0);
}
- else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+ }
+ else if (eventName.equals(ViewportRanges.STARTSEQ))
+ {
+ // scroll
+ fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+ }
+ else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+ {
+ if (av.getWrapAlignment())
{
- if (av.getWrapAlignment())
- {
- fastPaintWrapped(scrollX);
- }
- else
- {
- fastPaint(scrollX, 0);
- }
+ fastPaintWrapped(scrollX);
+ }
+ else
+ {
+ fastPaint(scrollX, 0);
+ }
}
else if (eventName.equals(ViewportRanges.STARTSEQ))
{
return;
}
- if (fastpainting || gg == null)
+ if (fastpainting || img == null)
{
return;
}
try
{
+
+ Graphics gg = img.getGraphics();
+
calculateWrappedGeometry(getWidth(), getHeight());
/*
if (scrollX < 0)
{
int startRes = ranges.getStartRes();
- drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
- - scrollX - 1, getHeight());
+ drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
+ startRes - scrollX - 1, getHeight());
}
else
{
*/
drawWrappedDecorators(gg, ranges.getStartRes());
+ gg.dispose();
+
repaint();
} finally
{
return;
}
+ Graphics gg = img.getGraphics();
+
ViewportRanges ranges = av.getRanges();
int viewportWidth = ranges.getViewportWidth();
int charWidth = av.getCharWidth();
*/
int visibleWidths = wrappedVisibleWidths;
int canvasHeight = getHeight();
- boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
+ boolean lastWidthPartHeight = (wrappedVisibleWidths
+ * wrappedRepeatHeightPx) > canvasHeight;
if (lastWidthPartHeight)
{
/*
* white fill first to erase annotations
*/
+
gg.translate(xOffset, 0);
gg.setColor(Color.white);
- gg.fillRect(labelWidthWest, ypos,
- (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
+ gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
+ wrappedRepeatHeightPx);
gg.translate(-xOffset, 0);
drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+
}
/*
gg.setColor(Color.white);
gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
}
+ gg.dispose();
}
/**
{
return;
}
+
+ Graphics gg = img.getGraphics();
+
int charWidth = av.getCharWidth();
int canvasHeight = getHeight();
if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
&& (xpos + viewportWidth <= xMax))
{
- gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
- * charWidth, heightToCopy, widthToCopy,
+ gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
+ -positions * charWidth, heightToCopy, widthToCopy,
-wrappedRepeatHeightPx);
}
-
y += wrappedRepeatHeightPx;
xpos += viewportWidth;
}
}
+ gg.dispose();
}
-
/**
* Redraws any positions in the search results in the visible region of a
* wrapped alignment. Any highlights are drawn depending on the search results
*/
protected boolean drawMappedPositionsWrapped(SearchResultsI results)
{
- if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
+ if ((results == null) || (img == null)) // JAL-2784 check gg is not null
{
return false;
}
}
int firstVisibleColumn = ranges.getStartRes();
- int lastVisibleColumn = ranges.getStartRes() + repeats
- * ranges.getViewportWidth() - 1;
+ int lastVisibleColumn = ranges.getStartRes()
+ + repeats * ranges.getViewportWidth() - 1;
AlignmentI alignment = av.getAlignment();
if (av.hasHiddenColumns())
int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
+ Graphics gg = img.getGraphics();
+
for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
.getEndSeq(); seqNo++)
{
* transX: offset from left edge of canvas to residue position
*/
int transX = labelWidthWest
- + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
- * av.getCharWidth();
+ + ((displayColumn - ranges.getStartRes())
+ % wrappedWidth) * av.getCharWidth();
/*
* transY: offset from top edge of canvas to residue position
}
}
}
-
+
+ gg.dispose();
+
return matchFound;
}
{
return labelWidthWest;
}
+
}