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 // Don't do this! Graphics handles are supposed to be transient
103 // private Graphics2D gg;
106 * Creates a new SeqCanvas object.
110 public SeqCanvas(AlignmentPanel ap)
113 fr = new FeatureRenderer(ap);
114 seqRdr = new SequenceRenderer(av);
115 setLayout(new BorderLayout());
116 PaintRefresher.Register(this, av.getSequenceSetId());
117 setBackground(Color.white);
119 av.getRanges().addPropertyChangeListener(this);
122 public SequenceRenderer getSequenceRenderer()
127 public FeatureRenderer getFeatureRenderer()
133 * Draws the scale above a region of a wrapped alignment, consisting of a
134 * column number every major interval (10 columns).
137 * the graphics context to draw on, positioned at the start (bottom
138 * left) of the line on which to draw any scale marks
140 * start alignment column (0..)
142 * end alignment column (0..)
144 * y offset to draw at
146 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
148 int charHeight = av.getCharHeight();
149 int charWidth = av.getCharWidth();
152 * white fill the scale space (for the fastPaint case)
154 g.setColor(Color.white);
155 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
156 charHeight * 3 / 2 + 2);
157 g.setColor(Color.black);
159 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
161 for (ScaleMark mark : marks)
163 int mpos = mark.column; // (i - startx - 1)
168 String mstring = mark.text;
174 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
178 * draw a tick mark below the column number, centred on the column;
179 * height of tick mark is 4 pixels less than half a character
181 int xpos = (mpos * charWidth) + (charWidth / 2);
182 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
188 * Draw the scale to the left or right of a wrapped alignment
191 * graphics context, positioned at the start of the scale to be drawn
193 * first column of wrapped width (0.. excluding any hidden columns)
195 * last column of wrapped width (0.. excluding any hidden columns)
197 * vertical offset at which to begin the scale
199 * if true, scale is left of residues, if false, scale is right
201 void drawVerticalScale(Graphics g, final int startx, final int endx,
202 final int ypos, final boolean left)
204 int charHeight = av.getCharHeight();
205 int charWidth = av.getCharWidth();
207 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();
350 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
351 // + horizontal + " " + vertical + " " + startRes + " " + endRes
352 // + " " + startSeq + " " + endSeq);
354 Graphics gg = img.getGraphics();
355 gg.copyArea(horizontal * charWidth, vertical * charHeight,
356 img.getWidth(), img.getHeight(), -horizontal * charWidth,
357 -vertical * charHeight);
358 gg.translate(transX, transY);
359 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
360 gg.translate(-transX, -transY);
363 // Call repaint on alignment panel so that repaints from other alignment
364 // panel components can be aggregated. Otherwise performance of the
365 // overview window and others may be adversely affected.
366 // System.out.println("SeqCanvas fastPaint() repaint() request...");
367 av.getAlignPanel().repaint();
370 fastpainting = false;
375 public void paintComponent(Graphics g)
377 if (av.getAlignPanel().getHoldRepaint())
384 if (availWidth == 0 || availHeight == 0)
389 ViewportRanges ranges = av.getRanges();
390 int startRes = ranges.getStartRes();
391 int startSeq = ranges.getStartSeq();
392 int endRes = ranges.getEndRes();
393 int endSeq = ranges.getEndSeq();
395 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
396 // repaint() requests in unpredictable ways. In this case, the issue was
397 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
398 // repaint request preceded two full requests, thus resulting
399 // in a full request for paint. In constrast, in JavaScript, the three
400 // requests were bundled together into one, so the fastPaint flag was
401 // still present for the second and third request.
403 // This resulted in incomplete painting.
405 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
406 // in PaintRefresher when the target to be painted is one of those two
411 // An initial idea; can be removed once we determine this issue is closed:
412 // if (av.isFastPaintDisabled())
414 // fastPaint = false;
420 || (vis = getVisibleRect()).width != (clip = g
421 .getClipBounds()).width
422 || vis.height != clip.height))
424 g.drawImage(img, 0, 0, this);
425 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
431 // img is a cached version of the last view we drew.
432 // If we have no img or the size has changed, make a new one.
434 if (img == null || availWidth != img.getWidth()
435 || availHeight != img.getHeight())
437 img = new BufferedImage(availWidth, availHeight,
438 BufferedImage.TYPE_INT_RGB);
441 Graphics2D gg = (Graphics2D) img.getGraphics();
442 gg.setFont(av.getFont());
446 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
447 RenderingHints.VALUE_ANTIALIAS_ON);
450 gg.setColor(Color.white);
451 gg.fillRect(0, 0, availWidth, availHeight);
453 if (av.getWrapAlignment())
455 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
459 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
462 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
464 g.drawImage(img, 0, 0, this);
470 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 * Using the current font, determine fields labelWidthEast and labelWidthWest,
523 * and return the number of residues that can fill the remaining width
526 * the width in pixels (possibly including scales)
528 * @return the visible width in residues, after allowing for East or West
532 public int getWrappedCanvasWidth(int w)
534 int charWidth = av.getCharWidth();
536 FontMetrics fm = getFontMetrics(av.getFont());
538 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
542 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
544 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
546 return (w - labelWidthEast - labelWidthWest) / charWidth;
550 * Returns a pixel width sufficient to show the largest sequence coordinate
551 * (end position) in the alignment, calculated as the FontMetrics width of
552 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
553 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
554 * half a character width space on either side.
559 protected int getLabelWidth(FontMetrics fm)
562 * find the biggest sequence end position we need to show
563 * (note this is not necessarily the sequence length)
566 AlignmentI alignment = av.getAlignment();
567 for (int i = 0; i < alignment.getHeight(); i++)
569 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
574 for (int i = maxWidth; i > 0; i /= 10)
579 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
583 * Draws as many widths of a wrapped alignment as can fit in the visible
588 * available width in pixels
590 * available height in pixels
592 * the first column (0...) of the alignment to draw
594 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
595 final int startColumn)
597 int wrappedWidthInResidues = calculateWrappedGeometry();
598 av.setWrappedWidth(wrappedWidthInResidues);
599 ViewportRanges ranges = av.getRanges();
600 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
602 // we need to call this again to make sure the startColumn +
603 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
605 calculateWrappedGeometry();
608 * draw one width at a time (excluding any scales shown),
609 * until we have run out of either alignment or vertical space available
611 int ypos = wrappedSpaceAboveAlignment;
612 int maxWidth = ranges.getVisibleAlignmentWidth();
614 int start = startColumn;
615 int currentWidth = 0;
616 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
618 int endColumn = Math.min(maxWidth,
619 start + wrappedWidthInResidues - 1);
620 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
621 ypos += wrappedRepeatHeightPx;
622 start += wrappedWidthInResidues;
626 drawWrappedDecorators(g, startColumn);
629 private void getAvailSizes()
631 int charHeight = av.getCharHeight();
632 int charWidth = av.getCharWidth();
633 availWidth = getWidth();
634 availHeight = getHeight();
635 availWidth -= (availWidth % charWidth);
636 availHeight -= (availHeight % charHeight);
640 * Calculates and saves values needed when rendering a wrapped alignment.
641 * These depend on many factors, including
643 * <li>canvas width and height</li>
644 * <li>number of visible sequences, and height of annotations if shown</li>
645 * <li>font and character width</li>
646 * <li>whether scales are shown left, right or above the alignment</li>
651 * @return the number of residue columns in each width
653 protected int calculateWrappedGeometry()
656 return calculateWrappedGeometry(availWidth, availHeight);
663 * @param canvasHeight
666 public int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
669 int charHeight = av.getCharHeight();
672 * vertical space in pixels between wrapped widths of alignment
673 * - one character height, or two if scale above is drawn
675 wrappedSpaceAboveAlignment = charHeight
676 * (av.getScaleAboveWrapped() ? 2 : 1);
679 * compute height in pixels of the wrapped widths
680 * - start with space above plus sequences
682 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment
683 + av.getAlignment().getHeight() * charHeight;
686 * add annotations panel height if shown
687 * also gap between sequences and annotations
689 if (av.isShowAnnotation())
691 wrappedRepeatHeightPx += getAnnotationHeight();
692 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
696 * number of visible widths (the last one may be part height),
697 * ensuring a part height includes at least one sequence
699 ViewportRanges ranges = av.getRanges();
700 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
701 int remainder = canvasHeight % wrappedRepeatHeightPx;
702 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
704 wrappedVisibleWidths++;
708 * compute width in residues; this also sets East and West label widths
710 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
713 * limit visibleWidths to not exceed width of alignment
715 int xMax = ranges.getVisibleAlignmentWidth();
716 int startToEnd = xMax - ranges.getStartRes();
717 int maxWidths = startToEnd / wrappedWidthInResidues;
718 if (startToEnd % wrappedWidthInResidues > 0)
722 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
724 return wrappedWidthInResidues;
728 * Draws one width of a wrapped alignment, including sequences and
729 * annnotations, if shown, but not scales or hidden column markers
735 * @param canvasHeight
737 protected void drawWrappedWidth(Graphics g, final int ypos,
738 final int startColumn, final int endColumn,
739 final int canvasHeight)
741 ViewportRanges ranges = av.getRanges();
742 int viewportWidth = ranges.getViewportWidth();
744 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
747 * move right before drawing by the width of the scale left (if any)
748 * plus column offset from left margin (usually zero, but may be non-zero
749 * when fast painting is drawing just a few columns)
751 int charWidth = av.getCharWidth();
752 int xOffset = labelWidthWest
753 + ((startColumn - ranges.getStartRes()) % viewportWidth)
756 g.translate(xOffset, 0);
759 * white fill the region to be drawn (so incremental fast paint doesn't
760 * scribble over an existing image)
762 g.setColor(Color.white);
763 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
764 wrappedRepeatHeightPx);
766 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
769 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
771 if (av.isShowAnnotation())
773 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
774 g.translate(0, yShift);
775 if (annotations == null)
777 annotations = new AnnotationPanel(av);
780 annotations.renderer.drawComponent(annotations, av, g, -1,
781 startColumn, endx + 1);
782 g.translate(0, -yShift);
784 g.translate(-xOffset, 0);
788 * Draws scales left, right and above (if shown), and any hidden column
789 * markers, on all widths of the wrapped alignment
794 protected void drawWrappedDecorators(Graphics g, final int startColumn)
796 int charWidth = av.getCharWidth();
798 g.setFont(av.getFont());
800 g.setColor(Color.black);
802 int ypos = wrappedSpaceAboveAlignment;
803 ViewportRanges ranges = av.getRanges();
804 int viewportWidth = ranges.getViewportWidth();
805 int maxWidth = ranges.getVisibleAlignmentWidth();
807 int startCol = startColumn;
809 while (widthsDrawn < wrappedVisibleWidths)
811 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
813 if (av.getScaleLeftWrapped())
815 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
818 if (av.getScaleRightWrapped())
820 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();
909 * Draw a selection group over a wrapped alignment
911 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
912 int canvasWidth, int canvasHeight, int startRes)
914 // chop the wrapped alignment extent up into panel-sized blocks and treat
915 // each block as if it were a block from an unwrapped alignment
916 g.setStroke(dottedStroke);
917 g.setColor(Color.RED);
919 int charWidth = av.getCharWidth();
920 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
922 int startx = startRes;
923 int maxwidth = av.getAlignment().getVisibleWidth();
925 // JAL-3253-applet had this:
926 // // height gap above each panel
927 // int charHeight = av.getCharHeight();
928 // int hgap = charHeight;
929 // if (av.getScaleAboveWrapped())
931 // hgap += charHeight;
933 // int dy = getAnnotationHeight() + hgap
934 // + av.getAlignment().getHeight() * charHeight;
935 // int ypos = hgap; // vertical offset
937 // this is from 0b573ed (gmungoc)
938 int dy = wrappedRepeatHeightPx;
939 int ypos = wrappedSpaceAboveAlignment;
941 while ((ypos <= canvasHeight) && (startx < maxwidth))
943 // set end value to be start + width, or maxwidth, whichever is smaller
944 int endx = startx + cWidth - 1;
951 g.translate(labelWidthWest, 0);
953 drawUnwrappedSelection(g, group, startx, endx, 0,
954 av.getAlignment().getHeight() - 1, ypos);
956 g.translate(-labelWidthWest, 0);
958 // update vertical offset
961 // update horizontal offset
964 g.setStroke(basicStroke);
968 * Answers zero if annotations are not shown, otherwise recalculates and
969 * answers the total height of all annotation rows in pixels
973 int getAnnotationHeight()
975 if (!av.isShowAnnotation())
980 if (annotations == null)
982 annotations = new AnnotationPanel(av);
985 return annotations.adjustPanelHeight();
989 * Draws the visible region of the alignment on the graphics context. If there
990 * are hidden column markers in the visible region, then each sub-region
991 * between the markers is drawn separately, followed by the hidden column
995 * the graphics context, positioned at the first residue to be drawn
997 * offset of the first column to draw (0..)
999 * offset of the last column to draw (0..)
1001 * offset of the first sequence to draw (0..)
1003 * offset of the last sequence to draw (0..)
1005 * vertical offset at which to draw (for wrapped alignments)
1007 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1008 final int startSeq, final int endSeq, final int yOffset)
1010 int charHeight = av.getCharHeight();
1011 int charWidth = av.getCharWidth();
1013 if (!av.hasHiddenColumns())
1015 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1023 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1024 VisibleContigsIterator regions = hidden
1025 .getVisContigsIterator(startRes, endRes + 1, true);
1027 while (regions.hasNext())
1029 int[] region = regions.next();
1030 blockEnd = region[1];
1031 blockStart = region[0];
1034 * draw up to just before the next hidden region, or the end of
1035 * the visible region, whichever comes first
1037 g1.translate(screenY * charWidth, 0);
1039 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1042 * draw the downline of the hidden column marker (ScalePanel draws the
1043 * triangle on top) if we reached it
1045 if (av.getShowHiddenMarkers()
1046 && (regions.hasNext() || regions.endsAtHidden()))
1048 g1.setColor(Color.blue);
1050 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1051 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1052 (endSeq - startSeq + 1) * charHeight + yOffset);
1055 g1.translate(-screenY * charWidth, 0);
1056 screenY += blockEnd - blockStart + 1;
1063 * Draws a region of the visible alignment
1067 * offset of the first column in the visible region (0..)
1069 * offset of the last column in the visible region (0..)
1071 * offset of the first sequence in the visible region (0..)
1073 * offset of the last sequence in the visible region (0..)
1075 * vertical offset at which to draw (for wrapped alignments)
1077 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1078 int endSeq, int offset)
1080 int charHeight = av.getCharHeight();
1081 int charWidth = av.getCharWidth();
1083 g.setFont(av.getFont());
1084 seqRdr.prepare(g, av.isRenderGaps());
1088 // / First draw the sequences
1089 // ///////////////////////////
1090 for (int i = startSeq; i <= endSeq; i++)
1092 nextSeq = av.getAlignment().getSequenceAt(i);
1093 if (nextSeq == null)
1095 // occasionally, a race condition occurs such that the alignment row is
1099 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1100 startRes, endRes, offset + ((i - startSeq) * charHeight));
1102 if (av.isShowSequenceFeatures())
1104 fr.drawSequence(g, nextSeq, startRes, endRes,
1105 offset + ((i - startSeq) * charHeight), false);
1109 * highlight search Results once sequence has been drawn
1111 if (av.hasSearchResults())
1113 SearchResultsI searchResults = av.getSearchResults();
1114 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1116 if (visibleResults != null)
1118 for (int r = 0; r < visibleResults.length; r += 2)
1120 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1121 visibleResults[r + 1],
1122 (visibleResults[r] - startRes) * charWidth,
1123 offset + ((i - startSeq) * charHeight));
1129 if (av.getSelectionGroup() != null
1130 || av.getAlignment().getGroups().size() > 0)
1132 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1138 * Draws the outlines of any groups defined on the alignment (excluding the
1139 * current selection group, if any)
1148 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1149 int startSeq, int endSeq, int offset)
1151 Graphics2D g = (Graphics2D) g1;
1153 SequenceGroup group = null;
1154 int groupIndex = -1;
1156 if (av.getAlignment().getGroups().size() > 0)
1158 group = av.getAlignment().getGroups().get(0);
1166 g.setColor(group.getOutlineColour());
1167 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1171 if (groupIndex >= av.getAlignment().getGroups().size())
1175 group = av.getAlignment().getGroups().get(groupIndex);
1176 } while (groupIndex < av.getAlignment().getGroups().size());
1181 * Draws the outline of the current selection group (if any)
1189 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1190 int startSeq, int endSeq)
1192 SequenceGroup group = av.getSelectionGroup();
1198 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1199 BasicStroke.JOIN_ROUND, 3f, new float[]
1201 g.setColor(Color.RED);
1202 if (!av.getWrapAlignment())
1204 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1209 drawWrappedSelection(g, group, getWidth(), getHeight(),
1210 av.getRanges().getStartRes());
1212 g.setStroke(new BasicStroke());
1216 * Draw the cursor as a separate image and overlay
1219 * start residue of area to draw cursor in
1221 * end residue of area to draw cursor in
1223 * start sequence of area to draw cursor in
1225 * end sequence of are to draw cursor in
1226 * @return a transparent image of the same size as the sequence canvas, with
1227 * the cursor drawn on it, if any
1229 private void drawCursor(Graphics g, int startRes, int endRes,
1230 int startSeq, int endSeq)
1232 // convert the cursorY into a position on the visible alignment
1233 int cursor_ypos = cursorY;
1235 // don't do work unless we have to
1236 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1240 int startx = startRes;
1243 // convert the cursorX into a position on the visible alignment
1244 int cursor_xpos = av.getAlignment().getHiddenColumns()
1245 .absoluteToVisibleColumn(cursorX);
1247 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1250 if (av.getWrapAlignment())
1252 // work out the correct offsets for the cursor
1253 int charHeight = av.getCharHeight();
1254 int charWidth = av.getCharWidth();
1255 int canvasWidth = getWidth();
1256 int canvasHeight = getHeight();
1258 // height gap above each panel
1259 int hgap = charHeight;
1260 if (av.getScaleAboveWrapped())
1265 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1267 int cHeight = av.getAlignment().getHeight() * charHeight;
1269 endx = startx + cWidth - 1;
1270 int ypos = hgap; // vertical offset
1272 // iterate down the wrapped panels
1273 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1275 // update vertical offset
1276 ypos += cHeight + getAnnotationHeight() + hgap;
1278 // update horizontal offset
1280 endx = startx + cWidth - 1;
1283 xoffset = labelWidthWest;
1286 // now check if cursor is within range for x values
1287 if (cursor_xpos >= startx && cursor_xpos <= endx)
1289 // get the character the cursor is drawn at
1290 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1291 char s = seq.getCharAt(cursorX);
1293 seqRdr.drawCursor(g, s,
1294 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1295 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1302 * Draw a selection group over an unwrapped alignment
1305 * graphics object to draw with
1309 * start residue of area to draw
1311 * end residue of area to draw
1313 * start sequence of area to draw
1315 * end sequence of area to draw
1317 * vertical offset (used when called from wrapped alignment code)
1319 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1320 int startRes, int endRes, int startSeq, int endSeq, int offset)
1322 int charWidth = av.getCharWidth();
1324 if (!av.hasHiddenColumns())
1326 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1331 // package into blocks of visible columns
1336 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1337 VisibleContigsIterator regions = hidden
1338 .getVisContigsIterator(startRes, endRes + 1, true);
1339 while (regions.hasNext())
1341 int[] region = regions.next();
1342 blockEnd = region[1];
1343 blockStart = region[0];
1345 g.translate(screenY * charWidth, 0);
1346 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1349 g.translate(-screenY * charWidth, 0);
1350 screenY += blockEnd - blockStart + 1;
1356 * Draws part of a selection group outline
1364 * @param verticalOffset
1366 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1367 int startRes, int endRes, int startSeq, int endSeq,
1370 int charHeight = av.getCharHeight();
1371 int charWidth = av.getCharWidth();
1372 int visWidth = (endRes - startRes + 1) * charWidth;
1376 boolean inGroup = false;
1381 List<SequenceI> seqs = group.getSequences(null);
1383 // position of start residue of group relative to startRes, in pixels
1384 int sx = (group.getStartRes() - startRes) * charWidth;
1386 // width of group in pixels
1387 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1390 if (!(sx + xwidth < 0 || sx > visWidth))
1392 for (i = startSeq; i <= endSeq; i++)
1394 sy = verticalOffset + (i - startSeq) * charHeight;
1396 if ((sx <= (endRes - startRes) * charWidth)
1397 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1400 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1402 bottom = sy + charHeight;
1407 if (((top == -1) && (i == 0)) || !seqs
1408 .contains(av.getAlignment().getSequenceAt(i - 1)))
1419 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1420 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1422 // reset top and bottom
1430 sy = verticalOffset + ((i - startSeq) * charHeight);
1431 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1432 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1438 * Draw horizontal selection group boundaries at top and bottom positions
1441 * graphics object to draw on
1447 * visWidth maximum available width
1449 * position to draw top of group at
1451 * position to draw bottom of group at
1453 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1454 int visWidth, int top, int bottom)
1464 // don't let width extend beyond current block, or group extent
1466 if (startx + width >= visWidth)
1468 width = visWidth - startx;
1473 g.drawLine(startx, top, startx + width, top);
1478 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1483 * Draw vertical lines at sx and sx+xwidth providing they lie within
1487 * graphics object to draw on
1493 * visWidth maximum available width
1499 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1502 // if start position is visible, draw vertical line to left of
1504 if (sx >= 0 && sx < visWidth)
1506 g.drawLine(sx, oldY, sx, sy);
1509 // if end position is visible, draw vertical line to right of
1511 if (sx + xwidth < visWidth)
1513 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1518 * Highlights search results in the visible region by rendering as white text
1519 * on a black background. Any previous highlighting is removed. Answers true
1520 * if any highlight was left on the visible alignment (so status bar should be
1521 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1522 * so allows the next repaint to update the whole display.
1527 public boolean highlightSearchResults(SearchResultsI results)
1529 return highlightSearchResults(results, false);
1534 * Highlights search results in the visible region by rendering as white text
1535 * on a black background. Any previous highlighting is removed. Answers true
1536 * if any highlight was left on the visible alignment (so status bar should be
1537 * set to match), else false.
1539 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1540 * highlighted regions are modified. This speeds up highlighting across linked
1543 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1544 * a wrapped alignment had to be scrolled to show the highlighted region, then
1545 * it should be fully redrawn, otherwise a fast paint can be performed. This
1546 * argument could be removed if fast paint of scrolled wrapped alignment is
1547 * coded in future (JAL-2609).
1550 * @param doFastPaint
1551 * if true, sets a flag so the next repaint only redraws the modified
1555 public boolean highlightSearchResults(SearchResultsI results,
1556 boolean doFastPaint)
1562 boolean wrapped = av.getWrapAlignment();
1565 fastPaint = doFastPaint;
1566 fastpainting = fastPaint;
1569 * to avoid redrawing the whole visible region, we instead
1570 * redraw just the minimal regions to remove previous highlights
1573 SearchResultsI previous = av.getSearchResults();
1574 av.setSearchResults(results);
1575 boolean redrawn = false;
1576 boolean drawn = false;
1579 redrawn = drawMappedPositionsWrapped(previous);
1580 drawn = drawMappedPositionsWrapped(results);
1585 redrawn = drawMappedPositions(previous);
1586 drawn = drawMappedPositions(results);
1591 * if highlights were either removed or added, repaint
1599 * return true only if highlights were added
1605 fastpainting = false;
1610 * Redraws the minimal rectangle in the visible region (if any) that includes
1611 * mapped positions of the given search results. Whether or not positions are
1612 * highlighted depends on the SearchResults set on the Viewport. This allows
1613 * this method to be called to either clear or set highlighting. Answers true
1614 * if any positions were drawn (in which case a repaint is still required),
1620 protected boolean drawMappedPositions(SearchResultsI results)
1622 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1628 * calculate the minimal rectangle to redraw that
1629 * includes both new and existing search results
1631 int firstSeq = Integer.MAX_VALUE;
1633 int firstCol = Integer.MAX_VALUE;
1635 boolean matchFound = false;
1637 ViewportRanges ranges = av.getRanges();
1638 int firstVisibleColumn = ranges.getStartRes();
1639 int lastVisibleColumn = ranges.getEndRes();
1640 AlignmentI alignment = av.getAlignment();
1641 if (av.hasHiddenColumns())
1643 firstVisibleColumn = alignment.getHiddenColumns()
1644 .visibleToAbsoluteColumn(firstVisibleColumn);
1645 lastVisibleColumn = alignment.getHiddenColumns()
1646 .visibleToAbsoluteColumn(lastVisibleColumn);
1649 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1650 .getEndSeq(); seqNo++)
1652 SequenceI seq = alignment.getSequenceAt(seqNo);
1654 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1656 if (visibleResults != null)
1658 for (int i = 0; i < visibleResults.length - 1; i += 2)
1660 int firstMatchedColumn = visibleResults[i];
1661 int lastMatchedColumn = visibleResults[i + 1];
1662 if (firstMatchedColumn <= lastVisibleColumn
1663 && lastMatchedColumn >= firstVisibleColumn)
1666 * found a search results match in the visible region -
1667 * remember the first and last sequence matched, and the first
1668 * and last visible columns in the matched positions
1671 firstSeq = Math.min(firstSeq, seqNo);
1672 lastSeq = Math.max(lastSeq, seqNo);
1673 firstMatchedColumn = Math.max(firstMatchedColumn,
1674 firstVisibleColumn);
1675 lastMatchedColumn = Math.min(lastMatchedColumn,
1677 firstCol = Math.min(firstCol, firstMatchedColumn);
1678 lastCol = Math.max(lastCol, lastMatchedColumn);
1686 if (av.hasHiddenColumns())
1688 firstCol = alignment.getHiddenColumns()
1689 .absoluteToVisibleColumn(firstCol);
1690 lastCol = alignment.getHiddenColumns()
1691 .absoluteToVisibleColumn(lastCol);
1693 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1694 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1695 Graphics gg = img.getGraphics();
1696 gg.translate(transX, transY);
1697 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1698 gg.translate(-transX, -transY);
1706 public void propertyChange(PropertyChangeEvent evt)
1708 String eventName = evt.getPropertyName();
1710 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1711 // logic, emphasizing no check for ENDRES or ENDSEQ
1713 // Both scrolling and resizing change viewport ranges: scrolling changes
1714 // both start and end points, but resize only changes end values.
1715 // Here we only want to fastpaint on a scroll, with resize using a normal
1716 // paint, so scroll events are identified as changes to the horizontal or
1717 // vertical start value.
1719 // Make sure we're not trying to draw a panel
1720 // larger than the visible window
1725 case SequenceGroup.SEQ_GROUP_CHANGED:
1729 case ViewportRanges.MOVE_VIEWPORT:
1733 case ViewportRanges.STARTSEQ:
1734 // meaning STARTOREND
1735 // typically scroll, but possibly just the end changed
1736 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1738 case ViewportRanges.STARTRES:
1739 // meaning STARTOREND
1740 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1742 case ViewportRanges.STARTRESANDSEQ:
1743 scrollX = ((int[]) evt.getNewValue())[0]
1744 - ((int[]) evt.getOldValue())[0];
1745 scrollY = ((int[]) evt.getNewValue())[1]
1746 - ((int[]) evt.getOldValue())[1];
1748 // System.out.println("SC dx dy " + scrollX + " " + scrollY);
1750 if (scrollX != 0 && scrollY != 0)
1752 // all sorts of problems in JavaScript if this is commented out.
1762 ViewportRanges vpRanges = av.getRanges();
1763 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1764 scrollX = Math.max(Math.min(scrollX, range), -range);
1765 // only STARTRES or STARTRESANDSEQ:
1766 if (av.getWrapAlignment())
1768 fastPaintWrapped(scrollX);
1772 fastPaint(scrollX, scrollY);
1775 // BH 2019.07.27 was:
1776 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1778 // fastPaint = true;
1782 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1784 // fastPaint = false;
1785 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1790 // if (eventName.equals(ViewportRanges.STARTRES)
1791 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1793 // // Make sure we're not trying to draw a panel
1794 // // larger than the visible window
1795 // if (eventName.equals(ViewportRanges.STARTRES))
1797 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1801 // scrollX = ((int[]) evt.getNewValue())[0]
1802 // - ((int[]) evt.getOldValue())[0];
1804 // ViewportRanges vpRanges = av.getRanges();
1806 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1807 // if (scrollX > range)
1811 // else if (scrollX < -range)
1813 // scrollX = -range;
1816 // Both scrolling and resizing change viewport ranges: scrolling changes
1817 // both start and end points, but resize only changes end values.
1818 // Here we only want to fastpaint on a scroll, with resize using a normal
1819 // paint, so scroll events are identified as changes to the horizontal or
1820 // vertical start value.
1821 // BH 2019.07.27 was:
1822 // if (eventName.equals(ViewportRanges.STARTRES))
1824 // if (av.getWrapAlignment())
1826 // fastPaintWrapped(scrollX);
1830 // fastPaint(scrollX, 0);
1833 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1836 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1838 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1840 // if (av.getWrapAlignment())
1842 // fastPaintWrapped(scrollX);
1846 // fastPaint(scrollX, 0);
1852 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1855 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1857 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1859 // if (av.getWrapAlignment())
1861 // fastPaintWrapped(scrollX);
1867 * Does a minimal update of the image for a scroll movement. This method
1868 * handles scroll movements of up to one width of the wrapped alignment (one
1869 * click in the vertical scrollbar). Larger movements (for example after a
1870 * scroll to highlight a mapped position) trigger a full redraw instead.
1873 * number of positions scrolled (right if positive, left if negative)
1875 protected void fastPaintWrapped(int scrollX)
1877 ViewportRanges ranges = av.getRanges();
1879 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1882 * shift of one view width or more is
1883 * overcomplicated to handle in this method
1890 if (fastpainting || img == null)
1896 fastpainting = true;
1901 Graphics gg = img.getGraphics();
1903 calculateWrappedGeometry();
1906 * relocate the regions of the alignment that are still visible
1908 shiftWrappedAlignment(-scrollX);
1911 * add new columns (sequence, annotation)
1912 * - at top left if scrollX < 0
1913 * - at right of last two widths if scrollX > 0
1917 int startRes = ranges.getStartRes();
1918 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1919 startRes - scrollX - 1, getHeight());
1923 fastPaintWrappedAddRight(scrollX);
1927 * draw all scales (if shown) and hidden column markers
1929 drawWrappedDecorators(gg, ranges.getStartRes());
1936 fastpainting = false;
1941 * Draws the specified number of columns at the 'end' (bottom right) of a
1942 * wrapped alignment view, including sequences and annotations if shown, but
1943 * not scales. Also draws the same number of columns at the right hand end of
1944 * the second last width shown, if the last width is not full height (so
1945 * cannot simply be copied from the graphics image).
1949 protected void fastPaintWrappedAddRight(int columns)
1956 Graphics gg = img.getGraphics();
1958 ViewportRanges ranges = av.getRanges();
1959 int viewportWidth = ranges.getViewportWidth();
1960 int charWidth = av.getCharWidth();
1963 * draw full height alignment in the second last row, last columns, if the
1964 * last row was not full height
1966 int visibleWidths = wrappedVisibleWidths;
1967 int canvasHeight = getHeight();
1968 boolean lastWidthPartHeight = (wrappedVisibleWidths
1969 * wrappedRepeatHeightPx) > canvasHeight;
1971 if (lastWidthPartHeight)
1973 int widthsAbove = Math.max(0, visibleWidths - 2);
1974 int ypos = wrappedRepeatHeightPx * widthsAbove
1975 + wrappedSpaceAboveAlignment;
1976 int endRes = ranges.getEndRes();
1977 endRes += widthsAbove * viewportWidth;
1978 int startRes = endRes - columns;
1979 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1983 * white fill first to erase annotations
1986 gg.translate(xOffset, 0);
1987 gg.setColor(Color.white);
1988 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1989 wrappedRepeatHeightPx);
1990 gg.translate(-xOffset, 0);
1992 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1997 * draw newly visible columns in last wrapped width (none if we
1998 * have reached the end of the alignment)
1999 * y-offset for drawing last width is height of widths above,
2002 int widthsAbove = visibleWidths - 1;
2003 int ypos = wrappedRepeatHeightPx * widthsAbove
2004 + wrappedSpaceAboveAlignment;
2005 int endRes = ranges.getEndRes();
2006 endRes += widthsAbove * viewportWidth;
2007 int startRes = endRes - columns + 1;
2010 * white fill first to erase annotations
2012 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
2014 gg.translate(xOffset, 0);
2015 gg.setColor(Color.white);
2016 int width = viewportWidth * charWidth - xOffset;
2017 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2018 gg.translate(-xOffset, 0);
2020 gg.setFont(av.getFont());
2021 gg.setColor(Color.black);
2023 if (startRes < ranges.getVisibleAlignmentWidth())
2025 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2029 * and finally, white fill any space below the visible alignment
2031 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2032 if (heightBelow > 0)
2034 gg.setColor(Color.white);
2035 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2041 * Shifts the visible alignment by the specified number of columns - left if
2042 * negative, right if positive. Copies and moves sequences and annotations (if
2043 * shown). Scales, hidden column markers and any newly visible columns must be
2048 protected void shiftWrappedAlignment(int positions)
2055 Graphics gg = img.getGraphics();
2057 int charWidth = av.getCharWidth();
2059 int canvasHeight = getHeight();
2060 ViewportRanges ranges = av.getRanges();
2061 int viewportWidth = ranges.getViewportWidth();
2062 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2064 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2065 int xMax = ranges.getVisibleAlignmentWidth();
2070 * shift right (after scroll left)
2071 * for each wrapped width (starting with the last), copy (width-positions)
2072 * columns from the left margin to the right margin, and copy positions
2073 * columns from the right margin of the row above (if any) to the
2074 * left margin of the current row
2078 * get y-offset of last wrapped width, first row of sequences
2080 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2081 y += wrappedSpaceAboveAlignment;
2082 int copyFromLeftStart = labelWidthWest;
2083 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2088 * shift 'widthToCopy' residues by 'positions' places to the right
2090 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2091 positions * charWidth, 0);
2095 * copy 'positions' residue from the row above (right hand end)
2096 * to this row's left hand end
2098 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2099 positions * charWidth, heightToCopy, -widthToCopy,
2100 wrappedRepeatHeightPx);
2103 y -= wrappedRepeatHeightPx;
2109 * shift left (after scroll right)
2110 * for each wrapped width (starting with the first), copy (width-positions)
2111 * columns from the right margin to the left margin, and copy positions
2112 * columns from the left margin of the row below (if any) to the
2113 * right margin of the current row
2115 int xpos = av.getRanges().getStartRes();
2116 int y = wrappedSpaceAboveAlignment;
2117 int copyFromRightStart = labelWidthWest - positions * charWidth;
2119 while (y < canvasHeight)
2121 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2122 positions * charWidth, 0);
2123 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2124 && (xpos + viewportWidth <= xMax))
2126 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2127 -positions * charWidth, heightToCopy, widthToCopy,
2128 -wrappedRepeatHeightPx);
2130 y += wrappedRepeatHeightPx;
2131 xpos += viewportWidth;
2138 * Redraws any positions in the search results in the visible region of a
2139 * wrapped alignment. Any highlights are drawn depending on the search results
2140 * set on the Viewport, not the <code>results</code> argument. This allows
2141 * this method to be called either to clear highlights (passing the previous
2142 * search results), or to draw new highlights.
2147 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2149 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2153 int charHeight = av.getCharHeight();
2155 boolean matchFound = false;
2157 calculateWrappedGeometry();
2158 int wrappedWidth = av.getWrappedWidth();
2159 int wrappedHeight = wrappedRepeatHeightPx;
2161 ViewportRanges ranges = av.getRanges();
2162 int canvasHeight = getHeight();
2163 int repeats = canvasHeight / wrappedHeight;
2164 if (canvasHeight / wrappedHeight > 0)
2169 int firstVisibleColumn = ranges.getStartRes();
2170 int lastVisibleColumn = ranges.getStartRes()
2171 + repeats * ranges.getViewportWidth() - 1;
2173 AlignmentI alignment = av.getAlignment();
2174 if (av.hasHiddenColumns())
2176 firstVisibleColumn = alignment.getHiddenColumns()
2177 .visibleToAbsoluteColumn(firstVisibleColumn);
2178 lastVisibleColumn = alignment.getHiddenColumns()
2179 .visibleToAbsoluteColumn(lastVisibleColumn);
2182 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2184 Graphics gg = img.getGraphics();
2186 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2187 .getEndSeq(); seqNo++)
2189 SequenceI seq = alignment.getSequenceAt(seqNo);
2191 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2193 if (visibleResults != null)
2195 for (int i = 0; i < visibleResults.length - 1; i += 2)
2197 int firstMatchedColumn = visibleResults[i];
2198 int lastMatchedColumn = visibleResults[i + 1];
2199 if (firstMatchedColumn <= lastVisibleColumn
2200 && lastMatchedColumn >= firstVisibleColumn)
2203 * found a search results match in the visible region
2205 firstMatchedColumn = Math.max(firstMatchedColumn,
2206 firstVisibleColumn);
2207 lastMatchedColumn = Math.min(lastMatchedColumn,
2211 * draw each mapped position separately (as contiguous positions may
2212 * wrap across lines)
2214 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2216 int displayColumn = mappedPos;
2217 if (av.hasHiddenColumns())
2219 displayColumn = alignment.getHiddenColumns()
2220 .absoluteToVisibleColumn(displayColumn);
2224 * transX: offset from left edge of canvas to residue position
2226 int transX = labelWidthWest
2227 + ((displayColumn - ranges.getStartRes())
2228 % wrappedWidth) * av.getCharWidth();
2231 * transY: offset from top edge of canvas to residue position
2233 int transY = gapHeight;
2234 transY += (displayColumn - ranges.getStartRes())
2235 / wrappedWidth * wrappedHeight;
2236 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2239 * yOffset is from graphics origin to start of visible region
2241 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2242 if (transY < getHeight())
2245 gg.translate(transX, transY);
2246 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2248 gg.translate(-transX, -transY);
2262 * Answers the width in pixels of the left scale labels (0 if not shown)
2266 int getLabelWidthWest()
2268 return labelWidthWest;
2272 * Clears the flag that allows a 'fast paint' on the next repaint, so
2273 * requiring a full repaint
2275 public void setNoFastPaint()