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 be non-zero
632 * when fast painting is drawing 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());
654 * white fill the region to be drawn (so incremental fast paint doesn't
655 * scribble over an existing image)
657 gg.setColor(Color.white);
658 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
659 wrappedRepeatHeightPx);
661 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
664 int cHeight = av.getAlignment().getHeight() * charHeight;
666 if (av.isShowAnnotation())
668 g.translate(0, cHeight + ypos + 3);
669 if (annotations == null)
671 annotations = new AnnotationPanel(av);
674 annotations.renderer.drawComponent(annotations, av, g, -1,
675 startColumn, endx + 1);
676 g.translate(0, -cHeight - ypos - 3);
679 g.translate(-xOffset, 0);
683 * Draws scales left, right and above (if shown), and any hidden column
684 * markers, on all widths of the wrapped alignment
689 protected void drawWrappedDecorators(Graphics g, int startColumn)
691 g.setFont(av.getFont());
692 g.setColor(Color.black);
694 int ypos = wrappedSpaceAboveAlignment;
695 ViewportRanges ranges = av.getRanges();
696 int viewportWidth = ranges.getViewportWidth();
697 int maxWidth = ranges.getVisibleAlignmentWidth();
699 while (widthsDrawn < wrappedVisibleWidths)
701 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
703 if (av.getScaleLeftWrapped())
705 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
708 if (av.getScaleRightWrapped())
710 drawVerticalScale(g, startColumn, endColumn, ypos, false);
713 g.translate(labelWidthWest, 0);
716 * white fill region of scale above and hidden column markers
717 * (to support incremental fast paint of image)
719 g.setColor(Color.white);
720 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
721 * charWidth, wrappedSpaceAboveAlignment);
722 g.setColor(Color.black);
724 if (av.getScaleAboveWrapped())
726 drawNorthScale(g, startColumn, endColumn, ypos);
729 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
731 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
734 g.translate(-labelWidthWest, 0);
736 ypos += wrappedRepeatHeightPx;
737 startColumn += viewportWidth;
748 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
749 int startColumn, int endColumn)
751 g.setColor(Color.blue);
752 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
753 List<Integer> positions = hidden.findHiddenRegionPositions();
754 for (int pos : positions)
756 int res = pos - startColumn;
758 if (res < 0 || res > endColumn - startColumn)
764 * draw a downward-pointing triangle at the hidden columns location
765 * (before the following visible column)
767 int xMiddle = res * charWidth;
768 int[] xPoints = new int[] { xMiddle - charHeight / 4,
769 xMiddle + charHeight / 4, xMiddle };
770 int yTop = ypos - (charHeight / 2);
771 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
772 gg.fillPolygon(xPoints, yPoints, 3);
776 int getAnnotationHeight()
778 if (!av.isShowAnnotation())
783 if (annotations == null)
785 annotations = new AnnotationPanel(av);
788 return annotations.adjustPanelHeight();
792 * Draws the visible region of the alignment on the graphics context. If there
793 * are hidden column markers in the visible region, then each sub-region
794 * between the markers is drawn separately, followed by the hidden column
798 * the graphics context, positioned at the first residue to be drawn
800 * offset of the first column to draw (0..)
802 * offset of the last column to draw (0..)
804 * offset of the first sequence to draw (0..)
806 * offset of the last sequence to draw (0..)
808 * vertical offset at which to draw (for wrapped alignments)
810 public void drawPanel(Graphics g1, final int startRes, final int endRes,
811 final int startSeq, final int endSeq, final int yOffset)
814 if (!av.hasHiddenColumns())
816 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
821 final int screenYMax = endRes - startRes;
822 int blockStart = startRes;
823 int blockEnd = endRes;
825 for (int[] region : av.getAlignment().getHiddenColumns()
826 .getHiddenColumnsCopy())
828 int hideStart = region[0];
829 int hideEnd = region[1];
831 if (hideStart <= blockStart)
833 blockStart += (hideEnd - hideStart) + 1;
838 * draw up to just before the next hidden region, or the end of
839 * the visible region, whichever comes first
841 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
844 g1.translate(screenY * charWidth, 0);
846 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
849 * draw the downline of the hidden column marker (ScalePanel draws the
850 * triangle on top) if we reached it
852 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
854 g1.setColor(Color.blue);
856 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
857 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
858 (endSeq - startSeq + 1) * charHeight + yOffset);
861 g1.translate(-screenY * charWidth, 0);
862 screenY += blockEnd - blockStart + 1;
863 blockStart = hideEnd + 1;
865 if (screenY > screenYMax)
867 // already rendered last block
872 if (screenY <= screenYMax)
874 // remaining visible region to render
875 blockEnd = blockStart + screenYMax - screenY;
876 g1.translate(screenY * charWidth, 0);
877 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
879 g1.translate(-screenY * charWidth, 0);
886 * Draws a region of the visible alignment
890 * offset of the first column in the visible region (0..)
892 * offset of the last column in the visible region (0..)
894 * offset of the first sequence in the visible region (0..)
896 * offset of the last sequence in the visible region (0..)
898 * vertical offset at which to draw (for wrapped alignments)
900 private void draw(Graphics g, int startRes, int endRes, int startSeq,
901 int endSeq, int offset)
903 g.setFont(av.getFont());
904 sr.prepare(g, av.isRenderGaps());
908 // / First draw the sequences
909 // ///////////////////////////
910 for (int i = startSeq; i <= endSeq; i++)
912 nextSeq = av.getAlignment().getSequenceAt(i);
915 // occasionally, a race condition occurs such that the alignment row is
919 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
920 startRes, endRes, offset + ((i - startSeq) * charHeight));
922 if (av.isShowSequenceFeatures())
924 fr.drawSequence(g, nextSeq, startRes, endRes, offset
925 + ((i - startSeq) * charHeight), false);
929 * highlight search Results once sequence has been drawn
931 if (av.hasSearchResults())
933 SearchResultsI searchResults = av.getSearchResults();
934 int[] visibleResults = searchResults.getResults(nextSeq,
936 if (visibleResults != null)
938 for (int r = 0; r < visibleResults.length; r += 2)
940 sr.drawHighlightedText(nextSeq, visibleResults[r],
941 visibleResults[r + 1], (visibleResults[r] - startRes)
943 + ((i - startSeq) * charHeight));
948 if (av.cursorMode && cursorY == i && cursorX >= startRes
949 && cursorX <= endRes)
951 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
952 offset + ((i - startSeq) * charHeight));
956 if (av.getSelectionGroup() != null
957 || av.getAlignment().getGroups().size() > 0)
959 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
964 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
965 int startSeq, int endSeq, int offset)
967 Graphics2D g = (Graphics2D) g1;
969 // ///////////////////////////////////
970 // Now outline any areas if necessary
971 // ///////////////////////////////////
972 SequenceGroup group = av.getSelectionGroup();
978 int visWidth = (endRes - startRes + 1) * charWidth;
980 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
982 group = av.getAlignment().getGroups().get(0);
992 boolean inGroup = false;
996 for (i = startSeq; i <= endSeq; i++)
998 sx = (group.getStartRes() - startRes) * charWidth;
999 sy = offset + ((i - startSeq) * charHeight);
1000 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
1002 if (sx + ex < 0 || sx > visWidth)
1007 if ((sx <= (endRes - startRes) * charWidth)
1008 && group.getSequences(null).contains(
1009 av.getAlignment().getSequenceAt(i)))
1012 && !group.getSequences(null).contains(
1013 av.getAlignment().getSequenceAt(i + 1)))
1015 bottom = sy + charHeight;
1020 if (((top == -1) && (i == 0))
1021 || !group.getSequences(null).contains(
1022 av.getAlignment().getSequenceAt(i - 1)))
1030 if (group == av.getSelectionGroup())
1032 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1033 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
1035 g.setColor(Color.RED);
1039 g.setStroke(new BasicStroke());
1040 g.setColor(group.getOutlineColour());
1048 if (sx >= 0 && sx < visWidth)
1050 g.drawLine(sx, oldY, sx, sy);
1053 if (sx + ex < visWidth)
1055 g.drawLine(sx + ex, oldY, sx + ex, sy);
1064 if (sx + ex > visWidth)
1069 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1071 ex = (endRes - startRes + 1) * charWidth;
1076 g.drawLine(sx, top, sx + ex, top);
1082 g.drawLine(sx, bottom, sx + ex, bottom);
1093 sy = offset + ((i - startSeq) * charHeight);
1094 if (sx >= 0 && sx < visWidth)
1096 g.drawLine(sx, oldY, sx, sy);
1099 if (sx + ex < visWidth)
1101 g.drawLine(sx + ex, oldY, sx + ex, sy);
1110 if (sx + ex > visWidth)
1114 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1116 ex = (endRes - startRes + 1) * charWidth;
1121 g.drawLine(sx, top, sx + ex, top);
1127 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1136 g.setStroke(new BasicStroke());
1138 if (groupIndex >= av.getAlignment().getGroups().size())
1143 group = av.getAlignment().getGroups().get(groupIndex);
1145 } while (groupIndex < av.getAlignment().getGroups().size());
1152 * Highlights search results in the visible region by rendering as white text
1153 * on a black background. Any previous highlighting is removed. Answers true
1154 * if any highlight was left on the visible alignment (so status bar should be
1155 * set to match), else false.
1157 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1158 * alignment had to be scrolled to show the highlighted region, then it should
1159 * be fully redrawn, otherwise a fast paint can be performed. This argument
1160 * could be removed if fast paint of scrolled wrapped alignment is coded in
1161 * future (JAL-2609).
1164 * @param noFastPaint
1167 public boolean highlightSearchResults(SearchResultsI results,
1168 boolean noFastPaint)
1174 boolean wrapped = av.getWrapAlignment();
1178 fastPaint = !noFastPaint;
1179 fastpainting = fastPaint;
1184 * to avoid redrawing the whole visible region, we instead
1185 * redraw just the minimal regions to remove previous highlights
1188 SearchResultsI previous = av.getSearchResults();
1189 av.setSearchResults(results);
1190 boolean redrawn = false;
1191 boolean drawn = false;
1194 redrawn = drawMappedPositionsWrapped(previous);
1195 drawn = drawMappedPositionsWrapped(results);
1200 redrawn = drawMappedPositions(previous);
1201 drawn = drawMappedPositions(results);
1206 * if highlights were either removed or added, repaint
1214 * return true only if highlights were added
1220 fastpainting = false;
1225 * Redraws the minimal rectangle in the visible region (if any) that includes
1226 * mapped positions of the given search results. Whether or not positions are
1227 * highlighted depends on the SearchResults set on the Viewport. This allows
1228 * this method to be called to either clear or set highlighting. Answers true
1229 * if any positions were drawn (in which case a repaint is still required),
1235 protected boolean drawMappedPositions(SearchResultsI results)
1237 if (results == null)
1243 * calculate the minimal rectangle to redraw that
1244 * includes both new and existing search results
1246 int firstSeq = Integer.MAX_VALUE;
1248 int firstCol = Integer.MAX_VALUE;
1250 boolean matchFound = false;
1252 ViewportRanges ranges = av.getRanges();
1253 int firstVisibleColumn = ranges.getStartRes();
1254 int lastVisibleColumn = ranges.getEndRes();
1255 AlignmentI alignment = av.getAlignment();
1256 if (av.hasHiddenColumns())
1258 firstVisibleColumn = alignment.getHiddenColumns()
1259 .adjustForHiddenColumns(firstVisibleColumn);
1260 lastVisibleColumn = alignment.getHiddenColumns()
1261 .adjustForHiddenColumns(lastVisibleColumn);
1264 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1265 .getEndSeq(); seqNo++)
1267 SequenceI seq = alignment.getSequenceAt(seqNo);
1269 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1271 if (visibleResults != null)
1273 for (int i = 0; i < visibleResults.length - 1; i += 2)
1275 int firstMatchedColumn = visibleResults[i];
1276 int lastMatchedColumn = visibleResults[i + 1];
1277 if (firstMatchedColumn <= lastVisibleColumn
1278 && lastMatchedColumn >= firstVisibleColumn)
1281 * found a search results match in the visible region -
1282 * remember the first and last sequence matched, and the first
1283 * and last visible columns in the matched positions
1286 firstSeq = Math.min(firstSeq, seqNo);
1287 lastSeq = Math.max(lastSeq, seqNo);
1288 firstMatchedColumn = Math.max(firstMatchedColumn,
1289 firstVisibleColumn);
1290 lastMatchedColumn = Math.min(lastMatchedColumn,
1292 firstCol = Math.min(firstCol, firstMatchedColumn);
1293 lastCol = Math.max(lastCol, lastMatchedColumn);
1301 if (av.hasHiddenColumns())
1303 firstCol = alignment.getHiddenColumns()
1304 .findColumnPosition(firstCol);
1305 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1307 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1308 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1309 gg.translate(transX, transY);
1310 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1311 gg.translate(-transX, -transY);
1318 public void propertyChange(PropertyChangeEvent evt)
1320 String eventName = evt.getPropertyName();
1323 if (eventName.equals(ViewportRanges.STARTRES))
1325 // Make sure we're not trying to draw a panel
1326 // larger than the visible window
1327 ViewportRanges vpRanges = av.getRanges();
1328 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1329 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1330 if (scrollX > range)
1334 else if (scrollX < -range)
1340 // Both scrolling and resizing change viewport ranges: scrolling changes
1341 // both start and end points, but resize only changes end values.
1342 // Here we only want to fastpaint on a scroll, with resize using a normal
1343 // paint, so scroll events are identified as changes to the horizontal or
1344 // vertical start value.
1345 if (eventName.equals(ViewportRanges.STARTRES))
1347 // scroll - startres and endres both change
1348 if (av.getWrapAlignment())
1350 fastPaintWrapped(scrollX);
1354 fastPaint(scrollX, 0);
1357 else if (eventName.equals(ViewportRanges.STARTSEQ))
1359 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1364 * Does a minimal update of the image for a scroll movement. This method
1365 * handles scroll movements of up to one width of the wrapped alignment (one
1366 * click in the vertical scrollbar). Larger movements (for example after a
1367 * scroll to highlight a mapped position) trigger a full redraw instead.
1370 * number of positions scrolled (right if positive, left if negative)
1372 protected void fastPaintWrapped(int scrollX)
1374 ViewportRanges ranges = av.getRanges();
1376 if (Math.abs(scrollX) > ranges.getViewportWidth())
1379 * shift of more than one view width is
1380 * overcomplicated to handle in this method
1387 if (fastpainting || gg == null)
1393 fastpainting = true;
1397 calculateWrappedGeometry(getWidth(), getHeight());
1400 * relocate the regions of the alignment that are still visible
1402 shiftWrappedAlignment(-scrollX);
1405 * add new columns (sequence, annotation)
1406 * - at top left if scrollX < 0
1407 * - at right of last two widths if scrollX > 0
1411 int startRes = ranges.getStartRes();
1412 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1413 - scrollX - 1, getHeight());
1417 fastPaintWrappedAddRight(scrollX);
1421 * draw all scales (if shown) and hidden column markers
1423 drawWrappedDecorators(gg, ranges.getStartRes());
1428 fastpainting = false;
1433 * Draws the specified number of columns at the 'end' (bottom right) of a
1434 * wrapped alignment view, including sequences and annotations if shown, but
1435 * not scales. Also draws the same number of columns at the right hand end of
1436 * the second last width shown, if the last width is not full height (so
1437 * cannot simply be copied from the graphics image).
1441 protected void fastPaintWrappedAddRight(int columns)
1448 ViewportRanges ranges = av.getRanges();
1449 int viewportWidth = ranges.getViewportWidth();
1452 * draw full height alignment in the second last row, last columns, if the
1453 * last row was not full height
1455 int visibleWidths = wrappedVisibleWidths;
1456 int canvasHeight = getHeight();
1457 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1459 if (lastWidthPartHeight)
1461 int widthsAbove = visibleWidths - 2;
1462 int ypos = wrappedRepeatHeightPx * widthsAbove
1463 + wrappedSpaceAboveAlignment;
1464 int endRes = ranges.getEndRes();
1465 endRes += widthsAbove * viewportWidth;
1466 int startRes = endRes - columns;
1467 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1471 * white fill first to erase annotations
1473 gg.translate(xOffset, 0);
1474 gg.setColor(Color.white);
1475 gg.fillRect(labelWidthWest, ypos,
1476 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1477 gg.translate(-xOffset, 0);
1479 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1483 * y-offset for drawing last width is height of widths above,
1486 int widthsAbove = visibleWidths - 1;
1487 int ypos = wrappedRepeatHeightPx * widthsAbove
1488 + wrappedSpaceAboveAlignment;
1489 int endRes = ranges.getEndRes();
1490 endRes += widthsAbove * viewportWidth;
1491 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1492 int startRes = endRes - columns + 1;
1495 * white fill first to erase annotations
1497 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1499 gg.translate(xOffset, 0);
1500 gg.setColor(Color.white);
1501 int width = viewportWidth * charWidth - xOffset;
1502 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1503 gg.translate(-xOffset, 0);
1505 gg.setFont(av.getFont());
1506 gg.setColor(Color.black);
1508 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1511 * and finally, white fill any space below the visible alignment
1513 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1514 if (heightBelow > 0)
1516 gg.setColor(Color.white);
1517 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1522 * Shifts the visible alignment by the specified number of columns - left if
1523 * negative, right if positive. Copies and moves sequences and annotations (if
1524 * shown). Scales, hidden column markers and any newly visible columns must be
1529 protected void shiftWrappedAlignment(int positions)
1536 int canvasHeight = getHeight();
1537 ViewportRanges ranges = av.getRanges();
1538 int viewportWidth = ranges.getViewportWidth();
1539 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1541 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1542 int xMax = ranges.getVisibleAlignmentWidth();
1547 * shift right (after scroll left)
1548 * for each wrapped width (starting with the last), copy (width-positions)
1549 * columns from the left margin to the right margin, and copy positions
1550 * columns from the right margin of the row above (if any) to the
1551 * left margin of the current row
1555 * get y-offset of last wrapped width, first row of sequences
1557 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1558 y += wrappedSpaceAboveAlignment;
1559 int copyFromLeftStart = labelWidthWest;
1560 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1564 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1565 positions * charWidth, 0);
1568 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1569 positions * charWidth, heightToCopy, -widthToCopy,
1570 wrappedRepeatHeightPx);
1573 y -= wrappedRepeatHeightPx;
1579 * shift left (after scroll right)
1580 * for each wrapped width (starting with the first), copy (width-positions)
1581 * columns from the right margin to the left margin, and copy positions
1582 * columns from the left margin of the row below (if any) to the
1583 * right margin of the current row
1585 int xpos = av.getRanges().getStartRes();
1586 int y = wrappedSpaceAboveAlignment;
1587 int copyFromRightStart = labelWidthWest - positions * charWidth;
1589 while (y < canvasHeight)
1591 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1592 positions * charWidth, 0);
1593 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1594 && (xpos + viewportWidth <= xMax))
1596 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1597 * charWidth, heightToCopy, widthToCopy,
1598 -wrappedRepeatHeightPx);
1601 y += wrappedRepeatHeightPx;
1602 xpos += viewportWidth;
1608 * Redraws any positions in the search results in the visible region of a
1609 * wrapped alignment. Any highlights are drawn depending on the search results
1610 * set on the Viewport, not the <code>results</code> argument. This allows
1611 * this method to be called either to clear highlights (passing the previous
1612 * search results), or to draw new highlights.
1617 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1619 if (results == null)
1624 boolean matchFound = false;
1626 int wrappedWidth = av.getWrappedWidth();
1627 int wrappedHeight = getRepeatHeightWrapped();
1629 ViewportRanges ranges = av.getRanges();
1630 int canvasHeight = getHeight();
1631 int repeats = canvasHeight / wrappedHeight;
1632 if (canvasHeight / wrappedHeight > 0)
1637 int firstVisibleColumn = ranges.getStartRes();
1638 int lastVisibleColumn = ranges.getStartRes() + repeats
1639 * ranges.getViewportWidth() - 1;
1641 AlignmentI alignment = av.getAlignment();
1642 if (av.hasHiddenColumns())
1644 firstVisibleColumn = alignment.getHiddenColumns()
1645 .adjustForHiddenColumns(firstVisibleColumn);
1646 lastVisibleColumn = alignment.getHiddenColumns()
1647 .adjustForHiddenColumns(lastVisibleColumn);
1650 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1652 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1653 .getEndSeq(); seqNo++)
1655 SequenceI seq = alignment.getSequenceAt(seqNo);
1657 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1659 if (visibleResults != null)
1661 for (int i = 0; i < visibleResults.length - 1; i += 2)
1663 int firstMatchedColumn = visibleResults[i];
1664 int lastMatchedColumn = visibleResults[i + 1];
1665 if (firstMatchedColumn <= lastVisibleColumn
1666 && lastMatchedColumn >= firstVisibleColumn)
1669 * found a search results match in the visible region
1671 firstMatchedColumn = Math.max(firstMatchedColumn,
1672 firstVisibleColumn);
1673 lastMatchedColumn = Math.min(lastMatchedColumn,
1677 * draw each mapped position separately (as contiguous positions may
1678 * wrap across lines)
1680 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1682 int displayColumn = mappedPos;
1683 if (av.hasHiddenColumns())
1685 displayColumn = alignment.getHiddenColumns()
1686 .findColumnPosition(displayColumn);
1690 * transX: offset from left edge of canvas to residue position
1692 int transX = labelWidthWest
1693 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1694 * av.getCharWidth();
1697 * transY: offset from top edge of canvas to residue position
1699 int transY = gapHeight;
1700 transY += (displayColumn - ranges.getStartRes())
1701 / wrappedWidth * wrappedHeight;
1702 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1705 * yOffset is from graphics origin to start of visible region
1707 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1708 if (transY < getHeight())
1711 gg.translate(transX, transY);
1712 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1714 gg.translate(-transX, -transY);
1726 * Answers the height in pixels of a repeating section of the wrapped
1727 * alignment, including space above, scale above if shown, sequences, and
1728 * annotation panel if shown
1732 protected int getRepeatHeightWrapped()
1734 // gap (and maybe scale) above
1735 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1738 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1740 // add annotations panel height if shown
1741 repeatHeight += getAnnotationHeight();
1743 return repeatHeight;