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 // System.out.println("SeqCanvas fastPaint() repaint() request...");
368 av.getAlignPanel().repaint();
371 fastpainting = false;
376 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 width and height are 0
388 if (width == 0 || height == 0)
393 ViewportRanges ranges = av.getRanges();
394 int startRes = ranges.getStartRes();
395 int startSeq = ranges.getStartSeq();
396 int endRes = ranges.getEndRes();
397 int endSeq = ranges.getEndSeq();
399 // System.err.println(">>SeqCanvas paintComponent " + fastPaint + "\n"
400 // + getVisibleRect() + "\n" + g.getClipBounds());
401 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent "
402 // + startRes + " " + endRes + " " + startSeq + " " + endSeq);
406 || (vis = getVisibleRect()).width != (clip = g
407 .getClipBounds()).width
408 || vis.height != clip.height))
410 g.drawImage(img, 0, 0, this);
411 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent FAST");
412 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
415 //System.out.println("SeqCanvas fast paint");
420 System.out.println("SeqCanvas full paint");
422 * img is a cached version of the last view we drew.
423 * If we have no img or the size has changed, make a new one
425 if (img == null || width != img.getWidth()
426 || height != img.getHeight())
428 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
431 Graphics2D gg = (Graphics2D) img.getGraphics();
432 gg.setFont(av.getFont());
436 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
437 RenderingHints.VALUE_ANTIALIAS_ON);
440 gg.setColor(Color.white);
441 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
443 if (av.getWrapAlignment())
445 drawWrappedPanel(gg, width, height, ranges.getStartRes());
449 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
452 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
454 g.drawImage(img, 0, 0, this);
460 drawCursor(g, startRes, endRes, startSeq, endSeq);
465 * Draw an alignment panel for printing
468 * Graphics object to draw with
470 * start residue of print area
472 * end residue of print area
474 * start sequence of print area
476 * end sequence of print area
478 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
479 int startSeq, int endSeq)
481 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
483 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
488 * Draw a wrapped alignment panel for printing
491 * Graphics object to draw with
493 * width of drawing area
494 * @param canvasHeight
495 * height of drawing area
497 * start residue of print area
499 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
500 int canvasHeight, int startRes)
502 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
504 SequenceGroup group = av.getSelectionGroup();
507 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
513 * Returns the visible width of the canvas in residues, after allowing for
514 * East or West scales (if shown)
517 * the width in pixels (possibly including scales)
521 public int getWrappedCanvasWidth(int canvasWidth)
523 int charWidth = av.getCharWidth();
525 FontMetrics fm = getFontMetrics(av.getFont());
529 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
531 labelWidth = getLabelWidth(fm);
534 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
536 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
538 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
542 * Returns a pixel width sufficient to show the largest sequence coordinate
543 * (end position) in the alignment, calculated as the FontMetrics width of
544 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
545 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
546 * half a character width space on either side.
551 protected int getLabelWidth(FontMetrics fm)
554 * find the biggest sequence end position we need to show
555 * (note this is not necessarily the sequence length)
558 AlignmentI alignment = av.getAlignment();
559 for (int i = 0; i < alignment.getHeight(); i++)
561 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
565 for (int i = maxWidth; i > 0; i /= 10)
570 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
574 * Draws as many widths of a wrapped alignment as can fit in the visible
579 * available width in pixels
580 * @param canvasHeight
581 * available height in pixels
583 * the first column (0...) of the alignment to draw
585 public void drawWrappedPanel(Graphics g, int canvasWidth,
586 int canvasHeight, final int startColumn)
588 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
591 av.setWrappedWidth(wrappedWidthInResidues);
593 ViewportRanges ranges = av.getRanges();
594 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
596 // we need to call this again to make sure the startColumn +
597 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
599 calculateWrappedGeometry(canvasWidth, canvasHeight);
602 * draw one width at a time (excluding any scales shown),
603 * until we have run out of either alignment or vertical space available
605 int ypos = wrappedSpaceAboveAlignment;
606 int maxWidth = ranges.getVisibleAlignmentWidth();
608 int start = startColumn;
609 int currentWidth = 0;
610 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
613 .min(maxWidth, start + wrappedWidthInResidues - 1);
614 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
615 ypos += wrappedRepeatHeightPx;
616 start += wrappedWidthInResidues;
620 drawWrappedDecorators(g, startColumn);
624 * Calculates and saves values needed when rendering a wrapped alignment.
625 * These depend on many factors, including
627 * <li>canvas width and height</li>
628 * <li>number of visible sequences, and height of annotations if shown</li>
629 * <li>font and character width</li>
630 * <li>whether scales are shown left, right or above the alignment</li>
634 * @param canvasHeight
635 * @return the number of residue columns in each width
637 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
639 int charHeight = av.getCharHeight();
642 * vertical space in pixels between wrapped widths of alignment
643 * - one character height, or two if scale above is drawn
645 wrappedSpaceAboveAlignment = charHeight
646 * (av.getScaleAboveWrapped() ? 2 : 1);
649 * compute height in pixels of the wrapped widths
650 * - start with space above plus sequences
652 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
653 wrappedRepeatHeightPx += av.getAlignment().getHeight()
657 * add annotations panel height if shown
658 * also gap between sequences and annotations
660 if (av.isShowAnnotation())
662 wrappedRepeatHeightPx += getAnnotationHeight();
663 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
667 * number of visible widths (the last one may be part height),
668 * ensuring a part height includes at least one sequence
670 ViewportRanges ranges = av.getRanges();
671 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
672 int remainder = canvasHeight % wrappedRepeatHeightPx;
673 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
675 wrappedVisibleWidths++;
679 * compute width in residues; this also sets East and West label widths
681 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
684 * limit visibleWidths to not exceed width of alignment
686 int xMax = ranges.getVisibleAlignmentWidth();
687 int startToEnd = xMax - ranges.getStartRes();
688 int maxWidths = startToEnd / wrappedWidthInResidues;
689 if (startToEnd % wrappedWidthInResidues > 0)
693 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
695 return wrappedWidthInResidues;
699 * Draws one width of a wrapped alignment, including sequences and
700 * annnotations, if shown, but not scales or hidden column markers
706 * @param canvasHeight
708 protected void drawWrappedWidth(Graphics g, final int ypos,
709 final int startColumn, final int endColumn,
710 final int canvasHeight)
712 ViewportRanges ranges = av.getRanges();
713 int viewportWidth = ranges.getViewportWidth();
715 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
718 * move right before drawing by the width of the scale left (if any)
719 * plus column offset from left margin (usually zero, but may be non-zero
720 * when fast painting is drawing just a few columns)
722 int charWidth = av.getCharWidth();
723 int xOffset = labelWidthWest
724 + ((startColumn - ranges.getStartRes()) % viewportWidth)
727 g.translate(xOffset, 0);
730 * white fill the region to be drawn (so incremental fast paint doesn't
731 * scribble over an existing image)
733 g.setColor(Color.white);
734 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
735 wrappedRepeatHeightPx);
737 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
740 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
742 if (av.isShowAnnotation())
744 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
745 g.translate(0, yShift);
746 if (annotations == null)
748 annotations = new AnnotationPanel(av);
751 annotations.renderer.drawComponent(annotations, av, g, -1,
752 startColumn, endx + 1);
753 g.translate(0, -yShift);
755 g.translate(-xOffset, 0);
759 * Draws scales left, right and above (if shown), and any hidden column
760 * markers, on all widths of the wrapped alignment
765 protected void drawWrappedDecorators(Graphics g, final int startColumn)
767 int charWidth = av.getCharWidth();
769 g.setFont(av.getFont());
771 g.setColor(Color.black);
773 int ypos = wrappedSpaceAboveAlignment;
774 ViewportRanges ranges = av.getRanges();
775 int viewportWidth = ranges.getViewportWidth();
776 int maxWidth = ranges.getVisibleAlignmentWidth();
778 int startCol = startColumn;
780 while (widthsDrawn < wrappedVisibleWidths)
782 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
784 if (av.getScaleLeftWrapped())
786 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
789 if (av.getScaleRightWrapped())
791 int x = labelWidthWest + viewportWidth * charWidth;
794 drawVerticalScale(g, startCol, endColumn, ypos, false);
799 * white fill region of scale above and hidden column markers
800 * (to support incremental fast paint of image)
802 g.translate(labelWidthWest, 0);
803 g.setColor(Color.white);
804 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
805 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
806 g.setColor(Color.black);
807 g.translate(-labelWidthWest, 0);
809 g.translate(labelWidthWest, 0);
811 if (av.getScaleAboveWrapped())
813 drawNorthScale(g, startCol, endColumn, ypos);
816 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
818 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
821 g.translate(-labelWidthWest, 0);
823 ypos += wrappedRepeatHeightPx;
824 startCol += viewportWidth;
830 * Draws markers (triangles) above hidden column positions between startColumn
838 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
839 int startColumn, int endColumn)
841 int charHeight = av.getCharHeight();
842 int charWidth = av.getCharWidth();
844 g.setColor(Color.blue);
846 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
848 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
852 res = it.next() - startColumn;
854 if (res < 0 || res > endColumn - startColumn + 1)
860 * draw a downward-pointing triangle at the hidden columns location
861 * (before the following visible column)
863 int xMiddle = res * charWidth;
864 int[] xPoints = new int[] { xMiddle - charHeight / 4,
865 xMiddle + charHeight / 4, xMiddle };
866 int yTop = ypos - (charHeight / 2);
867 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
868 g.fillPolygon(xPoints, yPoints, 3);
873 * Draw a selection group over a wrapped alignment
875 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
877 int canvasHeight, int startRes)
879 int charHeight = av.getCharHeight();
880 int charWidth = av.getCharWidth();
882 // height gap above each panel
883 int hgap = charHeight;
884 if (av.getScaleAboveWrapped())
889 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
891 int cHeight = av.getAlignment().getHeight() * charHeight;
893 int startx = startRes;
895 int ypos = hgap; // vertical offset
896 int maxwidth = av.getAlignment().getVisibleWidth();
898 // chop the wrapped alignment extent up into panel-sized blocks and treat
899 // each block as if it were a block from an unwrapped alignment
900 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
901 BasicStroke.JOIN_ROUND, 3f, new float[]
903 g.setColor(Color.RED);
904 while ((ypos <= canvasHeight) && (startx < maxwidth))
906 // set end value to be start + width, or maxwidth, whichever is smaller
907 endx = startx + cWidth - 1;
914 g.translate(labelWidthWest, 0);
916 drawUnwrappedSelection(g, group, startx, endx, 0,
917 av.getAlignment().getHeight() - 1,
920 g.translate(-labelWidthWest, 0);
922 // update vertical offset
923 ypos += cHeight + getAnnotationHeight() + hgap;
925 // update horizontal offset
928 g.setStroke(new BasicStroke());
931 int getAnnotationHeight()
933 if (!av.isShowAnnotation())
938 if (annotations == null)
940 annotations = new AnnotationPanel(av);
943 return annotations.adjustPanelHeight();
947 * Draws the visible region of the alignment on the graphics context. If there
948 * are hidden column markers in the visible region, then each sub-region
949 * between the markers is drawn separately, followed by the hidden column
953 * the graphics context, positioned at the first residue to be drawn
955 * offset of the first column to draw (0..)
957 * offset of the last column to draw (0..)
959 * offset of the first sequence to draw (0..)
961 * offset of the last sequence to draw (0..)
963 * vertical offset at which to draw (for wrapped alignments)
965 public void drawPanel(Graphics g1, final int startRes, final int endRes,
966 final int startSeq, final int endSeq, final int yOffset)
968 int charHeight = av.getCharHeight();
969 int charWidth = av.getCharWidth();
971 if (!av.hasHiddenColumns())
973 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
981 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
982 VisibleContigsIterator regions = hidden
983 .getVisContigsIterator(startRes, endRes + 1, true);
985 while (regions.hasNext())
987 int[] region = regions.next();
988 blockEnd = region[1];
989 blockStart = region[0];
992 * draw up to just before the next hidden region, or the end of
993 * the visible region, whichever comes first
995 g1.translate(screenY * charWidth, 0);
997 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1000 * draw the downline of the hidden column marker (ScalePanel draws the
1001 * triangle on top) if we reached it
1003 if (av.getShowHiddenMarkers()
1004 && (regions.hasNext() || regions.endsAtHidden()))
1006 g1.setColor(Color.blue);
1008 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1009 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1010 (endSeq - startSeq + 1) * charHeight + yOffset);
1013 g1.translate(-screenY * charWidth, 0);
1014 screenY += blockEnd - blockStart + 1;
1021 * Draws a region of the visible alignment
1025 * offset of the first column in the visible region (0..)
1027 * offset of the last column in the visible region (0..)
1029 * offset of the first sequence in the visible region (0..)
1031 * offset of the last sequence in the visible region (0..)
1033 * vertical offset at which to draw (for wrapped alignments)
1035 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1036 int endSeq, int offset)
1038 int charHeight = av.getCharHeight();
1039 int charWidth = av.getCharWidth();
1041 g.setFont(av.getFont());
1042 seqRdr.prepare(g, av.isRenderGaps());
1046 // / First draw the sequences
1047 // ///////////////////////////
1048 for (int i = startSeq; i <= endSeq; i++)
1050 nextSeq = av.getAlignment().getSequenceAt(i);
1051 if (nextSeq == null)
1053 // occasionally, a race condition occurs such that the alignment row is
1057 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1058 startRes, endRes, offset + ((i - startSeq) * charHeight));
1060 if (av.isShowSequenceFeatures())
1062 fr.drawSequence(g, nextSeq, startRes, endRes,
1063 offset + ((i - startSeq) * charHeight), false);
1067 * highlight search Results once sequence has been drawn
1069 if (av.hasSearchResults())
1071 SearchResultsI searchResults = av.getSearchResults();
1072 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1074 if (visibleResults != null)
1076 for (int r = 0; r < visibleResults.length; r += 2)
1078 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1079 visibleResults[r + 1],
1080 (visibleResults[r] - startRes) * charWidth,
1081 offset + ((i - startSeq) * charHeight));
1087 if (av.getSelectionGroup() != null
1088 || av.getAlignment().getGroups().size() > 0)
1090 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1096 * Draws the outlines of any groups defined on the alignment (excluding the
1097 * current selection group, if any)
1106 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1107 int startSeq, int endSeq, int offset)
1109 Graphics2D g = (Graphics2D) g1;
1111 SequenceGroup group = null;
1112 int groupIndex = -1;
1114 if (av.getAlignment().getGroups().size() > 0)
1116 group = av.getAlignment().getGroups().get(0);
1124 g.setColor(group.getOutlineColour());
1125 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1129 if (groupIndex >= av.getAlignment().getGroups().size())
1133 group = av.getAlignment().getGroups().get(groupIndex);
1134 } while (groupIndex < av.getAlignment().getGroups().size());
1139 * Draws the outline of the current selection group (if any)
1147 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1148 int startSeq, int endSeq)
1150 SequenceGroup group = av.getSelectionGroup();
1156 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1157 BasicStroke.JOIN_ROUND, 3f, new float[]
1159 g.setColor(Color.RED);
1160 if (!av.getWrapAlignment())
1162 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1167 drawWrappedSelection(g, group, getWidth(), getHeight(),
1168 av.getRanges().getStartRes());
1170 g.setStroke(new BasicStroke());
1174 * Draw the cursor as a separate image and overlay
1177 * start residue of area to draw cursor in
1179 * end residue of area to draw cursor in
1181 * start sequence of area to draw cursor in
1183 * end sequence of are to draw cursor in
1184 * @return a transparent image of the same size as the sequence canvas, with
1185 * the cursor drawn on it, if any
1187 private void drawCursor(Graphics g, int startRes, int endRes,
1191 // convert the cursorY into a position on the visible alignment
1192 int cursor_ypos = cursorY;
1194 // don't do work unless we have to
1195 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1199 int startx = startRes;
1202 // convert the cursorX into a position on the visible alignment
1203 int cursor_xpos = av.getAlignment().getHiddenColumns()
1204 .absoluteToVisibleColumn(cursorX);
1206 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1209 if (av.getWrapAlignment())
1211 // work out the correct offsets for the cursor
1212 int charHeight = av.getCharHeight();
1213 int charWidth = av.getCharWidth();
1214 int canvasWidth = getWidth();
1215 int canvasHeight = getHeight();
1217 // height gap above each panel
1218 int hgap = charHeight;
1219 if (av.getScaleAboveWrapped())
1224 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1226 int cHeight = av.getAlignment().getHeight() * charHeight;
1228 endx = startx + cWidth - 1;
1229 int ypos = hgap; // vertical offset
1231 // iterate down the wrapped panels
1232 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1234 // update vertical offset
1235 ypos += cHeight + getAnnotationHeight() + hgap;
1237 // update horizontal offset
1239 endx = startx + cWidth - 1;
1242 xoffset = labelWidthWest;
1245 // now check if cursor is within range for x values
1246 if (cursor_xpos >= startx && cursor_xpos <= endx)
1248 // get the character the cursor is drawn at
1249 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1250 char s = seq.getCharAt(cursorX);
1252 seqRdr.drawCursor(g, s,
1253 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1254 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1262 * Draw a selection group over an unwrapped alignment
1265 * graphics object to draw with
1269 * start residue of area to draw
1271 * end residue of area to draw
1273 * start sequence of area to draw
1275 * end sequence of area to draw
1277 * vertical offset (used when called from wrapped alignment code)
1279 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1280 int startRes, int endRes, int startSeq, int endSeq, int offset)
1282 int charWidth = av.getCharWidth();
1284 if (!av.hasHiddenColumns())
1286 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1291 // package into blocks of visible columns
1296 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1297 VisibleContigsIterator regions = hidden
1298 .getVisContigsIterator(startRes, endRes + 1, true);
1299 while (regions.hasNext())
1301 int[] region = regions.next();
1302 blockEnd = region[1];
1303 blockStart = region[0];
1305 g.translate(screenY * charWidth, 0);
1306 drawPartialGroupOutline(g, group,
1307 blockStart, blockEnd, startSeq, endSeq, offset);
1309 g.translate(-screenY * charWidth, 0);
1310 screenY += blockEnd - blockStart + 1;
1316 * Draws part of a selection group outline
1324 * @param verticalOffset
1326 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1327 int startRes, int endRes, int startSeq, int endSeq,
1330 int charHeight = av.getCharHeight();
1331 int charWidth = av.getCharWidth();
1332 int visWidth = (endRes - startRes + 1) * charWidth;
1336 boolean inGroup = false;
1341 List<SequenceI> seqs = group.getSequences(null);
1343 // position of start residue of group relative to startRes, in pixels
1344 int sx = (group.getStartRes() - startRes) * charWidth;
1346 // width of group in pixels
1347 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1350 if (!(sx + xwidth < 0 || sx > visWidth))
1352 for (i = startSeq; i <= endSeq; i++)
1354 sy = verticalOffset + (i - startSeq) * charHeight;
1356 if ((sx <= (endRes - startRes) * charWidth)
1357 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1360 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1362 bottom = sy + charHeight;
1367 if (((top == -1) && (i == 0)) || !seqs
1368 .contains(av.getAlignment().getSequenceAt(i - 1)))
1379 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1380 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1382 // reset top and bottom
1390 sy = verticalOffset + ((i - startSeq) * charHeight);
1391 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1392 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1398 * Draw horizontal selection group boundaries at top and bottom positions
1401 * graphics object to draw on
1407 * visWidth maximum available width
1409 * position to draw top of group at
1411 * position to draw bottom of group at
1413 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1414 int visWidth, int top, int bottom)
1424 // don't let width extend beyond current block, or group extent
1426 if (startx + width >= visWidth)
1428 width = visWidth - startx;
1433 g.drawLine(startx, top, startx + width, top);
1438 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1443 * Draw vertical lines at sx and sx+xwidth providing they lie within
1447 * graphics object to draw on
1453 * visWidth maximum available width
1459 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1462 // if start position is visible, draw vertical line to left of
1464 if (sx >= 0 && sx < visWidth)
1466 g.drawLine(sx, oldY, sx, sy);
1469 // if end position is visible, draw vertical line to right of
1471 if (sx + xwidth < visWidth)
1473 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1478 * Highlights search results in the visible region by rendering as white text
1479 * on a black background. Any previous highlighting is removed. Answers true
1480 * if any highlight was left on the visible alignment (so status bar should be
1481 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1482 * so allows the next repaint to update the whole display.
1487 public boolean highlightSearchResults(SearchResultsI results)
1489 return highlightSearchResults(results, false);
1494 * Highlights search results in the visible region by rendering as white text
1495 * on a black background. Any previous highlighting is removed. Answers true
1496 * if any highlight was left on the visible alignment (so status bar should be
1497 * set to match), else false.
1499 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1500 * highlighted regions are modified. This speeds up highlighting across linked
1503 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1504 * a wrapped alignment had to be scrolled to show the highlighted region, then
1505 * it should be fully redrawn, otherwise a fast paint can be performed. This
1506 * argument could be removed if fast paint of scrolled wrapped alignment is
1507 * coded in future (JAL-2609).
1510 * @param doFastPaint
1511 * if true, sets a flag so the next repaint only redraws the modified
1515 public boolean highlightSearchResults(SearchResultsI results,
1516 boolean doFastPaint)
1522 boolean wrapped = av.getWrapAlignment();
1525 fastPaint = doFastPaint;
1526 fastpainting = fastPaint;
1529 * to avoid redrawing the whole visible region, we instead
1530 * redraw just the minimal regions to remove previous highlights
1533 SearchResultsI previous = av.getSearchResults();
1534 av.setSearchResults(results);
1535 boolean redrawn = false;
1536 boolean drawn = false;
1539 redrawn = drawMappedPositionsWrapped(previous);
1540 drawn = drawMappedPositionsWrapped(results);
1545 redrawn = drawMappedPositions(previous);
1546 drawn = drawMappedPositions(results);
1551 * if highlights were either removed or added, repaint
1559 * return true only if highlights were added
1565 fastpainting = false;
1570 * Redraws the minimal rectangle in the visible region (if any) that includes
1571 * mapped positions of the given search results. Whether or not positions are
1572 * highlighted depends on the SearchResults set on the Viewport. This allows
1573 * this method to be called to either clear or set highlighting. Answers true
1574 * if any positions were drawn (in which case a repaint is still required),
1580 protected boolean drawMappedPositions(SearchResultsI results)
1582 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1588 * calculate the minimal rectangle to redraw that
1589 * includes both new and existing search results
1591 int firstSeq = Integer.MAX_VALUE;
1593 int firstCol = Integer.MAX_VALUE;
1595 boolean matchFound = false;
1597 ViewportRanges ranges = av.getRanges();
1598 int firstVisibleColumn = ranges.getStartRes();
1599 int lastVisibleColumn = ranges.getEndRes();
1600 AlignmentI alignment = av.getAlignment();
1601 if (av.hasHiddenColumns())
1603 firstVisibleColumn = alignment.getHiddenColumns()
1604 .visibleToAbsoluteColumn(firstVisibleColumn);
1605 lastVisibleColumn = alignment.getHiddenColumns()
1606 .visibleToAbsoluteColumn(lastVisibleColumn);
1609 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1610 .getEndSeq(); seqNo++)
1612 SequenceI seq = alignment.getSequenceAt(seqNo);
1614 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1616 if (visibleResults != null)
1618 for (int i = 0; i < visibleResults.length - 1; i += 2)
1620 int firstMatchedColumn = visibleResults[i];
1621 int lastMatchedColumn = visibleResults[i + 1];
1622 if (firstMatchedColumn <= lastVisibleColumn
1623 && lastMatchedColumn >= firstVisibleColumn)
1626 * found a search results match in the visible region -
1627 * remember the first and last sequence matched, and the first
1628 * and last visible columns in the matched positions
1631 firstSeq = Math.min(firstSeq, seqNo);
1632 lastSeq = Math.max(lastSeq, seqNo);
1633 firstMatchedColumn = Math.max(firstMatchedColumn,
1634 firstVisibleColumn);
1635 lastMatchedColumn = Math.min(lastMatchedColumn,
1637 firstCol = Math.min(firstCol, firstMatchedColumn);
1638 lastCol = Math.max(lastCol, lastMatchedColumn);
1646 if (av.hasHiddenColumns())
1648 firstCol = alignment.getHiddenColumns()
1649 .absoluteToVisibleColumn(firstCol);
1650 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1652 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1653 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1654 Graphics gg = img.getGraphics();
1655 gg.translate(transX, transY);
1656 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1657 gg.translate(-transX, -transY);
1665 public void propertyChange(PropertyChangeEvent evt)
1667 String eventName = evt.getPropertyName();
1668 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1669 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1675 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1678 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1684 if (eventName.equals(ViewportRanges.STARTRES)
1685 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1687 // Make sure we're not trying to draw a panel
1688 // larger than the visible window
1689 if (eventName.equals(ViewportRanges.STARTRES))
1691 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1695 scrollX = ((int[]) evt.getNewValue())[0]
1696 - ((int[]) evt.getOldValue())[0];
1698 ViewportRanges vpRanges = av.getRanges();
1700 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1701 if (scrollX > range)
1705 else if (scrollX < -range)
1710 // Both scrolling and resizing change viewport ranges: scrolling changes
1711 // both start and end points, but resize only changes end values.
1712 // Here we only want to fastpaint on a scroll, with resize using a normal
1713 // paint, so scroll events are identified as changes to the horizontal or
1714 // vertical start value.
1715 if (eventName.equals(ViewportRanges.STARTRES))
1717 if (av.getWrapAlignment())
1719 fastPaintWrapped(scrollX);
1723 fastPaint(scrollX, 0);
1726 else if (eventName.equals(ViewportRanges.STARTSEQ))
1729 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1731 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1733 if (av.getWrapAlignment())
1735 fastPaintWrapped(scrollX);
1739 fastPaint(scrollX, 0);
1742 else if (eventName.equals(ViewportRanges.STARTSEQ))
1745 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1747 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1749 if (av.getWrapAlignment())
1751 fastPaintWrapped(scrollX);
1757 * Does a minimal update of the image for a scroll movement. This method
1758 * handles scroll movements of up to one width of the wrapped alignment (one
1759 * click in the vertical scrollbar). Larger movements (for example after a
1760 * scroll to highlight a mapped position) trigger a full redraw instead.
1763 * number of positions scrolled (right if positive, left if negative)
1765 protected void fastPaintWrapped(int scrollX)
1767 ViewportRanges ranges = av.getRanges();
1769 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1772 * shift of one view width or more is
1773 * overcomplicated to handle in this method
1780 if (fastpainting || img == null)
1786 fastpainting = true;
1791 Graphics gg = img.getGraphics();
1793 calculateWrappedGeometry(getWidth(), getHeight());
1796 * relocate the regions of the alignment that are still visible
1798 shiftWrappedAlignment(-scrollX);
1801 * add new columns (sequence, annotation)
1802 * - at top left if scrollX < 0
1803 * - at right of last two widths if scrollX > 0
1807 int startRes = ranges.getStartRes();
1808 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1809 - scrollX - 1, getHeight());
1813 fastPaintWrappedAddRight(scrollX);
1817 * draw all scales (if shown) and hidden column markers
1819 drawWrappedDecorators(gg, ranges.getStartRes());
1826 fastpainting = false;
1831 * Draws the specified number of columns at the 'end' (bottom right) of a
1832 * wrapped alignment view, including sequences and annotations if shown, but
1833 * not scales. Also draws the same number of columns at the right hand end of
1834 * the second last width shown, if the last width is not full height (so
1835 * cannot simply be copied from the graphics image).
1839 protected void fastPaintWrappedAddRight(int columns)
1846 Graphics gg = img.getGraphics();
1848 ViewportRanges ranges = av.getRanges();
1849 int viewportWidth = ranges.getViewportWidth();
1850 int charWidth = av.getCharWidth();
1853 * draw full height alignment in the second last row, last columns, if the
1854 * last row was not full height
1856 int visibleWidths = wrappedVisibleWidths;
1857 int canvasHeight = getHeight();
1858 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1860 if (lastWidthPartHeight)
1862 int widthsAbove = Math.max(0, visibleWidths - 2);
1863 int ypos = wrappedRepeatHeightPx * widthsAbove
1864 + wrappedSpaceAboveAlignment;
1865 int endRes = ranges.getEndRes();
1866 endRes += widthsAbove * viewportWidth;
1867 int startRes = endRes - columns;
1868 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1872 * white fill first to erase annotations
1876 gg.translate(xOffset, 0);
1877 gg.setColor(Color.white);
1878 gg.fillRect(labelWidthWest, ypos,
1879 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1880 gg.translate(-xOffset, 0);
1882 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1887 * draw newly visible columns in last wrapped width (none if we
1888 * have reached the end of the alignment)
1889 * y-offset for drawing last width is height of widths above,
1892 int widthsAbove = visibleWidths - 1;
1893 int ypos = wrappedRepeatHeightPx * widthsAbove
1894 + wrappedSpaceAboveAlignment;
1895 int endRes = ranges.getEndRes();
1896 endRes += widthsAbove * viewportWidth;
1897 int startRes = endRes - columns + 1;
1900 * white fill first to erase annotations
1902 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1904 gg.translate(xOffset, 0);
1905 gg.setColor(Color.white);
1906 int width = viewportWidth * charWidth - xOffset;
1907 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1908 gg.translate(-xOffset, 0);
1910 gg.setFont(av.getFont());
1911 gg.setColor(Color.black);
1913 if (startRes < ranges.getVisibleAlignmentWidth())
1915 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1919 * and finally, white fill any space below the visible alignment
1921 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1922 if (heightBelow > 0)
1924 gg.setColor(Color.white);
1925 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1931 * Shifts the visible alignment by the specified number of columns - left if
1932 * negative, right if positive. Copies and moves sequences and annotations (if
1933 * shown). Scales, hidden column markers and any newly visible columns must be
1938 protected void shiftWrappedAlignment(int positions)
1945 Graphics gg = img.getGraphics();
1947 int charWidth = av.getCharWidth();
1949 int canvasHeight = getHeight();
1950 ViewportRanges ranges = av.getRanges();
1951 int viewportWidth = ranges.getViewportWidth();
1952 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1954 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1955 int xMax = ranges.getVisibleAlignmentWidth();
1960 * shift right (after scroll left)
1961 * for each wrapped width (starting with the last), copy (width-positions)
1962 * columns from the left margin to the right margin, and copy positions
1963 * columns from the right margin of the row above (if any) to the
1964 * left margin of the current row
1968 * get y-offset of last wrapped width, first row of sequences
1970 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1971 y += wrappedSpaceAboveAlignment;
1972 int copyFromLeftStart = labelWidthWest;
1973 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1978 * shift 'widthToCopy' residues by 'positions' places to the right
1980 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1981 positions * charWidth, 0);
1985 * copy 'positions' residue from the row above (right hand end)
1986 * to this row's left hand end
1988 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1989 positions * charWidth, heightToCopy, -widthToCopy,
1990 wrappedRepeatHeightPx);
1993 y -= wrappedRepeatHeightPx;
1999 * shift left (after scroll right)
2000 * for each wrapped width (starting with the first), copy (width-positions)
2001 * columns from the right margin to the left margin, and copy positions
2002 * columns from the left margin of the row below (if any) to the
2003 * right margin of the current row
2005 int xpos = av.getRanges().getStartRes();
2006 int y = wrappedSpaceAboveAlignment;
2007 int copyFromRightStart = labelWidthWest - positions * charWidth;
2009 while (y < canvasHeight)
2011 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2012 positions * charWidth, 0);
2013 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2014 && (xpos + viewportWidth <= xMax))
2016 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2017 * charWidth, heightToCopy, widthToCopy,
2018 -wrappedRepeatHeightPx);
2020 y += wrappedRepeatHeightPx;
2021 xpos += viewportWidth;
2029 * Redraws any positions in the search results in the visible region of a
2030 * wrapped alignment. Any highlights are drawn depending on the search results
2031 * set on the Viewport, not the <code>results</code> argument. This allows
2032 * this method to be called either to clear highlights (passing the previous
2033 * search results), or to draw new highlights.
2038 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2040 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2044 int charHeight = av.getCharHeight();
2046 boolean matchFound = false;
2048 calculateWrappedGeometry(getWidth(), getHeight());
2049 int wrappedWidth = av.getWrappedWidth();
2050 int wrappedHeight = wrappedRepeatHeightPx;
2052 ViewportRanges ranges = av.getRanges();
2053 int canvasHeight = getHeight();
2054 int repeats = canvasHeight / wrappedHeight;
2055 if (canvasHeight / wrappedHeight > 0)
2060 int firstVisibleColumn = ranges.getStartRes();
2061 int lastVisibleColumn = ranges.getStartRes() + repeats
2062 * ranges.getViewportWidth() - 1;
2064 AlignmentI alignment = av.getAlignment();
2065 if (av.hasHiddenColumns())
2067 firstVisibleColumn = alignment.getHiddenColumns()
2068 .visibleToAbsoluteColumn(firstVisibleColumn);
2069 lastVisibleColumn = alignment.getHiddenColumns()
2070 .visibleToAbsoluteColumn(lastVisibleColumn);
2073 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2076 Graphics gg = img.getGraphics();
2078 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2079 .getEndSeq(); seqNo++)
2081 SequenceI seq = alignment.getSequenceAt(seqNo);
2083 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2085 if (visibleResults != null)
2087 for (int i = 0; i < visibleResults.length - 1; i += 2)
2089 int firstMatchedColumn = visibleResults[i];
2090 int lastMatchedColumn = visibleResults[i + 1];
2091 if (firstMatchedColumn <= lastVisibleColumn
2092 && lastMatchedColumn >= firstVisibleColumn)
2095 * found a search results match in the visible region
2097 firstMatchedColumn = Math.max(firstMatchedColumn,
2098 firstVisibleColumn);
2099 lastMatchedColumn = Math.min(lastMatchedColumn,
2103 * draw each mapped position separately (as contiguous positions may
2104 * wrap across lines)
2106 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2108 int displayColumn = mappedPos;
2109 if (av.hasHiddenColumns())
2111 displayColumn = alignment.getHiddenColumns()
2112 .absoluteToVisibleColumn(displayColumn);
2116 * transX: offset from left edge of canvas to residue position
2118 int transX = labelWidthWest
2119 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2120 * av.getCharWidth();
2123 * transY: offset from top edge of canvas to residue position
2125 int transY = gapHeight;
2126 transY += (displayColumn - ranges.getStartRes())
2127 / wrappedWidth * wrappedHeight;
2128 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2131 * yOffset is from graphics origin to start of visible region
2133 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2134 if (transY < getHeight())
2137 gg.translate(transX, transY);
2138 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2140 gg.translate(-transX, -transY);
2154 * Answers the width in pixels of the left scale labels (0 if not shown)
2158 int getLabelWidthWest()
2160 return labelWidthWest;
2164 * Ensure that a full paint is done next, for whatever reason. This was
2165 * necessary for JavaScript; apparently in Java the timing is just right on
2166 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
2167 * away with one fast paint before the others, but this ensures that in the
2168 * end we get a full paint. Problem arose in relation to copy/paste, where the
2169 * paste was not finalized with a full paint.
2171 * @author hansonr 2019.04.17
2173 public void clearFastPaint()