2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.Rectangle;
42 import java.awt.RenderingHints;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JPanel;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 @SuppressWarnings("serial")
57 public class SeqCanvas extends JPanel implements ViewportListenerI
60 * pixels gap between sequences and annotations when in wrapped mode
62 static final int SEQS_ANNOTATION_GAP = 3;
64 private static final String ZEROS = "0000000000";
66 final FeatureRenderer fr;
76 private final SequenceRenderer seqRdr;
78 boolean fastPaint = false;
80 private boolean fastpainting = false;
82 private AnnotationPanel annotations;
85 * measurements for drawing a wrapped alignment
87 private int labelWidthEast; // label right width in pixels if shown
89 private int labelWidthWest; // label left width in pixels if shown
91 int wrappedSpaceAboveAlignment; // gap between widths
93 int wrappedRepeatHeightPx; // height in pixels of wrapped width
95 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 // Don't do this! Graphics handles are supposed to be transient
98 //private Graphics2D gg;
101 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
108 fr = new FeatureRenderer(ap);
109 seqRdr = new SequenceRenderer(av);
110 setLayout(new BorderLayout());
111 PaintRefresher.Register(this, av.getSequenceSetId());
112 setBackground(Color.white);
114 av.getRanges().addPropertyChangeListener(this);
117 public SequenceRenderer getSequenceRenderer()
122 public FeatureRenderer getFeatureRenderer()
128 * Draws the scale above a region of a wrapped alignment, consisting of a
129 * column number every major interval (10 columns).
132 * the graphics context to draw on, positioned at the start (bottom
133 * left) of the line on which to draw any scale marks
135 * start alignment column (0..)
137 * end alignment column (0..)
139 * y offset to draw at
141 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
143 int charHeight = av.getCharHeight();
144 int charWidth = av.getCharWidth();
147 * white fill the scale space (for the fastPaint case)
149 g.setColor(Color.white);
150 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
151 charHeight * 3 / 2 + 2);
152 g.setColor(Color.black);
154 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
156 for (ScaleMark mark : marks)
158 int mpos = mark.column; // (i - startx - 1)
163 String mstring = mark.text;
169 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
173 * draw a tick mark below the column number, centred on the column;
174 * height of tick mark is 4 pixels less than half a character
176 int xpos = (mpos * charWidth) + (charWidth / 2);
177 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
183 * Draw the scale to the left or right of a wrapped alignment
186 * graphics context, positioned at the start of the scale to be drawn
188 * first column of wrapped width (0.. excluding any hidden columns)
190 * last column of wrapped width (0.. excluding any hidden columns)
192 * vertical offset at which to begin the scale
194 * if true, scale is left of residues, if false, scale is right
196 void drawVerticalScale(Graphics g, final int startx, final int endx,
197 final int ypos, final boolean left)
199 int charHeight = av.getCharHeight();
200 int charWidth = av.getCharWidth();
202 int yPos = ypos + charHeight;
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
210 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startX : endX;
224 while (index >= startX && index <= endX)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
243 * white fill the space for the scale
245 g.setColor(Color.white);
246 int y = (yPos + (i * charHeight)) - (charHeight / 5);
247 // fillRect origin is top left of rectangle
248 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
254 * draw scale value, right justified within its width less half a
255 * character width padding on the right
257 int labelSpace = left ? labelWidthWest : labelWidthEast;
258 labelSpace -= charWidth / 2; // leave space to the right
259 String valueAsString = String.valueOf(value);
260 int labelLength = fm.stringWidth(valueAsString);
261 int xOffset = labelSpace - labelLength;
262 g.setColor(Color.black);
263 g.drawString(valueAsString, xOffset, y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
280 * <li>pasting a block of sequences</li>
284 * columns to shift right (positive) or left (negative)
286 * rows to shift down (positive) or up (negative)
288 public void fastPaint(int horizontal, int vertical)
292 // if (horizontal != 0 && vertical != 0)
293 // throw new InvalidArgumentException();
294 if (fastpainting || img == null)
302 int charHeight = av.getCharHeight();
303 int charWidth = av.getCharWidth();
305 ViewportRanges ranges = av.getRanges();
306 int startRes = ranges.getStartRes();
307 int endRes = ranges.getEndRes();
308 int startSeq = ranges.getStartSeq();
309 int endSeq = ranges.getEndSeq();
313 if (horizontal > 0) // scrollbar pulled right, image to the left
315 transX = (endRes - startRes - horizontal) * charWidth;
316 startRes = endRes - horizontal;
318 else if (horizontal < 0)
320 endRes = startRes - horizontal;
323 if (vertical > 0) // scroll down
325 startSeq = endSeq - vertical;
327 if (startSeq < ranges.getStartSeq())
328 { // ie scrolling too fast, more than a page at a time
329 startSeq = ranges.getStartSeq();
333 transY = img.getHeight() - ((vertical + 1) * charHeight);
336 else if (vertical < 0)
338 endSeq = startSeq - vertical;
340 if (endSeq > ranges.getEndSeq())
342 endSeq = ranges.getEndSeq();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
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)
375 int charHeight = av.getCharHeight();
376 int charWidth = av.getCharWidth();
378 int availWidth = getWidth();
379 int availHeight = getHeight();
381 availWidth -= (availWidth % charWidth);
382 availHeight -= (availHeight % charHeight);
384 // BH 2019 can't possibly fastPaint if either width or height is 0
386 if (availWidth == 0 || availHeight == 0)
391 ViewportRanges ranges = av.getRanges();
392 int startRes = ranges.getStartRes();
393 int startSeq = ranges.getStartSeq();
394 int endRes = ranges.getEndRes();
395 int endSeq = ranges.getEndSeq();
397 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
398 // repaint() requests in unpredictable ways. In this case, the issue was
399 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
400 // repaint request preceded two full requests, thus resulting
401 // in a full request for paint. In constrast, in JavaScript, the three
402 // requests were bundled together into one, so the fastPaint flag was
403 // still present for the second and third request.
405 // This resulted in incomplete painting.
407 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
408 // in PaintRefresher when the target to be painted is one of those two
413 // An initial idea; can be removed once we determine this issue is closed:
414 // if (av.isFastPaintDisabled())
416 // fastPaint = false;
422 || (vis = getVisibleRect()).width != (clip = g
423 .getClipBounds()).width
424 || vis.height != clip.height))
426 g.drawImage(img, 0, 0, this);
427 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
433 // img is a cached version of the last view we drew.
434 // If we have no img or the size has changed, make a new one.
436 if (img == null || availWidth != img.getWidth()
437 || availHeight != img.getHeight())
439 img = new BufferedImage(availWidth, availHeight,
440 BufferedImage.TYPE_INT_RGB);
443 Graphics2D gg = (Graphics2D) img.getGraphics();
444 gg.setFont(av.getFont());
448 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
449 RenderingHints.VALUE_ANTIALIAS_ON);
452 gg.setColor(Color.white);
453 gg.fillRect(0, 0, availWidth, availHeight);
455 if (av.getWrapAlignment())
457 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
461 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
464 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
466 g.drawImage(img, 0, 0, this);
472 drawCursor(g, startRes, endRes, startSeq, endSeq);
477 * Draw an alignment panel for printing
480 * Graphics object to draw with
482 * start residue of print area
484 * end residue of print area
486 * start sequence of print area
488 * end sequence of print area
490 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
491 int startSeq, int endSeq)
493 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
495 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
500 * Draw a wrapped alignment panel for printing
503 * Graphics object to draw with
505 * width of drawing area
506 * @param canvasHeight
507 * height of drawing area
509 * start residue of print area
511 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
512 int canvasHeight, int startRes)
514 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
516 SequenceGroup group = av.getSelectionGroup();
519 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
525 * Using the current font, determine fields labelWidthEast and labelWidthWest,
526 * and return the number of residues that can fill the remaining width.
529 * the width in pixels (possibly including scales)
531 * @return the visible width in residues, after allowing for East or West
535 public int getWrappedCanvasWidth(int width)
537 int charWidth = av.getCharWidth();
539 FontMetrics fm = getFontMetrics(av.getFont());
541 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
545 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
547 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
549 return (width - labelWidthEast - labelWidthWest) / charWidth;
553 * Returns a pixel width sufficient to show the largest sequence coordinate
554 * (end position) in the alignment, calculated as the FontMetrics width of
555 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
556 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
557 * half a character width space on either side.
562 protected int getLabelWidth(FontMetrics fm)
565 * find the biggest sequence end position we need to show
566 * (note this is not necessarily the sequence length)
569 AlignmentI alignment = av.getAlignment();
570 for (int i = 0; i < alignment.getHeight(); i++)
572 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
577 for (int i = maxWidth; i > 0; i /= 10)
582 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
586 * Draws as many widths of a wrapped alignment as can fit in the visible
591 * available width in pixels
593 * available height in pixels
595 * the first column (0...) of the alignment to draw
597 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
598 final int startColumn)
600 int wrappedWidthInResidues = calculateWrappedGeometry(availWidth,
603 av.setWrappedWidth(wrappedWidthInResidues);
605 ViewportRanges ranges = av.getRanges();
606 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
608 // we need to call this again to make sure the startColumn +
609 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
611 calculateWrappedGeometry(availWidth, availHeight);
614 * draw one width at a time (excluding any scales shown),
615 * until we have run out of either alignment or vertical space available
617 int ypos = wrappedSpaceAboveAlignment;
618 int maxWidth = ranges.getVisibleAlignmentWidth();
620 int start = startColumn;
621 int currentWidth = 0;
622 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
625 .min(maxWidth, start + wrappedWidthInResidues - 1);
626 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
627 ypos += wrappedRepeatHeightPx;
628 start += wrappedWidthInResidues;
632 drawWrappedDecorators(g, startColumn);
636 * Calculates and saves values needed when rendering a wrapped alignment.
637 * These depend on many factors, including
639 * <li>canvas width and height</li>
640 * <li>number of visible sequences, and height of annotations if shown</li>
641 * <li>font and character width</li>
642 * <li>whether scales are shown left, right or above the alignment</li>
647 * @return the number of residue columns in each width
649 protected int calculateWrappedGeometry(int availWidth, int availHeight)
651 int charHeight = av.getCharHeight();
654 * vertical space in pixels between wrapped widths of alignment
655 * - one character height, or two if scale above is drawn
657 wrappedSpaceAboveAlignment = charHeight
658 * (av.getScaleAboveWrapped() ? 2 : 1);
661 * compute height in pixels of the wrapped widths
662 * - start with space above plus sequences
664 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
665 wrappedRepeatHeightPx += av.getAlignment().getHeight()
669 * add annotations panel height if shown
670 * also gap between sequences and annotations
672 if (av.isShowAnnotation())
674 wrappedRepeatHeightPx += getAnnotationHeight();
675 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
679 * number of visible widths (the last one may be part height),
680 * ensuring a part height includes at least one sequence
682 ViewportRanges ranges = av.getRanges();
683 wrappedVisibleWidths = availHeight / wrappedRepeatHeightPx;
684 int remainder = availHeight % wrappedRepeatHeightPx;
685 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
687 wrappedVisibleWidths++;
691 * compute width in residues; this also sets East and West label widths
693 int wrappedWidthInResidues = getWrappedCanvasWidth(availWidth);
696 * limit visibleWidths to not exceed width of alignment
698 int xMax = ranges.getVisibleAlignmentWidth();
699 int startToEnd = xMax - ranges.getStartRes();
700 int maxWidths = startToEnd / wrappedWidthInResidues;
701 if (startToEnd % wrappedWidthInResidues > 0)
705 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
707 return wrappedWidthInResidues;
711 * Draws one width of a wrapped alignment, including sequences and
712 * annnotations, if shown, but not scales or hidden column markers
718 * @param canvasHeight
720 protected void drawWrappedWidth(Graphics g, final int ypos,
721 final int startColumn, final int endColumn,
722 final int canvasHeight)
724 ViewportRanges ranges = av.getRanges();
725 int viewportWidth = ranges.getViewportWidth();
727 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
730 * move right before drawing by the width of the scale left (if any)
731 * plus column offset from left margin (usually zero, but may be non-zero
732 * when fast painting is drawing just a few columns)
734 int charWidth = av.getCharWidth();
735 int xOffset = labelWidthWest
736 + ((startColumn - ranges.getStartRes()) % viewportWidth)
739 g.translate(xOffset, 0);
742 * white fill the region to be drawn (so incremental fast paint doesn't
743 * scribble over an existing image)
745 g.setColor(Color.white);
746 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
747 wrappedRepeatHeightPx);
749 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
752 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
754 if (av.isShowAnnotation())
756 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
757 g.translate(0, yShift);
758 if (annotations == null)
760 annotations = new AnnotationPanel(av);
763 annotations.renderer.drawComponent(annotations, av, g, -1,
764 startColumn, endx + 1);
765 g.translate(0, -yShift);
767 g.translate(-xOffset, 0);
771 * Draws scales left, right and above (if shown), and any hidden column
772 * markers, on all widths of the wrapped alignment
777 protected void drawWrappedDecorators(Graphics g, final int startColumn)
779 int charWidth = av.getCharWidth();
781 g.setFont(av.getFont());
783 g.setColor(Color.black);
785 int ypos = wrappedSpaceAboveAlignment;
786 ViewportRanges ranges = av.getRanges();
787 int viewportWidth = ranges.getViewportWidth();
788 int maxWidth = ranges.getVisibleAlignmentWidth();
790 int startCol = startColumn;
792 while (widthsDrawn < wrappedVisibleWidths)
794 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
796 if (av.getScaleLeftWrapped())
798 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
801 if (av.getScaleRightWrapped())
803 int x = labelWidthWest + viewportWidth * charWidth;
806 drawVerticalScale(g, startCol, endColumn, ypos, false);
811 * white fill region of scale above and hidden column markers
812 * (to support incremental fast paint of image)
814 g.translate(labelWidthWest, 0);
815 g.setColor(Color.white);
816 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
817 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
818 g.setColor(Color.black);
819 g.translate(-labelWidthWest, 0);
821 g.translate(labelWidthWest, 0);
823 if (av.getScaleAboveWrapped())
825 drawNorthScale(g, startCol, endColumn, ypos);
828 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
830 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
833 g.translate(-labelWidthWest, 0);
835 ypos += wrappedRepeatHeightPx;
836 startCol += viewportWidth;
842 * Draws markers (triangles) above hidden column positions between startColumn
850 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
851 int startColumn, int endColumn)
853 int charHeight = av.getCharHeight();
854 int charWidth = av.getCharWidth();
856 g.setColor(Color.blue);
858 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
860 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
864 res = it.next() - startColumn;
866 if (res < 0 || res > endColumn - startColumn + 1)
872 * draw a downward-pointing triangle at the hidden columns location
873 * (before the following visible column)
875 int xMiddle = res * charWidth;
876 int[] xPoints = new int[] { xMiddle - charHeight / 4,
877 xMiddle + charHeight / 4, xMiddle };
878 int yTop = ypos - (charHeight / 2);
879 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
880 g.fillPolygon(xPoints, yPoints, 3);
885 * Draw a selection group over a wrapped alignment
887 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
889 int canvasHeight, int startRes)
891 int charHeight = av.getCharHeight();
892 int charWidth = av.getCharWidth();
894 // height gap above each panel
895 int hgap = charHeight;
896 if (av.getScaleAboveWrapped())
901 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
903 int cHeight = av.getAlignment().getHeight() * charHeight;
905 int startx = startRes;
907 int ypos = hgap; // vertical offset
908 int maxwidth = av.getAlignment().getVisibleWidth();
910 // chop the wrapped alignment extent up into panel-sized blocks and treat
911 // each block as if it were a block from an unwrapped alignment
912 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
913 BasicStroke.JOIN_ROUND, 3f, new float[]
915 g.setColor(Color.RED);
916 while ((ypos <= canvasHeight) && (startx < maxwidth))
918 // set end value to be start + width, or maxwidth, whichever is smaller
919 endx = startx + cWidth - 1;
926 g.translate(labelWidthWest, 0);
928 drawUnwrappedSelection(g, group, startx, endx, 0,
929 av.getAlignment().getHeight() - 1,
932 g.translate(-labelWidthWest, 0);
934 // update vertical offset
935 ypos += cHeight + getAnnotationHeight() + hgap;
937 // update horizontal offset
940 g.setStroke(new BasicStroke());
943 int getAnnotationHeight()
945 if (!av.isShowAnnotation())
950 if (annotations == null)
952 annotations = new AnnotationPanel(av);
955 return annotations.adjustPanelHeight();
959 * Draws the visible region of the alignment on the graphics context. If there
960 * are hidden column markers in the visible region, then each sub-region
961 * between the markers is drawn separately, followed by the hidden column
965 * the graphics context, positioned at the first residue to be drawn
967 * offset of the first column to draw (0..)
969 * offset of the last column to draw (0..)
971 * offset of the first sequence to draw (0..)
973 * offset of the last sequence to draw (0..)
975 * vertical offset at which to draw (for wrapped alignments)
977 public void drawPanel(Graphics g1, final int startRes, final int endRes,
978 final int startSeq, final int endSeq, final int yOffset)
980 int charHeight = av.getCharHeight();
981 int charWidth = av.getCharWidth();
983 if (!av.hasHiddenColumns())
985 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
993 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
994 VisibleContigsIterator regions = hidden
995 .getVisContigsIterator(startRes, endRes + 1, true);
997 while (regions.hasNext())
999 int[] region = regions.next();
1000 blockEnd = region[1];
1001 blockStart = region[0];
1004 * draw up to just before the next hidden region, or the end of
1005 * the visible region, whichever comes first
1007 g1.translate(screenY * charWidth, 0);
1009 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1012 * draw the downline of the hidden column marker (ScalePanel draws the
1013 * triangle on top) if we reached it
1015 if (av.getShowHiddenMarkers()
1016 && (regions.hasNext() || regions.endsAtHidden()))
1018 g1.setColor(Color.blue);
1020 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1021 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1022 (endSeq - startSeq + 1) * charHeight + yOffset);
1025 g1.translate(-screenY * charWidth, 0);
1026 screenY += blockEnd - blockStart + 1;
1033 * Draws a region of the visible alignment
1037 * offset of the first column in the visible region (0..)
1039 * offset of the last column in the visible region (0..)
1041 * offset of the first sequence in the visible region (0..)
1043 * offset of the last sequence in the visible region (0..)
1045 * vertical offset at which to draw (for wrapped alignments)
1047 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1048 int endSeq, int offset)
1050 int charHeight = av.getCharHeight();
1051 int charWidth = av.getCharWidth();
1053 g.setFont(av.getFont());
1054 seqRdr.prepare(g, av.isRenderGaps());
1058 // / First draw the sequences
1059 // ///////////////////////////
1060 for (int i = startSeq; i <= endSeq; i++)
1062 nextSeq = av.getAlignment().getSequenceAt(i);
1063 if (nextSeq == null)
1065 // occasionally, a race condition occurs such that the alignment row is
1069 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1070 startRes, endRes, offset + ((i - startSeq) * charHeight));
1072 if (av.isShowSequenceFeatures())
1074 fr.drawSequence(g, nextSeq, startRes, endRes,
1075 offset + ((i - startSeq) * charHeight), false);
1079 * highlight search Results once sequence has been drawn
1081 if (av.hasSearchResults())
1083 SearchResultsI searchResults = av.getSearchResults();
1084 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1086 if (visibleResults != null)
1088 for (int r = 0; r < visibleResults.length; r += 2)
1090 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1091 visibleResults[r + 1],
1092 (visibleResults[r] - startRes) * charWidth,
1093 offset + ((i - startSeq) * charHeight));
1099 if (av.getSelectionGroup() != null
1100 || av.getAlignment().getGroups().size() > 0)
1102 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1108 * Draws the outlines of any groups defined on the alignment (excluding the
1109 * current selection group, if any)
1118 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1119 int startSeq, int endSeq, int offset)
1121 Graphics2D g = (Graphics2D) g1;
1123 SequenceGroup group = null;
1124 int groupIndex = -1;
1126 if (av.getAlignment().getGroups().size() > 0)
1128 group = av.getAlignment().getGroups().get(0);
1136 g.setColor(group.getOutlineColour());
1137 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1141 if (groupIndex >= av.getAlignment().getGroups().size())
1145 group = av.getAlignment().getGroups().get(groupIndex);
1146 } while (groupIndex < av.getAlignment().getGroups().size());
1151 * Draws the outline of the current selection group (if any)
1159 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1160 int startSeq, int endSeq)
1162 SequenceGroup group = av.getSelectionGroup();
1168 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1169 BasicStroke.JOIN_ROUND, 3f, new float[]
1171 g.setColor(Color.RED);
1172 if (!av.getWrapAlignment())
1174 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1179 drawWrappedSelection(g, group, getWidth(), getHeight(),
1180 av.getRanges().getStartRes());
1182 g.setStroke(new BasicStroke());
1186 * Draw the cursor as a separate image and overlay
1189 * start residue of area to draw cursor in
1191 * end residue of area to draw cursor in
1193 * start sequence of area to draw cursor in
1195 * end sequence of are to draw cursor in
1196 * @return a transparent image of the same size as the sequence canvas, with
1197 * the cursor drawn on it, if any
1199 private void drawCursor(Graphics g, int startRes, int endRes,
1203 // convert the cursorY into a position on the visible alignment
1204 int cursor_ypos = cursorY;
1206 // don't do work unless we have to
1207 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1211 int startx = startRes;
1214 // convert the cursorX into a position on the visible alignment
1215 int cursor_xpos = av.getAlignment().getHiddenColumns()
1216 .absoluteToVisibleColumn(cursorX);
1218 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1221 if (av.getWrapAlignment())
1223 // work out the correct offsets for the cursor
1224 int charHeight = av.getCharHeight();
1225 int charWidth = av.getCharWidth();
1226 int canvasWidth = getWidth();
1227 int canvasHeight = getHeight();
1229 // height gap above each panel
1230 int hgap = charHeight;
1231 if (av.getScaleAboveWrapped())
1236 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1238 int cHeight = av.getAlignment().getHeight() * charHeight;
1240 endx = startx + cWidth - 1;
1241 int ypos = hgap; // vertical offset
1243 // iterate down the wrapped panels
1244 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1246 // update vertical offset
1247 ypos += cHeight + getAnnotationHeight() + hgap;
1249 // update horizontal offset
1251 endx = startx + cWidth - 1;
1254 xoffset = labelWidthWest;
1257 // now check if cursor is within range for x values
1258 if (cursor_xpos >= startx && cursor_xpos <= endx)
1260 // get the character the cursor is drawn at
1261 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1262 char s = seq.getCharAt(cursorX);
1264 seqRdr.drawCursor(g, s,
1265 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1266 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1274 * Draw a selection group over an unwrapped alignment
1277 * graphics object to draw with
1281 * start residue of area to draw
1283 * end residue of area to draw
1285 * start sequence of area to draw
1287 * end sequence of area to draw
1289 * vertical offset (used when called from wrapped alignment code)
1291 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1292 int startRes, int endRes, int startSeq, int endSeq, int offset)
1294 int charWidth = av.getCharWidth();
1296 if (!av.hasHiddenColumns())
1298 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1303 // package into blocks of visible columns
1308 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1309 VisibleContigsIterator regions = hidden
1310 .getVisContigsIterator(startRes, endRes + 1, true);
1311 while (regions.hasNext())
1313 int[] region = regions.next();
1314 blockEnd = region[1];
1315 blockStart = region[0];
1317 g.translate(screenY * charWidth, 0);
1318 drawPartialGroupOutline(g, group,
1319 blockStart, blockEnd, startSeq, endSeq, offset);
1321 g.translate(-screenY * charWidth, 0);
1322 screenY += blockEnd - blockStart + 1;
1328 * Draws part of a selection group outline
1336 * @param verticalOffset
1338 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1339 int startRes, int endRes, int startSeq, int endSeq,
1342 int charHeight = av.getCharHeight();
1343 int charWidth = av.getCharWidth();
1344 int visWidth = (endRes - startRes + 1) * charWidth;
1348 boolean inGroup = false;
1353 List<SequenceI> seqs = group.getSequences(null);
1355 // position of start residue of group relative to startRes, in pixels
1356 int sx = (group.getStartRes() - startRes) * charWidth;
1358 // width of group in pixels
1359 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1362 if (!(sx + xwidth < 0 || sx > visWidth))
1364 for (i = startSeq; i <= endSeq; i++)
1366 sy = verticalOffset + (i - startSeq) * charHeight;
1368 if ((sx <= (endRes - startRes) * charWidth)
1369 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1372 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1374 bottom = sy + charHeight;
1379 if (((top == -1) && (i == 0)) || !seqs
1380 .contains(av.getAlignment().getSequenceAt(i - 1)))
1391 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1392 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1394 // reset top and bottom
1402 sy = verticalOffset + ((i - startSeq) * charHeight);
1403 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1404 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1410 * Draw horizontal selection group boundaries at top and bottom positions
1413 * graphics object to draw on
1419 * visWidth maximum available width
1421 * position to draw top of group at
1423 * position to draw bottom of group at
1425 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1426 int visWidth, int top, int bottom)
1436 // don't let width extend beyond current block, or group extent
1438 if (startx + width >= visWidth)
1440 width = visWidth - startx;
1445 g.drawLine(startx, top, startx + width, top);
1450 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1455 * Draw vertical lines at sx and sx+xwidth providing they lie within
1459 * graphics object to draw on
1465 * visWidth maximum available width
1471 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1474 // if start position is visible, draw vertical line to left of
1476 if (sx >= 0 && sx < visWidth)
1478 g.drawLine(sx, oldY, sx, sy);
1481 // if end position is visible, draw vertical line to right of
1483 if (sx + xwidth < visWidth)
1485 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1490 * Highlights search results in the visible region by rendering as white text
1491 * on a black background. Any previous highlighting is removed. Answers true
1492 * if any highlight was left on the visible alignment (so status bar should be
1493 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1494 * so allows the next repaint to update the whole display.
1499 public boolean highlightSearchResults(SearchResultsI results)
1501 return highlightSearchResults(results, false);
1506 * Highlights search results in the visible region by rendering as white text
1507 * on a black background. Any previous highlighting is removed. Answers true
1508 * if any highlight was left on the visible alignment (so status bar should be
1509 * set to match), else false.
1511 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1512 * highlighted regions are modified. This speeds up highlighting across linked
1515 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1516 * a wrapped alignment had to be scrolled to show the highlighted region, then
1517 * it should be fully redrawn, otherwise a fast paint can be performed. This
1518 * argument could be removed if fast paint of scrolled wrapped alignment is
1519 * coded in future (JAL-2609).
1522 * @param doFastPaint
1523 * if true, sets a flag so the next repaint only redraws the modified
1527 public boolean highlightSearchResults(SearchResultsI results,
1528 boolean doFastPaint)
1534 boolean wrapped = av.getWrapAlignment();
1537 fastPaint = doFastPaint;
1538 fastpainting = fastPaint;
1541 * to avoid redrawing the whole visible region, we instead
1542 * redraw just the minimal regions to remove previous highlights
1545 SearchResultsI previous = av.getSearchResults();
1546 av.setSearchResults(results);
1547 boolean redrawn = false;
1548 boolean drawn = false;
1551 redrawn = drawMappedPositionsWrapped(previous);
1552 drawn = drawMappedPositionsWrapped(results);
1557 redrawn = drawMappedPositions(previous);
1558 drawn = drawMappedPositions(results);
1563 * if highlights were either removed or added, repaint
1571 * return true only if highlights were added
1577 fastpainting = false;
1582 * Redraws the minimal rectangle in the visible region (if any) that includes
1583 * mapped positions of the given search results. Whether or not positions are
1584 * highlighted depends on the SearchResults set on the Viewport. This allows
1585 * this method to be called to either clear or set highlighting. Answers true
1586 * if any positions were drawn (in which case a repaint is still required),
1592 protected boolean drawMappedPositions(SearchResultsI results)
1594 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1600 * calculate the minimal rectangle to redraw that
1601 * includes both new and existing search results
1603 int firstSeq = Integer.MAX_VALUE;
1605 int firstCol = Integer.MAX_VALUE;
1607 boolean matchFound = false;
1609 ViewportRanges ranges = av.getRanges();
1610 int firstVisibleColumn = ranges.getStartRes();
1611 int lastVisibleColumn = ranges.getEndRes();
1612 AlignmentI alignment = av.getAlignment();
1613 if (av.hasHiddenColumns())
1615 firstVisibleColumn = alignment.getHiddenColumns()
1616 .visibleToAbsoluteColumn(firstVisibleColumn);
1617 lastVisibleColumn = alignment.getHiddenColumns()
1618 .visibleToAbsoluteColumn(lastVisibleColumn);
1621 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1622 .getEndSeq(); seqNo++)
1624 SequenceI seq = alignment.getSequenceAt(seqNo);
1626 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1628 if (visibleResults != null)
1630 for (int i = 0; i < visibleResults.length - 1; i += 2)
1632 int firstMatchedColumn = visibleResults[i];
1633 int lastMatchedColumn = visibleResults[i + 1];
1634 if (firstMatchedColumn <= lastVisibleColumn
1635 && lastMatchedColumn >= firstVisibleColumn)
1638 * found a search results match in the visible region -
1639 * remember the first and last sequence matched, and the first
1640 * and last visible columns in the matched positions
1643 firstSeq = Math.min(firstSeq, seqNo);
1644 lastSeq = Math.max(lastSeq, seqNo);
1645 firstMatchedColumn = Math.max(firstMatchedColumn,
1646 firstVisibleColumn);
1647 lastMatchedColumn = Math.min(lastMatchedColumn,
1649 firstCol = Math.min(firstCol, firstMatchedColumn);
1650 lastCol = Math.max(lastCol, lastMatchedColumn);
1658 if (av.hasHiddenColumns())
1660 firstCol = alignment.getHiddenColumns()
1661 .absoluteToVisibleColumn(firstCol);
1662 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1664 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1665 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1666 Graphics gg = img.getGraphics();
1667 gg.translate(transX, transY);
1668 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1669 gg.translate(-transX, -transY);
1677 public void propertyChange(PropertyChangeEvent evt)
1679 String eventName = evt.getPropertyName();
1681 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1682 // logic, emphasizing no check for ENDRES or ENDSEQ
1684 // Both scrolling and resizing change viewport ranges: scrolling changes
1685 // both start and end points, but resize only changes end values.
1686 // Here we only want to fastpaint on a scroll, with resize using a normal
1687 // paint, so scroll events are identified as changes to the horizontal or
1688 // vertical start value.
1690 // Make sure we're not trying to draw a panel
1691 // larger than the visible window
1696 case SequenceGroup.SEQ_GROUP_CHANGED:
1700 case ViewportRanges.MOVE_VIEWPORT:
1704 case ViewportRanges.STARTSEQ:
1705 // meaning STARTOREND
1706 // typically scroll, but possibly just the end changed
1707 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1709 case ViewportRanges.ENDRES:
1710 case ViewportRanges.ENDSEQ:
1711 // meaning second event along with "START" -- ENDONLY,NOTSTART
1714 case ViewportRanges.STARTRES:
1715 // meaning STARTOREND
1716 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1718 case ViewportRanges.STARTRESANDSEQ:
1719 scrollX = ((int[]) evt.getNewValue())[0]
1720 - ((int[]) evt.getOldValue())[0];
1721 scrollY = ((int[]) evt.getNewValue())[1]
1722 - ((int[]) evt.getOldValue())[1];
1724 // System.out.println("SC dx dy " + scrollX + " " + scrollY);
1726 if (scrollX != 0 && scrollY != 0)
1728 // all sorts of problems in JavaScript if this is commented out.
1736 ViewportRanges vpRanges = av.getRanges();
1737 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1738 scrollX = Math.max(Math.min(scrollX, range), -range);
1739 // only STARTRES or STARTRESANDSEQ:
1740 if (av.getWrapAlignment())
1742 fastPaintWrapped(scrollX);
1746 fastPaint(scrollX, scrollY);
1749 // BH 2019.07.27 was:
1750 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1752 // fastPaint = true;
1756 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1758 // fastPaint = false;
1759 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1764 // if (eventName.equals(ViewportRanges.STARTRES)
1765 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1767 // // Make sure we're not trying to draw a panel
1768 // // larger than the visible window
1769 // if (eventName.equals(ViewportRanges.STARTRES))
1771 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1775 // scrollX = ((int[]) evt.getNewValue())[0]
1776 // - ((int[]) evt.getOldValue())[0];
1778 // ViewportRanges vpRanges = av.getRanges();
1780 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1781 // if (scrollX > range)
1785 // else if (scrollX < -range)
1787 // scrollX = -range;
1790 // Both scrolling and resizing change viewport ranges: scrolling changes
1791 // both start and end points, but resize only changes end values.
1792 // Here we only want to fastpaint on a scroll, with resize using a normal
1793 // paint, so scroll events are identified as changes to the horizontal or
1794 // vertical start value.
1795 // BH 2019.07.27 was:
1796 // if (eventName.equals(ViewportRanges.STARTRES))
1798 // if (av.getWrapAlignment())
1800 // fastPaintWrapped(scrollX);
1804 // fastPaint(scrollX, 0);
1807 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1810 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1812 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1814 // if (av.getWrapAlignment())
1816 // fastPaintWrapped(scrollX);
1820 // fastPaint(scrollX, 0);
1826 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1829 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1831 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1833 // if (av.getWrapAlignment())
1835 // fastPaintWrapped(scrollX);
1841 * Does a minimal update of the image for a scroll movement. This method
1842 * handles scroll movements of up to one width of the wrapped alignment (one
1843 * click in the vertical scrollbar). Larger movements (for example after a
1844 * scroll to highlight a mapped position) trigger a full redraw instead.
1847 * number of positions scrolled (right if positive, left if negative)
1849 protected void fastPaintWrapped(int scrollX)
1851 ViewportRanges ranges = av.getRanges();
1853 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1856 * shift of one view width or more is
1857 * overcomplicated to handle in this method
1864 if (fastpainting || img == null)
1870 fastpainting = true;
1875 Graphics gg = img.getGraphics();
1877 calculateWrappedGeometry(getWidth(), getHeight());
1880 * relocate the regions of the alignment that are still visible
1882 shiftWrappedAlignment(-scrollX);
1885 * add new columns (sequence, annotation)
1886 * - at top left if scrollX < 0
1887 * - at right of last two widths if scrollX > 0
1891 int startRes = ranges.getStartRes();
1892 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1893 - scrollX - 1, getHeight());
1897 fastPaintWrappedAddRight(scrollX);
1901 * draw all scales (if shown) and hidden column markers
1903 drawWrappedDecorators(gg, ranges.getStartRes());
1910 fastpainting = false;
1915 * Draws the specified number of columns at the 'end' (bottom right) of a
1916 * wrapped alignment view, including sequences and annotations if shown, but
1917 * not scales. Also draws the same number of columns at the right hand end of
1918 * the second last width shown, if the last width is not full height (so
1919 * cannot simply be copied from the graphics image).
1923 protected void fastPaintWrappedAddRight(int columns)
1930 Graphics gg = img.getGraphics();
1932 ViewportRanges ranges = av.getRanges();
1933 int viewportWidth = ranges.getViewportWidth();
1934 int charWidth = av.getCharWidth();
1937 * draw full height alignment in the second last row, last columns, if the
1938 * last row was not full height
1940 int visibleWidths = wrappedVisibleWidths;
1941 int canvasHeight = getHeight();
1942 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1944 if (lastWidthPartHeight)
1946 int widthsAbove = Math.max(0, visibleWidths - 2);
1947 int ypos = wrappedRepeatHeightPx * widthsAbove
1948 + wrappedSpaceAboveAlignment;
1949 int endRes = ranges.getEndRes();
1950 endRes += widthsAbove * viewportWidth;
1951 int startRes = endRes - columns;
1952 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1956 * white fill first to erase annotations
1960 gg.translate(xOffset, 0);
1961 gg.setColor(Color.white);
1962 gg.fillRect(labelWidthWest, ypos,
1963 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1964 gg.translate(-xOffset, 0);
1966 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1971 * draw newly visible columns in last wrapped width (none if we
1972 * have reached the end of the alignment)
1973 * y-offset for drawing last width is height of widths above,
1976 int widthsAbove = visibleWidths - 1;
1977 int ypos = wrappedRepeatHeightPx * widthsAbove
1978 + wrappedSpaceAboveAlignment;
1979 int endRes = ranges.getEndRes();
1980 endRes += widthsAbove * viewportWidth;
1981 int startRes = endRes - columns + 1;
1984 * white fill first to erase annotations
1986 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1988 gg.translate(xOffset, 0);
1989 gg.setColor(Color.white);
1990 int width = viewportWidth * charWidth - xOffset;
1991 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1992 gg.translate(-xOffset, 0);
1994 gg.setFont(av.getFont());
1995 gg.setColor(Color.black);
1997 if (startRes < ranges.getVisibleAlignmentWidth())
1999 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2003 * and finally, white fill any space below the visible alignment
2005 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2006 if (heightBelow > 0)
2008 gg.setColor(Color.white);
2009 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2015 * Shifts the visible alignment by the specified number of columns - left if
2016 * negative, right if positive. Copies and moves sequences and annotations (if
2017 * shown). Scales, hidden column markers and any newly visible columns must be
2022 protected void shiftWrappedAlignment(int positions)
2029 Graphics gg = img.getGraphics();
2031 int charWidth = av.getCharWidth();
2033 int canvasHeight = getHeight();
2034 ViewportRanges ranges = av.getRanges();
2035 int viewportWidth = ranges.getViewportWidth();
2036 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2038 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2039 int xMax = ranges.getVisibleAlignmentWidth();
2044 * shift right (after scroll left)
2045 * for each wrapped width (starting with the last), copy (width-positions)
2046 * columns from the left margin to the right margin, and copy positions
2047 * columns from the right margin of the row above (if any) to the
2048 * left margin of the current row
2052 * get y-offset of last wrapped width, first row of sequences
2054 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2055 y += wrappedSpaceAboveAlignment;
2056 int copyFromLeftStart = labelWidthWest;
2057 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2062 * shift 'widthToCopy' residues by 'positions' places to the right
2064 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2065 positions * charWidth, 0);
2069 * copy 'positions' residue from the row above (right hand end)
2070 * to this row's left hand end
2072 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2073 positions * charWidth, heightToCopy, -widthToCopy,
2074 wrappedRepeatHeightPx);
2077 y -= wrappedRepeatHeightPx;
2083 * shift left (after scroll right)
2084 * for each wrapped width (starting with the first), copy (width-positions)
2085 * columns from the right margin to the left margin, and copy positions
2086 * columns from the left margin of the row below (if any) to the
2087 * right margin of the current row
2089 int xpos = av.getRanges().getStartRes();
2090 int y = wrappedSpaceAboveAlignment;
2091 int copyFromRightStart = labelWidthWest - positions * charWidth;
2093 while (y < canvasHeight)
2095 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2096 positions * charWidth, 0);
2097 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2098 && (xpos + viewportWidth <= xMax))
2100 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2101 * charWidth, heightToCopy, widthToCopy,
2102 -wrappedRepeatHeightPx);
2104 y += wrappedRepeatHeightPx;
2105 xpos += viewportWidth;
2113 * Redraws any positions in the search results in the visible region of a
2114 * wrapped alignment. Any highlights are drawn depending on the search results
2115 * set on the Viewport, not the <code>results</code> argument. This allows
2116 * this method to be called either to clear highlights (passing the previous
2117 * search results), or to draw new highlights.
2122 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2124 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2128 int charHeight = av.getCharHeight();
2130 boolean matchFound = false;
2132 calculateWrappedGeometry(getWidth(), getHeight());
2133 int wrappedWidth = av.getWrappedWidth();
2134 int wrappedHeight = wrappedRepeatHeightPx;
2136 ViewportRanges ranges = av.getRanges();
2137 int canvasHeight = getHeight();
2138 int repeats = canvasHeight / wrappedHeight;
2139 if (canvasHeight / wrappedHeight > 0)
2144 int firstVisibleColumn = ranges.getStartRes();
2145 int lastVisibleColumn = ranges.getStartRes() + repeats
2146 * ranges.getViewportWidth() - 1;
2148 AlignmentI alignment = av.getAlignment();
2149 if (av.hasHiddenColumns())
2151 firstVisibleColumn = alignment.getHiddenColumns()
2152 .visibleToAbsoluteColumn(firstVisibleColumn);
2153 lastVisibleColumn = alignment.getHiddenColumns()
2154 .visibleToAbsoluteColumn(lastVisibleColumn);
2157 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2160 Graphics gg = img.getGraphics();
2162 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2163 .getEndSeq(); seqNo++)
2165 SequenceI seq = alignment.getSequenceAt(seqNo);
2167 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2169 if (visibleResults != null)
2171 for (int i = 0; i < visibleResults.length - 1; i += 2)
2173 int firstMatchedColumn = visibleResults[i];
2174 int lastMatchedColumn = visibleResults[i + 1];
2175 if (firstMatchedColumn <= lastVisibleColumn
2176 && lastMatchedColumn >= firstVisibleColumn)
2179 * found a search results match in the visible region
2181 firstMatchedColumn = Math.max(firstMatchedColumn,
2182 firstVisibleColumn);
2183 lastMatchedColumn = Math.min(lastMatchedColumn,
2187 * draw each mapped position separately (as contiguous positions may
2188 * wrap across lines)
2190 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2192 int displayColumn = mappedPos;
2193 if (av.hasHiddenColumns())
2195 displayColumn = alignment.getHiddenColumns()
2196 .absoluteToVisibleColumn(displayColumn);
2200 * transX: offset from left edge of canvas to residue position
2202 int transX = labelWidthWest
2203 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2204 * av.getCharWidth();
2207 * transY: offset from top edge of canvas to residue position
2209 int transY = gapHeight;
2210 transY += (displayColumn - ranges.getStartRes())
2211 / wrappedWidth * wrappedHeight;
2212 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2215 * yOffset is from graphics origin to start of visible region
2217 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2218 if (transY < getHeight())
2221 gg.translate(transX, transY);
2222 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2224 gg.translate(-transX, -transY);
2238 * Answers the width in pixels of the left scale labels (0 if not shown)
2242 int getLabelWidthWest()
2244 return labelWidthWest;