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.RenderingHints;
42 import java.awt.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.Iterator;
45 import java.util.List;
47 import javax.swing.JPanel;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 @SuppressWarnings("serial")
56 public class SeqCanvas extends JPanel implements ViewportListenerI
59 * pixels gap between sequences and annotations when in wrapped mode
61 static final int SEQS_ANNOTATION_GAP = 3;
63 private static final String ZEROS = "0000000000";
65 final FeatureRenderer fr;
75 private final SequenceRenderer seqRdr;
77 private boolean fastPaint = false;
79 private boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 private int labelWidthEast; // label right width in pixels if shown
88 private int labelWidthWest; // label left width in pixels if shown
90 int wrappedSpaceAboveAlignment; // gap between widths
92 int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
96 // Don't do this! Graphics handles are supposed to be transient
97 //private Graphics2D gg;
100 * Creates a new SeqCanvas object.
104 public SeqCanvas(AlignmentPanel ap)
107 fr = new FeatureRenderer(ap);
108 seqRdr = new SequenceRenderer(av);
109 setLayout(new BorderLayout());
110 PaintRefresher.Register(this, av.getSequenceSetId());
111 setBackground(Color.white);
113 av.getRanges().addPropertyChangeListener(this);
116 public SequenceRenderer getSequenceRenderer()
121 public FeatureRenderer getFeatureRenderer()
127 * Draws the scale above a region of a wrapped alignment, consisting of a
128 * column number every major interval (10 columns).
131 * the graphics context to draw on, positioned at the start (bottom
132 * left) of the line on which to draw any scale marks
134 * start alignment column (0..)
136 * end alignment column (0..)
138 * y offset to draw at
140 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
142 int charHeight = av.getCharHeight();
143 int charWidth = av.getCharWidth();
146 * white fill the scale space (for the fastPaint case)
148 g.setColor(Color.white);
149 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
150 charHeight * 3 / 2 + 2);
151 g.setColor(Color.black);
153 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
155 for (ScaleMark mark : marks)
157 int mpos = mark.column; // (i - startx - 1)
162 String mstring = mark.text;
168 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
172 * draw a tick mark below the column number, centred on the column;
173 * height of tick mark is 4 pixels less than half a character
175 int xpos = (mpos * charWidth) + (charWidth / 2);
176 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
182 * Draw the scale to the left or right of a wrapped alignment
185 * graphics context, positioned at the start of the scale to be drawn
187 * first column of wrapped width (0.. excluding any hidden columns)
189 * last column of wrapped width (0.. excluding any hidden columns)
191 * vertical offset at which to begin the scale
193 * if true, scale is left of residues, if false, scale is right
195 void drawVerticalScale(Graphics g, final int startx, final int endx,
196 final int ypos, final boolean left)
198 int charHeight = av.getCharHeight();
199 int charWidth = av.getCharWidth();
201 int yPos = ypos + charHeight;
205 if (av.hasHiddenColumns())
207 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
208 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
209 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
211 FontMetrics fm = getFontMetrics(av.getFont());
213 for (int i = 0; i < av.getAlignment().getHeight(); i++)
215 SequenceI seq = av.getAlignment().getSequenceAt(i);
218 * find sequence position of first non-gapped position -
219 * to the right if scale left, to the left if scale right
221 int index = left ? startX : endX;
223 while (index >= startX && index <= endX)
225 if (!Comparison.isGap(seq.getCharAt(index)))
227 value = seq.findPosition(index);
242 * white fill the space for the scale
244 g.setColor(Color.white);
245 int y = (yPos + (i * charHeight)) - (charHeight / 5);
246 // fillRect origin is top left of rectangle
247 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
253 * draw scale value, right justified within its width less half a
254 * character width padding on the right
256 int labelSpace = left ? labelWidthWest : labelWidthEast;
257 labelSpace -= charWidth / 2; // leave space to the right
258 String valueAsString = String.valueOf(value);
259 int labelLength = fm.stringWidth(valueAsString);
260 int xOffset = labelSpace - labelLength;
261 g.setColor(Color.black);
262 g.drawString(valueAsString, xOffset, y);
269 * Does a fast paint of an alignment in response to a scroll. Most of the
270 * visible region is simply copied and shifted, and then any newly visible
271 * columns or rows are drawn. The scroll may be horizontal or vertical, but
272 * not both at once. Scrolling may be the result of
274 * <li>dragging a scroll bar</li>
275 * <li>clicking in the scroll bar</li>
276 * <li>scrolling by trackpad, middle mouse button, or other device</li>
277 * <li>by moving the box in the Overview window</li>
278 * <li>programmatically to make a highlighted position visible</li>
282 * columns to shift right (positive) or left (negative)
284 * rows to shift down (positive) or up (negative)
286 public void fastPaint(int horizontal, int vertical)
288 if (fastpainting || img == null)
296 int charHeight = av.getCharHeight();
297 int charWidth = av.getCharWidth();
299 ViewportRanges ranges = av.getRanges();
300 int startRes = ranges.getStartRes();
301 int endRes = ranges.getEndRes();
302 int startSeq = ranges.getStartSeq();
303 int endSeq = ranges.getEndSeq();
307 Graphics gg = img.getGraphics();
308 gg.copyArea(horizontal * charWidth, vertical * charHeight,
309 img.getWidth(), img.getHeight(), -horizontal * charWidth,
310 -vertical * charHeight);
312 if (horizontal > 0) // scrollbar pulled right, image to the left
314 transX = (endRes - startRes - horizontal) * charWidth;
315 startRes = endRes - horizontal;
317 else if (horizontal < 0)
319 endRes = startRes - horizontal;
322 if (vertical > 0) // scroll down
324 startSeq = endSeq - vertical;
326 if (startSeq < ranges.getStartSeq())
327 { // ie scrolling too fast, more than a page at a time
328 startSeq = ranges.getStartSeq();
332 transY = img.getHeight() - ((vertical + 1) * charHeight);
335 else if (vertical < 0)
337 endSeq = startSeq - vertical;
339 if (endSeq > ranges.getEndSeq())
341 endSeq = ranges.getEndSeq();
345 gg.translate(transX, transY);
346 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
347 gg.translate(-transX, -transY);
350 // Call repaint on alignment panel so that repaints from other alignment
351 // panel components can be aggregated. Otherwise performance of the
352 // overview window and others may be adversely affected.
353 av.getAlignPanel().repaint();
356 fastpainting = false;
361 public void paintComponent(Graphics g)
363 //super.paintComponent(g); // BH 2019
365 int charHeight = av.getCharHeight();
366 int charWidth = av.getCharWidth();
368 ViewportRanges ranges = av.getRanges();
370 int width = getWidth();
371 int height = getHeight();
373 width -= (width % charWidth);
374 height -= (height % charHeight);
376 if ((img != null) && (fastPaint
377 || (getVisibleRect().width != g.getClipBounds().width)
378 || (getVisibleRect().height != g.getClipBounds().height)))
380 g.drawImage(img, 0, 0, this);
382 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
383 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
387 else if (width > 0 && height > 0)
390 * img is a cached version of the last view we drew, if any
391 * if we have no img or the size has changed, make a new one
393 if (img == null || width != img.getWidth()
394 || height != img.getHeight())
396 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
399 Graphics2D gg = (Graphics2D) img.getGraphics();
400 gg.setFont(av.getFont());
404 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
405 RenderingHints.VALUE_ANTIALIAS_ON);
408 gg.setColor(Color.white);
409 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
411 if (av.getWrapAlignment())
413 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
417 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
418 ranges.getStartSeq(), ranges.getEndSeq(), 0);
421 drawSelectionGroup(gg, ranges.getStartRes(),
422 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
424 g.drawImage(img, 0, 0, this);
430 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
431 ranges.getStartSeq(), ranges.getEndSeq());
436 * Draw an alignment panel for printing
439 * Graphics object to draw with
441 * start residue of print area
443 * end residue of print area
445 * start sequence of print area
447 * end sequence of print area
449 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
450 int startSeq, int endSeq)
452 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
454 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
459 * Draw a wrapped alignment panel for printing
462 * Graphics object to draw with
464 * width of drawing area
465 * @param canvasHeight
466 * height of drawing area
468 * start residue of print area
470 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
471 int canvasHeight, int startRes)
473 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
475 SequenceGroup group = av.getSelectionGroup();
478 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
484 * Returns the visible width of the canvas in residues, after allowing for
485 * East or West scales (if shown)
488 * the width in pixels (possibly including scales)
492 public int getWrappedCanvasWidth(int canvasWidth)
494 int charWidth = av.getCharWidth();
496 FontMetrics fm = getFontMetrics(av.getFont());
500 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
502 labelWidth = getLabelWidth(fm);
505 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
507 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
509 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
513 * Returns a pixel width sufficient to show the largest sequence coordinate
514 * (end position) in the alignment, calculated as the FontMetrics width of
515 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
516 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
517 * half a character width space on either side.
522 protected int getLabelWidth(FontMetrics fm)
525 * find the biggest sequence end position we need to show
526 * (note this is not necessarily the sequence length)
529 AlignmentI alignment = av.getAlignment();
530 for (int i = 0; i < alignment.getHeight(); i++)
532 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
536 for (int i = maxWidth; i > 0; i /= 10)
541 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
545 * Draws as many widths of a wrapped alignment as can fit in the visible
550 * available width in pixels
551 * @param canvasHeight
552 * available height in pixels
554 * the first column (0...) of the alignment to draw
556 public void drawWrappedPanel(Graphics g, int canvasWidth,
557 int canvasHeight, final int startColumn)
559 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
562 av.setWrappedWidth(wrappedWidthInResidues);
564 ViewportRanges ranges = av.getRanges();
565 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
567 // we need to call this again to make sure the startColumn +
568 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
570 calculateWrappedGeometry(canvasWidth, canvasHeight);
573 * draw one width at a time (excluding any scales shown),
574 * until we have run out of either alignment or vertical space available
576 int ypos = wrappedSpaceAboveAlignment;
577 int maxWidth = ranges.getVisibleAlignmentWidth();
579 int start = startColumn;
580 int currentWidth = 0;
581 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
584 .min(maxWidth, start + wrappedWidthInResidues - 1);
585 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
586 ypos += wrappedRepeatHeightPx;
587 start += wrappedWidthInResidues;
591 drawWrappedDecorators(g, startColumn);
595 * Calculates and saves values needed when rendering a wrapped alignment.
596 * These depend on many factors, including
598 * <li>canvas width and height</li>
599 * <li>number of visible sequences, and height of annotations if shown</li>
600 * <li>font and character width</li>
601 * <li>whether scales are shown left, right or above the alignment</li>
605 * @param canvasHeight
606 * @return the number of residue columns in each width
608 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
610 int charHeight = av.getCharHeight();
613 * vertical space in pixels between wrapped widths of alignment
614 * - one character height, or two if scale above is drawn
616 wrappedSpaceAboveAlignment = charHeight
617 * (av.getScaleAboveWrapped() ? 2 : 1);
620 * compute height in pixels of the wrapped widths
621 * - start with space above plus sequences
623 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
624 wrappedRepeatHeightPx += av.getAlignment().getHeight()
628 * add annotations panel height if shown
629 * also gap between sequences and annotations
631 if (av.isShowAnnotation())
633 wrappedRepeatHeightPx += getAnnotationHeight();
634 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
638 * number of visible widths (the last one may be part height),
639 * ensuring a part height includes at least one sequence
641 ViewportRanges ranges = av.getRanges();
642 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
643 int remainder = canvasHeight % wrappedRepeatHeightPx;
644 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
646 wrappedVisibleWidths++;
650 * compute width in residues; this also sets East and West label widths
652 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
655 * limit visibleWidths to not exceed width of alignment
657 int xMax = ranges.getVisibleAlignmentWidth();
658 int startToEnd = xMax - ranges.getStartRes();
659 int maxWidths = startToEnd / wrappedWidthInResidues;
660 if (startToEnd % wrappedWidthInResidues > 0)
664 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
666 return wrappedWidthInResidues;
670 * Draws one width of a wrapped alignment, including sequences and
671 * annnotations, if shown, but not scales or hidden column markers
677 * @param canvasHeight
679 protected void drawWrappedWidth(Graphics g, final int ypos,
680 final int startColumn, final int endColumn,
681 final int canvasHeight)
683 ViewportRanges ranges = av.getRanges();
684 int viewportWidth = ranges.getViewportWidth();
686 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
689 * move right before drawing by the width of the scale left (if any)
690 * plus column offset from left margin (usually zero, but may be non-zero
691 * when fast painting is drawing just a few columns)
693 int charWidth = av.getCharWidth();
694 int xOffset = labelWidthWest
695 + ((startColumn - ranges.getStartRes()) % viewportWidth)
698 g.translate(xOffset, 0);
701 * white fill the region to be drawn (so incremental fast paint doesn't
702 * scribble over an existing image)
704 g.setColor(Color.white);
705 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
706 wrappedRepeatHeightPx);
708 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
711 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
713 if (av.isShowAnnotation())
715 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
716 g.translate(0, yShift);
717 if (annotations == null)
719 annotations = new AnnotationPanel(av);
722 annotations.renderer.drawComponent(annotations, av, g, -1,
723 startColumn, endx + 1);
724 g.translate(0, -yShift);
726 g.translate(-xOffset, 0);
730 * Draws scales left, right and above (if shown), and any hidden column
731 * markers, on all widths of the wrapped alignment
736 protected void drawWrappedDecorators(Graphics g, final int startColumn)
738 int charWidth = av.getCharWidth();
740 g.setFont(av.getFont());
742 g.setColor(Color.black);
744 int ypos = wrappedSpaceAboveAlignment;
745 ViewportRanges ranges = av.getRanges();
746 int viewportWidth = ranges.getViewportWidth();
747 int maxWidth = ranges.getVisibleAlignmentWidth();
749 int startCol = startColumn;
751 while (widthsDrawn < wrappedVisibleWidths)
753 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
755 if (av.getScaleLeftWrapped())
757 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
760 if (av.getScaleRightWrapped())
762 int x = labelWidthWest + viewportWidth * charWidth;
765 drawVerticalScale(g, startCol, endColumn, ypos, false);
770 * white fill region of scale above and hidden column markers
771 * (to support incremental fast paint of image)
773 g.translate(labelWidthWest, 0);
774 g.setColor(Color.white);
775 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
776 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
777 g.setColor(Color.black);
778 g.translate(-labelWidthWest, 0);
780 g.translate(labelWidthWest, 0);
782 if (av.getScaleAboveWrapped())
784 drawNorthScale(g, startCol, endColumn, ypos);
787 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
789 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
792 g.translate(-labelWidthWest, 0);
794 ypos += wrappedRepeatHeightPx;
795 startCol += viewportWidth;
801 * Draws markers (triangles) above hidden column positions between startColumn
809 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
810 int startColumn, int endColumn)
812 int charHeight = av.getCharHeight();
813 int charWidth = av.getCharWidth();
815 g.setColor(Color.blue);
817 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
819 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
823 res = it.next() - startColumn;
825 if (res < 0 || res > endColumn - startColumn + 1)
831 * draw a downward-pointing triangle at the hidden columns location
832 * (before the following visible column)
834 int xMiddle = res * charWidth;
835 int[] xPoints = new int[] { xMiddle - charHeight / 4,
836 xMiddle + charHeight / 4, xMiddle };
837 int yTop = ypos - (charHeight / 2);
838 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
839 g.fillPolygon(xPoints, yPoints, 3);
844 * Draw a selection group over a wrapped alignment
846 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
848 int canvasHeight, int startRes)
850 int charHeight = av.getCharHeight();
851 int charWidth = av.getCharWidth();
853 // height gap above each panel
854 int hgap = charHeight;
855 if (av.getScaleAboveWrapped())
860 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
862 int cHeight = av.getAlignment().getHeight() * charHeight;
864 int startx = startRes;
866 int ypos = hgap; // vertical offset
867 int maxwidth = av.getAlignment().getVisibleWidth();
869 // chop the wrapped alignment extent up into panel-sized blocks and treat
870 // each block as if it were a block from an unwrapped alignment
871 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
872 BasicStroke.JOIN_ROUND, 3f, new float[]
874 g.setColor(Color.RED);
875 while ((ypos <= canvasHeight) && (startx < maxwidth))
877 // set end value to be start + width, or maxwidth, whichever is smaller
878 endx = startx + cWidth - 1;
885 g.translate(labelWidthWest, 0);
887 drawUnwrappedSelection(g, group, startx, endx, 0,
888 av.getAlignment().getHeight() - 1,
891 g.translate(-labelWidthWest, 0);
893 // update vertical offset
894 ypos += cHeight + getAnnotationHeight() + hgap;
896 // update horizontal offset
899 g.setStroke(new BasicStroke());
902 int getAnnotationHeight()
904 if (!av.isShowAnnotation())
909 if (annotations == null)
911 annotations = new AnnotationPanel(av);
914 return annotations.adjustPanelHeight();
918 * Draws the visible region of the alignment on the graphics context. If there
919 * are hidden column markers in the visible region, then each sub-region
920 * between the markers is drawn separately, followed by the hidden column
924 * the graphics context, positioned at the first residue to be drawn
926 * offset of the first column to draw (0..)
928 * offset of the last column to draw (0..)
930 * offset of the first sequence to draw (0..)
932 * offset of the last sequence to draw (0..)
934 * vertical offset at which to draw (for wrapped alignments)
936 public void drawPanel(Graphics g1, final int startRes, final int endRes,
937 final int startSeq, final int endSeq, final int yOffset)
939 int charHeight = av.getCharHeight();
940 int charWidth = av.getCharWidth();
942 if (!av.hasHiddenColumns())
944 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
952 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
953 VisibleContigsIterator regions = hidden
954 .getVisContigsIterator(startRes, endRes + 1, true);
956 while (regions.hasNext())
958 int[] region = regions.next();
959 blockEnd = region[1];
960 blockStart = region[0];
963 * draw up to just before the next hidden region, or the end of
964 * the visible region, whichever comes first
966 g1.translate(screenY * charWidth, 0);
968 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
971 * draw the downline of the hidden column marker (ScalePanel draws the
972 * triangle on top) if we reached it
974 if (av.getShowHiddenMarkers()
975 && (regions.hasNext() || regions.endsAtHidden()))
977 g1.setColor(Color.blue);
979 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
980 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
981 (endSeq - startSeq + 1) * charHeight + yOffset);
984 g1.translate(-screenY * charWidth, 0);
985 screenY += blockEnd - blockStart + 1;
992 * Draws a region of the visible alignment
996 * offset of the first column in the visible region (0..)
998 * offset of the last column in the visible region (0..)
1000 * offset of the first sequence in the visible region (0..)
1002 * offset of the last sequence in the visible region (0..)
1004 * vertical offset at which to draw (for wrapped alignments)
1006 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1007 int endSeq, int offset)
1009 int charHeight = av.getCharHeight();
1010 int charWidth = av.getCharWidth();
1012 g.setFont(av.getFont());
1013 seqRdr.prepare(g, av.isRenderGaps());
1017 // / First draw the sequences
1018 // ///////////////////////////
1019 for (int i = startSeq; i <= endSeq; i++)
1021 nextSeq = av.getAlignment().getSequenceAt(i);
1022 if (nextSeq == null)
1024 // occasionally, a race condition occurs such that the alignment row is
1028 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1029 startRes, endRes, offset + ((i - startSeq) * charHeight));
1031 if (av.isShowSequenceFeatures())
1033 fr.drawSequence(g, nextSeq, startRes, endRes,
1034 offset + ((i - startSeq) * charHeight), false);
1038 * highlight search Results once sequence has been drawn
1040 if (av.hasSearchResults())
1042 SearchResultsI searchResults = av.getSearchResults();
1043 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1045 if (visibleResults != null)
1047 for (int r = 0; r < visibleResults.length; r += 2)
1049 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1050 visibleResults[r + 1],
1051 (visibleResults[r] - startRes) * charWidth,
1052 offset + ((i - startSeq) * charHeight));
1058 if (av.getSelectionGroup() != null
1059 || av.getAlignment().getGroups().size() > 0)
1061 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1067 * Draws the outlines of any groups defined on the alignment (excluding the
1068 * current selection group, if any)
1077 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1078 int startSeq, int endSeq, int offset)
1080 Graphics2D g = (Graphics2D) g1;
1082 SequenceGroup group = null;
1083 int groupIndex = -1;
1085 if (av.getAlignment().getGroups().size() > 0)
1087 group = av.getAlignment().getGroups().get(0);
1095 g.setColor(group.getOutlineColour());
1096 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1100 if (groupIndex >= av.getAlignment().getGroups().size())
1104 group = av.getAlignment().getGroups().get(groupIndex);
1105 } while (groupIndex < av.getAlignment().getGroups().size());
1110 * Draws the outline of the current selection group (if any)
1118 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1119 int startSeq, int endSeq)
1121 SequenceGroup group = av.getSelectionGroup();
1127 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1128 BasicStroke.JOIN_ROUND, 3f, new float[]
1130 g.setColor(Color.RED);
1131 if (!av.getWrapAlignment())
1133 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1138 drawWrappedSelection(g, group, getWidth(), getHeight(),
1139 av.getRanges().getStartRes());
1141 g.setStroke(new BasicStroke());
1145 * Draw the cursor as a separate image and overlay
1148 * start residue of area to draw cursor in
1150 * end residue of area to draw cursor in
1152 * start sequence of area to draw cursor in
1154 * end sequence of are to draw cursor in
1155 * @return a transparent image of the same size as the sequence canvas, with
1156 * the cursor drawn on it, if any
1158 private void drawCursor(Graphics g, int startRes, int endRes,
1162 // convert the cursorY into a position on the visible alignment
1163 int cursor_ypos = cursorY;
1165 // don't do work unless we have to
1166 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1170 int startx = startRes;
1173 // convert the cursorX into a position on the visible alignment
1174 int cursor_xpos = av.getAlignment().getHiddenColumns()
1175 .absoluteToVisibleColumn(cursorX);
1177 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1180 if (av.getWrapAlignment())
1182 // work out the correct offsets for the cursor
1183 int charHeight = av.getCharHeight();
1184 int charWidth = av.getCharWidth();
1185 int canvasWidth = getWidth();
1186 int canvasHeight = getHeight();
1188 // height gap above each panel
1189 int hgap = charHeight;
1190 if (av.getScaleAboveWrapped())
1195 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1197 int cHeight = av.getAlignment().getHeight() * charHeight;
1199 endx = startx + cWidth - 1;
1200 int ypos = hgap; // vertical offset
1202 // iterate down the wrapped panels
1203 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1205 // update vertical offset
1206 ypos += cHeight + getAnnotationHeight() + hgap;
1208 // update horizontal offset
1210 endx = startx + cWidth - 1;
1213 xoffset = labelWidthWest;
1216 // now check if cursor is within range for x values
1217 if (cursor_xpos >= startx && cursor_xpos <= endx)
1219 // get the character the cursor is drawn at
1220 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1221 char s = seq.getCharAt(cursorX);
1223 seqRdr.drawCursor(g, s,
1224 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1225 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1233 * Draw a selection group over an unwrapped alignment
1236 * graphics object to draw with
1240 * start residue of area to draw
1242 * end residue of area to draw
1244 * start sequence of area to draw
1246 * end sequence of area to draw
1248 * vertical offset (used when called from wrapped alignment code)
1250 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1251 int startRes, int endRes, int startSeq, int endSeq, int offset)
1253 int charWidth = av.getCharWidth();
1255 if (!av.hasHiddenColumns())
1257 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1262 // package into blocks of visible columns
1267 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1268 VisibleContigsIterator regions = hidden
1269 .getVisContigsIterator(startRes, endRes + 1, true);
1270 while (regions.hasNext())
1272 int[] region = regions.next();
1273 blockEnd = region[1];
1274 blockStart = region[0];
1276 g.translate(screenY * charWidth, 0);
1277 drawPartialGroupOutline(g, group,
1278 blockStart, blockEnd, startSeq, endSeq, offset);
1280 g.translate(-screenY * charWidth, 0);
1281 screenY += blockEnd - blockStart + 1;
1287 * Draws part of a selection group outline
1295 * @param verticalOffset
1297 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1298 int startRes, int endRes, int startSeq, int endSeq,
1301 int charHeight = av.getCharHeight();
1302 int charWidth = av.getCharWidth();
1303 int visWidth = (endRes - startRes + 1) * charWidth;
1307 boolean inGroup = false;
1312 List<SequenceI> seqs = group.getSequences(null);
1314 // position of start residue of group relative to startRes, in pixels
1315 int sx = (group.getStartRes() - startRes) * charWidth;
1317 // width of group in pixels
1318 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1321 if (!(sx + xwidth < 0 || sx > visWidth))
1323 for (i = startSeq; i <= endSeq; i++)
1325 sy = verticalOffset + (i - startSeq) * charHeight;
1327 if ((sx <= (endRes - startRes) * charWidth)
1328 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1331 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1333 bottom = sy + charHeight;
1338 if (((top == -1) && (i == 0)) || !seqs
1339 .contains(av.getAlignment().getSequenceAt(i - 1)))
1350 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1351 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1353 // reset top and bottom
1361 sy = verticalOffset + ((i - startSeq) * charHeight);
1362 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1363 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1369 * Draw horizontal selection group boundaries at top and bottom positions
1372 * graphics object to draw on
1378 * visWidth maximum available width
1380 * position to draw top of group at
1382 * position to draw bottom of group at
1384 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1385 int visWidth, int top, int bottom)
1395 // don't let width extend beyond current block, or group extent
1397 if (startx + width >= visWidth)
1399 width = visWidth - startx;
1404 g.drawLine(startx, top, startx + width, top);
1409 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1414 * Draw vertical lines at sx and sx+xwidth providing they lie within
1418 * graphics object to draw on
1424 * visWidth maximum available width
1430 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1433 // if start position is visible, draw vertical line to left of
1435 if (sx >= 0 && sx < visWidth)
1437 g.drawLine(sx, oldY, sx, sy);
1440 // if end position is visible, draw vertical line to right of
1442 if (sx + xwidth < visWidth)
1444 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1449 * Highlights search results in the visible region by rendering as white text
1450 * on a black background. Any previous highlighting is removed. Answers true
1451 * if any highlight was left on the visible alignment (so status bar should be
1452 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1453 * so allows the next repaint to update the whole display.
1458 public boolean highlightSearchResults(SearchResultsI results)
1460 return highlightSearchResults(results, false);
1465 * Highlights search results in the visible region by rendering as white text
1466 * on a black background. Any previous highlighting is removed. Answers true
1467 * if any highlight was left on the visible alignment (so status bar should be
1468 * set to match), else false.
1470 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1471 * highlighted regions are modified. This speeds up highlighting across linked
1474 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1475 * a wrapped alignment had to be scrolled to show the highlighted region, then
1476 * it should be fully redrawn, otherwise a fast paint can be performed. This
1477 * argument could be removed if fast paint of scrolled wrapped alignment is
1478 * coded in future (JAL-2609).
1481 * @param doFastPaint
1482 * if true, sets a flag so the next repaint only redraws the modified
1486 public boolean highlightSearchResults(SearchResultsI results,
1487 boolean doFastPaint)
1493 boolean wrapped = av.getWrapAlignment();
1496 fastPaint = doFastPaint;
1497 fastpainting = fastPaint;
1500 * to avoid redrawing the whole visible region, we instead
1501 * redraw just the minimal regions to remove previous highlights
1504 SearchResultsI previous = av.getSearchResults();
1505 av.setSearchResults(results);
1506 boolean redrawn = false;
1507 boolean drawn = false;
1510 redrawn = drawMappedPositionsWrapped(previous);
1511 drawn = drawMappedPositionsWrapped(results);
1516 redrawn = drawMappedPositions(previous);
1517 drawn = drawMappedPositions(results);
1522 * if highlights were either removed or added, repaint
1530 * return true only if highlights were added
1536 fastpainting = false;
1541 * Redraws the minimal rectangle in the visible region (if any) that includes
1542 * mapped positions of the given search results. Whether or not positions are
1543 * highlighted depends on the SearchResults set on the Viewport. This allows
1544 * this method to be called to either clear or set highlighting. Answers true
1545 * if any positions were drawn (in which case a repaint is still required),
1551 protected boolean drawMappedPositions(SearchResultsI results)
1553 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1559 * calculate the minimal rectangle to redraw that
1560 * includes both new and existing search results
1562 int firstSeq = Integer.MAX_VALUE;
1564 int firstCol = Integer.MAX_VALUE;
1566 boolean matchFound = false;
1568 ViewportRanges ranges = av.getRanges();
1569 int firstVisibleColumn = ranges.getStartRes();
1570 int lastVisibleColumn = ranges.getEndRes();
1571 AlignmentI alignment = av.getAlignment();
1572 if (av.hasHiddenColumns())
1574 firstVisibleColumn = alignment.getHiddenColumns()
1575 .visibleToAbsoluteColumn(firstVisibleColumn);
1576 lastVisibleColumn = alignment.getHiddenColumns()
1577 .visibleToAbsoluteColumn(lastVisibleColumn);
1580 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1581 .getEndSeq(); seqNo++)
1583 SequenceI seq = alignment.getSequenceAt(seqNo);
1585 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1587 if (visibleResults != null)
1589 for (int i = 0; i < visibleResults.length - 1; i += 2)
1591 int firstMatchedColumn = visibleResults[i];
1592 int lastMatchedColumn = visibleResults[i + 1];
1593 if (firstMatchedColumn <= lastVisibleColumn
1594 && lastMatchedColumn >= firstVisibleColumn)
1597 * found a search results match in the visible region -
1598 * remember the first and last sequence matched, and the first
1599 * and last visible columns in the matched positions
1602 firstSeq = Math.min(firstSeq, seqNo);
1603 lastSeq = Math.max(lastSeq, seqNo);
1604 firstMatchedColumn = Math.max(firstMatchedColumn,
1605 firstVisibleColumn);
1606 lastMatchedColumn = Math.min(lastMatchedColumn,
1608 firstCol = Math.min(firstCol, firstMatchedColumn);
1609 lastCol = Math.max(lastCol, lastMatchedColumn);
1617 if (av.hasHiddenColumns())
1619 firstCol = alignment.getHiddenColumns()
1620 .absoluteToVisibleColumn(firstCol);
1621 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1623 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1624 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1625 Graphics gg = img.getGraphics();
1626 gg.translate(transX, transY);
1627 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1628 gg.translate(-transX, -transY);
1636 public void propertyChange(PropertyChangeEvent evt)
1638 String eventName = evt.getPropertyName();
1640 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1646 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1654 if (eventName.equals(ViewportRanges.STARTRES)
1655 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1657 // Make sure we're not trying to draw a panel
1658 // larger than the visible window
1659 if (eventName.equals(ViewportRanges.STARTRES))
1661 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1665 scrollX = ((int[]) evt.getNewValue())[0]
1666 - ((int[]) evt.getOldValue())[0];
1668 ViewportRanges vpRanges = av.getRanges();
1670 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1671 if (scrollX > range)
1675 else if (scrollX < -range)
1680 // Both scrolling and resizing change viewport ranges: scrolling changes
1681 // both start and end points, but resize only changes end values.
1682 // Here we only want to fastpaint on a scroll, with resize using a normal
1683 // paint, so scroll events are identified as changes to the horizontal or
1684 // vertical start value.
1685 if (eventName.equals(ViewportRanges.STARTRES))
1687 if (av.getWrapAlignment())
1689 fastPaintWrapped(scrollX);
1693 fastPaint(scrollX, 0);
1696 else if (eventName.equals(ViewportRanges.STARTSEQ))
1699 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1701 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1703 if (av.getWrapAlignment())
1705 fastPaintWrapped(scrollX);
1709 fastPaint(scrollX, 0);
1712 else if (eventName.equals(ViewportRanges.STARTSEQ))
1715 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1717 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1719 if (av.getWrapAlignment())
1721 fastPaintWrapped(scrollX);
1727 * Does a minimal update of the image for a scroll movement. This method
1728 * handles scroll movements of up to one width of the wrapped alignment (one
1729 * click in the vertical scrollbar). Larger movements (for example after a
1730 * scroll to highlight a mapped position) trigger a full redraw instead.
1733 * number of positions scrolled (right if positive, left if negative)
1735 protected void fastPaintWrapped(int scrollX)
1737 ViewportRanges ranges = av.getRanges();
1739 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1742 * shift of one view width or more is
1743 * overcomplicated to handle in this method
1750 if (fastpainting || img == null)
1756 fastpainting = true;
1758 .println("fastpaintwrapped fastpainting true; fastPaint="
1764 Graphics gg = img.getGraphics();
1766 calculateWrappedGeometry(getWidth(), getHeight());
1769 * relocate the regions of the alignment that are still visible
1771 shiftWrappedAlignment(-scrollX);
1774 * add new columns (sequence, annotation)
1775 * - at top left if scrollX < 0
1776 * - at right of last two widths if scrollX > 0
1780 int startRes = ranges.getStartRes();
1781 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1782 - scrollX - 1, getHeight());
1786 fastPaintWrappedAddRight(scrollX);
1790 * draw all scales (if shown) and hidden column markers
1792 drawWrappedDecorators(gg, ranges.getStartRes());
1799 fastpainting = false;
1804 * Draws the specified number of columns at the 'end' (bottom right) of a
1805 * wrapped alignment view, including sequences and annotations if shown, but
1806 * not scales. Also draws the same number of columns at the right hand end of
1807 * the second last width shown, if the last width is not full height (so
1808 * cannot simply be copied from the graphics image).
1812 protected void fastPaintWrappedAddRight(int columns)
1819 Graphics gg = img.getGraphics();
1821 ViewportRanges ranges = av.getRanges();
1822 int viewportWidth = ranges.getViewportWidth();
1823 int charWidth = av.getCharWidth();
1826 * draw full height alignment in the second last row, last columns, if the
1827 * last row was not full height
1829 int visibleWidths = wrappedVisibleWidths;
1830 int canvasHeight = getHeight();
1831 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1833 if (lastWidthPartHeight)
1835 int widthsAbove = Math.max(0, visibleWidths - 2);
1836 int ypos = wrappedRepeatHeightPx * widthsAbove
1837 + wrappedSpaceAboveAlignment;
1838 int endRes = ranges.getEndRes();
1839 endRes += widthsAbove * viewportWidth;
1840 int startRes = endRes - columns;
1841 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1845 * white fill first to erase annotations
1849 gg.translate(xOffset, 0);
1850 gg.setColor(Color.white);
1851 gg.fillRect(labelWidthWest, ypos,
1852 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1853 gg.translate(-xOffset, 0);
1855 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1860 * draw newly visible columns in last wrapped width (none if we
1861 * have reached the end of the alignment)
1862 * y-offset for drawing last width is height of widths above,
1865 int widthsAbove = visibleWidths - 1;
1866 int ypos = wrappedRepeatHeightPx * widthsAbove
1867 + wrappedSpaceAboveAlignment;
1868 int endRes = ranges.getEndRes();
1869 endRes += widthsAbove * viewportWidth;
1870 int startRes = endRes - columns + 1;
1873 * white fill first to erase annotations
1875 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1877 gg.translate(xOffset, 0);
1878 gg.setColor(Color.white);
1879 int width = viewportWidth * charWidth - xOffset;
1880 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1881 gg.translate(-xOffset, 0);
1883 gg.setFont(av.getFont());
1884 gg.setColor(Color.black);
1886 if (startRes < ranges.getVisibleAlignmentWidth())
1888 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1892 * and finally, white fill any space below the visible alignment
1894 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1895 if (heightBelow > 0)
1897 gg.setColor(Color.white);
1898 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1904 * Shifts the visible alignment by the specified number of columns - left if
1905 * negative, right if positive. Copies and moves sequences and annotations (if
1906 * shown). Scales, hidden column markers and any newly visible columns must be
1911 protected void shiftWrappedAlignment(int positions)
1918 Graphics gg = img.getGraphics();
1920 int charWidth = av.getCharWidth();
1922 int canvasHeight = getHeight();
1923 ViewportRanges ranges = av.getRanges();
1924 int viewportWidth = ranges.getViewportWidth();
1925 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1927 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1928 int xMax = ranges.getVisibleAlignmentWidth();
1933 * shift right (after scroll left)
1934 * for each wrapped width (starting with the last), copy (width-positions)
1935 * columns from the left margin to the right margin, and copy positions
1936 * columns from the right margin of the row above (if any) to the
1937 * left margin of the current row
1941 * get y-offset of last wrapped width, first row of sequences
1943 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1944 y += wrappedSpaceAboveAlignment;
1945 int copyFromLeftStart = labelWidthWest;
1946 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1951 * shift 'widthToCopy' residues by 'positions' places to the right
1953 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1954 positions * charWidth, 0);
1958 * copy 'positions' residue from the row above (right hand end)
1959 * to this row's left hand end
1961 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1962 positions * charWidth, heightToCopy, -widthToCopy,
1963 wrappedRepeatHeightPx);
1966 y -= wrappedRepeatHeightPx;
1972 * shift left (after scroll right)
1973 * for each wrapped width (starting with the first), copy (width-positions)
1974 * columns from the right margin to the left margin, and copy positions
1975 * columns from the left margin of the row below (if any) to the
1976 * right margin of the current row
1978 int xpos = av.getRanges().getStartRes();
1979 int y = wrappedSpaceAboveAlignment;
1980 int copyFromRightStart = labelWidthWest - positions * charWidth;
1982 while (y < canvasHeight)
1984 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1985 positions * charWidth, 0);
1986 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1987 && (xpos + viewportWidth <= xMax))
1989 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1990 * charWidth, heightToCopy, widthToCopy,
1991 -wrappedRepeatHeightPx);
1993 y += wrappedRepeatHeightPx;
1994 xpos += viewportWidth;
2002 * Redraws any positions in the search results in the visible region of a
2003 * wrapped alignment. Any highlights are drawn depending on the search results
2004 * set on the Viewport, not the <code>results</code> argument. This allows
2005 * this method to be called either to clear highlights (passing the previous
2006 * search results), or to draw new highlights.
2011 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2013 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2017 int charHeight = av.getCharHeight();
2019 boolean matchFound = false;
2021 calculateWrappedGeometry(getWidth(), getHeight());
2022 int wrappedWidth = av.getWrappedWidth();
2023 int wrappedHeight = wrappedRepeatHeightPx;
2025 ViewportRanges ranges = av.getRanges();
2026 int canvasHeight = getHeight();
2027 int repeats = canvasHeight / wrappedHeight;
2028 if (canvasHeight / wrappedHeight > 0)
2033 int firstVisibleColumn = ranges.getStartRes();
2034 int lastVisibleColumn = ranges.getStartRes() + repeats
2035 * ranges.getViewportWidth() - 1;
2037 AlignmentI alignment = av.getAlignment();
2038 if (av.hasHiddenColumns())
2040 firstVisibleColumn = alignment.getHiddenColumns()
2041 .visibleToAbsoluteColumn(firstVisibleColumn);
2042 lastVisibleColumn = alignment.getHiddenColumns()
2043 .visibleToAbsoluteColumn(lastVisibleColumn);
2046 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2049 Graphics gg = img.getGraphics();
2051 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2052 .getEndSeq(); seqNo++)
2054 SequenceI seq = alignment.getSequenceAt(seqNo);
2056 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2058 if (visibleResults != null)
2060 for (int i = 0; i < visibleResults.length - 1; i += 2)
2062 int firstMatchedColumn = visibleResults[i];
2063 int lastMatchedColumn = visibleResults[i + 1];
2064 if (firstMatchedColumn <= lastVisibleColumn
2065 && lastMatchedColumn >= firstVisibleColumn)
2068 * found a search results match in the visible region
2070 firstMatchedColumn = Math.max(firstMatchedColumn,
2071 firstVisibleColumn);
2072 lastMatchedColumn = Math.min(lastMatchedColumn,
2076 * draw each mapped position separately (as contiguous positions may
2077 * wrap across lines)
2079 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2081 int displayColumn = mappedPos;
2082 if (av.hasHiddenColumns())
2084 displayColumn = alignment.getHiddenColumns()
2085 .absoluteToVisibleColumn(displayColumn);
2089 * transX: offset from left edge of canvas to residue position
2091 int transX = labelWidthWest
2092 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2093 * av.getCharWidth();
2096 * transY: offset from top edge of canvas to residue position
2098 int transY = gapHeight;
2099 transY += (displayColumn - ranges.getStartRes())
2100 / wrappedWidth * wrappedHeight;
2101 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2104 * yOffset is from graphics origin to start of visible region
2106 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2107 if (transY < getHeight())
2110 gg.translate(transX, transY);
2111 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2113 gg.translate(-transX, -transY);
2127 * Answers the width in pixels of the left scale labels (0 if not shown)
2131 int getLabelWidthWest()
2133 return labelWidthWest;
2137 * Ensure that a full paint is done next, for whatever reason. This was
2138 * necessary for JavaScript; apparently in Java the timing is just right on
2139 * multiple threads (EventQueue-0, Consensus, Conservation) that we can get
2140 * away with one fast paint before the others, but this ensures that in the
2141 * end we get a full paint. Problem arose in relation to copy/paste, where the
2142 * paste was not finalized with a full paint.
2144 * @author hansonr 2019.04.17
2146 public void clearFastPaint()