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,
433 // System.out.println("SeqCanvas fast paint");
437 // System.out.println("SeqCanvas full paint");
439 // img is a cached version of the last view we drew.
440 // If we have no img or the size has changed, make a new one.
442 if (img == null || width != img.getWidth()
443 || height != img.getHeight())
445 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
448 Graphics2D gg = (Graphics2D) img.getGraphics();
449 gg.setFont(av.getFont());
453 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
454 RenderingHints.VALUE_ANTIALIAS_ON);
457 gg.setColor(Color.white);
458 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
460 if (av.getWrapAlignment())
462 drawWrappedPanel(gg, width, height, ranges.getStartRes());
466 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
469 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
471 g.drawImage(img, 0, 0, this);
477 drawCursor(g, startRes, endRes, startSeq, endSeq);
482 * Draw an alignment panel for printing
485 * Graphics object to draw with
487 * start residue of print area
489 * end residue of print area
491 * start sequence of print area
493 * end sequence of print area
495 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
496 int startSeq, int endSeq)
498 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
500 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
505 * Draw a wrapped alignment panel for printing
508 * Graphics object to draw with
510 * width of drawing area
511 * @param canvasHeight
512 * height of drawing area
514 * start residue of print area
516 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
517 int canvasHeight, int startRes)
519 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
521 SequenceGroup group = av.getSelectionGroup();
524 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
530 * Returns the visible width of the canvas in residues, after allowing for
531 * East or West scales (if shown)
534 * the width in pixels (possibly including scales)
538 public int getWrappedCanvasWidth(int canvasWidth)
540 int charWidth = av.getCharWidth();
542 FontMetrics fm = getFontMetrics(av.getFont());
546 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
548 labelWidth = getLabelWidth(fm);
551 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
553 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
555 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
559 * Returns a pixel width sufficient to show the largest sequence coordinate
560 * (end position) in the alignment, calculated as the FontMetrics width of
561 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
562 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
563 * half a character width space on either side.
568 protected int getLabelWidth(FontMetrics fm)
571 * find the biggest sequence end position we need to show
572 * (note this is not necessarily the sequence length)
575 AlignmentI alignment = av.getAlignment();
576 for (int i = 0; i < alignment.getHeight(); i++)
578 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
582 for (int i = maxWidth; i > 0; i /= 10)
587 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
591 * Draws as many widths of a wrapped alignment as can fit in the visible
596 * available width in pixels
597 * @param canvasHeight
598 * available height in pixels
600 * the first column (0...) of the alignment to draw
602 public void drawWrappedPanel(Graphics g, int canvasWidth,
603 int canvasHeight, final int startColumn)
605 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
608 av.setWrappedWidth(wrappedWidthInResidues);
610 ViewportRanges ranges = av.getRanges();
611 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
613 // we need to call this again to make sure the startColumn +
614 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
616 calculateWrappedGeometry(canvasWidth, canvasHeight);
619 * draw one width at a time (excluding any scales shown),
620 * until we have run out of either alignment or vertical space available
622 int ypos = wrappedSpaceAboveAlignment;
623 int maxWidth = ranges.getVisibleAlignmentWidth();
625 int start = startColumn;
626 int currentWidth = 0;
627 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
630 .min(maxWidth, start + wrappedWidthInResidues - 1);
631 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
632 ypos += wrappedRepeatHeightPx;
633 start += wrappedWidthInResidues;
637 drawWrappedDecorators(g, startColumn);
641 * Calculates and saves values needed when rendering a wrapped alignment.
642 * These depend on many factors, including
644 * <li>canvas width and height</li>
645 * <li>number of visible sequences, and height of annotations if shown</li>
646 * <li>font and character width</li>
647 * <li>whether scales are shown left, right or above the alignment</li>
651 * @param canvasHeight
652 * @return the number of residue columns in each width
654 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
656 int charHeight = av.getCharHeight();
659 * vertical space in pixels between wrapped widths of alignment
660 * - one character height, or two if scale above is drawn
662 wrappedSpaceAboveAlignment = charHeight
663 * (av.getScaleAboveWrapped() ? 2 : 1);
666 * compute height in pixels of the wrapped widths
667 * - start with space above plus sequences
669 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
670 wrappedRepeatHeightPx += av.getAlignment().getHeight()
674 * add annotations panel height if shown
675 * also gap between sequences and annotations
677 if (av.isShowAnnotation())
679 wrappedRepeatHeightPx += getAnnotationHeight();
680 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
684 * number of visible widths (the last one may be part height),
685 * ensuring a part height includes at least one sequence
687 ViewportRanges ranges = av.getRanges();
688 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
689 int remainder = canvasHeight % wrappedRepeatHeightPx;
690 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
692 wrappedVisibleWidths++;
696 * compute width in residues; this also sets East and West label widths
698 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
701 * limit visibleWidths to not exceed width of alignment
703 int xMax = ranges.getVisibleAlignmentWidth();
704 int startToEnd = xMax - ranges.getStartRes();
705 int maxWidths = startToEnd / wrappedWidthInResidues;
706 if (startToEnd % wrappedWidthInResidues > 0)
710 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
712 return wrappedWidthInResidues;
716 * Draws one width of a wrapped alignment, including sequences and
717 * annnotations, if shown, but not scales or hidden column markers
723 * @param canvasHeight
725 protected void drawWrappedWidth(Graphics g, final int ypos,
726 final int startColumn, final int endColumn,
727 final int canvasHeight)
729 ViewportRanges ranges = av.getRanges();
730 int viewportWidth = ranges.getViewportWidth();
732 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
735 * move right before drawing by the width of the scale left (if any)
736 * plus column offset from left margin (usually zero, but may be non-zero
737 * when fast painting is drawing just a few columns)
739 int charWidth = av.getCharWidth();
740 int xOffset = labelWidthWest
741 + ((startColumn - ranges.getStartRes()) % viewportWidth)
744 g.translate(xOffset, 0);
747 * white fill the region to be drawn (so incremental fast paint doesn't
748 * scribble over an existing image)
750 g.setColor(Color.white);
751 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
752 wrappedRepeatHeightPx);
754 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
757 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
759 if (av.isShowAnnotation())
761 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
762 g.translate(0, yShift);
763 if (annotations == null)
765 annotations = new AnnotationPanel(av);
768 annotations.renderer.drawComponent(annotations, av, g, -1,
769 startColumn, endx + 1);
770 g.translate(0, -yShift);
772 g.translate(-xOffset, 0);
776 * Draws scales left, right and above (if shown), and any hidden column
777 * markers, on all widths of the wrapped alignment
782 protected void drawWrappedDecorators(Graphics g, final int startColumn)
784 int charWidth = av.getCharWidth();
786 g.setFont(av.getFont());
788 g.setColor(Color.black);
790 int ypos = wrappedSpaceAboveAlignment;
791 ViewportRanges ranges = av.getRanges();
792 int viewportWidth = ranges.getViewportWidth();
793 int maxWidth = ranges.getVisibleAlignmentWidth();
795 int startCol = startColumn;
797 while (widthsDrawn < wrappedVisibleWidths)
799 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
801 if (av.getScaleLeftWrapped())
803 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
806 if (av.getScaleRightWrapped())
808 int x = labelWidthWest + viewportWidth * charWidth;
811 drawVerticalScale(g, startCol, endColumn, ypos, false);
816 * white fill region of scale above and hidden column markers
817 * (to support incremental fast paint of image)
819 g.translate(labelWidthWest, 0);
820 g.setColor(Color.white);
821 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
822 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
823 g.setColor(Color.black);
824 g.translate(-labelWidthWest, 0);
826 g.translate(labelWidthWest, 0);
828 if (av.getScaleAboveWrapped())
830 drawNorthScale(g, startCol, endColumn, ypos);
833 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
835 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
838 g.translate(-labelWidthWest, 0);
840 ypos += wrappedRepeatHeightPx;
841 startCol += viewportWidth;
847 * Draws markers (triangles) above hidden column positions between startColumn
855 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
856 int startColumn, int endColumn)
858 int charHeight = av.getCharHeight();
859 int charWidth = av.getCharWidth();
861 g.setColor(Color.blue);
863 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
865 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
869 res = it.next() - startColumn;
871 if (res < 0 || res > endColumn - startColumn + 1)
877 * draw a downward-pointing triangle at the hidden columns location
878 * (before the following visible column)
880 int xMiddle = res * charWidth;
881 int[] xPoints = new int[] { xMiddle - charHeight / 4,
882 xMiddle + charHeight / 4, xMiddle };
883 int yTop = ypos - (charHeight / 2);
884 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
885 g.fillPolygon(xPoints, yPoints, 3);
890 * Draw a selection group over a wrapped alignment
892 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
894 int canvasHeight, int startRes)
896 int charHeight = av.getCharHeight();
897 int charWidth = av.getCharWidth();
899 // height gap above each panel
900 int hgap = charHeight;
901 if (av.getScaleAboveWrapped())
906 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
908 int cHeight = av.getAlignment().getHeight() * charHeight;
910 int startx = startRes;
912 int ypos = hgap; // vertical offset
913 int maxwidth = av.getAlignment().getVisibleWidth();
915 // chop the wrapped alignment extent up into panel-sized blocks and treat
916 // each block as if it were a block from an unwrapped alignment
917 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
918 BasicStroke.JOIN_ROUND, 3f, new float[]
920 g.setColor(Color.RED);
921 while ((ypos <= canvasHeight) && (startx < maxwidth))
923 // set end value to be start + width, or maxwidth, whichever is smaller
924 endx = startx + cWidth - 1;
931 g.translate(labelWidthWest, 0);
933 drawUnwrappedSelection(g, group, startx, endx, 0,
934 av.getAlignment().getHeight() - 1,
937 g.translate(-labelWidthWest, 0);
939 // update vertical offset
940 ypos += cHeight + getAnnotationHeight() + hgap;
942 // update horizontal offset
945 g.setStroke(new BasicStroke());
948 int getAnnotationHeight()
950 if (!av.isShowAnnotation())
955 if (annotations == null)
957 annotations = new AnnotationPanel(av);
960 return annotations.adjustPanelHeight();
964 * Draws the visible region of the alignment on the graphics context. If there
965 * are hidden column markers in the visible region, then each sub-region
966 * between the markers is drawn separately, followed by the hidden column
970 * the graphics context, positioned at the first residue to be drawn
972 * offset of the first column to draw (0..)
974 * offset of the last column to draw (0..)
976 * offset of the first sequence to draw (0..)
978 * offset of the last sequence to draw (0..)
980 * vertical offset at which to draw (for wrapped alignments)
982 public void drawPanel(Graphics g1, final int startRes, final int endRes,
983 final int startSeq, final int endSeq, final int yOffset)
985 int charHeight = av.getCharHeight();
986 int charWidth = av.getCharWidth();
988 if (!av.hasHiddenColumns())
990 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
998 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
999 VisibleContigsIterator regions = hidden
1000 .getVisContigsIterator(startRes, endRes + 1, true);
1002 while (regions.hasNext())
1004 int[] region = regions.next();
1005 blockEnd = region[1];
1006 blockStart = region[0];
1009 * draw up to just before the next hidden region, or the end of
1010 * the visible region, whichever comes first
1012 g1.translate(screenY * charWidth, 0);
1014 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1017 * draw the downline of the hidden column marker (ScalePanel draws the
1018 * triangle on top) if we reached it
1020 if (av.getShowHiddenMarkers()
1021 && (regions.hasNext() || regions.endsAtHidden()))
1023 g1.setColor(Color.blue);
1025 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1026 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1027 (endSeq - startSeq + 1) * charHeight + yOffset);
1030 g1.translate(-screenY * charWidth, 0);
1031 screenY += blockEnd - blockStart + 1;
1038 * Draws a region of the visible alignment
1042 * offset of the first column in the visible region (0..)
1044 * offset of the last column in the visible region (0..)
1046 * offset of the first sequence in the visible region (0..)
1048 * offset of the last sequence in the visible region (0..)
1050 * vertical offset at which to draw (for wrapped alignments)
1052 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1053 int endSeq, int offset)
1055 int charHeight = av.getCharHeight();
1056 int charWidth = av.getCharWidth();
1058 g.setFont(av.getFont());
1059 seqRdr.prepare(g, av.isRenderGaps());
1063 // / First draw the sequences
1064 // ///////////////////////////
1065 for (int i = startSeq; i <= endSeq; i++)
1067 nextSeq = av.getAlignment().getSequenceAt(i);
1068 if (nextSeq == null)
1070 // occasionally, a race condition occurs such that the alignment row is
1074 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1075 startRes, endRes, offset + ((i - startSeq) * charHeight));
1077 if (av.isShowSequenceFeatures())
1079 fr.drawSequence(g, nextSeq, startRes, endRes,
1080 offset + ((i - startSeq) * charHeight), false);
1084 * highlight search Results once sequence has been drawn
1086 if (av.hasSearchResults())
1088 SearchResultsI searchResults = av.getSearchResults();
1089 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1091 if (visibleResults != null)
1093 for (int r = 0; r < visibleResults.length; r += 2)
1095 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1096 visibleResults[r + 1],
1097 (visibleResults[r] - startRes) * charWidth,
1098 offset + ((i - startSeq) * charHeight));
1104 if (av.getSelectionGroup() != null
1105 || av.getAlignment().getGroups().size() > 0)
1107 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1113 * Draws the outlines of any groups defined on the alignment (excluding the
1114 * current selection group, if any)
1123 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1124 int startSeq, int endSeq, int offset)
1126 Graphics2D g = (Graphics2D) g1;
1128 SequenceGroup group = null;
1129 int groupIndex = -1;
1131 if (av.getAlignment().getGroups().size() > 0)
1133 group = av.getAlignment().getGroups().get(0);
1141 g.setColor(group.getOutlineColour());
1142 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1146 if (groupIndex >= av.getAlignment().getGroups().size())
1150 group = av.getAlignment().getGroups().get(groupIndex);
1151 } while (groupIndex < av.getAlignment().getGroups().size());
1156 * Draws the outline of the current selection group (if any)
1164 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1165 int startSeq, int endSeq)
1167 SequenceGroup group = av.getSelectionGroup();
1173 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1174 BasicStroke.JOIN_ROUND, 3f, new float[]
1176 g.setColor(Color.RED);
1177 if (!av.getWrapAlignment())
1179 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1184 drawWrappedSelection(g, group, getWidth(), getHeight(),
1185 av.getRanges().getStartRes());
1187 g.setStroke(new BasicStroke());
1191 * Draw the cursor as a separate image and overlay
1194 * start residue of area to draw cursor in
1196 * end residue of area to draw cursor in
1198 * start sequence of area to draw cursor in
1200 * end sequence of are to draw cursor in
1201 * @return a transparent image of the same size as the sequence canvas, with
1202 * the cursor drawn on it, if any
1204 private void drawCursor(Graphics g, int startRes, int endRes,
1208 // convert the cursorY into a position on the visible alignment
1209 int cursor_ypos = cursorY;
1211 // don't do work unless we have to
1212 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1216 int startx = startRes;
1219 // convert the cursorX into a position on the visible alignment
1220 int cursor_xpos = av.getAlignment().getHiddenColumns()
1221 .absoluteToVisibleColumn(cursorX);
1223 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1226 if (av.getWrapAlignment())
1228 // work out the correct offsets for the cursor
1229 int charHeight = av.getCharHeight();
1230 int charWidth = av.getCharWidth();
1231 int canvasWidth = getWidth();
1232 int canvasHeight = getHeight();
1234 // height gap above each panel
1235 int hgap = charHeight;
1236 if (av.getScaleAboveWrapped())
1241 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1243 int cHeight = av.getAlignment().getHeight() * charHeight;
1245 endx = startx + cWidth - 1;
1246 int ypos = hgap; // vertical offset
1248 // iterate down the wrapped panels
1249 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1251 // update vertical offset
1252 ypos += cHeight + getAnnotationHeight() + hgap;
1254 // update horizontal offset
1256 endx = startx + cWidth - 1;
1259 xoffset = labelWidthWest;
1262 // now check if cursor is within range for x values
1263 if (cursor_xpos >= startx && cursor_xpos <= endx)
1265 // get the character the cursor is drawn at
1266 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1267 char s = seq.getCharAt(cursorX);
1269 seqRdr.drawCursor(g, s,
1270 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1271 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1279 * Draw a selection group over an unwrapped alignment
1282 * graphics object to draw with
1286 * start residue of area to draw
1288 * end residue of area to draw
1290 * start sequence of area to draw
1292 * end sequence of area to draw
1294 * vertical offset (used when called from wrapped alignment code)
1296 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1297 int startRes, int endRes, int startSeq, int endSeq, int offset)
1299 int charWidth = av.getCharWidth();
1301 if (!av.hasHiddenColumns())
1303 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1308 // package into blocks of visible columns
1313 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1314 VisibleContigsIterator regions = hidden
1315 .getVisContigsIterator(startRes, endRes + 1, true);
1316 while (regions.hasNext())
1318 int[] region = regions.next();
1319 blockEnd = region[1];
1320 blockStart = region[0];
1322 g.translate(screenY * charWidth, 0);
1323 drawPartialGroupOutline(g, group,
1324 blockStart, blockEnd, startSeq, endSeq, offset);
1326 g.translate(-screenY * charWidth, 0);
1327 screenY += blockEnd - blockStart + 1;
1333 * Draws part of a selection group outline
1341 * @param verticalOffset
1343 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1344 int startRes, int endRes, int startSeq, int endSeq,
1347 int charHeight = av.getCharHeight();
1348 int charWidth = av.getCharWidth();
1349 int visWidth = (endRes - startRes + 1) * charWidth;
1353 boolean inGroup = false;
1358 List<SequenceI> seqs = group.getSequences(null);
1360 // position of start residue of group relative to startRes, in pixels
1361 int sx = (group.getStartRes() - startRes) * charWidth;
1363 // width of group in pixels
1364 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1367 if (!(sx + xwidth < 0 || sx > visWidth))
1369 for (i = startSeq; i <= endSeq; i++)
1371 sy = verticalOffset + (i - startSeq) * charHeight;
1373 if ((sx <= (endRes - startRes) * charWidth)
1374 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1377 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1379 bottom = sy + charHeight;
1384 if (((top == -1) && (i == 0)) || !seqs
1385 .contains(av.getAlignment().getSequenceAt(i - 1)))
1396 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1397 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1399 // reset top and bottom
1407 sy = verticalOffset + ((i - startSeq) * charHeight);
1408 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1409 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1415 * Draw horizontal selection group boundaries at top and bottom positions
1418 * graphics object to draw on
1424 * visWidth maximum available width
1426 * position to draw top of group at
1428 * position to draw bottom of group at
1430 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1431 int visWidth, int top, int bottom)
1441 // don't let width extend beyond current block, or group extent
1443 if (startx + width >= visWidth)
1445 width = visWidth - startx;
1450 g.drawLine(startx, top, startx + width, top);
1455 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1460 * Draw vertical lines at sx and sx+xwidth providing they lie within
1464 * graphics object to draw on
1470 * visWidth maximum available width
1476 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1479 // if start position is visible, draw vertical line to left of
1481 if (sx >= 0 && sx < visWidth)
1483 g.drawLine(sx, oldY, sx, sy);
1486 // if end position is visible, draw vertical line to right of
1488 if (sx + xwidth < visWidth)
1490 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1495 * Highlights search results in the visible region by rendering as white text
1496 * on a black background. Any previous highlighting is removed. Answers true
1497 * if any highlight was left on the visible alignment (so status bar should be
1498 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1499 * so allows the next repaint to update the whole display.
1504 public boolean highlightSearchResults(SearchResultsI results)
1506 return highlightSearchResults(results, false);
1511 * Highlights search results in the visible region by rendering as white text
1512 * on a black background. Any previous highlighting is removed. Answers true
1513 * if any highlight was left on the visible alignment (so status bar should be
1514 * set to match), else false.
1516 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1517 * highlighted regions are modified. This speeds up highlighting across linked
1520 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1521 * a wrapped alignment had to be scrolled to show the highlighted region, then
1522 * it should be fully redrawn, otherwise a fast paint can be performed. This
1523 * argument could be removed if fast paint of scrolled wrapped alignment is
1524 * coded in future (JAL-2609).
1527 * @param doFastPaint
1528 * if true, sets a flag so the next repaint only redraws the modified
1532 public boolean highlightSearchResults(SearchResultsI results,
1533 boolean doFastPaint)
1539 boolean wrapped = av.getWrapAlignment();
1542 fastPaint = doFastPaint;
1543 fastpainting = fastPaint;
1546 * to avoid redrawing the whole visible region, we instead
1547 * redraw just the minimal regions to remove previous highlights
1550 SearchResultsI previous = av.getSearchResults();
1551 av.setSearchResults(results);
1552 boolean redrawn = false;
1553 boolean drawn = false;
1556 redrawn = drawMappedPositionsWrapped(previous);
1557 drawn = drawMappedPositionsWrapped(results);
1562 redrawn = drawMappedPositions(previous);
1563 drawn = drawMappedPositions(results);
1568 * if highlights were either removed or added, repaint
1576 * return true only if highlights were added
1582 fastpainting = false;
1587 * Redraws the minimal rectangle in the visible region (if any) that includes
1588 * mapped positions of the given search results. Whether or not positions are
1589 * highlighted depends on the SearchResults set on the Viewport. This allows
1590 * this method to be called to either clear or set highlighting. Answers true
1591 * if any positions were drawn (in which case a repaint is still required),
1597 protected boolean drawMappedPositions(SearchResultsI results)
1599 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1605 * calculate the minimal rectangle to redraw that
1606 * includes both new and existing search results
1608 int firstSeq = Integer.MAX_VALUE;
1610 int firstCol = Integer.MAX_VALUE;
1612 boolean matchFound = false;
1614 ViewportRanges ranges = av.getRanges();
1615 int firstVisibleColumn = ranges.getStartRes();
1616 int lastVisibleColumn = ranges.getEndRes();
1617 AlignmentI alignment = av.getAlignment();
1618 if (av.hasHiddenColumns())
1620 firstVisibleColumn = alignment.getHiddenColumns()
1621 .visibleToAbsoluteColumn(firstVisibleColumn);
1622 lastVisibleColumn = alignment.getHiddenColumns()
1623 .visibleToAbsoluteColumn(lastVisibleColumn);
1626 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1627 .getEndSeq(); seqNo++)
1629 SequenceI seq = alignment.getSequenceAt(seqNo);
1631 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1633 if (visibleResults != null)
1635 for (int i = 0; i < visibleResults.length - 1; i += 2)
1637 int firstMatchedColumn = visibleResults[i];
1638 int lastMatchedColumn = visibleResults[i + 1];
1639 if (firstMatchedColumn <= lastVisibleColumn
1640 && lastMatchedColumn >= firstVisibleColumn)
1643 * found a search results match in the visible region -
1644 * remember the first and last sequence matched, and the first
1645 * and last visible columns in the matched positions
1648 firstSeq = Math.min(firstSeq, seqNo);
1649 lastSeq = Math.max(lastSeq, seqNo);
1650 firstMatchedColumn = Math.max(firstMatchedColumn,
1651 firstVisibleColumn);
1652 lastMatchedColumn = Math.min(lastMatchedColumn,
1654 firstCol = Math.min(firstCol, firstMatchedColumn);
1655 lastCol = Math.max(lastCol, lastMatchedColumn);
1663 if (av.hasHiddenColumns())
1665 firstCol = alignment.getHiddenColumns()
1666 .absoluteToVisibleColumn(firstCol);
1667 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1669 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1670 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1671 Graphics gg = img.getGraphics();
1672 gg.translate(transX, transY);
1673 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1674 gg.translate(-transX, -transY);
1682 public void propertyChange(PropertyChangeEvent evt)
1684 String eventName = evt.getPropertyName();
1685 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1686 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1692 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1695 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1701 if (eventName.equals(ViewportRanges.STARTRES)
1702 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1704 // Make sure we're not trying to draw a panel
1705 // larger than the visible window
1706 if (eventName.equals(ViewportRanges.STARTRES))
1708 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1712 scrollX = ((int[]) evt.getNewValue())[0]
1713 - ((int[]) evt.getOldValue())[0];
1715 ViewportRanges vpRanges = av.getRanges();
1717 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1718 if (scrollX > range)
1722 else if (scrollX < -range)
1727 // Both scrolling and resizing change viewport ranges: scrolling changes
1728 // both start and end points, but resize only changes end values.
1729 // Here we only want to fastpaint on a scroll, with resize using a normal
1730 // paint, so scroll events are identified as changes to the horizontal or
1731 // vertical start value.
1732 if (eventName.equals(ViewportRanges.STARTRES))
1734 if (av.getWrapAlignment())
1736 fastPaintWrapped(scrollX);
1740 fastPaint(scrollX, 0);
1743 else if (eventName.equals(ViewportRanges.STARTSEQ))
1746 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1748 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1750 if (av.getWrapAlignment())
1752 fastPaintWrapped(scrollX);
1756 fastPaint(scrollX, 0);
1759 else if (eventName.equals(ViewportRanges.STARTSEQ))
1762 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1764 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1766 if (av.getWrapAlignment())
1768 fastPaintWrapped(scrollX);
1774 * Does a minimal update of the image for a scroll movement. This method
1775 * handles scroll movements of up to one width of the wrapped alignment (one
1776 * click in the vertical scrollbar). Larger movements (for example after a
1777 * scroll to highlight a mapped position) trigger a full redraw instead.
1780 * number of positions scrolled (right if positive, left if negative)
1782 protected void fastPaintWrapped(int scrollX)
1784 ViewportRanges ranges = av.getRanges();
1786 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1789 * shift of one view width or more is
1790 * overcomplicated to handle in this method
1797 if (fastpainting || img == null)
1803 fastpainting = true;
1808 Graphics gg = img.getGraphics();
1810 calculateWrappedGeometry(getWidth(), getHeight());
1813 * relocate the regions of the alignment that are still visible
1815 shiftWrappedAlignment(-scrollX);
1818 * add new columns (sequence, annotation)
1819 * - at top left if scrollX < 0
1820 * - at right of last two widths if scrollX > 0
1824 int startRes = ranges.getStartRes();
1825 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1826 - scrollX - 1, getHeight());
1830 fastPaintWrappedAddRight(scrollX);
1834 * draw all scales (if shown) and hidden column markers
1836 drawWrappedDecorators(gg, ranges.getStartRes());
1843 fastpainting = false;
1848 * Draws the specified number of columns at the 'end' (bottom right) of a
1849 * wrapped alignment view, including sequences and annotations if shown, but
1850 * not scales. Also draws the same number of columns at the right hand end of
1851 * the second last width shown, if the last width is not full height (so
1852 * cannot simply be copied from the graphics image).
1856 protected void fastPaintWrappedAddRight(int columns)
1863 Graphics gg = img.getGraphics();
1865 ViewportRanges ranges = av.getRanges();
1866 int viewportWidth = ranges.getViewportWidth();
1867 int charWidth = av.getCharWidth();
1870 * draw full height alignment in the second last row, last columns, if the
1871 * last row was not full height
1873 int visibleWidths = wrappedVisibleWidths;
1874 int canvasHeight = getHeight();
1875 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1877 if (lastWidthPartHeight)
1879 int widthsAbove = Math.max(0, visibleWidths - 2);
1880 int ypos = wrappedRepeatHeightPx * widthsAbove
1881 + wrappedSpaceAboveAlignment;
1882 int endRes = ranges.getEndRes();
1883 endRes += widthsAbove * viewportWidth;
1884 int startRes = endRes - columns;
1885 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1889 * white fill first to erase annotations
1893 gg.translate(xOffset, 0);
1894 gg.setColor(Color.white);
1895 gg.fillRect(labelWidthWest, ypos,
1896 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1897 gg.translate(-xOffset, 0);
1899 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1904 * draw newly visible columns in last wrapped width (none if we
1905 * have reached the end of the alignment)
1906 * y-offset for drawing last width is height of widths above,
1909 int widthsAbove = visibleWidths - 1;
1910 int ypos = wrappedRepeatHeightPx * widthsAbove
1911 + wrappedSpaceAboveAlignment;
1912 int endRes = ranges.getEndRes();
1913 endRes += widthsAbove * viewportWidth;
1914 int startRes = endRes - columns + 1;
1917 * white fill first to erase annotations
1919 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1921 gg.translate(xOffset, 0);
1922 gg.setColor(Color.white);
1923 int width = viewportWidth * charWidth - xOffset;
1924 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1925 gg.translate(-xOffset, 0);
1927 gg.setFont(av.getFont());
1928 gg.setColor(Color.black);
1930 if (startRes < ranges.getVisibleAlignmentWidth())
1932 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1936 * and finally, white fill any space below the visible alignment
1938 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1939 if (heightBelow > 0)
1941 gg.setColor(Color.white);
1942 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1948 * Shifts the visible alignment by the specified number of columns - left if
1949 * negative, right if positive. Copies and moves sequences and annotations (if
1950 * shown). Scales, hidden column markers and any newly visible columns must be
1955 protected void shiftWrappedAlignment(int positions)
1962 Graphics gg = img.getGraphics();
1964 int charWidth = av.getCharWidth();
1966 int canvasHeight = getHeight();
1967 ViewportRanges ranges = av.getRanges();
1968 int viewportWidth = ranges.getViewportWidth();
1969 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1971 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1972 int xMax = ranges.getVisibleAlignmentWidth();
1977 * shift right (after scroll left)
1978 * for each wrapped width (starting with the last), copy (width-positions)
1979 * columns from the left margin to the right margin, and copy positions
1980 * columns from the right margin of the row above (if any) to the
1981 * left margin of the current row
1985 * get y-offset of last wrapped width, first row of sequences
1987 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1988 y += wrappedSpaceAboveAlignment;
1989 int copyFromLeftStart = labelWidthWest;
1990 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1995 * shift 'widthToCopy' residues by 'positions' places to the right
1997 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1998 positions * charWidth, 0);
2002 * copy 'positions' residue from the row above (right hand end)
2003 * to this row's left hand end
2005 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2006 positions * charWidth, heightToCopy, -widthToCopy,
2007 wrappedRepeatHeightPx);
2010 y -= wrappedRepeatHeightPx;
2016 * shift left (after scroll right)
2017 * for each wrapped width (starting with the first), copy (width-positions)
2018 * columns from the right margin to the left margin, and copy positions
2019 * columns from the left margin of the row below (if any) to the
2020 * right margin of the current row
2022 int xpos = av.getRanges().getStartRes();
2023 int y = wrappedSpaceAboveAlignment;
2024 int copyFromRightStart = labelWidthWest - positions * charWidth;
2026 while (y < canvasHeight)
2028 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2029 positions * charWidth, 0);
2030 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2031 && (xpos + viewportWidth <= xMax))
2033 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2034 * charWidth, heightToCopy, widthToCopy,
2035 -wrappedRepeatHeightPx);
2037 y += wrappedRepeatHeightPx;
2038 xpos += viewportWidth;
2046 * Redraws any positions in the search results in the visible region of a
2047 * wrapped alignment. Any highlights are drawn depending on the search results
2048 * set on the Viewport, not the <code>results</code> argument. This allows
2049 * this method to be called either to clear highlights (passing the previous
2050 * search results), or to draw new highlights.
2055 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2057 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2061 int charHeight = av.getCharHeight();
2063 boolean matchFound = false;
2065 calculateWrappedGeometry(getWidth(), getHeight());
2066 int wrappedWidth = av.getWrappedWidth();
2067 int wrappedHeight = wrappedRepeatHeightPx;
2069 ViewportRanges ranges = av.getRanges();
2070 int canvasHeight = getHeight();
2071 int repeats = canvasHeight / wrappedHeight;
2072 if (canvasHeight / wrappedHeight > 0)
2077 int firstVisibleColumn = ranges.getStartRes();
2078 int lastVisibleColumn = ranges.getStartRes() + repeats
2079 * ranges.getViewportWidth() - 1;
2081 AlignmentI alignment = av.getAlignment();
2082 if (av.hasHiddenColumns())
2084 firstVisibleColumn = alignment.getHiddenColumns()
2085 .visibleToAbsoluteColumn(firstVisibleColumn);
2086 lastVisibleColumn = alignment.getHiddenColumns()
2087 .visibleToAbsoluteColumn(lastVisibleColumn);
2090 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2093 Graphics gg = img.getGraphics();
2095 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2096 .getEndSeq(); seqNo++)
2098 SequenceI seq = alignment.getSequenceAt(seqNo);
2100 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2102 if (visibleResults != null)
2104 for (int i = 0; i < visibleResults.length - 1; i += 2)
2106 int firstMatchedColumn = visibleResults[i];
2107 int lastMatchedColumn = visibleResults[i + 1];
2108 if (firstMatchedColumn <= lastVisibleColumn
2109 && lastMatchedColumn >= firstVisibleColumn)
2112 * found a search results match in the visible region
2114 firstMatchedColumn = Math.max(firstMatchedColumn,
2115 firstVisibleColumn);
2116 lastMatchedColumn = Math.min(lastMatchedColumn,
2120 * draw each mapped position separately (as contiguous positions may
2121 * wrap across lines)
2123 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2125 int displayColumn = mappedPos;
2126 if (av.hasHiddenColumns())
2128 displayColumn = alignment.getHiddenColumns()
2129 .absoluteToVisibleColumn(displayColumn);
2133 * transX: offset from left edge of canvas to residue position
2135 int transX = labelWidthWest
2136 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2137 * av.getCharWidth();
2140 * transY: offset from top edge of canvas to residue position
2142 int transY = gapHeight;
2143 transY += (displayColumn - ranges.getStartRes())
2144 / wrappedWidth * wrappedHeight;
2145 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2148 * yOffset is from graphics origin to start of visible region
2150 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2151 if (transY < getHeight())
2154 gg.translate(transX, transY);
2155 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2157 gg.translate(-transX, -transY);
2171 * Answers the width in pixels of the left scale labels (0 if not shown)
2175 int getLabelWidthWest()
2177 return labelWidthWest;