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.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.Rectangle;
42 import java.awt.RenderingHints;
43 import java.awt.geom.AffineTransform;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.Iterator;
47 import java.util.List;
49 import javax.swing.JPanel;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 @SuppressWarnings("serial")
58 public class SeqCanvas extends JPanel implements ViewportListenerI
61 * vertical gap in pixels between sequences and annotations when in wrapped
64 static final int SEQS_ANNOTATION_GAP = 3;
66 private static final String ZEROS = "0000000000";
68 final FeatureRenderer fr;
78 private final SequenceRenderer seqRdr;
80 boolean fastPaint = false;
82 private boolean fastpainting = false;
84 private AnnotationPanel annotations;
87 * measurements for drawing a wrapped alignment
89 private int labelWidthEast; // label right width in pixels if shown
91 private int labelWidthWest; // label left width in pixels if shown
93 int wrappedSpaceAboveAlignment; // gap between widths
95 int wrappedRepeatHeightPx; // height in pixels of wrapped width
97 private int wrappedVisibleWidths; // number of wrapped widths displayed
99 // Don't do this! Graphics handles are supposed to be transient
100 // private Graphics2D gg;
103 * Creates a new SeqCanvas object.
107 public SeqCanvas(AlignmentPanel ap)
110 fr = new FeatureRenderer(ap);
111 seqRdr = new SequenceRenderer(av);
112 setLayout(new BorderLayout());
113 PaintRefresher.Register(this, av.getSequenceSetId());
114 setBackground(Color.white);
116 av.getRanges().addPropertyChangeListener(this);
119 public SequenceRenderer getSequenceRenderer()
124 public FeatureRenderer getFeatureRenderer()
130 * Draws the scale above a region of a wrapped alignment, consisting of a
131 * column number every major interval (10 columns).
134 * the graphics context to draw on, positioned at the start (bottom
135 * left) of the line on which to draw any scale marks
137 * start alignment column (0..)
139 * end alignment column (0..)
141 * y offset to draw at
143 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
145 int charHeight = av.getCharHeight();
146 int charWidth = av.getCharWidth();
149 * white fill the scale space (for the fastPaint case)
151 g.setColor(Color.white);
152 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
153 charHeight * 3 / 2 + 2);
154 g.setColor(Color.black);
156 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
158 for (ScaleMark mark : marks)
160 int mpos = mark.column; // (i - startx - 1)
165 String mstring = mark.text;
171 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
175 * draw a tick mark below the column number, centred on the column;
176 * height of tick mark is 4 pixels less than half a character
178 int xpos = (mpos * charWidth) + (charWidth / 2);
179 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
185 * Draw the scale to the left or right of a wrapped alignment
188 * graphics context, positioned at the start of the scale to be drawn
190 * first column of wrapped width (0.. excluding any hidden columns)
192 * last column of wrapped width (0.. excluding any hidden columns)
194 * vertical offset at which to begin the scale
196 * if true, scale is left of residues, if false, scale is right
198 void drawVerticalScale(Graphics g, final int startx, final int endx,
199 final int ypos, final boolean left)
201 int charHeight = av.getCharHeight();
202 int charWidth = av.getCharWidth();
204 int yPos = ypos + charHeight;
208 if (av.hasHiddenColumns())
210 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
211 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
212 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
214 FontMetrics fm = getFontMetrics(av.getFont());
216 for (int i = 0; i < av.getAlignment().getHeight(); i++)
218 SequenceI seq = av.getAlignment().getSequenceAt(i);
221 * find sequence position of first non-gapped position -
222 * to the right if scale left, to the left if scale right
224 int index = left ? startX : endX;
226 while (index >= startX && index <= endX)
228 if (!Comparison.isGap(seq.getCharAt(index)))
230 value = seq.findPosition(index);
244 * white fill the space for the scale
246 g.setColor(Color.white);
247 int y = (yPos + (i * charHeight)) - (charHeight / 5);
248 // fillRect origin is top left of rectangle
249 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
255 * draw scale value, right justified within its width less half a
256 * character width padding on the right
258 int labelSpace = left ? labelWidthWest : labelWidthEast;
259 labelSpace -= charWidth / 2; // leave space to the right
260 String valueAsString = String.valueOf(value);
261 int labelLength = fm.stringWidth(valueAsString);
262 int xOffset = labelSpace - labelLength;
263 g.setColor(Color.black);
264 g.drawString(valueAsString, xOffset, y);
271 * Does a fast paint of an alignment in response to a scroll. Most of the
272 * visible region is simply copied and shifted, and then any newly visible
273 * columns or rows are drawn. The scroll may be horizontal or vertical, but
274 * not both at once. Scrolling may be the result of
276 * <li>dragging a scroll bar</li>
277 * <li>clicking in the scroll bar</li>
278 * <li>scrolling by trackpad, middle mouse button, or other device</li>
279 * <li>by moving the box in the Overview window</li>
280 * <li>programmatically to make a highlighted position visible</li>
281 * <li>pasting a block of sequences</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)
293 // if (horizontal != 0 && vertical != 0)
294 // throw new InvalidArgumentException();
295 if (fastpainting || img == null)
303 int charHeight = av.getCharHeight();
304 int charWidth = av.getCharWidth();
306 ViewportRanges ranges = av.getRanges();
307 int startRes = ranges.getStartRes();
308 int endRes = ranges.getEndRes();
309 int startSeq = ranges.getStartSeq();
310 int endSeq = ranges.getEndSeq();
314 if (horizontal > 0) // scrollbar pulled right, image to the left
316 transX = (endRes - startRes - horizontal) * charWidth;
317 startRes = endRes - horizontal;
319 else if (horizontal < 0)
321 endRes = startRes - horizontal;
324 if (vertical > 0) // scroll down
326 startSeq = endSeq - vertical;
328 if (startSeq < ranges.getStartSeq())
329 { // ie scrolling too fast, more than a page at a time
330 startSeq = ranges.getStartSeq();
334 transY = ((int)0.5*img.getHeight()) - ((vertical + 1) * charHeight);
337 else if (vertical < 0)
339 endSeq = startSeq - vertical;
341 if (endSeq > ranges.getEndSeq())
343 endSeq = ranges.getEndSeq();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(2*horizontal * charWidth, 2*vertical * charHeight,
353 img.getWidth(), img.getHeight(), -2*horizontal * charWidth,
354 -vertical * charHeight);
357 /** @j2sNative xxi = this.img */
359 gg.translate(transX, transY);
360 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
361 gg.translate(-transX, -transY);
364 // Call repaint on alignment panel so that repaints from other alignment
365 // panel components can be aggregated. Otherwise performance of the
366 // overview window and others may be adversely affected.
367 // System.out.println("SeqCanvas fastPaint() repaint() request...");
368 av.getAlignPanel().repaint();
371 fastpainting = false;
376 public void paintComponent(Graphics g)
379 int charHeight = av.getCharHeight();
380 int charWidth = av.getCharWidth();
382 int width = getWidth();
383 int height = getHeight();
385 width -= (width % charWidth);
386 height -= (height % charHeight);
388 // BH 2019 can't possibly fastPaint if either width or height is 0
390 if (width == 0 || height == 0)
395 ViewportRanges ranges = av.getRanges();
396 int startRes = ranges.getStartRes();
397 int startSeq = ranges.getStartSeq();
398 int endRes = ranges.getEndRes();
399 int endSeq = ranges.getEndSeq();
401 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
402 // repaint() requests in unpredictable ways. In this case, the issue was
403 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
404 // repaint request preceded two full requests, thus resulting
405 // in a full request for paint. In constrast, in JavaScript, the three
406 // requests were bundled together into one, so the fastPaint flag was
407 // still present for the second and third request.
409 // This resulted in incomplete painting.
411 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
412 // in PaintRefresher when the target to be painted is one of those two
417 // An initial idea; can be removed once we determine this issue is closed:
418 // if (av.isFastPaintDisabled())
420 // fastPaint = false;
422 Graphics2D g2g = (Graphics2D) g;
428 || (vis = getVisibleRect()).width != (clip = g
429 .getClipBounds()).width
430 || vis.height != clip.height))
432 g.drawImage(img, 0, 0, this);
433 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
439 // img is a cached version of the last view we drew.
440 // If we have no img or the size has changed, make a new one.
442 if (img == null || width != img.getWidth()
443 || height != img.getHeight())
445 img = new BufferedImage(2*width, 2*height, BufferedImage.TYPE_INT_RGB);
448 Graphics2D gg = (Graphics2D) img.getGraphics();
450 gg.setFont(av.getFont());
454 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
455 RenderingHints.VALUE_ANTIALIAS_ON);
458 gg.setColor(Color.white);
459 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
461 if (av.getWrapAlignment())
463 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
467 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
470 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
471 // java.awt.geom.AffineTransform tr = ((Graphics2D) g).getDeviceConfiguration().getNormalizingTransform();
473 g.drawImage(img, 0, 0, this);
479 drawCursor(g, startRes, endRes, startSeq, endSeq);
485 * Draw an alignment panel for printing
488 * Graphics object to draw with
490 * start residue of print area
492 * end residue of print area
494 * start sequence of print area
496 * end sequence of print area
498 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
499 int startSeq, int endSeq)
501 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
503 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
507 * Draw a wrapped alignment panel for printing
510 * Graphics object to draw with
512 * width of drawing area
513 * @param canvasHeight
514 * height of drawing area
516 * start residue of print area
518 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
519 int canvasHeight, int startRes)
521 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
523 SequenceGroup group = av.getSelectionGroup();
526 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
532 * Returns the visible width of the canvas in residues, after allowing for
533 * East or West scales (if shown)
536 * the width in pixels (possibly including scales)
540 public int getWrappedCanvasWidth(int canvasWidth)
542 int charWidth = av.getCharWidth();
544 FontMetrics fm = getFontMetrics(av.getFont());
548 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
550 labelWidth = getLabelWidth(fm);
553 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
555 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
557 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
561 * Returns a pixel width sufficient to show the largest sequence coordinate
562 * (end position) in the alignment, calculated as the FontMetrics width of
563 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
564 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
565 * half a character width space on either side.
570 protected int getLabelWidth(FontMetrics fm)
573 * find the biggest sequence end position we need to show
574 * (note this is not necessarily the sequence length)
577 AlignmentI alignment = av.getAlignment();
578 for (int i = 0; i < alignment.getHeight(); i++)
580 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
584 for (int i = maxWidth; i > 0; i /= 10)
589 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
593 * Draws as many widths of a wrapped alignment as can fit in the visible
598 * available width in pixels
599 * @param canvasHeight
600 * available height in pixels
602 * the first column (0...) of the alignment to draw
604 public void drawWrappedPanel(Graphics g, int canvasWidth,
605 int canvasHeight, final int startColumn)
607 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
610 av.setWrappedWidth(wrappedWidthInResidues);
612 ViewportRanges ranges = av.getRanges();
613 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
615 // we need to call this again to make sure the startColumn +
616 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
618 calculateWrappedGeometry(canvasWidth, canvasHeight);
621 * draw one width at a time (excluding any scales shown),
622 * until we have run out of either alignment or vertical space available
624 int ypos = wrappedSpaceAboveAlignment;
625 int maxWidth = ranges.getVisibleAlignmentWidth();
627 int start = startColumn;
628 int currentWidth = 0;
629 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
631 int endColumn = Math.min(maxWidth,
632 start + wrappedWidthInResidues - 1);
633 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
634 ypos += wrappedRepeatHeightPx;
635 start += wrappedWidthInResidues;
639 drawWrappedDecorators(g, startColumn);
643 * Calculates and saves values needed when rendering a wrapped alignment.
644 * These depend on many factors, including
646 * <li>canvas width and height</li>
647 * <li>number of visible sequences, and height of annotations if shown</li>
648 * <li>font and character width</li>
649 * <li>whether scales are shown left, right or above the alignment</li>
653 * @param canvasHeight
654 * @return the number of residue columns in each width
656 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
658 int charHeight = av.getCharHeight();
661 * vertical space in pixels between wrapped widths of alignment
662 * - one character height, or two if scale above is drawn
664 wrappedSpaceAboveAlignment = charHeight
665 * (av.getScaleAboveWrapped() ? 2 : 1);
668 * compute height in pixels of the wrapped widths
669 * - start with space above plus sequences
671 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
672 wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
675 * add annotations panel height if shown
676 * also gap between sequences and annotations
678 if (av.isShowAnnotation())
680 wrappedRepeatHeightPx += getAnnotationHeight();
681 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
685 * number of visible widths (the last one may be part height),
686 * ensuring a part height includes at least one sequence
688 ViewportRanges ranges = av.getRanges();
689 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
690 int remainder = canvasHeight % wrappedRepeatHeightPx;
691 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
693 wrappedVisibleWidths++;
697 * compute width in residues; this also sets East and West label widths
699 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
702 * limit visibleWidths to not exceed width of alignment
704 int xMax = ranges.getVisibleAlignmentWidth();
705 int startToEnd = xMax - ranges.getStartRes();
706 int maxWidths = startToEnd / wrappedWidthInResidues;
707 if (startToEnd % wrappedWidthInResidues > 0)
711 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
713 return wrappedWidthInResidues;
717 * Draws one width of a wrapped alignment, including sequences and
718 * annnotations, if shown, but not scales or hidden column markers
724 * @param canvasHeight
726 protected void drawWrappedWidth(Graphics g, final int ypos,
727 final int startColumn, final int endColumn,
728 final int canvasHeight)
730 ViewportRanges ranges = av.getRanges();
731 int viewportWidth = ranges.getViewportWidth();
733 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
736 * move right before drawing by the width of the scale left (if any)
737 * plus column offset from left margin (usually zero, but may be non-zero
738 * when fast painting is drawing just a few columns)
740 int charWidth = av.getCharWidth();
741 int xOffset = labelWidthWest
742 + ((startColumn - ranges.getStartRes()) % viewportWidth)
745 g.translate(xOffset, 0);
748 * white fill the region to be drawn (so incremental fast paint doesn't
749 * scribble over an existing image)
751 g.setColor(Color.white);
752 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
753 wrappedRepeatHeightPx);
755 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
758 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
760 if (av.isShowAnnotation())
762 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
763 g.translate(0, yShift);
764 if (annotations == null)
766 annotations = new AnnotationPanel(av);
769 annotations.renderer.drawComponent(annotations, av, g, -1,
770 startColumn, endx + 1);
771 g.translate(0, -yShift);
773 g.translate(-xOffset, 0);
777 * Draws scales left, right and above (if shown), and any hidden column
778 * markers, on all widths of the wrapped alignment
783 protected void drawWrappedDecorators(Graphics g, final int startColumn)
785 int charWidth = av.getCharWidth();
787 g.setFont(av.getFont());
789 g.setColor(Color.black);
791 int ypos = wrappedSpaceAboveAlignment;
792 ViewportRanges ranges = av.getRanges();
793 int viewportWidth = ranges.getViewportWidth();
794 int maxWidth = ranges.getVisibleAlignmentWidth();
796 int startCol = startColumn;
798 while (widthsDrawn < wrappedVisibleWidths)
800 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
802 if (av.getScaleLeftWrapped())
804 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
807 if (av.getScaleRightWrapped())
809 int x = labelWidthWest + viewportWidth * charWidth;
812 drawVerticalScale(g, startCol, endColumn, ypos, false);
817 * white fill region of scale above and hidden column markers
818 * (to support incremental fast paint of image)
820 g.translate(labelWidthWest, 0);
821 g.setColor(Color.white);
822 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
823 viewportWidth * charWidth + labelWidthWest,
824 wrappedSpaceAboveAlignment);
825 g.setColor(Color.black);
826 g.translate(-labelWidthWest, 0);
828 g.translate(labelWidthWest, 0);
830 if (av.getScaleAboveWrapped())
832 drawNorthScale(g, startCol, endColumn, ypos);
835 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
837 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
840 g.translate(-labelWidthWest, 0);
842 ypos += wrappedRepeatHeightPx;
843 startCol += viewportWidth;
849 * Draws markers (triangles) above hidden column positions between startColumn
857 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
858 int startColumn, int endColumn)
860 int charHeight = av.getCharHeight();
861 int charWidth = av.getCharWidth();
863 g.setColor(Color.blue);
865 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
867 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
871 res = it.next() - startColumn;
873 if (res < 0 || res > endColumn - startColumn + 1)
879 * draw a downward-pointing triangle at the hidden columns location
880 * (before the following visible column)
882 int xMiddle = res * charWidth;
883 int[] xPoints = new int[] { xMiddle - charHeight / 4,
884 xMiddle + charHeight / 4, xMiddle };
885 int yTop = ypos - (charHeight / 2);
886 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
887 g.fillPolygon(xPoints, yPoints, 3);
892 * Draw a selection group over a wrapped alignment
894 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
895 int canvasWidth, int canvasHeight, int startRes)
897 // chop the wrapped alignment extent up into panel-sized blocks and treat
898 // each block as if it were a block from an unwrapped alignment
899 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
900 BasicStroke.JOIN_ROUND, 3f, new float[]
902 g.setColor(Color.RED);
904 int charWidth = av.getCharWidth();
905 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
907 int startx = startRes;
908 int maxwidth = av.getAlignment().getVisibleWidth();
909 int ypos = wrappedSpaceAboveAlignment;
911 while ((ypos <= canvasHeight) && (startx < maxwidth))
913 // set end value to be start + width, or maxwidth, whichever is smaller
914 int endx = startx + cWidth - 1;
921 g.translate(labelWidthWest, 0);
922 drawUnwrappedSelection(g, group, startx, endx, 0,
923 av.getAlignment().getHeight() - 1, ypos);
924 g.translate(-labelWidthWest, 0);
926 ypos += wrappedRepeatHeightPx;
930 g.setStroke(new BasicStroke());
934 * Answers zero if annotations are not shown, otherwise recalculates and
935 * answers the total height of all annotation rows in pixels
939 int getAnnotationHeight()
941 if (!av.isShowAnnotation())
946 if (annotations == null)
948 annotations = new AnnotationPanel(av);
951 return annotations.adjustPanelHeight();
955 * Draws the visible region of the alignment on the graphics context. If there
956 * are hidden column markers in the visible region, then each sub-region
957 * between the markers is drawn separately, followed by the hidden column
961 * the graphics context, positioned at the first residue to be drawn
963 * offset of the first column to draw (0..)
965 * offset of the last column to draw (0..)
967 * offset of the first sequence to draw (0..)
969 * offset of the last sequence to draw (0..)
971 * vertical offset at which to draw (for wrapped alignments)
973 public void drawPanel(Graphics g1, final int startRes, final int endRes,
974 final int startSeq, final int endSeq, final int yOffset)
976 int charHeight = av.getCharHeight();
977 int charWidth = av.getCharWidth();
979 if (!av.hasHiddenColumns())
981 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
989 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
990 VisibleContigsIterator regions = hidden
991 .getVisContigsIterator(startRes, endRes + 1, true);
993 while (regions.hasNext())
995 int[] region = regions.next();
996 blockEnd = region[1];
997 blockStart = region[0];
1000 * draw up to just before the next hidden region, or the end of
1001 * the visible region, whichever comes first
1003 g1.translate(screenY * charWidth, 0);
1005 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1008 * draw the downline of the hidden column marker (ScalePanel draws the
1009 * triangle on top) if we reached it
1011 if (av.getShowHiddenMarkers()
1012 && (regions.hasNext() || regions.endsAtHidden()))
1014 g1.setColor(Color.blue);
1016 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1017 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1018 (endSeq - startSeq + 1) * charHeight + yOffset);
1021 g1.translate(-screenY * charWidth, 0);
1022 screenY += blockEnd - blockStart + 1;
1029 * Draws a region of the visible alignment
1033 * offset of the first column in the visible region (0..)
1035 * offset of the last column in the visible region (0..)
1037 * offset of the first sequence in the visible region (0..)
1039 * offset of the last sequence in the visible region (0..)
1041 * vertical offset at which to draw (for wrapped alignments)
1043 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1044 int endSeq, int offset)
1046 int charHeight = av.getCharHeight();
1047 int charWidth = av.getCharWidth();
1049 g.setFont(av.getFont());
1050 seqRdr.prepare(g, av.isRenderGaps());
1054 // / First draw the sequences
1055 // ///////////////////////////
1056 for (int i = startSeq; i <= endSeq; i++)
1058 nextSeq = av.getAlignment().getSequenceAt(i);
1059 if (nextSeq == null)
1061 // occasionally, a race condition occurs such that the alignment row is
1065 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1066 startRes, endRes, offset + ((i - startSeq) * charHeight));
1068 if (av.isShowSequenceFeatures())
1070 fr.drawSequence(g, nextSeq, startRes, endRes,
1071 offset + ((i - startSeq) * charHeight), false);
1075 * highlight search Results once sequence has been drawn
1077 if (av.hasSearchResults())
1079 SearchResultsI searchResults = av.getSearchResults();
1080 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1082 if (visibleResults != null)
1084 for (int r = 0; r < visibleResults.length; r += 2)
1086 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1087 visibleResults[r + 1],
1088 (visibleResults[r] - startRes) * charWidth,
1089 offset + ((i - startSeq) * charHeight));
1095 if (av.getSelectionGroup() != null
1096 || av.getAlignment().getGroups().size() > 0)
1098 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1104 * Draws the outlines of any groups defined on the alignment (excluding the
1105 * current selection group, if any)
1114 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1115 int startSeq, int endSeq, int offset)
1117 Graphics2D g = (Graphics2D) g1;
1119 SequenceGroup group = null;
1120 int groupIndex = -1;
1122 if (av.getAlignment().getGroups().size() > 0)
1124 group = av.getAlignment().getGroups().get(0);
1132 g.setColor(group.getOutlineColour());
1133 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1137 if (groupIndex >= av.getAlignment().getGroups().size())
1141 group = av.getAlignment().getGroups().get(groupIndex);
1142 } while (groupIndex < av.getAlignment().getGroups().size());
1147 * Draws the outline of the current selection group (if any)
1155 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1156 int startSeq, int endSeq)
1158 SequenceGroup group = av.getSelectionGroup();
1164 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1165 BasicStroke.JOIN_ROUND, 3f, new float[]
1167 g.setColor(Color.RED);
1168 if (!av.getWrapAlignment())
1170 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1175 drawWrappedSelection(g, group, getWidth(), getHeight(),
1176 av.getRanges().getStartRes());
1178 g.setStroke(new BasicStroke());
1182 * Draw the cursor as a separate image and overlay
1185 * start residue of area to draw cursor in
1187 * end residue of area to draw cursor in
1189 * start sequence of area to draw cursor in
1191 * end sequence of are to draw cursor in
1192 * @return a transparent image of the same size as the sequence canvas, with
1193 * the cursor drawn on it, if any
1195 private void drawCursor(Graphics g, int startRes, int endRes,
1196 int startSeq, int endSeq)
1198 // convert the cursorY into a position on the visible alignment
1199 int cursor_ypos = cursorY;
1201 // don't do work unless we have to
1202 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1206 int startx = startRes;
1209 // convert the cursorX into a position on the visible alignment
1210 int cursor_xpos = av.getAlignment().getHiddenColumns()
1211 .absoluteToVisibleColumn(cursorX);
1213 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1216 if (av.getWrapAlignment())
1218 // work out the correct offsets for the cursor
1219 int charHeight = av.getCharHeight();
1220 int charWidth = av.getCharWidth();
1221 int canvasWidth = getWidth();
1222 int canvasHeight = getHeight();
1224 // height gap above each panel
1225 int hgap = charHeight;
1226 if (av.getScaleAboveWrapped())
1231 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1233 int cHeight = av.getAlignment().getHeight() * charHeight;
1235 endx = startx + cWidth - 1;
1236 int ypos = hgap; // vertical offset
1238 // iterate down the wrapped panels
1239 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1241 // update vertical offset
1242 ypos += cHeight + getAnnotationHeight() + hgap;
1244 // update horizontal offset
1246 endx = startx + cWidth - 1;
1249 xoffset = labelWidthWest;
1252 // now check if cursor is within range for x values
1253 if (cursor_xpos >= startx && cursor_xpos <= endx)
1255 // get the character the cursor is drawn at
1256 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1257 char s = seq.getCharAt(cursorX);
1259 seqRdr.drawCursor(g, s,
1260 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1261 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1268 * Draw a selection group over an unwrapped alignment
1271 * graphics object to draw with
1275 * start residue of area to draw
1277 * end residue of area to draw
1279 * start sequence of area to draw
1281 * end sequence of area to draw
1283 * vertical offset (used when called from wrapped alignment code)
1285 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1286 int startRes, int endRes, int startSeq, int endSeq, int offset)
1288 int charWidth = av.getCharWidth();
1290 if (!av.hasHiddenColumns())
1292 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1297 // package into blocks of visible columns
1302 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1303 VisibleContigsIterator regions = hidden
1304 .getVisContigsIterator(startRes, endRes + 1, true);
1305 while (regions.hasNext())
1307 int[] region = regions.next();
1308 blockEnd = region[1];
1309 blockStart = region[0];
1311 g.translate(screenY * charWidth, 0);
1312 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1315 g.translate(-screenY * charWidth, 0);
1316 screenY += blockEnd - blockStart + 1;
1322 * Draws part of a selection group outline
1330 * @param verticalOffset
1332 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1333 int startRes, int endRes, int startSeq, int endSeq,
1336 int charHeight = av.getCharHeight();
1337 int charWidth = av.getCharWidth();
1338 int visWidth = (endRes - startRes + 1) * charWidth;
1342 boolean inGroup = false;
1347 List<SequenceI> seqs = group.getSequences(null);
1349 // position of start residue of group relative to startRes, in pixels
1350 int sx = (group.getStartRes() - startRes) * charWidth;
1352 // width of group in pixels
1353 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1356 if (!(sx + xwidth < 0 || sx > visWidth))
1358 for (i = startSeq; i <= endSeq; i++)
1360 sy = verticalOffset + (i - startSeq) * charHeight;
1362 if ((sx <= (endRes - startRes) * charWidth)
1363 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1366 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1368 bottom = sy + charHeight;
1373 if (((top == -1) && (i == 0)) || !seqs
1374 .contains(av.getAlignment().getSequenceAt(i - 1)))
1385 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1386 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1388 // reset top and bottom
1396 sy = verticalOffset + ((i - startSeq) * charHeight);
1397 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1398 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1404 * Draw horizontal selection group boundaries at top and bottom positions
1407 * graphics object to draw on
1413 * visWidth maximum available width
1415 * position to draw top of group at
1417 * position to draw bottom of group at
1419 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1420 int visWidth, int top, int bottom)
1430 // don't let width extend beyond current block, or group extent
1432 if (startx + width >= visWidth)
1434 width = visWidth - startx;
1439 g.drawLine(startx, top, startx + width, top);
1444 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1449 * Draw vertical lines at sx and sx+xwidth providing they lie within
1453 * graphics object to draw on
1459 * visWidth maximum available width
1465 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1468 // if start position is visible, draw vertical line to left of
1470 if (sx >= 0 && sx < visWidth)
1472 g.drawLine(sx, oldY, sx, sy);
1475 // if end position is visible, draw vertical line to right of
1477 if (sx + xwidth < visWidth)
1479 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1484 * Highlights search results in the visible region by rendering as white text
1485 * on a black background. Any previous highlighting is removed. Answers true
1486 * if any highlight was left on the visible alignment (so status bar should be
1487 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1488 * so allows the next repaint to update the whole display.
1493 public boolean highlightSearchResults(SearchResultsI results)
1495 return highlightSearchResults(results, false);
1500 * Highlights search results in the visible region by rendering as white text
1501 * on a black background. Any previous highlighting is removed. Answers true
1502 * if any highlight was left on the visible alignment (so status bar should be
1503 * set to match), else false.
1505 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1506 * highlighted regions are modified. This speeds up highlighting across linked
1509 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1510 * a wrapped alignment had to be scrolled to show the highlighted region, then
1511 * it should be fully redrawn, otherwise a fast paint can be performed. This
1512 * argument could be removed if fast paint of scrolled wrapped alignment is
1513 * coded in future (JAL-2609).
1516 * @param doFastPaint
1517 * if true, sets a flag so the next repaint only redraws the modified
1521 public boolean highlightSearchResults(SearchResultsI results,
1522 boolean doFastPaint)
1528 boolean wrapped = av.getWrapAlignment();
1531 fastPaint = doFastPaint;
1532 fastpainting = fastPaint;
1535 * to avoid redrawing the whole visible region, we instead
1536 * redraw just the minimal regions to remove previous highlights
1539 SearchResultsI previous = av.getSearchResults();
1540 av.setSearchResults(results);
1541 boolean redrawn = false;
1542 boolean drawn = false;
1545 redrawn = drawMappedPositionsWrapped(previous);
1546 drawn = drawMappedPositionsWrapped(results);
1551 redrawn = drawMappedPositions(previous);
1552 drawn = drawMappedPositions(results);
1557 * if highlights were either removed or added, repaint
1565 * return true only if highlights were added
1571 fastpainting = false;
1576 * Redraws the minimal rectangle in the visible region (if any) that includes
1577 * mapped positions of the given search results. Whether or not positions are
1578 * highlighted depends on the SearchResults set on the Viewport. This allows
1579 * this method to be called to either clear or set highlighting. Answers true
1580 * if any positions were drawn (in which case a repaint is still required),
1586 protected boolean drawMappedPositions(SearchResultsI results)
1588 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1594 * calculate the minimal rectangle to redraw that
1595 * includes both new and existing search results
1597 int firstSeq = Integer.MAX_VALUE;
1599 int firstCol = Integer.MAX_VALUE;
1601 boolean matchFound = false;
1603 ViewportRanges ranges = av.getRanges();
1604 int firstVisibleColumn = ranges.getStartRes();
1605 int lastVisibleColumn = ranges.getEndRes();
1606 AlignmentI alignment = av.getAlignment();
1607 if (av.hasHiddenColumns())
1609 firstVisibleColumn = alignment.getHiddenColumns()
1610 .visibleToAbsoluteColumn(firstVisibleColumn);
1611 lastVisibleColumn = alignment.getHiddenColumns()
1612 .visibleToAbsoluteColumn(lastVisibleColumn);
1615 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1616 .getEndSeq(); seqNo++)
1618 SequenceI seq = alignment.getSequenceAt(seqNo);
1620 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1622 if (visibleResults != null)
1624 for (int i = 0; i < visibleResults.length - 1; i += 2)
1626 int firstMatchedColumn = visibleResults[i];
1627 int lastMatchedColumn = visibleResults[i + 1];
1628 if (firstMatchedColumn <= lastVisibleColumn
1629 && lastMatchedColumn >= firstVisibleColumn)
1632 * found a search results match in the visible region -
1633 * remember the first and last sequence matched, and the first
1634 * and last visible columns in the matched positions
1637 firstSeq = Math.min(firstSeq, seqNo);
1638 lastSeq = Math.max(lastSeq, seqNo);
1639 firstMatchedColumn = Math.max(firstMatchedColumn,
1640 firstVisibleColumn);
1641 lastMatchedColumn = Math.min(lastMatchedColumn,
1643 firstCol = Math.min(firstCol, firstMatchedColumn);
1644 lastCol = Math.max(lastCol, lastMatchedColumn);
1652 if (av.hasHiddenColumns())
1654 firstCol = alignment.getHiddenColumns()
1655 .absoluteToVisibleColumn(firstCol);
1656 lastCol = alignment.getHiddenColumns()
1657 .absoluteToVisibleColumn(lastCol);
1659 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1660 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1661 Graphics gg = img.getGraphics();
1662 gg.translate(transX, transY);
1663 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1664 gg.translate(-transX, -transY);
1672 public void propertyChange(PropertyChangeEvent evt)
1674 String eventName = evt.getPropertyName();
1675 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1676 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1682 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1685 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1691 if (eventName.equals(ViewportRanges.STARTRES)
1692 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1694 // Make sure we're not trying to draw a panel
1695 // larger than the visible window
1696 if (eventName.equals(ViewportRanges.STARTRES))
1698 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1702 scrollX = ((int[]) evt.getNewValue())[0]
1703 - ((int[]) evt.getOldValue())[0];
1705 ViewportRanges vpRanges = av.getRanges();
1707 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1708 if (scrollX > range)
1712 else if (scrollX < -range)
1717 // Both scrolling and resizing change viewport ranges: scrolling changes
1718 // both start and end points, but resize only changes end values.
1719 // Here we only want to fastpaint on a scroll, with resize using a normal
1720 // paint, so scroll events are identified as changes to the horizontal or
1721 // vertical start value.
1722 if (eventName.equals(ViewportRanges.STARTRES))
1724 if (av.getWrapAlignment())
1726 fastPaintWrapped(scrollX);
1730 fastPaint(scrollX, 0);
1733 else if (eventName.equals(ViewportRanges.STARTSEQ))
1736 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1738 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1740 if (av.getWrapAlignment())
1742 fastPaintWrapped(scrollX);
1746 fastPaint(scrollX, 0);
1749 else if (eventName.equals(ViewportRanges.STARTSEQ))
1752 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1754 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1756 if (av.getWrapAlignment())
1758 fastPaintWrapped(scrollX);
1764 * Does a minimal update of the image for a scroll movement. This method
1765 * handles scroll movements of up to one width of the wrapped alignment (one
1766 * click in the vertical scrollbar). Larger movements (for example after a
1767 * scroll to highlight a mapped position) trigger a full redraw instead.
1770 * number of positions scrolled (right if positive, left if negative)
1772 protected void fastPaintWrapped(int scrollX)
1774 ViewportRanges ranges = av.getRanges();
1776 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1779 * shift of one view width or more is
1780 * overcomplicated to handle in this method
1787 if (fastpainting || img == null)
1793 fastpainting = true;
1798 Graphics gg = img.getGraphics();
1800 calculateWrappedGeometry(getWidth(), getHeight());
1803 * relocate the regions of the alignment that are still visible
1805 shiftWrappedAlignment(-scrollX);
1808 * add new columns (sequence, annotation)
1809 * - at top left if scrollX < 0
1810 * - at right of last two widths if scrollX > 0
1814 int startRes = ranges.getStartRes();
1815 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1816 startRes - scrollX - 1, getHeight());
1820 fastPaintWrappedAddRight(scrollX);
1824 * draw all scales (if shown) and hidden column markers
1826 drawWrappedDecorators(gg, ranges.getStartRes());
1833 fastpainting = false;
1838 * Draws the specified number of columns at the 'end' (bottom right) of a
1839 * wrapped alignment view, including sequences and annotations if shown, but
1840 * not scales. Also draws the same number of columns at the right hand end of
1841 * the second last width shown, if the last width is not full height (so
1842 * cannot simply be copied from the graphics image).
1846 protected void fastPaintWrappedAddRight(int columns)
1853 Graphics gg = img.getGraphics();
1855 ViewportRanges ranges = av.getRanges();
1856 int viewportWidth = ranges.getViewportWidth();
1857 int charWidth = av.getCharWidth();
1860 * draw full height alignment in the second last row, last columns, if the
1861 * last row was not full height
1863 int visibleWidths = wrappedVisibleWidths;
1864 int canvasHeight = getHeight();
1865 boolean lastWidthPartHeight = (wrappedVisibleWidths
1866 * wrappedRepeatHeightPx) > canvasHeight;
1868 if (lastWidthPartHeight)
1870 int widthsAbove = Math.max(0, visibleWidths - 2);
1871 int ypos = wrappedRepeatHeightPx * widthsAbove
1872 + wrappedSpaceAboveAlignment;
1873 int endRes = ranges.getEndRes();
1874 endRes += widthsAbove * viewportWidth;
1875 int startRes = endRes - columns;
1876 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1880 * white fill first to erase annotations
1883 gg.translate(xOffset, 0);
1884 gg.setColor(Color.white);
1885 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1886 wrappedRepeatHeightPx);
1887 gg.translate(-xOffset, 0);
1889 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1894 * draw newly visible columns in last wrapped width (none if we
1895 * have reached the end of the alignment)
1896 * y-offset for drawing last width is height of widths above,
1899 int widthsAbove = visibleWidths - 1;
1900 int ypos = wrappedRepeatHeightPx * widthsAbove
1901 + wrappedSpaceAboveAlignment;
1902 int endRes = ranges.getEndRes();
1903 endRes += widthsAbove * viewportWidth;
1904 int startRes = endRes - columns + 1;
1907 * white fill first to erase annotations
1909 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1911 gg.translate(xOffset, 0);
1912 gg.setColor(Color.white);
1913 int width = viewportWidth * charWidth - xOffset;
1914 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1915 gg.translate(-xOffset, 0);
1917 gg.setFont(av.getFont());
1918 gg.setColor(Color.black);
1920 if (startRes < ranges.getVisibleAlignmentWidth())
1922 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1926 * and finally, white fill any space below the visible alignment
1928 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1929 if (heightBelow > 0)
1931 gg.setColor(Color.white);
1932 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1938 * Shifts the visible alignment by the specified number of columns - left if
1939 * negative, right if positive. Copies and moves sequences and annotations (if
1940 * shown). Scales, hidden column markers and any newly visible columns must be
1945 protected void shiftWrappedAlignment(int positions)
1952 Graphics gg = img.getGraphics();
1954 int charWidth = av.getCharWidth();
1956 int canvasHeight = getHeight();
1957 ViewportRanges ranges = av.getRanges();
1958 int viewportWidth = ranges.getViewportWidth();
1959 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1961 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1962 int xMax = ranges.getVisibleAlignmentWidth();
1967 * shift right (after scroll left)
1968 * for each wrapped width (starting with the last), copy (width-positions)
1969 * columns from the left margin to the right margin, and copy positions
1970 * columns from the right margin of the row above (if any) to the
1971 * left margin of the current row
1975 * get y-offset of last wrapped width, first row of sequences
1977 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1978 y += wrappedSpaceAboveAlignment;
1979 int copyFromLeftStart = labelWidthWest;
1980 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1985 * shift 'widthToCopy' residues by 'positions' places to the right
1987 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1988 positions * charWidth, 0);
1992 * copy 'positions' residue from the row above (right hand end)
1993 * to this row's left hand end
1995 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1996 positions * charWidth, heightToCopy, -widthToCopy,
1997 wrappedRepeatHeightPx);
2000 y -= wrappedRepeatHeightPx;
2006 * shift left (after scroll right)
2007 * for each wrapped width (starting with the first), copy (width-positions)
2008 * columns from the right margin to the left margin, and copy positions
2009 * columns from the left margin of the row below (if any) to the
2010 * right margin of the current row
2012 int xpos = av.getRanges().getStartRes();
2013 int y = wrappedSpaceAboveAlignment;
2014 int copyFromRightStart = labelWidthWest - positions * charWidth;
2016 while (y < canvasHeight)
2018 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2019 positions * charWidth, 0);
2020 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2021 && (xpos + viewportWidth <= xMax))
2023 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2024 -positions * charWidth, heightToCopy, widthToCopy,
2025 -wrappedRepeatHeightPx);
2027 y += wrappedRepeatHeightPx;
2028 xpos += viewportWidth;
2035 * Redraws any positions in the search results in the visible region of a
2036 * wrapped alignment. Any highlights are drawn depending on the search results
2037 * set on the Viewport, not the <code>results</code> argument. This allows
2038 * this method to be called either to clear highlights (passing the previous
2039 * search results), or to draw new highlights.
2044 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2046 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2050 int charHeight = av.getCharHeight();
2052 boolean matchFound = false;
2054 calculateWrappedGeometry(getWidth(), getHeight());
2055 int wrappedWidth = av.getWrappedWidth();
2056 int wrappedHeight = wrappedRepeatHeightPx;
2058 ViewportRanges ranges = av.getRanges();
2059 int canvasHeight = getHeight();
2060 int repeats = canvasHeight / wrappedHeight;
2061 if (canvasHeight / wrappedHeight > 0)
2066 int firstVisibleColumn = ranges.getStartRes();
2067 int lastVisibleColumn = ranges.getStartRes()
2068 + repeats * ranges.getViewportWidth() - 1;
2070 AlignmentI alignment = av.getAlignment();
2071 if (av.hasHiddenColumns())
2073 firstVisibleColumn = alignment.getHiddenColumns()
2074 .visibleToAbsoluteColumn(firstVisibleColumn);
2075 lastVisibleColumn = alignment.getHiddenColumns()
2076 .visibleToAbsoluteColumn(lastVisibleColumn);
2079 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2081 Graphics gg = img.getGraphics();
2083 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2084 .getEndSeq(); seqNo++)
2086 SequenceI seq = alignment.getSequenceAt(seqNo);
2088 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2090 if (visibleResults != null)
2092 for (int i = 0; i < visibleResults.length - 1; i += 2)
2094 int firstMatchedColumn = visibleResults[i];
2095 int lastMatchedColumn = visibleResults[i + 1];
2096 if (firstMatchedColumn <= lastVisibleColumn
2097 && lastMatchedColumn >= firstVisibleColumn)
2100 * found a search results match in the visible region
2102 firstMatchedColumn = Math.max(firstMatchedColumn,
2103 firstVisibleColumn);
2104 lastMatchedColumn = Math.min(lastMatchedColumn,
2108 * draw each mapped position separately (as contiguous positions may
2109 * wrap across lines)
2111 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2113 int displayColumn = mappedPos;
2114 if (av.hasHiddenColumns())
2116 displayColumn = alignment.getHiddenColumns()
2117 .absoluteToVisibleColumn(displayColumn);
2121 * transX: offset from left edge of canvas to residue position
2123 int transX = labelWidthWest
2124 + ((displayColumn - ranges.getStartRes())
2125 % wrappedWidth) * av.getCharWidth();
2128 * transY: offset from top edge of canvas to residue position
2130 int transY = gapHeight;
2131 transY += (displayColumn - ranges.getStartRes())
2132 / wrappedWidth * wrappedHeight;
2133 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2136 * yOffset is from graphics origin to start of visible region
2138 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2139 if (transY < getHeight())
2142 gg.translate(transX, transY);
2143 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2145 gg.translate(-transX, -transY);
2159 * Answers the width in pixels of the left scale labels (0 if not shown)
2163 int getLabelWidthWest()
2165 return labelWidthWest;