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.util.Comparison;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
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 sr;
72 boolean fastPaint = false;
74 boolean fastpainting = false;
84 private AnnotationPanel annotations;
87 * measurements for drawing a wrapped alignment
89 int labelWidthWest; // label left width in pixels if shown
91 private int labelWidthEast; // label right width in pixels if shown
93 private int wrappedSpaceAboveAlignment; // gap between widths
95 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
97 private int wrappedVisibleWidths; // number of wrapped widths displayed
100 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
109 fr = new FeatureRenderer(ap);
110 sr = new SequenceRenderer(av);
111 setLayout(new BorderLayout());
112 PaintRefresher.Register(this, av.getSequenceSetId());
113 setBackground(Color.white);
115 av.getRanges().addPropertyChangeListener(this);
118 public SequenceRenderer getSequenceRenderer()
123 public FeatureRenderer getFeatureRenderer()
128 private void updateViewport()
130 charHeight = av.getCharHeight();
131 charWidth = av.getCharWidth();
135 * Draws the scale above a region of a wrapped alignment, consisting of a
136 * column number every major interval (10 columns).
139 * the graphics context to draw on, positioned at the start (bottom
140 * left) of the line on which to draw any scale marks
142 * start alignment column (0..)
144 * end alignment column (0..)
146 * y offset to draw at
148 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
153 * white fill the scale space (for the fastPaint case)
155 g.setColor(Color.white);
156 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
157 charHeight * 3 / 2 + 2);
158 g.setColor(Color.black);
160 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
162 for (ScaleMark mark : marks)
164 int mpos = mark.column; // (i - startx - 1)
169 String mstring = mark.text;
175 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
179 * draw a tick mark below the column number, centred on the column;
180 * height of tick mark is 4 pixels less than half a character
182 int xpos = (mpos * charWidth) + (charWidth / 2);
183 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
189 * Draw the scale to the left or right of a wrapped alignment
193 * first column of wrapped width (0.. excluding any hidden columns)
195 * last column of wrapped width (0.. excluding any hidden columns)
197 * vertical offset at which to begin the scale
199 * if true, scale is left of residues, if false, scale is right
201 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startx = hiddenColumns.adjustForHiddenColumns(startx);
210 endx = hiddenColumns.adjustForHiddenColumns(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startx : endx;
224 while (index >= startx && index <= endx)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
242 * white fill the space for the scale
244 g.setColor(Color.white);
245 int y = (ypos + (i * charHeight)) - (charHeight / 5);
246 y -= charHeight; // fillRect: origin is top left of rectangle
247 int xpos = left ? 0 : labelWidthWest + charWidth
248 * av.getRanges().getViewportWidth();
249 g.fillRect(xpos, y, left ? labelWidthWest : labelWidthEast,
251 y += charHeight; // drawString: origin is bottom left of text
257 * draw scale value, right justified, with half a character width
258 * separation from the sequence data
260 String valueAsString = String.valueOf(value);
261 int justify = fm.stringWidth(valueAsString) + charWidth;
262 xpos = left ? labelWidthWest - justify + charWidth / 2
263 : getWidth() - justify - charWidth / 2;
265 g.setColor(Color.black);
266 g.drawString(valueAsString, xpos, y);
272 * Does a fast paint of an alignment in response to a scroll. Most of the
273 * visible region is simply copied and shifted, and then any newly visible
274 * columns or rows are drawn. The scroll may be horizontal or vertical, but
275 * not both at once. Scrolling may be the result of
277 * <li>dragging a scroll bar</li>
278 * <li>clicking in the scroll bar</li>
279 * <li>scrolling by trackpad, middle mouse button, or other device</li>
280 * <li>by moving the box in the Overview window</li>
281 * <li>programmatically to make a highlighted position visible</li>
285 * columns to shift right (positive) or left (negative)
287 * rows to shift down (positive) or up (negative)
289 public void fastPaint(int horizontal, int vertical)
291 if (fastpainting || gg == null)
302 ViewportRanges ranges = av.getRanges();
303 int startRes = ranges.getStartRes();
304 int endRes = ranges.getEndRes();
305 int startSeq = ranges.getStartSeq();
306 int endSeq = ranges.getEndSeq();
310 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
311 imgHeight, -horizontal * charWidth, -vertical * charHeight);
313 if (horizontal > 0) // scrollbar pulled right, image to the left
315 transX = (endRes - startRes - horizontal) * charWidth;
316 startRes = endRes - horizontal;
318 else if (horizontal < 0)
320 endRes = startRes - horizontal;
322 else if (vertical > 0) // scroll down
324 startSeq = endSeq - vertical;
326 if (startSeq < ranges.getStartSeq())
327 { // ie scrolling too fast, more than a page at a time
328 startSeq = ranges.getStartSeq();
332 transY = imgHeight - ((vertical + 1) * charHeight);
335 else if (vertical < 0)
337 endSeq = startSeq - vertical;
339 if (endSeq > ranges.getEndSeq())
341 endSeq = ranges.getEndSeq();
345 gg.translate(transX, transY);
346 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
347 gg.translate(-transX, -transY);
352 fastpainting = false;
357 public void paintComponent(Graphics g)
360 BufferedImage lcimg = img; // take reference since other threads may null
361 // img and call later.
362 super.paintComponent(g);
364 if (lcimg != null && (fastPaint
365 || (getVisibleRect().width != g.getClipBounds().width)
366 || (getVisibleRect().height != g.getClipBounds().height)))
368 g.drawImage(lcimg, 0, 0, this);
373 // this draws the whole of the alignment
374 imgWidth = getWidth();
375 imgHeight = getHeight();
377 imgWidth -= (imgWidth % charWidth);
378 imgHeight -= (imgHeight % charHeight);
380 if ((imgWidth < 1) || (imgHeight < 1))
385 if (lcimg == null || imgWidth != lcimg.getWidth()
386 || imgHeight != lcimg.getHeight())
390 lcimg = img = new BufferedImage(imgWidth, imgHeight,
391 BufferedImage.TYPE_INT_RGB);
392 gg = (Graphics2D) img.getGraphics();
393 gg.setFont(av.getFont());
394 } catch (OutOfMemoryError er)
397 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
398 new OOMWarning("Creating alignment image for display", er);
406 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
407 RenderingHints.VALUE_ANTIALIAS_ON);
410 gg.setColor(Color.white);
411 gg.fillRect(0, 0, imgWidth, imgHeight);
413 ViewportRanges ranges = av.getRanges();
414 if (av.getWrapAlignment())
416 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
420 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
421 ranges.getStartSeq(), ranges.getEndSeq(), 0);
424 g.drawImage(lcimg, 0, 0, this);
429 * Returns the visible width of the canvas in residues, after allowing for
430 * East or West scales (if shown)
433 * the width in pixels (possibly including scales)
437 public int getWrappedCanvasWidth(int canvasWidth)
439 FontMetrics fm = getFontMetrics(av.getFont());
444 if (av.getScaleRightWrapped())
446 labelWidthEast = getLabelWidth(fm);
449 if (av.getScaleLeftWrapped())
451 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
455 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
459 * Returns a pixel width suitable for showing the largest sequence coordinate
460 * (end position) in the alignment. Returns 2 plus the number of decimal
461 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
466 protected int getLabelWidth(FontMetrics fm)
469 * find the biggest sequence end position we need to show
470 * (note this is not necessarily the sequence length)
473 AlignmentI alignment = av.getAlignment();
474 for (int i = 0; i < alignment.getHeight(); i++)
476 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
480 for (int i = maxWidth; i > 0; i /= 10)
485 return fm.stringWidth(ZEROS.substring(0, length));
489 * Draws as many widths of a wrapped alignment as can fit in the visible
494 * available width in pixels
495 * @param canvasHeight
496 * available height in pixels
498 * the first column (0...) of the alignment to draw
500 public void drawWrappedPanel(Graphics g, int canvasWidth,
501 int canvasHeight, final int startColumn)
505 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
508 av.setWrappedWidth(wrappedWidthInResidues);
510 ViewportRanges ranges = av.getRanges();
511 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
514 * draw one width at a time (including any scales or annotation shown),
515 * until we have run out of either alignment or vertical space available
517 int yposMax = canvasHeight;
518 // ensure room for at least one sequence
519 yposMax -= wrappedSpaceAboveAlignment - charHeight;
520 int ypos = wrappedSpaceAboveAlignment;
521 int maxWidth = ranges.getVisibleAlignmentWidth();
523 int start = startColumn;
524 while ((ypos <= yposMax) && (start < maxWidth))
527 .min(maxWidth, start + wrappedWidthInResidues - 1);
528 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
529 ypos += wrappedRepeatHeightPx;
530 start += wrappedWidthInResidues;
533 drawWrappedDecorators(g, startColumn);
537 * Calculates and saves values needed when rendering a wrapped alignment.
538 * These depend on many factors, including
540 * <li>canvas width and height</li>
541 * <li>number of visible sequences, and height of annotations if shown</li>
542 * <li>font and character width</li>
543 * <li>whether scales are shown left, right or above the alignment</li>
547 * @param canvasHeight
548 * @return the number of residue columns in each width
550 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
553 * width of labels in pixels left and right (if shown)
556 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
558 FontMetrics fm = getFontMetrics(av.getFont());
559 labelWidth = getLabelWidth(fm);
561 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
562 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
565 * vertical space in pixels between wrapped widths of alignment
567 wrappedSpaceAboveAlignment = charHeight
568 * (av.getScaleAboveWrapped() ? 2 : 1);
571 * height in pixels of the wrapped widths
573 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
575 wrappedRepeatHeightPx += av.getRanges().getViewportHeight() * charHeight;
576 // add annotations panel height if shown
577 wrappedRepeatHeightPx += getAnnotationHeight();
580 * number of visible widths (the last one may be part height)
582 ViewportRanges ranges = av.getRanges();
583 int xMax = ranges.getVisibleAlignmentWidth();
584 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
585 int remainder = canvasHeight % wrappedRepeatHeightPx;
586 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
588 wrappedVisibleWidths++;
592 * limit visibleWidths to not exceed width of alignment
594 int viewportWidth = ranges.getViewportWidth();
595 int maxWidths = (xMax - ranges.getStartRes()) / viewportWidth;
596 if (xMax % viewportWidth > 0)
600 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
603 * number of whole width residue columns we can show in each row
605 int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
607 return wrappedWidthInResidues;
611 * Draws one width of a wrapped alignment, including sequences and
612 * annnotations, if shown, but not scales or hidden column markers
618 * @param canvasHeight
620 protected void drawWrappedWidth(Graphics g, int ypos,
621 int startColumn, int endColumn, int canvasHeight)
623 ViewportRanges ranges = av.getRanges();
624 int viewportWidth = ranges.getViewportWidth();
626 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
629 * move right before drawing by the width of the scale left (if any)
630 * plus column offset from left margin (usually zero, but may be non-zero
631 * when fast painting is drawing just a few columns)
633 int xOffset = labelWidthWest
634 + ((startColumn - ranges.getStartRes()) % viewportWidth)
636 g.translate(xOffset, 0);
638 // When printing we have an extra clipped region,
639 // the Printable page which we need to account for here
640 Shape clip = g.getClip();
644 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
648 g.setClip(0, (int) clip.getBounds().getY(),
649 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
653 * white fill the region to be drawn (so incremental fast paint doesn't
654 * scribble over an existing image)
656 gg.setColor(Color.white);
657 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
658 wrappedRepeatHeightPx);
660 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
663 int cHeight = av.getAlignment().getHeight() * charHeight;
665 if (av.isShowAnnotation())
667 g.translate(0, cHeight + ypos + 3);
668 if (annotations == null)
670 annotations = new AnnotationPanel(av);
673 annotations.renderer.drawComponent(annotations, av, g, -1,
674 startColumn, endx + 1);
675 g.translate(0, -cHeight - ypos - 3);
678 g.translate(-xOffset, 0);
682 * Draws scales left, right and above (if shown), and any hidden column
683 * markers, on all widths of the wrapped alignment
688 protected void drawWrappedDecorators(Graphics g, int startColumn)
690 g.setFont(av.getFont());
691 g.setColor(Color.black);
693 int ypos = wrappedSpaceAboveAlignment;
694 ViewportRanges ranges = av.getRanges();
695 int viewportWidth = ranges.getViewportWidth();
696 int maxWidth = ranges.getVisibleAlignmentWidth();
698 while (widthsDrawn < wrappedVisibleWidths)
700 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
702 if (av.getScaleLeftWrapped())
704 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
707 if (av.getScaleRightWrapped())
709 drawVerticalScale(g, startColumn, endColumn, ypos, false);
712 g.translate(labelWidthWest, 0);
715 * white fill region of scale above and hidden column markers
716 * (to support incremental fast paint of image)
718 g.setColor(Color.white);
719 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
720 * charWidth, wrappedSpaceAboveAlignment);
721 g.setColor(Color.black);
723 if (av.getScaleAboveWrapped())
725 drawNorthScale(g, startColumn, endColumn, ypos);
728 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
730 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
733 g.translate(-labelWidthWest, 0);
735 ypos += wrappedRepeatHeightPx;
736 startColumn += viewportWidth;
747 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
748 int startColumn, int endColumn)
750 g.setColor(Color.blue);
751 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
752 List<Integer> positions = hidden.findHiddenRegionPositions();
753 for (int pos : positions)
755 int res = pos - startColumn;
757 if (res < 0 || res > endColumn - startColumn)
763 * draw a downward-pointing triangle at the hidden columns location
764 * (before the following visible column)
766 int xMiddle = res * charWidth;
767 int[] xPoints = new int[] { xMiddle - charHeight / 4,
768 xMiddle + charHeight / 4, xMiddle };
769 int yTop = ypos - (charHeight / 2);
770 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
771 gg.fillPolygon(xPoints, yPoints, 3);
775 int getAnnotationHeight()
777 if (!av.isShowAnnotation())
782 if (annotations == null)
784 annotations = new AnnotationPanel(av);
787 return annotations.adjustPanelHeight();
791 * Draws the visible region of the alignment on the graphics context. If there
792 * are hidden column markers in the visible region, then each sub-region
793 * between the markers is drawn separately, followed by the hidden column
797 * the graphics context, positioned at the first residue to be drawn
799 * offset of the first column to draw (0..)
801 * offset of the last column to draw (0..)
803 * offset of the first sequence to draw (0..)
805 * offset of the last sequence to draw (0..)
807 * vertical offset at which to draw (for wrapped alignments)
809 public void drawPanel(Graphics g1, final int startRes, final int endRes,
810 final int startSeq, final int endSeq, final int yOffset)
813 if (!av.hasHiddenColumns())
815 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
820 final int screenYMax = endRes - startRes;
821 int blockStart = startRes;
822 int blockEnd = endRes;
824 for (int[] region : av.getAlignment().getHiddenColumns()
825 .getHiddenColumnsCopy())
827 int hideStart = region[0];
828 int hideEnd = region[1];
830 if (hideStart <= blockStart)
832 blockStart += (hideEnd - hideStart) + 1;
837 * draw up to just before the next hidden region, or the end of
838 * the visible region, whichever comes first
840 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
843 g1.translate(screenY * charWidth, 0);
845 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
848 * draw the downline of the hidden column marker (ScalePanel draws the
849 * triangle on top) if we reached it
851 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
853 g1.setColor(Color.blue);
855 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
856 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
857 (endSeq - startSeq + 1) * charHeight + yOffset);
860 g1.translate(-screenY * charWidth, 0);
861 screenY += blockEnd - blockStart + 1;
862 blockStart = hideEnd + 1;
864 if (screenY > screenYMax)
866 // already rendered last block
871 if (screenY <= screenYMax)
873 // remaining visible region to render
874 blockEnd = blockStart + screenYMax - screenY;
875 g1.translate(screenY * charWidth, 0);
876 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
878 g1.translate(-screenY * charWidth, 0);
885 * Draws a region of the visible alignment
889 * offset of the first column in the visible region (0..)
891 * offset of the last column in the visible region (0..)
893 * offset of the first sequence in the visible region (0..)
895 * offset of the last sequence in the visible region (0..)
897 * vertical offset at which to draw (for wrapped alignments)
899 private void draw(Graphics g, int startRes, int endRes, int startSeq,
900 int endSeq, int offset)
902 g.setFont(av.getFont());
903 sr.prepare(g, av.isRenderGaps());
907 // / First draw the sequences
908 // ///////////////////////////
909 for (int i = startSeq; i <= endSeq; i++)
911 nextSeq = av.getAlignment().getSequenceAt(i);
914 // occasionally, a race condition occurs such that the alignment row is
918 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
919 startRes, endRes, offset + ((i - startSeq) * charHeight));
921 if (av.isShowSequenceFeatures())
923 fr.drawSequence(g, nextSeq, startRes, endRes,
924 offset + ((i - startSeq) * charHeight), false);
928 * highlight search Results once sequence has been drawn
930 if (av.hasSearchResults())
932 SearchResultsI searchResults = av.getSearchResults();
933 int[] visibleResults = searchResults.getResults(nextSeq,
935 if (visibleResults != null)
937 for (int r = 0; r < visibleResults.length; r += 2)
939 sr.drawHighlightedText(nextSeq, visibleResults[r],
940 visibleResults[r + 1],
941 (visibleResults[r] - startRes) * charWidth,
942 offset + ((i - startSeq) * charHeight));
947 if (av.cursorMode && cursorY == i && cursorX >= startRes
948 && cursorX <= endRes)
950 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
951 offset + ((i - startSeq) * charHeight));
955 if (av.getSelectionGroup() != null
956 || av.getAlignment().getGroups().size() > 0)
958 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
963 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
964 int startSeq, int endSeq, int offset)
966 Graphics2D g = (Graphics2D) g1;
968 // ///////////////////////////////////
969 // Now outline any areas if necessary
970 // ///////////////////////////////////
971 SequenceGroup group = av.getSelectionGroup();
977 int visWidth = (endRes - startRes + 1) * charWidth;
979 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
981 group = av.getAlignment().getGroups().get(0);
991 boolean inGroup = false;
995 for (i = startSeq; i <= endSeq; i++)
997 sx = (group.getStartRes() - startRes) * charWidth;
998 sy = offset + ((i - startSeq) * charHeight);
999 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1002 if (sx + ex < 0 || sx > visWidth)
1007 if ((sx <= (endRes - startRes) * charWidth)
1008 && group.getSequences(null)
1009 .contains(av.getAlignment().getSequenceAt(i)))
1011 if ((bottom == -1) && !group.getSequences(null)
1012 .contains(av.getAlignment().getSequenceAt(i + 1)))
1014 bottom = sy + charHeight;
1019 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1020 .contains(av.getAlignment().getSequenceAt(i - 1)))
1028 if (group == av.getSelectionGroup())
1030 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1031 BasicStroke.JOIN_ROUND, 3f, new float[]
1033 g.setColor(Color.RED);
1037 g.setStroke(new BasicStroke());
1038 g.setColor(group.getOutlineColour());
1046 if (sx >= 0 && sx < visWidth)
1048 g.drawLine(sx, oldY, sx, sy);
1051 if (sx + ex < visWidth)
1053 g.drawLine(sx + ex, oldY, sx + ex, sy);
1062 if (sx + ex > visWidth)
1067 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1069 ex = (endRes - startRes + 1) * charWidth;
1074 g.drawLine(sx, top, sx + ex, top);
1080 g.drawLine(sx, bottom, sx + ex, bottom);
1091 sy = offset + ((i - startSeq) * charHeight);
1092 if (sx >= 0 && sx < visWidth)
1094 g.drawLine(sx, oldY, sx, sy);
1097 if (sx + ex < visWidth)
1099 g.drawLine(sx + ex, oldY, sx + ex, sy);
1108 if (sx + ex > visWidth)
1112 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1114 ex = (endRes - startRes + 1) * charWidth;
1119 g.drawLine(sx, top, sx + ex, top);
1125 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1134 g.setStroke(new BasicStroke());
1136 if (groupIndex >= av.getAlignment().getGroups().size())
1141 group = av.getAlignment().getGroups().get(groupIndex);
1143 } while (groupIndex < av.getAlignment().getGroups().size());
1150 * Highlights search results in the visible region by rendering as white text
1151 * on a black background. Any previous highlighting is removed. Answers true
1152 * if any highlight was left on the visible alignment (so status bar should be
1153 * set to match), else false.
1155 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1156 * alignment had to be scrolled to show the highlighted region, then it should
1157 * be fully redrawn, otherwise a fast paint can be performed. This argument
1158 * could be removed if fast paint of scrolled wrapped alignment is coded in
1159 * future (JAL-2609).
1162 * @param noFastPaint
1165 public boolean highlightSearchResults(SearchResultsI results,
1166 boolean noFastPaint)
1172 boolean wrapped = av.getWrapAlignment();
1176 fastPaint = !noFastPaint;
1177 fastpainting = fastPaint;
1182 * to avoid redrawing the whole visible region, we instead
1183 * redraw just the minimal regions to remove previous highlights
1186 SearchResultsI previous = av.getSearchResults();
1187 av.setSearchResults(results);
1188 boolean redrawn = false;
1189 boolean drawn = false;
1192 redrawn = drawMappedPositionsWrapped(previous);
1193 drawn = drawMappedPositionsWrapped(results);
1198 redrawn = drawMappedPositions(previous);
1199 drawn = drawMappedPositions(results);
1204 * if highlights were either removed or added, repaint
1212 * return true only if highlights were added
1218 fastpainting = false;
1223 * Redraws the minimal rectangle in the visible region (if any) that includes
1224 * mapped positions of the given search results. Whether or not positions are
1225 * highlighted depends on the SearchResults set on the Viewport. This allows
1226 * this method to be called to either clear or set highlighting. Answers true
1227 * if any positions were drawn (in which case a repaint is still required),
1233 protected boolean drawMappedPositions(SearchResultsI results)
1235 if (results == null)
1241 * calculate the minimal rectangle to redraw that
1242 * includes both new and existing search results
1244 int firstSeq = Integer.MAX_VALUE;
1246 int firstCol = Integer.MAX_VALUE;
1248 boolean matchFound = false;
1250 ViewportRanges ranges = av.getRanges();
1251 int firstVisibleColumn = ranges.getStartRes();
1252 int lastVisibleColumn = ranges.getEndRes();
1253 AlignmentI alignment = av.getAlignment();
1254 if (av.hasHiddenColumns())
1256 firstVisibleColumn = alignment.getHiddenColumns()
1257 .adjustForHiddenColumns(firstVisibleColumn);
1258 lastVisibleColumn = alignment.getHiddenColumns()
1259 .adjustForHiddenColumns(lastVisibleColumn);
1262 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1263 .getEndSeq(); seqNo++)
1265 SequenceI seq = alignment.getSequenceAt(seqNo);
1267 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1269 if (visibleResults != null)
1271 for (int i = 0; i < visibleResults.length - 1; i += 2)
1273 int firstMatchedColumn = visibleResults[i];
1274 int lastMatchedColumn = visibleResults[i + 1];
1275 if (firstMatchedColumn <= lastVisibleColumn
1276 && lastMatchedColumn >= firstVisibleColumn)
1279 * found a search results match in the visible region -
1280 * remember the first and last sequence matched, and the first
1281 * and last visible columns in the matched positions
1284 firstSeq = Math.min(firstSeq, seqNo);
1285 lastSeq = Math.max(lastSeq, seqNo);
1286 firstMatchedColumn = Math.max(firstMatchedColumn,
1287 firstVisibleColumn);
1288 lastMatchedColumn = Math.min(lastMatchedColumn,
1290 firstCol = Math.min(firstCol, firstMatchedColumn);
1291 lastCol = Math.max(lastCol, lastMatchedColumn);
1299 if (av.hasHiddenColumns())
1301 firstCol = alignment.getHiddenColumns()
1302 .findColumnPosition(firstCol);
1303 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1305 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1306 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1307 gg.translate(transX, transY);
1308 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1309 gg.translate(-transX, -transY);
1316 public void propertyChange(PropertyChangeEvent evt)
1318 String eventName = evt.getPropertyName();
1321 if (eventName.equals(ViewportRanges.STARTRES))
1323 // Make sure we're not trying to draw a panel
1324 // larger than the visible window
1325 ViewportRanges vpRanges = av.getRanges();
1326 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1327 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1328 if (scrollX > range)
1332 else if (scrollX < -range)
1338 // Both scrolling and resizing change viewport ranges: scrolling changes
1339 // both start and end points, but resize only changes end values.
1340 // Here we only want to fastpaint on a scroll, with resize using a normal
1341 // paint, so scroll events are identified as changes to the horizontal or
1342 // vertical start value.
1343 if (eventName.equals(ViewportRanges.STARTRES))
1345 // scroll - startres and endres both change
1346 if (av.getWrapAlignment())
1348 fastPaintWrapped(scrollX);
1352 fastPaint(scrollX, 0);
1355 else if (eventName.equals(ViewportRanges.STARTSEQ))
1357 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1362 * Does a minimal update of the image for a scroll movement. This method
1363 * handles scroll movements of up to one width of the wrapped alignment (one
1364 * click in the vertical scrollbar). Larger movements (for example after a
1365 * scroll to highlight a mapped position) trigger a full redraw instead.
1368 * number of positions scrolled (right if positive, left if negative)
1370 protected void fastPaintWrapped(int scrollX)
1372 ViewportRanges ranges = av.getRanges();
1374 if (Math.abs(scrollX) > ranges.getViewportWidth())
1377 * shift of more than one view width is
1378 * overcomplicated to handle in this method
1385 if (fastpainting || gg == null)
1391 fastpainting = true;
1395 calculateWrappedGeometry(getWidth(), getHeight());
1398 * relocate the regions of the alignment that are still visible
1400 shiftWrappedAlignment(-scrollX);
1403 * add new columns (sequence, annotation)
1404 * - at top left if scrollX < 0
1405 * - at right of last two widths if scrollX > 0
1409 int startRes = ranges.getStartRes();
1410 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1411 - scrollX - 1, getHeight());
1415 fastPaintWrappedAddRight(scrollX);
1419 * draw all scales (if shown) and hidden column markers
1421 drawWrappedDecorators(gg, ranges.getStartRes());
1426 fastpainting = false;
1431 * Draws the specified number of columns at the 'end' (bottom right) of a
1432 * wrapped alignment view, including sequences and annotations if shown, but
1433 * not scales. Also draws the same number of columns at the right hand end of
1434 * the second last width shown, if the last width is not full height (so
1435 * cannot simply be copied from the graphics image).
1439 protected void fastPaintWrappedAddRight(int columns)
1446 ViewportRanges ranges = av.getRanges();
1447 int viewportWidth = ranges.getViewportWidth();
1450 * draw full height alignment in the second last row, last columns, if the
1451 * last row was not full height
1453 int visibleWidths = wrappedVisibleWidths;
1454 int canvasHeight = getHeight();
1455 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1457 if (lastWidthPartHeight)
1459 int widthsAbove = visibleWidths - 2;
1460 int ypos = wrappedRepeatHeightPx * widthsAbove
1461 + wrappedSpaceAboveAlignment;
1462 int endRes = ranges.getEndRes();
1463 endRes += widthsAbove * viewportWidth;
1464 int startRes = endRes - columns;
1465 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1469 * white fill first to erase annotations
1471 gg.translate(xOffset, 0);
1472 gg.setColor(Color.white);
1473 gg.fillRect(labelWidthWest, ypos,
1474 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1475 gg.translate(-xOffset, 0);
1477 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1481 * y-offset for drawing last width is height of widths above,
1484 int widthsAbove = visibleWidths - 1;
1485 int ypos = wrappedRepeatHeightPx * widthsAbove
1486 + wrappedSpaceAboveAlignment;
1487 int endRes = ranges.getEndRes();
1488 endRes += widthsAbove * viewportWidth;
1489 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1490 int startRes = endRes - columns + 1;
1493 * white fill first to erase annotations
1495 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1497 gg.translate(xOffset, 0);
1498 gg.setColor(Color.white);
1499 int width = viewportWidth * charWidth - xOffset;
1500 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1501 gg.translate(-xOffset, 0);
1503 gg.setFont(av.getFont());
1504 gg.setColor(Color.black);
1506 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1509 * and finally, white fill any space below the visible alignment
1511 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1512 if (heightBelow > 0)
1514 gg.setColor(Color.white);
1515 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1520 * Shifts the visible alignment by the specified number of columns - left if
1521 * negative, right if positive. Copies and moves sequences and annotations (if
1522 * shown). Scales, hidden column markers and any newly visible columns must be
1527 protected void shiftWrappedAlignment(int positions)
1534 int canvasHeight = getHeight();
1535 ViewportRanges ranges = av.getRanges();
1536 int viewportWidth = ranges.getViewportWidth();
1537 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1539 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1540 int xMax = ranges.getVisibleAlignmentWidth();
1545 * shift right (after scroll left)
1546 * for each wrapped width (starting with the last), copy (width-positions)
1547 * columns from the left margin to the right margin, and copy positions
1548 * columns from the right margin of the row above (if any) to the
1549 * left margin of the current row
1553 * get y-offset of last wrapped width, first row of sequences
1555 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1556 y += wrappedSpaceAboveAlignment;
1557 int copyFromLeftStart = labelWidthWest;
1558 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1562 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1563 positions * charWidth, 0);
1566 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1567 positions * charWidth, heightToCopy, -widthToCopy,
1568 wrappedRepeatHeightPx);
1571 y -= wrappedRepeatHeightPx;
1577 * shift left (after scroll right)
1578 * for each wrapped width (starting with the first), copy (width-positions)
1579 * columns from the right margin to the left margin, and copy positions
1580 * columns from the left margin of the row below (if any) to the
1581 * right margin of the current row
1583 int xpos = av.getRanges().getStartRes();
1584 int y = wrappedSpaceAboveAlignment;
1585 int copyFromRightStart = labelWidthWest - positions * charWidth;
1587 while (y < canvasHeight)
1589 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1590 positions * charWidth, 0);
1591 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1592 && (xpos + viewportWidth <= xMax))
1594 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1595 * charWidth, heightToCopy, widthToCopy,
1596 -wrappedRepeatHeightPx);
1599 y += wrappedRepeatHeightPx;
1600 xpos += viewportWidth;
1606 * Redraws any positions in the search results in the visible region of a
1607 * wrapped alignment. Any highlights are drawn depending on the search results
1608 * set on the Viewport, not the <code>results</code> argument. This allows
1609 * this method to be called either to clear highlights (passing the previous
1610 * search results), or to draw new highlights.
1615 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1617 if (results == null)
1622 boolean matchFound = false;
1624 int wrappedWidth = av.getWrappedWidth();
1625 int wrappedHeight = getRepeatHeightWrapped();
1627 ViewportRanges ranges = av.getRanges();
1628 int canvasHeight = getHeight();
1629 int repeats = canvasHeight / wrappedHeight;
1630 if (canvasHeight / wrappedHeight > 0)
1635 int firstVisibleColumn = ranges.getStartRes();
1636 int lastVisibleColumn = ranges.getStartRes() + repeats
1637 * ranges.getViewportWidth() - 1;
1639 AlignmentI alignment = av.getAlignment();
1640 if (av.hasHiddenColumns())
1642 firstVisibleColumn = alignment.getHiddenColumns()
1643 .adjustForHiddenColumns(firstVisibleColumn);
1644 lastVisibleColumn = alignment.getHiddenColumns()
1645 .adjustForHiddenColumns(lastVisibleColumn);
1648 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1650 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1651 .getEndSeq(); seqNo++)
1653 SequenceI seq = alignment.getSequenceAt(seqNo);
1655 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1657 if (visibleResults != null)
1659 for (int i = 0; i < visibleResults.length - 1; i += 2)
1661 int firstMatchedColumn = visibleResults[i];
1662 int lastMatchedColumn = visibleResults[i + 1];
1663 if (firstMatchedColumn <= lastVisibleColumn
1664 && lastMatchedColumn >= firstVisibleColumn)
1667 * found a search results match in the visible region
1669 firstMatchedColumn = Math.max(firstMatchedColumn,
1670 firstVisibleColumn);
1671 lastMatchedColumn = Math.min(lastMatchedColumn,
1675 * draw each mapped position separately (as contiguous positions may
1676 * wrap across lines)
1678 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1680 int displayColumn = mappedPos;
1681 if (av.hasHiddenColumns())
1683 displayColumn = alignment.getHiddenColumns()
1684 .findColumnPosition(displayColumn);
1688 * transX: offset from left edge of canvas to residue position
1690 int transX = labelWidthWest
1691 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1692 * av.getCharWidth();
1695 * transY: offset from top edge of canvas to residue position
1697 int transY = gapHeight;
1698 transY += (displayColumn - ranges.getStartRes())
1699 / wrappedWidth * wrappedHeight;
1700 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1703 * yOffset is from graphics origin to start of visible region
1705 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1706 if (transY < getHeight())
1709 gg.translate(transX, transY);
1710 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1712 gg.translate(-transX, -transY);
1724 * Answers the height in pixels of a repeating section of the wrapped
1725 * alignment, including space above, scale above if shown, sequences, and
1726 * annotation panel if shown
1730 protected int getRepeatHeightWrapped()
1732 // gap (and maybe scale) above
1733 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1736 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1738 // add annotations panel height if shown
1739 repeatHeight += getAnnotationHeight();
1741 return repeatHeight;