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);
366 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
367 .getClipBounds().height)))
369 g.drawImage(lcimg, 0, 0, this);
374 // this draws the whole of the alignment
375 imgWidth = getWidth();
376 imgHeight = getHeight();
378 imgWidth -= (imgWidth % charWidth);
379 imgHeight -= (imgHeight % charHeight);
381 if ((imgWidth < 1) || (imgHeight < 1))
386 if (lcimg == null || imgWidth != lcimg.getWidth()
387 || imgHeight != lcimg.getHeight())
391 lcimg = img = new BufferedImage(imgWidth, imgHeight,
392 BufferedImage.TYPE_INT_RGB);
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
395 } catch (OutOfMemoryError er)
398 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
399 new OOMWarning("Creating alignment image for display", er);
407 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
408 RenderingHints.VALUE_ANTIALIAS_ON);
411 gg.setColor(Color.white);
412 gg.fillRect(0, 0, imgWidth, imgHeight);
414 ViewportRanges ranges = av.getRanges();
415 if (av.getWrapAlignment())
417 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
421 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
422 ranges.getStartSeq(), ranges.getEndSeq(), 0);
425 g.drawImage(lcimg, 0, 0, this);
430 * Returns the visible width of the canvas in residues, after allowing for
431 * East or West scales (if shown)
434 * the width in pixels (possibly including scales)
438 public int getWrappedCanvasWidth(int canvasWidth)
440 FontMetrics fm = getFontMetrics(av.getFont());
445 if (av.getScaleRightWrapped())
447 labelWidthEast = getLabelWidth(fm);
450 if (av.getScaleLeftWrapped())
452 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
456 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
460 * Returns a pixel width suitable for showing the largest sequence coordinate
461 * (end position) in the alignment. Returns 2 plus the number of decimal
462 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
467 protected int getLabelWidth(FontMetrics fm)
470 * find the biggest sequence end position we need to show
471 * (note this is not necessarily the sequence length)
474 AlignmentI alignment = av.getAlignment();
475 for (int i = 0; i < alignment.getHeight(); i++)
477 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
481 for (int i = maxWidth; i > 0; i /= 10)
486 return fm.stringWidth(ZEROS.substring(0, length));
490 * Draws as many widths of a wrapped alignment as can fit in the visible
495 * available width in pixels
496 * @param canvasHeight
497 * available height in pixels
499 * the first column (0...) of the alignment to draw
501 public void drawWrappedPanel(Graphics g, int canvasWidth,
502 int canvasHeight, final int startColumn)
506 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
509 av.setWrappedWidth(wrappedWidthInResidues);
511 ViewportRanges ranges = av.getRanges();
512 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
515 * draw one width at a time (including any scales or annotation shown),
516 * until we have run out of either alignment or vertical space available
518 int yposMax = canvasHeight;
519 // ensure room for at least one sequence
520 yposMax -= wrappedSpaceAboveAlignment - charHeight;
521 int ypos = wrappedSpaceAboveAlignment;
522 int maxWidth = ranges.getVisibleAlignmentWidth();
524 int start = startColumn;
525 while ((ypos <= yposMax) && (start < maxWidth))
528 .min(maxWidth, start + wrappedWidthInResidues - 1);
529 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
530 ypos += wrappedRepeatHeightPx;
531 start += wrappedWidthInResidues;
534 drawWrappedDecorators(g, startColumn);
538 * Calculates and saves values needed when rendering a wrapped alignment.
539 * These depend on many factors, including
541 * <li>canvas width and height</li>
542 * <li>number of visible sequences, and height of annotations if shown</li>
543 * <li>font and character width</li>
544 * <li>whether scales are shown left, right or above the alignment</li>
548 * @param canvasHeight
549 * @return the number of residue columns in each width
551 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
554 * width of labels in pixels left and right (if shown)
557 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
559 FontMetrics fm = getFontMetrics(av.getFont());
560 labelWidth = getLabelWidth(fm);
562 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
563 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
566 * vertical space in pixels between wrapped widths of alignment
568 wrappedSpaceAboveAlignment = charHeight
569 * (av.getScaleAboveWrapped() ? 2 : 1);
572 * height in pixels of the wrapped widths
574 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
576 wrappedRepeatHeightPx += av.getRanges().getViewportHeight() * charHeight;
577 // add annotations panel height if shown
578 wrappedRepeatHeightPx += getAnnotationHeight();
581 * number of visible widths (the last one may be part height)
583 ViewportRanges ranges = av.getRanges();
584 int xMax = ranges.getVisibleAlignmentWidth();
585 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
586 int remainder = canvasHeight % wrappedRepeatHeightPx;
587 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
589 wrappedVisibleWidths++;
593 * limit visibleWidths to not exceed width of alignment
595 int viewportWidth = ranges.getViewportWidth();
596 int maxWidths = (xMax - ranges.getStartRes()) / viewportWidth;
597 if (xMax % viewportWidth > 0)
601 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
604 * number of whole width residue columns we can show in each row
606 int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
608 return wrappedWidthInResidues;
612 * Draws one width of a wrapped alignment, including sequences and
613 * annnotations, if shown, but not scales or hidden column markers
619 * @param canvasHeight
621 protected void drawWrappedWidth(Graphics g, int ypos,
622 int startColumn, int endColumn, int canvasHeight)
624 ViewportRanges ranges = av.getRanges();
625 int viewportWidth = ranges.getViewportWidth();
627 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
630 * move right before drawing by the width of the scale left (if any)
631 * plus column offset from left margin (usually zero, but may not be
632 * when fast painting draws just a few columns)
634 int xOffset = labelWidthWest
635 + ((startColumn - ranges.getStartRes()) % viewportWidth)
637 g.translate(xOffset, 0);
639 // When printing we have an extra clipped region,
640 // the Printable page which we need to account for here
641 Shape clip = g.getClip();
645 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
649 g.setClip(0, (int) clip.getBounds().getY(),
650 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
653 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
656 int cHeight = av.getAlignment().getHeight() * charHeight;
658 if (av.isShowAnnotation())
660 g.translate(0, cHeight + ypos + 3);
661 if (annotations == null)
663 annotations = new AnnotationPanel(av);
666 annotations.renderer.drawComponent(annotations, av, g, -1,
667 startColumn, endx + 1);
668 g.translate(0, -cHeight - ypos - 3);
671 g.translate(-xOffset, 0);
675 * Draws scales left, right and above (if shown), and any hidden column
676 * markers, on all widths of the wrapped alignment
681 protected void drawWrappedDecorators(Graphics g, int startColumn)
683 g.setFont(av.getFont());
684 g.setColor(Color.black);
686 int ypos = wrappedSpaceAboveAlignment;
687 ViewportRanges ranges = av.getRanges();
688 int viewportWidth = ranges.getViewportWidth();
689 int maxWidth = ranges.getVisibleAlignmentWidth();
691 while (widthsDrawn < wrappedVisibleWidths)
693 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
695 if (av.getScaleLeftWrapped())
697 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
700 if (av.getScaleRightWrapped())
702 drawVerticalScale(g, startColumn, endColumn, ypos, false);
705 g.translate(labelWidthWest, 0);
708 * white fill region of scale above and hidden column markers
709 * (to support incremental fast paint of image)
711 g.setColor(Color.white);
712 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
713 * charWidth, wrappedSpaceAboveAlignment);
714 g.setColor(Color.black);
716 if (av.getScaleAboveWrapped())
718 drawNorthScale(g, startColumn, endColumn, ypos);
721 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
723 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
726 g.translate(-labelWidthWest, 0);
728 ypos += wrappedRepeatHeightPx;
729 startColumn += viewportWidth;
740 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
741 int startColumn, int endColumn)
743 g.setColor(Color.blue);
744 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
745 List<Integer> positions = hidden.findHiddenRegionPositions();
746 for (int pos : positions)
748 int res = pos - startColumn;
750 if (res < 0 || res > endColumn - startColumn)
756 * draw a downward-pointing triangle at the hidden columns location
757 * (before the following visible column)
759 int xMiddle = res * charWidth;
760 int[] xPoints = new int[] { xMiddle - charHeight / 4,
761 xMiddle + charHeight / 4, xMiddle };
762 int yTop = ypos - (charHeight / 2);
763 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
764 gg.fillPolygon(xPoints, yPoints, 3);
768 int getAnnotationHeight()
770 if (!av.isShowAnnotation())
775 if (annotations == null)
777 annotations = new AnnotationPanel(av);
780 return annotations.adjustPanelHeight();
784 * Draws the visible region of the alignment on the graphics context. If there
785 * are hidden column markers in the visible region, then each sub-region
786 * between the markers is drawn separately, followed by the hidden column
790 * the graphics context, positioned at the first residue to be drawn
792 * offset of the first column to draw (0..)
794 * offset of the last column to draw (0..)
796 * offset of the first sequence to draw (0..)
798 * offset of the last sequence to draw (0..)
800 * vertical offset at which to draw (for wrapped alignments)
802 public void drawPanel(Graphics g1, final int startRes, final int endRes,
803 final int startSeq, final int endSeq, final int yOffset)
806 if (!av.hasHiddenColumns())
808 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
813 final int screenYMax = endRes - startRes;
814 int blockStart = startRes;
815 int blockEnd = endRes;
817 for (int[] region : av.getAlignment().getHiddenColumns()
818 .getHiddenColumnsCopy())
820 int hideStart = region[0];
821 int hideEnd = region[1];
823 if (hideStart <= blockStart)
825 blockStart += (hideEnd - hideStart) + 1;
830 * draw up to just before the next hidden region, or the end of
831 * the visible region, whichever comes first
833 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
836 g1.translate(screenY * charWidth, 0);
838 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
841 * draw the downline of the hidden column marker (ScalePanel draws the
842 * triangle on top) if we reached it
844 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
846 g1.setColor(Color.blue);
848 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
849 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
850 (endSeq - startSeq + 1) * charHeight + yOffset);
853 g1.translate(-screenY * charWidth, 0);
854 screenY += blockEnd - blockStart + 1;
855 blockStart = hideEnd + 1;
857 if (screenY > screenYMax)
859 // already rendered last block
864 if (screenY <= screenYMax)
866 // remaining visible region to render
867 blockEnd = blockStart + screenYMax - screenY;
868 g1.translate(screenY * charWidth, 0);
869 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
871 g1.translate(-screenY * charWidth, 0);
878 * Draws a region of the visible alignment
882 * offset of the first column in the visible region (0..)
884 * offset of the last column in the visible region (0..)
886 * offset of the first sequence in the visible region (0..)
888 * offset of the last sequence in the visible region (0..)
890 * vertical offset at which to draw (for wrapped alignments)
892 private void draw(Graphics g, int startRes, int endRes, int startSeq,
893 int endSeq, int offset)
895 g.setFont(av.getFont());
896 sr.prepare(g, av.isRenderGaps());
900 // / First draw the sequences
901 // ///////////////////////////
902 for (int i = startSeq; i <= endSeq; i++)
904 nextSeq = av.getAlignment().getSequenceAt(i);
907 // occasionally, a race condition occurs such that the alignment row is
911 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
912 startRes, endRes, offset + ((i - startSeq) * charHeight));
914 if (av.isShowSequenceFeatures())
916 fr.drawSequence(g, nextSeq, startRes, endRes, offset
917 + ((i - startSeq) * charHeight), false);
921 * highlight search Results once sequence has been drawn
923 if (av.hasSearchResults())
925 SearchResultsI searchResults = av.getSearchResults();
926 int[] visibleResults = searchResults.getResults(nextSeq,
928 if (visibleResults != null)
930 for (int r = 0; r < visibleResults.length; r += 2)
932 sr.drawHighlightedText(nextSeq, visibleResults[r],
933 visibleResults[r + 1], (visibleResults[r] - startRes)
935 + ((i - startSeq) * charHeight));
940 if (av.cursorMode && cursorY == i && cursorX >= startRes
941 && cursorX <= endRes)
943 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
944 offset + ((i - startSeq) * charHeight));
948 if (av.getSelectionGroup() != null
949 || av.getAlignment().getGroups().size() > 0)
951 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
956 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
957 int startSeq, int endSeq, int offset)
959 Graphics2D g = (Graphics2D) g1;
961 // ///////////////////////////////////
962 // Now outline any areas if necessary
963 // ///////////////////////////////////
964 SequenceGroup group = av.getSelectionGroup();
970 int visWidth = (endRes - startRes + 1) * charWidth;
972 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
974 group = av.getAlignment().getGroups().get(0);
984 boolean inGroup = false;
988 for (i = startSeq; i <= endSeq; i++)
990 sx = (group.getStartRes() - startRes) * charWidth;
991 sy = offset + ((i - startSeq) * charHeight);
992 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
994 if (sx + ex < 0 || sx > visWidth)
999 if ((sx <= (endRes - startRes) * charWidth)
1000 && group.getSequences(null).contains(
1001 av.getAlignment().getSequenceAt(i)))
1004 && !group.getSequences(null).contains(
1005 av.getAlignment().getSequenceAt(i + 1)))
1007 bottom = sy + charHeight;
1012 if (((top == -1) && (i == 0))
1013 || !group.getSequences(null).contains(
1014 av.getAlignment().getSequenceAt(i - 1)))
1022 if (group == av.getSelectionGroup())
1024 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1025 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
1027 g.setColor(Color.RED);
1031 g.setStroke(new BasicStroke());
1032 g.setColor(group.getOutlineColour());
1040 if (sx >= 0 && sx < visWidth)
1042 g.drawLine(sx, oldY, sx, sy);
1045 if (sx + ex < visWidth)
1047 g.drawLine(sx + ex, oldY, sx + ex, sy);
1056 if (sx + ex > visWidth)
1061 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1063 ex = (endRes - startRes + 1) * charWidth;
1068 g.drawLine(sx, top, sx + ex, top);
1074 g.drawLine(sx, bottom, sx + ex, bottom);
1085 sy = offset + ((i - startSeq) * charHeight);
1086 if (sx >= 0 && sx < visWidth)
1088 g.drawLine(sx, oldY, sx, sy);
1091 if (sx + ex < visWidth)
1093 g.drawLine(sx + ex, oldY, sx + ex, sy);
1102 if (sx + ex > visWidth)
1106 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1108 ex = (endRes - startRes + 1) * charWidth;
1113 g.drawLine(sx, top, sx + ex, top);
1119 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1128 g.setStroke(new BasicStroke());
1130 if (groupIndex >= av.getAlignment().getGroups().size())
1135 group = av.getAlignment().getGroups().get(groupIndex);
1137 } while (groupIndex < av.getAlignment().getGroups().size());
1144 * Highlights search results in the visible region by rendering as white text
1145 * on a black background. Any previous highlighting is removed. Answers true
1146 * if any highlight was left on the visible alignment (so status bar should be
1147 * set to match), else false.
1149 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1150 * alignment had to be scrolled to show the highlighted region, then it should
1151 * be fully redrawn, otherwise a fast paint can be performed. This argument
1152 * could be removed if fast paint of scrolled wrapped alignment is coded in
1153 * future (JAL-2609).
1156 * @param noFastPaint
1159 public boolean highlightSearchResults(SearchResultsI results,
1160 boolean noFastPaint)
1166 boolean wrapped = av.getWrapAlignment();
1170 fastPaint = !noFastPaint;
1171 fastpainting = fastPaint;
1176 * to avoid redrawing the whole visible region, we instead
1177 * redraw just the minimal regions to remove previous highlights
1180 SearchResultsI previous = av.getSearchResults();
1181 av.setSearchResults(results);
1182 boolean redrawn = false;
1183 boolean drawn = false;
1186 redrawn = drawMappedPositionsWrapped(previous);
1187 drawn = drawMappedPositionsWrapped(results);
1192 redrawn = drawMappedPositions(previous);
1193 drawn = drawMappedPositions(results);
1198 * if highlights were either removed or added, repaint
1206 * return true only if highlights were added
1212 fastpainting = false;
1217 * Redraws the minimal rectangle in the visible region (if any) that includes
1218 * mapped positions of the given search results. Whether or not positions are
1219 * highlighted depends on the SearchResults set on the Viewport. This allows
1220 * this method to be called to either clear or set highlighting. Answers true
1221 * if any positions were drawn (in which case a repaint is still required),
1227 protected boolean drawMappedPositions(SearchResultsI results)
1229 if (results == null)
1235 * calculate the minimal rectangle to redraw that
1236 * includes both new and existing search results
1238 int firstSeq = Integer.MAX_VALUE;
1240 int firstCol = Integer.MAX_VALUE;
1242 boolean matchFound = false;
1244 ViewportRanges ranges = av.getRanges();
1245 int firstVisibleColumn = ranges.getStartRes();
1246 int lastVisibleColumn = ranges.getEndRes();
1247 AlignmentI alignment = av.getAlignment();
1248 if (av.hasHiddenColumns())
1250 firstVisibleColumn = alignment.getHiddenColumns()
1251 .adjustForHiddenColumns(firstVisibleColumn);
1252 lastVisibleColumn = alignment.getHiddenColumns()
1253 .adjustForHiddenColumns(lastVisibleColumn);
1256 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1257 .getEndSeq(); seqNo++)
1259 SequenceI seq = alignment.getSequenceAt(seqNo);
1261 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1263 if (visibleResults != null)
1265 for (int i = 0; i < visibleResults.length - 1; i += 2)
1267 int firstMatchedColumn = visibleResults[i];
1268 int lastMatchedColumn = visibleResults[i + 1];
1269 if (firstMatchedColumn <= lastVisibleColumn
1270 && lastMatchedColumn >= firstVisibleColumn)
1273 * found a search results match in the visible region -
1274 * remember the first and last sequence matched, and the first
1275 * and last visible columns in the matched positions
1278 firstSeq = Math.min(firstSeq, seqNo);
1279 lastSeq = Math.max(lastSeq, seqNo);
1280 firstMatchedColumn = Math.max(firstMatchedColumn,
1281 firstVisibleColumn);
1282 lastMatchedColumn = Math.min(lastMatchedColumn,
1284 firstCol = Math.min(firstCol, firstMatchedColumn);
1285 lastCol = Math.max(lastCol, lastMatchedColumn);
1293 if (av.hasHiddenColumns())
1295 firstCol = alignment.getHiddenColumns()
1296 .findColumnPosition(firstCol);
1297 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1299 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1300 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1301 gg.translate(transX, transY);
1302 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1303 gg.translate(-transX, -transY);
1310 public void propertyChange(PropertyChangeEvent evt)
1312 String eventName = evt.getPropertyName();
1315 if (eventName.equals(ViewportRanges.STARTRES))
1317 // Make sure we're not trying to draw a panel
1318 // larger than the visible window
1319 ViewportRanges vpRanges = av.getRanges();
1320 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1321 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1322 if (scrollX > range)
1326 else if (scrollX < -range)
1332 // Both scrolling and resizing change viewport ranges: scrolling changes
1333 // both start and end points, but resize only changes end values.
1334 // Here we only want to fastpaint on a scroll, with resize using a normal
1335 // paint, so scroll events are identified as changes to the horizontal or
1336 // vertical start value.
1337 if (eventName.equals(ViewportRanges.STARTRES))
1339 // scroll - startres and endres both change
1340 if (av.getWrapAlignment())
1342 fastPaintWrapped(scrollX);
1346 fastPaint(scrollX, 0);
1349 else if (eventName.equals(ViewportRanges.STARTSEQ))
1351 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1356 * Does a minimal update of the image for a scroll movement. This method
1357 * handles scroll movements of up to one width of the wrapped alignment (one
1358 * click in the vertical scrollbar). Larger movements (for example after a
1359 * scroll to highlight a mapped position) trigger a full redraw instead.
1362 * number of positions scrolled (right if positive, left if negative)
1364 protected void fastPaintWrapped(int scrollX)
1366 ViewportRanges ranges = av.getRanges();
1368 if (Math.abs(scrollX) > ranges.getViewportWidth())
1371 * shift of more than one view width is
1372 * overcomplicated to handle in this method
1379 if (fastpainting || gg == null)
1385 fastpainting = true;
1389 calculateWrappedGeometry(getWidth(), getHeight());
1392 * relocate the regions of the alignment that are still visible
1394 shiftWrappedAlignment(-scrollX);
1397 * add new columns (sequence, annotation)
1398 * - at top left if scrollX < 0
1399 * - at right of last two widths if scrollX > 0
1403 int startRes = ranges.getStartRes();
1404 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1405 - scrollX - 1, getHeight());
1409 fastPaintWrappedAddRight(scrollX);
1413 * draw all scales (if shown) and hidden column markers
1415 drawWrappedDecorators(gg, ranges.getStartRes());
1420 fastpainting = false;
1425 * Draws the specified number of columns at the 'end' (bottom right) of a
1426 * wrapped alignment view, including sequences and annotations if shown, but
1427 * not scales. Also draws the same number of columns at the right hand end of
1428 * the second last width shown, if the last width is not full height (so
1429 * cannot simply be copied from the graphics image).
1433 protected void fastPaintWrappedAddRight(int columns)
1440 ViewportRanges ranges = av.getRanges();
1441 int viewportWidth = ranges.getViewportWidth();
1444 * draw full height alignment in the second last row, last columns, if the
1445 * last row was not full height
1447 int visibleWidths = wrappedVisibleWidths;
1448 int canvasHeight = getHeight();
1449 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1451 if (lastWidthPartHeight)
1453 int widthsAbove = visibleWidths - 2;
1454 int ypos = wrappedRepeatHeightPx * widthsAbove
1455 + wrappedSpaceAboveAlignment;
1456 int endRes = ranges.getEndRes();
1457 endRes += widthsAbove * viewportWidth;
1458 int startRes = endRes - columns;
1459 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1463 * white fill first to erase annotations
1465 gg.translate(xOffset, 0);
1466 gg.setColor(Color.white);
1467 gg.fillRect(labelWidthWest, ypos,
1468 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1469 gg.translate(-xOffset, 0);
1471 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1475 * y-offset for drawing last width is height of widths above,
1478 int widthsAbove = visibleWidths - 1;
1479 int ypos = wrappedRepeatHeightPx * widthsAbove
1480 + wrappedSpaceAboveAlignment;
1481 int endRes = ranges.getEndRes();
1482 endRes += widthsAbove * viewportWidth;
1483 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1484 int startRes = endRes - columns + 1;
1487 * white fill first to erase annotations
1489 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1491 gg.translate(xOffset, 0);
1492 gg.setColor(Color.white);
1493 int width = viewportWidth * charWidth - xOffset;
1494 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1495 gg.translate(-xOffset, 0);
1497 gg.setFont(av.getFont());
1498 gg.setColor(Color.black);
1500 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1503 * and finally, white fill any space below the visible alignment
1505 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1506 if (heightBelow > 0)
1508 gg.setColor(Color.white);
1509 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1514 * Shifts the visible alignment by the specified number of columns - left if
1515 * negative, right if positive. Copies and moves sequences and annotations (if
1516 * shown). Scales, hidden column markers and any newly visible columns must be
1521 protected void shiftWrappedAlignment(int positions)
1528 int canvasHeight = getHeight();
1529 ViewportRanges ranges = av.getRanges();
1530 int viewportWidth = ranges.getViewportWidth();
1531 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1533 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1534 int xMax = ranges.getVisibleAlignmentWidth();
1539 * shift right (after scroll left)
1540 * for each wrapped width (starting with the last), copy (width-positions)
1541 * columns from the left margin to the right margin, and copy positions
1542 * columns from the right margin of the row above (if any) to the
1543 * left margin of the current row
1547 * get y-offset of last wrapped width, first row of sequences
1549 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1550 y += wrappedSpaceAboveAlignment;
1551 int copyFromLeftStart = labelWidthWest;
1552 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1556 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1557 positions * charWidth, 0);
1560 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1561 positions * charWidth, heightToCopy, -widthToCopy,
1562 wrappedRepeatHeightPx);
1565 y -= wrappedRepeatHeightPx;
1571 * shift left (after scroll right)
1572 * for each wrapped width (starting with the first), copy (width-positions)
1573 * columns from the right margin to the left margin, and copy positions
1574 * columns from the left margin of the row below (if any) to the
1575 * right margin of the current row
1577 int xpos = av.getRanges().getStartRes();
1578 int y = wrappedSpaceAboveAlignment;
1579 int copyFromRightStart = labelWidthWest - positions * charWidth;
1581 while (y < canvasHeight)
1583 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1584 positions * charWidth, 0);
1585 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1586 && (xpos + viewportWidth <= xMax))
1588 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1589 * charWidth, heightToCopy, widthToCopy,
1590 -wrappedRepeatHeightPx);
1593 y += wrappedRepeatHeightPx;
1594 xpos += viewportWidth;
1600 * Redraws any positions in the search results in the visible region of a
1601 * wrapped alignment. Any highlights are drawn depending on the search results
1602 * set on the Viewport, not the <code>results</code> argument. This allows
1603 * this method to be called either to clear highlights (passing the previous
1604 * search results), or to draw new highlights.
1609 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1611 if (results == null)
1616 boolean matchFound = false;
1618 int wrappedWidth = av.getWrappedWidth();
1619 int wrappedHeight = getRepeatHeightWrapped();
1621 ViewportRanges ranges = av.getRanges();
1622 int canvasHeight = getHeight();
1623 int repeats = canvasHeight / wrappedHeight;
1624 if (canvasHeight / wrappedHeight > 0)
1629 int firstVisibleColumn = ranges.getStartRes();
1630 int lastVisibleColumn = ranges.getStartRes() + repeats
1631 * ranges.getViewportWidth() - 1;
1633 AlignmentI alignment = av.getAlignment();
1634 if (av.hasHiddenColumns())
1636 firstVisibleColumn = alignment.getHiddenColumns()
1637 .adjustForHiddenColumns(firstVisibleColumn);
1638 lastVisibleColumn = alignment.getHiddenColumns()
1639 .adjustForHiddenColumns(lastVisibleColumn);
1642 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1644 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1645 .getEndSeq(); seqNo++)
1647 SequenceI seq = alignment.getSequenceAt(seqNo);
1649 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1651 if (visibleResults != null)
1653 for (int i = 0; i < visibleResults.length - 1; i += 2)
1655 int firstMatchedColumn = visibleResults[i];
1656 int lastMatchedColumn = visibleResults[i + 1];
1657 if (firstMatchedColumn <= lastVisibleColumn
1658 && lastMatchedColumn >= firstVisibleColumn)
1661 * found a search results match in the visible region
1663 firstMatchedColumn = Math.max(firstMatchedColumn,
1664 firstVisibleColumn);
1665 lastMatchedColumn = Math.min(lastMatchedColumn,
1669 * draw each mapped position separately (as contiguous positions may
1670 * wrap across lines)
1672 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1674 int displayColumn = mappedPos;
1675 if (av.hasHiddenColumns())
1677 displayColumn = alignment.getHiddenColumns()
1678 .findColumnPosition(displayColumn);
1682 * transX: offset from left edge of canvas to residue position
1684 int transX = labelWidthWest
1685 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1686 * av.getCharWidth();
1689 * transY: offset from top edge of canvas to residue position
1691 int transY = gapHeight;
1692 transY += (displayColumn - ranges.getStartRes())
1693 / wrappedWidth * wrappedHeight;
1694 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1697 * yOffset is from graphics origin to start of visible region
1699 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1700 if (transY < getHeight())
1703 gg.translate(transX, transY);
1704 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1706 gg.translate(-transX, -transY);
1718 * Answers the height in pixels of a repeating section of the wrapped
1719 * alignment, including space above, scale above if shown, sequences, and
1720 * annotation panel if shown
1724 protected int getRepeatHeightWrapped()
1726 // gap (and maybe scale) above
1727 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1730 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1732 // add annotations panel height if shown
1733 repeatHeight += getAnnotationHeight();
1735 return repeatHeight;