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.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JPanel;
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 * pixels gap between sequences and annotations when in wrapped mode
62 static final int SEQS_ANNOTATION_GAP = 3;
64 private static final String ZEROS = "0000000000";
66 final FeatureRenderer fr;
76 private final SequenceRenderer seqRdr;
78 boolean fastPaint = false;
80 private boolean fastpainting = false;
82 private AnnotationPanel annotations;
85 * measurements for drawing a wrapped alignment
87 private int labelWidthEast; // label right width in pixels if shown
89 private int labelWidthWest; // label left width in pixels if shown
91 int wrappedSpaceAboveAlignment; // gap between widths
93 int wrappedRepeatHeightPx; // height in pixels of wrapped width
95 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 // Don't do this! Graphics handles are supposed to be transient
98 //private Graphics2D gg;
101 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
108 fr = new FeatureRenderer(ap);
109 seqRdr = new SequenceRenderer(av);
110 setLayout(new BorderLayout());
111 PaintRefresher.Register(this, av.getSequenceSetId());
112 setBackground(Color.white);
114 av.getRanges().addPropertyChangeListener(this);
117 public SequenceRenderer getSequenceRenderer()
122 public FeatureRenderer getFeatureRenderer()
128 * Draws the scale above a region of a wrapped alignment, consisting of a
129 * column number every major interval (10 columns).
132 * the graphics context to draw on, positioned at the start (bottom
133 * left) of the line on which to draw any scale marks
135 * start alignment column (0..)
137 * end alignment column (0..)
139 * y offset to draw at
141 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
143 int charHeight = av.getCharHeight();
144 int charWidth = av.getCharWidth();
147 * white fill the scale space (for the fastPaint case)
149 g.setColor(Color.white);
150 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
151 charHeight * 3 / 2 + 2);
152 g.setColor(Color.black);
154 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
156 for (ScaleMark mark : marks)
158 int mpos = mark.column; // (i - startx - 1)
163 String mstring = mark.text;
169 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
173 * draw a tick mark below the column number, centred on the column;
174 * height of tick mark is 4 pixels less than half a character
176 int xpos = (mpos * charWidth) + (charWidth / 2);
177 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
183 * Draw the scale to the left or right of a wrapped alignment
186 * graphics context, positioned at the start of the scale to be drawn
188 * first column of wrapped width (0.. excluding any hidden columns)
190 * last column of wrapped width (0.. excluding any hidden columns)
192 * vertical offset at which to begin the scale
194 * if true, scale is left of residues, if false, scale is right
196 void drawVerticalScale(Graphics g, final int startx, final int endx,
197 final int ypos, final boolean left)
199 int charHeight = av.getCharHeight();
200 int charWidth = av.getCharWidth();
202 int yPos = ypos + charHeight;
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
210 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startX : endX;
224 while (index >= startX && index <= endX)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
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();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
356 /** @j2sNative xxi = this.img */
358 gg.translate(transX, transY);
359 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
360 gg.translate(-transX, -transY);
363 // Call repaint on alignment panel so that repaints from other alignment
364 // panel components can be aggregated. Otherwise performance of the
365 // overview window and others may be adversely affected.
366 // System.out.println("SeqCanvas fastPaint() repaint() request...");
367 av.getAlignPanel().repaint();
370 fastpainting = false;
375 public void paintComponent(Graphics g)
378 int charHeight = av.getCharHeight();
379 int charWidth = av.getCharWidth();
381 int width = getWidth();
382 int height = getHeight();
384 width -= (width % charWidth);
385 height -= (height % charHeight);
387 // BH 2019 can't possibly fastPaint if either width or height is 0
389 if (width == 0 || height == 0)
394 ViewportRanges ranges = av.getRanges();
395 int startRes = ranges.getStartRes();
396 int startSeq = ranges.getStartSeq();
397 int endRes = ranges.getEndRes();
398 int endSeq = ranges.getEndSeq();
400 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
401 // repaint() requests in unpredictable ways. In this case, the issue was
402 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
403 // repaint request preceded two full requests, thus resulting
404 // in a full request for paint. In constrast, in JavaScript, the three
405 // requests were bundled together into one, so the fastPaint flag was
406 // still present for the second and third request.
408 // This resulted in incomplete painting.
410 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
411 // in PaintRefresher when the target to be painted is one of those two
416 // An initial idea; can be removed once we determine this issue is closed:
417 // if (av.isFastPaintDisabled())
419 // fastPaint = false;
425 || (vis = getVisibleRect()).width != (clip = g
426 .getClipBounds()).width
427 || vis.height != clip.height))
429 g.drawImage(img, 0, 0, this);
430 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
436 // img is a cached version of the last view we drew.
437 // If we have no img or the size has changed, make a new one.
439 if (img == null || width != img.getWidth()
440 || height != img.getHeight())
442 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
445 Graphics2D gg = (Graphics2D) img.getGraphics();
446 gg.setFont(av.getFont());
450 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
451 RenderingHints.VALUE_ANTIALIAS_ON);
454 gg.setColor(Color.white);
455 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
457 if (av.getWrapAlignment())
459 drawWrappedPanel(gg, width, height, ranges.getStartRes());
463 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
466 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
468 g.drawImage(img, 0, 0, this);
474 drawCursor(g, startRes, endRes, startSeq, endSeq);
479 * Draw an alignment panel for printing
482 * Graphics object to draw with
484 * start residue of print area
486 * end residue of print area
488 * start sequence of print area
490 * end sequence of print area
492 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
493 int startSeq, int endSeq)
495 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
497 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
502 * Draw a wrapped alignment panel for printing
505 * Graphics object to draw with
507 * width of drawing area
508 * @param canvasHeight
509 * height of drawing area
511 * start residue of print area
513 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
514 int canvasHeight, int startRes)
516 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
518 SequenceGroup group = av.getSelectionGroup();
521 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
527 * Returns the visible width of the canvas in residues, after allowing for
528 * East or West scales (if shown)
531 * the width in pixels (possibly including scales)
535 public int getWrappedCanvasWidth(int canvasWidth)
537 int charWidth = av.getCharWidth();
539 FontMetrics fm = getFontMetrics(av.getFont());
543 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
545 labelWidth = getLabelWidth(fm);
548 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
550 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
552 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
556 * Returns a pixel width sufficient to show the largest sequence coordinate
557 * (end position) in the alignment, calculated as the FontMetrics width of
558 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
559 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
560 * half a character width space on either side.
565 protected int getLabelWidth(FontMetrics fm)
568 * find the biggest sequence end position we need to show
569 * (note this is not necessarily the sequence length)
572 AlignmentI alignment = av.getAlignment();
573 for (int i = 0; i < alignment.getHeight(); i++)
575 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
579 for (int i = maxWidth; i > 0; i /= 10)
584 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
588 * Draws as many widths of a wrapped alignment as can fit in the visible
593 * available width in pixels
594 * @param canvasHeight
595 * available height in pixels
597 * the first column (0...) of the alignment to draw
599 public void drawWrappedPanel(Graphics g, int canvasWidth,
600 int canvasHeight, final int startColumn)
602 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
605 av.setWrappedWidth(wrappedWidthInResidues);
607 ViewportRanges ranges = av.getRanges();
608 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
610 // we need to call this again to make sure the startColumn +
611 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
613 calculateWrappedGeometry(canvasWidth, canvasHeight);
616 * draw one width at a time (excluding any scales shown),
617 * until we have run out of either alignment or vertical space available
619 int ypos = wrappedSpaceAboveAlignment;
620 int maxWidth = ranges.getVisibleAlignmentWidth();
622 int start = startColumn;
623 int currentWidth = 0;
624 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
627 .min(maxWidth, start + wrappedWidthInResidues - 1);
628 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
629 ypos += wrappedRepeatHeightPx;
630 start += wrappedWidthInResidues;
634 drawWrappedDecorators(g, startColumn);
638 * Calculates and saves values needed when rendering a wrapped alignment.
639 * These depend on many factors, including
641 * <li>canvas width and height</li>
642 * <li>number of visible sequences, and height of annotations if shown</li>
643 * <li>font and character width</li>
644 * <li>whether scales are shown left, right or above the alignment</li>
648 * @param canvasHeight
649 * @return the number of residue columns in each width
651 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
653 int charHeight = av.getCharHeight();
656 * vertical space in pixels between wrapped widths of alignment
657 * - one character height, or two if scale above is drawn
659 wrappedSpaceAboveAlignment = charHeight
660 * (av.getScaleAboveWrapped() ? 2 : 1);
663 * compute height in pixels of the wrapped widths
664 * - start with space above plus sequences
666 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
667 wrappedRepeatHeightPx += av.getAlignment().getHeight()
671 * add annotations panel height if shown
672 * also gap between sequences and annotations
674 if (av.isShowAnnotation())
676 wrappedRepeatHeightPx += getAnnotationHeight();
677 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
681 * number of visible widths (the last one may be part height),
682 * ensuring a part height includes at least one sequence
684 ViewportRanges ranges = av.getRanges();
685 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
686 int remainder = canvasHeight % wrappedRepeatHeightPx;
687 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
689 wrappedVisibleWidths++;
693 * compute width in residues; this also sets East and West label widths
695 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
698 * limit visibleWidths to not exceed width of alignment
700 int xMax = ranges.getVisibleAlignmentWidth();
701 int startToEnd = xMax - ranges.getStartRes();
702 int maxWidths = startToEnd / wrappedWidthInResidues;
703 if (startToEnd % wrappedWidthInResidues > 0)
707 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
709 return wrappedWidthInResidues;
713 * Draws one width of a wrapped alignment, including sequences and
714 * annnotations, if shown, but not scales or hidden column markers
720 * @param canvasHeight
722 protected void drawWrappedWidth(Graphics g, final int ypos,
723 final int startColumn, final int endColumn,
724 final int canvasHeight)
726 ViewportRanges ranges = av.getRanges();
727 int viewportWidth = ranges.getViewportWidth();
729 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
732 * move right before drawing by the width of the scale left (if any)
733 * plus column offset from left margin (usually zero, but may be non-zero
734 * when fast painting is drawing just a few columns)
736 int charWidth = av.getCharWidth();
737 int xOffset = labelWidthWest
738 + ((startColumn - ranges.getStartRes()) % viewportWidth)
741 g.translate(xOffset, 0);
744 * white fill the region to be drawn (so incremental fast paint doesn't
745 * scribble over an existing image)
747 g.setColor(Color.white);
748 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
749 wrappedRepeatHeightPx);
751 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
754 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
756 if (av.isShowAnnotation())
758 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
759 g.translate(0, yShift);
760 if (annotations == null)
762 annotations = new AnnotationPanel(av);
765 annotations.renderer.drawComponent(annotations, av, g, -1,
766 startColumn, endx + 1);
767 g.translate(0, -yShift);
769 g.translate(-xOffset, 0);
773 * Draws scales left, right and above (if shown), and any hidden column
774 * markers, on all widths of the wrapped alignment
779 protected void drawWrappedDecorators(Graphics g, final int startColumn)
781 int charWidth = av.getCharWidth();
783 g.setFont(av.getFont());
785 g.setColor(Color.black);
787 int ypos = wrappedSpaceAboveAlignment;
788 ViewportRanges ranges = av.getRanges();
789 int viewportWidth = ranges.getViewportWidth();
790 int maxWidth = ranges.getVisibleAlignmentWidth();
792 int startCol = startColumn;
794 while (widthsDrawn < wrappedVisibleWidths)
796 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
798 if (av.getScaleLeftWrapped())
800 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
803 if (av.getScaleRightWrapped())
805 int x = labelWidthWest + viewportWidth * charWidth;
808 drawVerticalScale(g, startCol, endColumn, ypos, false);
813 * white fill region of scale above and hidden column markers
814 * (to support incremental fast paint of image)
816 g.translate(labelWidthWest, 0);
817 g.setColor(Color.white);
818 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
819 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
820 g.setColor(Color.black);
821 g.translate(-labelWidthWest, 0);
823 g.translate(labelWidthWest, 0);
825 if (av.getScaleAboveWrapped())
827 drawNorthScale(g, startCol, endColumn, ypos);
830 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
832 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
835 g.translate(-labelWidthWest, 0);
837 ypos += wrappedRepeatHeightPx;
838 startCol += viewportWidth;
844 * Draws markers (triangles) above hidden column positions between startColumn
852 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
853 int startColumn, int endColumn)
855 int charHeight = av.getCharHeight();
856 int charWidth = av.getCharWidth();
858 g.setColor(Color.blue);
860 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
862 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
866 res = it.next() - startColumn;
868 if (res < 0 || res > endColumn - startColumn + 1)
874 * draw a downward-pointing triangle at the hidden columns location
875 * (before the following visible column)
877 int xMiddle = res * charWidth;
878 int[] xPoints = new int[] { xMiddle - charHeight / 4,
879 xMiddle + charHeight / 4, xMiddle };
880 int yTop = ypos - (charHeight / 2);
881 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
882 g.fillPolygon(xPoints, yPoints, 3);
887 * Draw a selection group over a wrapped alignment
889 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
891 int canvasHeight, int startRes)
893 int charHeight = av.getCharHeight();
894 int charWidth = av.getCharWidth();
896 // height gap above each panel
897 int hgap = charHeight;
898 if (av.getScaleAboveWrapped())
903 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
905 int cHeight = av.getAlignment().getHeight() * charHeight;
907 int startx = startRes;
909 int ypos = hgap; // vertical offset
910 int maxwidth = av.getAlignment().getVisibleWidth();
912 // chop the wrapped alignment extent up into panel-sized blocks and treat
913 // each block as if it were a block from an unwrapped alignment
914 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
915 BasicStroke.JOIN_ROUND, 3f, new float[]
917 g.setColor(Color.RED);
918 while ((ypos <= canvasHeight) && (startx < maxwidth))
920 // set end value to be start + width, or maxwidth, whichever is smaller
921 endx = startx + cWidth - 1;
928 g.translate(labelWidthWest, 0);
930 drawUnwrappedSelection(g, group, startx, endx, 0,
931 av.getAlignment().getHeight() - 1,
934 g.translate(-labelWidthWest, 0);
936 // update vertical offset
937 ypos += cHeight + getAnnotationHeight() + hgap;
939 // update horizontal offset
942 g.setStroke(new BasicStroke());
945 int getAnnotationHeight()
947 if (!av.isShowAnnotation())
952 if (annotations == null)
954 annotations = new AnnotationPanel(av);
957 return annotations.adjustPanelHeight();
961 * Draws the visible region of the alignment on the graphics context. If there
962 * are hidden column markers in the visible region, then each sub-region
963 * between the markers is drawn separately, followed by the hidden column
967 * the graphics context, positioned at the first residue to be drawn
969 * offset of the first column to draw (0..)
971 * offset of the last column to draw (0..)
973 * offset of the first sequence to draw (0..)
975 * offset of the last sequence to draw (0..)
977 * vertical offset at which to draw (for wrapped alignments)
979 public void drawPanel(Graphics g1, final int startRes, final int endRes,
980 final int startSeq, final int endSeq, final int yOffset)
982 int charHeight = av.getCharHeight();
983 int charWidth = av.getCharWidth();
985 if (!av.hasHiddenColumns())
987 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
995 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
996 VisibleContigsIterator regions = hidden
997 .getVisContigsIterator(startRes, endRes + 1, true);
999 while (regions.hasNext())
1001 int[] region = regions.next();
1002 blockEnd = region[1];
1003 blockStart = region[0];
1006 * draw up to just before the next hidden region, or the end of
1007 * the visible region, whichever comes first
1009 g1.translate(screenY * charWidth, 0);
1011 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1014 * draw the downline of the hidden column marker (ScalePanel draws the
1015 * triangle on top) if we reached it
1017 if (av.getShowHiddenMarkers()
1018 && (regions.hasNext() || regions.endsAtHidden()))
1020 g1.setColor(Color.blue);
1022 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1023 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1024 (endSeq - startSeq + 1) * charHeight + yOffset);
1027 g1.translate(-screenY * charWidth, 0);
1028 screenY += blockEnd - blockStart + 1;
1035 * Draws a region of the visible alignment
1039 * offset of the first column in the visible region (0..)
1041 * offset of the last column in the visible region (0..)
1043 * offset of the first sequence in the visible region (0..)
1045 * offset of the last sequence in the visible region (0..)
1047 * vertical offset at which to draw (for wrapped alignments)
1049 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1050 int endSeq, int offset)
1052 int charHeight = av.getCharHeight();
1053 int charWidth = av.getCharWidth();
1055 g.setFont(av.getFont());
1056 seqRdr.prepare(g, av.isRenderGaps());
1060 // / First draw the sequences
1061 // ///////////////////////////
1062 for (int i = startSeq; i <= endSeq; i++)
1064 nextSeq = av.getAlignment().getSequenceAt(i);
1065 if (nextSeq == null)
1067 // occasionally, a race condition occurs such that the alignment row is
1071 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1072 startRes, endRes, offset + ((i - startSeq) * charHeight));
1074 if (av.isShowSequenceFeatures())
1076 fr.drawSequence(g, nextSeq, startRes, endRes,
1077 offset + ((i - startSeq) * charHeight), false);
1081 * highlight search Results once sequence has been drawn
1083 if (av.hasSearchResults())
1085 SearchResultsI searchResults = av.getSearchResults();
1086 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1088 if (visibleResults != null)
1090 for (int r = 0; r < visibleResults.length; r += 2)
1092 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1093 visibleResults[r + 1],
1094 (visibleResults[r] - startRes) * charWidth,
1095 offset + ((i - startSeq) * charHeight));
1101 if (av.getSelectionGroup() != null
1102 || av.getAlignment().getGroups().size() > 0)
1104 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1110 * Draws the outlines of any groups defined on the alignment (excluding the
1111 * current selection group, if any)
1120 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1121 int startSeq, int endSeq, int offset)
1123 Graphics2D g = (Graphics2D) g1;
1125 SequenceGroup group = null;
1126 int groupIndex = -1;
1128 if (av.getAlignment().getGroups().size() > 0)
1130 group = av.getAlignment().getGroups().get(0);
1138 g.setColor(group.getOutlineColour());
1139 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1143 if (groupIndex >= av.getAlignment().getGroups().size())
1147 group = av.getAlignment().getGroups().get(groupIndex);
1148 } while (groupIndex < av.getAlignment().getGroups().size());
1153 * Draws the outline of the current selection group (if any)
1161 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1162 int startSeq, int endSeq)
1164 SequenceGroup group = av.getSelectionGroup();
1170 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1171 BasicStroke.JOIN_ROUND, 3f, new float[]
1173 g.setColor(Color.RED);
1174 if (!av.getWrapAlignment())
1176 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1181 drawWrappedSelection(g, group, getWidth(), getHeight(),
1182 av.getRanges().getStartRes());
1184 g.setStroke(new BasicStroke());
1188 * Draw the cursor as a separate image and overlay
1191 * start residue of area to draw cursor in
1193 * end residue of area to draw cursor in
1195 * start sequence of area to draw cursor in
1197 * end sequence of are to draw cursor in
1198 * @return a transparent image of the same size as the sequence canvas, with
1199 * the cursor drawn on it, if any
1201 private void drawCursor(Graphics g, int startRes, int endRes,
1205 // convert the cursorY into a position on the visible alignment
1206 int cursor_ypos = cursorY;
1208 // don't do work unless we have to
1209 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1213 int startx = startRes;
1216 // convert the cursorX into a position on the visible alignment
1217 int cursor_xpos = av.getAlignment().getHiddenColumns()
1218 .absoluteToVisibleColumn(cursorX);
1220 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1223 if (av.getWrapAlignment())
1225 // work out the correct offsets for the cursor
1226 int charHeight = av.getCharHeight();
1227 int charWidth = av.getCharWidth();
1228 int canvasWidth = getWidth();
1229 int canvasHeight = getHeight();
1231 // height gap above each panel
1232 int hgap = charHeight;
1233 if (av.getScaleAboveWrapped())
1238 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1240 int cHeight = av.getAlignment().getHeight() * charHeight;
1242 endx = startx + cWidth - 1;
1243 int ypos = hgap; // vertical offset
1245 // iterate down the wrapped panels
1246 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1248 // update vertical offset
1249 ypos += cHeight + getAnnotationHeight() + hgap;
1251 // update horizontal offset
1253 endx = startx + cWidth - 1;
1256 xoffset = labelWidthWest;
1259 // now check if cursor is within range for x values
1260 if (cursor_xpos >= startx && cursor_xpos <= endx)
1262 // get the character the cursor is drawn at
1263 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1264 char s = seq.getCharAt(cursorX);
1266 seqRdr.drawCursor(g, s,
1267 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1268 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1276 * Draw a selection group over an unwrapped alignment
1279 * graphics object to draw with
1283 * start residue of area to draw
1285 * end residue of area to draw
1287 * start sequence of area to draw
1289 * end sequence of area to draw
1291 * vertical offset (used when called from wrapped alignment code)
1293 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1294 int startRes, int endRes, int startSeq, int endSeq, int offset)
1296 int charWidth = av.getCharWidth();
1298 if (!av.hasHiddenColumns())
1300 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1305 // package into blocks of visible columns
1310 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1311 VisibleContigsIterator regions = hidden
1312 .getVisContigsIterator(startRes, endRes + 1, true);
1313 while (regions.hasNext())
1315 int[] region = regions.next();
1316 blockEnd = region[1];
1317 blockStart = region[0];
1319 g.translate(screenY * charWidth, 0);
1320 drawPartialGroupOutline(g, group,
1321 blockStart, blockEnd, startSeq, endSeq, offset);
1323 g.translate(-screenY * charWidth, 0);
1324 screenY += blockEnd - blockStart + 1;
1330 * Draws part of a selection group outline
1338 * @param verticalOffset
1340 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1341 int startRes, int endRes, int startSeq, int endSeq,
1344 int charHeight = av.getCharHeight();
1345 int charWidth = av.getCharWidth();
1346 int visWidth = (endRes - startRes + 1) * charWidth;
1350 boolean inGroup = false;
1355 List<SequenceI> seqs = group.getSequences(null);
1357 // position of start residue of group relative to startRes, in pixels
1358 int sx = (group.getStartRes() - startRes) * charWidth;
1360 // width of group in pixels
1361 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1364 if (!(sx + xwidth < 0 || sx > visWidth))
1366 for (i = startSeq; i <= endSeq; i++)
1368 sy = verticalOffset + (i - startSeq) * charHeight;
1370 if ((sx <= (endRes - startRes) * charWidth)
1371 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1374 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1376 bottom = sy + charHeight;
1381 if (((top == -1) && (i == 0)) || !seqs
1382 .contains(av.getAlignment().getSequenceAt(i - 1)))
1393 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1394 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1396 // reset top and bottom
1404 sy = verticalOffset + ((i - startSeq) * charHeight);
1405 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1406 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1412 * Draw horizontal selection group boundaries at top and bottom positions
1415 * graphics object to draw on
1421 * visWidth maximum available width
1423 * position to draw top of group at
1425 * position to draw bottom of group at
1427 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1428 int visWidth, int top, int bottom)
1438 // don't let width extend beyond current block, or group extent
1440 if (startx + width >= visWidth)
1442 width = visWidth - startx;
1447 g.drawLine(startx, top, startx + width, top);
1452 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1457 * Draw vertical lines at sx and sx+xwidth providing they lie within
1461 * graphics object to draw on
1467 * visWidth maximum available width
1473 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1476 // if start position is visible, draw vertical line to left of
1478 if (sx >= 0 && sx < visWidth)
1480 g.drawLine(sx, oldY, sx, sy);
1483 // if end position is visible, draw vertical line to right of
1485 if (sx + xwidth < visWidth)
1487 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1492 * Highlights search results in the visible region by rendering as white text
1493 * on a black background. Any previous highlighting is removed. Answers true
1494 * if any highlight was left on the visible alignment (so status bar should be
1495 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1496 * so allows the next repaint to update the whole display.
1501 public boolean highlightSearchResults(SearchResultsI results)
1503 return highlightSearchResults(results, false);
1508 * Highlights search results in the visible region by rendering as white text
1509 * on a black background. Any previous highlighting is removed. Answers true
1510 * if any highlight was left on the visible alignment (so status bar should be
1511 * set to match), else false.
1513 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1514 * highlighted regions are modified. This speeds up highlighting across linked
1517 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1518 * a wrapped alignment had to be scrolled to show the highlighted region, then
1519 * it should be fully redrawn, otherwise a fast paint can be performed. This
1520 * argument could be removed if fast paint of scrolled wrapped alignment is
1521 * coded in future (JAL-2609).
1524 * @param doFastPaint
1525 * if true, sets a flag so the next repaint only redraws the modified
1529 public boolean highlightSearchResults(SearchResultsI results,
1530 boolean doFastPaint)
1536 boolean wrapped = av.getWrapAlignment();
1539 fastPaint = doFastPaint;
1540 fastpainting = fastPaint;
1543 * to avoid redrawing the whole visible region, we instead
1544 * redraw just the minimal regions to remove previous highlights
1547 SearchResultsI previous = av.getSearchResults();
1548 av.setSearchResults(results);
1549 boolean redrawn = false;
1550 boolean drawn = false;
1553 redrawn = drawMappedPositionsWrapped(previous);
1554 drawn = drawMappedPositionsWrapped(results);
1559 redrawn = drawMappedPositions(previous);
1560 drawn = drawMappedPositions(results);
1565 * if highlights were either removed or added, repaint
1573 * return true only if highlights were added
1579 fastpainting = false;
1584 * Redraws the minimal rectangle in the visible region (if any) that includes
1585 * mapped positions of the given search results. Whether or not positions are
1586 * highlighted depends on the SearchResults set on the Viewport. This allows
1587 * this method to be called to either clear or set highlighting. Answers true
1588 * if any positions were drawn (in which case a repaint is still required),
1594 protected boolean drawMappedPositions(SearchResultsI results)
1596 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1602 * calculate the minimal rectangle to redraw that
1603 * includes both new and existing search results
1605 int firstSeq = Integer.MAX_VALUE;
1607 int firstCol = Integer.MAX_VALUE;
1609 boolean matchFound = false;
1611 ViewportRanges ranges = av.getRanges();
1612 int firstVisibleColumn = ranges.getStartRes();
1613 int lastVisibleColumn = ranges.getEndRes();
1614 AlignmentI alignment = av.getAlignment();
1615 if (av.hasHiddenColumns())
1617 firstVisibleColumn = alignment.getHiddenColumns()
1618 .visibleToAbsoluteColumn(firstVisibleColumn);
1619 lastVisibleColumn = alignment.getHiddenColumns()
1620 .visibleToAbsoluteColumn(lastVisibleColumn);
1623 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1624 .getEndSeq(); seqNo++)
1626 SequenceI seq = alignment.getSequenceAt(seqNo);
1628 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1630 if (visibleResults != null)
1632 for (int i = 0; i < visibleResults.length - 1; i += 2)
1634 int firstMatchedColumn = visibleResults[i];
1635 int lastMatchedColumn = visibleResults[i + 1];
1636 if (firstMatchedColumn <= lastVisibleColumn
1637 && lastMatchedColumn >= firstVisibleColumn)
1640 * found a search results match in the visible region -
1641 * remember the first and last sequence matched, and the first
1642 * and last visible columns in the matched positions
1645 firstSeq = Math.min(firstSeq, seqNo);
1646 lastSeq = Math.max(lastSeq, seqNo);
1647 firstMatchedColumn = Math.max(firstMatchedColumn,
1648 firstVisibleColumn);
1649 lastMatchedColumn = Math.min(lastMatchedColumn,
1651 firstCol = Math.min(firstCol, firstMatchedColumn);
1652 lastCol = Math.max(lastCol, lastMatchedColumn);
1660 if (av.hasHiddenColumns())
1662 firstCol = alignment.getHiddenColumns()
1663 .absoluteToVisibleColumn(firstCol);
1664 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1666 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1667 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1668 Graphics gg = img.getGraphics();
1669 gg.translate(transX, transY);
1670 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1671 gg.translate(-transX, -transY);
1679 public void propertyChange(PropertyChangeEvent evt)
1681 String eventName = evt.getPropertyName();
1682 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1683 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1689 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1692 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1698 if (eventName.equals(ViewportRanges.STARTRES)
1699 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1701 // Make sure we're not trying to draw a panel
1702 // larger than the visible window
1703 if (eventName.equals(ViewportRanges.STARTRES))
1705 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1709 scrollX = ((int[]) evt.getNewValue())[0]
1710 - ((int[]) evt.getOldValue())[0];
1712 ViewportRanges vpRanges = av.getRanges();
1714 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1715 if (scrollX > range)
1719 else if (scrollX < -range)
1724 // Both scrolling and resizing change viewport ranges: scrolling changes
1725 // both start and end points, but resize only changes end values.
1726 // Here we only want to fastpaint on a scroll, with resize using a normal
1727 // paint, so scroll events are identified as changes to the horizontal or
1728 // vertical start value.
1729 if (eventName.equals(ViewportRanges.STARTRES))
1731 if (av.getWrapAlignment())
1733 fastPaintWrapped(scrollX);
1737 fastPaint(scrollX, 0);
1740 else if (eventName.equals(ViewportRanges.STARTSEQ))
1743 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1745 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1747 if (av.getWrapAlignment())
1749 fastPaintWrapped(scrollX);
1753 fastPaint(scrollX, 0);
1756 else if (eventName.equals(ViewportRanges.STARTSEQ))
1759 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1761 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1763 if (av.getWrapAlignment())
1765 fastPaintWrapped(scrollX);
1771 * Does a minimal update of the image for a scroll movement. This method
1772 * handles scroll movements of up to one width of the wrapped alignment (one
1773 * click in the vertical scrollbar). Larger movements (for example after a
1774 * scroll to highlight a mapped position) trigger a full redraw instead.
1777 * number of positions scrolled (right if positive, left if negative)
1779 protected void fastPaintWrapped(int scrollX)
1781 ViewportRanges ranges = av.getRanges();
1783 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1786 * shift of one view width or more is
1787 * overcomplicated to handle in this method
1794 if (fastpainting || img == null)
1800 fastpainting = true;
1805 Graphics gg = img.getGraphics();
1807 calculateWrappedGeometry(getWidth(), getHeight());
1810 * relocate the regions of the alignment that are still visible
1812 shiftWrappedAlignment(-scrollX);
1815 * add new columns (sequence, annotation)
1816 * - at top left if scrollX < 0
1817 * - at right of last two widths if scrollX > 0
1821 int startRes = ranges.getStartRes();
1822 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1823 - scrollX - 1, getHeight());
1827 fastPaintWrappedAddRight(scrollX);
1831 * draw all scales (if shown) and hidden column markers
1833 drawWrappedDecorators(gg, ranges.getStartRes());
1840 fastpainting = false;
1845 * Draws the specified number of columns at the 'end' (bottom right) of a
1846 * wrapped alignment view, including sequences and annotations if shown, but
1847 * not scales. Also draws the same number of columns at the right hand end of
1848 * the second last width shown, if the last width is not full height (so
1849 * cannot simply be copied from the graphics image).
1853 protected void fastPaintWrappedAddRight(int columns)
1860 Graphics gg = img.getGraphics();
1862 ViewportRanges ranges = av.getRanges();
1863 int viewportWidth = ranges.getViewportWidth();
1864 int charWidth = av.getCharWidth();
1867 * draw full height alignment in the second last row, last columns, if the
1868 * last row was not full height
1870 int visibleWidths = wrappedVisibleWidths;
1871 int canvasHeight = getHeight();
1872 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1874 if (lastWidthPartHeight)
1876 int widthsAbove = Math.max(0, visibleWidths - 2);
1877 int ypos = wrappedRepeatHeightPx * widthsAbove
1878 + wrappedSpaceAboveAlignment;
1879 int endRes = ranges.getEndRes();
1880 endRes += widthsAbove * viewportWidth;
1881 int startRes = endRes - columns;
1882 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1886 * white fill first to erase annotations
1890 gg.translate(xOffset, 0);
1891 gg.setColor(Color.white);
1892 gg.fillRect(labelWidthWest, ypos,
1893 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1894 gg.translate(-xOffset, 0);
1896 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1901 * draw newly visible columns in last wrapped width (none if we
1902 * have reached the end of the alignment)
1903 * y-offset for drawing last width is height of widths above,
1906 int widthsAbove = visibleWidths - 1;
1907 int ypos = wrappedRepeatHeightPx * widthsAbove
1908 + wrappedSpaceAboveAlignment;
1909 int endRes = ranges.getEndRes();
1910 endRes += widthsAbove * viewportWidth;
1911 int startRes = endRes - columns + 1;
1914 * white fill first to erase annotations
1916 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1918 gg.translate(xOffset, 0);
1919 gg.setColor(Color.white);
1920 int width = viewportWidth * charWidth - xOffset;
1921 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1922 gg.translate(-xOffset, 0);
1924 gg.setFont(av.getFont());
1925 gg.setColor(Color.black);
1927 if (startRes < ranges.getVisibleAlignmentWidth())
1929 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1933 * and finally, white fill any space below the visible alignment
1935 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1936 if (heightBelow > 0)
1938 gg.setColor(Color.white);
1939 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1945 * Shifts the visible alignment by the specified number of columns - left if
1946 * negative, right if positive. Copies and moves sequences and annotations (if
1947 * shown). Scales, hidden column markers and any newly visible columns must be
1952 protected void shiftWrappedAlignment(int positions)
1959 Graphics gg = img.getGraphics();
1961 int charWidth = av.getCharWidth();
1963 int canvasHeight = getHeight();
1964 ViewportRanges ranges = av.getRanges();
1965 int viewportWidth = ranges.getViewportWidth();
1966 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1968 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1969 int xMax = ranges.getVisibleAlignmentWidth();
1974 * shift right (after scroll left)
1975 * for each wrapped width (starting with the last), copy (width-positions)
1976 * columns from the left margin to the right margin, and copy positions
1977 * columns from the right margin of the row above (if any) to the
1978 * left margin of the current row
1982 * get y-offset of last wrapped width, first row of sequences
1984 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1985 y += wrappedSpaceAboveAlignment;
1986 int copyFromLeftStart = labelWidthWest;
1987 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1992 * shift 'widthToCopy' residues by 'positions' places to the right
1994 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1995 positions * charWidth, 0);
1999 * copy 'positions' residue from the row above (right hand end)
2000 * to this row's left hand end
2002 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2003 positions * charWidth, heightToCopy, -widthToCopy,
2004 wrappedRepeatHeightPx);
2007 y -= wrappedRepeatHeightPx;
2013 * shift left (after scroll right)
2014 * for each wrapped width (starting with the first), copy (width-positions)
2015 * columns from the right margin to the left margin, and copy positions
2016 * columns from the left margin of the row below (if any) to the
2017 * right margin of the current row
2019 int xpos = av.getRanges().getStartRes();
2020 int y = wrappedSpaceAboveAlignment;
2021 int copyFromRightStart = labelWidthWest - positions * charWidth;
2023 while (y < canvasHeight)
2025 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2026 positions * charWidth, 0);
2027 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2028 && (xpos + viewportWidth <= xMax))
2030 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2031 * charWidth, heightToCopy, widthToCopy,
2032 -wrappedRepeatHeightPx);
2034 y += wrappedRepeatHeightPx;
2035 xpos += viewportWidth;
2043 * Redraws any positions in the search results in the visible region of a
2044 * wrapped alignment. Any highlights are drawn depending on the search results
2045 * set on the Viewport, not the <code>results</code> argument. This allows
2046 * this method to be called either to clear highlights (passing the previous
2047 * search results), or to draw new highlights.
2052 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2054 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2058 int charHeight = av.getCharHeight();
2060 boolean matchFound = false;
2062 calculateWrappedGeometry(getWidth(), getHeight());
2063 int wrappedWidth = av.getWrappedWidth();
2064 int wrappedHeight = wrappedRepeatHeightPx;
2066 ViewportRanges ranges = av.getRanges();
2067 int canvasHeight = getHeight();
2068 int repeats = canvasHeight / wrappedHeight;
2069 if (canvasHeight / wrappedHeight > 0)
2074 int firstVisibleColumn = ranges.getStartRes();
2075 int lastVisibleColumn = ranges.getStartRes() + repeats
2076 * ranges.getViewportWidth() - 1;
2078 AlignmentI alignment = av.getAlignment();
2079 if (av.hasHiddenColumns())
2081 firstVisibleColumn = alignment.getHiddenColumns()
2082 .visibleToAbsoluteColumn(firstVisibleColumn);
2083 lastVisibleColumn = alignment.getHiddenColumns()
2084 .visibleToAbsoluteColumn(lastVisibleColumn);
2087 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2090 Graphics gg = img.getGraphics();
2092 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2093 .getEndSeq(); seqNo++)
2095 SequenceI seq = alignment.getSequenceAt(seqNo);
2097 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2099 if (visibleResults != null)
2101 for (int i = 0; i < visibleResults.length - 1; i += 2)
2103 int firstMatchedColumn = visibleResults[i];
2104 int lastMatchedColumn = visibleResults[i + 1];
2105 if (firstMatchedColumn <= lastVisibleColumn
2106 && lastMatchedColumn >= firstVisibleColumn)
2109 * found a search results match in the visible region
2111 firstMatchedColumn = Math.max(firstMatchedColumn,
2112 firstVisibleColumn);
2113 lastMatchedColumn = Math.min(lastMatchedColumn,
2117 * draw each mapped position separately (as contiguous positions may
2118 * wrap across lines)
2120 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2122 int displayColumn = mappedPos;
2123 if (av.hasHiddenColumns())
2125 displayColumn = alignment.getHiddenColumns()
2126 .absoluteToVisibleColumn(displayColumn);
2130 * transX: offset from left edge of canvas to residue position
2132 int transX = labelWidthWest
2133 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2134 * av.getCharWidth();
2137 * transY: offset from top edge of canvas to residue position
2139 int transY = gapHeight;
2140 transY += (displayColumn - ranges.getStartRes())
2141 / wrappedWidth * wrappedHeight;
2142 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2145 * yOffset is from graphics origin to start of visible region
2147 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2148 if (transY < getHeight())
2151 gg.translate(transX, transY);
2152 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2154 gg.translate(-transX, -transY);
2168 * Answers the width in pixels of the left scale labels (0 if not shown)
2172 int getLabelWidthWest()
2174 return labelWidthWest;