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 java.awt.BasicStroke;
24 import java.awt.BorderLayout;
25 import java.awt.Color;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.Rectangle;
30 import java.awt.RenderingHints;
31 import java.awt.image.BufferedImage;
32 import java.beans.PropertyChangeEvent;
33 import java.util.Iterator;
34 import java.util.List;
36 import javax.swing.JPanel;
38 import jalview.datamodel.AlignmentI;
39 import jalview.datamodel.HiddenColumns;
40 import jalview.datamodel.SearchResultsI;
41 import jalview.datamodel.SequenceGroup;
42 import jalview.datamodel.SequenceI;
43 import jalview.datamodel.VisibleContigsIterator;
44 import jalview.renderer.ScaleRenderer;
45 import jalview.renderer.ScaleRenderer.ScaleMark;
46 import jalview.util.Comparison;
47 import jalview.viewmodel.ViewportListenerI;
48 import jalview.viewmodel.ViewportRanges;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 @SuppressWarnings("serial")
57 public class SeqCanvas extends JPanel implements ViewportListenerI
60 * vertical gap in pixels between sequences and annotations when in wrapped
63 static final int SEQS_ANNOTATION_GAP = 3;
65 private static final String ZEROS = "0000000000";
67 final FeatureRenderer fr;
77 private final SequenceRenderer seqRdr;
79 boolean fastPaint = false;
81 private boolean fastpainting = false;
83 private AnnotationPanel annotations;
86 * measurements for drawing a wrapped alignment
88 private int labelWidthEast; // label right width in pixels if shown
90 private int labelWidthWest; // label left width in pixels if shown
92 int wrappedSpaceAboveAlignment; // gap between widths
94 int wrappedRepeatHeightPx; // height in pixels of wrapped width
96 private int wrappedVisibleWidths; // number of wrapped widths displayed
98 // Don't do this! Graphics handles are supposed to be transient
99 // private Graphics2D gg;
102 * Creates a new SeqCanvas object.
106 public SeqCanvas(AlignmentPanel ap)
109 fr = new FeatureRenderer(ap);
110 seqRdr = 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()
129 * Draws the scale above a region of a wrapped alignment, consisting of a
130 * column number every major interval (10 columns).
133 * the graphics context to draw on, positioned at the start (bottom
134 * left) of the line on which to draw any scale marks
136 * start alignment column (0..)
138 * end alignment column (0..)
140 * y offset to draw at
142 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
144 int charHeight = av.getCharHeight();
145 int charWidth = av.getCharWidth();
148 * white fill the scale space (for the fastPaint case)
150 g.setColor(Color.white);
151 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
152 charHeight * 3 / 2 + 2);
153 g.setColor(Color.black);
155 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
157 for (ScaleMark mark : marks)
159 int mpos = mark.column; // (i - startx - 1)
164 String mstring = mark.text;
170 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
174 * draw a tick mark below the column number, centred on the column;
175 * height of tick mark is 4 pixels less than half a character
177 int xpos = (mpos * charWidth) + (charWidth / 2);
178 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
184 * Draw the scale to the left or right of a wrapped alignment
187 * graphics context, positioned at the start of the scale to be drawn
189 * first column of wrapped width (0.. excluding any hidden columns)
191 * last column of wrapped width (0.. excluding any hidden columns)
193 * vertical offset at which to begin the scale
195 * if true, scale is left of residues, if false, scale is right
197 void drawVerticalScale(Graphics g, final int startx, final int endx,
198 final int ypos, final boolean left)
200 int charHeight = av.getCharHeight();
201 int charWidth = av.getCharWidth();
203 int yPos = ypos + charHeight;
207 if (av.hasHiddenColumns())
209 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
210 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
211 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
213 FontMetrics fm = getFontMetrics(av.getFont());
215 for (int i = 0; i < av.getAlignment().getHeight(); i++)
217 SequenceI seq = av.getAlignment().getSequenceAt(i);
220 * find sequence position of first non-gapped position -
221 * to the right if scale left, to the left if scale right
223 int index = left ? startX : endX;
225 while (index >= startX && index <= endX)
227 if (!Comparison.isGap(seq.getCharAt(index)))
229 value = seq.findPosition(index);
243 * white fill the space for the scale
245 g.setColor(Color.white);
246 int y = (yPos + (i * charHeight)) - (charHeight / 5);
247 // fillRect origin is top left of rectangle
248 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
254 * draw scale value, right justified within its width less half a
255 * character width padding on the right
257 int labelSpace = left ? labelWidthWest : labelWidthEast;
258 labelSpace -= charWidth / 2; // leave space to the right
259 String valueAsString = String.valueOf(value);
260 int labelLength = fm.stringWidth(valueAsString);
261 int xOffset = labelSpace - labelLength;
262 g.setColor(Color.black);
263 g.drawString(valueAsString, xOffset, y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
280 * <li>pasting a block of sequences</li>
284 * columns to shift right (positive) or left (negative)
286 * rows to shift down (positive) or up (negative)
288 public void fastPaint(int horizontal, int vertical)
292 // if (horizontal != 0 && vertical != 0)
293 // throw new InvalidArgumentException();
294 if (fastpainting || img == null)
302 int charHeight = av.getCharHeight();
303 int charWidth = av.getCharWidth();
305 ViewportRanges ranges = av.getRanges();
306 int startRes = ranges.getStartRes();
307 int endRes = ranges.getEndRes();
308 int startSeq = ranges.getStartSeq();
309 int endSeq = ranges.getEndSeq();
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;
323 if (vertical > 0) // scroll down
325 startSeq = endSeq - vertical;
327 if (startSeq < ranges.getStartSeq())
328 { // ie scrolling too fast, more than a page at a time
329 startSeq = ranges.getStartSeq();
333 transY = img.getHeight() - ((vertical + 1) * charHeight);
336 else if (vertical < 0)
338 endSeq = startSeq - vertical;
340 if (endSeq > ranges.getEndSeq())
342 endSeq = ranges.getEndSeq();
346 // jalview.bin.Console.errPrintln(">>> FastPaint to " + transX + " " + transY + " "
347 // + horizontal + " " + vertical + " " + startRes + " " + endRes
348 // + " " + startSeq + " " + endSeq);
350 Graphics gg = img.getGraphics();
351 gg.copyArea(horizontal * charWidth, vertical * charHeight,
352 img.getWidth(), img.getHeight(), -horizontal * charWidth,
353 -vertical * charHeight);
355 /** @j2sNative xxi = this.img */
357 gg.translate(transX, transY);
358 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
359 gg.translate(-transX, -transY);
362 // Call repaint on alignment panel so that repaints from other alignment
363 // panel components can be aggregated. Otherwise performance of the
364 // overview window and others may be adversely affected.
365 // jalview.bin.Console.outPrintln("SeqCanvas fastPaint() repaint() request...");
366 av.getAlignPanel().repaint();
369 fastpainting = false;
374 public void paintComponent(Graphics g)
377 int charHeight = av.getCharHeight();
378 int charWidth = av.getCharWidth();
380 int width = getWidth();
381 int height = getHeight();
383 width -= (width % charWidth);
384 height -= (height % charHeight);
386 // BH 2019 can't possibly fastPaint if either width or height is 0
388 if (width == 0 || height == 0)
393 ViewportRanges ranges = av.getRanges();
394 int startRes = ranges.getStartRes();
395 int startSeq = ranges.getStartSeq();
396 int endRes = ranges.getEndRes();
397 int endSeq = ranges.getEndSeq();
399 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
400 // repaint() requests in unpredictable ways. In this case, the issue was
401 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
402 // repaint request preceded two full requests, thus resulting
403 // in a full request for paint. In constrast, in JavaScript, the three
404 // requests were bundled together into one, so the fastPaint flag was
405 // still present for the second and third request.
407 // This resulted in incomplete painting.
409 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
410 // in PaintRefresher when the target to be painted is one of those two
415 // An initial idea; can be removed once we determine this issue is closed:
416 // if (av.isFastPaintDisabled())
418 // fastPaint = false;
424 || (vis = getVisibleRect()).width != (clip = g
425 .getClipBounds()).width
426 || vis.height != clip.height))
428 g.drawImage(img, 0, 0, this);
429 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
435 // img is a cached version of the last view we drew.
436 // If we have no img or the size has changed, make a new one.
438 if (img == null || width != img.getWidth()
439 || height != img.getHeight())
441 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
444 Graphics2D gg = (Graphics2D) img.getGraphics();
445 gg.setFont(av.getFont());
449 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
450 RenderingHints.VALUE_ANTIALIAS_ON);
453 gg.setColor(Color.white);
454 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
456 if (av.getWrapAlignment())
458 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
462 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
465 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
467 g.drawImage(img, 0, 0, this);
473 drawCursor(g, startRes, endRes, startSeq, endSeq);
478 * Draw an alignment panel for printing
481 * Graphics object to draw with
483 * start residue of print area
485 * end residue of print area
487 * start sequence of print area
489 * end sequence of print area
491 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
492 int startSeq, int endSeq)
494 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
496 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
500 * Draw a wrapped alignment panel for printing
503 * Graphics object to draw with
505 * width of drawing area
506 * @param canvasHeight
507 * height of drawing area
509 * start residue of print area
511 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
512 int canvasHeight, int startRes)
514 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
516 SequenceGroup group = av.getSelectionGroup();
519 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
525 * Returns the visible width of the canvas in residues, after allowing for
526 * East or West scales (if shown)
529 * the width in pixels (possibly including scales)
533 public int getWrappedCanvasWidth(int canvasWidth)
535 int charWidth = av.getCharWidth();
537 FontMetrics fm = getFontMetrics(av.getFont());
541 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
543 labelWidth = getLabelWidth(fm);
546 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
548 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
550 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
553 public int getMinimumWrappedCanvasWidth()
555 int charWidth = av.getCharWidth();
556 FontMetrics fm = getFontMetrics(av.getFont());
558 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
560 labelWidth = getLabelWidth(fm);
562 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
563 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
564 return labelWidthEast + labelWidthWest + charWidth;
568 * Returns a pixel width sufficient to show the largest sequence coordinate
569 * (end position) in the alignment, calculated as the FontMetrics width of
570 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
571 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
572 * half a character width space on either side.
577 protected int getLabelWidth(FontMetrics fm)
580 * find the biggest sequence end position we need to show
581 * (note this is not necessarily the sequence length)
584 AlignmentI alignment = av.getAlignment();
585 for (int i = 0; i < alignment.getHeight(); i++)
587 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
591 for (int i = maxWidth; i > 0; i /= 10)
596 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
600 * Draws as many widths of a wrapped alignment as can fit in the visible
605 * available width in pixels
606 * @param canvasHeight
607 * available height in pixels
609 * the first column (0...) of the alignment to draw
611 public void drawWrappedPanel(Graphics g, int canvasWidth,
612 int canvasHeight, final int startColumn)
614 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
617 av.setWrappedWidth(wrappedWidthInResidues);
619 ViewportRanges ranges = av.getRanges();
620 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
622 // we need to call this again to make sure the startColumn +
623 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
625 calculateWrappedGeometry(canvasWidth, canvasHeight);
628 * draw one width at a time (excluding any scales shown),
629 * until we have run out of either alignment or vertical space available
631 int ypos = wrappedSpaceAboveAlignment;
632 int maxWidth = ranges.getVisibleAlignmentWidth();
634 int start = startColumn;
635 int currentWidth = 0;
636 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
638 int endColumn = Math.min(maxWidth,
639 start + wrappedWidthInResidues - 1);
640 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
641 ypos += wrappedRepeatHeightPx;
642 start += wrappedWidthInResidues;
646 drawWrappedDecorators(g, startColumn);
650 * Calculates and saves values needed when rendering a wrapped alignment.
651 * These depend on many factors, including
653 * <li>canvas width and height</li>
654 * <li>number of visible sequences, and height of annotations if shown</li>
655 * <li>font and character width</li>
656 * <li>whether scales are shown left, right or above the alignment</li>
660 * @param canvasHeight
661 * @return the number of residue columns in each width
663 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
665 int charHeight = av.getCharHeight();
668 * vertical space in pixels between wrapped widths of alignment
669 * - one character height, or two if scale above is drawn
671 wrappedSpaceAboveAlignment = charHeight
672 * (av.getScaleAboveWrapped() ? 2 : 1);
675 * compute height in pixels of the wrapped widths
676 * - start with space above plus sequences
678 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
679 wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
682 * add annotations panel height if shown
683 * also gap between sequences and annotations
685 if (av.isShowAnnotation())
687 wrappedRepeatHeightPx += getAnnotationHeight();
688 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
692 * number of visible widths (the last one may be part height),
693 * ensuring a part height includes at least one sequence
695 ViewportRanges ranges = av.getRanges();
696 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
697 int remainder = canvasHeight % wrappedRepeatHeightPx;
698 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
700 wrappedVisibleWidths++;
704 * compute width in residues; this also sets East and West label widths
706 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
707 av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
709 * limit visibleWidths to not exceed width of alignment
711 int xMax = ranges.getVisibleAlignmentWidth();
712 int startToEnd = xMax - ranges.getStartRes();
713 int maxWidths = startToEnd / wrappedWidthInResidues;
714 if (startToEnd % wrappedWidthInResidues > 0)
718 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
720 return wrappedWidthInResidues;
724 * Draws one width of a wrapped alignment, including sequences and
725 * annnotations, if shown, but not scales or hidden column markers
731 * @param canvasHeight
733 protected void drawWrappedWidth(Graphics g, final int ypos,
734 final int startColumn, final int endColumn,
735 final int canvasHeight)
737 ViewportRanges ranges = av.getRanges();
738 int viewportWidth = ranges.getViewportWidth();
740 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
743 * move right before drawing by the width of the scale left (if any)
744 * plus column offset from left margin (usually zero, but may be non-zero
745 * when fast painting is drawing just a few columns)
747 int charWidth = av.getCharWidth();
748 int xOffset = labelWidthWest
749 + ((startColumn - ranges.getStartRes()) % viewportWidth)
752 g.translate(xOffset, 0);
755 * white fill the region to be drawn (so incremental fast paint doesn't
756 * scribble over an existing image)
758 g.setColor(Color.white);
759 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
760 wrappedRepeatHeightPx);
762 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
765 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
767 if (av.isShowAnnotation())
769 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
770 g.translate(0, yShift);
771 if (annotations == null)
773 annotations = new AnnotationPanel(av);
776 annotations.renderer.drawComponent(annotations, av, g, -1,
777 startColumn, endx + 1);
778 g.translate(0, -yShift);
780 g.translate(-xOffset, 0);
784 * Draws scales left, right and above (if shown), and any hidden column
785 * markers, on all widths of the wrapped alignment
790 protected void drawWrappedDecorators(Graphics g, final int startColumn)
792 int charWidth = av.getCharWidth();
794 g.setFont(av.getFont());
796 g.setColor(Color.black);
798 int ypos = wrappedSpaceAboveAlignment;
799 ViewportRanges ranges = av.getRanges();
800 int viewportWidth = ranges.getViewportWidth();
801 int maxWidth = ranges.getVisibleAlignmentWidth();
803 int startCol = startColumn;
805 while (widthsDrawn < wrappedVisibleWidths)
807 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
809 if (av.getScaleLeftWrapped())
811 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
814 if (av.getScaleRightWrapped())
816 int x = labelWidthWest + viewportWidth * charWidth;
819 drawVerticalScale(g, startCol, endColumn, ypos, false);
824 * white fill region of scale above and hidden column markers
825 * (to support incremental fast paint of image)
827 g.translate(labelWidthWest, 0);
828 g.setColor(Color.white);
829 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
830 viewportWidth * charWidth + labelWidthWest,
831 wrappedSpaceAboveAlignment);
832 g.setColor(Color.black);
833 g.translate(-labelWidthWest, 0);
835 g.translate(labelWidthWest, 0);
837 if (av.getScaleAboveWrapped())
839 drawNorthScale(g, startCol, endColumn, ypos);
842 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
844 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
847 g.translate(-labelWidthWest, 0);
849 ypos += wrappedRepeatHeightPx;
850 startCol += viewportWidth;
856 * Draws markers (triangles) above hidden column positions between startColumn
864 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
865 int startColumn, int endColumn)
867 int charHeight = av.getCharHeight();
868 int charWidth = av.getCharWidth();
870 g.setColor(Color.blue);
872 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
874 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
878 res = it.next() - startColumn;
880 if (res < 0 || res > endColumn - startColumn + 1)
886 * draw a downward-pointing triangle at the hidden columns location
887 * (before the following visible column)
889 int xMiddle = res * charWidth;
890 int[] xPoints = new int[] { xMiddle - charHeight / 4,
891 xMiddle + charHeight / 4, xMiddle };
892 int yTop = ypos - (charHeight / 2);
893 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
894 g.fillPolygon(xPoints, yPoints, 3);
899 * Draw a selection group over a wrapped alignment
901 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
902 int canvasWidth, int canvasHeight, int startRes)
904 // chop the wrapped alignment extent up into panel-sized blocks and treat
905 // each block as if it were a block from an unwrapped alignment
906 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
907 BasicStroke.JOIN_ROUND, 3f, new float[]
909 g.setColor(Color.RED);
911 int charWidth = av.getCharWidth();
912 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
914 int startx = startRes;
915 int maxwidth = av.getAlignment().getVisibleWidth();
916 int ypos = wrappedSpaceAboveAlignment;
918 while ((ypos <= canvasHeight) && (startx < maxwidth))
920 // set end value to be start + width, or maxwidth, whichever is smaller
921 int endx = startx + cWidth - 1;
928 g.translate(labelWidthWest, 0);
929 drawUnwrappedSelection(g, group, startx, endx, 0,
930 av.getAlignment().getHeight() - 1, ypos);
931 g.translate(-labelWidthWest, 0);
933 ypos += wrappedRepeatHeightPx;
937 g.setStroke(new BasicStroke());
941 * Answers zero if annotations are not shown, otherwise recalculates and
942 * answers the total height of all annotation rows in pixels
946 int getAnnotationHeight()
948 if (!av.isShowAnnotation())
953 if (annotations == null)
955 annotations = new AnnotationPanel(av);
958 return annotations.adjustPanelHeight();
962 * Draws the visible region of the alignment on the graphics context. If there
963 * are hidden column markers in the visible region, then each sub-region
964 * between the markers is drawn separately, followed by the hidden column
968 * the graphics context, positioned at the first residue to be drawn
970 * offset of the first column to draw (0..)
972 * offset of the last column to draw (0..)
974 * offset of the first sequence to draw (0..)
976 * offset of the last sequence to draw (0..)
978 * vertical offset at which to draw (for wrapped alignments)
980 public void drawPanel(Graphics g1, final int startRes, final int endRes,
981 final int startSeq, final int endSeq, final int yOffset)
983 int charHeight = av.getCharHeight();
984 int charWidth = av.getCharWidth();
986 if (!av.hasHiddenColumns())
988 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
996 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
997 VisibleContigsIterator regions = hidden
998 .getVisContigsIterator(startRes, endRes + 1, true);
1000 while (regions.hasNext())
1002 int[] region = regions.next();
1003 blockEnd = region[1];
1004 blockStart = region[0];
1007 * draw up to just before the next hidden region, or the end of
1008 * the visible region, whichever comes first
1010 g1.translate(screenY * charWidth, 0);
1012 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1015 * draw the downline of the hidden column marker (ScalePanel draws the
1016 * triangle on top) if we reached it
1018 if (av.getShowHiddenMarkers()
1019 && (regions.hasNext() || regions.endsAtHidden()))
1021 g1.setColor(Color.blue);
1023 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1024 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1025 (endSeq - startSeq + 1) * charHeight + yOffset);
1028 g1.translate(-screenY * charWidth, 0);
1029 screenY += blockEnd - blockStart + 1;
1036 * Draws a region of the visible alignment
1040 * offset of the first column in the visible region (0..)
1042 * offset of the last column in the visible region (0..)
1044 * offset of the first sequence in the visible region (0..)
1046 * offset of the last sequence in the visible region (0..)
1048 * vertical offset at which to draw (for wrapped alignments)
1050 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1051 int endSeq, int offset)
1053 int charHeight = av.getCharHeight();
1054 int charWidth = av.getCharWidth();
1056 g.setFont(av.getFont());
1057 seqRdr.prepare(g, av.isRenderGaps());
1061 // / First draw the sequences
1062 // ///////////////////////////
1063 for (int i = startSeq; i <= endSeq; i++)
1065 nextSeq = av.getAlignment().getSequenceAt(i);
1066 if (nextSeq == null)
1068 // occasionally, a race condition occurs such that the alignment row is
1072 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1073 startRes, endRes, offset + ((i - startSeq) * charHeight));
1075 if (av.isShowSequenceFeatures())
1077 fr.drawSequence(g, nextSeq, startRes, endRes,
1078 offset + ((i - startSeq) * charHeight), false);
1082 * highlight search Results once sequence has been drawn
1084 if (av.hasSearchResults())
1086 SearchResultsI searchResults = av.getSearchResults();
1087 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1089 if (visibleResults != null)
1091 for (int r = 0; r < visibleResults.length; r += 2)
1093 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1094 visibleResults[r + 1],
1095 (visibleResults[r] - startRes) * charWidth,
1096 offset + ((i - startSeq) * charHeight));
1102 if (av.getSelectionGroup() != null
1103 || av.getAlignment().getGroups().size() > 0)
1105 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1111 * Draws the outlines of any groups defined on the alignment (excluding the
1112 * current selection group, if any)
1121 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1122 int startSeq, int endSeq, int offset)
1124 Graphics2D g = (Graphics2D) g1;
1126 SequenceGroup group = null;
1127 int groupIndex = -1;
1129 if (av.getAlignment().getGroups().size() > 0)
1131 group = av.getAlignment().getGroups().get(0);
1139 g.setColor(group.getOutlineColour());
1140 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1144 if (groupIndex >= av.getAlignment().getGroups().size())
1148 group = av.getAlignment().getGroups().get(groupIndex);
1149 } while (groupIndex < av.getAlignment().getGroups().size());
1154 * Draws the outline of the current selection group (if any)
1162 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1163 int startSeq, int endSeq)
1165 SequenceGroup group = av.getSelectionGroup();
1171 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1172 BasicStroke.JOIN_ROUND, 3f, new float[]
1174 g.setColor(Color.RED);
1175 if (!av.getWrapAlignment())
1177 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1182 drawWrappedSelection(g, group, getWidth(), getHeight(),
1183 av.getRanges().getStartRes());
1185 g.setStroke(new BasicStroke());
1189 * Draw the cursor as a separate image and overlay
1192 * start residue of area to draw cursor in
1194 * end residue of area to draw cursor in
1196 * start sequence of area to draw cursor in
1198 * end sequence of are to draw cursor in
1199 * @return a transparent image of the same size as the sequence canvas, with
1200 * the cursor drawn on it, if any
1202 private void drawCursor(Graphics g, int startRes, int endRes,
1203 int startSeq, int endSeq)
1205 // convert the cursorY into a position on the visible alignment
1206 int cursor_ypos = cursorY;
1208 // don't do work unless we have to
1209 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1213 int startx = startRes;
1216 // convert the cursorX into a position on the visible alignment
1217 int cursor_xpos = av.getAlignment().getHiddenColumns()
1218 .absoluteToVisibleColumn(cursorX);
1220 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1223 if (av.getWrapAlignment())
1225 // work out the correct offsets for the cursor
1226 int charHeight = av.getCharHeight();
1227 int charWidth = av.getCharWidth();
1228 int canvasWidth = getWidth();
1229 int canvasHeight = getHeight();
1231 // height gap above each panel
1232 int hgap = charHeight;
1233 if (av.getScaleAboveWrapped())
1238 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1240 int cHeight = av.getAlignment().getHeight() * charHeight;
1242 endx = startx + cWidth - 1;
1243 int ypos = hgap; // vertical offset
1245 // iterate down the wrapped panels
1246 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1248 // update vertical offset
1249 ypos += cHeight + getAnnotationHeight() + hgap;
1251 // update horizontal offset
1253 endx = startx + cWidth - 1;
1256 xoffset = labelWidthWest;
1259 // now check if cursor is within range for x values
1260 if (cursor_xpos >= startx && cursor_xpos <= endx)
1262 // get the character the cursor is drawn at
1263 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1264 char s = seq.getCharAt(cursorX);
1266 seqRdr.drawCursor(g, s,
1267 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1268 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1275 * Draw a selection group over an unwrapped alignment
1278 * graphics object to draw with
1282 * start residue of area to draw
1284 * end residue of area to draw
1286 * start sequence of area to draw
1288 * end sequence of area to draw
1290 * vertical offset (used when called from wrapped alignment code)
1292 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1293 int startRes, int endRes, int startSeq, int endSeq, int offset)
1295 int charWidth = av.getCharWidth();
1297 if (!av.hasHiddenColumns())
1299 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1304 // package into blocks of visible columns
1309 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1310 VisibleContigsIterator regions = hidden
1311 .getVisContigsIterator(startRes, endRes + 1, true);
1312 while (regions.hasNext())
1314 int[] region = regions.next();
1315 blockEnd = region[1];
1316 blockStart = region[0];
1318 g.translate(screenY * charWidth, 0);
1319 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1322 g.translate(-screenY * charWidth, 0);
1323 screenY += blockEnd - blockStart + 1;
1329 * Draws part of a selection group outline
1337 * @param verticalOffset
1339 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1340 int startRes, int endRes, int startSeq, int endSeq,
1343 int charHeight = av.getCharHeight();
1344 int charWidth = av.getCharWidth();
1345 int visWidth = (endRes - startRes + 1) * charWidth;
1349 boolean inGroup = false;
1354 List<SequenceI> seqs = group.getSequences(null);
1356 // position of start residue of group relative to startRes, in pixels
1357 int sx = (group.getStartRes() - startRes) * charWidth;
1359 // width of group in pixels
1360 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1363 if (!(sx + xwidth < 0 || sx > visWidth))
1365 for (i = startSeq; i <= endSeq; i++)
1367 sy = verticalOffset + (i - startSeq) * charHeight;
1369 if ((sx <= (endRes - startRes) * charWidth)
1370 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1373 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1375 bottom = sy + charHeight;
1380 if (((top == -1) && (i == 0)) || !seqs
1381 .contains(av.getAlignment().getSequenceAt(i - 1)))
1392 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1393 drawHorizontals(g, sx, xwidth, visWidth, top, bottom+1);
1395 // reset top and bottom
1403 sy = verticalOffset + ((i - startSeq) * charHeight);
1404 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1405 drawHorizontals(g, sx, xwidth, visWidth, top, bottom+1);
1411 * Draw horizontal selection group boundaries at top and bottom positions
1414 * graphics object to draw on
1420 * visWidth maximum available width
1422 * position to draw top of group at
1424 * position to draw bottom of group at
1426 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1427 int visWidth, int top, int bottom)
1437 // don't let width extend beyond current block, or group extent
1439 if (startx + width >= visWidth)
1441 width = visWidth - startx;
1446 g.drawLine(startx, top, startx + width, top);
1451 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1456 * Draw vertical lines at sx and sx+xwidth providing they lie within
1460 * graphics object to draw on
1466 * visWidth maximum available width
1472 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1475 // if start position is visible, draw vertical line to left of
1477 if (sx >= 0 && sx < visWidth)
1479 g.drawLine(sx, oldY, sx, sy);
1482 // if end position is visible, draw vertical line to right of
1484 if (sx + xwidth < visWidth)
1486 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1491 * Highlights search results in the visible region by rendering as white text
1492 * on a black background. Any previous highlighting is removed. Answers true
1493 * if any highlight was left on the visible alignment (so status bar should be
1494 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1495 * so allows the next repaint to update the whole display.
1500 public boolean highlightSearchResults(SearchResultsI results)
1502 return highlightSearchResults(results, false);
1507 * Highlights search results in the visible region by rendering as white text
1508 * on a black background. Any previous highlighting is removed. Answers true
1509 * if any highlight was left on the visible alignment (so status bar should be
1510 * set to match), else false.
1512 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1513 * highlighted regions are modified. This speeds up highlighting across linked
1516 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1517 * a wrapped alignment had to be scrolled to show the highlighted region, then
1518 * it should be fully redrawn, otherwise a fast paint can be performed. This
1519 * argument could be removed if fast paint of scrolled wrapped alignment is
1520 * coded in future (JAL-2609).
1523 * @param doFastPaint
1524 * if true, sets a flag so the next repaint only redraws the modified
1528 public boolean highlightSearchResults(SearchResultsI results,
1529 boolean doFastPaint)
1535 boolean wrapped = av.getWrapAlignment();
1538 fastPaint = doFastPaint;
1539 fastpainting = fastPaint;
1542 * to avoid redrawing the whole visible region, we instead
1543 * redraw just the minimal regions to remove previous highlights
1546 SearchResultsI previous = av.getSearchResults();
1547 av.setSearchResults(results);
1548 boolean redrawn = false;
1549 boolean drawn = false;
1552 redrawn = drawMappedPositionsWrapped(previous);
1553 drawn = drawMappedPositionsWrapped(results);
1558 redrawn = drawMappedPositions(previous);
1559 drawn = drawMappedPositions(results);
1564 * if highlights were either removed or added, repaint
1572 * return true only if highlights were added
1578 fastpainting = false;
1583 * Redraws the minimal rectangle in the visible region (if any) that includes
1584 * mapped positions of the given search results. Whether or not positions are
1585 * highlighted depends on the SearchResults set on the Viewport. This allows
1586 * this method to be called to either clear or set highlighting. Answers true
1587 * if any positions were drawn (in which case a repaint is still required),
1593 protected boolean drawMappedPositions(SearchResultsI results)
1595 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1601 * calculate the minimal rectangle to redraw that
1602 * includes both new and existing search results
1604 int firstSeq = Integer.MAX_VALUE;
1606 int firstCol = Integer.MAX_VALUE;
1608 boolean matchFound = false;
1610 ViewportRanges ranges = av.getRanges();
1611 int firstVisibleColumn = ranges.getStartRes();
1612 int lastVisibleColumn = ranges.getEndRes();
1613 AlignmentI alignment = av.getAlignment();
1614 if (av.hasHiddenColumns())
1616 firstVisibleColumn = alignment.getHiddenColumns()
1617 .visibleToAbsoluteColumn(firstVisibleColumn);
1618 lastVisibleColumn = alignment.getHiddenColumns()
1619 .visibleToAbsoluteColumn(lastVisibleColumn);
1622 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1623 .getEndSeq(); seqNo++)
1625 SequenceI seq = alignment.getSequenceAt(seqNo);
1627 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1629 if (visibleResults != null)
1631 for (int i = 0; i < visibleResults.length - 1; i += 2)
1633 int firstMatchedColumn = visibleResults[i];
1634 int lastMatchedColumn = visibleResults[i + 1];
1635 if (firstMatchedColumn <= lastVisibleColumn
1636 && lastMatchedColumn >= firstVisibleColumn)
1639 * found a search results match in the visible region -
1640 * remember the first and last sequence matched, and the first
1641 * and last visible columns in the matched positions
1644 firstSeq = Math.min(firstSeq, seqNo);
1645 lastSeq = Math.max(lastSeq, seqNo);
1646 firstMatchedColumn = Math.max(firstMatchedColumn,
1647 firstVisibleColumn);
1648 lastMatchedColumn = Math.min(lastMatchedColumn,
1650 firstCol = Math.min(firstCol, firstMatchedColumn);
1651 lastCol = Math.max(lastCol, lastMatchedColumn);
1659 if (av.hasHiddenColumns())
1661 firstCol = alignment.getHiddenColumns()
1662 .absoluteToVisibleColumn(firstCol);
1663 lastCol = alignment.getHiddenColumns()
1664 .absoluteToVisibleColumn(lastCol);
1666 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1667 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1668 Graphics gg = img.getGraphics();
1669 gg.translate(transX, transY);
1670 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1671 gg.translate(-transX, -transY);
1679 public void propertyChange(PropertyChangeEvent evt)
1681 String eventName = evt.getPropertyName();
1682 // jalview.bin.Console.errPrintln(">>SeqCanvas propertyChange " + eventName);
1683 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1689 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1692 // jalview.bin.Console.errPrintln("!!!! fastPaint false from MOVE_VIEWPORT");
1698 if (eventName.equals(ViewportRanges.STARTRES)
1699 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1701 // Make sure we're not trying to draw a panel
1702 // larger than the visible window
1703 if (eventName.equals(ViewportRanges.STARTRES))
1705 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1709 scrollX = ((int[]) evt.getNewValue())[0]
1710 - ((int[]) evt.getOldValue())[0];
1712 ViewportRanges vpRanges = av.getRanges();
1714 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1715 if (scrollX > range)
1719 else if (scrollX < -range)
1724 // Both scrolling and resizing change viewport ranges: scrolling changes
1725 // both start and end points, but resize only changes end values.
1726 // Here we only want to fastpaint on a scroll, with resize using a normal
1727 // paint, so scroll events are identified as changes to the horizontal or
1728 // vertical start value.
1729 if (eventName.equals(ViewportRanges.STARTRES))
1731 if (av.getWrapAlignment())
1733 fastPaintWrapped(scrollX);
1737 fastPaint(scrollX, 0);
1740 else if (eventName.equals(ViewportRanges.STARTSEQ))
1743 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1745 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1747 if (av.getWrapAlignment())
1749 fastPaintWrapped(scrollX);
1753 fastPaint(scrollX, 0);
1756 else if (eventName.equals(ViewportRanges.STARTSEQ))
1759 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1761 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1763 if (av.getWrapAlignment())
1765 fastPaintWrapped(scrollX);
1771 * Does a minimal update of the image for a scroll movement. This method
1772 * handles scroll movements of up to one width of the wrapped alignment (one
1773 * click in the vertical scrollbar). Larger movements (for example after a
1774 * scroll to highlight a mapped position) trigger a full redraw instead.
1777 * number of positions scrolled (right if positive, left if negative)
1779 protected void fastPaintWrapped(int scrollX)
1781 ViewportRanges ranges = av.getRanges();
1783 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1786 * shift of one view width or more is
1787 * overcomplicated to handle in this method
1794 if (fastpainting || img == null)
1800 fastpainting = true;
1805 Graphics gg = img.getGraphics();
1807 calculateWrappedGeometry(getWidth(), getHeight());
1810 * relocate the regions of the alignment that are still visible
1812 shiftWrappedAlignment(-scrollX);
1815 * add new columns (sequence, annotation)
1816 * - at top left if scrollX < 0
1817 * - at right of last two widths if scrollX > 0
1821 int startRes = ranges.getStartRes();
1822 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1823 startRes - scrollX - 1, getHeight());
1827 fastPaintWrappedAddRight(scrollX);
1831 * draw all scales (if shown) and hidden column markers
1833 drawWrappedDecorators(gg, ranges.getStartRes());
1840 fastpainting = false;
1845 * Draws the specified number of columns at the 'end' (bottom right) of a
1846 * wrapped alignment view, including sequences and annotations if shown, but
1847 * not scales. Also draws the same number of columns at the right hand end of
1848 * the second last width shown, if the last width is not full height (so
1849 * cannot simply be copied from the graphics image).
1853 protected void fastPaintWrappedAddRight(int columns)
1860 Graphics gg = img.getGraphics();
1862 ViewportRanges ranges = av.getRanges();
1863 int viewportWidth = ranges.getViewportWidth();
1864 int charWidth = av.getCharWidth();
1867 * draw full height alignment in the second last row, last columns, if the
1868 * last row was not full height
1870 int visibleWidths = wrappedVisibleWidths;
1871 int canvasHeight = getHeight();
1872 boolean lastWidthPartHeight = (wrappedVisibleWidths
1873 * wrappedRepeatHeightPx) > canvasHeight;
1875 if (lastWidthPartHeight)
1877 int widthsAbove = Math.max(0, visibleWidths - 2);
1878 int ypos = wrappedRepeatHeightPx * widthsAbove
1879 + wrappedSpaceAboveAlignment;
1880 int endRes = ranges.getEndRes();
1881 endRes += widthsAbove * viewportWidth;
1882 int startRes = endRes - columns;
1883 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1887 * white fill first to erase annotations
1890 gg.translate(xOffset, 0);
1891 gg.setColor(Color.white);
1892 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1893 wrappedRepeatHeightPx);
1894 gg.translate(-xOffset, 0);
1896 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1901 * draw newly visible columns in last wrapped width (none if we
1902 * have reached the end of the alignment)
1903 * y-offset for drawing last width is height of widths above,
1906 int widthsAbove = visibleWidths - 1;
1907 int ypos = wrappedRepeatHeightPx * widthsAbove
1908 + wrappedSpaceAboveAlignment;
1909 int endRes = ranges.getEndRes();
1910 endRes += widthsAbove * viewportWidth;
1911 int startRes = endRes - columns + 1;
1914 * white fill first to erase annotations
1916 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1918 gg.translate(xOffset, 0);
1919 gg.setColor(Color.white);
1920 int width = viewportWidth * charWidth - xOffset;
1921 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1922 gg.translate(-xOffset, 0);
1924 gg.setFont(av.getFont());
1925 gg.setColor(Color.black);
1927 if (startRes < ranges.getVisibleAlignmentWidth())
1929 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1933 * and finally, white fill any space below the visible alignment
1935 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1936 if (heightBelow > 0)
1938 gg.setColor(Color.white);
1939 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1945 * Shifts the visible alignment by the specified number of columns - left if
1946 * negative, right if positive. Copies and moves sequences and annotations (if
1947 * shown). Scales, hidden column markers and any newly visible columns must be
1952 protected void shiftWrappedAlignment(int positions)
1959 Graphics gg = img.getGraphics();
1961 int charWidth = av.getCharWidth();
1963 int canvasHeight = getHeight();
1964 ViewportRanges ranges = av.getRanges();
1965 int viewportWidth = ranges.getViewportWidth();
1966 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1968 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1969 int xMax = ranges.getVisibleAlignmentWidth();
1974 * shift right (after scroll left)
1975 * for each wrapped width (starting with the last), copy (width-positions)
1976 * columns from the left margin to the right margin, and copy positions
1977 * columns from the right margin of the row above (if any) to the
1978 * left margin of the current row
1982 * get y-offset of last wrapped width, first row of sequences
1984 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1985 y += wrappedSpaceAboveAlignment;
1986 int copyFromLeftStart = labelWidthWest;
1987 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1992 * shift 'widthToCopy' residues by 'positions' places to the right
1994 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1995 positions * charWidth, 0);
1999 * copy 'positions' residue from the row above (right hand end)
2000 * to this row's left hand end
2002 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2003 positions * charWidth, heightToCopy, -widthToCopy,
2004 wrappedRepeatHeightPx);
2007 y -= wrappedRepeatHeightPx;
2013 * shift left (after scroll right)
2014 * for each wrapped width (starting with the first), copy (width-positions)
2015 * columns from the right margin to the left margin, and copy positions
2016 * columns from the left margin of the row below (if any) to the
2017 * right margin of the current row
2019 int xpos = av.getRanges().getStartRes();
2020 int y = wrappedSpaceAboveAlignment;
2021 int copyFromRightStart = labelWidthWest - positions * charWidth;
2023 while (y < canvasHeight)
2025 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2026 positions * charWidth, 0);
2027 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2028 && (xpos + viewportWidth <= xMax))
2030 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2031 -positions * charWidth, heightToCopy, widthToCopy,
2032 -wrappedRepeatHeightPx);
2034 y += wrappedRepeatHeightPx;
2035 xpos += viewportWidth;
2042 * Redraws any positions in the search results in the visible region of a
2043 * wrapped alignment. Any highlights are drawn depending on the search results
2044 * set on the Viewport, not the <code>results</code> argument. This allows
2045 * this method to be called either to clear highlights (passing the previous
2046 * search results), or to draw new highlights.
2051 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2053 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2057 int charHeight = av.getCharHeight();
2059 boolean matchFound = false;
2061 calculateWrappedGeometry(getWidth(), getHeight());
2062 int wrappedWidth = av.getWrappedWidth();
2063 int wrappedHeight = wrappedRepeatHeightPx;
2065 ViewportRanges ranges = av.getRanges();
2066 int canvasHeight = getHeight();
2067 int repeats = canvasHeight / wrappedHeight;
2068 if (canvasHeight / wrappedHeight > 0)
2073 int firstVisibleColumn = ranges.getStartRes();
2074 int lastVisibleColumn = ranges.getStartRes()
2075 + repeats * ranges.getViewportWidth() - 1;
2077 AlignmentI alignment = av.getAlignment();
2078 if (av.hasHiddenColumns())
2080 firstVisibleColumn = alignment.getHiddenColumns()
2081 .visibleToAbsoluteColumn(firstVisibleColumn);
2082 lastVisibleColumn = alignment.getHiddenColumns()
2083 .visibleToAbsoluteColumn(lastVisibleColumn);
2086 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2088 Graphics gg = img.getGraphics();
2090 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2091 .getEndSeq(); seqNo++)
2093 SequenceI seq = alignment.getSequenceAt(seqNo);
2095 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2097 if (visibleResults != null)
2099 for (int i = 0; i < visibleResults.length - 1; i += 2)
2101 int firstMatchedColumn = visibleResults[i];
2102 int lastMatchedColumn = visibleResults[i + 1];
2103 if (firstMatchedColumn <= lastVisibleColumn
2104 && lastMatchedColumn >= firstVisibleColumn)
2107 * found a search results match in the visible region
2109 firstMatchedColumn = Math.max(firstMatchedColumn,
2110 firstVisibleColumn);
2111 lastMatchedColumn = Math.min(lastMatchedColumn,
2115 * draw each mapped position separately (as contiguous positions may
2116 * wrap across lines)
2118 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2120 int displayColumn = mappedPos;
2121 if (av.hasHiddenColumns())
2123 displayColumn = alignment.getHiddenColumns()
2124 .absoluteToVisibleColumn(displayColumn);
2128 * transX: offset from left edge of canvas to residue position
2130 int transX = labelWidthWest
2131 + ((displayColumn - ranges.getStartRes())
2132 % wrappedWidth) * av.getCharWidth();
2135 * transY: offset from top edge of canvas to residue position
2137 int transY = gapHeight;
2138 transY += (displayColumn - ranges.getStartRes())
2139 / wrappedWidth * wrappedHeight;
2140 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2143 * yOffset is from graphics origin to start of visible region
2145 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2146 if (transY < getHeight())
2149 gg.translate(transX, transY);
2150 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2152 gg.translate(-transX, -transY);
2166 * Answers the width in pixels of the left scale labels (0 if not shown)
2170 int getLabelWidthWest()
2172 return labelWidthWest;