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 // System.err.println(">>> 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 // System.out.println("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;
554 * Returns a pixel width sufficient to show the largest sequence coordinate
555 * (end position) in the alignment, calculated as the FontMetrics width of
556 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
557 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
558 * half a character width space on either side.
563 protected int getLabelWidth(FontMetrics fm)
566 * find the biggest sequence end position we need to show
567 * (note this is not necessarily the sequence length)
570 AlignmentI alignment = av.getAlignment();
571 for (int i = 0; i < alignment.getHeight(); i++)
573 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
577 for (int i = maxWidth; i > 0; i /= 10)
582 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
586 * Draws as many widths of a wrapped alignment as can fit in the visible
591 * available width in pixels
592 * @param canvasHeight
593 * available height in pixels
595 * the first column (0...) of the alignment to draw
597 public void drawWrappedPanel(Graphics g, int canvasWidth,
598 int canvasHeight, final int startColumn)
600 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
603 av.setWrappedWidth(wrappedWidthInResidues);
605 ViewportRanges ranges = av.getRanges();
606 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
608 // we need to call this again to make sure the startColumn +
609 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
611 calculateWrappedGeometry(canvasWidth, canvasHeight);
614 * draw one width at a time (excluding any scales shown),
615 * until we have run out of either alignment or vertical space available
617 int ypos = wrappedSpaceAboveAlignment;
618 int maxWidth = ranges.getVisibleAlignmentWidth();
620 int start = startColumn;
621 int currentWidth = 0;
622 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
624 int endColumn = Math.min(maxWidth,
625 start + wrappedWidthInResidues - 1);
626 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
627 ypos += wrappedRepeatHeightPx;
628 start += wrappedWidthInResidues;
632 drawWrappedDecorators(g, startColumn);
636 * Calculates and saves values needed when rendering a wrapped alignment.
637 * These depend on many factors, including
639 * <li>canvas width and height</li>
640 * <li>number of visible sequences, and height of annotations if shown</li>
641 * <li>font and character width</li>
642 * <li>whether scales are shown left, right or above the alignment</li>
646 * @param canvasHeight
647 * @return the number of residue columns in each width
649 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
651 int charHeight = av.getCharHeight();
654 * vertical space in pixels between wrapped widths of alignment
655 * - one character height, or two if scale above is drawn
657 wrappedSpaceAboveAlignment = charHeight
658 * (av.getScaleAboveWrapped() ? 2 : 1);
661 * compute height in pixels of the wrapped widths
662 * - start with space above plus sequences
664 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
665 wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
668 * add annotations panel height if shown
669 * also gap between sequences and annotations
671 if (av.isShowAnnotation())
673 wrappedRepeatHeightPx += getAnnotationHeight();
674 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
678 * number of visible widths (the last one may be part height),
679 * ensuring a part height includes at least one sequence
681 ViewportRanges ranges = av.getRanges();
682 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
683 int remainder = canvasHeight % wrappedRepeatHeightPx;
684 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
686 wrappedVisibleWidths++;
690 * compute width in residues; this also sets East and West label widths
692 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
693 av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
695 * limit visibleWidths to not exceed width of alignment
697 int xMax = ranges.getVisibleAlignmentWidth();
698 int startToEnd = xMax - ranges.getStartRes();
699 int maxWidths = startToEnd / wrappedWidthInResidues;
700 if (startToEnd % wrappedWidthInResidues > 0)
704 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
706 return wrappedWidthInResidues;
710 * Draws one width of a wrapped alignment, including sequences and
711 * annnotations, if shown, but not scales or hidden column markers
717 * @param canvasHeight
719 protected void drawWrappedWidth(Graphics g, final int ypos,
720 final int startColumn, final int endColumn,
721 final int canvasHeight)
723 ViewportRanges ranges = av.getRanges();
724 int viewportWidth = ranges.getViewportWidth();
726 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
729 * move right before drawing by the width of the scale left (if any)
730 * plus column offset from left margin (usually zero, but may be non-zero
731 * when fast painting is drawing just a few columns)
733 int charWidth = av.getCharWidth();
734 int xOffset = labelWidthWest
735 + ((startColumn - ranges.getStartRes()) % viewportWidth)
738 g.translate(xOffset, 0);
741 * white fill the region to be drawn (so incremental fast paint doesn't
742 * scribble over an existing image)
744 g.setColor(Color.white);
745 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
746 wrappedRepeatHeightPx);
748 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
751 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
753 if (av.isShowAnnotation())
755 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
756 g.translate(0, yShift);
757 if (annotations == null)
759 annotations = new AnnotationPanel(av);
762 annotations.renderer.drawComponent(annotations, av, g, -1,
763 startColumn, endx + 1);
764 g.translate(0, -yShift);
766 g.translate(-xOffset, 0);
770 * Draws scales left, right and above (if shown), and any hidden column
771 * markers, on all widths of the wrapped alignment
776 protected void drawWrappedDecorators(Graphics g, final int startColumn)
778 int charWidth = av.getCharWidth();
780 g.setFont(av.getFont());
782 g.setColor(Color.black);
784 int ypos = wrappedSpaceAboveAlignment;
785 ViewportRanges ranges = av.getRanges();
786 int viewportWidth = ranges.getViewportWidth();
787 int maxWidth = ranges.getVisibleAlignmentWidth();
789 int startCol = startColumn;
791 while (widthsDrawn < wrappedVisibleWidths)
793 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
795 if (av.getScaleLeftWrapped())
797 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
800 if (av.getScaleRightWrapped())
802 int x = labelWidthWest + viewportWidth * charWidth;
805 drawVerticalScale(g, startCol, endColumn, ypos, false);
810 * white fill region of scale above and hidden column markers
811 * (to support incremental fast paint of image)
813 g.translate(labelWidthWest, 0);
814 g.setColor(Color.white);
815 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
816 viewportWidth * charWidth + labelWidthWest,
817 wrappedSpaceAboveAlignment);
818 g.setColor(Color.black);
819 g.translate(-labelWidthWest, 0);
821 g.translate(labelWidthWest, 0);
823 if (av.getScaleAboveWrapped())
825 drawNorthScale(g, startCol, endColumn, ypos);
828 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
830 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
833 g.translate(-labelWidthWest, 0);
835 ypos += wrappedRepeatHeightPx;
836 startCol += viewportWidth;
842 * Draws markers (triangles) above hidden column positions between startColumn
850 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
851 int startColumn, int endColumn)
853 int charHeight = av.getCharHeight();
854 int charWidth = av.getCharWidth();
856 g.setColor(Color.blue);
858 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
860 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
864 res = it.next() - startColumn;
866 if (res < 0 || res > endColumn - startColumn + 1)
872 * draw a downward-pointing triangle at the hidden columns location
873 * (before the following visible column)
875 int xMiddle = res * charWidth;
876 int[] xPoints = new int[] { xMiddle - charHeight / 4,
877 xMiddle + charHeight / 4, xMiddle };
878 int yTop = ypos - (charHeight / 2);
879 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
880 g.fillPolygon(xPoints, yPoints, 3);
885 * Draw a selection group over a wrapped alignment
887 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
888 int canvasWidth, int canvasHeight, int startRes)
890 // chop the wrapped alignment extent up into panel-sized blocks and treat
891 // each block as if it were a block from an unwrapped alignment
892 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
893 BasicStroke.JOIN_ROUND, 3f, new float[]
895 g.setColor(Color.RED);
897 int charWidth = av.getCharWidth();
898 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
900 int startx = startRes;
901 int maxwidth = av.getAlignment().getVisibleWidth();
902 int ypos = wrappedSpaceAboveAlignment;
904 while ((ypos <= canvasHeight) && (startx < maxwidth))
906 // set end value to be start + width, or maxwidth, whichever is smaller
907 int endx = startx + cWidth - 1;
914 g.translate(labelWidthWest, 0);
915 drawUnwrappedSelection(g, group, startx, endx, 0,
916 av.getAlignment().getHeight() - 1, ypos);
917 g.translate(-labelWidthWest, 0);
919 ypos += wrappedRepeatHeightPx;
923 g.setStroke(new BasicStroke());
927 * Answers zero if annotations are not shown, otherwise recalculates and
928 * answers the total height of all annotation rows in pixels
932 int getAnnotationHeight()
934 if (!av.isShowAnnotation())
939 if (annotations == null)
941 annotations = new AnnotationPanel(av);
944 return annotations.adjustPanelHeight();
948 * Draws the visible region of the alignment on the graphics context. If there
949 * are hidden column markers in the visible region, then each sub-region
950 * between the markers is drawn separately, followed by the hidden column
954 * the graphics context, positioned at the first residue to be drawn
956 * offset of the first column to draw (0..)
958 * offset of the last column to draw (0..)
960 * offset of the first sequence to draw (0..)
962 * offset of the last sequence to draw (0..)
964 * vertical offset at which to draw (for wrapped alignments)
966 public void drawPanel(Graphics g1, final int startRes, final int endRes,
967 final int startSeq, final int endSeq, final int yOffset)
969 int charHeight = av.getCharHeight();
970 int charWidth = av.getCharWidth();
972 if (!av.hasHiddenColumns())
974 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
982 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
983 VisibleContigsIterator regions = hidden
984 .getVisContigsIterator(startRes, endRes + 1, true);
986 while (regions.hasNext())
988 int[] region = regions.next();
989 blockEnd = region[1];
990 blockStart = region[0];
993 * draw up to just before the next hidden region, or the end of
994 * the visible region, whichever comes first
996 g1.translate(screenY * charWidth, 0);
998 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1001 * draw the downline of the hidden column marker (ScalePanel draws the
1002 * triangle on top) if we reached it
1004 if (av.getShowHiddenMarkers()
1005 && (regions.hasNext() || regions.endsAtHidden()))
1007 g1.setColor(Color.blue);
1009 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1010 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1011 (endSeq - startSeq + 1) * charHeight + yOffset);
1014 g1.translate(-screenY * charWidth, 0);
1015 screenY += blockEnd - blockStart + 1;
1022 * Draws a region of the visible alignment
1026 * offset of the first column in the visible region (0..)
1028 * offset of the last column in the visible region (0..)
1030 * offset of the first sequence in the visible region (0..)
1032 * offset of the last sequence in the visible region (0..)
1034 * vertical offset at which to draw (for wrapped alignments)
1036 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1037 int endSeq, int offset)
1039 int charHeight = av.getCharHeight();
1040 int charWidth = av.getCharWidth();
1042 g.setFont(av.getFont());
1043 seqRdr.prepare(g, av.isRenderGaps());
1047 // / First draw the sequences
1048 // ///////////////////////////
1049 for (int i = startSeq; i <= endSeq; i++)
1051 nextSeq = av.getAlignment().getSequenceAt(i);
1052 if (nextSeq == null)
1054 // occasionally, a race condition occurs such that the alignment row is
1058 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1059 startRes, endRes, offset + ((i - startSeq) * charHeight));
1061 if (av.isShowSequenceFeatures())
1063 fr.drawSequence(g, nextSeq, startRes, endRes,
1064 offset + ((i - startSeq) * charHeight), false);
1068 * highlight search Results once sequence has been drawn
1070 if (av.hasSearchResults())
1072 SearchResultsI searchResults = av.getSearchResults();
1073 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1075 if (visibleResults != null)
1077 for (int r = 0; r < visibleResults.length; r += 2)
1079 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1080 visibleResults[r + 1],
1081 (visibleResults[r] - startRes) * charWidth,
1082 offset + ((i - startSeq) * charHeight));
1088 if (av.getSelectionGroup() != null
1089 || av.getAlignment().getGroups().size() > 0)
1091 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1097 * Draws the outlines of any groups defined on the alignment (excluding the
1098 * current selection group, if any)
1107 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1108 int startSeq, int endSeq, int offset)
1110 Graphics2D g = (Graphics2D) g1;
1112 SequenceGroup group = null;
1113 int groupIndex = -1;
1115 if (av.getAlignment().getGroups().size() > 0)
1117 group = av.getAlignment().getGroups().get(0);
1125 g.setColor(group.getOutlineColour());
1126 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1130 if (groupIndex >= av.getAlignment().getGroups().size())
1134 group = av.getAlignment().getGroups().get(groupIndex);
1135 } while (groupIndex < av.getAlignment().getGroups().size());
1140 * Draws the outline of the current selection group (if any)
1148 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1149 int startSeq, int endSeq)
1151 SequenceGroup group = av.getSelectionGroup();
1157 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1158 BasicStroke.JOIN_ROUND, 3f, new float[]
1160 g.setColor(Color.RED);
1161 if (!av.getWrapAlignment())
1163 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1168 drawWrappedSelection(g, group, getWidth(), getHeight(),
1169 av.getRanges().getStartRes());
1171 g.setStroke(new BasicStroke());
1175 * Draw the cursor as a separate image and overlay
1178 * start residue of area to draw cursor in
1180 * end residue of area to draw cursor in
1182 * start sequence of area to draw cursor in
1184 * end sequence of are to draw cursor in
1185 * @return a transparent image of the same size as the sequence canvas, with
1186 * the cursor drawn on it, if any
1188 private void drawCursor(Graphics g, int startRes, int endRes,
1189 int startSeq, int endSeq)
1191 // convert the cursorY into a position on the visible alignment
1192 int cursor_ypos = cursorY;
1194 // don't do work unless we have to
1195 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1199 int startx = startRes;
1202 // convert the cursorX into a position on the visible alignment
1203 int cursor_xpos = av.getAlignment().getHiddenColumns()
1204 .absoluteToVisibleColumn(cursorX);
1206 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1209 if (av.getWrapAlignment())
1211 // work out the correct offsets for the cursor
1212 int charHeight = av.getCharHeight();
1213 int charWidth = av.getCharWidth();
1214 int canvasWidth = getWidth();
1215 int canvasHeight = getHeight();
1217 // height gap above each panel
1218 int hgap = charHeight;
1219 if (av.getScaleAboveWrapped())
1224 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1226 int cHeight = av.getAlignment().getHeight() * charHeight;
1228 endx = startx + cWidth - 1;
1229 int ypos = hgap; // vertical offset
1231 // iterate down the wrapped panels
1232 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1234 // update vertical offset
1235 ypos += cHeight + getAnnotationHeight() + hgap;
1237 // update horizontal offset
1239 endx = startx + cWidth - 1;
1242 xoffset = labelWidthWest;
1245 // now check if cursor is within range for x values
1246 if (cursor_xpos >= startx && cursor_xpos <= endx)
1248 // get the character the cursor is drawn at
1249 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1250 char s = seq.getCharAt(cursorX);
1252 seqRdr.drawCursor(g, s,
1253 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1254 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1261 * Draw a selection group over an unwrapped alignment
1264 * graphics object to draw with
1268 * start residue of area to draw
1270 * end residue of area to draw
1272 * start sequence of area to draw
1274 * end sequence of area to draw
1276 * vertical offset (used when called from wrapped alignment code)
1278 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1279 int startRes, int endRes, int startSeq, int endSeq, int offset)
1281 int charWidth = av.getCharWidth();
1283 if (!av.hasHiddenColumns())
1285 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1290 // package into blocks of visible columns
1295 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1296 VisibleContigsIterator regions = hidden
1297 .getVisContigsIterator(startRes, endRes + 1, true);
1298 while (regions.hasNext())
1300 int[] region = regions.next();
1301 blockEnd = region[1];
1302 blockStart = region[0];
1304 g.translate(screenY * charWidth, 0);
1305 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1308 g.translate(-screenY * charWidth, 0);
1309 screenY += blockEnd - blockStart + 1;
1315 * Draws part of a selection group outline
1323 * @param verticalOffset
1325 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1326 int startRes, int endRes, int startSeq, int endSeq,
1329 int charHeight = av.getCharHeight();
1330 int charWidth = av.getCharWidth();
1331 int visWidth = (endRes - startRes + 1) * charWidth;
1335 boolean inGroup = false;
1340 List<SequenceI> seqs = group.getSequences(null);
1342 // position of start residue of group relative to startRes, in pixels
1343 int sx = (group.getStartRes() - startRes) * charWidth;
1345 // width of group in pixels
1346 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1349 if (!(sx + xwidth < 0 || sx > visWidth))
1351 for (i = startSeq; i <= endSeq; i++)
1353 sy = verticalOffset + (i - startSeq) * charHeight;
1355 if ((sx <= (endRes - startRes) * charWidth)
1356 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1359 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1361 bottom = sy + charHeight;
1366 if (((top == -1) && (i == 0)) || !seqs
1367 .contains(av.getAlignment().getSequenceAt(i - 1)))
1378 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1379 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1381 // reset top and bottom
1389 sy = verticalOffset + ((i - startSeq) * charHeight);
1390 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1391 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1397 * Draw horizontal selection group boundaries at top and bottom positions
1400 * graphics object to draw on
1406 * visWidth maximum available width
1408 * position to draw top of group at
1410 * position to draw bottom of group at
1412 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1413 int visWidth, int top, int bottom)
1423 // don't let width extend beyond current block, or group extent
1425 if (startx + width >= visWidth)
1427 width = visWidth - startx;
1432 g.drawLine(startx, top, startx + width, top);
1437 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1442 * Draw vertical lines at sx and sx+xwidth providing they lie within
1446 * graphics object to draw on
1452 * visWidth maximum available width
1458 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1461 // if start position is visible, draw vertical line to left of
1463 if (sx >= 0 && sx < visWidth)
1465 g.drawLine(sx, oldY, sx, sy);
1468 // if end position is visible, draw vertical line to right of
1470 if (sx + xwidth < visWidth)
1472 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1477 * Highlights search results in the visible region by rendering as white text
1478 * on a black background. Any previous highlighting is removed. Answers true
1479 * if any highlight was left on the visible alignment (so status bar should be
1480 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1481 * so allows the next repaint to update the whole display.
1486 public boolean highlightSearchResults(SearchResultsI results)
1488 return highlightSearchResults(results, false);
1493 * Highlights search results in the visible region by rendering as white text
1494 * on a black background. Any previous highlighting is removed. Answers true
1495 * if any highlight was left on the visible alignment (so status bar should be
1496 * set to match), else false.
1498 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1499 * highlighted regions are modified. This speeds up highlighting across linked
1502 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1503 * a wrapped alignment had to be scrolled to show the highlighted region, then
1504 * it should be fully redrawn, otherwise a fast paint can be performed. This
1505 * argument could be removed if fast paint of scrolled wrapped alignment is
1506 * coded in future (JAL-2609).
1509 * @param doFastPaint
1510 * if true, sets a flag so the next repaint only redraws the modified
1514 public boolean highlightSearchResults(SearchResultsI results,
1515 boolean doFastPaint)
1521 boolean wrapped = av.getWrapAlignment();
1524 fastPaint = doFastPaint;
1525 fastpainting = fastPaint;
1528 * to avoid redrawing the whole visible region, we instead
1529 * redraw just the minimal regions to remove previous highlights
1532 SearchResultsI previous = av.getSearchResults();
1533 av.setSearchResults(results);
1534 boolean redrawn = false;
1535 boolean drawn = false;
1538 redrawn = drawMappedPositionsWrapped(previous);
1539 drawn = drawMappedPositionsWrapped(results);
1544 redrawn = drawMappedPositions(previous);
1545 drawn = drawMappedPositions(results);
1550 * if highlights were either removed or added, repaint
1558 * return true only if highlights were added
1564 fastpainting = false;
1569 * Redraws the minimal rectangle in the visible region (if any) that includes
1570 * mapped positions of the given search results. Whether or not positions are
1571 * highlighted depends on the SearchResults set on the Viewport. This allows
1572 * this method to be called to either clear or set highlighting. Answers true
1573 * if any positions were drawn (in which case a repaint is still required),
1579 protected boolean drawMappedPositions(SearchResultsI results)
1581 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1587 * calculate the minimal rectangle to redraw that
1588 * includes both new and existing search results
1590 int firstSeq = Integer.MAX_VALUE;
1592 int firstCol = Integer.MAX_VALUE;
1594 boolean matchFound = false;
1596 ViewportRanges ranges = av.getRanges();
1597 int firstVisibleColumn = ranges.getStartRes();
1598 int lastVisibleColumn = ranges.getEndRes();
1599 AlignmentI alignment = av.getAlignment();
1600 if (av.hasHiddenColumns())
1602 firstVisibleColumn = alignment.getHiddenColumns()
1603 .visibleToAbsoluteColumn(firstVisibleColumn);
1604 lastVisibleColumn = alignment.getHiddenColumns()
1605 .visibleToAbsoluteColumn(lastVisibleColumn);
1608 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1609 .getEndSeq(); seqNo++)
1611 SequenceI seq = alignment.getSequenceAt(seqNo);
1613 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1615 if (visibleResults != null)
1617 for (int i = 0; i < visibleResults.length - 1; i += 2)
1619 int firstMatchedColumn = visibleResults[i];
1620 int lastMatchedColumn = visibleResults[i + 1];
1621 if (firstMatchedColumn <= lastVisibleColumn
1622 && lastMatchedColumn >= firstVisibleColumn)
1625 * found a search results match in the visible region -
1626 * remember the first and last sequence matched, and the first
1627 * and last visible columns in the matched positions
1630 firstSeq = Math.min(firstSeq, seqNo);
1631 lastSeq = Math.max(lastSeq, seqNo);
1632 firstMatchedColumn = Math.max(firstMatchedColumn,
1633 firstVisibleColumn);
1634 lastMatchedColumn = Math.min(lastMatchedColumn,
1636 firstCol = Math.min(firstCol, firstMatchedColumn);
1637 lastCol = Math.max(lastCol, lastMatchedColumn);
1645 if (av.hasHiddenColumns())
1647 firstCol = alignment.getHiddenColumns()
1648 .absoluteToVisibleColumn(firstCol);
1649 lastCol = alignment.getHiddenColumns()
1650 .absoluteToVisibleColumn(lastCol);
1652 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1653 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1654 Graphics gg = img.getGraphics();
1655 gg.translate(transX, transY);
1656 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1657 gg.translate(-transX, -transY);
1665 public void propertyChange(PropertyChangeEvent evt)
1667 String eventName = evt.getPropertyName();
1668 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1669 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1675 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1678 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1684 if (eventName.equals(ViewportRanges.STARTRES)
1685 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1687 // Make sure we're not trying to draw a panel
1688 // larger than the visible window
1689 if (eventName.equals(ViewportRanges.STARTRES))
1691 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1695 scrollX = ((int[]) evt.getNewValue())[0]
1696 - ((int[]) evt.getOldValue())[0];
1698 ViewportRanges vpRanges = av.getRanges();
1700 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1701 if (scrollX > range)
1705 else if (scrollX < -range)
1710 // Both scrolling and resizing change viewport ranges: scrolling changes
1711 // both start and end points, but resize only changes end values.
1712 // Here we only want to fastpaint on a scroll, with resize using a normal
1713 // paint, so scroll events are identified as changes to the horizontal or
1714 // vertical start value.
1715 if (eventName.equals(ViewportRanges.STARTRES))
1717 if (av.getWrapAlignment())
1719 fastPaintWrapped(scrollX);
1723 fastPaint(scrollX, 0);
1726 else if (eventName.equals(ViewportRanges.STARTSEQ))
1729 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1731 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1733 if (av.getWrapAlignment())
1735 fastPaintWrapped(scrollX);
1739 fastPaint(scrollX, 0);
1742 else if (eventName.equals(ViewportRanges.STARTSEQ))
1745 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1747 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1749 if (av.getWrapAlignment())
1751 fastPaintWrapped(scrollX);
1757 * Does a minimal update of the image for a scroll movement. This method
1758 * handles scroll movements of up to one width of the wrapped alignment (one
1759 * click in the vertical scrollbar). Larger movements (for example after a
1760 * scroll to highlight a mapped position) trigger a full redraw instead.
1763 * number of positions scrolled (right if positive, left if negative)
1765 protected void fastPaintWrapped(int scrollX)
1767 ViewportRanges ranges = av.getRanges();
1769 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1772 * shift of one view width or more is
1773 * overcomplicated to handle in this method
1780 if (fastpainting || img == null)
1786 fastpainting = true;
1791 Graphics gg = img.getGraphics();
1793 calculateWrappedGeometry(getWidth(), getHeight());
1796 * relocate the regions of the alignment that are still visible
1798 shiftWrappedAlignment(-scrollX);
1801 * add new columns (sequence, annotation)
1802 * - at top left if scrollX < 0
1803 * - at right of last two widths if scrollX > 0
1807 int startRes = ranges.getStartRes();
1808 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1809 startRes - scrollX - 1, getHeight());
1813 fastPaintWrappedAddRight(scrollX);
1817 * draw all scales (if shown) and hidden column markers
1819 drawWrappedDecorators(gg, ranges.getStartRes());
1826 fastpainting = false;
1831 * Draws the specified number of columns at the 'end' (bottom right) of a
1832 * wrapped alignment view, including sequences and annotations if shown, but
1833 * not scales. Also draws the same number of columns at the right hand end of
1834 * the second last width shown, if the last width is not full height (so
1835 * cannot simply be copied from the graphics image).
1839 protected void fastPaintWrappedAddRight(int columns)
1846 Graphics gg = img.getGraphics();
1848 ViewportRanges ranges = av.getRanges();
1849 int viewportWidth = ranges.getViewportWidth();
1850 int charWidth = av.getCharWidth();
1853 * draw full height alignment in the second last row, last columns, if the
1854 * last row was not full height
1856 int visibleWidths = wrappedVisibleWidths;
1857 int canvasHeight = getHeight();
1858 boolean lastWidthPartHeight = (wrappedVisibleWidths
1859 * wrappedRepeatHeightPx) > canvasHeight;
1861 if (lastWidthPartHeight)
1863 int widthsAbove = Math.max(0, visibleWidths - 2);
1864 int ypos = wrappedRepeatHeightPx * widthsAbove
1865 + wrappedSpaceAboveAlignment;
1866 int endRes = ranges.getEndRes();
1867 endRes += widthsAbove * viewportWidth;
1868 int startRes = endRes - columns;
1869 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1873 * white fill first to erase annotations
1876 gg.translate(xOffset, 0);
1877 gg.setColor(Color.white);
1878 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1879 wrappedRepeatHeightPx);
1880 gg.translate(-xOffset, 0);
1882 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1887 * draw newly visible columns in last wrapped width (none if we
1888 * have reached the end of the alignment)
1889 * y-offset for drawing last width is height of widths above,
1892 int widthsAbove = visibleWidths - 1;
1893 int ypos = wrappedRepeatHeightPx * widthsAbove
1894 + wrappedSpaceAboveAlignment;
1895 int endRes = ranges.getEndRes();
1896 endRes += widthsAbove * viewportWidth;
1897 int startRes = endRes - columns + 1;
1900 * white fill first to erase annotations
1902 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1904 gg.translate(xOffset, 0);
1905 gg.setColor(Color.white);
1906 int width = viewportWidth * charWidth - xOffset;
1907 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1908 gg.translate(-xOffset, 0);
1910 gg.setFont(av.getFont());
1911 gg.setColor(Color.black);
1913 if (startRes < ranges.getVisibleAlignmentWidth())
1915 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1919 * and finally, white fill any space below the visible alignment
1921 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1922 if (heightBelow > 0)
1924 gg.setColor(Color.white);
1925 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1931 * Shifts the visible alignment by the specified number of columns - left if
1932 * negative, right if positive. Copies and moves sequences and annotations (if
1933 * shown). Scales, hidden column markers and any newly visible columns must be
1938 protected void shiftWrappedAlignment(int positions)
1945 Graphics gg = img.getGraphics();
1947 int charWidth = av.getCharWidth();
1949 int canvasHeight = getHeight();
1950 ViewportRanges ranges = av.getRanges();
1951 int viewportWidth = ranges.getViewportWidth();
1952 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1954 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1955 int xMax = ranges.getVisibleAlignmentWidth();
1960 * shift right (after scroll left)
1961 * for each wrapped width (starting with the last), copy (width-positions)
1962 * columns from the left margin to the right margin, and copy positions
1963 * columns from the right margin of the row above (if any) to the
1964 * left margin of the current row
1968 * get y-offset of last wrapped width, first row of sequences
1970 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1971 y += wrappedSpaceAboveAlignment;
1972 int copyFromLeftStart = labelWidthWest;
1973 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1978 * shift 'widthToCopy' residues by 'positions' places to the right
1980 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1981 positions * charWidth, 0);
1985 * copy 'positions' residue from the row above (right hand end)
1986 * to this row's left hand end
1988 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1989 positions * charWidth, heightToCopy, -widthToCopy,
1990 wrappedRepeatHeightPx);
1993 y -= wrappedRepeatHeightPx;
1999 * shift left (after scroll right)
2000 * for each wrapped width (starting with the first), copy (width-positions)
2001 * columns from the right margin to the left margin, and copy positions
2002 * columns from the left margin of the row below (if any) to the
2003 * right margin of the current row
2005 int xpos = av.getRanges().getStartRes();
2006 int y = wrappedSpaceAboveAlignment;
2007 int copyFromRightStart = labelWidthWest - positions * charWidth;
2009 while (y < canvasHeight)
2011 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2012 positions * charWidth, 0);
2013 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2014 && (xpos + viewportWidth <= xMax))
2016 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2017 -positions * charWidth, heightToCopy, widthToCopy,
2018 -wrappedRepeatHeightPx);
2020 y += wrappedRepeatHeightPx;
2021 xpos += viewportWidth;
2028 * Redraws any positions in the search results in the visible region of a
2029 * wrapped alignment. Any highlights are drawn depending on the search results
2030 * set on the Viewport, not the <code>results</code> argument. This allows
2031 * this method to be called either to clear highlights (passing the previous
2032 * search results), or to draw new highlights.
2037 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2039 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2043 int charHeight = av.getCharHeight();
2045 boolean matchFound = false;
2047 calculateWrappedGeometry(getWidth(), getHeight());
2048 int wrappedWidth = av.getWrappedWidth();
2049 int wrappedHeight = wrappedRepeatHeightPx;
2051 ViewportRanges ranges = av.getRanges();
2052 int canvasHeight = getHeight();
2053 int repeats = canvasHeight / wrappedHeight;
2054 if (canvasHeight / wrappedHeight > 0)
2059 int firstVisibleColumn = ranges.getStartRes();
2060 int lastVisibleColumn = ranges.getStartRes()
2061 + repeats * ranges.getViewportWidth() - 1;
2063 AlignmentI alignment = av.getAlignment();
2064 if (av.hasHiddenColumns())
2066 firstVisibleColumn = alignment.getHiddenColumns()
2067 .visibleToAbsoluteColumn(firstVisibleColumn);
2068 lastVisibleColumn = alignment.getHiddenColumns()
2069 .visibleToAbsoluteColumn(lastVisibleColumn);
2072 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2074 Graphics gg = img.getGraphics();
2076 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2077 .getEndSeq(); seqNo++)
2079 SequenceI seq = alignment.getSequenceAt(seqNo);
2081 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2083 if (visibleResults != null)
2085 for (int i = 0; i < visibleResults.length - 1; i += 2)
2087 int firstMatchedColumn = visibleResults[i];
2088 int lastMatchedColumn = visibleResults[i + 1];
2089 if (firstMatchedColumn <= lastVisibleColumn
2090 && lastMatchedColumn >= firstVisibleColumn)
2093 * found a search results match in the visible region
2095 firstMatchedColumn = Math.max(firstMatchedColumn,
2096 firstVisibleColumn);
2097 lastMatchedColumn = Math.min(lastMatchedColumn,
2101 * draw each mapped position separately (as contiguous positions may
2102 * wrap across lines)
2104 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2106 int displayColumn = mappedPos;
2107 if (av.hasHiddenColumns())
2109 displayColumn = alignment.getHiddenColumns()
2110 .absoluteToVisibleColumn(displayColumn);
2114 * transX: offset from left edge of canvas to residue position
2116 int transX = labelWidthWest
2117 + ((displayColumn - ranges.getStartRes())
2118 % wrappedWidth) * av.getCharWidth();
2121 * transY: offset from top edge of canvas to residue position
2123 int transY = gapHeight;
2124 transY += (displayColumn - ranges.getStartRes())
2125 / wrappedWidth * wrappedHeight;
2126 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2129 * yOffset is from graphics origin to start of visible region
2131 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2132 if (transY < getHeight())
2135 gg.translate(transX, transY);
2136 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2138 gg.translate(-transX, -transY);
2152 * Answers the width in pixels of the left scale labels (0 if not shown)
2156 int getLabelWidthWest()
2158 return labelWidthWest;