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, canvasHeight, 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 the wrapped alignment
683 protected void drawWrappedDecorators(Graphics g, int canvasHeight,
686 g.setFont(av.getFont());
687 g.setColor(Color.black);
689 int ypos = wrappedSpaceAboveAlignment;
690 ViewportRanges ranges = av.getRanges();
691 int viewportWidth = ranges.getViewportWidth();
692 int maxWidth = ranges.getVisibleAlignmentWidth();
694 while (widthsDrawn < wrappedVisibleWidths)
696 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
698 if (av.getScaleLeftWrapped())
700 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
703 if (av.getScaleRightWrapped())
705 drawVerticalScale(g, startColumn, endColumn, ypos, false);
708 g.translate(labelWidthWest, 0);
710 if (av.getScaleAboveWrapped())
712 drawNorthScale(g, startColumn, endColumn, ypos);
715 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
717 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
720 g.translate(-labelWidthWest, 0);
722 ypos += wrappedRepeatHeightPx;
723 startColumn += viewportWidth;
734 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
735 int startColumn, int endColumn)
737 g.setColor(Color.blue);
738 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
739 List<Integer> positions = hidden.findHiddenRegionPositions();
740 for (int pos : positions)
742 int res = pos - startColumn;
744 if (res < 0 || res > endColumn - startColumn)
750 * draw a downward-pointing triangle at the hidden columns location
751 * (before the following visible column)
753 int xMiddle = res * charWidth;
754 int[] xPoints = new int[] { xMiddle - charHeight / 4,
755 xMiddle + charHeight / 4, xMiddle };
756 int yTop = ypos - (charHeight / 2);
757 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
758 gg.fillPolygon(xPoints, yPoints, 3);
762 int getAnnotationHeight()
764 if (!av.isShowAnnotation())
769 if (annotations == null)
771 annotations = new AnnotationPanel(av);
774 return annotations.adjustPanelHeight();
778 * Draws the visible region of the alignment on the graphics context. If there
779 * are hidden column markers in the visible region, then each sub-region
780 * between the markers is drawn separately, followed by the hidden column
784 * the graphics context, positioned at the first residue to be drawn
786 * offset of the first column to draw (0..)
788 * offset of the last column to draw (0..)
790 * offset of the first sequence to draw (0..)
792 * offset of the last sequence to draw (0..)
794 * vertical offset at which to draw (for wrapped alignments)
796 public void drawPanel(Graphics g1, final int startRes, final int endRes,
797 final int startSeq, final int endSeq, final int yOffset)
800 if (!av.hasHiddenColumns())
802 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
807 final int screenYMax = endRes - startRes;
808 int blockStart = startRes;
809 int blockEnd = endRes;
811 for (int[] region : av.getAlignment().getHiddenColumns()
812 .getHiddenColumnsCopy())
814 int hideStart = region[0];
815 int hideEnd = region[1];
817 if (hideStart <= blockStart)
819 blockStart += (hideEnd - hideStart) + 1;
824 * draw up to just before the next hidden region, or the end of
825 * the visible region, whichever comes first
827 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
830 g1.translate(screenY * charWidth, 0);
832 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
835 * draw the downline of the hidden column marker (ScalePanel draws the
836 * triangle on top) if we reached it
838 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
840 g1.setColor(Color.blue);
842 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
843 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
844 (endSeq - startSeq + 1) * charHeight + yOffset);
847 g1.translate(-screenY * charWidth, 0);
848 screenY += blockEnd - blockStart + 1;
849 blockStart = hideEnd + 1;
851 if (screenY > screenYMax)
853 // already rendered last block
858 if (screenY <= screenYMax)
860 // remaining visible region to render
861 blockEnd = blockStart + screenYMax - screenY;
862 g1.translate(screenY * charWidth, 0);
863 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
865 g1.translate(-screenY * charWidth, 0);
872 * Draws a region of the visible alignment
876 * offset of the first column in the visible region (0..)
878 * offset of the last column in the visible region (0..)
880 * offset of the first sequence in the visible region (0..)
882 * offset of the last sequence in the visible region (0..)
884 * vertical offset at which to draw (for wrapped alignments)
886 private void draw(Graphics g, int startRes, int endRes, int startSeq,
887 int endSeq, int offset)
889 g.setFont(av.getFont());
890 sr.prepare(g, av.isRenderGaps());
894 // / First draw the sequences
895 // ///////////////////////////
896 for (int i = startSeq; i <= endSeq; i++)
898 nextSeq = av.getAlignment().getSequenceAt(i);
901 // occasionally, a race condition occurs such that the alignment row is
905 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
906 startRes, endRes, offset + ((i - startSeq) * charHeight));
908 if (av.isShowSequenceFeatures())
910 fr.drawSequence(g, nextSeq, startRes, endRes, offset
911 + ((i - startSeq) * charHeight), false);
915 * highlight search Results once sequence has been drawn
917 if (av.hasSearchResults())
919 SearchResultsI searchResults = av.getSearchResults();
920 int[] visibleResults = searchResults.getResults(nextSeq,
922 if (visibleResults != null)
924 for (int r = 0; r < visibleResults.length; r += 2)
926 sr.drawHighlightedText(nextSeq, visibleResults[r],
927 visibleResults[r + 1], (visibleResults[r] - startRes)
929 + ((i - startSeq) * charHeight));
934 if (av.cursorMode && cursorY == i && cursorX >= startRes
935 && cursorX <= endRes)
937 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
938 offset + ((i - startSeq) * charHeight));
942 if (av.getSelectionGroup() != null
943 || av.getAlignment().getGroups().size() > 0)
945 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
950 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
951 int startSeq, int endSeq, int offset)
953 Graphics2D g = (Graphics2D) g1;
955 // ///////////////////////////////////
956 // Now outline any areas if necessary
957 // ///////////////////////////////////
958 SequenceGroup group = av.getSelectionGroup();
964 int visWidth = (endRes - startRes + 1) * charWidth;
966 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
968 group = av.getAlignment().getGroups().get(0);
978 boolean inGroup = false;
982 for (i = startSeq; i <= endSeq; i++)
984 sx = (group.getStartRes() - startRes) * charWidth;
985 sy = offset + ((i - startSeq) * charHeight);
986 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
988 if (sx + ex < 0 || sx > visWidth)
993 if ((sx <= (endRes - startRes) * charWidth)
994 && group.getSequences(null).contains(
995 av.getAlignment().getSequenceAt(i)))
998 && !group.getSequences(null).contains(
999 av.getAlignment().getSequenceAt(i + 1)))
1001 bottom = sy + charHeight;
1006 if (((top == -1) && (i == 0))
1007 || !group.getSequences(null).contains(
1008 av.getAlignment().getSequenceAt(i - 1)))
1016 if (group == av.getSelectionGroup())
1018 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1019 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
1021 g.setColor(Color.RED);
1025 g.setStroke(new BasicStroke());
1026 g.setColor(group.getOutlineColour());
1034 if (sx >= 0 && sx < visWidth)
1036 g.drawLine(sx, oldY, sx, sy);
1039 if (sx + ex < visWidth)
1041 g.drawLine(sx + ex, oldY, sx + ex, sy);
1050 if (sx + ex > visWidth)
1055 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1057 ex = (endRes - startRes + 1) * charWidth;
1062 g.drawLine(sx, top, sx + ex, top);
1068 g.drawLine(sx, bottom, sx + ex, bottom);
1079 sy = offset + ((i - startSeq) * charHeight);
1080 if (sx >= 0 && sx < visWidth)
1082 g.drawLine(sx, oldY, sx, sy);
1085 if (sx + ex < visWidth)
1087 g.drawLine(sx + ex, oldY, sx + ex, sy);
1096 if (sx + ex > visWidth)
1100 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1102 ex = (endRes - startRes + 1) * charWidth;
1107 g.drawLine(sx, top, sx + ex, top);
1113 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1122 g.setStroke(new BasicStroke());
1124 if (groupIndex >= av.getAlignment().getGroups().size())
1129 group = av.getAlignment().getGroups().get(groupIndex);
1131 } while (groupIndex < av.getAlignment().getGroups().size());
1138 * Highlights search results in the visible region by rendering as white text
1139 * on a black background. Any previous highlighting is removed. Answers true
1140 * if any highlight was left on the visible alignment (so status bar should be
1141 * set to match), else false.
1143 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1144 * alignment had to be scrolled to show the highlighted region, then it should
1145 * be fully redrawn, otherwise a fast paint can be performed. This argument
1146 * could be removed if fast paint of scrolled wrapped alignment is coded in
1147 * future (JAL-2609).
1150 * @param noFastPaint
1153 public boolean highlightSearchResults(SearchResultsI results,
1154 boolean noFastPaint)
1160 boolean wrapped = av.getWrapAlignment();
1164 fastPaint = !noFastPaint;
1165 fastpainting = fastPaint;
1170 * to avoid redrawing the whole visible region, we instead
1171 * redraw just the minimal regions to remove previous highlights
1174 SearchResultsI previous = av.getSearchResults();
1175 av.setSearchResults(results);
1176 boolean redrawn = false;
1177 boolean drawn = false;
1180 redrawn = drawMappedPositionsWrapped(previous);
1181 drawn = drawMappedPositionsWrapped(results);
1186 redrawn = drawMappedPositions(previous);
1187 drawn = drawMappedPositions(results);
1192 * if highlights were either removed or added, repaint
1200 * return true only if highlights were added
1206 fastpainting = false;
1211 * Redraws the minimal rectangle in the visible region (if any) that includes
1212 * mapped positions of the given search results. Whether or not positions are
1213 * highlighted depends on the SearchResults set on the Viewport. This allows
1214 * this method to be called to either clear or set highlighting. Answers true
1215 * if any positions were drawn (in which case a repaint is still required),
1221 protected boolean drawMappedPositions(SearchResultsI results)
1223 if (results == null)
1229 * calculate the minimal rectangle to redraw that
1230 * includes both new and existing search results
1232 int firstSeq = Integer.MAX_VALUE;
1234 int firstCol = Integer.MAX_VALUE;
1236 boolean matchFound = false;
1238 ViewportRanges ranges = av.getRanges();
1239 int firstVisibleColumn = ranges.getStartRes();
1240 int lastVisibleColumn = ranges.getEndRes();
1241 AlignmentI alignment = av.getAlignment();
1242 if (av.hasHiddenColumns())
1244 firstVisibleColumn = alignment.getHiddenColumns()
1245 .adjustForHiddenColumns(firstVisibleColumn);
1246 lastVisibleColumn = alignment.getHiddenColumns()
1247 .adjustForHiddenColumns(lastVisibleColumn);
1250 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1251 .getEndSeq(); seqNo++)
1253 SequenceI seq = alignment.getSequenceAt(seqNo);
1255 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1257 if (visibleResults != null)
1259 for (int i = 0; i < visibleResults.length - 1; i += 2)
1261 int firstMatchedColumn = visibleResults[i];
1262 int lastMatchedColumn = visibleResults[i + 1];
1263 if (firstMatchedColumn <= lastVisibleColumn
1264 && lastMatchedColumn >= firstVisibleColumn)
1267 * found a search results match in the visible region -
1268 * remember the first and last sequence matched, and the first
1269 * and last visible columns in the matched positions
1272 firstSeq = Math.min(firstSeq, seqNo);
1273 lastSeq = Math.max(lastSeq, seqNo);
1274 firstMatchedColumn = Math.max(firstMatchedColumn,
1275 firstVisibleColumn);
1276 lastMatchedColumn = Math.min(lastMatchedColumn,
1278 firstCol = Math.min(firstCol, firstMatchedColumn);
1279 lastCol = Math.max(lastCol, lastMatchedColumn);
1287 if (av.hasHiddenColumns())
1289 firstCol = alignment.getHiddenColumns()
1290 .findColumnPosition(firstCol);
1291 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1293 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1294 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1295 gg.translate(transX, transY);
1296 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1297 gg.translate(-transX, -transY);
1304 public void propertyChange(PropertyChangeEvent evt)
1306 String eventName = evt.getPropertyName();
1309 if (eventName.equals(ViewportRanges.STARTRES))
1311 // Make sure we're not trying to draw a panel
1312 // larger than the visible window
1313 ViewportRanges vpRanges = av.getRanges();
1314 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1315 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1316 if (scrollX > range)
1320 else if (scrollX < -range)
1326 // Both scrolling and resizing change viewport ranges: scrolling changes
1327 // both start and end points, but resize only changes end values.
1328 // Here we only want to fastpaint on a scroll, with resize using a normal
1329 // paint, so scroll events are identified as changes to the horizontal or
1330 // vertical start value.
1331 if (eventName.equals(ViewportRanges.STARTRES))
1333 // scroll - startres and endres both change
1334 if (av.getWrapAlignment())
1336 fastPaintWrapped(scrollX);
1340 fastPaint(scrollX, 0);
1343 else if (eventName.equals(ViewportRanges.STARTSEQ))
1345 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1350 * Does a minimal update of the image for a scroll movement. This method
1351 * handles scroll movements of up to one width of the wrapped alignment (one
1352 * click in the vertical scrollbar). Larger movements (for example after a
1353 * scroll to highlight a mapped position) trigger a full redraw instead.
1356 * number of positions scrolled (right if positive, left if negative)
1358 protected void fastPaintWrapped(int scrollX)
1360 ViewportRanges ranges = av.getRanges();
1362 if (Math.abs(scrollX) > ranges.getViewportWidth())
1365 * shift of more than one view width is
1366 * overcomplicated to handle in this method
1373 if (fastpainting || gg == null)
1379 fastpainting = true;
1383 calculateWrappedGeometry(getWidth(), getHeight());
1386 * relocate the regions of the alignment that are still visible
1388 shiftWrappedAlignment(-scrollX);
1391 * add new columns (sequence, annotation)
1392 * - at top left if scrollX < 0
1393 * - at right of last two widths if scrollX > 0
1397 int startRes = ranges.getStartRes();
1398 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1399 - scrollX - 1, getHeight());
1403 fastPaintWrappedAddRight(scrollX);
1407 * draw all scales (if shown) and hidden column markers
1409 drawWrappedDecorators(gg, getHeight(), ranges.getStartRes());
1414 fastpainting = false;
1419 * Draws the specified number of columns at the 'end' (bottom right) of a
1420 * wrapped alignment view, including sequences and annotations if shown, but
1421 * not scales. Also draws the same number of columns at the right hand end of
1422 * the second last width shown, if the last width is not full height (so
1423 * cannot simply be copied from the graphics image).
1427 protected void fastPaintWrappedAddRight(int columns)
1434 ViewportRanges ranges = av.getRanges();
1435 int viewportWidth = ranges.getViewportWidth();
1438 * draw full height alignment in the second last row, last columns, if the
1439 * last row was not full height
1441 int visibleWidths = wrappedVisibleWidths;
1442 int canvasHeight = getHeight();
1443 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1445 if (lastWidthPartHeight)
1447 int widthsAbove = visibleWidths - 2;
1448 int ypos = wrappedRepeatHeightPx * widthsAbove
1449 + wrappedSpaceAboveAlignment;
1450 int endRes = ranges.getEndRes();
1451 endRes += widthsAbove * viewportWidth;
1452 int startRes = endRes - columns;
1453 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1457 * white fill first to erase annotations
1459 gg.translate(xOffset, 0);
1460 gg.setColor(Color.white);
1461 gg.fillRect(labelWidthWest, ypos,
1462 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1463 gg.translate(-xOffset, 0);
1465 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1469 * y-offset for drawing last width is height of widths above,
1472 int widthsAbove = visibleWidths - 1;
1473 int ypos = wrappedRepeatHeightPx * widthsAbove
1474 + wrappedSpaceAboveAlignment;
1475 int endRes = ranges.getEndRes();
1476 endRes += widthsAbove * viewportWidth;
1477 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1478 int startRes = endRes - columns + 1;
1481 * white fill first to erase annotations
1483 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1485 gg.translate(xOffset, 0);
1486 gg.setColor(Color.white);
1487 int width = viewportWidth * charWidth - xOffset;
1488 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1489 gg.translate(-xOffset, 0);
1491 gg.setFont(av.getFont());
1492 gg.setColor(Color.black);
1494 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1497 * and finally, white fill any space below the visible alignment
1499 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1500 if (heightBelow > 0)
1502 gg.setColor(Color.white);
1503 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1508 * Shifts the visible alignment by the specified number of columns - left if
1509 * negative, right if positive. Copies and moves sequences and annotations (if
1510 * shown). Scales, hidden column markers and any newly visible columns must be
1515 protected void shiftWrappedAlignment(int positions)
1522 int canvasHeight = getHeight();
1523 ViewportRanges ranges = av.getRanges();
1524 int viewportWidth = ranges.getViewportWidth();
1525 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1527 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1528 int xMax = ranges.getVisibleAlignmentWidth();
1533 * shift right (after scroll left)
1534 * for each wrapped width (starting with the last), copy (width-positions)
1535 * columns from the left margin to the right margin, and copy positions
1536 * columns from the right margin of the row above (if any) to the
1537 * left margin of the current row
1541 * get y-offset of last wrapped width, first row of sequences
1543 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1544 y += wrappedSpaceAboveAlignment;
1545 int copyFromLeftStart = labelWidthWest;
1546 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1550 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1551 positions * charWidth, 0);
1554 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1555 positions * charWidth, heightToCopy, -widthToCopy,
1556 wrappedRepeatHeightPx);
1559 y -= wrappedRepeatHeightPx;
1565 * shift left (after scroll right)
1566 * for each wrapped width (starting with the first), copy (width-positions)
1567 * columns from the right margin to the left margin, and copy positions
1568 * columns from the left margin of the row below (if any) to the
1569 * right margin of the current row
1571 int xpos = av.getRanges().getStartRes();
1572 int y = wrappedSpaceAboveAlignment;
1573 int copyFromRightStart = labelWidthWest - positions * charWidth;
1575 while (y < canvasHeight)
1577 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1578 positions * charWidth, 0);
1579 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1580 && (xpos + viewportWidth <= xMax))
1582 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1583 * charWidth, heightToCopy, widthToCopy,
1584 -wrappedRepeatHeightPx);
1587 y += wrappedRepeatHeightPx;
1588 xpos += viewportWidth;
1594 * Redraws any positions in the search results in the visible region of a
1595 * wrapped alignment. Any highlights are drawn depending on the search results
1596 * set on the Viewport, not the <code>results</code> argument. This allows
1597 * this method to be called either to clear highlights (passing the previous
1598 * search results), or to draw new highlights.
1603 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1605 if (results == null)
1610 boolean matchFound = false;
1612 int wrappedWidth = av.getWrappedWidth();
1613 int wrappedHeight = getRepeatHeightWrapped();
1615 ViewportRanges ranges = av.getRanges();
1616 int canvasHeight = getHeight();
1617 int repeats = canvasHeight / wrappedHeight;
1618 if (canvasHeight / wrappedHeight > 0)
1623 int firstVisibleColumn = ranges.getStartRes();
1624 int lastVisibleColumn = ranges.getStartRes() + repeats
1625 * ranges.getViewportWidth() - 1;
1627 AlignmentI alignment = av.getAlignment();
1628 if (av.hasHiddenColumns())
1630 firstVisibleColumn = alignment.getHiddenColumns()
1631 .adjustForHiddenColumns(firstVisibleColumn);
1632 lastVisibleColumn = alignment.getHiddenColumns()
1633 .adjustForHiddenColumns(lastVisibleColumn);
1636 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1638 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1639 .getEndSeq(); seqNo++)
1641 SequenceI seq = alignment.getSequenceAt(seqNo);
1643 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1645 if (visibleResults != null)
1647 for (int i = 0; i < visibleResults.length - 1; i += 2)
1649 int firstMatchedColumn = visibleResults[i];
1650 int lastMatchedColumn = visibleResults[i + 1];
1651 if (firstMatchedColumn <= lastVisibleColumn
1652 && lastMatchedColumn >= firstVisibleColumn)
1655 * found a search results match in the visible region
1657 firstMatchedColumn = Math.max(firstMatchedColumn,
1658 firstVisibleColumn);
1659 lastMatchedColumn = Math.min(lastMatchedColumn,
1663 * draw each mapped position separately (as contiguous positions may
1664 * wrap across lines)
1666 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1668 int displayColumn = mappedPos;
1669 if (av.hasHiddenColumns())
1671 displayColumn = alignment.getHiddenColumns()
1672 .findColumnPosition(displayColumn);
1676 * transX: offset from left edge of canvas to residue position
1678 int transX = labelWidthWest
1679 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1680 * av.getCharWidth();
1683 * transY: offset from top edge of canvas to residue position
1685 int transY = gapHeight;
1686 transY += (displayColumn - ranges.getStartRes())
1687 / wrappedWidth * wrappedHeight;
1688 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1691 * yOffset is from graphics origin to start of visible region
1693 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1694 if (transY < getHeight())
1697 gg.translate(transX, transY);
1698 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1700 gg.translate(-transX, -transY);
1712 * Answers the height in pixels of a repeating section of the wrapped
1713 * alignment, including space above, scale above if shown, sequences, and
1714 * annotation panel if shown
1718 protected int getRepeatHeightWrapped()
1720 // gap (and maybe scale) above
1721 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1724 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1726 // add annotations panel height if shown
1727 repeatHeight += getAnnotationHeight();
1729 return repeatHeight;