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;
104 // Don't do this! Graphics handles are supposed to be transient
105 // private Graphics2D gg;
108 * Creates a new SeqCanvas object.
112 public SeqCanvas(AlignmentPanel ap)
115 fr = new FeatureRenderer(ap);
116 seqRdr = new SequenceRenderer(av);
117 setLayout(new BorderLayout());
118 PaintRefresher.Register(this, av.getSequenceSetId());
119 setBackground(Color.white);
121 av.getRanges().addPropertyChangeListener(this);
124 public SequenceRenderer getSequenceRenderer()
129 public FeatureRenderer getFeatureRenderer()
135 * Draws the scale above a region of a wrapped alignment, consisting of a
136 * column number every major interval (10 columns).
139 * the graphics context to draw on, positioned at the start (bottom
140 * left) of the line on which to draw any scale marks
142 * start alignment column (0..)
144 * end alignment column (0..)
146 * y offset to draw at
148 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
150 int charHeight = av.getCharHeight();
151 int charWidth = av.getCharWidth();
154 * white fill the scale space (for the fastPaint case)
156 g.setColor(Color.white);
157 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
158 charHeight * 3 / 2 + 2);
159 g.setColor(Color.black);
161 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
163 for (ScaleMark mark : marks)
165 int mpos = mark.column; // (i - startx - 1)
170 String mstring = mark.text;
176 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
180 * draw a tick mark below the column number, centred on the column;
181 * height of tick mark is 4 pixels less than half a character
183 int xpos = (mpos * charWidth) + (charWidth / 2);
184 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
190 * Draw the scale to the left or right of a wrapped alignment
193 * graphics context, positioned at the start of the scale to be drawn
195 * first column of wrapped width (0.. excluding any hidden columns)
197 * last column of wrapped width (0.. excluding any hidden columns)
199 * vertical offset at which to begin the scale
201 * if true, scale is left of residues, if false, scale is right
203 void drawVerticalScale(Graphics g, final int startx, final int endx,
204 final int ypos, final boolean left)
206 int charHeight = av.getCharHeight();
207 int charWidth = av.getCharWidth();
209 int yPos = ypos + charHeight;
213 if (av.hasHiddenColumns())
215 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
216 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
217 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
219 FontMetrics fm = getFontMetrics(av.getFont());
221 for (int i = 0; i < av.getAlignment().getHeight(); i++)
223 SequenceI seq = av.getAlignment().getSequenceAt(i);
226 * find sequence position of first non-gapped position -
227 * to the right if scale left, to the left if scale right
229 int index = left ? startX : endX;
231 while (index >= startX && index <= endX)
233 if (!Comparison.isGap(seq.getCharAt(index)))
235 value = seq.findPosition(index);
249 * white fill the space for the scale
251 g.setColor(Color.white);
252 int y = (yPos + (i * charHeight)) - (charHeight / 5);
253 // fillRect origin is top left of rectangle
254 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
260 * draw scale value, right justified within its width less half a
261 * character width padding on the right
263 int labelSpace = left ? labelWidthWest : labelWidthEast;
264 labelSpace -= charWidth / 2; // leave space to the right
265 String valueAsString = String.valueOf(value);
266 int labelLength = fm.stringWidth(valueAsString);
267 int xOffset = labelSpace - labelLength;
268 g.setColor(Color.black);
269 g.drawString(valueAsString, xOffset, y);
276 * Does a fast paint of an alignment in response to a scroll. Most of the
277 * visible region is simply copied and shifted, and then any newly visible
278 * columns or rows are drawn. The scroll may be horizontal or vertical, but
279 * not both at once. Scrolling may be the result of
281 * <li>dragging a scroll bar</li>
282 * <li>clicking in the scroll bar</li>
283 * <li>scrolling by trackpad, middle mouse button, or other device</li>
284 * <li>by moving the box in the Overview window</li>
285 * <li>programmatically to make a highlighted position visible</li>
286 * <li>pasting a block of sequences</li>
290 * columns to shift right (positive) or left (negative)
292 * rows to shift down (positive) or up (negative)
294 public void fastPaint(int horizontal, int vertical)
298 // if (horizontal != 0 && vertical != 0)
299 // throw new InvalidArgumentException();
300 if (fastpainting || img == null)
308 int charHeight = av.getCharHeight();
309 int charWidth = av.getCharWidth();
311 ViewportRanges ranges = av.getRanges();
312 int startRes = ranges.getStartRes();
313 int endRes = ranges.getEndRes();
314 int startSeq = ranges.getStartSeq();
315 int endSeq = ranges.getEndSeq();
319 if (horizontal > 0) // scrollbar pulled right, image to the left
321 transX = (endRes - startRes - horizontal) * charWidth;
322 startRes = endRes - horizontal;
324 else if (horizontal < 0)
326 endRes = startRes - horizontal;
329 if (vertical > 0) // scroll down
331 startSeq = endSeq - vertical;
333 if (startSeq < ranges.getStartSeq())
334 { // ie scrolling too fast, more than a page at a time
335 startSeq = ranges.getStartSeq();
339 transY = img.getHeight() - ((vertical + 1) * charHeight);
342 else if (vertical < 0)
344 endSeq = startSeq - vertical;
346 if (endSeq > ranges.getEndSeq())
348 endSeq = ranges.getEndSeq();
352 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
353 // + horizontal + " " + vertical + " " + startRes + " " + endRes
354 // + " " + startSeq + " " + endSeq);
356 Graphics gg = img.getGraphics();
357 gg.copyArea(horizontal * charWidth, vertical * charHeight,
358 img.getWidth(), img.getHeight(), -horizontal * charWidth,
359 -vertical * charHeight);
360 gg.translate(transX, transY);
361 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
362 gg.translate(-transX, -transY);
365 // Call repaint on alignment panel so that repaints from other alignment
366 // panel components can be aggregated. Otherwise performance of the
367 // overview window and others may be adversely affected.
368 av.getAlignPanel().repaint();
371 fastpainting = false;
376 public void paintComponent(Graphics g)
378 if (av.getAlignPanel().getHoldRepaint())
385 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;
418 if (allowFastPaint && img != null
419 && (fastPaint || (vis = getVisibleRect()).width != (clip = g.getClipBounds()).width
420 || vis.height != clip.height))
422 g.drawImage(img, 0, 0, this);
423 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
429 allowFastPaint = true;
430 // img is a cached version of the last view we drew.
431 // If we have no img or the size has changed, make a new one.
433 if (img == null || availWidth != img.getWidth()
434 || availHeight != img.getHeight())
436 img = new BufferedImage(availWidth, availHeight,
437 BufferedImage.TYPE_INT_RGB);
440 Graphics2D gg = (Graphics2D) img.getGraphics();
441 gg.setFont(av.getFont());
445 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
446 RenderingHints.VALUE_ANTIALIAS_ON);
449 gg.setColor(Color.white);
450 gg.fillRect(0, 0, availWidth, availHeight);
452 if (av.getWrapAlignment())
454 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
458 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
461 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
463 g.drawImage(img, 0, 0, this);
469 drawCursor(g, startRes, endRes, startSeq, endSeq);
474 * Draw an alignment panel for printing
477 * Graphics object to draw with
479 * start residue of print area
481 * end residue of print area
483 * start sequence of print area
485 * end sequence of print area
487 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
488 int startSeq, int endSeq)
490 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
492 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
496 * Draw a wrapped alignment panel for printing
499 * Graphics object to draw with
501 * width of drawing area
502 * @param canvasHeight
503 * height of drawing area
505 * start residue of print area
507 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
508 int canvasHeight, int startRes)
510 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
512 SequenceGroup group = av.getSelectionGroup();
515 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
521 * Using the current font, determine fields labelWidthEast and labelWidthWest,
522 * and return the number of residues that can fill the remaining width
525 * the width in pixels (possibly including scales)
527 * @return the visible width in residues, after allowing for East or West
531 public int getWrappedCanvasWidth(int w)
533 int charWidth = av.getCharWidth();
535 FontMetrics fm = getFontMetrics(av.getFont());
537 int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
541 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
543 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
545 return (w - labelWidthEast - labelWidthWest) / charWidth;
549 * Returns a pixel width sufficient to show the largest sequence coordinate
550 * (end position) in the alignment, calculated as the FontMetrics width of
551 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
552 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
553 * half a character width space on either side.
558 protected int getLabelWidth(FontMetrics fm)
561 * find the biggest sequence end position we need to show
562 * (note this is not necessarily the sequence length)
565 AlignmentI alignment = av.getAlignment();
566 for (int i = 0; i < alignment.getHeight(); i++)
568 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
573 for (int i = maxWidth; i > 0; i /= 10)
578 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
582 * Draws as many widths of a wrapped alignment as can fit in the visible
587 * available width in pixels
589 * available height in pixels
591 * the first column (0...) of the alignment to draw
593 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
594 final int startColumn)
596 int wrappedWidthInResidues = calculateWrappedGeometry();
597 av.setWrappedWidth(wrappedWidthInResidues);
598 ViewportRanges ranges = av.getRanges();
599 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
601 // we need to call this again to make sure the startColumn +
602 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
604 calculateWrappedGeometry();
607 * draw one width at a time (excluding any scales shown),
608 * until we have run out of either alignment or vertical space available
610 int ypos = wrappedSpaceAboveAlignment;
611 int maxWidth = ranges.getVisibleAlignmentWidth();
613 int start = startColumn;
614 int currentWidth = 0;
615 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
617 int endColumn = Math.min(maxWidth,
618 start + wrappedWidthInResidues - 1);
619 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
620 ypos += wrappedRepeatHeightPx;
621 start += wrappedWidthInResidues;
625 drawWrappedDecorators(g, startColumn);
628 private void getAvailSizes()
630 int charHeight = av.getCharHeight();
631 int charWidth = av.getCharWidth();
632 availWidth = getWidth();
633 availHeight = getHeight();
634 availWidth -= (availWidth % charWidth);
635 availHeight -= (availHeight % charHeight);
639 * Calculates and saves values needed when rendering a wrapped alignment.
640 * These depend on many factors, including
642 * <li>canvas width and height</li>
643 * <li>number of visible sequences, and height of annotations if shown</li>
644 * <li>font and character width</li>
645 * <li>whether scales are shown left, right or above the alignment</li>
650 * @return the number of residue columns in each width
652 protected int calculateWrappedGeometry()
655 return calculateWrappedGeometry(availWidth, availHeight);
662 * @param canvasHeight
665 public int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
668 int charHeight = av.getCharHeight();
671 * vertical space in pixels between wrapped widths of alignment
672 * - one character height, or two if scale above is drawn
674 wrappedSpaceAboveAlignment = charHeight
675 * (av.getScaleAboveWrapped() ? 2 : 1);
678 * compute height in pixels of the wrapped widths
679 * - start with space above plus sequences
681 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment
682 + av.getAlignment().getHeight() * charHeight;
685 * add annotations panel height if shown
686 * also gap between sequences and annotations
688 if (av.isShowAnnotation())
690 wrappedRepeatHeightPx += getAnnotationHeight();
691 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
695 * number of visible widths (the last one may be part height),
696 * ensuring a part height includes at least one sequence
698 ViewportRanges ranges = av.getRanges();
699 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
700 int remainder = canvasHeight % wrappedRepeatHeightPx;
701 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
703 wrappedVisibleWidths++;
707 * compute width in residues; this also sets East and West label widths
709 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
712 * limit visibleWidths to not exceed width of alignment
714 int xMax = ranges.getVisibleAlignmentWidth();
715 int startToEnd = xMax - ranges.getStartRes();
716 int maxWidths = startToEnd / wrappedWidthInResidues;
717 if (startToEnd % wrappedWidthInResidues > 0)
721 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
723 return wrappedWidthInResidues;
727 * Draws one width of a wrapped alignment, including sequences and
728 * annnotations, if shown, but not scales or hidden column markers
734 * @param canvasHeight
736 protected void drawWrappedWidth(Graphics g, final int ypos,
737 final int startColumn, final int endColumn,
738 final int canvasHeight)
740 ViewportRanges ranges = av.getRanges();
741 int viewportWidth = ranges.getViewportWidth();
743 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
746 * move right before drawing by the width of the scale left (if any)
747 * plus column offset from left margin (usually zero, but may be non-zero
748 * when fast painting is drawing just a few columns)
750 int charWidth = av.getCharWidth();
751 int xOffset = labelWidthWest
752 + ((startColumn - ranges.getStartRes()) % viewportWidth)
755 g.translate(xOffset, 0);
758 * white fill the region to be drawn (so incremental fast paint doesn't
759 * scribble over an existing image)
761 g.setColor(Color.white);
762 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
763 wrappedRepeatHeightPx);
765 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
768 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
770 if (av.isShowAnnotation())
772 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
773 g.translate(0, yShift);
774 if (annotations == null)
776 annotations = new AnnotationPanel(av);
779 annotations.renderer.drawComponent(annotations, av, g, -1,
780 startColumn, endx + 1);
781 g.translate(0, -yShift);
783 g.translate(-xOffset, 0);
787 * Draws scales left, right and above (if shown), and any hidden column
788 * markers, on all widths of the wrapped alignment
793 protected void drawWrappedDecorators(Graphics g, final int startColumn)
795 int charWidth = av.getCharWidth();
797 g.setFont(av.getFont());
799 g.setColor(Color.black);
801 int ypos = wrappedSpaceAboveAlignment;
802 ViewportRanges ranges = av.getRanges();
803 int viewportWidth = ranges.getViewportWidth();
804 int maxWidth = ranges.getVisibleAlignmentWidth();
806 int startCol = startColumn;
808 while (widthsDrawn < wrappedVisibleWidths)
810 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
812 if (av.getScaleLeftWrapped())
814 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
817 if (av.getScaleRightWrapped())
819 int x = labelWidthWest + viewportWidth * charWidth;
822 drawVerticalScale(g, startCol, endColumn, ypos, false);
827 * white fill region of scale above and hidden column markers
828 * (to support incremental fast paint of image)
830 g.translate(labelWidthWest, 0);
831 g.setColor(Color.white);
832 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
833 viewportWidth * charWidth + labelWidthWest,
834 wrappedSpaceAboveAlignment);
835 g.setColor(Color.black);
836 g.translate(-labelWidthWest, 0);
838 g.translate(labelWidthWest, 0);
840 if (av.getScaleAboveWrapped())
842 drawNorthScale(g, startCol, endColumn, ypos);
845 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
847 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
850 g.translate(-labelWidthWest, 0);
852 ypos += wrappedRepeatHeightPx;
853 startCol += viewportWidth;
859 * Draws markers (triangles) above hidden column positions between startColumn
867 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
868 int startColumn, int endColumn)
870 int charHeight = av.getCharHeight();
871 int charWidth = av.getCharWidth();
873 g.setColor(Color.blue);
875 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
877 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
881 res = it.next() - startColumn;
883 if (res < 0 || res > endColumn - startColumn + 1)
889 * draw a downward-pointing triangle at the hidden columns location
890 * (before the following visible column)
892 int xMiddle = res * charWidth;
893 int[] xPoints = new int[] { xMiddle - charHeight / 4,
894 xMiddle + charHeight / 4, xMiddle };
895 int yTop = ypos - (charHeight / 2);
896 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
897 g.fillPolygon(xPoints, yPoints, 3);
901 private final static BasicStroke dottedStroke = new BasicStroke(1,
902 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
905 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();
924 // JAL-3253-applet had this:
925 // // height gap above each panel
926 // int charHeight = av.getCharHeight();
927 // int hgap = charHeight;
928 // if (av.getScaleAboveWrapped())
930 // hgap += charHeight;
932 // int dy = getAnnotationHeight() + hgap
933 // + av.getAlignment().getHeight() * charHeight;
934 // int ypos = hgap; // vertical offset
936 // this is from 0b573ed (gmungoc)
937 int dy = wrappedRepeatHeightPx;
938 int ypos = wrappedSpaceAboveAlignment;
940 while ((ypos <= canvasHeight) && (startx < maxwidth))
942 // set end value to be start + width, or maxwidth, whichever is smaller
943 int endx = startx + cWidth - 1;
950 g.translate(labelWidthWest, 0);
952 drawUnwrappedSelection(g, group, startx, endx, 0,
953 av.getAlignment().getHeight() - 1, ypos);
955 g.translate(-labelWidthWest, 0);
957 // update vertical offset
960 // update horizontal offset
963 g.setStroke(basicStroke);
967 * Answers zero if annotations are not shown, otherwise recalculates and
968 * answers the total height of all annotation rows in pixels
972 int getAnnotationHeight()
974 if (!av.isShowAnnotation())
979 if (annotations == null)
981 annotations = new AnnotationPanel(av);
984 return annotations.adjustPanelHeight();
988 * Draws the visible region of the alignment on the graphics context. If there
989 * are hidden column markers in the visible region, then each sub-region
990 * between the markers is drawn separately, followed by the hidden column
994 * the graphics context, positioned at the first residue to be drawn
996 * offset of the first column to draw (0..)
998 * offset of the last column to draw (0..)
1000 * offset of the first sequence to draw (0..)
1002 * offset of the last sequence to draw (0..)
1004 * vertical offset at which to draw (for wrapped alignments)
1006 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1007 final int startSeq, final int endSeq, final int yOffset)
1009 int charHeight = av.getCharHeight();
1010 int charWidth = av.getCharWidth();
1012 if (!av.hasHiddenColumns())
1014 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1022 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1023 VisibleContigsIterator regions = hidden
1024 .getVisContigsIterator(startRes, endRes + 1, true);
1026 while (regions.hasNext())
1028 int[] region = regions.next();
1029 blockEnd = region[1];
1030 blockStart = region[0];
1033 * draw up to just before the next hidden region, or the end of
1034 * the visible region, whichever comes first
1036 g1.translate(screenY * charWidth, 0);
1038 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1041 * draw the downline of the hidden column marker (ScalePanel draws the
1042 * triangle on top) if we reached it
1044 if (av.getShowHiddenMarkers()
1045 && (regions.hasNext() || regions.endsAtHidden()))
1047 g1.setColor(Color.blue);
1049 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1050 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1051 (endSeq - startSeq + 1) * charHeight + yOffset);
1054 g1.translate(-screenY * charWidth, 0);
1055 screenY += blockEnd - blockStart + 1;
1062 * Draws a region of the visible alignment
1066 * offset of the first column in the visible region (0..)
1068 * offset of the last column in the visible region (0..)
1070 * offset of the first sequence in the visible region (0..)
1072 * offset of the last sequence in the visible region (0..)
1074 * vertical offset at which to draw (for wrapped alignments)
1076 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1077 int endSeq, int offset)
1079 int charHeight = av.getCharHeight();
1080 int charWidth = av.getCharWidth();
1082 g.setFont(av.getFont());
1083 seqRdr.prepare(g, av.isRenderGaps());
1087 // / First draw the sequences
1088 // ///////////////////////////
1089 for (int i = startSeq; i <= endSeq; i++)
1091 nextSeq = av.getAlignment().getSequenceAt(i);
1092 if (nextSeq == null)
1094 // occasionally, a race condition occurs such that the alignment row is
1098 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1099 startRes, endRes, offset + ((i - startSeq) * charHeight));
1101 if (av.isShowSequenceFeatures())
1103 fr.drawSequence(g, nextSeq, startRes, endRes,
1104 offset + ((i - startSeq) * charHeight), false);
1108 * highlight search Results once sequence has been drawn
1110 if (av.hasSearchResults())
1112 SearchResultsI searchResults = av.getSearchResults();
1113 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1115 if (visibleResults != null)
1117 for (int r = 0; r < visibleResults.length; r += 2)
1119 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1120 visibleResults[r + 1],
1121 (visibleResults[r] - startRes) * charWidth,
1122 offset + ((i - startSeq) * charHeight));
1128 if (av.getSelectionGroup() != null
1129 || av.getAlignment().getGroups().size() > 0)
1131 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1137 * Draws the outlines of any groups defined on the alignment (excluding the
1138 * current selection group, if any)
1147 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1148 int startSeq, int endSeq, int offset)
1150 Graphics2D g = (Graphics2D) g1;
1152 SequenceGroup group = null;
1153 int groupIndex = -1;
1155 if (av.getAlignment().getGroups().size() > 0)
1157 group = av.getAlignment().getGroups().get(0);
1165 g.setColor(group.getOutlineColour());
1166 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1170 if (groupIndex >= av.getAlignment().getGroups().size())
1174 group = av.getAlignment().getGroups().get(groupIndex);
1175 } while (groupIndex < av.getAlignment().getGroups().size());
1180 * Draws the outline of the current selection group (if any)
1188 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1189 int startSeq, int endSeq)
1191 SequenceGroup group = av.getSelectionGroup();
1197 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1198 BasicStroke.JOIN_ROUND, 3f, new float[]
1200 g.setColor(Color.RED);
1201 if (!av.getWrapAlignment())
1203 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1208 drawWrappedSelection(g, group, getWidth(), getHeight(),
1209 av.getRanges().getStartRes());
1211 g.setStroke(new BasicStroke());
1215 * Draw the cursor as a separate image and overlay
1218 * start residue of area to draw cursor in
1220 * end residue of area to draw cursor in
1222 * start sequence of area to draw cursor in
1224 * end sequence of are to draw cursor in
1225 * @return a transparent image of the same size as the sequence canvas, with
1226 * the cursor drawn on it, if any
1228 private void drawCursor(Graphics g, int startRes, int endRes,
1229 int startSeq, int endSeq)
1231 // convert the cursorY into a position on the visible alignment
1232 int cursor_ypos = cursorY;
1234 // don't do work unless we have to
1235 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1239 int startx = startRes;
1242 // convert the cursorX into a position on the visible alignment
1243 int cursor_xpos = av.getAlignment().getHiddenColumns()
1244 .absoluteToVisibleColumn(cursorX);
1246 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1249 if (av.getWrapAlignment())
1251 // work out the correct offsets for the cursor
1252 int charHeight = av.getCharHeight();
1253 int charWidth = av.getCharWidth();
1254 int canvasWidth = getWidth();
1255 int canvasHeight = getHeight();
1257 // height gap above each panel
1258 int hgap = charHeight;
1259 if (av.getScaleAboveWrapped())
1264 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1266 int cHeight = av.getAlignment().getHeight() * charHeight;
1268 endx = startx + cWidth - 1;
1269 int ypos = hgap; // vertical offset
1271 // iterate down the wrapped panels
1272 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1274 // update vertical offset
1275 ypos += cHeight + getAnnotationHeight() + hgap;
1277 // update horizontal offset
1279 endx = startx + cWidth - 1;
1282 xoffset = labelWidthWest;
1285 // now check if cursor is within range for x values
1286 if (cursor_xpos >= startx && cursor_xpos <= endx)
1288 // get the character the cursor is drawn at
1289 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1290 char s = seq.getCharAt(cursorX);
1292 seqRdr.drawCursor(g, s,
1293 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1294 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1301 * Draw a selection group over an unwrapped alignment
1304 * graphics object to draw with
1308 * start residue of area to draw
1310 * end residue of area to draw
1312 * start sequence of area to draw
1314 * end sequence of area to draw
1316 * vertical offset (used when called from wrapped alignment code)
1318 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1319 int startRes, int endRes, int startSeq, int endSeq, int offset)
1321 int charWidth = av.getCharWidth();
1323 if (!av.hasHiddenColumns())
1325 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1330 // package into blocks of visible columns
1335 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1336 VisibleContigsIterator regions = hidden
1337 .getVisContigsIterator(startRes, endRes + 1, true);
1338 while (regions.hasNext())
1340 int[] region = regions.next();
1341 blockEnd = region[1];
1342 blockStart = region[0];
1344 g.translate(screenY * charWidth, 0);
1345 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1348 g.translate(-screenY * charWidth, 0);
1349 screenY += blockEnd - blockStart + 1;
1355 * Draws part of a selection group outline
1363 * @param verticalOffset
1365 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1366 int startRes, int endRes, int startSeq, int endSeq,
1369 int charHeight = av.getCharHeight();
1370 int charWidth = av.getCharWidth();
1371 int visWidth = (endRes - startRes + 1) * charWidth;
1375 boolean inGroup = false;
1380 List<SequenceI> seqs = group.getSequences(null);
1382 // position of start residue of group relative to startRes, in pixels
1383 int sx = (group.getStartRes() - startRes) * charWidth;
1385 // width of group in pixels
1386 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1389 if (!(sx + xwidth < 0 || sx > visWidth))
1391 for (i = startSeq; i <= endSeq; i++)
1393 sy = verticalOffset + (i - startSeq) * charHeight;
1395 if ((sx <= (endRes - startRes) * charWidth)
1396 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1399 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1401 bottom = sy + charHeight;
1406 if (((top == -1) && (i == 0)) || !seqs
1407 .contains(av.getAlignment().getSequenceAt(i - 1)))
1418 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1419 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1421 // reset top and bottom
1429 sy = verticalOffset + ((i - startSeq) * charHeight);
1430 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1431 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1437 * Draw horizontal selection group boundaries at top and bottom positions
1440 * graphics object to draw on
1446 * visWidth maximum available width
1448 * position to draw top of group at
1450 * position to draw bottom of group at
1452 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1453 int visWidth, int top, int bottom)
1463 // don't let width extend beyond current block, or group extent
1465 if (startx + width >= visWidth)
1467 width = visWidth - startx;
1472 g.drawLine(startx, top, startx + width, top);
1477 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1482 * Draw vertical lines at sx and sx+xwidth providing they lie within
1486 * graphics object to draw on
1492 * visWidth maximum available width
1498 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1501 // if start position is visible, draw vertical line to left of
1503 if (sx >= 0 && sx < visWidth)
1505 g.drawLine(sx, oldY, sx, sy);
1508 // if end position is visible, draw vertical line to right of
1510 if (sx + xwidth < visWidth)
1512 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1517 * Highlights search results in the visible region by rendering as white text
1518 * on a black background. Any previous highlighting is removed. Answers true
1519 * if any highlight was left on the visible alignment (so status bar should be
1520 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1521 * so allows the next repaint to update the whole display.
1526 public boolean highlightSearchResults(SearchResultsI results)
1528 return highlightSearchResults(results, false);
1533 * Highlights search results in the visible region by rendering as white text
1534 * on a black background. Any previous highlighting is removed. Answers true
1535 * if any highlight was left on the visible alignment (so status bar should be
1536 * set to match), else false.
1538 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1539 * highlighted regions are modified. This speeds up highlighting across linked
1542 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1543 * a wrapped alignment had to be scrolled to show the highlighted region, then
1544 * it should be fully redrawn, otherwise a fast paint can be performed. This
1545 * argument could be removed if fast paint of scrolled wrapped alignment is
1546 * coded in future (JAL-2609).
1549 * @param doFastPaint
1550 * if true, sets a flag so the next repaint only redraws the modified
1554 public boolean highlightSearchResults(SearchResultsI results,
1555 boolean doFastPaint)
1561 boolean wrapped = av.getWrapAlignment();
1564 fastPaint = doFastPaint;
1565 fastpainting = fastPaint;
1568 * to avoid redrawing the whole visible region, we instead
1569 * redraw just the minimal regions to remove previous highlights
1572 SearchResultsI previous = av.getSearchResults();
1573 av.setSearchResults(results);
1574 boolean redrawn = false;
1575 boolean drawn = false;
1578 redrawn = drawMappedPositionsWrapped(previous);
1579 drawn = drawMappedPositionsWrapped(results);
1584 redrawn = drawMappedPositions(previous);
1585 drawn = drawMappedPositions(results);
1590 * if highlights were either removed or added, repaint
1598 * return true only if highlights were added
1604 fastpainting = false;
1609 * Redraws the minimal rectangle in the visible region (if any) that includes
1610 * mapped positions of the given search results. Whether or not positions are
1611 * highlighted depends on the SearchResults set on the Viewport. This allows
1612 * this method to be called to either clear or set highlighting. Answers true
1613 * if any positions were drawn (in which case a repaint is still required),
1619 protected boolean drawMappedPositions(SearchResultsI results)
1621 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1627 * calculate the minimal rectangle to redraw that
1628 * includes both new and existing search results
1630 int firstSeq = Integer.MAX_VALUE;
1632 int firstCol = Integer.MAX_VALUE;
1634 boolean matchFound = false;
1636 ViewportRanges ranges = av.getRanges();
1637 int firstVisibleColumn = ranges.getStartRes();
1638 int lastVisibleColumn = ranges.getEndRes();
1639 AlignmentI alignment = av.getAlignment();
1640 if (av.hasHiddenColumns())
1642 firstVisibleColumn = alignment.getHiddenColumns()
1643 .visibleToAbsoluteColumn(firstVisibleColumn);
1644 lastVisibleColumn = alignment.getHiddenColumns()
1645 .visibleToAbsoluteColumn(lastVisibleColumn);
1648 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1649 .getEndSeq(); seqNo++)
1651 SequenceI seq = alignment.getSequenceAt(seqNo);
1653 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1655 if (visibleResults != null)
1657 for (int i = 0; i < visibleResults.length - 1; i += 2)
1659 int firstMatchedColumn = visibleResults[i];
1660 int lastMatchedColumn = visibleResults[i + 1];
1661 if (firstMatchedColumn <= lastVisibleColumn
1662 && lastMatchedColumn >= firstVisibleColumn)
1665 * found a search results match in the visible region -
1666 * remember the first and last sequence matched, and the first
1667 * and last visible columns in the matched positions
1670 firstSeq = Math.min(firstSeq, seqNo);
1671 lastSeq = Math.max(lastSeq, seqNo);
1672 firstMatchedColumn = Math.max(firstMatchedColumn,
1673 firstVisibleColumn);
1674 lastMatchedColumn = Math.min(lastMatchedColumn,
1676 firstCol = Math.min(firstCol, firstMatchedColumn);
1677 lastCol = Math.max(lastCol, lastMatchedColumn);
1685 if (av.hasHiddenColumns())
1687 firstCol = alignment.getHiddenColumns()
1688 .absoluteToVisibleColumn(firstCol);
1689 lastCol = alignment.getHiddenColumns()
1690 .absoluteToVisibleColumn(lastCol);
1692 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1693 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1694 Graphics gg = img.getGraphics();
1695 gg.translate(transX, transY);
1696 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1697 gg.translate(-transX, -transY);
1705 public void propertyChange(PropertyChangeEvent evt)
1707 String eventName = evt.getPropertyName();
1709 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1710 // logic, emphasizing no check for ENDRES or ENDSEQ
1712 // Both scrolling and resizing change viewport ranges: scrolling changes
1713 // both start and end points, but resize only changes end values.
1714 // Here we only want to fastpaint on a scroll, with resize using a normal
1715 // paint, so scroll events are identified as changes to the horizontal or
1716 // vertical start value.
1718 // Make sure we're not trying to draw a panel
1719 // larger than the visible window
1724 case SequenceGroup.SEQ_GROUP_CHANGED:
1728 case ViewportRanges.MOVE_VIEWPORT:
1732 case ViewportRanges.STARTSEQ:
1733 // meaning STARTOREND
1734 // typically scroll, but possibly just the end changed
1735 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1737 case ViewportRanges.STARTRES:
1738 // meaning STARTOREND
1739 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1741 case ViewportRanges.STARTRESANDSEQ:
1742 scrollX = ((int[]) evt.getNewValue())[0]
1743 - ((int[]) evt.getOldValue())[0];
1744 scrollY = ((int[]) evt.getNewValue())[1]
1745 - ((int[]) evt.getOldValue())[1];
1746 if (scrollX != 0 && scrollY != 0)
1748 // all sorts of problems in JavaScript if this is commented out.
1758 ViewportRanges vpRanges = av.getRanges();
1759 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1760 scrollX = Math.max(Math.min(scrollX, range), -range);
1761 // only STARTRES or STARTRESANDSEQ:
1762 if (av.getWrapAlignment())
1764 fastPaintWrapped(scrollX);
1768 fastPaint(scrollX, scrollY);
1771 // BH 2019.07.27 was:
1772 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1774 // fastPaint = true;
1778 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1780 // fastPaint = false;
1781 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1786 // if (eventName.equals(ViewportRanges.STARTRES)
1787 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1789 // // Make sure we're not trying to draw a panel
1790 // // larger than the visible window
1791 // if (eventName.equals(ViewportRanges.STARTRES))
1793 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1797 // scrollX = ((int[]) evt.getNewValue())[0]
1798 // - ((int[]) evt.getOldValue())[0];
1800 // ViewportRanges vpRanges = av.getRanges();
1802 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1803 // if (scrollX > range)
1807 // else if (scrollX < -range)
1809 // scrollX = -range;
1812 // Both scrolling and resizing change viewport ranges: scrolling changes
1813 // both start and end points, but resize only changes end values.
1814 // Here we only want to fastpaint on a scroll, with resize using a normal
1815 // paint, so scroll events are identified as changes to the horizontal or
1816 // vertical start value.
1817 // BH 2019.07.27 was:
1818 // if (eventName.equals(ViewportRanges.STARTRES))
1820 // if (av.getWrapAlignment())
1822 // fastPaintWrapped(scrollX);
1826 // fastPaint(scrollX, 0);
1829 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1832 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1834 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1836 // if (av.getWrapAlignment())
1838 // fastPaintWrapped(scrollX);
1842 // fastPaint(scrollX, 0);
1848 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1851 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1853 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1855 // if (av.getWrapAlignment())
1857 // fastPaintWrapped(scrollX);
1863 * Does a minimal update of the image for a scroll movement. This method
1864 * handles scroll movements of up to one width of the wrapped alignment (one
1865 * click in the vertical scrollbar). Larger movements (for example after a
1866 * scroll to highlight a mapped position) trigger a full redraw instead.
1869 * number of positions scrolled (right if positive, left if negative)
1871 protected void fastPaintWrapped(int scrollX)
1873 ViewportRanges ranges = av.getRanges();
1875 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1878 * shift of one view width or more is
1879 * overcomplicated to handle in this method
1886 if (fastpainting || img == null)
1892 fastpainting = true;
1897 Graphics gg = img.getGraphics();
1899 calculateWrappedGeometry();
1902 * relocate the regions of the alignment that are still visible
1904 shiftWrappedAlignment(-scrollX);
1907 * add new columns (sequence, annotation)
1908 * - at top left if scrollX < 0
1909 * - at right of last two widths if scrollX > 0
1913 int startRes = ranges.getStartRes();
1914 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1915 startRes - scrollX - 1, getHeight());
1919 fastPaintWrappedAddRight(scrollX);
1923 * draw all scales (if shown) and hidden column markers
1925 drawWrappedDecorators(gg, ranges.getStartRes());
1932 fastpainting = false;
1937 * Draws the specified number of columns at the 'end' (bottom right) of a
1938 * wrapped alignment view, including sequences and annotations if shown, but
1939 * not scales. Also draws the same number of columns at the right hand end of
1940 * the second last width shown, if the last width is not full height (so
1941 * cannot simply be copied from the graphics image).
1945 protected void fastPaintWrappedAddRight(int columns)
1952 Graphics gg = img.getGraphics();
1954 ViewportRanges ranges = av.getRanges();
1955 int viewportWidth = ranges.getViewportWidth();
1956 int charWidth = av.getCharWidth();
1959 * draw full height alignment in the second last row, last columns, if the
1960 * last row was not full height
1962 int visibleWidths = wrappedVisibleWidths;
1963 int canvasHeight = getHeight();
1964 boolean lastWidthPartHeight = (wrappedVisibleWidths
1965 * wrappedRepeatHeightPx) > canvasHeight;
1967 if (lastWidthPartHeight)
1969 int widthsAbove = Math.max(0, visibleWidths - 2);
1970 int ypos = wrappedRepeatHeightPx * widthsAbove
1971 + wrappedSpaceAboveAlignment;
1972 int endRes = ranges.getEndRes();
1973 endRes += widthsAbove * viewportWidth;
1974 int startRes = endRes - columns;
1975 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1979 * white fill first to erase annotations
1982 gg.translate(xOffset, 0);
1983 gg.setColor(Color.white);
1984 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1985 wrappedRepeatHeightPx);
1986 gg.translate(-xOffset, 0);
1988 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1993 * draw newly visible columns in last wrapped width (none if we
1994 * have reached the end of the alignment)
1995 * y-offset for drawing last width is height of widths above,
1998 int widthsAbove = visibleWidths - 1;
1999 int ypos = wrappedRepeatHeightPx * widthsAbove
2000 + wrappedSpaceAboveAlignment;
2001 int endRes = ranges.getEndRes();
2002 endRes += widthsAbove * viewportWidth;
2003 int startRes = endRes - columns + 1;
2006 * white fill first to erase annotations
2008 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
2010 gg.translate(xOffset, 0);
2011 gg.setColor(Color.white);
2012 int width = viewportWidth * charWidth - xOffset;
2013 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2014 gg.translate(-xOffset, 0);
2016 gg.setFont(av.getFont());
2017 gg.setColor(Color.black);
2019 if (startRes < ranges.getVisibleAlignmentWidth())
2021 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2025 * and finally, white fill any space below the visible alignment
2027 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2028 if (heightBelow > 0)
2030 gg.setColor(Color.white);
2031 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2037 * Shifts the visible alignment by the specified number of columns - left if
2038 * negative, right if positive. Copies and moves sequences and annotations (if
2039 * shown). Scales, hidden column markers and any newly visible columns must be
2044 protected void shiftWrappedAlignment(int positions)
2051 Graphics gg = img.getGraphics();
2053 int charWidth = av.getCharWidth();
2055 int canvasHeight = getHeight();
2056 ViewportRanges ranges = av.getRanges();
2057 int viewportWidth = ranges.getViewportWidth();
2058 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2060 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2061 int xMax = ranges.getVisibleAlignmentWidth();
2066 * shift right (after scroll left)
2067 * for each wrapped width (starting with the last), copy (width-positions)
2068 * columns from the left margin to the right margin, and copy positions
2069 * columns from the right margin of the row above (if any) to the
2070 * left margin of the current row
2074 * get y-offset of last wrapped width, first row of sequences
2076 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2077 y += wrappedSpaceAboveAlignment;
2078 int copyFromLeftStart = labelWidthWest;
2079 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2084 * shift 'widthToCopy' residues by 'positions' places to the right
2086 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2087 positions * charWidth, 0);
2091 * copy 'positions' residue from the row above (right hand end)
2092 * to this row's left hand end
2094 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2095 positions * charWidth, heightToCopy, -widthToCopy,
2096 wrappedRepeatHeightPx);
2099 y -= wrappedRepeatHeightPx;
2105 * shift left (after scroll right)
2106 * for each wrapped width (starting with the first), copy (width-positions)
2107 * columns from the right margin to the left margin, and copy positions
2108 * columns from the left margin of the row below (if any) to the
2109 * right margin of the current row
2111 int xpos = av.getRanges().getStartRes();
2112 int y = wrappedSpaceAboveAlignment;
2113 int copyFromRightStart = labelWidthWest - positions * charWidth;
2115 while (y < canvasHeight)
2117 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2118 positions * charWidth, 0);
2119 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2120 && (xpos + viewportWidth <= xMax))
2122 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2123 -positions * charWidth, heightToCopy, widthToCopy,
2124 -wrappedRepeatHeightPx);
2126 y += wrappedRepeatHeightPx;
2127 xpos += viewportWidth;
2134 * Redraws any positions in the search results in the visible region of a
2135 * wrapped alignment. Any highlights are drawn depending on the search results
2136 * set on the Viewport, not the <code>results</code> argument. This allows
2137 * this method to be called either to clear highlights (passing the previous
2138 * search results), or to draw new highlights.
2143 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2145 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2149 int charHeight = av.getCharHeight();
2151 boolean matchFound = false;
2153 calculateWrappedGeometry();
2154 int wrappedWidth = av.getWrappedWidth();
2155 int wrappedHeight = wrappedRepeatHeightPx;
2157 ViewportRanges ranges = av.getRanges();
2158 int canvasHeight = getHeight();
2159 int repeats = canvasHeight / wrappedHeight;
2160 if (canvasHeight / wrappedHeight > 0)
2165 int firstVisibleColumn = ranges.getStartRes();
2166 int lastVisibleColumn = ranges.getStartRes()
2167 + repeats * ranges.getViewportWidth() - 1;
2169 AlignmentI alignment = av.getAlignment();
2170 if (av.hasHiddenColumns())
2172 firstVisibleColumn = alignment.getHiddenColumns()
2173 .visibleToAbsoluteColumn(firstVisibleColumn);
2174 lastVisibleColumn = alignment.getHiddenColumns()
2175 .visibleToAbsoluteColumn(lastVisibleColumn);
2178 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2180 Graphics gg = img.getGraphics();
2182 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2183 .getEndSeq(); seqNo++)
2185 SequenceI seq = alignment.getSequenceAt(seqNo);
2187 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2189 if (visibleResults != null)
2191 for (int i = 0; i < visibleResults.length - 1; i += 2)
2193 int firstMatchedColumn = visibleResults[i];
2194 int lastMatchedColumn = visibleResults[i + 1];
2195 if (firstMatchedColumn <= lastVisibleColumn
2196 && lastMatchedColumn >= firstVisibleColumn)
2199 * found a search results match in the visible region
2201 firstMatchedColumn = Math.max(firstMatchedColumn,
2202 firstVisibleColumn);
2203 lastMatchedColumn = Math.min(lastMatchedColumn,
2207 * draw each mapped position separately (as contiguous positions may
2208 * wrap across lines)
2210 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2212 int displayColumn = mappedPos;
2213 if (av.hasHiddenColumns())
2215 displayColumn = alignment.getHiddenColumns()
2216 .absoluteToVisibleColumn(displayColumn);
2220 * transX: offset from left edge of canvas to residue position
2222 int transX = labelWidthWest
2223 + ((displayColumn - ranges.getStartRes())
2224 % wrappedWidth) * av.getCharWidth();
2227 * transY: offset from top edge of canvas to residue position
2229 int transY = gapHeight;
2230 transY += (displayColumn - ranges.getStartRes())
2231 / wrappedWidth * wrappedHeight;
2232 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2235 * yOffset is from graphics origin to start of visible region
2237 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2238 if (transY < getHeight())
2241 gg.translate(transX, transY);
2242 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2244 gg.translate(-transX, -transY);
2258 * Answers the width in pixels of the left scale labels (0 if not shown)
2262 int getLabelWidthWest()
2264 return labelWidthWest;
2268 * Clears the flag that allows a 'fast paint' on the next repaint, so
2269 * requiring a full repaint
2271 public void setNoFastPaint()
2273 allowFastPaint = false;