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)
292 // if (horizontal != 0 && vertical != 0)
293 // throw new InvalidArgumentException();
294 if (fastpainting || img == null)
302 int charHeight = av.getCharHeight();
303 int charWidth = av.getCharWidth();
305 ViewportRanges ranges = av.getRanges();
306 int startRes = ranges.getStartRes();
307 int endRes = ranges.getEndRes();
308 int startSeq = ranges.getStartSeq();
309 int endSeq = ranges.getEndSeq();
313 if (horizontal > 0) // scrollbar pulled right, image to the left
315 transX = (endRes - startRes - horizontal) * charWidth;
316 startRes = endRes - horizontal;
318 else if (horizontal < 0)
320 endRes = startRes - horizontal;
323 if (vertical > 0) // scroll down
325 startSeq = endSeq - vertical;
327 if (startSeq < ranges.getStartSeq())
328 { // ie scrolling too fast, more than a page at a time
329 startSeq = ranges.getStartSeq();
333 transY = img.getHeight() - ((vertical + 1) * charHeight);
336 else if (vertical < 0)
338 endSeq = startSeq - vertical;
340 if (endSeq > ranges.getEndSeq())
342 endSeq = ranges.getEndSeq();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
355 gg.translate(transX, transY);
356 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
357 gg.translate(-transX, -transY);
360 // Call repaint on alignment panel so that repaints from other alignment
361 // panel components can be aggregated. Otherwise performance of the
362 // overview window and others may be adversely affected.
363 // System.out.println("SeqCanvas fastPaint() repaint() request...");
364 av.getAlignPanel().repaint();
367 fastpainting = false;
372 public void paintComponent(Graphics g)
374 if (av.getAlignPanel().getHoldRepaint())
378 int charHeight = av.getCharHeight();
379 int charWidth = av.getCharWidth();
381 int availWidth = getWidth();
382 int availHeight = getHeight();
384 availWidth -= (availWidth % charWidth);
385 availHeight -= (availHeight % charHeight);
387 if (availWidth == 0 || availHeight == 0)
392 ViewportRanges ranges = av.getRanges();
393 int startRes = ranges.getStartRes();
394 int startSeq = ranges.getStartSeq();
395 int endRes = ranges.getEndRes();
396 int endSeq = ranges.getEndSeq();
401 || (vis = getVisibleRect()).width != (clip = g
402 .getClipBounds()).width
403 || vis.height != clip.height))
405 g.drawImage(img, 0, 0, this);
406 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
412 // img is a cached version of the last view we drew.
413 // If we have no img or the size has changed, make a new one.
415 if (img == null || availWidth != img.getWidth()
416 || availHeight != img.getHeight())
418 img = new BufferedImage(availWidth, availHeight,
419 BufferedImage.TYPE_INT_RGB);
422 Graphics2D gg = (Graphics2D) img.getGraphics();
423 gg.setFont(av.getFont());
427 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
428 RenderingHints.VALUE_ANTIALIAS_ON);
431 gg.setColor(Color.white);
432 gg.fillRect(0, 0, availWidth, availHeight);
434 if (av.getWrapAlignment())
436 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
440 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
443 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
445 g.drawImage(img, 0, 0, this);
451 drawCursor(g, startRes, endRes, startSeq, endSeq);
456 * Draw an alignment panel for printing
459 * Graphics object to draw with
461 * start residue of print area
463 * end residue of print area
465 * start sequence of print area
467 * end sequence of print area
469 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
470 int startSeq, int endSeq)
472 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
474 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
479 * Draw a wrapped alignment panel for printing
482 * Graphics object to draw with
484 * width of drawing area
485 * @param canvasHeight
486 * height of drawing area
488 * start residue of print area
490 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
491 int canvasHeight, int startRes)
493 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
495 SequenceGroup group = av.getSelectionGroup();
498 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
504 * Using the current font, determine fields labelWidthEast and labelWidthWest,
505 * and return the number of residues that can fill the remaining width
508 * the width in pixels (possibly including scales)
510 * @return the visible width in residues, after allowing for East or West
514 public int getWrappedCanvasWidth(int w)
516 int charWidth = av.getCharWidth();
518 FontMetrics fm = getFontMetrics(av.getFont());
520 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
524 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
526 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
528 return (w - labelWidthEast - labelWidthWest) / charWidth;
532 * Returns a pixel width sufficient to show the largest sequence coordinate
533 * (end position) in the alignment, calculated as the FontMetrics width of
534 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
535 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
536 * half a character width space on either side.
541 protected int getLabelWidth(FontMetrics fm)
544 * find the biggest sequence end position we need to show
545 * (note this is not necessarily the sequence length)
548 AlignmentI alignment = av.getAlignment();
549 for (int i = 0; i < alignment.getHeight(); i++)
551 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
556 for (int i = maxWidth; i > 0; i /= 10)
561 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
565 * Draws as many widths of a wrapped alignment as can fit in the visible
570 * available width in pixels
572 * available height in pixels
574 * the first column (0...) of the alignment to draw
576 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
577 final int startColumn)
579 int wrappedWidthInResidues = calculateWrappedGeometry(availWidth,
582 av.setWrappedWidth(wrappedWidthInResidues);
584 ViewportRanges ranges = av.getRanges();
585 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
587 // we need to call this again to make sure the startColumn +
588 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
590 calculateWrappedGeometry(availWidth, availHeight);
593 * draw one width at a time (excluding any scales shown),
594 * until we have run out of either alignment or vertical space available
596 int ypos = wrappedSpaceAboveAlignment;
597 int maxWidth = ranges.getVisibleAlignmentWidth();
599 int start = startColumn;
600 int currentWidth = 0;
601 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
604 .min(maxWidth, start + wrappedWidthInResidues - 1);
605 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
606 ypos += wrappedRepeatHeightPx;
607 start += wrappedWidthInResidues;
611 drawWrappedDecorators(g, startColumn);
615 * Calculates and saves values needed when rendering a wrapped alignment.
616 * These depend on many factors, including
618 * <li>canvas width and height</li>
619 * <li>number of visible sequences, and height of annotations if shown</li>
620 * <li>font and character width</li>
621 * <li>whether scales are shown left, right or above the alignment</li>
626 * @return the number of residue columns in each width
628 protected int calculateWrappedGeometry(int availWidth, int availHeight)
630 int charHeight = av.getCharHeight();
633 * vertical space in pixels between wrapped widths of alignment
634 * - one character height, or two if scale above is drawn
636 wrappedSpaceAboveAlignment = charHeight
637 * (av.getScaleAboveWrapped() ? 2 : 1);
640 * compute height in pixels of the wrapped widths
641 * - start with space above plus sequences
643 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
644 wrappedRepeatHeightPx += av.getAlignment().getHeight()
648 * add annotations panel height if shown
649 * also gap between sequences and annotations
651 if (av.isShowAnnotation())
653 wrappedRepeatHeightPx += getAnnotationHeight();
654 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
658 * number of visible widths (the last one may be part height),
659 * ensuring a part height includes at least one sequence
661 ViewportRanges ranges = av.getRanges();
662 wrappedVisibleWidths = availHeight / wrappedRepeatHeightPx;
663 int remainder = availHeight % wrappedRepeatHeightPx;
664 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
666 wrappedVisibleWidths++;
670 * compute width in residues; this also sets East and West label widths
672 int wrappedWidthInResidues = getWrappedCanvasWidth(availWidth);
675 * limit visibleWidths to not exceed width of alignment
677 int xMax = ranges.getVisibleAlignmentWidth();
678 int startToEnd = xMax - ranges.getStartRes();
679 int maxWidths = startToEnd / wrappedWidthInResidues;
680 if (startToEnd % wrappedWidthInResidues > 0)
684 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
686 return wrappedWidthInResidues;
690 * Draws one width of a wrapped alignment, including sequences and
691 * annnotations, if shown, but not scales or hidden column markers
697 * @param canvasHeight
699 protected void drawWrappedWidth(Graphics g, final int ypos,
700 final int startColumn, final int endColumn,
701 final int canvasHeight)
703 ViewportRanges ranges = av.getRanges();
704 int viewportWidth = ranges.getViewportWidth();
706 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
709 * move right before drawing by the width of the scale left (if any)
710 * plus column offset from left margin (usually zero, but may be non-zero
711 * when fast painting is drawing just a few columns)
713 int charWidth = av.getCharWidth();
714 int xOffset = labelWidthWest
715 + ((startColumn - ranges.getStartRes()) % viewportWidth)
718 g.translate(xOffset, 0);
721 * white fill the region to be drawn (so incremental fast paint doesn't
722 * scribble over an existing image)
724 g.setColor(Color.white);
725 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
726 wrappedRepeatHeightPx);
728 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
731 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
733 if (av.isShowAnnotation())
735 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
736 g.translate(0, yShift);
737 if (annotations == null)
739 annotations = new AnnotationPanel(av);
742 annotations.renderer.drawComponent(annotations, av, g, -1,
743 startColumn, endx + 1);
744 g.translate(0, -yShift);
746 g.translate(-xOffset, 0);
750 * Draws scales left, right and above (if shown), and any hidden column
751 * markers, on all widths of the wrapped alignment
756 protected void drawWrappedDecorators(Graphics g, final int startColumn)
758 int charWidth = av.getCharWidth();
760 g.setFont(av.getFont());
762 g.setColor(Color.black);
764 int ypos = wrappedSpaceAboveAlignment;
765 ViewportRanges ranges = av.getRanges();
766 int viewportWidth = ranges.getViewportWidth();
767 int maxWidth = ranges.getVisibleAlignmentWidth();
769 int startCol = startColumn;
771 while (widthsDrawn < wrappedVisibleWidths)
773 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
775 if (av.getScaleLeftWrapped())
777 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
780 if (av.getScaleRightWrapped())
782 int x = labelWidthWest + viewportWidth * charWidth;
785 drawVerticalScale(g, startCol, endColumn, ypos, false);
790 * white fill region of scale above and hidden column markers
791 * (to support incremental fast paint of image)
793 g.translate(labelWidthWest, 0);
794 g.setColor(Color.white);
795 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
796 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
797 g.setColor(Color.black);
798 g.translate(-labelWidthWest, 0);
800 g.translate(labelWidthWest, 0);
802 if (av.getScaleAboveWrapped())
804 drawNorthScale(g, startCol, endColumn, ypos);
807 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
809 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
812 g.translate(-labelWidthWest, 0);
814 ypos += wrappedRepeatHeightPx;
815 startCol += viewportWidth;
821 * Draws markers (triangles) above hidden column positions between startColumn
829 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
830 int startColumn, int endColumn)
832 int charHeight = av.getCharHeight();
833 int charWidth = av.getCharWidth();
835 g.setColor(Color.blue);
837 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
839 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
843 res = it.next() - startColumn;
845 if (res < 0 || res > endColumn - startColumn + 1)
851 * draw a downward-pointing triangle at the hidden columns location
852 * (before the following visible column)
854 int xMiddle = res * charWidth;
855 int[] xPoints = new int[] { xMiddle - charHeight / 4,
856 xMiddle + charHeight / 4, xMiddle };
857 int yTop = ypos - (charHeight / 2);
858 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
859 g.fillPolygon(xPoints, yPoints, 3);
864 * Draw a selection group over a wrapped alignment
866 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
868 int canvasHeight, int startRes)
870 int charHeight = av.getCharHeight();
871 int charWidth = av.getCharWidth();
873 // height gap above each panel
874 int hgap = charHeight;
875 if (av.getScaleAboveWrapped())
880 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
882 int cHeight = av.getAlignment().getHeight() * charHeight;
884 int startx = startRes;
886 int ypos = hgap; // vertical offset
887 int maxwidth = av.getAlignment().getVisibleWidth();
889 // chop the wrapped alignment extent up into panel-sized blocks and treat
890 // each block as if it were a block from an unwrapped alignment
891 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
892 BasicStroke.JOIN_ROUND, 3f, new float[]
894 g.setColor(Color.RED);
895 while ((ypos <= canvasHeight) && (startx < maxwidth))
897 // set end value to be start + width, or maxwidth, whichever is smaller
898 endx = startx + cWidth - 1;
905 g.translate(labelWidthWest, 0);
907 drawUnwrappedSelection(g, group, startx, endx, 0,
908 av.getAlignment().getHeight() - 1,
911 g.translate(-labelWidthWest, 0);
913 // update vertical offset
914 ypos += cHeight + getAnnotationHeight() + hgap;
916 // update horizontal offset
919 g.setStroke(new BasicStroke());
922 int getAnnotationHeight()
924 if (!av.isShowAnnotation())
929 if (annotations == null)
931 annotations = new AnnotationPanel(av);
934 return annotations.adjustPanelHeight();
938 * Draws the visible region of the alignment on the graphics context. If there
939 * are hidden column markers in the visible region, then each sub-region
940 * between the markers is drawn separately, followed by the hidden column
944 * the graphics context, positioned at the first residue to be drawn
946 * offset of the first column to draw (0..)
948 * offset of the last column to draw (0..)
950 * offset of the first sequence to draw (0..)
952 * offset of the last sequence to draw (0..)
954 * vertical offset at which to draw (for wrapped alignments)
956 public void drawPanel(Graphics g1, final int startRes, final int endRes,
957 final int startSeq, final int endSeq, final int yOffset)
959 int charHeight = av.getCharHeight();
960 int charWidth = av.getCharWidth();
962 if (!av.hasHiddenColumns())
964 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
972 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
973 VisibleContigsIterator regions = hidden
974 .getVisContigsIterator(startRes, endRes + 1, true);
976 while (regions.hasNext())
978 int[] region = regions.next();
979 blockEnd = region[1];
980 blockStart = region[0];
983 * draw up to just before the next hidden region, or the end of
984 * the visible region, whichever comes first
986 g1.translate(screenY * charWidth, 0);
988 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
991 * draw the downline of the hidden column marker (ScalePanel draws the
992 * triangle on top) if we reached it
994 if (av.getShowHiddenMarkers()
995 && (regions.hasNext() || regions.endsAtHidden()))
997 g1.setColor(Color.blue);
999 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1000 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1001 (endSeq - startSeq + 1) * charHeight + yOffset);
1004 g1.translate(-screenY * charWidth, 0);
1005 screenY += blockEnd - blockStart + 1;
1012 * Draws a region of the visible alignment
1016 * offset of the first column in the visible region (0..)
1018 * offset of the last column in the visible region (0..)
1020 * offset of the first sequence in the visible region (0..)
1022 * offset of the last sequence in the visible region (0..)
1024 * vertical offset at which to draw (for wrapped alignments)
1026 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1027 int endSeq, int offset)
1029 int charHeight = av.getCharHeight();
1030 int charWidth = av.getCharWidth();
1032 g.setFont(av.getFont());
1033 seqRdr.prepare(g, av.isRenderGaps());
1037 // / First draw the sequences
1038 // ///////////////////////////
1039 for (int i = startSeq; i <= endSeq; i++)
1041 nextSeq = av.getAlignment().getSequenceAt(i);
1042 if (nextSeq == null)
1044 // occasionally, a race condition occurs such that the alignment row is
1048 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1049 startRes, endRes, offset + ((i - startSeq) * charHeight));
1051 if (av.isShowSequenceFeatures())
1053 fr.drawSequence(g, nextSeq, startRes, endRes,
1054 offset + ((i - startSeq) * charHeight), false);
1058 * highlight search Results once sequence has been drawn
1060 if (av.hasSearchResults())
1062 SearchResultsI searchResults = av.getSearchResults();
1063 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1065 if (visibleResults != null)
1067 for (int r = 0; r < visibleResults.length; r += 2)
1069 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1070 visibleResults[r + 1],
1071 (visibleResults[r] - startRes) * charWidth,
1072 offset + ((i - startSeq) * charHeight));
1078 if (av.getSelectionGroup() != null
1079 || av.getAlignment().getGroups().size() > 0)
1081 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1087 * Draws the outlines of any groups defined on the alignment (excluding the
1088 * current selection group, if any)
1097 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1098 int startSeq, int endSeq, int offset)
1100 Graphics2D g = (Graphics2D) g1;
1102 SequenceGroup group = null;
1103 int groupIndex = -1;
1105 if (av.getAlignment().getGroups().size() > 0)
1107 group = av.getAlignment().getGroups().get(0);
1115 g.setColor(group.getOutlineColour());
1116 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1120 if (groupIndex >= av.getAlignment().getGroups().size())
1124 group = av.getAlignment().getGroups().get(groupIndex);
1125 } while (groupIndex < av.getAlignment().getGroups().size());
1130 * Draws the outline of the current selection group (if any)
1138 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1139 int startSeq, int endSeq)
1141 SequenceGroup group = av.getSelectionGroup();
1147 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1148 BasicStroke.JOIN_ROUND, 3f, new float[]
1150 g.setColor(Color.RED);
1151 if (!av.getWrapAlignment())
1153 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1158 drawWrappedSelection(g, group, getWidth(), getHeight(),
1159 av.getRanges().getStartRes());
1161 g.setStroke(new BasicStroke());
1165 * Draw the cursor as a separate image and overlay
1168 * start residue of area to draw cursor in
1170 * end residue of area to draw cursor in
1172 * start sequence of area to draw cursor in
1174 * end sequence of are to draw cursor in
1175 * @return a transparent image of the same size as the sequence canvas, with
1176 * the cursor drawn on it, if any
1178 private void drawCursor(Graphics g, int startRes, int endRes,
1182 // convert the cursorY into a position on the visible alignment
1183 int cursor_ypos = cursorY;
1185 // don't do work unless we have to
1186 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1190 int startx = startRes;
1193 // convert the cursorX into a position on the visible alignment
1194 int cursor_xpos = av.getAlignment().getHiddenColumns()
1195 .absoluteToVisibleColumn(cursorX);
1197 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1200 if (av.getWrapAlignment())
1202 // work out the correct offsets for the cursor
1203 int charHeight = av.getCharHeight();
1204 int charWidth = av.getCharWidth();
1205 int canvasWidth = getWidth();
1206 int canvasHeight = getHeight();
1208 // height gap above each panel
1209 int hgap = charHeight;
1210 if (av.getScaleAboveWrapped())
1215 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1217 int cHeight = av.getAlignment().getHeight() * charHeight;
1219 endx = startx + cWidth - 1;
1220 int ypos = hgap; // vertical offset
1222 // iterate down the wrapped panels
1223 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1225 // update vertical offset
1226 ypos += cHeight + getAnnotationHeight() + hgap;
1228 // update horizontal offset
1230 endx = startx + cWidth - 1;
1233 xoffset = labelWidthWest;
1236 // now check if cursor is within range for x values
1237 if (cursor_xpos >= startx && cursor_xpos <= endx)
1239 // get the character the cursor is drawn at
1240 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1241 char s = seq.getCharAt(cursorX);
1243 seqRdr.drawCursor(g, s,
1244 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1245 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1253 * Draw a selection group over an unwrapped alignment
1256 * graphics object to draw with
1260 * start residue of area to draw
1262 * end residue of area to draw
1264 * start sequence of area to draw
1266 * end sequence of area to draw
1268 * vertical offset (used when called from wrapped alignment code)
1270 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1271 int startRes, int endRes, int startSeq, int endSeq, int offset)
1273 int charWidth = av.getCharWidth();
1275 if (!av.hasHiddenColumns())
1277 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1282 // package into blocks of visible columns
1287 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1288 VisibleContigsIterator regions = hidden
1289 .getVisContigsIterator(startRes, endRes + 1, true);
1290 while (regions.hasNext())
1292 int[] region = regions.next();
1293 blockEnd = region[1];
1294 blockStart = region[0];
1296 g.translate(screenY * charWidth, 0);
1297 drawPartialGroupOutline(g, group,
1298 blockStart, blockEnd, startSeq, endSeq, offset);
1300 g.translate(-screenY * charWidth, 0);
1301 screenY += blockEnd - blockStart + 1;
1307 * Draws part of a selection group outline
1315 * @param verticalOffset
1317 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1318 int startRes, int endRes, int startSeq, int endSeq,
1321 int charHeight = av.getCharHeight();
1322 int charWidth = av.getCharWidth();
1323 int visWidth = (endRes - startRes + 1) * charWidth;
1327 boolean inGroup = false;
1332 List<SequenceI> seqs = group.getSequences(null);
1334 // position of start residue of group relative to startRes, in pixels
1335 int sx = (group.getStartRes() - startRes) * charWidth;
1337 // width of group in pixels
1338 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1341 if (!(sx + xwidth < 0 || sx > visWidth))
1343 for (i = startSeq; i <= endSeq; i++)
1345 sy = verticalOffset + (i - startSeq) * charHeight;
1347 if ((sx <= (endRes - startRes) * charWidth)
1348 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1351 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1353 bottom = sy + charHeight;
1358 if (((top == -1) && (i == 0)) || !seqs
1359 .contains(av.getAlignment().getSequenceAt(i - 1)))
1370 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1371 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1373 // reset top and bottom
1381 sy = verticalOffset + ((i - startSeq) * charHeight);
1382 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1383 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1389 * Draw horizontal selection group boundaries at top and bottom positions
1392 * graphics object to draw on
1398 * visWidth maximum available width
1400 * position to draw top of group at
1402 * position to draw bottom of group at
1404 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1405 int visWidth, int top, int bottom)
1415 // don't let width extend beyond current block, or group extent
1417 if (startx + width >= visWidth)
1419 width = visWidth - startx;
1424 g.drawLine(startx, top, startx + width, top);
1429 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1434 * Draw vertical lines at sx and sx+xwidth providing they lie within
1438 * graphics object to draw on
1444 * visWidth maximum available width
1450 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1453 // if start position is visible, draw vertical line to left of
1455 if (sx >= 0 && sx < visWidth)
1457 g.drawLine(sx, oldY, sx, sy);
1460 // if end position is visible, draw vertical line to right of
1462 if (sx + xwidth < visWidth)
1464 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1469 * Highlights search results in the visible region by rendering as white text
1470 * on a black background. Any previous highlighting is removed. Answers true
1471 * if any highlight was left on the visible alignment (so status bar should be
1472 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1473 * so allows the next repaint to update the whole display.
1478 public boolean highlightSearchResults(SearchResultsI results)
1480 return highlightSearchResults(results, false);
1485 * Highlights search results in the visible region by rendering as white text
1486 * on a black background. Any previous highlighting is removed. Answers true
1487 * if any highlight was left on the visible alignment (so status bar should be
1488 * set to match), else false.
1490 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1491 * highlighted regions are modified. This speeds up highlighting across linked
1494 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1495 * a wrapped alignment had to be scrolled to show the highlighted region, then
1496 * it should be fully redrawn, otherwise a fast paint can be performed. This
1497 * argument could be removed if fast paint of scrolled wrapped alignment is
1498 * coded in future (JAL-2609).
1501 * @param doFastPaint
1502 * if true, sets a flag so the next repaint only redraws the modified
1506 public boolean highlightSearchResults(SearchResultsI results,
1507 boolean doFastPaint)
1513 boolean wrapped = av.getWrapAlignment();
1516 fastPaint = doFastPaint;
1517 fastpainting = fastPaint;
1520 * to avoid redrawing the whole visible region, we instead
1521 * redraw just the minimal regions to remove previous highlights
1524 SearchResultsI previous = av.getSearchResults();
1525 av.setSearchResults(results);
1526 boolean redrawn = false;
1527 boolean drawn = false;
1530 redrawn = drawMappedPositionsWrapped(previous);
1531 drawn = drawMappedPositionsWrapped(results);
1536 redrawn = drawMappedPositions(previous);
1537 drawn = drawMappedPositions(results);
1542 * if highlights were either removed or added, repaint
1550 * return true only if highlights were added
1556 fastpainting = false;
1561 * Redraws the minimal rectangle in the visible region (if any) that includes
1562 * mapped positions of the given search results. Whether or not positions are
1563 * highlighted depends on the SearchResults set on the Viewport. This allows
1564 * this method to be called to either clear or set highlighting. Answers true
1565 * if any positions were drawn (in which case a repaint is still required),
1571 protected boolean drawMappedPositions(SearchResultsI results)
1573 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1579 * calculate the minimal rectangle to redraw that
1580 * includes both new and existing search results
1582 int firstSeq = Integer.MAX_VALUE;
1584 int firstCol = Integer.MAX_VALUE;
1586 boolean matchFound = false;
1588 ViewportRanges ranges = av.getRanges();
1589 int firstVisibleColumn = ranges.getStartRes();
1590 int lastVisibleColumn = ranges.getEndRes();
1591 AlignmentI alignment = av.getAlignment();
1592 if (av.hasHiddenColumns())
1594 firstVisibleColumn = alignment.getHiddenColumns()
1595 .visibleToAbsoluteColumn(firstVisibleColumn);
1596 lastVisibleColumn = alignment.getHiddenColumns()
1597 .visibleToAbsoluteColumn(lastVisibleColumn);
1600 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1601 .getEndSeq(); seqNo++)
1603 SequenceI seq = alignment.getSequenceAt(seqNo);
1605 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1607 if (visibleResults != null)
1609 for (int i = 0; i < visibleResults.length - 1; i += 2)
1611 int firstMatchedColumn = visibleResults[i];
1612 int lastMatchedColumn = visibleResults[i + 1];
1613 if (firstMatchedColumn <= lastVisibleColumn
1614 && lastMatchedColumn >= firstVisibleColumn)
1617 * found a search results match in the visible region -
1618 * remember the first and last sequence matched, and the first
1619 * and last visible columns in the matched positions
1622 firstSeq = Math.min(firstSeq, seqNo);
1623 lastSeq = Math.max(lastSeq, seqNo);
1624 firstMatchedColumn = Math.max(firstMatchedColumn,
1625 firstVisibleColumn);
1626 lastMatchedColumn = Math.min(lastMatchedColumn,
1628 firstCol = Math.min(firstCol, firstMatchedColumn);
1629 lastCol = Math.max(lastCol, lastMatchedColumn);
1637 if (av.hasHiddenColumns())
1639 firstCol = alignment.getHiddenColumns()
1640 .absoluteToVisibleColumn(firstCol);
1641 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1643 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1644 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1645 Graphics gg = img.getGraphics();
1646 gg.translate(transX, transY);
1647 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1648 gg.translate(-transX, -transY);
1656 public void propertyChange(PropertyChangeEvent evt)
1658 String eventName = evt.getPropertyName();
1660 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1661 // logic, emphasizing no check for ENDRES or ENDSEQ
1663 // Both scrolling and resizing change viewport ranges: scrolling changes
1664 // both start and end points, but resize only changes end values.
1665 // Here we only want to fastpaint on a scroll, with resize using a normal
1666 // paint, so scroll events are identified as changes to the horizontal or
1667 // vertical start value.
1669 // Make sure we're not trying to draw a panel
1670 // larger than the visible window
1675 case SequenceGroup.SEQ_GROUP_CHANGED:
1679 case ViewportRanges.MOVE_VIEWPORT:
1683 case ViewportRanges.STARTSEQ:
1684 // meaning STARTOREND
1685 // typically scroll, but possibly just the end changed
1686 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1688 case ViewportRanges.STARTRES:
1689 // meaning STARTOREND
1690 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1692 case ViewportRanges.STARTRESANDSEQ:
1693 scrollX = ((int[]) evt.getNewValue())[0]
1694 - ((int[]) evt.getOldValue())[0];
1695 scrollY = ((int[]) evt.getNewValue())[1]
1696 - ((int[]) evt.getOldValue())[1];
1698 // System.out.println("SC dx dy " + scrollX + " " + scrollY);
1700 if (scrollX != 0 && scrollY != 0)
1702 // all sorts of problems in JavaScript if this is commented out.
1712 ViewportRanges vpRanges = av.getRanges();
1713 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1714 scrollX = Math.max(Math.min(scrollX, range), -range);
1715 // only STARTRES or STARTRESANDSEQ:
1716 if (av.getWrapAlignment())
1718 fastPaintWrapped(scrollX);
1722 fastPaint(scrollX, scrollY);
1725 // BH 2019.07.27 was:
1726 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1728 // fastPaint = true;
1732 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1734 // fastPaint = false;
1735 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1740 // if (eventName.equals(ViewportRanges.STARTRES)
1741 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1743 // // Make sure we're not trying to draw a panel
1744 // // larger than the visible window
1745 // if (eventName.equals(ViewportRanges.STARTRES))
1747 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1751 // scrollX = ((int[]) evt.getNewValue())[0]
1752 // - ((int[]) evt.getOldValue())[0];
1754 // ViewportRanges vpRanges = av.getRanges();
1756 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1757 // if (scrollX > range)
1761 // else if (scrollX < -range)
1763 // scrollX = -range;
1766 // Both scrolling and resizing change viewport ranges: scrolling changes
1767 // both start and end points, but resize only changes end values.
1768 // Here we only want to fastpaint on a scroll, with resize using a normal
1769 // paint, so scroll events are identified as changes to the horizontal or
1770 // vertical start value.
1771 // BH 2019.07.27 was:
1772 // if (eventName.equals(ViewportRanges.STARTRES))
1774 // if (av.getWrapAlignment())
1776 // fastPaintWrapped(scrollX);
1780 // fastPaint(scrollX, 0);
1783 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1786 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1788 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1790 // if (av.getWrapAlignment())
1792 // fastPaintWrapped(scrollX);
1796 // fastPaint(scrollX, 0);
1802 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1805 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1807 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1809 // if (av.getWrapAlignment())
1811 // fastPaintWrapped(scrollX);
1817 * Does a minimal update of the image for a scroll movement. This method
1818 * handles scroll movements of up to one width of the wrapped alignment (one
1819 * click in the vertical scrollbar). Larger movements (for example after a
1820 * scroll to highlight a mapped position) trigger a full redraw instead.
1823 * number of positions scrolled (right if positive, left if negative)
1825 protected void fastPaintWrapped(int scrollX)
1827 ViewportRanges ranges = av.getRanges();
1829 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1832 * shift of one view width or more is
1833 * overcomplicated to handle in this method
1840 if (fastpainting || img == null)
1846 fastpainting = true;
1851 Graphics gg = img.getGraphics();
1853 calculateWrappedGeometry(getWidth(), getHeight());
1856 * relocate the regions of the alignment that are still visible
1858 shiftWrappedAlignment(-scrollX);
1861 * add new columns (sequence, annotation)
1862 * - at top left if scrollX < 0
1863 * - at right of last two widths if scrollX > 0
1867 int startRes = ranges.getStartRes();
1868 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1869 - scrollX - 1, getHeight());
1873 fastPaintWrappedAddRight(scrollX);
1877 * draw all scales (if shown) and hidden column markers
1879 drawWrappedDecorators(gg, ranges.getStartRes());
1886 fastpainting = false;
1891 * Draws the specified number of columns at the 'end' (bottom right) of a
1892 * wrapped alignment view, including sequences and annotations if shown, but
1893 * not scales. Also draws the same number of columns at the right hand end of
1894 * the second last width shown, if the last width is not full height (so
1895 * cannot simply be copied from the graphics image).
1899 protected void fastPaintWrappedAddRight(int columns)
1906 Graphics gg = img.getGraphics();
1908 ViewportRanges ranges = av.getRanges();
1909 int viewportWidth = ranges.getViewportWidth();
1910 int charWidth = av.getCharWidth();
1913 * draw full height alignment in the second last row, last columns, if the
1914 * last row was not full height
1916 int visibleWidths = wrappedVisibleWidths;
1917 int canvasHeight = getHeight();
1918 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1920 if (lastWidthPartHeight)
1922 int widthsAbove = Math.max(0, visibleWidths - 2);
1923 int ypos = wrappedRepeatHeightPx * widthsAbove
1924 + wrappedSpaceAboveAlignment;
1925 int endRes = ranges.getEndRes();
1926 endRes += widthsAbove * viewportWidth;
1927 int startRes = endRes - columns;
1928 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1932 * white fill first to erase annotations
1936 gg.translate(xOffset, 0);
1937 gg.setColor(Color.white);
1938 gg.fillRect(labelWidthWest, ypos,
1939 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1940 gg.translate(-xOffset, 0);
1942 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1947 * draw newly visible columns in last wrapped width (none if we
1948 * have reached the end of the alignment)
1949 * y-offset for drawing last width is height of widths above,
1952 int widthsAbove = visibleWidths - 1;
1953 int ypos = wrappedRepeatHeightPx * widthsAbove
1954 + wrappedSpaceAboveAlignment;
1955 int endRes = ranges.getEndRes();
1956 endRes += widthsAbove * viewportWidth;
1957 int startRes = endRes - columns + 1;
1960 * white fill first to erase annotations
1962 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1964 gg.translate(xOffset, 0);
1965 gg.setColor(Color.white);
1966 int width = viewportWidth * charWidth - xOffset;
1967 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1968 gg.translate(-xOffset, 0);
1970 gg.setFont(av.getFont());
1971 gg.setColor(Color.black);
1973 if (startRes < ranges.getVisibleAlignmentWidth())
1975 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1979 * and finally, white fill any space below the visible alignment
1981 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1982 if (heightBelow > 0)
1984 gg.setColor(Color.white);
1985 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1991 * Shifts the visible alignment by the specified number of columns - left if
1992 * negative, right if positive. Copies and moves sequences and annotations (if
1993 * shown). Scales, hidden column markers and any newly visible columns must be
1998 protected void shiftWrappedAlignment(int positions)
2005 Graphics gg = img.getGraphics();
2007 int charWidth = av.getCharWidth();
2009 int canvasHeight = getHeight();
2010 ViewportRanges ranges = av.getRanges();
2011 int viewportWidth = ranges.getViewportWidth();
2012 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2014 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2015 int xMax = ranges.getVisibleAlignmentWidth();
2020 * shift right (after scroll left)
2021 * for each wrapped width (starting with the last), copy (width-positions)
2022 * columns from the left margin to the right margin, and copy positions
2023 * columns from the right margin of the row above (if any) to the
2024 * left margin of the current row
2028 * get y-offset of last wrapped width, first row of sequences
2030 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2031 y += wrappedSpaceAboveAlignment;
2032 int copyFromLeftStart = labelWidthWest;
2033 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2038 * shift 'widthToCopy' residues by 'positions' places to the right
2040 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2041 positions * charWidth, 0);
2045 * copy 'positions' residue from the row above (right hand end)
2046 * to this row's left hand end
2048 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2049 positions * charWidth, heightToCopy, -widthToCopy,
2050 wrappedRepeatHeightPx);
2053 y -= wrappedRepeatHeightPx;
2059 * shift left (after scroll right)
2060 * for each wrapped width (starting with the first), copy (width-positions)
2061 * columns from the right margin to the left margin, and copy positions
2062 * columns from the left margin of the row below (if any) to the
2063 * right margin of the current row
2065 int xpos = av.getRanges().getStartRes();
2066 int y = wrappedSpaceAboveAlignment;
2067 int copyFromRightStart = labelWidthWest - positions * charWidth;
2069 while (y < canvasHeight)
2071 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2072 positions * charWidth, 0);
2073 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2074 && (xpos + viewportWidth <= xMax))
2076 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2077 * charWidth, heightToCopy, widthToCopy,
2078 -wrappedRepeatHeightPx);
2080 y += wrappedRepeatHeightPx;
2081 xpos += viewportWidth;
2089 * Redraws any positions in the search results in the visible region of a
2090 * wrapped alignment. Any highlights are drawn depending on the search results
2091 * set on the Viewport, not the <code>results</code> argument. This allows
2092 * this method to be called either to clear highlights (passing the previous
2093 * search results), or to draw new highlights.
2098 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2100 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2104 int charHeight = av.getCharHeight();
2106 boolean matchFound = false;
2108 calculateWrappedGeometry(getWidth(), getHeight());
2109 int wrappedWidth = av.getWrappedWidth();
2110 int wrappedHeight = wrappedRepeatHeightPx;
2112 ViewportRanges ranges = av.getRanges();
2113 int canvasHeight = getHeight();
2114 int repeats = canvasHeight / wrappedHeight;
2115 if (canvasHeight / wrappedHeight > 0)
2120 int firstVisibleColumn = ranges.getStartRes();
2121 int lastVisibleColumn = ranges.getStartRes() + repeats
2122 * ranges.getViewportWidth() - 1;
2124 AlignmentI alignment = av.getAlignment();
2125 if (av.hasHiddenColumns())
2127 firstVisibleColumn = alignment.getHiddenColumns()
2128 .visibleToAbsoluteColumn(firstVisibleColumn);
2129 lastVisibleColumn = alignment.getHiddenColumns()
2130 .visibleToAbsoluteColumn(lastVisibleColumn);
2133 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2136 Graphics gg = img.getGraphics();
2138 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2139 .getEndSeq(); seqNo++)
2141 SequenceI seq = alignment.getSequenceAt(seqNo);
2143 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2145 if (visibleResults != null)
2147 for (int i = 0; i < visibleResults.length - 1; i += 2)
2149 int firstMatchedColumn = visibleResults[i];
2150 int lastMatchedColumn = visibleResults[i + 1];
2151 if (firstMatchedColumn <= lastVisibleColumn
2152 && lastMatchedColumn >= firstVisibleColumn)
2155 * found a search results match in the visible region
2157 firstMatchedColumn = Math.max(firstMatchedColumn,
2158 firstVisibleColumn);
2159 lastMatchedColumn = Math.min(lastMatchedColumn,
2163 * draw each mapped position separately (as contiguous positions may
2164 * wrap across lines)
2166 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2168 int displayColumn = mappedPos;
2169 if (av.hasHiddenColumns())
2171 displayColumn = alignment.getHiddenColumns()
2172 .absoluteToVisibleColumn(displayColumn);
2176 * transX: offset from left edge of canvas to residue position
2178 int transX = labelWidthWest
2179 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2180 * av.getCharWidth();
2183 * transY: offset from top edge of canvas to residue position
2185 int transY = gapHeight;
2186 transY += (displayColumn - ranges.getStartRes())
2187 / wrappedWidth * wrappedHeight;
2188 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2191 * yOffset is from graphics origin to start of visible region
2193 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2194 if (transY < getHeight())
2197 gg.translate(transX, transY);
2198 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2200 gg.translate(-transX, -transY);
2214 * Answers the width in pixels of the left scale labels (0 if not shown)
2218 int getLabelWidthWest()
2220 return labelWidthWest;
2224 * Clears the flag that allows a 'fast paint' on the next repaint, so
2225 * requiring a full repaint
2227 public void setNoFastPaint()