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 private 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)
290 // System.err.println("<<SeqCanvas fastPaint " + fastpainting + " "
291 // + horizontal + " " + vertical);
293 // if (horizontal != 0 && vertical != 0)
294 // throw new InvalidArgumentException();
295 if (fastpainting || img == null)
303 int charHeight = av.getCharHeight();
304 int charWidth = av.getCharWidth();
306 ViewportRanges ranges = av.getRanges();
307 int startRes = ranges.getStartRes();
308 int endRes = ranges.getEndRes();
309 int startSeq = ranges.getStartSeq();
310 int endSeq = ranges.getEndSeq();
314 if (horizontal > 0) // scrollbar pulled right, image to the left
316 transX = (endRes - startRes - horizontal) * charWidth;
317 startRes = endRes - horizontal;
319 else if (horizontal < 0)
321 endRes = startRes - horizontal;
324 if (vertical > 0) // scroll down
326 startSeq = endSeq - vertical;
328 if (startSeq < ranges.getStartSeq())
329 { // ie scrolling too fast, more than a page at a time
330 startSeq = ranges.getStartSeq();
334 transY = img.getHeight() - ((vertical + 1) * charHeight);
337 else if (vertical < 0)
339 endSeq = startSeq - vertical;
341 if (endSeq > ranges.getEndSeq())
343 endSeq = ranges.getEndSeq();
348 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
349 // + horizontal + " " + vertical + " " + startRes + " " + endRes
350 // + " " + startSeq + " " + endSeq);
352 Graphics gg = img.getGraphics();
353 gg.copyArea(horizontal * charWidth, vertical * charHeight,
354 img.getWidth(), img.getHeight(), -horizontal * charWidth,
355 -vertical * charHeight);
357 /** @j2sNative xxi = this.img */
359 gg.translate(transX, transY);
360 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
361 gg.translate(-transX, -transY);
364 // Call repaint on alignment panel so that repaints from other alignment
365 // panel components can be aggregated. Otherwise performance of the
366 // overview window and others may be adversely affected.
367 av.getAlignPanel().repaint();
370 fastpainting = false;
375 public void paintComponent(Graphics g)
377 int charHeight = av.getCharHeight();
378 int charWidth = av.getCharWidth();
380 int width = getWidth();
381 int height = getHeight();
383 width -= (width % charWidth);
384 height -= (height % charHeight);
386 // BH 2019 can't possibly fastPaint if width and height are 0
387 if (width == 0 || height == 0)
392 ViewportRanges ranges = av.getRanges();
393 int startRes = ranges.getStartRes();
394 int startSeq = ranges.getStartSeq();
395 int endRes = ranges.getEndRes();
396 int endSeq = ranges.getEndSeq();
398 // System.err.println(">>SeqCanvas paintComponent " + fastPaint + "\n"
399 // + getVisibleRect() + "\n" + g.getClipBounds());
400 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent "
401 // + startRes + " " + endRes + " " + startSeq + " " + endSeq);
405 || (vis = getVisibleRect()).width != (clip = g
406 .getClipBounds()).width
407 || vis.height != clip.height))
409 g.drawImage(img, 0, 0, this);
410 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent FAST");
411 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
419 * img is a cached version of the last view we drew.
420 * If we have no img or the size has changed, make a new one
422 if (img == null || width != img.getWidth()
423 || height != img.getHeight())
425 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
428 Graphics2D gg = (Graphics2D) img.getGraphics();
429 gg.setFont(av.getFont());
433 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
434 RenderingHints.VALUE_ANTIALIAS_ON);
437 gg.setColor(Color.white);
438 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
440 if (av.getWrapAlignment())
442 drawWrappedPanel(gg, width, height, ranges.getStartRes());
446 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
449 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
451 g.drawImage(img, 0, 0, this);
457 drawCursor(g, startRes, endRes, startSeq, endSeq);
462 * Draw an alignment panel for printing
465 * Graphics object to draw with
467 * start residue of print area
469 * end residue of print area
471 * start sequence of print area
473 * end sequence of print area
475 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
476 int startSeq, int endSeq)
478 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
480 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
485 * Draw a wrapped alignment panel for printing
488 * Graphics object to draw with
490 * width of drawing area
491 * @param canvasHeight
492 * height of drawing area
494 * start residue of print area
496 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
497 int canvasHeight, int startRes)
499 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
501 SequenceGroup group = av.getSelectionGroup();
504 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
510 * Returns the visible width of the canvas in residues, after allowing for
511 * East or West scales (if shown)
514 * the width in pixels (possibly including scales)
518 public int getWrappedCanvasWidth(int canvasWidth)
520 int charWidth = av.getCharWidth();
522 FontMetrics fm = getFontMetrics(av.getFont());
526 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
528 labelWidth = getLabelWidth(fm);
531 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
533 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
535 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
539 * Returns a pixel width sufficient to show the largest sequence coordinate
540 * (end position) in the alignment, calculated as the FontMetrics width of
541 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
542 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
543 * half a character width space on either side.
548 protected int getLabelWidth(FontMetrics fm)
551 * find the biggest sequence end position we need to show
552 * (note this is not necessarily the sequence length)
555 AlignmentI alignment = av.getAlignment();
556 for (int i = 0; i < alignment.getHeight(); i++)
558 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
562 for (int i = maxWidth; i > 0; i /= 10)
567 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
571 * Draws as many widths of a wrapped alignment as can fit in the visible
576 * available width in pixels
577 * @param canvasHeight
578 * available height in pixels
580 * the first column (0...) of the alignment to draw
582 public void drawWrappedPanel(Graphics g, int canvasWidth,
583 int canvasHeight, final int startColumn)
585 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
588 av.setWrappedWidth(wrappedWidthInResidues);
590 ViewportRanges ranges = av.getRanges();
591 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
593 // we need to call this again to make sure the startColumn +
594 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
596 calculateWrappedGeometry(canvasWidth, canvasHeight);
599 * draw one width at a time (excluding any scales shown),
600 * until we have run out of either alignment or vertical space available
602 int ypos = wrappedSpaceAboveAlignment;
603 int maxWidth = ranges.getVisibleAlignmentWidth();
605 int start = startColumn;
606 int currentWidth = 0;
607 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
610 .min(maxWidth, start + wrappedWidthInResidues - 1);
611 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
612 ypos += wrappedRepeatHeightPx;
613 start += wrappedWidthInResidues;
617 drawWrappedDecorators(g, startColumn);
621 * Calculates and saves values needed when rendering a wrapped alignment.
622 * These depend on many factors, including
624 * <li>canvas width and height</li>
625 * <li>number of visible sequences, and height of annotations if shown</li>
626 * <li>font and character width</li>
627 * <li>whether scales are shown left, right or above the alignment</li>
631 * @param canvasHeight
632 * @return the number of residue columns in each width
634 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
636 int charHeight = av.getCharHeight();
639 * vertical space in pixels between wrapped widths of alignment
640 * - one character height, or two if scale above is drawn
642 wrappedSpaceAboveAlignment = charHeight
643 * (av.getScaleAboveWrapped() ? 2 : 1);
646 * compute height in pixels of the wrapped widths
647 * - start with space above plus sequences
649 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
650 wrappedRepeatHeightPx += av.getAlignment().getHeight()
654 * add annotations panel height if shown
655 * also gap between sequences and annotations
657 if (av.isShowAnnotation())
659 wrappedRepeatHeightPx += getAnnotationHeight();
660 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
664 * number of visible widths (the last one may be part height),
665 * ensuring a part height includes at least one sequence
667 ViewportRanges ranges = av.getRanges();
668 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
669 int remainder = canvasHeight % wrappedRepeatHeightPx;
670 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
672 wrappedVisibleWidths++;
676 * compute width in residues; this also sets East and West label widths
678 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
681 * limit visibleWidths to not exceed width of alignment
683 int xMax = ranges.getVisibleAlignmentWidth();
684 int startToEnd = xMax - ranges.getStartRes();
685 int maxWidths = startToEnd / wrappedWidthInResidues;
686 if (startToEnd % wrappedWidthInResidues > 0)
690 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
692 return wrappedWidthInResidues;
696 * Draws one width of a wrapped alignment, including sequences and
697 * annnotations, if shown, but not scales or hidden column markers
703 * @param canvasHeight
705 protected void drawWrappedWidth(Graphics g, final int ypos,
706 final int startColumn, final int endColumn,
707 final int canvasHeight)
709 ViewportRanges ranges = av.getRanges();
710 int viewportWidth = ranges.getViewportWidth();
712 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
715 * move right before drawing by the width of the scale left (if any)
716 * plus column offset from left margin (usually zero, but may be non-zero
717 * when fast painting is drawing just a few columns)
719 int charWidth = av.getCharWidth();
720 int xOffset = labelWidthWest
721 + ((startColumn - ranges.getStartRes()) % viewportWidth)
724 g.translate(xOffset, 0);
727 * white fill the region to be drawn (so incremental fast paint doesn't
728 * scribble over an existing image)
730 g.setColor(Color.white);
731 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
732 wrappedRepeatHeightPx);
734 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
737 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
739 if (av.isShowAnnotation())
741 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
742 g.translate(0, yShift);
743 if (annotations == null)
745 annotations = new AnnotationPanel(av);
748 annotations.renderer.drawComponent(annotations, av, g, -1,
749 startColumn, endx + 1);
750 g.translate(0, -yShift);
752 g.translate(-xOffset, 0);
756 * Draws scales left, right and above (if shown), and any hidden column
757 * markers, on all widths of the wrapped alignment
762 protected void drawWrappedDecorators(Graphics g, final int startColumn)
764 int charWidth = av.getCharWidth();
766 g.setFont(av.getFont());
768 g.setColor(Color.black);
770 int ypos = wrappedSpaceAboveAlignment;
771 ViewportRanges ranges = av.getRanges();
772 int viewportWidth = ranges.getViewportWidth();
773 int maxWidth = ranges.getVisibleAlignmentWidth();
775 int startCol = startColumn;
777 while (widthsDrawn < wrappedVisibleWidths)
779 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
781 if (av.getScaleLeftWrapped())
783 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
786 if (av.getScaleRightWrapped())
788 int x = labelWidthWest + viewportWidth * charWidth;
791 drawVerticalScale(g, startCol, endColumn, ypos, false);
796 * white fill region of scale above and hidden column markers
797 * (to support incremental fast paint of image)
799 g.translate(labelWidthWest, 0);
800 g.setColor(Color.white);
801 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
802 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
803 g.setColor(Color.black);
804 g.translate(-labelWidthWest, 0);
806 g.translate(labelWidthWest, 0);
808 if (av.getScaleAboveWrapped())
810 drawNorthScale(g, startCol, endColumn, ypos);
813 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
815 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
818 g.translate(-labelWidthWest, 0);
820 ypos += wrappedRepeatHeightPx;
821 startCol += viewportWidth;
827 * Draws markers (triangles) above hidden column positions between startColumn
835 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
836 int startColumn, int endColumn)
838 int charHeight = av.getCharHeight();
839 int charWidth = av.getCharWidth();
841 g.setColor(Color.blue);
843 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
845 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
849 res = it.next() - startColumn;
851 if (res < 0 || res > endColumn - startColumn + 1)
857 * draw a downward-pointing triangle at the hidden columns location
858 * (before the following visible column)
860 int xMiddle = res * charWidth;
861 int[] xPoints = new int[] { xMiddle - charHeight / 4,
862 xMiddle + charHeight / 4, xMiddle };
863 int yTop = ypos - (charHeight / 2);
864 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
865 g.fillPolygon(xPoints, yPoints, 3);
870 * Draw a selection group over a wrapped alignment
872 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
874 int canvasHeight, int startRes)
876 int charHeight = av.getCharHeight();
877 int charWidth = av.getCharWidth();
879 // height gap above each panel
880 int hgap = charHeight;
881 if (av.getScaleAboveWrapped())
886 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
888 int cHeight = av.getAlignment().getHeight() * charHeight;
890 int startx = startRes;
892 int ypos = hgap; // vertical offset
893 int maxwidth = av.getAlignment().getVisibleWidth();
895 // chop the wrapped alignment extent up into panel-sized blocks and treat
896 // each block as if it were a block from an unwrapped alignment
897 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
898 BasicStroke.JOIN_ROUND, 3f, new float[]
900 g.setColor(Color.RED);
901 while ((ypos <= canvasHeight) && (startx < maxwidth))
903 // set end value to be start + width, or maxwidth, whichever is smaller
904 endx = startx + cWidth - 1;
911 g.translate(labelWidthWest, 0);
913 drawUnwrappedSelection(g, group, startx, endx, 0,
914 av.getAlignment().getHeight() - 1,
917 g.translate(-labelWidthWest, 0);
919 // update vertical offset
920 ypos += cHeight + getAnnotationHeight() + hgap;
922 // update horizontal offset
925 g.setStroke(new BasicStroke());
928 int getAnnotationHeight()
930 if (!av.isShowAnnotation())
935 if (annotations == null)
937 annotations = new AnnotationPanel(av);
940 return annotations.adjustPanelHeight();
944 * Draws the visible region of the alignment on the graphics context. If there
945 * are hidden column markers in the visible region, then each sub-region
946 * between the markers is drawn separately, followed by the hidden column
950 * the graphics context, positioned at the first residue to be drawn
952 * offset of the first column to draw (0..)
954 * offset of the last column to draw (0..)
956 * offset of the first sequence to draw (0..)
958 * offset of the last sequence to draw (0..)
960 * vertical offset at which to draw (for wrapped alignments)
962 public void drawPanel(Graphics g1, final int startRes, final int endRes,
963 final int startSeq, final int endSeq, final int yOffset)
965 int charHeight = av.getCharHeight();
966 int charWidth = av.getCharWidth();
968 if (!av.hasHiddenColumns())
970 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
978 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
979 VisibleContigsIterator regions = hidden
980 .getVisContigsIterator(startRes, endRes + 1, true);
982 while (regions.hasNext())
984 int[] region = regions.next();
985 blockEnd = region[1];
986 blockStart = region[0];
989 * draw up to just before the next hidden region, or the end of
990 * the visible region, whichever comes first
992 g1.translate(screenY * charWidth, 0);
994 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
997 * draw the downline of the hidden column marker (ScalePanel draws the
998 * triangle on top) if we reached it
1000 if (av.getShowHiddenMarkers()
1001 && (regions.hasNext() || regions.endsAtHidden()))
1003 g1.setColor(Color.blue);
1005 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1006 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1007 (endSeq - startSeq + 1) * charHeight + yOffset);
1010 g1.translate(-screenY * charWidth, 0);
1011 screenY += blockEnd - blockStart + 1;
1018 * Draws a region of the visible alignment
1022 * offset of the first column in the visible region (0..)
1024 * offset of the last column in the visible region (0..)
1026 * offset of the first sequence in the visible region (0..)
1028 * offset of the last sequence in the visible region (0..)
1030 * vertical offset at which to draw (for wrapped alignments)
1032 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1033 int endSeq, int offset)
1035 int charHeight = av.getCharHeight();
1036 int charWidth = av.getCharWidth();
1038 g.setFont(av.getFont());
1039 seqRdr.prepare(g, av.isRenderGaps());
1043 // / First draw the sequences
1044 // ///////////////////////////
1045 for (int i = startSeq; i <= endSeq; i++)
1047 nextSeq = av.getAlignment().getSequenceAt(i);
1048 if (nextSeq == null)
1050 // occasionally, a race condition occurs such that the alignment row is
1054 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1055 startRes, endRes, offset + ((i - startSeq) * charHeight));
1057 if (av.isShowSequenceFeatures())
1059 fr.drawSequence(g, nextSeq, startRes, endRes,
1060 offset + ((i - startSeq) * charHeight), false);
1064 * highlight search Results once sequence has been drawn
1066 if (av.hasSearchResults())
1068 SearchResultsI searchResults = av.getSearchResults();
1069 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1071 if (visibleResults != null)
1073 for (int r = 0; r < visibleResults.length; r += 2)
1075 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1076 visibleResults[r + 1],
1077 (visibleResults[r] - startRes) * charWidth,
1078 offset + ((i - startSeq) * charHeight));
1084 if (av.getSelectionGroup() != null
1085 || av.getAlignment().getGroups().size() > 0)
1087 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1093 * Draws the outlines of any groups defined on the alignment (excluding the
1094 * current selection group, if any)
1103 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1104 int startSeq, int endSeq, int offset)
1106 Graphics2D g = (Graphics2D) g1;
1108 SequenceGroup group = null;
1109 int groupIndex = -1;
1111 if (av.getAlignment().getGroups().size() > 0)
1113 group = av.getAlignment().getGroups().get(0);
1121 g.setColor(group.getOutlineColour());
1122 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1126 if (groupIndex >= av.getAlignment().getGroups().size())
1130 group = av.getAlignment().getGroups().get(groupIndex);
1131 } while (groupIndex < av.getAlignment().getGroups().size());
1136 * Draws the outline of the current selection group (if any)
1144 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1145 int startSeq, int endSeq)
1147 SequenceGroup group = av.getSelectionGroup();
1153 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1154 BasicStroke.JOIN_ROUND, 3f, new float[]
1156 g.setColor(Color.RED);
1157 if (!av.getWrapAlignment())
1159 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1164 drawWrappedSelection(g, group, getWidth(), getHeight(),
1165 av.getRanges().getStartRes());
1167 g.setStroke(new BasicStroke());
1171 * Draw the cursor as a separate image and overlay
1174 * start residue of area to draw cursor in
1176 * end residue of area to draw cursor in
1178 * start sequence of area to draw cursor in
1180 * end sequence of are to draw cursor in
1181 * @return a transparent image of the same size as the sequence canvas, with
1182 * the cursor drawn on it, if any
1184 private void drawCursor(Graphics g, int startRes, int endRes,
1188 // convert the cursorY into a position on the visible alignment
1189 int cursor_ypos = cursorY;
1191 // don't do work unless we have to
1192 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1196 int startx = startRes;
1199 // convert the cursorX into a position on the visible alignment
1200 int cursor_xpos = av.getAlignment().getHiddenColumns()
1201 .absoluteToVisibleColumn(cursorX);
1203 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1206 if (av.getWrapAlignment())
1208 // work out the correct offsets for the cursor
1209 int charHeight = av.getCharHeight();
1210 int charWidth = av.getCharWidth();
1211 int canvasWidth = getWidth();
1212 int canvasHeight = getHeight();
1214 // height gap above each panel
1215 int hgap = charHeight;
1216 if (av.getScaleAboveWrapped())
1221 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1223 int cHeight = av.getAlignment().getHeight() * charHeight;
1225 endx = startx + cWidth - 1;
1226 int ypos = hgap; // vertical offset
1228 // iterate down the wrapped panels
1229 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1231 // update vertical offset
1232 ypos += cHeight + getAnnotationHeight() + hgap;
1234 // update horizontal offset
1236 endx = startx + cWidth - 1;
1239 xoffset = labelWidthWest;
1242 // now check if cursor is within range for x values
1243 if (cursor_xpos >= startx && cursor_xpos <= endx)
1245 // get the character the cursor is drawn at
1246 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1247 char s = seq.getCharAt(cursorX);
1249 seqRdr.drawCursor(g, s,
1250 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1251 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1259 * Draw a selection group over an unwrapped alignment
1262 * graphics object to draw with
1266 * start residue of area to draw
1268 * end residue of area to draw
1270 * start sequence of area to draw
1272 * end sequence of area to draw
1274 * vertical offset (used when called from wrapped alignment code)
1276 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1277 int startRes, int endRes, int startSeq, int endSeq, int offset)
1279 int charWidth = av.getCharWidth();
1281 if (!av.hasHiddenColumns())
1283 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1288 // package into blocks of visible columns
1293 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1294 VisibleContigsIterator regions = hidden
1295 .getVisContigsIterator(startRes, endRes + 1, true);
1296 while (regions.hasNext())
1298 int[] region = regions.next();
1299 blockEnd = region[1];
1300 blockStart = region[0];
1302 g.translate(screenY * charWidth, 0);
1303 drawPartialGroupOutline(g, group,
1304 blockStart, blockEnd, startSeq, endSeq, offset);
1306 g.translate(-screenY * charWidth, 0);
1307 screenY += blockEnd - blockStart + 1;
1313 * Draws part of a selection group outline
1321 * @param verticalOffset
1323 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1324 int startRes, int endRes, int startSeq, int endSeq,
1327 int charHeight = av.getCharHeight();
1328 int charWidth = av.getCharWidth();
1329 int visWidth = (endRes - startRes + 1) * charWidth;
1333 boolean inGroup = false;
1338 List<SequenceI> seqs = group.getSequences(null);
1340 // position of start residue of group relative to startRes, in pixels
1341 int sx = (group.getStartRes() - startRes) * charWidth;
1343 // width of group in pixels
1344 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1347 if (!(sx + xwidth < 0 || sx > visWidth))
1349 for (i = startSeq; i <= endSeq; i++)
1351 sy = verticalOffset + (i - startSeq) * charHeight;
1353 if ((sx <= (endRes - startRes) * charWidth)
1354 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1357 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1359 bottom = sy + charHeight;
1364 if (((top == -1) && (i == 0)) || !seqs
1365 .contains(av.getAlignment().getSequenceAt(i - 1)))
1376 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1377 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1379 // reset top and bottom
1387 sy = verticalOffset + ((i - startSeq) * charHeight);
1388 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1389 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1395 * Draw horizontal selection group boundaries at top and bottom positions
1398 * graphics object to draw on
1404 * visWidth maximum available width
1406 * position to draw top of group at
1408 * position to draw bottom of group at
1410 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1411 int visWidth, int top, int bottom)
1421 // don't let width extend beyond current block, or group extent
1423 if (startx + width >= visWidth)
1425 width = visWidth - startx;
1430 g.drawLine(startx, top, startx + width, top);
1435 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1440 * Draw vertical lines at sx and sx+xwidth providing they lie within
1444 * graphics object to draw on
1450 * visWidth maximum available width
1456 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1459 // if start position is visible, draw vertical line to left of
1461 if (sx >= 0 && sx < visWidth)
1463 g.drawLine(sx, oldY, sx, sy);
1466 // if end position is visible, draw vertical line to right of
1468 if (sx + xwidth < visWidth)
1470 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1475 * Highlights search results in the visible region by rendering as white text
1476 * on a black background. Any previous highlighting is removed. Answers true
1477 * if any highlight was left on the visible alignment (so status bar should be
1478 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1479 * so allows the next repaint to update the whole display.
1484 public boolean highlightSearchResults(SearchResultsI results)
1486 return highlightSearchResults(results, false);
1491 * Highlights search results in the visible region by rendering as white text
1492 * on a black background. Any previous highlighting is removed. Answers true
1493 * if any highlight was left on the visible alignment (so status bar should be
1494 * set to match), else false.
1496 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1497 * highlighted regions are modified. This speeds up highlighting across linked
1500 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1501 * a wrapped alignment had to be scrolled to show the highlighted region, then
1502 * it should be fully redrawn, otherwise a fast paint can be performed. This
1503 * argument could be removed if fast paint of scrolled wrapped alignment is
1504 * coded in future (JAL-2609).
1507 * @param doFastPaint
1508 * if true, sets a flag so the next repaint only redraws the modified
1512 public boolean highlightSearchResults(SearchResultsI results,
1513 boolean doFastPaint)
1519 boolean wrapped = av.getWrapAlignment();
1522 fastPaint = doFastPaint;
1523 fastpainting = fastPaint;
1526 * to avoid redrawing the whole visible region, we instead
1527 * redraw just the minimal regions to remove previous highlights
1530 SearchResultsI previous = av.getSearchResults();
1531 av.setSearchResults(results);
1532 boolean redrawn = false;
1533 boolean drawn = false;
1536 redrawn = drawMappedPositionsWrapped(previous);
1537 drawn = drawMappedPositionsWrapped(results);
1542 redrawn = drawMappedPositions(previous);
1543 drawn = drawMappedPositions(results);
1548 * if highlights were either removed or added, repaint
1556 * return true only if highlights were added
1562 fastpainting = false;
1567 * Redraws the minimal rectangle in the visible region (if any) that includes
1568 * mapped positions of the given search results. Whether or not positions are
1569 * highlighted depends on the SearchResults set on the Viewport. This allows
1570 * this method to be called to either clear or set highlighting. Answers true
1571 * if any positions were drawn (in which case a repaint is still required),
1577 protected boolean drawMappedPositions(SearchResultsI results)
1579 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1585 * calculate the minimal rectangle to redraw that
1586 * includes both new and existing search results
1588 int firstSeq = Integer.MAX_VALUE;
1590 int firstCol = Integer.MAX_VALUE;
1592 boolean matchFound = false;
1594 ViewportRanges ranges = av.getRanges();
1595 int firstVisibleColumn = ranges.getStartRes();
1596 int lastVisibleColumn = ranges.getEndRes();
1597 AlignmentI alignment = av.getAlignment();
1598 if (av.hasHiddenColumns())
1600 firstVisibleColumn = alignment.getHiddenColumns()
1601 .visibleToAbsoluteColumn(firstVisibleColumn);
1602 lastVisibleColumn = alignment.getHiddenColumns()
1603 .visibleToAbsoluteColumn(lastVisibleColumn);
1606 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1607 .getEndSeq(); seqNo++)
1609 SequenceI seq = alignment.getSequenceAt(seqNo);
1611 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1613 if (visibleResults != null)
1615 for (int i = 0; i < visibleResults.length - 1; i += 2)
1617 int firstMatchedColumn = visibleResults[i];
1618 int lastMatchedColumn = visibleResults[i + 1];
1619 if (firstMatchedColumn <= lastVisibleColumn
1620 && lastMatchedColumn >= firstVisibleColumn)
1623 * found a search results match in the visible region -
1624 * remember the first and last sequence matched, and the first
1625 * and last visible columns in the matched positions
1628 firstSeq = Math.min(firstSeq, seqNo);
1629 lastSeq = Math.max(lastSeq, seqNo);
1630 firstMatchedColumn = Math.max(firstMatchedColumn,
1631 firstVisibleColumn);
1632 lastMatchedColumn = Math.min(lastMatchedColumn,
1634 firstCol = Math.min(firstCol, firstMatchedColumn);
1635 lastCol = Math.max(lastCol, lastMatchedColumn);
1643 if (av.hasHiddenColumns())
1645 firstCol = alignment.getHiddenColumns()
1646 .absoluteToVisibleColumn(firstCol);
1647 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1649 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1650 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1651 Graphics gg = img.getGraphics();
1652 gg.translate(transX, transY);
1653 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1654 gg.translate(-transX, -transY);
1662 public void propertyChange(PropertyChangeEvent evt)
1664 String eventName = evt.getPropertyName();
1665 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1666 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1672 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1675 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1681 if (eventName.equals(ViewportRanges.STARTRES)
1682 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1684 // Make sure we're not trying to draw a panel
1685 // larger than the visible window
1686 if (eventName.equals(ViewportRanges.STARTRES))
1688 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1692 scrollX = ((int[]) evt.getNewValue())[0]
1693 - ((int[]) evt.getOldValue())[0];
1695 ViewportRanges vpRanges = av.getRanges();
1697 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1698 if (scrollX > range)
1702 else if (scrollX < -range)
1707 // Both scrolling and resizing change viewport ranges: scrolling changes
1708 // both start and end points, but resize only changes end values.
1709 // Here we only want to fastpaint on a scroll, with resize using a normal
1710 // paint, so scroll events are identified as changes to the horizontal or
1711 // vertical start value.
1712 if (eventName.equals(ViewportRanges.STARTRES))
1714 if (av.getWrapAlignment())
1716 fastPaintWrapped(scrollX);
1720 fastPaint(scrollX, 0);
1723 else if (eventName.equals(ViewportRanges.STARTSEQ))
1726 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1728 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1730 if (av.getWrapAlignment())
1732 fastPaintWrapped(scrollX);
1736 fastPaint(scrollX, 0);
1739 else if (eventName.equals(ViewportRanges.STARTSEQ))
1742 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1744 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1746 if (av.getWrapAlignment())
1748 fastPaintWrapped(scrollX);
1754 * Does a minimal update of the image for a scroll movement. This method
1755 * handles scroll movements of up to one width of the wrapped alignment (one
1756 * click in the vertical scrollbar). Larger movements (for example after a
1757 * scroll to highlight a mapped position) trigger a full redraw instead.
1760 * number of positions scrolled (right if positive, left if negative)
1762 protected void fastPaintWrapped(int scrollX)
1764 ViewportRanges ranges = av.getRanges();
1766 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1769 * shift of one view width or more is
1770 * overcomplicated to handle in this method
1777 if (fastpainting || img == null)
1783 fastpainting = true;
1788 Graphics gg = img.getGraphics();
1790 calculateWrappedGeometry(getWidth(), getHeight());
1793 * relocate the regions of the alignment that are still visible
1795 shiftWrappedAlignment(-scrollX);
1798 * add new columns (sequence, annotation)
1799 * - at top left if scrollX < 0
1800 * - at right of last two widths if scrollX > 0
1804 int startRes = ranges.getStartRes();
1805 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1806 - scrollX - 1, getHeight());
1810 fastPaintWrappedAddRight(scrollX);
1814 * draw all scales (if shown) and hidden column markers
1816 drawWrappedDecorators(gg, ranges.getStartRes());
1823 fastpainting = false;
1828 * Draws the specified number of columns at the 'end' (bottom right) of a
1829 * wrapped alignment view, including sequences and annotations if shown, but
1830 * not scales. Also draws the same number of columns at the right hand end of
1831 * the second last width shown, if the last width is not full height (so
1832 * cannot simply be copied from the graphics image).
1836 protected void fastPaintWrappedAddRight(int columns)
1843 Graphics gg = img.getGraphics();
1845 ViewportRanges ranges = av.getRanges();
1846 int viewportWidth = ranges.getViewportWidth();
1847 int charWidth = av.getCharWidth();
1850 * draw full height alignment in the second last row, last columns, if the
1851 * last row was not full height
1853 int visibleWidths = wrappedVisibleWidths;
1854 int canvasHeight = getHeight();
1855 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1857 if (lastWidthPartHeight)
1859 int widthsAbove = Math.max(0, visibleWidths - 2);
1860 int ypos = wrappedRepeatHeightPx * widthsAbove
1861 + wrappedSpaceAboveAlignment;
1862 int endRes = ranges.getEndRes();
1863 endRes += widthsAbove * viewportWidth;
1864 int startRes = endRes - columns;
1865 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1869 * white fill first to erase annotations
1873 gg.translate(xOffset, 0);
1874 gg.setColor(Color.white);
1875 gg.fillRect(labelWidthWest, ypos,
1876 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1877 gg.translate(-xOffset, 0);
1879 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1884 * draw newly visible columns in last wrapped width (none if we
1885 * have reached the end of the alignment)
1886 * y-offset for drawing last width is height of widths above,
1889 int widthsAbove = visibleWidths - 1;
1890 int ypos = wrappedRepeatHeightPx * widthsAbove
1891 + wrappedSpaceAboveAlignment;
1892 int endRes = ranges.getEndRes();
1893 endRes += widthsAbove * viewportWidth;
1894 int startRes = endRes - columns + 1;
1897 * white fill first to erase annotations
1899 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1901 gg.translate(xOffset, 0);
1902 gg.setColor(Color.white);
1903 int width = viewportWidth * charWidth - xOffset;
1904 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1905 gg.translate(-xOffset, 0);
1907 gg.setFont(av.getFont());
1908 gg.setColor(Color.black);
1910 if (startRes < ranges.getVisibleAlignmentWidth())
1912 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1916 * and finally, white fill any space below the visible alignment
1918 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1919 if (heightBelow > 0)
1921 gg.setColor(Color.white);
1922 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1928 * Shifts the visible alignment by the specified number of columns - left if
1929 * negative, right if positive. Copies and moves sequences and annotations (if
1930 * shown). Scales, hidden column markers and any newly visible columns must be
1935 protected void shiftWrappedAlignment(int positions)
1942 Graphics gg = img.getGraphics();
1944 int charWidth = av.getCharWidth();
1946 int canvasHeight = getHeight();
1947 ViewportRanges ranges = av.getRanges();
1948 int viewportWidth = ranges.getViewportWidth();
1949 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1951 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1952 int xMax = ranges.getVisibleAlignmentWidth();
1957 * shift right (after scroll left)
1958 * for each wrapped width (starting with the last), copy (width-positions)
1959 * columns from the left margin to the right margin, and copy positions
1960 * columns from the right margin of the row above (if any) to the
1961 * left margin of the current row
1965 * get y-offset of last wrapped width, first row of sequences
1967 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1968 y += wrappedSpaceAboveAlignment;
1969 int copyFromLeftStart = labelWidthWest;
1970 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1975 * shift 'widthToCopy' residues by 'positions' places to the right
1977 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1978 positions * charWidth, 0);
1982 * copy 'positions' residue from the row above (right hand end)
1983 * to this row's left hand end
1985 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1986 positions * charWidth, heightToCopy, -widthToCopy,
1987 wrappedRepeatHeightPx);
1990 y -= wrappedRepeatHeightPx;
1996 * shift left (after scroll right)
1997 * for each wrapped width (starting with the first), copy (width-positions)
1998 * columns from the right margin to the left margin, and copy positions
1999 * columns from the left margin of the row below (if any) to the
2000 * right margin of the current row
2002 int xpos = av.getRanges().getStartRes();
2003 int y = wrappedSpaceAboveAlignment;
2004 int copyFromRightStart = labelWidthWest - positions * charWidth;
2006 while (y < canvasHeight)
2008 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2009 positions * charWidth, 0);
2010 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2011 && (xpos + viewportWidth <= xMax))
2013 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2014 * charWidth, heightToCopy, widthToCopy,
2015 -wrappedRepeatHeightPx);
2017 y += wrappedRepeatHeightPx;
2018 xpos += viewportWidth;
2026 * Redraws any positions in the search results in the visible region of a
2027 * wrapped alignment. Any highlights are drawn depending on the search results
2028 * set on the Viewport, not the <code>results</code> argument. This allows
2029 * this method to be called either to clear highlights (passing the previous
2030 * search results), or to draw new highlights.
2035 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2037 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2041 int charHeight = av.getCharHeight();
2043 boolean matchFound = false;
2045 calculateWrappedGeometry(getWidth(), getHeight());
2046 int wrappedWidth = av.getWrappedWidth();
2047 int wrappedHeight = wrappedRepeatHeightPx;
2049 ViewportRanges ranges = av.getRanges();
2050 int canvasHeight = getHeight();
2051 int repeats = canvasHeight / wrappedHeight;
2052 if (canvasHeight / wrappedHeight > 0)
2057 int firstVisibleColumn = ranges.getStartRes();
2058 int lastVisibleColumn = ranges.getStartRes() + repeats
2059 * ranges.getViewportWidth() - 1;
2061 AlignmentI alignment = av.getAlignment();
2062 if (av.hasHiddenColumns())
2064 firstVisibleColumn = alignment.getHiddenColumns()
2065 .visibleToAbsoluteColumn(firstVisibleColumn);
2066 lastVisibleColumn = alignment.getHiddenColumns()
2067 .visibleToAbsoluteColumn(lastVisibleColumn);
2070 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2073 Graphics gg = img.getGraphics();
2075 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2076 .getEndSeq(); seqNo++)
2078 SequenceI seq = alignment.getSequenceAt(seqNo);
2080 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2082 if (visibleResults != null)
2084 for (int i = 0; i < visibleResults.length - 1; i += 2)
2086 int firstMatchedColumn = visibleResults[i];
2087 int lastMatchedColumn = visibleResults[i + 1];
2088 if (firstMatchedColumn <= lastVisibleColumn
2089 && lastMatchedColumn >= firstVisibleColumn)
2092 * found a search results match in the visible region
2094 firstMatchedColumn = Math.max(firstMatchedColumn,
2095 firstVisibleColumn);
2096 lastMatchedColumn = Math.min(lastMatchedColumn,
2100 * draw each mapped position separately (as contiguous positions may
2101 * wrap across lines)
2103 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2105 int displayColumn = mappedPos;
2106 if (av.hasHiddenColumns())
2108 displayColumn = alignment.getHiddenColumns()
2109 .absoluteToVisibleColumn(displayColumn);
2113 * transX: offset from left edge of canvas to residue position
2115 int transX = labelWidthWest
2116 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2117 * av.getCharWidth();
2120 * transY: offset from top edge of canvas to residue position
2122 int transY = gapHeight;
2123 transY += (displayColumn - ranges.getStartRes())
2124 / wrappedWidth * wrappedHeight;
2125 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2128 * yOffset is from graphics origin to start of visible region
2130 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2131 if (transY < getHeight())
2134 gg.translate(transX, transY);
2135 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2137 gg.translate(-transX, -transY);
2151 * Answers the width in pixels of the left scale labels (0 if not shown)
2155 int getLabelWidthWest()
2157 return labelWidthWest;
2161 * Ensure that a full paint is done next, for whatever reason. This was
2162 * necessary for JavaScript; apparently in Java the timing is just right on
2163 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
2164 * away with one fast paint before the others, but this ensures that in the
2165 * end we get a full paint. Problem arose in relation to copy/paste, where the
2166 * paste was not finalized with a full paint.
2168 * @author hansonr 2019.04.17
2170 public void clearFastPaint()