2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.Rectangle;
42 import java.awt.RenderingHints;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JPanel;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 @SuppressWarnings("serial")
57 public class SeqCanvas extends JPanel implements ViewportListenerI
60 * pixels gap between sequences and annotations when in wrapped mode
62 static final int SEQS_ANNOTATION_GAP = 3;
64 private static final String ZEROS = "0000000000";
66 final FeatureRenderer fr;
76 private final SequenceRenderer seqRdr;
78 private boolean fastPaint = false;
80 private boolean fastpainting = false;
82 private AnnotationPanel annotations;
85 * measurements for drawing a wrapped alignment
87 private int labelWidthEast; // label right width in pixels if shown
89 private int labelWidthWest; // label left width in pixels if shown
91 int wrappedSpaceAboveAlignment; // gap between widths
93 int wrappedRepeatHeightPx; // height in pixels of wrapped width
95 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 // Don't do this! Graphics handles are supposed to be transient
98 //private Graphics2D gg;
101 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
108 fr = new FeatureRenderer(ap);
109 seqRdr = new SequenceRenderer(av);
110 setLayout(new BorderLayout());
111 PaintRefresher.Register(this, av.getSequenceSetId());
112 setBackground(Color.white);
114 av.getRanges().addPropertyChangeListener(this);
117 public SequenceRenderer getSequenceRenderer()
122 public FeatureRenderer getFeatureRenderer()
128 * Draws the scale above a region of a wrapped alignment, consisting of a
129 * column number every major interval (10 columns).
132 * the graphics context to draw on, positioned at the start (bottom
133 * left) of the line on which to draw any scale marks
135 * start alignment column (0..)
137 * end alignment column (0..)
139 * y offset to draw at
141 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
143 int charHeight = av.getCharHeight();
144 int charWidth = av.getCharWidth();
147 * white fill the scale space (for the fastPaint case)
149 g.setColor(Color.white);
150 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
151 charHeight * 3 / 2 + 2);
152 g.setColor(Color.black);
154 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
156 for (ScaleMark mark : marks)
158 int mpos = mark.column; // (i - startx - 1)
163 String mstring = mark.text;
169 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
173 * draw a tick mark below the column number, centred on the column;
174 * height of tick mark is 4 pixels less than half a character
176 int xpos = (mpos * charWidth) + (charWidth / 2);
177 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
183 * Draw the scale to the left or right of a wrapped alignment
186 * graphics context, positioned at the start of the scale to be drawn
188 * first column of wrapped width (0.. excluding any hidden columns)
190 * last column of wrapped width (0.. excluding any hidden columns)
192 * vertical offset at which to begin the scale
194 * if true, scale is left of residues, if false, scale is right
196 void drawVerticalScale(Graphics g, final int startx, final int endx,
197 final int ypos, final boolean left)
199 int charHeight = av.getCharHeight();
200 int charWidth = av.getCharWidth();
202 int yPos = ypos + charHeight;
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
210 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startX : endX;
224 while (index >= startX && index <= endX)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
243 * white fill the space for the scale
245 g.setColor(Color.white);
246 int y = (yPos + (i * charHeight)) - (charHeight / 5);
247 // fillRect origin is top left of rectangle
248 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
254 * draw scale value, right justified within its width less half a
255 * character width padding on the right
257 int labelSpace = left ? labelWidthWest : labelWidthEast;
258 labelSpace -= charWidth / 2; // leave space to the right
259 String valueAsString = String.valueOf(value);
260 int labelLength = fm.stringWidth(valueAsString);
261 int xOffset = labelSpace - labelLength;
262 g.setColor(Color.black);
263 g.drawString(valueAsString, xOffset, y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
280 * <li>pasting a block of sequences</li>
284 * columns to shift right (positive) or left (negative)
286 * rows to shift down (positive) or up (negative)
288 public void fastPaint(int horizontal, int vertical)
292 // if (horizontal != 0 && vertical != 0)
293 // throw new InvalidArgumentException();
294 if (fastpainting || img == null)
302 int charHeight = av.getCharHeight();
303 int charWidth = av.getCharWidth();
305 ViewportRanges ranges = av.getRanges();
306 int startRes = ranges.getStartRes();
307 int endRes = ranges.getEndRes();
308 int startSeq = ranges.getStartSeq();
309 int endSeq = ranges.getEndSeq();
313 if (horizontal > 0) // scrollbar pulled right, image to the left
315 transX = (endRes - startRes - horizontal) * charWidth;
316 startRes = endRes - horizontal;
318 else if (horizontal < 0)
320 endRes = startRes - horizontal;
323 if (vertical > 0) // scroll down
325 startSeq = endSeq - vertical;
327 if (startSeq < ranges.getStartSeq())
328 { // ie scrolling too fast, more than a page at a time
329 startSeq = ranges.getStartSeq();
333 transY = img.getHeight() - ((vertical + 1) * charHeight);
336 else if (vertical < 0)
338 endSeq = startSeq - vertical;
340 if (endSeq > ranges.getEndSeq())
342 endSeq = ranges.getEndSeq();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
355 gg.translate(transX, transY);
356 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
357 gg.translate(-transX, -transY);
360 // Call repaint on alignment panel so that repaints from other alignment
361 // panel components can be aggregated. Otherwise performance of the
362 // overview window and others may be adversely affected.
363 // System.out.println("SeqCanvas fastPaint() repaint() request...");
364 av.getAlignPanel().repaint();
367 fastpainting = false;
372 public void paintComponent(Graphics g)
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 if (availWidth == 0 || availHeight == 0)
393 ViewportRanges ranges = av.getRanges();
394 int startRes = ranges.getStartRes();
395 int startSeq = ranges.getStartSeq();
396 int endRes = ranges.getEndRes();
397 int endSeq = ranges.getEndSeq();
402 || (vis = getVisibleRect()).width != (clip = g
403 .getClipBounds()).width
404 || vis.height != clip.height))
406 g.drawImage(img, 0, 0, this);
407 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
413 // img is a cached version of the last view we drew.
414 // If we have no img or the size has changed, make a new one.
416 if (img == null || availWidth != img.getWidth()
417 || availHeight != img.getHeight())
419 img = new BufferedImage(availWidth, availHeight,
420 BufferedImage.TYPE_INT_RGB);
423 Graphics2D gg = (Graphics2D) img.getGraphics();
424 gg.setFont(av.getFont());
428 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
429 RenderingHints.VALUE_ANTIALIAS_ON);
432 gg.setColor(Color.white);
433 gg.fillRect(0, 0, availWidth, availHeight);
435 if (av.getWrapAlignment())
437 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
441 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
444 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
446 g.drawImage(img, 0, 0, this);
452 drawCursor(g, startRes, endRes, startSeq, endSeq);
457 * Draw an alignment panel for printing
460 * Graphics object to draw with
462 * start residue of print area
464 * end residue of print area
466 * start sequence of print area
468 * end sequence of print area
470 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
471 int startSeq, int endSeq)
473 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
475 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
480 * Draw a wrapped alignment panel for printing
483 * Graphics object to draw with
485 * width of drawing area
486 * @param canvasHeight
487 * height of drawing area
489 * start residue of print area
491 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
492 int canvasHeight, int startRes)
494 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
496 SequenceGroup group = av.getSelectionGroup();
499 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
505 * Using the current font, determine fields labelWidthEast and labelWidthWest,
506 * and return the number of residues that can fill the remaining width
509 * the width in pixels (possibly including scales)
511 * @return the visible width in residues, after allowing for East or West
515 public int getWrappedCanvasWidth(int width)
517 int charWidth = av.getCharWidth();
519 FontMetrics fm = getFontMetrics(av.getFont());
521 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
525 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
527 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
529 return (width - labelWidthEast - labelWidthWest) / charWidth;
533 * Returns a pixel width sufficient to show the largest sequence coordinate
534 * (end position) in the alignment, calculated as the FontMetrics width of
535 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
536 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
537 * half a character width space on either side.
542 protected int getLabelWidth(FontMetrics fm)
545 * find the biggest sequence end position we need to show
546 * (note this is not necessarily the sequence length)
549 AlignmentI alignment = av.getAlignment();
550 for (int i = 0; i < alignment.getHeight(); i++)
552 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
557 for (int i = maxWidth; i > 0; i /= 10)
562 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
566 * Draws as many widths of a wrapped alignment as can fit in the visible
571 * available width in pixels
573 * available height in pixels
575 * the first column (0...) of the alignment to draw
577 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
578 final int startColumn)
580 int wrappedWidthInResidues = calculateWrappedGeometry(availWidth,
583 av.setWrappedWidth(wrappedWidthInResidues);
585 ViewportRanges ranges = av.getRanges();
586 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
588 // we need to call this again to make sure the startColumn +
589 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
591 calculateWrappedGeometry(availWidth, availHeight);
594 * draw one width at a time (excluding any scales shown),
595 * until we have run out of either alignment or vertical space available
597 int ypos = wrappedSpaceAboveAlignment;
598 int maxWidth = ranges.getVisibleAlignmentWidth();
600 int start = startColumn;
601 int currentWidth = 0;
602 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
605 .min(maxWidth, start + wrappedWidthInResidues - 1);
606 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
607 ypos += wrappedRepeatHeightPx;
608 start += wrappedWidthInResidues;
612 drawWrappedDecorators(g, startColumn);
616 * Calculates and saves values needed when rendering a wrapped alignment.
617 * These depend on many factors, including
619 * <li>canvas width and height</li>
620 * <li>number of visible sequences, and height of annotations if shown</li>
621 * <li>font and character width</li>
622 * <li>whether scales are shown left, right or above the alignment</li>
627 * @return the number of residue columns in each width
629 protected int calculateWrappedGeometry(int availWidth, int availHeight)
631 int charHeight = av.getCharHeight();
634 * vertical space in pixels between wrapped widths of alignment
635 * - one character height, or two if scale above is drawn
637 wrappedSpaceAboveAlignment = charHeight
638 * (av.getScaleAboveWrapped() ? 2 : 1);
641 * compute height in pixels of the wrapped widths
642 * - start with space above plus sequences
644 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
645 wrappedRepeatHeightPx += av.getAlignment().getHeight()
649 * add annotations panel height if shown
650 * also gap between sequences and annotations
652 if (av.isShowAnnotation())
654 wrappedRepeatHeightPx += getAnnotationHeight();
655 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
659 * number of visible widths (the last one may be part height),
660 * ensuring a part height includes at least one sequence
662 ViewportRanges ranges = av.getRanges();
663 wrappedVisibleWidths = availHeight / wrappedRepeatHeightPx;
664 int remainder = availHeight % wrappedRepeatHeightPx;
665 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
667 wrappedVisibleWidths++;
671 * compute width in residues; this also sets East and West label widths
673 int wrappedWidthInResidues = getWrappedCanvasWidth(availWidth);
676 * limit visibleWidths to not exceed width of alignment
678 int xMax = ranges.getVisibleAlignmentWidth();
679 int startToEnd = xMax - ranges.getStartRes();
680 int maxWidths = startToEnd / wrappedWidthInResidues;
681 if (startToEnd % wrappedWidthInResidues > 0)
685 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
687 return wrappedWidthInResidues;
691 * Draws one width of a wrapped alignment, including sequences and
692 * annnotations, if shown, but not scales or hidden column markers
698 * @param canvasHeight
700 protected void drawWrappedWidth(Graphics g, final int ypos,
701 final int startColumn, final int endColumn,
702 final int canvasHeight)
704 ViewportRanges ranges = av.getRanges();
705 int viewportWidth = ranges.getViewportWidth();
707 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
710 * move right before drawing by the width of the scale left (if any)
711 * plus column offset from left margin (usually zero, but may be non-zero
712 * when fast painting is drawing just a few columns)
714 int charWidth = av.getCharWidth();
715 int xOffset = labelWidthWest
716 + ((startColumn - ranges.getStartRes()) % viewportWidth)
719 g.translate(xOffset, 0);
722 * white fill the region to be drawn (so incremental fast paint doesn't
723 * scribble over an existing image)
725 g.setColor(Color.white);
726 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
727 wrappedRepeatHeightPx);
729 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
732 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
734 if (av.isShowAnnotation())
736 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
737 g.translate(0, yShift);
738 if (annotations == null)
740 annotations = new AnnotationPanel(av);
743 annotations.renderer.drawComponent(annotations, av, g, -1,
744 startColumn, endx + 1);
745 g.translate(0, -yShift);
747 g.translate(-xOffset, 0);
751 * Draws scales left, right and above (if shown), and any hidden column
752 * markers, on all widths of the wrapped alignment
757 protected void drawWrappedDecorators(Graphics g, final int startColumn)
759 int charWidth = av.getCharWidth();
761 g.setFont(av.getFont());
763 g.setColor(Color.black);
765 int ypos = wrappedSpaceAboveAlignment;
766 ViewportRanges ranges = av.getRanges();
767 int viewportWidth = ranges.getViewportWidth();
768 int maxWidth = ranges.getVisibleAlignmentWidth();
770 int startCol = startColumn;
772 while (widthsDrawn < wrappedVisibleWidths)
774 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
776 if (av.getScaleLeftWrapped())
778 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
781 if (av.getScaleRightWrapped())
783 int x = labelWidthWest + viewportWidth * charWidth;
786 drawVerticalScale(g, startCol, endColumn, ypos, false);
791 * white fill region of scale above and hidden column markers
792 * (to support incremental fast paint of image)
794 g.translate(labelWidthWest, 0);
795 g.setColor(Color.white);
796 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
797 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
798 g.setColor(Color.black);
799 g.translate(-labelWidthWest, 0);
801 g.translate(labelWidthWest, 0);
803 if (av.getScaleAboveWrapped())
805 drawNorthScale(g, startCol, endColumn, ypos);
808 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
810 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
813 g.translate(-labelWidthWest, 0);
815 ypos += wrappedRepeatHeightPx;
816 startCol += viewportWidth;
822 * Draws markers (triangles) above hidden column positions between startColumn
830 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
831 int startColumn, int endColumn)
833 int charHeight = av.getCharHeight();
834 int charWidth = av.getCharWidth();
836 g.setColor(Color.blue);
838 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
840 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
844 res = it.next() - startColumn;
846 if (res < 0 || res > endColumn - startColumn + 1)
852 * draw a downward-pointing triangle at the hidden columns location
853 * (before the following visible column)
855 int xMiddle = res * charWidth;
856 int[] xPoints = new int[] { xMiddle - charHeight / 4,
857 xMiddle + charHeight / 4, xMiddle };
858 int yTop = ypos - (charHeight / 2);
859 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
860 g.fillPolygon(xPoints, yPoints, 3);
865 * Draw a selection group over a wrapped alignment
867 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
869 int canvasHeight, int startRes)
871 int charHeight = av.getCharHeight();
872 int charWidth = av.getCharWidth();
874 // height gap above each panel
875 int hgap = charHeight;
876 if (av.getScaleAboveWrapped())
881 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
883 int cHeight = av.getAlignment().getHeight() * charHeight;
885 int startx = startRes;
887 int ypos = hgap; // vertical offset
888 int maxwidth = av.getAlignment().getVisibleWidth();
890 // chop the wrapped alignment extent up into panel-sized blocks and treat
891 // each block as if it were a block from an unwrapped alignment
892 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
893 BasicStroke.JOIN_ROUND, 3f, new float[]
895 g.setColor(Color.RED);
896 while ((ypos <= canvasHeight) && (startx < maxwidth))
898 // set end value to be start + width, or maxwidth, whichever is smaller
899 endx = startx + cWidth - 1;
906 g.translate(labelWidthWest, 0);
908 drawUnwrappedSelection(g, group, startx, endx, 0,
909 av.getAlignment().getHeight() - 1,
912 g.translate(-labelWidthWest, 0);
914 // update vertical offset
915 ypos += cHeight + getAnnotationHeight() + hgap;
917 // update horizontal offset
920 g.setStroke(new BasicStroke());
923 int getAnnotationHeight()
925 if (!av.isShowAnnotation())
930 if (annotations == null)
932 annotations = new AnnotationPanel(av);
935 return annotations.adjustPanelHeight();
939 * Draws the visible region of the alignment on the graphics context. If there
940 * are hidden column markers in the visible region, then each sub-region
941 * between the markers is drawn separately, followed by the hidden column
945 * the graphics context, positioned at the first residue to be drawn
947 * offset of the first column to draw (0..)
949 * offset of the last column to draw (0..)
951 * offset of the first sequence to draw (0..)
953 * offset of the last sequence to draw (0..)
955 * vertical offset at which to draw (for wrapped alignments)
957 public void drawPanel(Graphics g1, final int startRes, final int endRes,
958 final int startSeq, final int endSeq, final int yOffset)
960 int charHeight = av.getCharHeight();
961 int charWidth = av.getCharWidth();
963 if (!av.hasHiddenColumns())
965 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
973 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
974 VisibleContigsIterator regions = hidden
975 .getVisContigsIterator(startRes, endRes + 1, true);
977 while (regions.hasNext())
979 int[] region = regions.next();
980 blockEnd = region[1];
981 blockStart = region[0];
984 * draw up to just before the next hidden region, or the end of
985 * the visible region, whichever comes first
987 g1.translate(screenY * charWidth, 0);
989 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
992 * draw the downline of the hidden column marker (ScalePanel draws the
993 * triangle on top) if we reached it
995 if (av.getShowHiddenMarkers()
996 && (regions.hasNext() || regions.endsAtHidden()))
998 g1.setColor(Color.blue);
1000 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1001 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1002 (endSeq - startSeq + 1) * charHeight + yOffset);
1005 g1.translate(-screenY * charWidth, 0);
1006 screenY += blockEnd - blockStart + 1;
1013 * Draws a region of the visible alignment
1017 * offset of the first column in the visible region (0..)
1019 * offset of the last column in the visible region (0..)
1021 * offset of the first sequence in the visible region (0..)
1023 * offset of the last sequence in the visible region (0..)
1025 * vertical offset at which to draw (for wrapped alignments)
1027 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1028 int endSeq, int offset)
1030 int charHeight = av.getCharHeight();
1031 int charWidth = av.getCharWidth();
1033 g.setFont(av.getFont());
1034 seqRdr.prepare(g, av.isRenderGaps());
1038 // / First draw the sequences
1039 // ///////////////////////////
1040 for (int i = startSeq; i <= endSeq; i++)
1042 nextSeq = av.getAlignment().getSequenceAt(i);
1043 if (nextSeq == null)
1045 // occasionally, a race condition occurs such that the alignment row is
1049 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1050 startRes, endRes, offset + ((i - startSeq) * charHeight));
1052 if (av.isShowSequenceFeatures())
1054 fr.drawSequence(g, nextSeq, startRes, endRes,
1055 offset + ((i - startSeq) * charHeight), false);
1059 * highlight search Results once sequence has been drawn
1061 if (av.hasSearchResults())
1063 SearchResultsI searchResults = av.getSearchResults();
1064 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1066 if (visibleResults != null)
1068 for (int r = 0; r < visibleResults.length; r += 2)
1070 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1071 visibleResults[r + 1],
1072 (visibleResults[r] - startRes) * charWidth,
1073 offset + ((i - startSeq) * charHeight));
1079 if (av.getSelectionGroup() != null
1080 || av.getAlignment().getGroups().size() > 0)
1082 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1088 * Draws the outlines of any groups defined on the alignment (excluding the
1089 * current selection group, if any)
1098 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1099 int startSeq, int endSeq, int offset)
1101 Graphics2D g = (Graphics2D) g1;
1103 SequenceGroup group = null;
1104 int groupIndex = -1;
1106 if (av.getAlignment().getGroups().size() > 0)
1108 group = av.getAlignment().getGroups().get(0);
1116 g.setColor(group.getOutlineColour());
1117 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1121 if (groupIndex >= av.getAlignment().getGroups().size())
1125 group = av.getAlignment().getGroups().get(groupIndex);
1126 } while (groupIndex < av.getAlignment().getGroups().size());
1131 * Draws the outline of the current selection group (if any)
1139 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1140 int startSeq, int endSeq)
1142 SequenceGroup group = av.getSelectionGroup();
1148 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1149 BasicStroke.JOIN_ROUND, 3f, new float[]
1151 g.setColor(Color.RED);
1152 if (!av.getWrapAlignment())
1154 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1159 drawWrappedSelection(g, group, getWidth(), getHeight(),
1160 av.getRanges().getStartRes());
1162 g.setStroke(new BasicStroke());
1166 * Draw the cursor as a separate image and overlay
1169 * start residue of area to draw cursor in
1171 * end residue of area to draw cursor in
1173 * start sequence of area to draw cursor in
1175 * end sequence of are to draw cursor in
1176 * @return a transparent image of the same size as the sequence canvas, with
1177 * the cursor drawn on it, if any
1179 private void drawCursor(Graphics g, int startRes, int endRes,
1183 // convert the cursorY into a position on the visible alignment
1184 int cursor_ypos = cursorY;
1186 // don't do work unless we have to
1187 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1191 int startx = startRes;
1194 // convert the cursorX into a position on the visible alignment
1195 int cursor_xpos = av.getAlignment().getHiddenColumns()
1196 .absoluteToVisibleColumn(cursorX);
1198 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1201 if (av.getWrapAlignment())
1203 // work out the correct offsets for the cursor
1204 int charHeight = av.getCharHeight();
1205 int charWidth = av.getCharWidth();
1206 int canvasWidth = getWidth();
1207 int canvasHeight = getHeight();
1209 // height gap above each panel
1210 int hgap = charHeight;
1211 if (av.getScaleAboveWrapped())
1216 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1218 int cHeight = av.getAlignment().getHeight() * charHeight;
1220 endx = startx + cWidth - 1;
1221 int ypos = hgap; // vertical offset
1223 // iterate down the wrapped panels
1224 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1226 // update vertical offset
1227 ypos += cHeight + getAnnotationHeight() + hgap;
1229 // update horizontal offset
1231 endx = startx + cWidth - 1;
1234 xoffset = labelWidthWest;
1237 // now check if cursor is within range for x values
1238 if (cursor_xpos >= startx && cursor_xpos <= endx)
1240 // get the character the cursor is drawn at
1241 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1242 char s = seq.getCharAt(cursorX);
1244 seqRdr.drawCursor(g, s,
1245 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1246 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1254 * Draw a selection group over an unwrapped alignment
1257 * graphics object to draw with
1261 * start residue of area to draw
1263 * end residue of area to draw
1265 * start sequence of area to draw
1267 * end sequence of area to draw
1269 * vertical offset (used when called from wrapped alignment code)
1271 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1272 int startRes, int endRes, int startSeq, int endSeq, int offset)
1274 int charWidth = av.getCharWidth();
1276 if (!av.hasHiddenColumns())
1278 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1283 // package into blocks of visible columns
1288 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1289 VisibleContigsIterator regions = hidden
1290 .getVisContigsIterator(startRes, endRes + 1, true);
1291 while (regions.hasNext())
1293 int[] region = regions.next();
1294 blockEnd = region[1];
1295 blockStart = region[0];
1297 g.translate(screenY * charWidth, 0);
1298 drawPartialGroupOutline(g, group,
1299 blockStart, blockEnd, startSeq, endSeq, offset);
1301 g.translate(-screenY * charWidth, 0);
1302 screenY += blockEnd - blockStart + 1;
1308 * Draws part of a selection group outline
1316 * @param verticalOffset
1318 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1319 int startRes, int endRes, int startSeq, int endSeq,
1322 int charHeight = av.getCharHeight();
1323 int charWidth = av.getCharWidth();
1324 int visWidth = (endRes - startRes + 1) * charWidth;
1328 boolean inGroup = false;
1333 List<SequenceI> seqs = group.getSequences(null);
1335 // position of start residue of group relative to startRes, in pixels
1336 int sx = (group.getStartRes() - startRes) * charWidth;
1338 // width of group in pixels
1339 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1342 if (!(sx + xwidth < 0 || sx > visWidth))
1344 for (i = startSeq; i <= endSeq; i++)
1346 sy = verticalOffset + (i - startSeq) * charHeight;
1348 if ((sx <= (endRes - startRes) * charWidth)
1349 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1352 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1354 bottom = sy + charHeight;
1359 if (((top == -1) && (i == 0)) || !seqs
1360 .contains(av.getAlignment().getSequenceAt(i - 1)))
1371 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1372 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1374 // reset top and bottom
1382 sy = verticalOffset + ((i - startSeq) * charHeight);
1383 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1384 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1390 * Draw horizontal selection group boundaries at top and bottom positions
1393 * graphics object to draw on
1399 * visWidth maximum available width
1401 * position to draw top of group at
1403 * position to draw bottom of group at
1405 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1406 int visWidth, int top, int bottom)
1416 // don't let width extend beyond current block, or group extent
1418 if (startx + width >= visWidth)
1420 width = visWidth - startx;
1425 g.drawLine(startx, top, startx + width, top);
1430 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1435 * Draw vertical lines at sx and sx+xwidth providing they lie within
1439 * graphics object to draw on
1445 * visWidth maximum available width
1451 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1454 // if start position is visible, draw vertical line to left of
1456 if (sx >= 0 && sx < visWidth)
1458 g.drawLine(sx, oldY, sx, sy);
1461 // if end position is visible, draw vertical line to right of
1463 if (sx + xwidth < visWidth)
1465 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1470 * Highlights search results in the visible region by rendering as white text
1471 * on a black background. Any previous highlighting is removed. Answers true
1472 * if any highlight was left on the visible alignment (so status bar should be
1473 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1474 * so allows the next repaint to update the whole display.
1479 public boolean highlightSearchResults(SearchResultsI results)
1481 return highlightSearchResults(results, false);
1486 * Highlights search results in the visible region by rendering as white text
1487 * on a black background. Any previous highlighting is removed. Answers true
1488 * if any highlight was left on the visible alignment (so status bar should be
1489 * set to match), else false.
1491 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1492 * highlighted regions are modified. This speeds up highlighting across linked
1495 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1496 * a wrapped alignment had to be scrolled to show the highlighted region, then
1497 * it should be fully redrawn, otherwise a fast paint can be performed. This
1498 * argument could be removed if fast paint of scrolled wrapped alignment is
1499 * coded in future (JAL-2609).
1502 * @param doFastPaint
1503 * if true, sets a flag so the next repaint only redraws the modified
1507 public boolean highlightSearchResults(SearchResultsI results,
1508 boolean doFastPaint)
1514 boolean wrapped = av.getWrapAlignment();
1517 fastPaint = doFastPaint;
1518 fastpainting = fastPaint;
1521 * to avoid redrawing the whole visible region, we instead
1522 * redraw just the minimal regions to remove previous highlights
1525 SearchResultsI previous = av.getSearchResults();
1526 av.setSearchResults(results);
1527 boolean redrawn = false;
1528 boolean drawn = false;
1531 redrawn = drawMappedPositionsWrapped(previous);
1532 drawn = drawMappedPositionsWrapped(results);
1537 redrawn = drawMappedPositions(previous);
1538 drawn = drawMappedPositions(results);
1543 * if highlights were either removed or added, repaint
1551 * return true only if highlights were added
1557 fastpainting = false;
1562 * Redraws the minimal rectangle in the visible region (if any) that includes
1563 * mapped positions of the given search results. Whether or not positions are
1564 * highlighted depends on the SearchResults set on the Viewport. This allows
1565 * this method to be called to either clear or set highlighting. Answers true
1566 * if any positions were drawn (in which case a repaint is still required),
1572 protected boolean drawMappedPositions(SearchResultsI results)
1574 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1580 * calculate the minimal rectangle to redraw that
1581 * includes both new and existing search results
1583 int firstSeq = Integer.MAX_VALUE;
1585 int firstCol = Integer.MAX_VALUE;
1587 boolean matchFound = false;
1589 ViewportRanges ranges = av.getRanges();
1590 int firstVisibleColumn = ranges.getStartRes();
1591 int lastVisibleColumn = ranges.getEndRes();
1592 AlignmentI alignment = av.getAlignment();
1593 if (av.hasHiddenColumns())
1595 firstVisibleColumn = alignment.getHiddenColumns()
1596 .visibleToAbsoluteColumn(firstVisibleColumn);
1597 lastVisibleColumn = alignment.getHiddenColumns()
1598 .visibleToAbsoluteColumn(lastVisibleColumn);
1601 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1602 .getEndSeq(); seqNo++)
1604 SequenceI seq = alignment.getSequenceAt(seqNo);
1606 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1608 if (visibleResults != null)
1610 for (int i = 0; i < visibleResults.length - 1; i += 2)
1612 int firstMatchedColumn = visibleResults[i];
1613 int lastMatchedColumn = visibleResults[i + 1];
1614 if (firstMatchedColumn <= lastVisibleColumn
1615 && lastMatchedColumn >= firstVisibleColumn)
1618 * found a search results match in the visible region -
1619 * remember the first and last sequence matched, and the first
1620 * and last visible columns in the matched positions
1623 firstSeq = Math.min(firstSeq, seqNo);
1624 lastSeq = Math.max(lastSeq, seqNo);
1625 firstMatchedColumn = Math.max(firstMatchedColumn,
1626 firstVisibleColumn);
1627 lastMatchedColumn = Math.min(lastMatchedColumn,
1629 firstCol = Math.min(firstCol, firstMatchedColumn);
1630 lastCol = Math.max(lastCol, lastMatchedColumn);
1638 if (av.hasHiddenColumns())
1640 firstCol = alignment.getHiddenColumns()
1641 .absoluteToVisibleColumn(firstCol);
1642 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1644 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1645 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1646 Graphics gg = img.getGraphics();
1647 gg.translate(transX, transY);
1648 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1649 gg.translate(-transX, -transY);
1657 public void propertyChange(PropertyChangeEvent evt)
1659 String eventName = evt.getPropertyName();
1661 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1662 // logic, emphasizing no check for ENDRES or ENDSEQ
1664 // Both scrolling and resizing change viewport ranges: scrolling changes
1665 // both start and end points, but resize only changes end values.
1666 // Here we only want to fastpaint on a scroll, with resize using a normal
1667 // paint, so scroll events are identified as changes to the horizontal or
1668 // vertical start value.
1670 // Make sure we're not trying to draw a panel
1671 // larger than the visible window
1676 case SequenceGroup.SEQ_GROUP_CHANGED:
1680 case ViewportRanges.MOVE_VIEWPORT:
1684 case ViewportRanges.STARTSEQ:
1685 // meaning STARTOREND
1686 // typically scroll, but possibly just the end changed
1687 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1689 case ViewportRanges.STARTRES:
1690 // meaning STARTOREND
1691 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1693 case ViewportRanges.STARTRESANDSEQ:
1694 scrollX = ((int[]) evt.getNewValue())[0]
1695 - ((int[]) evt.getOldValue())[0];
1696 scrollY = ((int[]) evt.getNewValue())[1]
1697 - ((int[]) evt.getOldValue())[1];
1699 // System.out.println("SC dx dy " + scrollX + " " + scrollY);
1701 if (scrollX != 0 && scrollY != 0)
1703 // all sorts of problems in JavaScript if this is commented out.
1713 ViewportRanges vpRanges = av.getRanges();
1714 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1715 scrollX = Math.max(Math.min(scrollX, range), -range);
1716 // only STARTRES or STARTRESANDSEQ:
1717 if (av.getWrapAlignment())
1719 fastPaintWrapped(scrollX);
1723 fastPaint(scrollX, scrollY);
1726 // BH 2019.07.27 was:
1727 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1729 // fastPaint = true;
1733 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1735 // fastPaint = false;
1736 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1741 // if (eventName.equals(ViewportRanges.STARTRES)
1742 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1744 // // Make sure we're not trying to draw a panel
1745 // // larger than the visible window
1746 // if (eventName.equals(ViewportRanges.STARTRES))
1748 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1752 // scrollX = ((int[]) evt.getNewValue())[0]
1753 // - ((int[]) evt.getOldValue())[0];
1755 // ViewportRanges vpRanges = av.getRanges();
1757 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1758 // if (scrollX > range)
1762 // else if (scrollX < -range)
1764 // scrollX = -range;
1767 // Both scrolling and resizing change viewport ranges: scrolling changes
1768 // both start and end points, but resize only changes end values.
1769 // Here we only want to fastpaint on a scroll, with resize using a normal
1770 // paint, so scroll events are identified as changes to the horizontal or
1771 // vertical start value.
1772 // BH 2019.07.27 was:
1773 // if (eventName.equals(ViewportRanges.STARTRES))
1775 // if (av.getWrapAlignment())
1777 // fastPaintWrapped(scrollX);
1781 // fastPaint(scrollX, 0);
1784 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1787 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1789 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1791 // if (av.getWrapAlignment())
1793 // fastPaintWrapped(scrollX);
1797 // fastPaint(scrollX, 0);
1803 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1806 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1808 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1810 // if (av.getWrapAlignment())
1812 // fastPaintWrapped(scrollX);
1818 * Does a minimal update of the image for a scroll movement. This method
1819 * handles scroll movements of up to one width of the wrapped alignment (one
1820 * click in the vertical scrollbar). Larger movements (for example after a
1821 * scroll to highlight a mapped position) trigger a full redraw instead.
1824 * number of positions scrolled (right if positive, left if negative)
1826 protected void fastPaintWrapped(int scrollX)
1828 ViewportRanges ranges = av.getRanges();
1830 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1833 * shift of one view width or more is
1834 * overcomplicated to handle in this method
1841 if (fastpainting || img == null)
1847 fastpainting = true;
1852 Graphics gg = img.getGraphics();
1854 calculateWrappedGeometry(getWidth(), getHeight());
1857 * relocate the regions of the alignment that are still visible
1859 shiftWrappedAlignment(-scrollX);
1862 * add new columns (sequence, annotation)
1863 * - at top left if scrollX < 0
1864 * - at right of last two widths if scrollX > 0
1868 int startRes = ranges.getStartRes();
1869 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1870 - scrollX - 1, getHeight());
1874 fastPaintWrappedAddRight(scrollX);
1878 * draw all scales (if shown) and hidden column markers
1880 drawWrappedDecorators(gg, ranges.getStartRes());
1887 fastpainting = false;
1892 * Draws the specified number of columns at the 'end' (bottom right) of a
1893 * wrapped alignment view, including sequences and annotations if shown, but
1894 * not scales. Also draws the same number of columns at the right hand end of
1895 * the second last width shown, if the last width is not full height (so
1896 * cannot simply be copied from the graphics image).
1900 protected void fastPaintWrappedAddRight(int columns)
1907 Graphics gg = img.getGraphics();
1909 ViewportRanges ranges = av.getRanges();
1910 int viewportWidth = ranges.getViewportWidth();
1911 int charWidth = av.getCharWidth();
1914 * draw full height alignment in the second last row, last columns, if the
1915 * last row was not full height
1917 int visibleWidths = wrappedVisibleWidths;
1918 int canvasHeight = getHeight();
1919 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1921 if (lastWidthPartHeight)
1923 int widthsAbove = Math.max(0, visibleWidths - 2);
1924 int ypos = wrappedRepeatHeightPx * widthsAbove
1925 + wrappedSpaceAboveAlignment;
1926 int endRes = ranges.getEndRes();
1927 endRes += widthsAbove * viewportWidth;
1928 int startRes = endRes - columns;
1929 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1933 * white fill first to erase annotations
1937 gg.translate(xOffset, 0);
1938 gg.setColor(Color.white);
1939 gg.fillRect(labelWidthWest, ypos,
1940 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1941 gg.translate(-xOffset, 0);
1943 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1948 * draw newly visible columns in last wrapped width (none if we
1949 * have reached the end of the alignment)
1950 * y-offset for drawing last width is height of widths above,
1953 int widthsAbove = visibleWidths - 1;
1954 int ypos = wrappedRepeatHeightPx * widthsAbove
1955 + wrappedSpaceAboveAlignment;
1956 int endRes = ranges.getEndRes();
1957 endRes += widthsAbove * viewportWidth;
1958 int startRes = endRes - columns + 1;
1961 * white fill first to erase annotations
1963 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1965 gg.translate(xOffset, 0);
1966 gg.setColor(Color.white);
1967 int width = viewportWidth * charWidth - xOffset;
1968 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1969 gg.translate(-xOffset, 0);
1971 gg.setFont(av.getFont());
1972 gg.setColor(Color.black);
1974 if (startRes < ranges.getVisibleAlignmentWidth())
1976 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1980 * and finally, white fill any space below the visible alignment
1982 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1983 if (heightBelow > 0)
1985 gg.setColor(Color.white);
1986 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1992 * Shifts the visible alignment by the specified number of columns - left if
1993 * negative, right if positive. Copies and moves sequences and annotations (if
1994 * shown). Scales, hidden column markers and any newly visible columns must be
1999 protected void shiftWrappedAlignment(int positions)
2006 Graphics gg = img.getGraphics();
2008 int charWidth = av.getCharWidth();
2010 int canvasHeight = getHeight();
2011 ViewportRanges ranges = av.getRanges();
2012 int viewportWidth = ranges.getViewportWidth();
2013 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2015 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2016 int xMax = ranges.getVisibleAlignmentWidth();
2021 * shift right (after scroll left)
2022 * for each wrapped width (starting with the last), copy (width-positions)
2023 * columns from the left margin to the right margin, and copy positions
2024 * columns from the right margin of the row above (if any) to the
2025 * left margin of the current row
2029 * get y-offset of last wrapped width, first row of sequences
2031 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2032 y += wrappedSpaceAboveAlignment;
2033 int copyFromLeftStart = labelWidthWest;
2034 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2039 * shift 'widthToCopy' residues by 'positions' places to the right
2041 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2042 positions * charWidth, 0);
2046 * copy 'positions' residue from the row above (right hand end)
2047 * to this row's left hand end
2049 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2050 positions * charWidth, heightToCopy, -widthToCopy,
2051 wrappedRepeatHeightPx);
2054 y -= wrappedRepeatHeightPx;
2060 * shift left (after scroll right)
2061 * for each wrapped width (starting with the first), copy (width-positions)
2062 * columns from the right margin to the left margin, and copy positions
2063 * columns from the left margin of the row below (if any) to the
2064 * right margin of the current row
2066 int xpos = av.getRanges().getStartRes();
2067 int y = wrappedSpaceAboveAlignment;
2068 int copyFromRightStart = labelWidthWest - positions * charWidth;
2070 while (y < canvasHeight)
2072 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2073 positions * charWidth, 0);
2074 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2075 && (xpos + viewportWidth <= xMax))
2077 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2078 * charWidth, heightToCopy, widthToCopy,
2079 -wrappedRepeatHeightPx);
2081 y += wrappedRepeatHeightPx;
2082 xpos += viewportWidth;
2090 * Redraws any positions in the search results in the visible region of a
2091 * wrapped alignment. Any highlights are drawn depending on the search results
2092 * set on the Viewport, not the <code>results</code> argument. This allows
2093 * this method to be called either to clear highlights (passing the previous
2094 * search results), or to draw new highlights.
2099 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2101 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2105 int charHeight = av.getCharHeight();
2107 boolean matchFound = false;
2109 calculateWrappedGeometry(getWidth(), getHeight());
2110 int wrappedWidth = av.getWrappedWidth();
2111 int wrappedHeight = wrappedRepeatHeightPx;
2113 ViewportRanges ranges = av.getRanges();
2114 int canvasHeight = getHeight();
2115 int repeats = canvasHeight / wrappedHeight;
2116 if (canvasHeight / wrappedHeight > 0)
2121 int firstVisibleColumn = ranges.getStartRes();
2122 int lastVisibleColumn = ranges.getStartRes() + repeats
2123 * ranges.getViewportWidth() - 1;
2125 AlignmentI alignment = av.getAlignment();
2126 if (av.hasHiddenColumns())
2128 firstVisibleColumn = alignment.getHiddenColumns()
2129 .visibleToAbsoluteColumn(firstVisibleColumn);
2130 lastVisibleColumn = alignment.getHiddenColumns()
2131 .visibleToAbsoluteColumn(lastVisibleColumn);
2134 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2137 Graphics gg = img.getGraphics();
2139 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2140 .getEndSeq(); seqNo++)
2142 SequenceI seq = alignment.getSequenceAt(seqNo);
2144 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2146 if (visibleResults != null)
2148 for (int i = 0; i < visibleResults.length - 1; i += 2)
2150 int firstMatchedColumn = visibleResults[i];
2151 int lastMatchedColumn = visibleResults[i + 1];
2152 if (firstMatchedColumn <= lastVisibleColumn
2153 && lastMatchedColumn >= firstVisibleColumn)
2156 * found a search results match in the visible region
2158 firstMatchedColumn = Math.max(firstMatchedColumn,
2159 firstVisibleColumn);
2160 lastMatchedColumn = Math.min(lastMatchedColumn,
2164 * draw each mapped position separately (as contiguous positions may
2165 * wrap across lines)
2167 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2169 int displayColumn = mappedPos;
2170 if (av.hasHiddenColumns())
2172 displayColumn = alignment.getHiddenColumns()
2173 .absoluteToVisibleColumn(displayColumn);
2177 * transX: offset from left edge of canvas to residue position
2179 int transX = labelWidthWest
2180 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2181 * av.getCharWidth();
2184 * transY: offset from top edge of canvas to residue position
2186 int transY = gapHeight;
2187 transY += (displayColumn - ranges.getStartRes())
2188 / wrappedWidth * wrappedHeight;
2189 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2192 * yOffset is from graphics origin to start of visible region
2194 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2195 if (transY < getHeight())
2198 gg.translate(transX, transY);
2199 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2201 gg.translate(-transX, -transY);
2215 * Answers the width in pixels of the left scale labels (0 if not shown)
2219 int getLabelWidthWest()
2221 return labelWidthWest;
2225 * Clears the flag that allows a 'fast paint' on the next repaint, so
2226 * requiring a full repaint
2228 public void setNoFastPaint()