2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.viewmodel.ViewportListenerI;
31 import jalview.viewmodel.ViewportRanges;
33 import java.awt.AlphaComposite;
34 import java.awt.BasicStroke;
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.FontMetrics;
38 import java.awt.Graphics;
39 import java.awt.Graphics2D;
40 import java.awt.RenderingHints;
41 import java.awt.Shape;
42 import java.awt.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.List;
46 import javax.swing.JComponent;
54 public class SeqCanvas extends JComponent implements ViewportListenerI
56 private static String ZEROS = "0000000000";
58 final FeatureRenderer fr;
60 final SequenceRenderer seqRdr;
68 boolean fastPaint = false;
82 boolean fastpainting = false;
84 AnnotationPanel annotations;
87 * Creates a new SeqCanvas object.
92 public SeqCanvas(AlignmentPanel ap)
96 fr = new FeatureRenderer(ap);
97 seqRdr = new SequenceRenderer(av);
98 setLayout(new BorderLayout());
99 PaintRefresher.Register(this, av.getSequenceSetId());
100 setBackground(Color.white);
102 av.getRanges().addPropertyChangeListener(this);
105 public SequenceRenderer getSequenceRenderer()
110 public FeatureRenderer getFeatureRenderer()
115 private void updateViewport()
117 charHeight = av.getCharHeight();
118 charWidth = av.getCharWidth();
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
139 int mpos = mark.column; // (i - startx - 1)
144 String mstring = mark.text;
150 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
152 g.drawLine((mpos * charWidth) + (charWidth / 2),
153 (ypos + 2) - (charHeight / 2),
154 (mpos * charWidth) + (charWidth / 2), ypos - 2);
171 void drawWestScale(Graphics g, int startx, int endx, int ypos)
173 FontMetrics fm = getFontMetrics(av.getFont());
176 if (av.hasHiddenColumns())
178 startx = av.getAlignment().getHiddenColumns()
179 .adjustForHiddenColumns(startx);
180 endx = av.getAlignment().getHiddenColumns()
181 .adjustForHiddenColumns(endx);
184 int maxwidth = av.getAlignment().getWidth();
185 if (av.hasHiddenColumns())
187 maxwidth = av.getAlignment().getHiddenColumns()
188 .findColumnPosition(maxwidth) - 1;
192 for (int i = 0; i < av.getAlignment().getHeight(); i++)
194 SequenceI seq = av.getAlignment().getSequenceAt(i);
200 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
207 value = av.getAlignment().getSequenceAt(i).findPosition(index);
214 int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
216 g.drawString(value + "", x,
217 (ypos + (i * charHeight)) - (charHeight / 5));
234 void drawEastScale(Graphics g, int startx, int endx, int ypos)
238 if (av.hasHiddenColumns())
240 endx = av.getAlignment().getHiddenColumns()
241 .adjustForHiddenColumns(endx);
246 for (int i = 0; i < av.getAlignment().getHeight(); i++)
248 seq = av.getAlignment().getSequenceAt(i);
252 while (index > startx)
254 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
261 value = seq.findPosition(index);
268 g.drawString(String.valueOf(value), 0,
269 (ypos + (i * charHeight)) - (charHeight / 5));
276 * need to make this thread safe move alignment rendering in response to
282 * shift up or down in repaint
284 public void fastPaint(int horizontal, int vertical)
286 if (fastpainting || gg == null)
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 gg.copyArea(horizontal * charWidth, vertical * charHeight,
303 img.getWidth(), img.getHeight(), -horizontal * charWidth,
304 -vertical * charHeight);
306 if (horizontal > 0) // scrollbar pulled right, image to the left
308 transX = (endRes - startRes - horizontal) * charWidth;
309 startRes = endRes - horizontal;
311 else if (horizontal < 0)
313 endRes = startRes - horizontal;
315 else if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
343 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
353 ViewportRanges ranges = av.getRanges();
355 int width = getWidth();
356 int height = getHeight();
358 width -= (width % charWidth);
359 height -= (height % charHeight);
361 // selectImage is the selection group outline image
362 BufferedImage selectImage = drawSelectionGroup(
363 ranges.getStartRes(), ranges.getEndRes(),
364 ranges.getStartSeq(), ranges.getEndSeq());
366 if ((img != null) && (fastPaint
367 || (getVisibleRect().width != g.getClipBounds().width)
368 || (getVisibleRect().height != g.getClipBounds().height)))
370 BufferedImage lcimg = buildLocalImage(selectImage);
371 g.drawImage(lcimg, 0, 0, this);
374 else if ((width > 0) && (height > 0))
376 // img is a cached version of the last view we drew, if any
377 // if we have no img or the size has changed, make a new one
378 if (img == null || width != img.getWidth()
379 || height != img.getHeight())
386 gg = (Graphics2D) img.getGraphics();
387 gg.setFont(av.getFont());
392 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
393 RenderingHints.VALUE_ANTIALIAS_ON);
396 gg.setColor(Color.white);
397 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
399 if (av.getWrapAlignment())
401 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
405 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
406 ranges.getStartSeq(), ranges.getEndSeq(), 0);
409 // lcimg is a local *copy* of img which we'll draw selectImage on top of
410 BufferedImage lcimg = buildLocalImage(selectImage);
411 g.drawImage(lcimg, 0, 0, this);
416 * Draw an alignment panel for printing
419 * Graphics object to draw with
421 * start residue of print area
423 * end residue of print area
425 * start sequence of print area
427 * end sequence of print area
429 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
430 int startSeq, int endSeq)
432 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
434 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
435 ((Graphics2D) g1).setComposite(
436 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
437 g1.drawImage(selectImage, 0, 0, this);
441 * Draw a wrapped alignment panel for printing
444 * Graphics object to draw with
446 * width of drawing area
447 * @param canvasHeight
448 * height of drawing area
450 * start residue of print area
452 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
453 int canvasHeight, int startRes)
455 SequenceGroup group = av.getSelectionGroup();
456 BufferedImage selectImage = null;
460 selectImage = new BufferedImage(canvasWidth, canvasHeight,
461 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
462 } catch (OutOfMemoryError er)
465 System.err.println("Print image OutOfMemory Error.\n" + er);
466 new OOMWarning("Creating wrapped alignment image for printing", er);
473 Graphics2D g2 = selectImage.createGraphics();
474 setupSelectionGroup(g2, selectImage);
475 drawWrappedSelection(g2, group, canvasWidth, canvasHeight, startRes);
476 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
477 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
478 g.drawImage(selectImage, 0, 0, this);
484 * Make a local image by combining the cached image img
487 private BufferedImage buildLocalImage(BufferedImage selectImage)
489 // clone the cached image
490 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
492 Graphics2D g2d = lcimg.createGraphics();
493 g2d.drawImage(img, 0, 0, null);
495 // overlay selection group on lcimg
496 if (selectImage != null)
499 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
500 g2d.drawImage(selectImage, 0, 0, this);
508 * Set up a buffered image of the correct height and size for the sequence canvas
510 private BufferedImage setupImage()
512 BufferedImage lcimg = null;
514 int width = getWidth();
515 int height = getHeight();
517 width -= (width % charWidth);
518 height -= (height % charHeight);
520 if ((width < 1) || (height < 1))
527 lcimg = new BufferedImage(width, height,
528 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
529 } catch (OutOfMemoryError er)
533 "Group image OutOfMemory Redraw Error.\n" + er);
534 new OOMWarning("Creating alignment image for display", er);
543 * Returns the visible width of the canvas in residues, after allowing for
544 * East or West scales (if shown)
547 * the width in pixels (possibly including scales)
551 public int getWrappedCanvasWidth(int canvasWidth)
553 FontMetrics fm = getFontMetrics(av.getFont());
558 if (av.getScaleRightWrapped())
560 labelWidthEast = getLabelWidth(fm);
563 if (av.getScaleLeftWrapped())
565 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
569 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
573 * Returns a pixel width suitable for showing the largest sequence coordinate
574 * (end position) in the alignment. Returns 2 plus the number of decimal
575 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
580 protected int getLabelWidth(FontMetrics fm)
583 * find the biggest sequence end position we need to show
584 * (note this is not necessarily the sequence length)
587 AlignmentI alignment = av.getAlignment();
588 for (int i = 0; i < alignment.getHeight(); i++)
590 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
594 for (int i = maxWidth; i > 0; i /= 10)
599 return fm.stringWidth(ZEROS.substring(0, length));
609 * @param canvasHeight
614 private void drawWrappedPanel(Graphics g, int canvasWidth,
615 int canvasHeight, int startRes)
618 AlignmentI al = av.getAlignment();
621 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
623 FontMetrics fm = getFontMetrics(av.getFont());
624 labelWidth = getLabelWidth(fm);
627 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
628 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
630 int hgap = charHeight;
631 if (av.getScaleAboveWrapped())
636 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
637 int cHeight = av.getAlignment().getHeight() * charHeight;
639 av.setWrappedWidth(cWidth);
641 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
645 int maxwidth = av.getAlignment().getWidth();
647 if (av.hasHiddenColumns())
649 maxwidth = av.getAlignment().getHiddenColumns()
650 .findColumnPosition(maxwidth);
653 int annotationHeight = getAnnotationHeight();
655 while ((ypos <= canvasHeight) && (startRes < maxwidth))
657 endx = startRes + cWidth - 1;
664 g.setFont(av.getFont());
665 g.setColor(Color.black);
667 if (av.getScaleLeftWrapped())
669 drawWestScale(g, startRes, endx, ypos);
672 if (av.getScaleRightWrapped())
674 g.translate(canvasWidth - labelWidthEast, 0);
675 drawEastScale(g, startRes, endx, ypos);
676 g.translate(-(canvasWidth - labelWidthEast), 0);
679 g.translate(labelWidthWest, 0);
681 if (av.getScaleAboveWrapped())
683 drawNorthScale(g, startRes, endx, ypos);
686 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
688 g.setColor(Color.blue);
690 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
691 List<Integer> positions = hidden.findHiddenRegionPositions();
692 for (int pos : positions)
694 res = pos - startRes;
696 if (res < 0 || res > endx - startRes)
703 { res * charWidth - charHeight / 4,
704 res * charWidth + charHeight / 4, res * charWidth },
706 { ypos - (charHeight / 2), ypos - (charHeight / 2),
707 ypos - (charHeight / 2) + 8 },
713 // When printing we have an extra clipped region,
714 // the Printable page which we need to account for here
715 Shape clip = g.getClip();
719 g.setClip(0, 0, cWidth * charWidth, canvasHeight);
723 g.setClip(0, (int) clip.getBounds().getY(), cWidth * charWidth,
724 (int) clip.getBounds().getHeight());
727 drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
729 if (av.isShowAnnotation())
731 g.translate(0, cHeight + ypos + 3);
732 if (annotations == null)
734 annotations = new AnnotationPanel(av);
737 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
739 g.translate(0, -cHeight - ypos - 3);
742 g.translate(-labelWidthWest, 0);
744 ypos += cHeight + annotationHeight + hgap;
751 * Draw a selection group over a wrapped alignment
753 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
755 int canvasHeight, int startRes)
757 // height gap above each panel
758 int hgap = charHeight;
759 if (av.getScaleAboveWrapped())
764 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
766 int cHeight = av.getAlignment().getHeight() * charHeight;
768 int startx = startRes;
770 int ypos = hgap; // vertical offset
771 int maxwidth = av.getAlignment().getWidth();
773 if (av.hasHiddenColumns())
775 maxwidth = av.getAlignment().getHiddenColumns()
776 .findColumnPosition(maxwidth);
779 // chop the wrapped alignment extent up into panel-sized blocks and treat
780 // each block as if it were a block from an unwrapped alignment
781 while ((ypos <= canvasHeight) && (startx < maxwidth))
783 // set end value to be start + width, or maxwidth, whichever is smaller
784 endx = startx + cWidth - 1;
791 g.translate(labelWidthWest, 0);
793 drawUnwrappedSelection(g, group, startx, endx, 0,
794 av.getAlignment().getHeight() - 1,
797 g.translate(-labelWidthWest, 0);
799 // update vertical offset
800 ypos += cHeight + getAnnotationHeight() + hgap;
802 // update horizontal offset
807 int getAnnotationHeight()
809 if (!av.isShowAnnotation())
814 if (annotations == null)
816 annotations = new AnnotationPanel(av);
819 return annotations.adjustPanelHeight();
824 * Draws the visible region of the alignment on the graphics context. If there
825 * are hidden column markers in the visible region, then each sub-region
826 * between the markers is drawn separately, followed by the hidden column
830 * Graphics object to draw with
832 * offset of the first column in the visible region (0..)
834 * offset of the last column in the visible region (0..)
836 * offset of the first sequence in the visible region (0..)
838 * offset of the last sequence in the visible region (0..)
840 * vertical offset at which to draw (for wrapped alignments)
842 private void drawPanel(Graphics g1, int startRes, int endRes,
843 int startSeq, int endSeq, int yOffset)
847 if (!av.hasHiddenColumns())
849 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
854 final int screenYMax = endRes - startRes;
855 int blockStart = startRes;
856 int blockEnd = endRes;
858 for (int[] region : av.getAlignment().getHiddenColumns()
859 .getHiddenColumnsCopy())
861 int hideStart = region[0];
862 int hideEnd = region[1];
864 if (hideStart <= blockStart)
866 blockStart += (hideEnd - hideStart) + 1;
871 * draw up to just before the next hidden region, or the end of
872 * the visible region, whichever comes first
874 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
877 g1.translate(screenY * charWidth, 0);
879 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
882 * draw the downline of the hidden column marker (ScalePanel draws the
883 * triangle on top) if we reached it
885 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
887 g1.setColor(Color.blue);
889 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
890 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
891 (endSeq - startSeq + 1) * charHeight + yOffset);
894 g1.translate(-screenY * charWidth, 0);
895 screenY += blockEnd - blockStart + 1;
896 blockStart = hideEnd + 1;
898 if (screenY > screenYMax)
900 // already rendered last block
905 if (screenY <= screenYMax)
907 // remaining visible region to render
908 blockEnd = blockStart + screenYMax - screenY;
909 g1.translate(screenY * charWidth, 0);
910 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
912 g1.translate(-screenY * charWidth, 0);
920 * Draws a region of the visible alignment
924 * offset of the first column in the visible region (0..)
926 * offset of the last column in the visible region (0..)
928 * offset of the first sequence in the visible region (0..)
930 * offset of the last sequence in the visible region (0..)
932 * vertical offset at which to draw (for wrapped alignments)
934 private void draw(Graphics g, int startRes, int endRes, int startSeq,
935 int endSeq, int offset)
937 g.setFont(av.getFont());
938 seqRdr.prepare(g, av.isRenderGaps());
942 // / First draw the sequences
943 // ///////////////////////////
944 for (int i = startSeq; i <= endSeq; i++)
946 nextSeq = av.getAlignment().getSequenceAt(i);
949 // occasionally, a race condition occurs such that the alignment row is
953 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
954 startRes, endRes, offset + ((i - startSeq) * charHeight));
956 if (av.isShowSequenceFeatures())
958 fr.drawSequence(g, nextSeq, startRes, endRes,
959 offset + ((i - startSeq) * charHeight), false);
963 * highlight search Results once sequence has been drawn
965 if (av.hasSearchResults())
967 SearchResultsI searchResults = av.getSearchResults();
968 int[] visibleResults = searchResults.getResults(nextSeq,
970 if (visibleResults != null)
972 for (int r = 0; r < visibleResults.length; r += 2)
974 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
975 visibleResults[r + 1], (visibleResults[r] - startRes)
977 + ((i - startSeq) * charHeight));
982 if (av.cursorMode && cursorY == i && cursorX >= startRes
983 && cursorX <= endRes)
985 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
986 offset + ((i - startSeq) * charHeight));
990 if (av.getSelectionGroup() != null
991 || av.getAlignment().getGroups().size() > 0)
993 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
998 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
999 int startSeq, int endSeq, int offset)
1001 Graphics2D g = (Graphics2D) g1;
1003 // ///////////////////////////////////
1004 // Now outline any areas if necessary
1005 // ///////////////////////////////////
1007 SequenceGroup group = null;
1008 int groupIndex = -1;
1010 if (av.getAlignment().getGroups().size() > 0)
1012 group = av.getAlignment().getGroups().get(0);
1018 g.setStroke(new BasicStroke());
1019 g.setColor(group.getOutlineColour());
1023 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1028 g.setStroke(new BasicStroke());
1030 if (groupIndex >= av.getAlignment().getGroups().size())
1035 group = av.getAlignment().getGroups().get(groupIndex);
1037 } while (groupIndex < av.getAlignment().getGroups().size());
1045 * Draw the selection group as a separate image and overlay
1047 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1048 int startSeq, int endSeq)
1050 // get a new image of the correct size
1051 BufferedImage selectionImage = setupImage();
1053 if (selectionImage == null)
1058 SequenceGroup group = av.getSelectionGroup();
1065 // set up drawing colour
1066 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1068 setupSelectionGroup(g, selectionImage);
1070 if (!av.getWrapAlignment())
1072 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1077 drawWrappedSelection(g, group, getWidth(), getHeight(),
1078 av.getRanges().getStartRes());
1082 return selectionImage;
1086 * Set up graphics for selection group
1088 private void setupSelectionGroup(Graphics2D g,
1089 BufferedImage selectionImage)
1091 // set background to transparent
1092 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1093 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1095 // set up foreground to draw red dashed line
1096 g.setComposite(AlphaComposite.Src);
1097 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1098 BasicStroke.JOIN_ROUND, 3f, new float[]
1100 g.setColor(Color.RED);
1104 * Draw a selection group over an unwrapped alignment
1105 * @param g graphics object to draw with
1106 * @param group selection group
1107 * @param startRes start residue of area to draw
1108 * @param endRes end residue of area to draw
1109 * @param startSeq start sequence of area to draw
1110 * @param endSeq end sequence of area to draw
1111 * @param offset vertical offset (used when called from wrapped alignment code)
1113 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1114 int startRes, int endRes, int startSeq, int endSeq, int offset)
1116 if (!av.hasHiddenColumns())
1118 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1123 // package into blocks of visible columns
1125 int blockStart = startRes;
1126 int blockEnd = endRes;
1128 for (int[] region : av.getAlignment().getHiddenColumns()
1129 .getHiddenColumnsCopy())
1131 int hideStart = region[0];
1132 int hideEnd = region[1];
1134 if (hideStart <= blockStart)
1136 blockStart += (hideEnd - hideStart) + 1;
1140 blockEnd = hideStart - 1;
1142 g.translate(screenY * charWidth, 0);
1143 drawPartialGroupOutline(g, group,
1144 blockStart, blockEnd, startSeq, endSeq, offset);
1146 g.translate(-screenY * charWidth, 0);
1147 screenY += blockEnd - blockStart + 1;
1148 blockStart = hideEnd + 1;
1150 if (screenY > (endRes - startRes))
1152 // already rendered last block
1157 if (screenY <= (endRes - startRes))
1159 // remaining visible region to render
1160 blockEnd = blockStart + (endRes - startRes) - screenY;
1161 g.translate(screenY * charWidth, 0);
1162 drawPartialGroupOutline(g, group,
1163 blockStart, blockEnd, startSeq, endSeq, offset);
1165 g.translate(-screenY * charWidth, 0);
1171 * Draw the selection group as a separate image and overlay
1173 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1174 int startRes, int endRes, int startSeq, int endSeq,
1177 int visWidth = (endRes - startRes + 1) * charWidth;
1181 boolean inGroup = false;
1189 for (i = startSeq; i <= endSeq; i++)
1191 // position of start residue of group relative to startRes, in pixels
1192 sx = (group.getStartRes() - startRes) * charWidth;
1194 // width of group in pixels
1195 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1198 sy = verticalOffset + (i - startSeq) * charHeight;
1200 if (sx + xwidth < 0 || sx > visWidth)
1205 if ((sx <= (endRes - startRes) * charWidth)
1206 && group.getSequences(null)
1207 .contains(av.getAlignment().getSequenceAt(i)))
1209 if ((bottom == -1) && !group.getSequences(null)
1210 .contains(av.getAlignment().getSequenceAt(i + 1)))
1212 bottom = sy + charHeight;
1217 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1218 .contains(av.getAlignment().getSequenceAt(i - 1)))
1231 // if start position is visible, draw vertical line to left of
1233 if (sx >= 0 && sx < visWidth)
1235 g.drawLine(sx, oldY, sx, sy);
1238 // if end position is visible, draw vertical line to right of
1240 if (sx + xwidth < visWidth)
1242 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1251 // don't let width extend beyond current block, or group extent
1253 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1255 xwidth = (endRes - startRes + 1) * charWidth - sx;
1258 // draw horizontal line at top of group
1261 g.drawLine(sx, top, sx + xwidth, top);
1265 // draw horizontal line at bottom of group
1268 g.drawLine(sx, bottom, sx + xwidth, bottom);
1279 sy = verticalOffset + ((i - startSeq) * charHeight);
1280 if (sx >= 0 && sx < visWidth)
1282 g.drawLine(sx, oldY, sx, sy);
1285 if (sx + xwidth < visWidth)
1287 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1296 if (sx + xwidth > visWidth)
1300 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1302 xwidth = (endRes - startRes + 1) * charWidth;
1307 g.drawLine(sx, top, sx + xwidth, top);
1313 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1322 * Highlights search results in the visible region by rendering as white text
1323 * on a black background. Any previous highlighting is removed. Answers true
1324 * if any highlight was left on the visible alignment (so status bar should be
1325 * set to match), else false.
1327 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1328 * alignment had to be scrolled to show the highlighted region, then it should
1329 * be fully redrawn, otherwise a fast paint can be performed. This argument
1330 * could be removed if fast paint of scrolled wrapped alignment is coded in
1331 * future (JAL-2609).
1334 * @param noFastPaint
1337 public boolean highlightSearchResults(SearchResultsI results,
1338 boolean noFastPaint)
1344 boolean wrapped = av.getWrapAlignment();
1348 fastPaint = !noFastPaint;
1349 fastpainting = fastPaint;
1354 * to avoid redrawing the whole visible region, we instead
1355 * redraw just the minimal regions to remove previous highlights
1358 SearchResultsI previous = av.getSearchResults();
1359 av.setSearchResults(results);
1360 boolean redrawn = false;
1361 boolean drawn = false;
1364 redrawn = drawMappedPositionsWrapped(previous);
1365 drawn = drawMappedPositionsWrapped(results);
1370 redrawn = drawMappedPositions(previous);
1371 drawn = drawMappedPositions(results);
1376 * if highlights were either removed or added, repaint
1384 * return true only if highlights were added
1390 fastpainting = false;
1395 * Redraws the minimal rectangle in the visible region (if any) that includes
1396 * mapped positions of the given search results. Whether or not positions are
1397 * highlighted depends on the SearchResults set on the Viewport. This allows
1398 * this method to be called to either clear or set highlighting. Answers true
1399 * if any positions were drawn (in which case a repaint is still required),
1405 protected boolean drawMappedPositions(SearchResultsI results)
1407 if (results == null)
1413 * calculate the minimal rectangle to redraw that
1414 * includes both new and existing search results
1416 int firstSeq = Integer.MAX_VALUE;
1418 int firstCol = Integer.MAX_VALUE;
1420 boolean matchFound = false;
1422 ViewportRanges ranges = av.getRanges();
1423 int firstVisibleColumn = ranges.getStartRes();
1424 int lastVisibleColumn = ranges.getEndRes();
1425 AlignmentI alignment = av.getAlignment();
1426 if (av.hasHiddenColumns())
1428 firstVisibleColumn = alignment.getHiddenColumns()
1429 .adjustForHiddenColumns(firstVisibleColumn);
1430 lastVisibleColumn = alignment.getHiddenColumns()
1431 .adjustForHiddenColumns(lastVisibleColumn);
1434 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1435 .getEndSeq(); seqNo++)
1437 SequenceI seq = alignment.getSequenceAt(seqNo);
1439 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1441 if (visibleResults != null)
1443 for (int i = 0; i < visibleResults.length - 1; i += 2)
1445 int firstMatchedColumn = visibleResults[i];
1446 int lastMatchedColumn = visibleResults[i + 1];
1447 if (firstMatchedColumn <= lastVisibleColumn
1448 && lastMatchedColumn >= firstVisibleColumn)
1451 * found a search results match in the visible region -
1452 * remember the first and last sequence matched, and the first
1453 * and last visible columns in the matched positions
1456 firstSeq = Math.min(firstSeq, seqNo);
1457 lastSeq = Math.max(lastSeq, seqNo);
1458 firstMatchedColumn = Math.max(firstMatchedColumn,
1459 firstVisibleColumn);
1460 lastMatchedColumn = Math.min(lastMatchedColumn,
1462 firstCol = Math.min(firstCol, firstMatchedColumn);
1463 lastCol = Math.max(lastCol, lastMatchedColumn);
1471 if (av.hasHiddenColumns())
1473 firstCol = alignment.getHiddenColumns()
1474 .findColumnPosition(firstCol);
1475 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1477 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1478 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1479 gg.translate(transX, transY);
1480 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1481 gg.translate(-transX, -transY);
1488 public void propertyChange(PropertyChangeEvent evt)
1490 String eventName = evt.getPropertyName();
1492 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1497 else if (av.getWrapAlignment())
1499 if (eventName.equals(ViewportRanges.STARTRES))
1507 if (eventName.equals(ViewportRanges.STARTRES))
1509 // Make sure we're not trying to draw a panel
1510 // larger than the visible window
1511 ViewportRanges vpRanges = av.getRanges();
1512 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1513 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1514 if (scrollX > range)
1518 else if (scrollX < -range)
1524 // Both scrolling and resizing change viewport ranges: scrolling changes
1525 // both start and end points, but resize only changes end values.
1526 // Here we only want to fastpaint on a scroll, with resize using a normal
1527 // paint, so scroll events are identified as changes to the horizontal or
1528 // vertical start value.
1529 if (eventName.equals(ViewportRanges.STARTRES))
1531 // scroll - startres and endres both change
1532 fastPaint(scrollX, 0);
1534 else if (eventName.equals(ViewportRanges.STARTSEQ))
1537 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1543 * Redraws any positions in the search results in the visible region of a
1544 * wrapped alignment. Any highlights are drawn depending on the search results
1545 * set on the Viewport, not the <code>results</code> argument. This allows
1546 * this method to be called either to clear highlights (passing the previous
1547 * search results), or to draw new highlights.
1552 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1554 if (results == null)
1559 boolean matchFound = false;
1561 int wrappedWidth = av.getWrappedWidth();
1562 int wrappedHeight = getRepeatHeightWrapped();
1564 ViewportRanges ranges = av.getRanges();
1565 int canvasHeight = getHeight();
1566 int repeats = canvasHeight / wrappedHeight;
1567 if (canvasHeight / wrappedHeight > 0)
1572 int firstVisibleColumn = ranges.getStartRes();
1573 int lastVisibleColumn = ranges.getStartRes() + repeats
1574 * ranges.getViewportWidth() - 1;
1576 AlignmentI alignment = av.getAlignment();
1577 if (av.hasHiddenColumns())
1579 firstVisibleColumn = alignment.getHiddenColumns()
1580 .adjustForHiddenColumns(firstVisibleColumn);
1581 lastVisibleColumn = alignment.getHiddenColumns()
1582 .adjustForHiddenColumns(lastVisibleColumn);
1585 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1587 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1588 .getEndSeq(); seqNo++)
1590 SequenceI seq = alignment.getSequenceAt(seqNo);
1592 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1594 if (visibleResults != null)
1596 for (int i = 0; i < visibleResults.length - 1; i += 2)
1598 int firstMatchedColumn = visibleResults[i];
1599 int lastMatchedColumn = visibleResults[i + 1];
1600 if (firstMatchedColumn <= lastVisibleColumn
1601 && lastMatchedColumn >= firstVisibleColumn)
1604 * found a search results match in the visible region
1606 firstMatchedColumn = Math.max(firstMatchedColumn,
1607 firstVisibleColumn);
1608 lastMatchedColumn = Math.min(lastMatchedColumn,
1612 * draw each mapped position separately (as contiguous positions may
1613 * wrap across lines)
1615 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1617 int displayColumn = mappedPos;
1618 if (av.hasHiddenColumns())
1620 displayColumn = alignment.getHiddenColumns()
1621 .findColumnPosition(displayColumn);
1625 * transX: offset from left edge of canvas to residue position
1627 int transX = labelWidthWest
1628 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1629 * av.getCharWidth();
1632 * transY: offset from top edge of canvas to residue position
1634 int transY = gapHeight;
1635 transY += (displayColumn - ranges.getStartRes())
1636 / wrappedWidth * wrappedHeight;
1637 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1640 * yOffset is from graphics origin to start of visible region
1642 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1643 if (transY < getHeight())
1646 gg.translate(transX, transY);
1647 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1649 gg.translate(-transX, -transY);
1661 * Answers the height in pixels of a repeating section of the wrapped
1662 * alignment, including space above, scale above if shown, sequences, and
1663 * annotation panel if shown
1667 protected int getRepeatHeightWrapped()
1669 // gap (and maybe scale) above
1670 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1673 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1675 // add annotations panel height if shown
1676 repeatHeight += getAnnotationHeight();
1678 return repeatHeight;