X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqCanvas.java;h=c6a8825bd86b43f5e2d48aa535bd88a5ce60f3e7;hb=e3df4942a8d6e95b622a4abe916f9d2b27ec523b;hp=edb41e6fec14506b051c21a690a027414f25b1a2;hpb=bee4e181ec5e773444de9ac85ef4e9fc6ca99ed6;p=jalview.git diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index edb41e6..c6a8825 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -30,6 +30,7 @@ import jalview.renderer.ScaleRenderer.ScaleMark; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; @@ -54,16 +55,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { final FeatureRenderer fr; - final SequenceRenderer sr; + final SequenceRenderer seqRdr; BufferedImage img; Graphics2D gg; - int imgWidth; - - int imgHeight; - AlignViewport av; boolean fastPaint = false; @@ -76,6 +73,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int cursorY = 0; + int charHeight = 0; + + int charWidth = 0; + + boolean fastpainting = false; + /** * Creates a new SeqCanvas object. * @@ -87,7 +90,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI this.av = ap.av; updateViewport(); fr = new FeatureRenderer(ap); - sr = new SequenceRenderer(av); + seqRdr = new SequenceRenderer(av); setLayout(new BorderLayout()); PaintRefresher.Register(this, av.getSequenceSetId()); setBackground(Color.white); @@ -97,7 +100,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI public SequenceRenderer getSequenceRenderer() { - return sr; + return seqRdr; } public FeatureRenderer getFeatureRenderer() @@ -105,8 +108,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI return fr; } - int charHeight = 0, charWidth = 0; - private void updateViewport() { charHeight = av.getCharHeight(); @@ -266,7 +267,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } } - boolean fastpainting = false; /** * need to make this thread safe move alignment rendering in response to @@ -295,8 +295,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int transX = 0; int transY = 0; - gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth, - imgHeight, -horizontal * charWidth, -vertical * charHeight); + gg.copyArea(horizontal * charWidth, vertical * charHeight, + img.getWidth(), img.getHeight(), -horizontal * charWidth, + -vertical * charHeight); if (horizontal > 0) // scrollbar pulled right, image to the left { @@ -317,7 +318,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } else { - transY = imgHeight - ((vertical + 1) * charHeight); + transY = img.getHeight() - ((vertical + 1) * charHeight); } } else if (vertical < 0) @@ -351,74 +352,131 @@ public class SeqCanvas extends JComponent implements ViewportListenerI @Override public void paintComponent(Graphics g) { - updateViewport(); - BufferedImage lcimg = img; // take reference since other threads may null - // img and call later. super.paintComponent(g); - if (lcimg != null - && (fastPaint - || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g - .getClipBounds().height))) - { - g.drawImage(lcimg, 0, 0, this); - fastPaint = false; - return; - } + updateViewport(); + + ViewportRanges ranges = av.getRanges(); + + int width = getWidth(); + int height = getHeight(); - // this draws the whole of the alignment - imgWidth = getWidth(); - imgHeight = getHeight(); + width -= (width % charWidth); + height -= (height % charHeight); - imgWidth -= (imgWidth % charWidth); - imgHeight -= (imgHeight % charHeight); + // selectImage is the selection group outline image + BufferedImage selectImage = drawSelectionGroup( + ranges.getStartRes(), ranges.getEndRes(), + ranges.getStartSeq(), ranges.getEndSeq()); - if ((imgWidth < 1) || (imgHeight < 1)) + if (fastPaint || (getVisibleRect().width != g.getClipBounds().width) + || (getVisibleRect().height != g.getClipBounds().height)) { - return; + BufferedImage lcimg = buildLocalImage(selectImage); + g.drawImage(lcimg, 0, 0, this); + fastPaint = false; } - - if (lcimg == null || imgWidth != lcimg.getWidth() - || imgHeight != lcimg.getHeight()) + else if ((width > 0) && (height > 0)) { - try + // 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()) { - lcimg = img = new BufferedImage(imgWidth, imgHeight, - BufferedImage.TYPE_INT_RGB); + img = setupImage(); + if (img == null) + { + return; + } gg = (Graphics2D) img.getGraphics(); gg.setFont(av.getFont()); - } catch (OutOfMemoryError er) + } + + if (av.antiAlias) { - System.gc(); - System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er); - new OOMWarning("Creating alignment image for display", er); + gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } - return; + 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); + } + + // 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); } + } - if (av.antiAlias) + /* + * 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()); + Graphics2D g2d = lcimg.createGraphics(); + g2d.drawImage(img, 0, 0, null); + + // overlay selection group on lcimg + if (selectImage != null) { - gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + g2d.drawImage(selectImage, 0, 0, this); } + g2d.dispose(); - gg.setColor(Color.white); - gg.fillRect(0, 0, imgWidth, imgHeight); + return lcimg; + } - ViewportRanges ranges = av.getRanges(); - if (av.getWrapAlignment()) + private void paintSeqGroup() + { + fastPaint = true; + repaint(); + } + + private BufferedImage setupImage() + { + BufferedImage lcimg = null; + + int width = getWidth(); + int height = getHeight(); + + width -= (width % charWidth); + height -= (height % charHeight); + + if ((width < 1) || (height < 1)) { - drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes()); + return null; } - else + + try { - drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(), - ranges.getStartSeq(), ranges.getEndSeq(), 0); - } + lcimg = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works + } catch (OutOfMemoryError er) + { + System.gc(); + System.err.println( + "Selection Group image OutOfMemory Redraw Error.\n" + er); + new OOMWarning("Creating alignment image for display", er); - g.drawImage(lcimg, 0, 0, this); + return null; + } + return lcimg; } /** @@ -624,6 +682,62 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } } + /* + * Draw a selection group over a wrapped alignment + */ + private void drawWrappedSelection(Graphics2D g, SequenceGroup group, + int canvasWidth, + int canvasHeight, int startRes) + { + // height gap above each panel + int hgap = charHeight; + if (av.getScaleAboveWrapped()) + { + hgap += charHeight; + } + + int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth; + int cHeight = av.getAlignment().getHeight() * charHeight; + + int startx = startRes; + int endx; + int ypos = hgap; // vertical offset + int maxwidth = av.getAlignment().getWidth(); + + if (av.hasHiddenColumns()) + { + maxwidth = av.getAlignment().getHiddenColumns() + .findColumnPosition(maxwidth); + } + + // chop the wrapped alignment extent up into panel-sized blocks and treat + // each block as if it were a block from an unwrapped alignment + while ((ypos <= canvasHeight) && (startx < maxwidth)) + { + // set end value to be start + width, or maxwidth, whichever is smaller + endx = startx + cWidth - 1; + + if (endx > maxwidth) + { + endx = maxwidth; + } + + g.translate(LABEL_WEST, 0); + + drawUnwrappedSelection(g, group, startx, endx, 0, + av.getAlignment().getHeight() - 1, + ypos); + + g.translate(-LABEL_WEST, 0); + + // update vertical offset + ypos += cHeight + getAnnotationHeight() + hgap; + + // update horizontal offset + startx += cWidth; + } + } + AnnotationPanel annotations; int getAnnotationHeight() @@ -722,13 +836,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } - // int startRes, int endRes, int startSeq, int endSeq, int x, int y, - // int x1, int x2, int y1, int y2, int startx, int starty, private void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq, int offset) { g.setFont(av.getFont()); - sr.prepare(g, av.isRenderGaps()); + seqRdr.prepare(g, av.isRenderGaps()); SequenceI nextSeq; @@ -743,7 +855,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // empty continue; } - sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq), + seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq), startRes, endRes, offset + ((i - startSeq) * charHeight)); if (av.isShowSequenceFeatures()) @@ -762,7 +874,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { for (int r = 0; r < visibleResults.length; r += 2) { - sr.drawHighlightedText(nextSeq, visibleResults[r], + seqRdr.drawHighlightedText(nextSeq, visibleResults[r], visibleResults[r + 1], (visibleResults[r] - startRes) * charWidth, offset + ((i - startSeq) * charHeight)); @@ -773,7 +885,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (av.cursorMode && cursorY == i && cursorX >= startRes && cursorX <= endRes) { - sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth, + seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth, offset + ((i - startSeq) * charHeight)); } } @@ -794,7 +906,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI // /////////////////////////////////// // Now outline any areas if necessary // /////////////////////////////////// - SequenceGroup group = av.getSelectionGroup(); + + SequenceGroup group = null; int sx = -1; int sy = -1; @@ -802,7 +915,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI int groupIndex = -1; int visWidth = (endRes - startRes + 1) * charWidth; - if ((group == null) && (av.getAlignment().getGroups().size() > 0)) + if (av.getAlignment().getGroups().size() > 0) { group = av.getAlignment().getGroups().get(0); groupIndex = 0; @@ -824,7 +937,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI sx = (group.getStartRes() - startRes) * charWidth; sy = offset + ((i - startSeq) * charHeight); // width of group in pixels - ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1; + ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) + - 1; if (sx + ex < 0 || sx > visWidth) { @@ -854,18 +968,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI oldY = sy; inGroup = true; - if (group == av.getSelectionGroup()) - { - g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f }, - 0f)); - g.setColor(Color.RED); - } - else - { - g.setStroke(new BasicStroke()); - g.setColor(group.getOutlineColour()); - } + g.setStroke(new BasicStroke()); + g.setColor(group.getOutlineColour()); } } else @@ -888,15 +992,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI if (sx < 0) { - // ex += sx; - // sx = 0; + ex += sx; + sx = 0; } if (sx + ex > visWidth) { ex = visWidth; } - else if (sx + ex >= (endRes - startRes + 1) * charWidth) { ex = (endRes - startRes + 1) * charWidth; @@ -981,6 +1084,273 @@ public class SeqCanvas extends JComponent implements ViewportListenerI } + + /* + * Draw the selection group as a separate image and overlay + */ + private BufferedImage drawSelectionGroup(int startRes, int endRes, + int startSeq, int endSeq) + { + // get a new image of the correct size + BufferedImage selectionImage = setupImage(); + + if (selectionImage == null) + { + return null; + } + + SequenceGroup group = av.getSelectionGroup(); + if (group == null) + { + // nothing to draw + return null; + } + + // set up drawing colour + Graphics2D g = (Graphics2D) selectionImage.getGraphics(); + + // 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); + + if (!av.getWrapAlignment()) + { + drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq, + 0); + } + else + { + drawWrappedSelection(g, group, getWidth(), getHeight(), + av.getRanges().getStartRes()); + } + + g.dispose(); + return selectionImage; + } + + /* + * 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) + */ + private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group, + int startRes, int endRes, int startSeq, int endSeq, int offset) + { + if (!av.hasHiddenColumns()) + { + drawSelectionGroupPart(g, group, startRes, endRes, startSeq, endSeq, + offset); + } + else + { + // package into blocks of visible columns + int screenY = 0; + int blockStart = startRes; + int blockEnd = endRes; + + for (int[] region : av.getAlignment().getHiddenColumns() + .getHiddenColumnsCopy()) + { + int hideStart = region[0]; + int hideEnd = region[1]; + + if (hideStart <= blockStart) + { + blockStart += (hideEnd - hideStart) + 1; + continue; + } + + blockEnd = hideStart - 1; + + g.translate(screenY * charWidth, 0); + drawSelectionGroupPart(g, group, + blockStart, blockEnd, startSeq, endSeq, offset); + + g.translate(-screenY * charWidth, 0); + screenY += blockEnd - blockStart + 1; + blockStart = hideEnd + 1; + + if (screenY > (endRes - startRes)) + { + // already rendered last block + break; + } + } + + if (screenY <= (endRes - startRes)) + { + // remaining visible region to render + blockEnd = blockStart + (endRes - startRes) - screenY; + g.translate(screenY * charWidth, 0); + drawSelectionGroupPart(g, group, + blockStart, blockEnd, startSeq, endSeq, offset); + + g.translate(-screenY * charWidth, 0); + } + } + } + + /* + * Draw the selection group as a separate image and overlay + */ + private void drawSelectionGroupPart(Graphics2D g, SequenceGroup group, + int startRes, int endRes, int startSeq, int endSeq, + int verticalOffset) + { + int visWidth = (endRes - startRes + 1) * charWidth; + + int oldY = -1; + int i = 0; + boolean inGroup = false; + int top = -1; + int bottom = -1; + + int sx = -1; + int sy = -1; + int xwidth = -1; + + for (i = startSeq; i <= endSeq; i++) + { + // position of start residue of group relative to startRes, in pixels + sx = (group.getStartRes() - startRes) * charWidth; + + // width of group in pixels + xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) + - 1; + + sy = verticalOffset + (i - startSeq) * charHeight; + + if (sx + xwidth < 0 || sx > visWidth) + { + continue; + } + + if ((sx <= (endRes - startRes) * charWidth) + && group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i))) + { + if ((bottom == -1) && !group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i + 1))) + { + bottom = sy + charHeight; + } + + if (!inGroup) + { + if (((top == -1) && (i == 0)) || !group.getSequences(null) + .contains(av.getAlignment().getSequenceAt(i - 1))) + { + top = sy; + } + + oldY = sy; + inGroup = true; + } + } + else + { + if (inGroup) + { + // if start position is visible, draw vertical line to left of + // group + if (sx >= 0 && sx < visWidth) + { + g.drawLine(sx, oldY, sx, sy); + } + + // if end position is visible, draw vertical line to right of + // group + if (sx + xwidth < visWidth) + { + g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); + } + + if (sx < 0) + { + xwidth += sx; + sx = 0; + } + + if (sx + xwidth >= (endRes - startRes + 1) * charWidth) + { + xwidth = (endRes - startRes + 1) * charWidth - sx; + } + + // draw horizontal line at top of group + if (top != -1) + { + g.drawLine(sx, top, sx + xwidth, top); + top = -1; + } + + // draw horizontal line at bottom of group + if (bottom != -1) + { + g.drawLine(sx, bottom, sx + xwidth, bottom); + bottom = -1; + } + + inGroup = false; + } + } + } + + if (inGroup) + { + sy = verticalOffset + ((i - startSeq) * charHeight); + if (sx >= 0 && sx < visWidth) + { + g.drawLine(sx, oldY, sx, sy); + } + + if (sx + xwidth < visWidth) + { + g.drawLine(sx + xwidth, oldY, sx + xwidth, sy); + } + + if (sx < 0) + { + xwidth += sx; + sx = 0; + } + + if (sx + xwidth > visWidth) + { + xwidth = visWidth; + } + else if (sx + xwidth >= (endRes - startRes + 1) * charWidth) + { + xwidth = (endRes - startRes + 1) * charWidth; + } + + if (top != -1) + { + g.drawLine(sx, top, sx + xwidth, top); + top = -1; + } + + if (bottom != -1) + { + g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1); + bottom = -1; + } + + inGroup = false; + } + } + /** * DOCUMENT ME! * @@ -1001,7 +1371,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI { String eventName = evt.getPropertyName(); - if (av.getWrapAlignment()) + if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED)) + { + paintSeqGroup(); + } + else if (av.getWrapAlignment()) { if (eventName.equals(ViewportRanges.STARTRES)) {