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 if (av.getAlignPanel().getHoldRepaint())
379 int charHeight = av.getCharHeight();
380 int charWidth = av.getCharWidth();
382 int availWidth = getWidth();
383 int availHeight = getHeight();
385 availWidth -= (availWidth % charWidth);
386 availHeight -= (availHeight % charHeight);
388 // BH 2019 can't possibly fastPaint if either width or height is 0
390 if (availWidth == 0 || availHeight == 0)
395 ViewportRanges ranges = av.getRanges();
396 int startRes = ranges.getStartRes();
397 int startSeq = ranges.getStartSeq();
398 int endRes = ranges.getEndRes();
399 int endSeq = ranges.getEndSeq();
401 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
402 // repaint() requests in unpredictable ways. In this case, the issue was
403 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
404 // repaint request preceded two full requests, thus resulting
405 // in a full request for paint. In constrast, in JavaScript, the three
406 // requests were bundled together into one, so the fastPaint flag was
407 // still present for the second and third request.
409 // This resulted in incomplete painting.
411 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
412 // in PaintRefresher when the target to be painted is one of those two
417 // An initial idea; can be removed once we determine this issue is closed:
418 // if (av.isFastPaintDisabled())
420 // fastPaint = false;
426 || (vis = getVisibleRect()).width != (clip = g
427 .getClipBounds()).width
428 || vis.height != clip.height))
430 g.drawImage(img, 0, 0, this);
431 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
437 // img is a cached version of the last view we drew.
438 // If we have no img or the size has changed, make a new one.
440 if (img == null || availWidth != img.getWidth()
441 || availHeight != img.getHeight())
443 img = new BufferedImage(availWidth, availHeight,
444 BufferedImage.TYPE_INT_RGB);
447 Graphics2D gg = (Graphics2D) img.getGraphics();
448 gg.setFont(av.getFont());
452 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
453 RenderingHints.VALUE_ANTIALIAS_ON);
456 gg.setColor(Color.white);
457 gg.fillRect(0, 0, availWidth, availHeight);
459 if (av.getWrapAlignment())
461 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
465 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
468 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
470 g.drawImage(img, 0, 0, this);
476 drawCursor(g, startRes, endRes, startSeq, endSeq);
481 * Draw an alignment panel for printing
484 * Graphics object to draw with
486 * start residue of print area
488 * end residue of print area
490 * start sequence of print area
492 * end sequence of print area
494 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
495 int startSeq, int endSeq)
497 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
499 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
504 * Draw a wrapped alignment panel for printing
507 * Graphics object to draw with
509 * width of drawing area
510 * @param canvasHeight
511 * height of drawing area
513 * start residue of print area
515 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
516 int canvasHeight, int startRes)
518 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
520 SequenceGroup group = av.getSelectionGroup();
523 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
529 * Using the current font, determine fields labelWidthEast and labelWidthWest,
530 * and return the number of residues that can fill the remaining width.
533 * the width in pixels (possibly including scales)
535 * @return the visible width in residues, after allowing for East or West
539 public int getWrappedCanvasWidth(int width)
541 int charWidth = av.getCharWidth();
543 FontMetrics fm = getFontMetrics(av.getFont());
545 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
549 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
551 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
553 return (width - labelWidthEast - labelWidthWest) / charWidth;
557 * Returns a pixel width sufficient to show the largest sequence coordinate
558 * (end position) in the alignment, calculated as the FontMetrics width of
559 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
560 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
561 * half a character width space on either side.
566 protected int getLabelWidth(FontMetrics fm)
569 * find the biggest sequence end position we need to show
570 * (note this is not necessarily the sequence length)
573 AlignmentI alignment = av.getAlignment();
574 for (int i = 0; i < alignment.getHeight(); i++)
576 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
581 for (int i = maxWidth; i > 0; i /= 10)
586 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
590 * Draws as many widths of a wrapped alignment as can fit in the visible
595 * available width in pixels
597 * available height in pixels
599 * the first column (0...) of the alignment to draw
601 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
602 final int startColumn)
604 int wrappedWidthInResidues = calculateWrappedGeometry(availWidth,
607 av.setWrappedWidth(wrappedWidthInResidues);
609 ViewportRanges ranges = av.getRanges();
610 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
612 // we need to call this again to make sure the startColumn +
613 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
615 calculateWrappedGeometry(availWidth, availHeight);
618 * draw one width at a time (excluding any scales shown),
619 * until we have run out of either alignment or vertical space available
621 int ypos = wrappedSpaceAboveAlignment;
622 int maxWidth = ranges.getVisibleAlignmentWidth();
624 int start = startColumn;
625 int currentWidth = 0;
626 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
629 .min(maxWidth, start + wrappedWidthInResidues - 1);
630 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
631 ypos += wrappedRepeatHeightPx;
632 start += wrappedWidthInResidues;
636 drawWrappedDecorators(g, startColumn);
640 * Calculates and saves values needed when rendering a wrapped alignment.
641 * These depend on many factors, including
643 * <li>canvas width and height</li>
644 * <li>number of visible sequences, and height of annotations if shown</li>
645 * <li>font and character width</li>
646 * <li>whether scales are shown left, right or above the alignment</li>
651 * @return the number of residue columns in each width
653 protected int calculateWrappedGeometry(int availWidth, int availHeight)
655 int charHeight = av.getCharHeight();
658 * vertical space in pixels between wrapped widths of alignment
659 * - one character height, or two if scale above is drawn
661 wrappedSpaceAboveAlignment = charHeight
662 * (av.getScaleAboveWrapped() ? 2 : 1);
665 * compute height in pixels of the wrapped widths
666 * - start with space above plus sequences
668 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
669 wrappedRepeatHeightPx += av.getAlignment().getHeight()
673 * add annotations panel height if shown
674 * also gap between sequences and annotations
676 if (av.isShowAnnotation())
678 wrappedRepeatHeightPx += getAnnotationHeight();
679 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
683 * number of visible widths (the last one may be part height),
684 * ensuring a part height includes at least one sequence
686 ViewportRanges ranges = av.getRanges();
687 wrappedVisibleWidths = availHeight / wrappedRepeatHeightPx;
688 int remainder = availHeight % wrappedRepeatHeightPx;
689 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
691 wrappedVisibleWidths++;
695 * compute width in residues; this also sets East and West label widths
697 int wrappedWidthInResidues = getWrappedCanvasWidth(availWidth);
700 * limit visibleWidths to not exceed width of alignment
702 int xMax = ranges.getVisibleAlignmentWidth();
703 int startToEnd = xMax - ranges.getStartRes();
704 int maxWidths = startToEnd / wrappedWidthInResidues;
705 if (startToEnd % wrappedWidthInResidues > 0)
709 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
711 return wrappedWidthInResidues;
715 * Draws one width of a wrapped alignment, including sequences and
716 * annnotations, if shown, but not scales or hidden column markers
722 * @param canvasHeight
724 protected void drawWrappedWidth(Graphics g, final int ypos,
725 final int startColumn, final int endColumn,
726 final int canvasHeight)
728 ViewportRanges ranges = av.getRanges();
729 int viewportWidth = ranges.getViewportWidth();
731 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
734 * move right before drawing by the width of the scale left (if any)
735 * plus column offset from left margin (usually zero, but may be non-zero
736 * when fast painting is drawing just a few columns)
738 int charWidth = av.getCharWidth();
739 int xOffset = labelWidthWest
740 + ((startColumn - ranges.getStartRes()) % viewportWidth)
743 g.translate(xOffset, 0);
746 * white fill the region to be drawn (so incremental fast paint doesn't
747 * scribble over an existing image)
749 g.setColor(Color.white);
750 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
751 wrappedRepeatHeightPx);
753 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
756 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
758 if (av.isShowAnnotation())
760 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
761 g.translate(0, yShift);
762 if (annotations == null)
764 annotations = new AnnotationPanel(av);
767 annotations.renderer.drawComponent(annotations, av, g, -1,
768 startColumn, endx + 1);
769 g.translate(0, -yShift);
771 g.translate(-xOffset, 0);
775 * Draws scales left, right and above (if shown), and any hidden column
776 * markers, on all widths of the wrapped alignment
781 protected void drawWrappedDecorators(Graphics g, final int startColumn)
783 int charWidth = av.getCharWidth();
785 g.setFont(av.getFont());
787 g.setColor(Color.black);
789 int ypos = wrappedSpaceAboveAlignment;
790 ViewportRanges ranges = av.getRanges();
791 int viewportWidth = ranges.getViewportWidth();
792 int maxWidth = ranges.getVisibleAlignmentWidth();
794 int startCol = startColumn;
796 while (widthsDrawn < wrappedVisibleWidths)
798 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
800 if (av.getScaleLeftWrapped())
802 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
805 if (av.getScaleRightWrapped())
807 int x = labelWidthWest + viewportWidth * charWidth;
810 drawVerticalScale(g, startCol, endColumn, ypos, false);
815 * white fill region of scale above and hidden column markers
816 * (to support incremental fast paint of image)
818 g.translate(labelWidthWest, 0);
819 g.setColor(Color.white);
820 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
821 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
822 g.setColor(Color.black);
823 g.translate(-labelWidthWest, 0);
825 g.translate(labelWidthWest, 0);
827 if (av.getScaleAboveWrapped())
829 drawNorthScale(g, startCol, endColumn, ypos);
832 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
834 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
837 g.translate(-labelWidthWest, 0);
839 ypos += wrappedRepeatHeightPx;
840 startCol += viewportWidth;
846 * Draws markers (triangles) above hidden column positions between startColumn
854 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
855 int startColumn, int endColumn)
857 int charHeight = av.getCharHeight();
858 int charWidth = av.getCharWidth();
860 g.setColor(Color.blue);
862 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
864 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
868 res = it.next() - startColumn;
870 if (res < 0 || res > endColumn - startColumn + 1)
876 * draw a downward-pointing triangle at the hidden columns location
877 * (before the following visible column)
879 int xMiddle = res * charWidth;
880 int[] xPoints = new int[] { xMiddle - charHeight / 4,
881 xMiddle + charHeight / 4, xMiddle };
882 int yTop = ypos - (charHeight / 2);
883 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
884 g.fillPolygon(xPoints, yPoints, 3);
889 * Draw a selection group over a wrapped alignment
891 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
893 int canvasHeight, int startRes)
895 int charHeight = av.getCharHeight();
896 int charWidth = av.getCharWidth();
898 // height gap above each panel
899 int hgap = charHeight;
900 if (av.getScaleAboveWrapped())
905 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
907 int cHeight = av.getAlignment().getHeight() * charHeight;
909 int startx = startRes;
911 int ypos = hgap; // vertical offset
912 int maxwidth = av.getAlignment().getVisibleWidth();
914 // chop the wrapped alignment extent up into panel-sized blocks and treat
915 // each block as if it were a block from an unwrapped alignment
916 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
917 BasicStroke.JOIN_ROUND, 3f, new float[]
919 g.setColor(Color.RED);
920 while ((ypos <= canvasHeight) && (startx < maxwidth))
922 // set end value to be start + width, or maxwidth, whichever is smaller
923 endx = startx + cWidth - 1;
930 g.translate(labelWidthWest, 0);
932 drawUnwrappedSelection(g, group, startx, endx, 0,
933 av.getAlignment().getHeight() - 1,
936 g.translate(-labelWidthWest, 0);
938 // update vertical offset
939 ypos += cHeight + getAnnotationHeight() + hgap;
941 // update horizontal offset
944 g.setStroke(new BasicStroke());
947 int getAnnotationHeight()
949 if (!av.isShowAnnotation())
954 if (annotations == null)
956 annotations = new AnnotationPanel(av);
959 return annotations.adjustPanelHeight();
963 * Draws the visible region of the alignment on the graphics context. If there
964 * are hidden column markers in the visible region, then each sub-region
965 * between the markers is drawn separately, followed by the hidden column
969 * the graphics context, positioned at the first residue to be drawn
971 * offset of the first column to draw (0..)
973 * offset of the last column to draw (0..)
975 * offset of the first sequence to draw (0..)
977 * offset of the last sequence to draw (0..)
979 * vertical offset at which to draw (for wrapped alignments)
981 public void drawPanel(Graphics g1, final int startRes, final int endRes,
982 final int startSeq, final int endSeq, final int yOffset)
984 int charHeight = av.getCharHeight();
985 int charWidth = av.getCharWidth();
987 if (!av.hasHiddenColumns())
989 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
997 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
998 VisibleContigsIterator regions = hidden
999 .getVisContigsIterator(startRes, endRes + 1, true);
1001 while (regions.hasNext())
1003 int[] region = regions.next();
1004 blockEnd = region[1];
1005 blockStart = region[0];
1008 * draw up to just before the next hidden region, or the end of
1009 * the visible region, whichever comes first
1011 g1.translate(screenY * charWidth, 0);
1013 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1016 * draw the downline of the hidden column marker (ScalePanel draws the
1017 * triangle on top) if we reached it
1019 if (av.getShowHiddenMarkers()
1020 && (regions.hasNext() || regions.endsAtHidden()))
1022 g1.setColor(Color.blue);
1024 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1025 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1026 (endSeq - startSeq + 1) * charHeight + yOffset);
1029 g1.translate(-screenY * charWidth, 0);
1030 screenY += blockEnd - blockStart + 1;
1037 * Draws a region of the visible alignment
1041 * offset of the first column in the visible region (0..)
1043 * offset of the last column in the visible region (0..)
1045 * offset of the first sequence in the visible region (0..)
1047 * offset of the last sequence in the visible region (0..)
1049 * vertical offset at which to draw (for wrapped alignments)
1051 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1052 int endSeq, int offset)
1054 int charHeight = av.getCharHeight();
1055 int charWidth = av.getCharWidth();
1057 g.setFont(av.getFont());
1058 seqRdr.prepare(g, av.isRenderGaps());
1062 // / First draw the sequences
1063 // ///////////////////////////
1064 for (int i = startSeq; i <= endSeq; i++)
1066 nextSeq = av.getAlignment().getSequenceAt(i);
1067 if (nextSeq == null)
1069 // occasionally, a race condition occurs such that the alignment row is
1073 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1074 startRes, endRes, offset + ((i - startSeq) * charHeight));
1076 if (av.isShowSequenceFeatures())
1078 fr.drawSequence(g, nextSeq, startRes, endRes,
1079 offset + ((i - startSeq) * charHeight), false);
1083 * highlight search Results once sequence has been drawn
1085 if (av.hasSearchResults())
1087 SearchResultsI searchResults = av.getSearchResults();
1088 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1090 if (visibleResults != null)
1092 for (int r = 0; r < visibleResults.length; r += 2)
1094 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1095 visibleResults[r + 1],
1096 (visibleResults[r] - startRes) * charWidth,
1097 offset + ((i - startSeq) * charHeight));
1103 if (av.getSelectionGroup() != null
1104 || av.getAlignment().getGroups().size() > 0)
1106 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1112 * Draws the outlines of any groups defined on the alignment (excluding the
1113 * current selection group, if any)
1122 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1123 int startSeq, int endSeq, int offset)
1125 Graphics2D g = (Graphics2D) g1;
1127 SequenceGroup group = null;
1128 int groupIndex = -1;
1130 if (av.getAlignment().getGroups().size() > 0)
1132 group = av.getAlignment().getGroups().get(0);
1140 g.setColor(group.getOutlineColour());
1141 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1145 if (groupIndex >= av.getAlignment().getGroups().size())
1149 group = av.getAlignment().getGroups().get(groupIndex);
1150 } while (groupIndex < av.getAlignment().getGroups().size());
1155 * Draws the outline of the current selection group (if any)
1163 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1164 int startSeq, int endSeq)
1166 SequenceGroup group = av.getSelectionGroup();
1172 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1173 BasicStroke.JOIN_ROUND, 3f, new float[]
1175 g.setColor(Color.RED);
1176 if (!av.getWrapAlignment())
1178 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1183 drawWrappedSelection(g, group, getWidth(), getHeight(),
1184 av.getRanges().getStartRes());
1186 g.setStroke(new BasicStroke());
1190 * Draw the cursor as a separate image and overlay
1193 * start residue of area to draw cursor in
1195 * end residue of area to draw cursor in
1197 * start sequence of area to draw cursor in
1199 * end sequence of are to draw cursor in
1200 * @return a transparent image of the same size as the sequence canvas, with
1201 * the cursor drawn on it, if any
1203 private void drawCursor(Graphics g, int startRes, int endRes,
1207 // convert the cursorY into a position on the visible alignment
1208 int cursor_ypos = cursorY;
1210 // don't do work unless we have to
1211 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1215 int startx = startRes;
1218 // convert the cursorX into a position on the visible alignment
1219 int cursor_xpos = av.getAlignment().getHiddenColumns()
1220 .absoluteToVisibleColumn(cursorX);
1222 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1225 if (av.getWrapAlignment())
1227 // work out the correct offsets for the cursor
1228 int charHeight = av.getCharHeight();
1229 int charWidth = av.getCharWidth();
1230 int canvasWidth = getWidth();
1231 int canvasHeight = getHeight();
1233 // height gap above each panel
1234 int hgap = charHeight;
1235 if (av.getScaleAboveWrapped())
1240 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1242 int cHeight = av.getAlignment().getHeight() * charHeight;
1244 endx = startx + cWidth - 1;
1245 int ypos = hgap; // vertical offset
1247 // iterate down the wrapped panels
1248 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1250 // update vertical offset
1251 ypos += cHeight + getAnnotationHeight() + hgap;
1253 // update horizontal offset
1255 endx = startx + cWidth - 1;
1258 xoffset = labelWidthWest;
1261 // now check if cursor is within range for x values
1262 if (cursor_xpos >= startx && cursor_xpos <= endx)
1264 // get the character the cursor is drawn at
1265 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1266 char s = seq.getCharAt(cursorX);
1268 seqRdr.drawCursor(g, s,
1269 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1270 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1278 * Draw a selection group over an unwrapped alignment
1281 * graphics object to draw with
1285 * start residue of area to draw
1287 * end residue of area to draw
1289 * start sequence of area to draw
1291 * end sequence of area to draw
1293 * vertical offset (used when called from wrapped alignment code)
1295 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1296 int startRes, int endRes, int startSeq, int endSeq, int offset)
1298 int charWidth = av.getCharWidth();
1300 if (!av.hasHiddenColumns())
1302 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1307 // package into blocks of visible columns
1312 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1313 VisibleContigsIterator regions = hidden
1314 .getVisContigsIterator(startRes, endRes + 1, true);
1315 while (regions.hasNext())
1317 int[] region = regions.next();
1318 blockEnd = region[1];
1319 blockStart = region[0];
1321 g.translate(screenY * charWidth, 0);
1322 drawPartialGroupOutline(g, group,
1323 blockStart, blockEnd, startSeq, endSeq, offset);
1325 g.translate(-screenY * charWidth, 0);
1326 screenY += blockEnd - blockStart + 1;
1332 * Draws part of a selection group outline
1340 * @param verticalOffset
1342 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1343 int startRes, int endRes, int startSeq, int endSeq,
1346 int charHeight = av.getCharHeight();
1347 int charWidth = av.getCharWidth();
1348 int visWidth = (endRes - startRes + 1) * charWidth;
1352 boolean inGroup = false;
1357 List<SequenceI> seqs = group.getSequences(null);
1359 // position of start residue of group relative to startRes, in pixels
1360 int sx = (group.getStartRes() - startRes) * charWidth;
1362 // width of group in pixels
1363 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1366 if (!(sx + xwidth < 0 || sx > visWidth))
1368 for (i = startSeq; i <= endSeq; i++)
1370 sy = verticalOffset + (i - startSeq) * charHeight;
1372 if ((sx <= (endRes - startRes) * charWidth)
1373 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1376 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1378 bottom = sy + charHeight;
1383 if (((top == -1) && (i == 0)) || !seqs
1384 .contains(av.getAlignment().getSequenceAt(i - 1)))
1395 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1396 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1398 // reset top and bottom
1406 sy = verticalOffset + ((i - startSeq) * charHeight);
1407 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1408 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1414 * Draw horizontal selection group boundaries at top and bottom positions
1417 * graphics object to draw on
1423 * visWidth maximum available width
1425 * position to draw top of group at
1427 * position to draw bottom of group at
1429 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1430 int visWidth, int top, int bottom)
1440 // don't let width extend beyond current block, or group extent
1442 if (startx + width >= visWidth)
1444 width = visWidth - startx;
1449 g.drawLine(startx, top, startx + width, top);
1454 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1459 * Draw vertical lines at sx and sx+xwidth providing they lie within
1463 * graphics object to draw on
1469 * visWidth maximum available width
1475 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1478 // if start position is visible, draw vertical line to left of
1480 if (sx >= 0 && sx < visWidth)
1482 g.drawLine(sx, oldY, sx, sy);
1485 // if end position is visible, draw vertical line to right of
1487 if (sx + xwidth < visWidth)
1489 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1494 * Highlights search results in the visible region by rendering as white text
1495 * on a black background. Any previous highlighting is removed. Answers true
1496 * if any highlight was left on the visible alignment (so status bar should be
1497 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1498 * so allows the next repaint to update the whole display.
1503 public boolean highlightSearchResults(SearchResultsI results)
1505 return highlightSearchResults(results, false);
1510 * Highlights search results in the visible region by rendering as white text
1511 * on a black background. Any previous highlighting is removed. Answers true
1512 * if any highlight was left on the visible alignment (so status bar should be
1513 * set to match), else false.
1515 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1516 * highlighted regions are modified. This speeds up highlighting across linked
1519 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1520 * a wrapped alignment had to be scrolled to show the highlighted region, then
1521 * it should be fully redrawn, otherwise a fast paint can be performed. This
1522 * argument could be removed if fast paint of scrolled wrapped alignment is
1523 * coded in future (JAL-2609).
1526 * @param doFastPaint
1527 * if true, sets a flag so the next repaint only redraws the modified
1531 public boolean highlightSearchResults(SearchResultsI results,
1532 boolean doFastPaint)
1538 boolean wrapped = av.getWrapAlignment();
1541 fastPaint = doFastPaint;
1542 fastpainting = fastPaint;
1545 * to avoid redrawing the whole visible region, we instead
1546 * redraw just the minimal regions to remove previous highlights
1549 SearchResultsI previous = av.getSearchResults();
1550 av.setSearchResults(results);
1551 boolean redrawn = false;
1552 boolean drawn = false;
1555 redrawn = drawMappedPositionsWrapped(previous);
1556 drawn = drawMappedPositionsWrapped(results);
1561 redrawn = drawMappedPositions(previous);
1562 drawn = drawMappedPositions(results);
1567 * if highlights were either removed or added, repaint
1575 * return true only if highlights were added
1581 fastpainting = false;
1586 * Redraws the minimal rectangle in the visible region (if any) that includes
1587 * mapped positions of the given search results. Whether or not positions are
1588 * highlighted depends on the SearchResults set on the Viewport. This allows
1589 * this method to be called to either clear or set highlighting. Answers true
1590 * if any positions were drawn (in which case a repaint is still required),
1596 protected boolean drawMappedPositions(SearchResultsI results)
1598 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1604 * calculate the minimal rectangle to redraw that
1605 * includes both new and existing search results
1607 int firstSeq = Integer.MAX_VALUE;
1609 int firstCol = Integer.MAX_VALUE;
1611 boolean matchFound = false;
1613 ViewportRanges ranges = av.getRanges();
1614 int firstVisibleColumn = ranges.getStartRes();
1615 int lastVisibleColumn = ranges.getEndRes();
1616 AlignmentI alignment = av.getAlignment();
1617 if (av.hasHiddenColumns())
1619 firstVisibleColumn = alignment.getHiddenColumns()
1620 .visibleToAbsoluteColumn(firstVisibleColumn);
1621 lastVisibleColumn = alignment.getHiddenColumns()
1622 .visibleToAbsoluteColumn(lastVisibleColumn);
1625 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1626 .getEndSeq(); seqNo++)
1628 SequenceI seq = alignment.getSequenceAt(seqNo);
1630 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1632 if (visibleResults != null)
1634 for (int i = 0; i < visibleResults.length - 1; i += 2)
1636 int firstMatchedColumn = visibleResults[i];
1637 int lastMatchedColumn = visibleResults[i + 1];
1638 if (firstMatchedColumn <= lastVisibleColumn
1639 && lastMatchedColumn >= firstVisibleColumn)
1642 * found a search results match in the visible region -
1643 * remember the first and last sequence matched, and the first
1644 * and last visible columns in the matched positions
1647 firstSeq = Math.min(firstSeq, seqNo);
1648 lastSeq = Math.max(lastSeq, seqNo);
1649 firstMatchedColumn = Math.max(firstMatchedColumn,
1650 firstVisibleColumn);
1651 lastMatchedColumn = Math.min(lastMatchedColumn,
1653 firstCol = Math.min(firstCol, firstMatchedColumn);
1654 lastCol = Math.max(lastCol, lastMatchedColumn);
1662 if (av.hasHiddenColumns())
1664 firstCol = alignment.getHiddenColumns()
1665 .absoluteToVisibleColumn(firstCol);
1666 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1668 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1669 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1670 Graphics gg = img.getGraphics();
1671 gg.translate(transX, transY);
1672 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1673 gg.translate(-transX, -transY);
1681 public void propertyChange(PropertyChangeEvent evt)
1683 String eventName = evt.getPropertyName();
1685 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1686 // logic, emphasizing no check for ENDRES or ENDSEQ
1688 // Both scrolling and resizing change viewport ranges: scrolling changes
1689 // both start and end points, but resize only changes end values.
1690 // Here we only want to fastpaint on a scroll, with resize using a normal
1691 // paint, so scroll events are identified as changes to the horizontal or
1692 // vertical start value.
1694 // Make sure we're not trying to draw a panel
1695 // larger than the visible window
1700 case SequenceGroup.SEQ_GROUP_CHANGED:
1704 case ViewportRanges.MOVE_VIEWPORT:
1708 case ViewportRanges.STARTSEQ:
1709 // meaning STARTOREND
1710 // typically scroll, but possibly just the end changed
1711 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1713 case ViewportRanges.ENDRES:
1714 case ViewportRanges.ENDSEQ:
1715 // meaning second event along with "START" -- ENDONLY,NOTSTART
1718 case ViewportRanges.STARTRES:
1719 // meaning STARTOREND
1720 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1722 case ViewportRanges.STARTRESANDSEQ:
1723 scrollX = ((int[]) evt.getNewValue())[0]
1724 - ((int[]) evt.getOldValue())[0];
1725 scrollY = ((int[]) evt.getNewValue())[1]
1726 - ((int[]) evt.getOldValue())[1];
1728 // System.out.println("SC dx dy " + scrollX + " " + scrollY);
1730 if (scrollX != 0 && scrollY != 0)
1732 // all sorts of problems in JavaScript if this is commented out.
1740 ViewportRanges vpRanges = av.getRanges();
1741 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1742 scrollX = Math.max(Math.min(scrollX, range), -range);
1743 // only STARTRES or STARTRESANDSEQ:
1744 if (av.getWrapAlignment())
1746 fastPaintWrapped(scrollX);
1750 fastPaint(scrollX, scrollY);
1753 // BH 2019.07.27 was:
1754 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1756 // fastPaint = true;
1760 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1762 // fastPaint = false;
1763 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1768 // if (eventName.equals(ViewportRanges.STARTRES)
1769 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1771 // // Make sure we're not trying to draw a panel
1772 // // larger than the visible window
1773 // if (eventName.equals(ViewportRanges.STARTRES))
1775 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1779 // scrollX = ((int[]) evt.getNewValue())[0]
1780 // - ((int[]) evt.getOldValue())[0];
1782 // ViewportRanges vpRanges = av.getRanges();
1784 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1785 // if (scrollX > range)
1789 // else if (scrollX < -range)
1791 // scrollX = -range;
1794 // Both scrolling and resizing change viewport ranges: scrolling changes
1795 // both start and end points, but resize only changes end values.
1796 // Here we only want to fastpaint on a scroll, with resize using a normal
1797 // paint, so scroll events are identified as changes to the horizontal or
1798 // vertical start value.
1799 // BH 2019.07.27 was:
1800 // if (eventName.equals(ViewportRanges.STARTRES))
1802 // if (av.getWrapAlignment())
1804 // fastPaintWrapped(scrollX);
1808 // fastPaint(scrollX, 0);
1811 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1814 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1816 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1818 // if (av.getWrapAlignment())
1820 // fastPaintWrapped(scrollX);
1824 // fastPaint(scrollX, 0);
1830 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1833 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1835 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1837 // if (av.getWrapAlignment())
1839 // fastPaintWrapped(scrollX);
1845 * Does a minimal update of the image for a scroll movement. This method
1846 * handles scroll movements of up to one width of the wrapped alignment (one
1847 * click in the vertical scrollbar). Larger movements (for example after a
1848 * scroll to highlight a mapped position) trigger a full redraw instead.
1851 * number of positions scrolled (right if positive, left if negative)
1853 protected void fastPaintWrapped(int scrollX)
1855 ViewportRanges ranges = av.getRanges();
1857 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1860 * shift of one view width or more is
1861 * overcomplicated to handle in this method
1868 if (fastpainting || img == null)
1874 fastpainting = true;
1879 Graphics gg = img.getGraphics();
1881 calculateWrappedGeometry(getWidth(), getHeight());
1884 * relocate the regions of the alignment that are still visible
1886 shiftWrappedAlignment(-scrollX);
1889 * add new columns (sequence, annotation)
1890 * - at top left if scrollX < 0
1891 * - at right of last two widths if scrollX > 0
1895 int startRes = ranges.getStartRes();
1896 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1897 - scrollX - 1, getHeight());
1901 fastPaintWrappedAddRight(scrollX);
1905 * draw all scales (if shown) and hidden column markers
1907 drawWrappedDecorators(gg, ranges.getStartRes());
1914 fastpainting = false;
1919 * Draws the specified number of columns at the 'end' (bottom right) of a
1920 * wrapped alignment view, including sequences and annotations if shown, but
1921 * not scales. Also draws the same number of columns at the right hand end of
1922 * the second last width shown, if the last width is not full height (so
1923 * cannot simply be copied from the graphics image).
1927 protected void fastPaintWrappedAddRight(int columns)
1934 Graphics gg = img.getGraphics();
1936 ViewportRanges ranges = av.getRanges();
1937 int viewportWidth = ranges.getViewportWidth();
1938 int charWidth = av.getCharWidth();
1941 * draw full height alignment in the second last row, last columns, if the
1942 * last row was not full height
1944 int visibleWidths = wrappedVisibleWidths;
1945 int canvasHeight = getHeight();
1946 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1948 if (lastWidthPartHeight)
1950 int widthsAbove = Math.max(0, visibleWidths - 2);
1951 int ypos = wrappedRepeatHeightPx * widthsAbove
1952 + wrappedSpaceAboveAlignment;
1953 int endRes = ranges.getEndRes();
1954 endRes += widthsAbove * viewportWidth;
1955 int startRes = endRes - columns;
1956 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1960 * white fill first to erase annotations
1964 gg.translate(xOffset, 0);
1965 gg.setColor(Color.white);
1966 gg.fillRect(labelWidthWest, ypos,
1967 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1968 gg.translate(-xOffset, 0);
1970 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1975 * draw newly visible columns in last wrapped width (none if we
1976 * have reached the end of the alignment)
1977 * y-offset for drawing last width is height of widths above,
1980 int widthsAbove = visibleWidths - 1;
1981 int ypos = wrappedRepeatHeightPx * widthsAbove
1982 + wrappedSpaceAboveAlignment;
1983 int endRes = ranges.getEndRes();
1984 endRes += widthsAbove * viewportWidth;
1985 int startRes = endRes - columns + 1;
1988 * white fill first to erase annotations
1990 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1992 gg.translate(xOffset, 0);
1993 gg.setColor(Color.white);
1994 int width = viewportWidth * charWidth - xOffset;
1995 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1996 gg.translate(-xOffset, 0);
1998 gg.setFont(av.getFont());
1999 gg.setColor(Color.black);
2001 if (startRes < ranges.getVisibleAlignmentWidth())
2003 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2007 * and finally, white fill any space below the visible alignment
2009 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2010 if (heightBelow > 0)
2012 gg.setColor(Color.white);
2013 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2019 * Shifts the visible alignment by the specified number of columns - left if
2020 * negative, right if positive. Copies and moves sequences and annotations (if
2021 * shown). Scales, hidden column markers and any newly visible columns must be
2026 protected void shiftWrappedAlignment(int positions)
2033 Graphics gg = img.getGraphics();
2035 int charWidth = av.getCharWidth();
2037 int canvasHeight = getHeight();
2038 ViewportRanges ranges = av.getRanges();
2039 int viewportWidth = ranges.getViewportWidth();
2040 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2042 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2043 int xMax = ranges.getVisibleAlignmentWidth();
2048 * shift right (after scroll left)
2049 * for each wrapped width (starting with the last), copy (width-positions)
2050 * columns from the left margin to the right margin, and copy positions
2051 * columns from the right margin of the row above (if any) to the
2052 * left margin of the current row
2056 * get y-offset of last wrapped width, first row of sequences
2058 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2059 y += wrappedSpaceAboveAlignment;
2060 int copyFromLeftStart = labelWidthWest;
2061 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2066 * shift 'widthToCopy' residues by 'positions' places to the right
2068 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2069 positions * charWidth, 0);
2073 * copy 'positions' residue from the row above (right hand end)
2074 * to this row's left hand end
2076 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2077 positions * charWidth, heightToCopy, -widthToCopy,
2078 wrappedRepeatHeightPx);
2081 y -= wrappedRepeatHeightPx;
2087 * shift left (after scroll right)
2088 * for each wrapped width (starting with the first), copy (width-positions)
2089 * columns from the right margin to the left margin, and copy positions
2090 * columns from the left margin of the row below (if any) to the
2091 * right margin of the current row
2093 int xpos = av.getRanges().getStartRes();
2094 int y = wrappedSpaceAboveAlignment;
2095 int copyFromRightStart = labelWidthWest - positions * charWidth;
2097 while (y < canvasHeight)
2099 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2100 positions * charWidth, 0);
2101 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2102 && (xpos + viewportWidth <= xMax))
2104 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2105 * charWidth, heightToCopy, widthToCopy,
2106 -wrappedRepeatHeightPx);
2108 y += wrappedRepeatHeightPx;
2109 xpos += viewportWidth;
2117 * Redraws any positions in the search results in the visible region of a
2118 * wrapped alignment. Any highlights are drawn depending on the search results
2119 * set on the Viewport, not the <code>results</code> argument. This allows
2120 * this method to be called either to clear highlights (passing the previous
2121 * search results), or to draw new highlights.
2126 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2128 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2132 int charHeight = av.getCharHeight();
2134 boolean matchFound = false;
2136 calculateWrappedGeometry(getWidth(), getHeight());
2137 int wrappedWidth = av.getWrappedWidth();
2138 int wrappedHeight = wrappedRepeatHeightPx;
2140 ViewportRanges ranges = av.getRanges();
2141 int canvasHeight = getHeight();
2142 int repeats = canvasHeight / wrappedHeight;
2143 if (canvasHeight / wrappedHeight > 0)
2148 int firstVisibleColumn = ranges.getStartRes();
2149 int lastVisibleColumn = ranges.getStartRes() + repeats
2150 * ranges.getViewportWidth() - 1;
2152 AlignmentI alignment = av.getAlignment();
2153 if (av.hasHiddenColumns())
2155 firstVisibleColumn = alignment.getHiddenColumns()
2156 .visibleToAbsoluteColumn(firstVisibleColumn);
2157 lastVisibleColumn = alignment.getHiddenColumns()
2158 .visibleToAbsoluteColumn(lastVisibleColumn);
2161 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2164 Graphics gg = img.getGraphics();
2166 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2167 .getEndSeq(); seqNo++)
2169 SequenceI seq = alignment.getSequenceAt(seqNo);
2171 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2173 if (visibleResults != null)
2175 for (int i = 0; i < visibleResults.length - 1; i += 2)
2177 int firstMatchedColumn = visibleResults[i];
2178 int lastMatchedColumn = visibleResults[i + 1];
2179 if (firstMatchedColumn <= lastVisibleColumn
2180 && lastMatchedColumn >= firstVisibleColumn)
2183 * found a search results match in the visible region
2185 firstMatchedColumn = Math.max(firstMatchedColumn,
2186 firstVisibleColumn);
2187 lastMatchedColumn = Math.min(lastMatchedColumn,
2191 * draw each mapped position separately (as contiguous positions may
2192 * wrap across lines)
2194 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2196 int displayColumn = mappedPos;
2197 if (av.hasHiddenColumns())
2199 displayColumn = alignment.getHiddenColumns()
2200 .absoluteToVisibleColumn(displayColumn);
2204 * transX: offset from left edge of canvas to residue position
2206 int transX = labelWidthWest
2207 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2208 * av.getCharWidth();
2211 * transY: offset from top edge of canvas to residue position
2213 int transY = gapHeight;
2214 transY += (displayColumn - ranges.getStartRes())
2215 / wrappedWidth * wrappedHeight;
2216 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2219 * yOffset is from graphics origin to start of visible region
2221 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2222 if (transY < getHeight())
2225 gg.translate(transX, transY);
2226 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2228 gg.translate(-transX, -transY);
2242 * Answers the width in pixels of the left scale labels (0 if not shown)
2246 int getLabelWidthWest()
2248 return labelWidthWest;