import jalview.viewmodel.ViewportListenerI;
import jalview.viewmodel.ViewportRanges;
- import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.util.Iterator;
import java.util.List;
-import javax.swing.JComponent;
+import javax.swing.JPanel;
/**
* 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
+public class SeqCanvas extends JPanel implements ViewportListenerI
{
private static final String ZEROS = "0000000000";
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.
int yPos = ypos + charHeight;
int startX = startx;
int endX = endx;
-
+
if (av.hasHiddenColumns())
{
HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
}
}
+
/*
* white fill the space for the scale
*/
g.drawString(valueAsString, xOffset, y);
}
}
+
}
/**
*/
public void fastPaint(int horizontal, int vertical)
{
- if (fastpainting || gg == null || img == null)
+ if (fastpainting || img == null)
{
return;
}
int transX = 0;
int transY = 0;
+ Graphics gg = img.getGraphics();
gg.copyArea(horizontal * charWidth, vertical * charHeight,
img.getWidth(), img.getHeight(), -horizontal * charWidth,
-vertical * charHeight);
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.
@Override
public void paintComponent(Graphics g)
{
- super.paintComponent(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);
-
- drawSelectionGroup((Graphics2D) g,
- ranges.getStartRes(), ranges.getEndRes(),
- ranges.getStartSeq(), ranges.getEndSeq());
-
+
- // selectImage is the selection group outline image
- BufferedImage selectImage = drawSelectionGroup(ranges.getStartRes(),
- ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
-
if ((img != null) && (fastPaint
|| (getVisibleRect().width != g.getClipBounds().width)
|| (getVisibleRect().height != g.getClipBounds().height)))
{
- BufferedImage lcimg = buildLocalImage(selectImage);
- g.drawImage(lcimg, 0, 0, this);
+ g.drawImage(img, 0, 0, this);
+
+ drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
+ ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+
fastPaint = false;
}
- else if ((width > 0) && (height > 0))
+ else if (width > 0 && height > 0)
{
- // 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 any
+ * if we have no img or the size has changed, make a new one
+ */
if (img == null || width != img.getWidth()
|| height != img.getHeight())
{
- img = setupImage();
- if (img == null)
- {
- return;
- }
+ 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());
drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
ranges.getStartSeq(), ranges.getEndSeq(), 0);
}
-
-
- gg.dispose();
- // lcimg is a local *copy* of img which we'll draw selectImage on top of
- BufferedImage lcimg = buildLocalImage(selectImage);
- g.drawImage(lcimg, 0, 0, this);
+ drawSelectionGroup(gg, ranges.getStartRes(),
+ ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+ g.drawImage(img, 0, 0, this);
}
if (av.cursorMode)
{
drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
- BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
+ drawSelectionGroup((Graphics2D) g1, startRes, endRes,
startSeq, endSeq);
- if (selectImage != null)
- {
- ((Graphics2D) g1).setComposite(AlphaComposite
- .getInstance(AlphaComposite.SRC_OVER));
- g1.drawImage(selectImage, 0, 0, this);
- }
}
/**
public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
int canvasHeight, int startRes)
{
- SequenceGroup group = av.getSelectionGroup();
-
drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
+ SequenceGroup group = av.getSelectionGroup();
if (group != null)
{
- BufferedImage selectImage = null;
- try
- {
- selectImage = new BufferedImage(canvasWidth, canvasHeight,
- BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
- } catch (OutOfMemoryError er)
- {
- System.gc();
- System.err.println("Print image OutOfMemory Error.\n" + er);
- new OOMWarning("Creating wrapped alignment image for printing", er);
- }
- if (selectImage != null)
- {
- Graphics2D g2 = selectImage.createGraphics();
- setupSelectionGroup(g2, selectImage);
- drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
+ drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
startRes);
}
}
- /*
- * Make a local image by combining the cached image img
- * with any selection
- */
- private BufferedImage buildLocalImage(BufferedImage selectImage)
- {
- // clone the cached image
- BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
- img.getType());
-
- // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
- // img.getType());
- Graphics2D g2d = lcimg.createGraphics();
- g2d.drawImage(img, 0, 0, null);
-
- // overlay selection group on lcimg
- if (selectImage != null)
- {
- g2d.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
- g2d.drawImage(selectImage, 0, 0, this);
- }
-
- g2d.dispose();
-
- return lcimg;
- }
-
- /*
- * Set up a buffered image of the correct height and size for the sequence canvas
- */
- private BufferedImage setupImage()
- {
- BufferedImage lcimg = null;
-
- int charWidth = av.getCharWidth();
- int charHeight = av.getCharHeight();
-
- int width = getWidth();
- int height = getHeight();
-
- width -= (width % charWidth);
- height -= (height % charHeight);
-
- if ((width < 1) || (height < 1))
- {
- return null;
- }
-
- try
- {
- lcimg = new BufferedImage(width, height,
- BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
- } catch (OutOfMemoryError er)
- {
- System.gc();
- System.err.println(
- "Group image OutOfMemory Redraw Error.\n" + er);
- new OOMWarning("Creating alignment image for display", er);
-
- return null;
- }
-
- return lcimg;
- }
-
/**
* Returns the visible width of the canvas in residues, after allowing for
* East or West scales (if shown)
calculateWrappedGeometry(canvasWidth, canvasHeight);
/*
- * draw one width at a time (including any scales or annotation shown),
+ * draw one width at a time (excluding any scales or annotation shown),
* until we have run out of either alignment or vertical space available
*/
int ypos = wrappedSpaceAboveAlignment;
*/
wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
// add sequences
- wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
+ wrappedRepeatHeightPx += av.getAlignment().getHeight()
* charHeight;
// add annotations panel height if shown
wrappedRepeatHeightPx += getAnnotationHeight();
int xOffset = labelWidthWest
+ ((startColumn - ranges.getStartRes()) % viewportWidth)
* charWidth;
+
+ // BH 2018 note: I have switched to using Graphics.create() here because it is
+ // more reliable (and simpler) to reset. The difference seems to be that SwingJS
+ // automatically sets a clipping region on an image to be the image dimensions, whereas
+ // Java sets no clip for an image. (A bug? Go figure!)
+ // Since we are using an off-screen BufferedImage here, the result is that g.getClip()
+ // returns non-null in JavaScript but not Java.
+ //
+ // Anyway, this works and, I suggest, is better design anyway.
+ //
+ // Graphics g = gg.create();
+ // mc 30/08/18 undone because gnu.jpdf.PDFGraphics doesn't handle
+ // get/setTransform
+
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();
-
if (clip == null)
{
g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
viewportWidth * charWidth, (int) clip.getBounds().getHeight());
}
+
/*
* white fill the region to be drawn (so incremental fast paint doesn't
* scribble over an existing image)
startColumn, endx + 1);
g.translate(0, -cHeight - ypos - 3);
}
- g.setClip(clip);
+ // g.dispose();
g.translate(-xOffset, 0);
+// g.setClip(clip);
}
/**
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);
// 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.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND, 3f, new float[]
+ { 5f, 3f }, 0f));
+ g.setColor(Color.RED);
while ((ypos <= canvasHeight) && (startx < maxwidth))
{
// set end value to be start + width, or maxwidth, whichever is smaller
// update horizontal offset
startx += cWidth;
}
+ g.setStroke(new BasicStroke());
}
int getAnnotationHeight()
}
+ /**
+ * Draws the outlines of any groups defined on the alignment (excluding the
+ * current selection group, if any)
+ *
+ * @param g1
+ * @param startRes
+ * @param endRes
+ * @param startSeq
+ * @param endSeq
+ * @param offset
+ */
void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
int startSeq, int endSeq, int offset)
{
Graphics2D g = (Graphics2D) g1;
- //
- // ///////////////////////////////////
- // Now outline any areas if necessary
- // ///////////////////////////////////
SequenceGroup group = null;
int groupIndex = -1;
if (group != null)
{
do
{
g.setColor(group.getOutlineColour());
-
drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
endSeq, offset);
groupIndex++;
--
- g.setStroke(new BasicStroke());
-
if (groupIndex >= av.getAlignment().getGroups().size())
{
break;
}
--
group = av.getAlignment().getGroups().get(groupIndex);
--
} while (groupIndex < av.getAlignment().getGroups().size());
--
}
--
}
-
- /*
- * Draw the selection group as a separate image and overlay
+ /**
+ * Draws the outline of the current selection group (if any)
+ *
+ * @param g
+ * @param startRes
+ * @param endRes
+ * @param startSeq
+ * @param endSeq
*/
- private BufferedImage drawSelectionGroup(int startRes, int endRes,
+ private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
int startSeq, int endSeq)
{
SequenceGroup group = av.getSelectionGroup();
if (group == null)
{
- // nothing to draw
- return null;
+ return;
}
- // set up drawing colour
- Graphics2D g = (Graphics2D) selectionImage.getGraphics();
-
- setupSelectionGroup(g, selectionImage);
-
+ g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND, 3f, new float[]
+ { 5f, 3f }, 0f));
+ g.setColor(Color.RED);
if (!av.getWrapAlignment())
{
drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
drawWrappedSelection(g, group, getWidth(), getHeight(),
av.getRanges().getStartRes());
}
-
- g.dispose();
- return selectionImage;
+ g.setStroke(new BasicStroke());
}
/**
}
- /*
- * Set up graphics for selection group
- */
- private void setupSelectionGroup(Graphics2D g,
- BufferedImage selectionImage)
- {
- // set background to transparent
- g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
- g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
-
- // set up foreground to draw red dashed line
- g.setComposite(AlphaComposite.Src);
- g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
- BasicStroke.JOIN_ROUND, 3f, new float[]
- { 5f, 3f }, 0f));
- g.setColor(Color.RED);
- }
-
- /*
+ /**
* Draw a selection group over an unwrapped alignment
- * @param g graphics object to draw with
- * @param group selection group
- * @param startRes start residue of area to draw
- * @param endRes end residue of area to draw
- * @param startSeq start sequence of area to draw
- * @param endSeq end sequence of area to draw
- * @param offset vertical offset (used when called from wrapped alignment code)
+ *
+ * @param g
+ * graphics object to draw with
+ * @param group
+ * selection group
+ * @param startRes
+ * start residue of area to draw
+ * @param endRes
+ * end residue of area to draw
+ * @param startSeq
+ * start sequence of area to draw
+ * @param endSeq
+ * end sequence of area to draw
+ * @param offset
+ * vertical offset (used when called from wrapped alignment code)
*/
private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
int startRes, int endRes, int startSeq, int endSeq, int offset)
}
}
- /*
- * Draw the selection group as a separate image and overlay
+ /**
+ * Draws part of a selection group outline
+ *
+ * @param g
+ * @param group
+ * @param startRes
+ * @param endRes
+ * @param startSeq
+ * @param endSeq
+ * @param verticalOffset
*/
private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
int startRes, int endRes, int startSeq, int endSeq,
* 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;
}
}
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;
return;
}
- if (fastpainting || gg == null)
+ if (fastpainting || img == null)
{
return;
}
try
{
+
+ Graphics gg = img.getGraphics();
+
calculateWrappedGeometry(getWidth(), getHeight());
/*
*/
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();
/*
* white fill first to erase annotations
*/
+
+
gg.translate(xOffset, 0);
gg.setColor(Color.white);
gg.fillRect(labelWidthWest, ypos,
gg.translate(-xOffset, 0);
drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+
}
/*
gg.setColor(Color.white);
gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
}
- }
+ gg.dispose();
+ }
/**
* Shifts the visible alignment by the specified number of columns - left if
{
return;
}
+
+ Graphics gg = img.getGraphics();
+
int charWidth = av.getCharWidth();
int canvasHeight = getHeight();
* charWidth, heightToCopy, widthToCopy,
-wrappedRepeatHeightPx);
}
-
y += wrappedRepeatHeightPx;
xpos += viewportWidth;
}
}
+ gg.dispose();
}
*/
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 gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
+
+ Graphics gg = img.getGraphics();
+
for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
.getEndSeq(); seqNo++)
{
}
}
+ gg.dispose();
+
return matchFound;
}