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 * vertical gap in pixels between sequences and annotations when in wrapped
63 static final int SEQS_ANNOTATION_GAP = 3;
65 private static final String ZEROS = "0000000000";
67 final FeatureRenderer fr;
77 private final SequenceRenderer seqRdr;
79 private boolean fastPaint = false;
81 private boolean fastpainting = false;
83 private AnnotationPanel annotations;
86 * measurements for drawing a wrapped alignment
88 private int labelWidthEast; // label right width in pixels if shown
90 private int labelWidthWest; // label left width in pixels if shown
92 int wrappedSpaceAboveAlignment; // gap between widths
94 int wrappedRepeatHeightPx; // height in pixels of wrapped width
96 private int wrappedVisibleWidths; // number of wrapped widths displayed
98 private int availWidth;
100 private int availHeight;
102 private boolean allowFastPaint;
103 // Don't do this! Graphics handles are supposed to be transient
104 // private Graphics2D gg;
107 * Creates a new SeqCanvas object.
111 public SeqCanvas(AlignmentPanel ap)
114 fr = new FeatureRenderer(ap);
115 seqRdr = new SequenceRenderer(av);
116 setLayout(new BorderLayout());
117 PaintRefresher.Register(this, av.getSequenceSetId());
118 setBackground(Color.white);
120 av.getRanges().addPropertyChangeListener(this);
123 public SequenceRenderer getSequenceRenderer()
128 public FeatureRenderer getFeatureRenderer()
134 * Draws the scale above a region of a wrapped alignment, consisting of a
135 * column number every major interval (10 columns).
138 * the graphics context to draw on, positioned at the start (bottom
139 * left) of the line on which to draw any scale marks
141 * start alignment column (0..)
143 * end alignment column (0..)
145 * y offset to draw at
147 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
149 int charHeight = av.getCharHeight();
150 int charWidth = av.getCharWidth();
153 * white fill the scale space (for the fastPaint case)
155 g.setColor(Color.white);
156 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
157 charHeight * 3 / 2 + 2);
158 g.setColor(Color.black);
160 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
162 for (ScaleMark mark : marks)
164 int mpos = mark.column; // (i - startx - 1)
169 String mstring = mark.text;
175 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
179 * draw a tick mark below the column number, centred on the column;
180 * height of tick mark is 4 pixels less than half a character
182 int xpos = (mpos * charWidth) + (charWidth / 2);
183 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
189 * Draw the scale to the left or right of a wrapped alignment
192 * graphics context, positioned at the start of the scale to be drawn
194 * first column of wrapped width (0.. excluding any hidden columns)
196 * last column of wrapped width (0.. excluding any hidden columns)
198 * vertical offset at which to begin the scale
200 * if true, scale is left of residues, if false, scale is right
202 void drawVerticalScale(Graphics g, final int startx, final int endx,
203 final int ypos, final boolean left)
205 int charHeight = av.getCharHeight();
206 int charWidth = av.getCharWidth();
208 int yPos = ypos + charHeight;
211 if (av.hasHiddenColumns())
213 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
214 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
215 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
217 FontMetrics fm = getFontMetrics(av.getFont());
219 for (int i = 0; i < av.getAlignment().getHeight(); i++)
221 SequenceI seq = av.getAlignment().getSequenceAt(i);
224 * find sequence position of first non-gapped position -
225 * to the right if scale left, to the left if scale right
227 int index = left ? startX : endX;
229 while (index >= startX && index <= endX)
231 if (!Comparison.isGap(seq.getCharAt(index)))
233 value = seq.findPosition(index);
247 * white fill the space for the scale
249 g.setColor(Color.white);
250 int y = (yPos + (i * charHeight)) - (charHeight / 5);
251 // fillRect origin is top left of rectangle
252 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
258 * draw scale value, right justified within its width less half a
259 * character width padding on the right
261 int labelSpace = left ? labelWidthWest : labelWidthEast;
262 labelSpace -= charWidth / 2; // leave space to the right
263 String valueAsString = String.valueOf(value);
264 int labelLength = fm.stringWidth(valueAsString);
265 int xOffset = labelSpace - labelLength;
266 g.setColor(Color.black);
267 g.drawString(valueAsString, xOffset, y);
274 * Does a fast paint of an alignment in response to a scroll. Most of the
275 * visible region is simply copied and shifted, and then any newly visible
276 * columns or rows are drawn. The scroll may be horizontal or vertical, but
277 * not both at once. Scrolling may be the result of
279 * <li>dragging a scroll bar</li>
280 * <li>clicking in the scroll bar</li>
281 * <li>scrolling by trackpad, middle mouse button, or other device</li>
282 * <li>by moving the box in the Overview window</li>
283 * <li>programmatically to make a highlighted position visible</li>
284 * <li>pasting a block of sequences</li>
288 * columns to shift right (positive) or left (negative)
290 * rows to shift down (positive) or up (negative)
292 public void fastPaint(int horizontal, int vertical)
296 // if (horizontal != 0 && vertical != 0)
297 // throw new InvalidArgumentException();
298 if (fastpainting || img == null)
306 int charHeight = av.getCharHeight();
307 int charWidth = av.getCharWidth();
309 ViewportRanges ranges = av.getRanges();
310 int startRes = ranges.getStartRes();
311 int endRes = ranges.getEndRes();
312 int startSeq = ranges.getStartSeq();
313 int endSeq = ranges.getEndSeq();
317 if (horizontal > 0) // scrollbar pulled right, image to the left
319 transX = (endRes - startRes - horizontal) * charWidth;
320 startRes = endRes - horizontal;
322 else if (horizontal < 0)
324 endRes = startRes - horizontal;
327 if (vertical > 0) // scroll down
329 startSeq = endSeq - vertical;
331 if (startSeq < ranges.getStartSeq())
332 { // ie scrolling too fast, more than a page at a time
333 startSeq = ranges.getStartSeq();
337 transY = img.getHeight() - ((vertical + 1) * charHeight);
340 else if (vertical < 0)
342 endSeq = startSeq - vertical;
344 if (endSeq > ranges.getEndSeq())
346 endSeq = ranges.getEndSeq();
351 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
352 // + horizontal + " " + vertical + " " + startRes + " " + endRes
353 // + " " + startSeq + " " + endSeq);
355 Graphics gg = img.getGraphics();
356 gg.copyArea(horizontal * charWidth, vertical * charHeight,
357 img.getWidth(), img.getHeight(), -horizontal * charWidth,
358 -vertical * charHeight);
361 gg.translate(transX, transY);
362 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
363 gg.translate(-transX, -transY);
366 // Call repaint on alignment panel so that repaints from other alignment
367 // panel components can be aggregated. Otherwise performance of the
368 // overview window and others may be adversely affected.
369 av.getAlignPanel().repaint();
372 fastpainting = false;
377 public void paintComponent(Graphics g)
379 if (av.getAlignPanel().getHoldRepaint())
386 if (availWidth == 0 || availHeight == 0)
391 ViewportRanges ranges = av.getRanges();
392 int startRes = ranges.getStartRes();
393 int startSeq = ranges.getStartSeq();
394 int endRes = ranges.getEndRes();
395 int endSeq = ranges.getEndSeq();
397 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
398 // repaint() requests in unpredictable ways. In this case, the issue was
399 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
400 // repaint request preceded two full requests, thus resulting
401 // in a full request for paint. In constrast, in JavaScript, the three
402 // requests were bundled together into one, so the fastPaint flag was
403 // still present for the second and third request.
405 // This resulted in incomplete painting.
407 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
408 // in PaintRefresher when the target to be painted is one of those two
413 // An initial idea; can be removed once we determine this issue is closed:
414 // if (av.isFastPaintDisabled())
416 // fastPaint = false;
420 if (allowFastPaint && img != null
421 && (fastPaint || (vis = getVisibleRect()).width != (clip = g.getClipBounds()).width
422 || vis.height != clip.height))
424 g.drawImage(img, 0, 0, this);
425 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
431 allowFastPaint = true;
432 // img is a cached version of the last view we drew.
433 // If we have no img or the size has changed, make a new one.
435 if (img == null || availWidth != img.getWidth()
436 || availHeight != img.getHeight())
438 img = new BufferedImage(availWidth, availHeight,
439 BufferedImage.TYPE_INT_RGB);
442 Graphics2D gg = (Graphics2D) img.getGraphics();
443 gg.setFont(av.getFont());
447 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
448 RenderingHints.VALUE_ANTIALIAS_ON);
451 gg.setColor(Color.white);
452 gg.fillRect(0, 0, availWidth, availHeight);
454 if (av.getWrapAlignment())
456 drawWrappedPanel(gg, width, height, ranges.getStartRes());
460 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
463 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
465 g.drawImage(img, 0, 0, this);
471 drawCursor(g, startRes, endRes, startSeq, endSeq);
475 * Draw an alignment panel for printing
478 * Graphics object to draw with
480 * start residue of print area
482 * end residue of print area
484 * start sequence of print area
486 * end sequence of print area
488 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
489 int startSeq, int endSeq)
491 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
493 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
497 * Draw a wrapped alignment panel for printing
500 * Graphics object to draw with
502 * width of drawing area
503 * @param canvasHeight
504 * height of drawing area
506 * start residue of print area
508 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
509 int canvasHeight, int startRes)
511 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
513 SequenceGroup group = av.getSelectionGroup();
516 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
522 * Returns the visible width of the canvas in residues, after allowing for
523 * East or West scales (if shown)
526 * the width in pixels (possibly including scales)
530 public int getWrappedCanvasWidth(int canvasWidth)
532 int charWidth = av.getCharWidth();
534 FontMetrics fm = getFontMetrics(av.getFont());
538 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
540 labelWidth = getLabelWidth(fm);
543 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
545 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
547 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
551 * Returns a pixel width sufficient to show the largest sequence coordinate
552 * (end position) in the alignment, calculated as the FontMetrics width of
553 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
554 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
555 * half a character width space on either side.
560 protected int getLabelWidth(FontMetrics fm)
563 * find the biggest sequence end position we need to show
564 * (note this is not necessarily the sequence length)
567 AlignmentI alignment = av.getAlignment();
568 for (int i = 0; i < alignment.getHeight(); i++)
570 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
575 for (int i = maxWidth; i > 0; i /= 10)
580 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
584 * Draws as many widths of a wrapped alignment as can fit in the visible
589 * available width in pixels
591 * available height in pixels
593 * the first column (0...) of the alignment to draw
595 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
596 final int startColumn)
598 int wrappedWidthInResidues = calculateWrappedGeometry();
600 av.setWrappedWidth(wrappedWidthInResidues);
602 ViewportRanges ranges = av.getRanges();
603 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
605 // we need to call this again to make sure the startColumn +
606 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
608 calculateWrappedGeometry();
611 * draw one width at a time (excluding any scales shown),
612 * until we have run out of either alignment or vertical space available
614 int ypos = wrappedSpaceAboveAlignment;
615 int maxWidth = ranges.getVisibleAlignmentWidth();
617 int start = startColumn;
618 int currentWidth = 0;
619 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
621 int endColumn = Math.min(maxWidth,
622 start + wrappedWidthInResidues - 1);
623 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
624 ypos += wrappedRepeatHeightPx;
625 start += wrappedWidthInResidues;
629 drawWrappedDecorators(g, startColumn);
632 private void getAvailSizes()
634 int charHeight = av.getCharHeight();
635 int charWidth = av.getCharWidth();
636 availWidth = getWidth();
637 availHeight = getHeight();
638 availWidth -= (availWidth % charWidth);
639 availHeight -= (availHeight % charHeight);
642 * Calculates and saves values needed when rendering a wrapped alignment.
643 * These depend on many factors, including
645 * <li>canvas width and height</li>
646 * <li>number of visible sequences, and height of annotations if shown</li>
647 * <li>font and character width</li>
648 * <li>whether scales are shown left, right or above the alignment</li>
653 * @return the number of residue columns in each width
655 protected int calculateWrappedGeometry()
658 return calculateWrappedGeometry(availWidth, availHeight);
665 * @param canvasHeight
668 public int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
670 int charHeight = av.getCharHeight();
673 * vertical space in pixels between wrapped widths of alignment
674 * - one character height, or two if scale above is drawn
676 wrappedSpaceAboveAlignment = charHeight
677 * (av.getScaleAboveWrapped() ? 2 : 1);
680 * compute height in pixels of the wrapped widths
681 * - start with space above plus sequences
683 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment
684 + av.getAlignment().getHeight() * charHeight;
687 * add annotations panel height if shown
688 * also gap between sequences and annotations
690 if (av.isShowAnnotation())
692 wrappedRepeatHeightPx += getAnnotationHeight();
693 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
697 * number of visible widths (the last one may be part height),
698 * ensuring a part height includes at least one sequence
700 ViewportRanges ranges = av.getRanges();
701 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
702 int remainder = canvasHeight % wrappedRepeatHeightPx;
703 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
705 wrappedVisibleWidths++;
709 * compute width in residues; this also sets East and West label widths
711 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
714 * limit visibleWidths to not exceed width of alignment
716 int xMax = ranges.getVisibleAlignmentWidth();
717 int startToEnd = xMax - ranges.getStartRes();
718 int maxWidths = startToEnd / wrappedWidthInResidues;
719 if (startToEnd % wrappedWidthInResidues > 0)
723 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
725 return wrappedWidthInResidues;
729 * Draws one width of a wrapped alignment, including sequences and
730 * annnotations, if shown, but not scales or hidden column markers
736 * @param canvasHeight
738 protected void drawWrappedWidth(Graphics g, final int ypos,
739 final int startColumn, final int endColumn,
740 final int canvasHeight)
742 ViewportRanges ranges = av.getRanges();
743 int viewportWidth = ranges.getViewportWidth();
745 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
748 * move right before drawing by the width of the scale left (if any)
749 * plus column offset from left margin (usually zero, but may be non-zero
750 * when fast painting is drawing just a few columns)
752 int charWidth = av.getCharWidth();
753 int xOffset = labelWidthWest
754 + ((startColumn - ranges.getStartRes()) % viewportWidth)
757 g.translate(xOffset, 0);
760 * white fill the region to be drawn (so incremental fast paint doesn't
761 * scribble over an existing image)
763 g.setColor(Color.white);
764 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
765 wrappedRepeatHeightPx);
767 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
770 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
772 if (av.isShowAnnotation())
774 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
775 g.translate(0, yShift);
776 if (annotations == null)
778 annotations = new AnnotationPanel(av);
781 annotations.renderer.drawComponent(annotations, av, g, -1,
782 startColumn, endx + 1);
783 g.translate(0, -yShift);
785 g.translate(-xOffset, 0);
789 * Draws scales left, right and above (if shown), and any hidden column
790 * markers, on all widths of the wrapped alignment
795 protected void drawWrappedDecorators(Graphics g, final int startColumn)
797 int charWidth = av.getCharWidth();
799 g.setFont(av.getFont());
801 g.setColor(Color.black);
803 int ypos = wrappedSpaceAboveAlignment;
804 ViewportRanges ranges = av.getRanges();
805 int viewportWidth = ranges.getViewportWidth();
806 int maxWidth = ranges.getVisibleAlignmentWidth();
808 int startCol = startColumn;
810 while (widthsDrawn < wrappedVisibleWidths)
812 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
814 if (av.getScaleLeftWrapped())
816 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
819 if (av.getScaleRightWrapped())
821 int x = labelWidthWest + viewportWidth * charWidth;
823 drawVerticalScale(g, startCol, endColumn, ypos, false);
828 * white fill region of scale above and hidden column markers
829 * (to support incremental fast paint of image)
831 g.translate(labelWidthWest, 0);
832 g.setColor(Color.white);
833 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
834 viewportWidth * charWidth + labelWidthWest,
835 wrappedSpaceAboveAlignment);
836 g.setColor(Color.black);
837 g.translate(-labelWidthWest, 0);
839 g.translate(labelWidthWest, 0);
841 if (av.getScaleAboveWrapped())
843 drawNorthScale(g, startCol, endColumn, ypos);
846 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
848 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
851 g.translate(-labelWidthWest, 0);
853 ypos += wrappedRepeatHeightPx;
854 startCol += viewportWidth;
860 * Draws markers (triangles) above hidden column positions between startColumn
868 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
869 int startColumn, int endColumn)
871 int charHeight = av.getCharHeight();
872 int charWidth = av.getCharWidth();
874 g.setColor(Color.blue);
876 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
878 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
882 res = it.next() - startColumn;
884 if (res < 0 || res > endColumn - startColumn + 1)
890 * draw a downward-pointing triangle at the hidden columns location
891 * (before the following visible column)
893 int xMiddle = res * charWidth;
894 int[] xPoints = new int[] { xMiddle - charHeight / 4,
895 xMiddle + charHeight / 4, xMiddle };
896 int yTop = ypos - (charHeight / 2);
897 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
898 g.fillPolygon(xPoints, yPoints, 3);
902 private final static BasicStroke dottedStroke = new BasicStroke(1,
903 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
906 private final static BasicStroke basicStroke = new BasicStroke();
908 * Draw a selection group over a wrapped alignment
910 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
911 int canvasWidth, int canvasHeight, int startRes)
913 // chop the wrapped alignment extent up into panel-sized blocks and treat
914 // each block as if it were a block from an unwrapped alignment
915 g.setStroke(dottedStroke);
916 g.setColor(Color.RED);
918 int charWidth = av.getCharWidth();
919 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
921 int startx = startRes;
922 int maxwidth = av.getAlignment().getVisibleWidth();
923 // JAL-3253-applet had this:
924 // // height gap above each panel
925 // int charHeight = av.getCharHeight();
926 // int hgap = charHeight;
927 // if (av.getScaleAboveWrapped())
929 // hgap += charHeight;
931 // int dy = getAnnotationHeight() + hgap
932 // + av.getAlignment().getHeight() * charHeight;
933 // int ypos = hgap; // vertical offset
935 // this is from 0b573ed (gmungoc)
936 int dy = wrappedRepeatHeightPx;
937 int ypos = wrappedSpaceAboveAlignment;
939 while ((ypos <= canvasHeight) && (startx < maxwidth))
941 // set end value to be start + width, or maxwidth, whichever is smaller
942 int endx = startx + cWidth - 1;
949 g.translate(labelWidthWest, 0);
950 drawUnwrappedSelection(g, group, startx, endx, 0,
951 av.getAlignment().getHeight() - 1, ypos);
952 g.translate(-labelWidthWest, 0);
954 // update vertical offset
957 // update horizontal offset
960 g.setStroke(basicStroke);
964 * Answers zero if annotations are not shown, otherwise recalculates and
965 * answers the total height of all annotation rows in pixels
969 int getAnnotationHeight()
971 if (!av.isShowAnnotation())
976 if (annotations == null)
978 annotations = new AnnotationPanel(av);
981 return annotations.adjustPanelHeight();
985 * Draws the visible region of the alignment on the graphics context. If there
986 * are hidden column markers in the visible region, then each sub-region
987 * between the markers is drawn separately, followed by the hidden column
991 * the graphics context, positioned at the first residue to be drawn
993 * offset of the first column to draw (0..)
995 * offset of the last column to draw (0..)
997 * offset of the first sequence to draw (0..)
999 * offset of the last sequence to draw (0..)
1001 * vertical offset at which to draw (for wrapped alignments)
1003 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1004 final int startSeq, final int endSeq, final int yOffset)
1006 int charHeight = av.getCharHeight();
1007 int charWidth = av.getCharWidth();
1009 if (!av.hasHiddenColumns())
1011 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1019 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1020 VisibleContigsIterator regions = hidden
1021 .getVisContigsIterator(startRes, endRes + 1, true);
1023 while (regions.hasNext())
1025 int[] region = regions.next();
1026 blockEnd = region[1];
1027 blockStart = region[0];
1030 * draw up to just before the next hidden region, or the end of
1031 * the visible region, whichever comes first
1033 g1.translate(screenY * charWidth, 0);
1035 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1038 * draw the downline of the hidden column marker (ScalePanel draws the
1039 * triangle on top) if we reached it
1041 if (av.getShowHiddenMarkers()
1042 && (regions.hasNext() || regions.endsAtHidden()))
1044 g1.setColor(Color.blue);
1046 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1047 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1048 (endSeq - startSeq + 1) * charHeight + yOffset);
1051 g1.translate(-screenY * charWidth, 0);
1052 screenY += blockEnd - blockStart + 1;
1059 * Draws a region of the visible alignment
1063 * offset of the first column in the visible region (0..)
1065 * offset of the last column in the visible region (0..)
1067 * offset of the first sequence in the visible region (0..)
1069 * offset of the last sequence in the visible region (0..)
1071 * vertical offset at which to draw (for wrapped alignments)
1073 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1074 int endSeq, int offset)
1076 int charHeight = av.getCharHeight();
1077 int charWidth = av.getCharWidth();
1079 g.setFont(av.getFont());
1080 seqRdr.prepare(g, av.isRenderGaps());
1084 // / First draw the sequences
1085 // ///////////////////////////
1086 for (int i = startSeq; i <= endSeq; i++)
1088 nextSeq = av.getAlignment().getSequenceAt(i);
1089 if (nextSeq == null)
1091 // occasionally, a race condition occurs such that the alignment row is
1095 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1096 startRes, endRes, offset + ((i - startSeq) * charHeight));
1098 if (av.isShowSequenceFeatures())
1100 fr.drawSequence(g, nextSeq, startRes, endRes,
1101 offset + ((i - startSeq) * charHeight), false);
1105 * highlight search Results once sequence has been drawn
1107 if (av.hasSearchResults())
1109 SearchResultsI searchResults = av.getSearchResults();
1110 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1112 if (visibleResults != null)
1114 for (int r = 0; r < visibleResults.length; r += 2)
1116 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1117 visibleResults[r + 1],
1118 (visibleResults[r] - startRes) * charWidth,
1119 offset + ((i - startSeq) * charHeight));
1125 if (av.getSelectionGroup() != null
1126 || av.getAlignment().getGroups().size() > 0)
1128 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1134 * Draws the outlines of any groups defined on the alignment (excluding the
1135 * current selection group, if any)
1144 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1145 int startSeq, int endSeq, int offset)
1147 Graphics2D g = (Graphics2D) g1;
1149 SequenceGroup group = null;
1150 int groupIndex = -1;
1152 if (av.getAlignment().getGroups().size() > 0)
1154 group = av.getAlignment().getGroups().get(0);
1162 g.setColor(group.getOutlineColour());
1163 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1167 if (groupIndex >= av.getAlignment().getGroups().size())
1171 group = av.getAlignment().getGroups().get(groupIndex);
1172 } while (groupIndex < av.getAlignment().getGroups().size());
1177 * Draws the outline of the current selection group (if any)
1185 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1186 int startSeq, int endSeq)
1188 SequenceGroup group = av.getSelectionGroup();
1194 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1195 BasicStroke.JOIN_ROUND, 3f, new float[]
1197 g.setColor(Color.RED);
1198 if (!av.getWrapAlignment())
1200 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1205 drawWrappedSelection(g, group, getWidth(), getHeight(),
1206 av.getRanges().getStartRes());
1208 g.setStroke(new BasicStroke());
1212 * Draw the cursor as a separate image and overlay
1215 * start residue of area to draw cursor in
1217 * end residue of area to draw cursor in
1219 * start sequence of area to draw cursor in
1221 * end sequence of are to draw cursor in
1222 * @return a transparent image of the same size as the sequence canvas, with
1223 * the cursor drawn on it, if any
1225 private void drawCursor(Graphics g, int startRes, int endRes,
1226 int startSeq, int endSeq)
1228 // convert the cursorY into a position on the visible alignment
1229 int cursor_ypos = cursorY;
1231 // don't do work unless we have to
1232 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1236 int startx = startRes;
1239 // convert the cursorX into a position on the visible alignment
1240 int cursor_xpos = av.getAlignment().getHiddenColumns()
1241 .absoluteToVisibleColumn(cursorX);
1243 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1246 if (av.getWrapAlignment())
1248 // work out the correct offsets for the cursor
1249 int charHeight = av.getCharHeight();
1250 int charWidth = av.getCharWidth();
1251 int canvasWidth = getWidth();
1252 int canvasHeight = getHeight();
1254 // height gap above each panel
1255 int hgap = charHeight;
1256 if (av.getScaleAboveWrapped())
1261 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1263 int cHeight = av.getAlignment().getHeight() * charHeight;
1265 endx = startx + cWidth - 1;
1266 int ypos = hgap; // vertical offset
1268 // iterate down the wrapped panels
1269 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1271 // update vertical offset
1272 ypos += cHeight + getAnnotationHeight() + hgap;
1274 // update horizontal offset
1276 endx = startx + cWidth - 1;
1279 xoffset = labelWidthWest;
1282 // now check if cursor is within range for x values
1283 if (cursor_xpos >= startx && cursor_xpos <= endx)
1285 // get the character the cursor is drawn at
1286 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1287 char s = seq.getCharAt(cursorX);
1289 seqRdr.drawCursor(g, s,
1290 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1291 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1299 * Draw a selection group over an unwrapped alignment
1302 * graphics object to draw with
1306 * start residue of area to draw
1308 * end residue of area to draw
1310 * start sequence of area to draw
1312 * end sequence of area to draw
1314 * vertical offset (used when called from wrapped alignment code)
1316 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1317 int startRes, int endRes, int startSeq, int endSeq, int offset)
1319 int charWidth = av.getCharWidth();
1320 if (!av.hasHiddenColumns())
1322 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1327 // package into blocks of visible columns
1332 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1333 VisibleContigsIterator regions = hidden
1334 .getVisContigsIterator(startRes, endRes + 1, true);
1335 while (regions.hasNext())
1337 int[] region = regions.next();
1338 blockEnd = region[1];
1339 blockStart = region[0];
1341 g.translate(screenY * charWidth, 0);
1342 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1345 g.translate(-screenY * charWidth, 0);
1346 screenY += blockEnd - blockStart + 1;
1352 * Draws part of a selection group outline
1360 * @param verticalOffset
1362 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1363 int startRes, int endRes, int startSeq, int endSeq,
1366 int charHeight = av.getCharHeight();
1367 int charWidth = av.getCharWidth();
1368 int visWidth = (endRes - startRes + 1) * charWidth;
1372 boolean inGroup = false;
1377 List<SequenceI> seqs = group.getSequences(null);
1379 // position of start residue of group relative to startRes, in pixels
1380 int sx = (group.getStartRes() - startRes) * charWidth;
1382 // width of group in pixels
1383 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1386 if (!(sx + xwidth < 0 || sx > visWidth))
1388 for (i = startSeq; i <= endSeq; i++)
1390 sy = verticalOffset + (i - startSeq) * charHeight;
1392 if ((sx <= (endRes - startRes) * charWidth)
1393 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1396 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1398 bottom = sy + charHeight;
1403 if (((top == -1) && (i == 0)) || !seqs
1404 .contains(av.getAlignment().getSequenceAt(i - 1)))
1415 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1416 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1418 // reset top and bottom
1426 sy = verticalOffset + ((i - startSeq) * charHeight);
1427 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1428 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1434 * Draw horizontal selection group boundaries at top and bottom positions
1437 * graphics object to draw on
1443 * visWidth maximum available width
1445 * position to draw top of group at
1447 * position to draw bottom of group at
1449 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1450 int visWidth, int top, int bottom)
1460 // don't let width extend beyond current block, or group extent
1462 if (startx + width >= visWidth)
1464 width = visWidth - startx;
1469 g.drawLine(startx, top, startx + width, top);
1474 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1479 * Draw vertical lines at sx and sx+xwidth providing they lie within
1483 * graphics object to draw on
1489 * visWidth maximum available width
1495 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1498 // if start position is visible, draw vertical line to left of
1500 if (sx >= 0 && sx < visWidth)
1502 g.drawLine(sx, oldY, sx, sy);
1505 // if end position is visible, draw vertical line to right of
1507 if (sx + xwidth < visWidth)
1509 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1513 * Highlights search results in the visible region by rendering as white text
1514 * on a black background. Any previous highlighting is removed. Answers true
1515 * if any highlight was left on the visible alignment (so status bar should be
1516 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1517 * so allows the next repaint to update the whole display.
1522 public boolean highlightSearchResults(SearchResultsI results)
1524 return highlightSearchResults(results, false);
1528 * Highlights search results in the visible region by rendering as white text
1529 * on a black background. Any previous highlighting is removed. Answers true
1530 * if any highlight was left on the visible alignment (so status bar should be
1531 * set to match), else false.
1533 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1534 * highlighted regions are modified. This speeds up highlighting across linked
1537 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1538 * a wrapped alignment had to be scrolled to show the highlighted region, then
1539 * it should be fully redrawn, otherwise a fast paint can be performed. This
1540 * argument could be removed if fast paint of scrolled wrapped alignment is
1541 * coded in future (JAL-2609).
1544 * @param doFastPaint
1545 * if true, sets a flag so the next repaint only redraws the modified
1549 public boolean highlightSearchResults(SearchResultsI results,
1550 boolean doFastPaint)
1556 boolean wrapped = av.getWrapAlignment();
1559 fastPaint = doFastPaint;
1560 fastpainting = fastPaint;
1563 * to avoid redrawing the whole visible region, we instead
1564 * redraw just the minimal regions to remove previous highlights
1567 SearchResultsI previous = av.getSearchResults();
1568 av.setSearchResults(results);
1569 boolean redrawn = false;
1570 boolean drawn = false;
1573 redrawn = drawMappedPositionsWrapped(previous);
1574 drawn = drawMappedPositionsWrapped(results);
1579 redrawn = drawMappedPositions(previous);
1580 drawn = drawMappedPositions(results);
1585 * if highlights were either removed or added, repaint
1593 * return true only if highlights were added
1599 fastpainting = false;
1604 * Redraws the minimal rectangle in the visible region (if any) that includes
1605 * mapped positions of the given search results. Whether or not positions are
1606 * highlighted depends on the SearchResults set on the Viewport. This allows
1607 * this method to be called to either clear or set highlighting. Answers true
1608 * if any positions were drawn (in which case a repaint is still required),
1614 protected boolean drawMappedPositions(SearchResultsI results)
1616 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1622 * calculate the minimal rectangle to redraw that
1623 * includes both new and existing search results
1625 int firstSeq = Integer.MAX_VALUE;
1627 int firstCol = Integer.MAX_VALUE;
1629 boolean matchFound = false;
1631 ViewportRanges ranges = av.getRanges();
1632 int firstVisibleColumn = ranges.getStartRes();
1633 int lastVisibleColumn = ranges.getEndRes();
1634 AlignmentI alignment = av.getAlignment();
1635 if (av.hasHiddenColumns())
1637 firstVisibleColumn = alignment.getHiddenColumns()
1638 .visibleToAbsoluteColumn(firstVisibleColumn);
1639 lastVisibleColumn = alignment.getHiddenColumns()
1640 .visibleToAbsoluteColumn(lastVisibleColumn);
1643 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1644 .getEndSeq(); seqNo++)
1646 SequenceI seq = alignment.getSequenceAt(seqNo);
1648 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1650 if (visibleResults != null)
1652 for (int i = 0; i < visibleResults.length - 1; i += 2)
1654 int firstMatchedColumn = visibleResults[i];
1655 int lastMatchedColumn = visibleResults[i + 1];
1656 if (firstMatchedColumn <= lastVisibleColumn
1657 && lastMatchedColumn >= firstVisibleColumn)
1660 * found a search results match in the visible region -
1661 * remember the first and last sequence matched, and the first
1662 * and last visible columns in the matched positions
1665 firstSeq = Math.min(firstSeq, seqNo);
1666 lastSeq = Math.max(lastSeq, seqNo);
1667 firstMatchedColumn = Math.max(firstMatchedColumn,
1668 firstVisibleColumn);
1669 lastMatchedColumn = Math.min(lastMatchedColumn,
1671 firstCol = Math.min(firstCol, firstMatchedColumn);
1672 lastCol = Math.max(lastCol, lastMatchedColumn);
1680 if (av.hasHiddenColumns())
1682 firstCol = alignment.getHiddenColumns()
1683 .absoluteToVisibleColumn(firstCol);
1684 lastCol = alignment.getHiddenColumns()
1685 .absoluteToVisibleColumn(lastCol);
1687 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1688 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1689 Graphics gg = img.getGraphics();
1690 gg.translate(transX, transY);
1691 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1692 gg.translate(-transX, -transY);
1700 public void propertyChange(PropertyChangeEvent evt)
1702 String eventName = evt.getPropertyName();
1703 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1704 // logic, emphasizing no check for ENDRES or ENDSEQ
1706 // Both scrolling and resizing change viewport ranges: scrolling changes
1707 // both start and end points, but resize only changes end values.
1708 // Here we only want to fastpaint on a scroll, with resize using a normal
1709 // paint, so scroll events are identified as changes to the horizontal or
1710 // vertical start value.
1712 // Make sure we're not trying to draw a panel
1713 // larger than the visible window
1718 case SequenceGroup.SEQ_GROUP_CHANGED:
1722 case ViewportRanges.MOVE_VIEWPORT:
1726 case ViewportRanges.STARTSEQ:
1727 // meaning STARTOREND
1728 // typically scroll, but possibly just the end changed
1729 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1731 case ViewportRanges.STARTRES:
1732 // meaning STARTOREND
1733 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1735 case ViewportRanges.STARTRESANDSEQ:
1736 scrollX = ((int[]) evt.getNewValue())[0]
1737 - ((int[]) evt.getOldValue())[0];
1738 scrollY = ((int[]) evt.getNewValue())[1]
1739 - ((int[]) evt.getOldValue())[1];
1740 if (scrollX != 0 && scrollY != 0)
1742 // all sorts of problems in JavaScript if this is commented out.
1752 ViewportRanges vpRanges = av.getRanges();
1753 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1754 scrollX = Math.max(Math.min(scrollX, range), -range);
1755 // only STARTRES or STARTRESANDSEQ:
1756 if (av.getWrapAlignment())
1758 fastPaintWrapped(scrollX);
1762 fastPaint(scrollX, scrollY);
1765 // BH 2019.07.27 was:
1766 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1768 // fastPaint = true;
1772 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1774 // fastPaint = false;
1775 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1780 // if (eventName.equals(ViewportRanges.STARTRES)
1781 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1783 // // Make sure we're not trying to draw a panel
1784 // // larger than the visible window
1785 // if (eventName.equals(ViewportRanges.STARTRES))
1787 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1791 // scrollX = ((int[]) evt.getNewValue())[0]
1792 // - ((int[]) evt.getOldValue())[0];
1794 // ViewportRanges vpRanges = av.getRanges();
1796 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1797 // if (scrollX > range)
1801 // else if (scrollX < -range)
1803 // scrollX = -range;
1806 // Both scrolling and resizing change viewport ranges: scrolling changes
1807 // both start and end points, but resize only changes end values.
1808 // Here we only want to fastpaint on a scroll, with resize using a normal
1809 // paint, so scroll events are identified as changes to the horizontal or
1810 // vertical start value.
1811 // BH 2019.07.27 was:
1812 // if (eventName.equals(ViewportRanges.STARTRES))
1814 // if (av.getWrapAlignment())
1816 // fastPaintWrapped(scrollX);
1820 // fastPaint(scrollX, 0);
1823 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1826 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1828 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1830 // if (av.getWrapAlignment())
1832 // fastPaintWrapped(scrollX);
1836 // fastPaint(scrollX, 0);
1842 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1845 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1847 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1849 // if (av.getWrapAlignment())
1851 // fastPaintWrapped(scrollX);
1857 * Does a minimal update of the image for a scroll movement. This method
1858 * handles scroll movements of up to one width of the wrapped alignment (one
1859 * click in the vertical scrollbar). Larger movements (for example after a
1860 * scroll to highlight a mapped position) trigger a full redraw instead.
1863 * number of positions scrolled (right if positive, left if negative)
1865 protected void fastPaintWrapped(int scrollX)
1867 ViewportRanges ranges = av.getRanges();
1869 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1872 * shift of one view width or more is
1873 * overcomplicated to handle in this method
1880 if (fastpainting || img == null)
1886 fastpainting = true;
1890 Graphics gg = img.getGraphics();
1891 calculateWrappedGeometry();
1894 * relocate the regions of the alignment that are still visible
1896 shiftWrappedAlignment(-scrollX);
1899 * add new columns (sequence, annotation)
1900 * - at top left if scrollX < 0
1901 * - at right of last two widths if scrollX > 0
1905 int startRes = ranges.getStartRes();
1906 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1907 startRes - scrollX - 1, getHeight());
1911 fastPaintWrappedAddRight(scrollX);
1915 * draw all scales (if shown) and hidden column markers
1917 drawWrappedDecorators(gg, ranges.getStartRes());
1923 fastpainting = false;
1928 * Draws the specified number of columns at the 'end' (bottom right) of a
1929 * wrapped alignment view, including sequences and annotations if shown, but
1930 * not scales. Also draws the same number of columns at the right hand end of
1931 * the second last width shown, if the last width is not full height (so
1932 * cannot simply be copied from the graphics image).
1936 protected void fastPaintWrappedAddRight(int columns)
1943 Graphics gg = img.getGraphics();
1944 ViewportRanges ranges = av.getRanges();
1945 int viewportWidth = ranges.getViewportWidth();
1946 int charWidth = av.getCharWidth();
1949 * draw full height alignment in the second last row, last columns, if the
1950 * last row was not full height
1952 int visibleWidths = wrappedVisibleWidths;
1953 int canvasHeight = getHeight();
1954 boolean lastWidthPartHeight = (wrappedVisibleWidths
1955 * wrappedRepeatHeightPx) > canvasHeight;
1957 if (lastWidthPartHeight)
1959 int widthsAbove = Math.max(0, visibleWidths - 2);
1960 int ypos = wrappedRepeatHeightPx * widthsAbove
1961 + wrappedSpaceAboveAlignment;
1962 int endRes = ranges.getEndRes();
1963 endRes += widthsAbove * viewportWidth;
1964 int startRes = endRes - columns;
1965 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1969 * white fill first to erase annotations
1971 gg.translate(xOffset, 0);
1972 gg.setColor(Color.white);
1973 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1974 wrappedRepeatHeightPx);
1975 gg.translate(-xOffset, 0);
1977 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1981 * draw newly visible columns in last wrapped width (none if we
1982 * have reached the end of the alignment)
1983 * y-offset for drawing last width is height of widths above,
1986 int widthsAbove = visibleWidths - 1;
1987 int ypos = wrappedRepeatHeightPx * widthsAbove
1988 + wrappedSpaceAboveAlignment;
1989 int endRes = ranges.getEndRes();
1990 endRes += widthsAbove * viewportWidth;
1991 int startRes = endRes - columns + 1;
1994 * white fill first to erase annotations
1996 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1998 gg.translate(xOffset, 0);
1999 gg.setColor(Color.white);
2000 int width = viewportWidth * charWidth - xOffset;
2001 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2002 gg.translate(-xOffset, 0);
2004 gg.setFont(av.getFont());
2005 gg.setColor(Color.black);
2007 if (startRes < ranges.getVisibleAlignmentWidth())
2009 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2013 * and finally, white fill any space below the visible alignment
2015 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2016 if (heightBelow > 0)
2018 gg.setColor(Color.white);
2019 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2025 * Shifts the visible alignment by the specified number of columns - left if
2026 * negative, right if positive. Copies and moves sequences and annotations (if
2027 * shown). Scales, hidden column markers and any newly visible columns must be
2032 protected void shiftWrappedAlignment(int positions)
2039 Graphics gg = img.getGraphics();
2041 int charWidth = av.getCharWidth();
2043 int canvasHeight = getHeight();
2044 ViewportRanges ranges = av.getRanges();
2045 int viewportWidth = ranges.getViewportWidth();
2046 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2048 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2049 int xMax = ranges.getVisibleAlignmentWidth();
2054 * shift right (after scroll left)
2055 * for each wrapped width (starting with the last), copy (width-positions)
2056 * columns from the left margin to the right margin, and copy positions
2057 * columns from the right margin of the row above (if any) to the
2058 * left margin of the current row
2062 * get y-offset of last wrapped width, first row of sequences
2064 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2065 y += wrappedSpaceAboveAlignment;
2066 int copyFromLeftStart = labelWidthWest;
2067 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2072 * shift 'widthToCopy' residues by 'positions' places to the right
2074 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2075 positions * charWidth, 0);
2079 * copy 'positions' residue from the row above (right hand end)
2080 * to this row's left hand end
2082 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2083 positions * charWidth, heightToCopy, -widthToCopy,
2084 wrappedRepeatHeightPx);
2087 y -= wrappedRepeatHeightPx;
2093 * shift left (after scroll right)
2094 * for each wrapped width (starting with the first), copy (width-positions)
2095 * columns from the right margin to the left margin, and copy positions
2096 * columns from the left margin of the row below (if any) to the
2097 * right margin of the current row
2099 int xpos = av.getRanges().getStartRes();
2100 int y = wrappedSpaceAboveAlignment;
2101 int copyFromRightStart = labelWidthWest - positions * charWidth;
2103 while (y < canvasHeight)
2105 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2106 positions * charWidth, 0);
2107 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2108 && (xpos + viewportWidth <= xMax))
2110 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2111 -positions * charWidth, heightToCopy, widthToCopy,
2112 -wrappedRepeatHeightPx);
2114 y += wrappedRepeatHeightPx;
2115 xpos += viewportWidth;
2122 * Redraws any positions in the search results in the visible region of a
2123 * wrapped alignment. Any highlights are drawn depending on the search results
2124 * set on the Viewport, not the <code>results</code> argument. This allows
2125 * this method to be called either to clear highlights (passing the previous
2126 * search results), or to draw new highlights.
2131 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2133 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2137 int charHeight = av.getCharHeight();
2139 boolean matchFound = false;
2141 calculateWrappedGeometry();
2142 int wrappedWidth = av.getWrappedWidth();
2143 int wrappedHeight = wrappedRepeatHeightPx;
2145 ViewportRanges ranges = av.getRanges();
2146 int canvasHeight = getHeight();
2147 int repeats = canvasHeight / wrappedHeight;
2148 if (canvasHeight / wrappedHeight > 0)
2153 int firstVisibleColumn = ranges.getStartRes();
2154 int lastVisibleColumn = ranges.getStartRes()
2155 + repeats * ranges.getViewportWidth() - 1;
2157 AlignmentI alignment = av.getAlignment();
2158 if (av.hasHiddenColumns())
2160 firstVisibleColumn = alignment.getHiddenColumns()
2161 .visibleToAbsoluteColumn(firstVisibleColumn);
2162 lastVisibleColumn = alignment.getHiddenColumns()
2163 .visibleToAbsoluteColumn(lastVisibleColumn);
2166 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2168 Graphics gg = img.getGraphics();
2170 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2171 .getEndSeq(); seqNo++)
2173 SequenceI seq = alignment.getSequenceAt(seqNo);
2175 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2177 if (visibleResults != null)
2179 for (int i = 0; i < visibleResults.length - 1; i += 2)
2181 int firstMatchedColumn = visibleResults[i];
2182 int lastMatchedColumn = visibleResults[i + 1];
2183 if (firstMatchedColumn <= lastVisibleColumn
2184 && lastMatchedColumn >= firstVisibleColumn)
2187 * found a search results match in the visible region
2189 firstMatchedColumn = Math.max(firstMatchedColumn,
2190 firstVisibleColumn);
2191 lastMatchedColumn = Math.min(lastMatchedColumn,
2195 * draw each mapped position separately (as contiguous positions may
2196 * wrap across lines)
2198 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2200 int displayColumn = mappedPos;
2201 if (av.hasHiddenColumns())
2203 displayColumn = alignment.getHiddenColumns()
2204 .absoluteToVisibleColumn(displayColumn);
2208 * transX: offset from left edge of canvas to residue position
2210 int transX = labelWidthWest
2211 + ((displayColumn - ranges.getStartRes())
2212 % wrappedWidth) * av.getCharWidth();
2215 * transY: offset from top edge of canvas to residue position
2217 int transY = gapHeight;
2218 transY += (displayColumn - ranges.getStartRes())
2219 / wrappedWidth * wrappedHeight;
2220 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2223 * yOffset is from graphics origin to start of visible region
2225 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2226 if (transY < getHeight())
2229 gg.translate(transX, transY);
2230 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2232 gg.translate(-transX, -transY);
2245 * Answers the width in pixels of the left scale labels (0 if not shown)
2249 int getLabelWidthWest()
2251 return labelWidthWest;
2255 * Clears the flag that allows a 'fast paint' on the next repaint, so
2256 * requiring a full repaint
2258 public void setNoFastPaint()
2260 allowFastPaint = false;