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 java.awt.BasicStroke;
24 import java.awt.BorderLayout;
25 import java.awt.Color;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.Rectangle;
30 import java.awt.RenderingHints;
31 import java.awt.image.BufferedImage;
32 import java.beans.PropertyChangeEvent;
33 import java.util.Iterator;
34 import java.util.List;
36 import javax.swing.JPanel;
38 import jalview.api.SequenceRenderer;
39 import jalview.datamodel.AlignmentI;
40 import jalview.datamodel.HiddenColumns;
41 import jalview.datamodel.SearchResultsI;
42 import jalview.datamodel.SequenceGroup;
43 import jalview.datamodel.SequenceI;
44 import jalview.datamodel.VisibleContigsIterator;
45 import jalview.renderer.ScaleRenderer;
46 import jalview.renderer.ScaleRenderer.ScaleMark;
47 import jalview.util.Comparison;
48 import jalview.viewmodel.ViewportListenerI;
49 import jalview.viewmodel.ViewportRanges;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 @SuppressWarnings("serial")
58 public class SeqCanvas extends JPanel implements ViewportListenerI
61 * vertical gap in pixels between sequences and annotations when in wrapped
64 static final int SEQS_ANNOTATION_GAP = 3;
66 private static final String ZEROS = "0000000000";
68 final FeatureRenderer fr;
78 private final SequenceRenderer seqRdr;
80 boolean fastPaint = false;
82 private boolean fastpainting = false;
84 private AnnotationPanel annotations;
87 * measurements for drawing a wrapped alignment
89 private int labelWidthEast; // label right width in pixels if shown
91 private int labelWidthWest; // label left width in pixels if shown
93 int wrappedSpaceAboveAlignment; // gap between widths
95 int wrappedRepeatHeightPx; // height in pixels of wrapped width
97 private int wrappedVisibleWidths; // number of wrapped widths displayed
99 // Don't do this! Graphics handles are supposed to be transient
100 // private Graphics2D gg;
103 * Creates a new SeqCanvas object.
107 public SeqCanvas(AlignmentPanel ap)
110 fr = new FeatureRenderer(ap);
111 seqRdr = new jalview.gui.SequenceRenderer(av);
112 setLayout(new BorderLayout());
113 PaintRefresher.Register(this, av.getSequenceSetId());
114 setBackground(Color.white);
116 av.getRanges().addPropertyChangeListener(this);
119 public SequenceRenderer getSequenceRenderer()
124 public FeatureRenderer getFeatureRenderer()
130 * Draws the scale above a region of a wrapped alignment, consisting of a
131 * column number every major interval (10 columns).
134 * the graphics context to draw on, positioned at the start (bottom
135 * left) of the line on which to draw any scale marks
137 * start alignment column (0..)
139 * end alignment column (0..)
141 * y offset to draw at
143 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
145 int charHeight = av.getCharHeight();
146 int charWidth = av.getCharWidth();
149 * white fill the scale space (for the fastPaint case)
151 g.setColor(Color.white);
152 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
153 charHeight * 3 / 2 + 2);
154 g.setColor(Color.black);
156 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
158 for (ScaleMark mark : marks)
160 int mpos = mark.column; // (i - startx - 1)
165 String mstring = mark.text;
171 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
175 * draw a tick mark below the column number, centred on the column;
176 * height of tick mark is 4 pixels less than half a character
178 int xpos = (mpos * charWidth) + (charWidth / 2);
179 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
185 * Draw the scale to the left or right of a wrapped alignment
188 * graphics context, positioned at the start of the scale to be drawn
190 * first column of wrapped width (0.. excluding any hidden columns)
192 * last column of wrapped width (0.. excluding any hidden columns)
194 * vertical offset at which to begin the scale
196 * if true, scale is left of residues, if false, scale is right
198 void drawVerticalScale(Graphics g, final int startx, final int endx,
199 final int ypos, final boolean left)
201 int charHeight = av.getCharHeight();
202 int charWidth = av.getCharWidth();
204 int yPos = ypos + charHeight;
208 if (av.hasHiddenColumns())
210 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
211 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
212 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
214 FontMetrics fm = getFontMetrics(av.getFont());
216 for (int i = 0; i < av.getAlignment().getHeight(); i++)
218 SequenceI seq = av.getAlignment().getSequenceAt(i);
221 * find sequence position of first non-gapped position -
222 * to the right if scale left, to the left if scale right
224 int index = left ? startX : endX;
226 while (index >= startX && index <= endX)
228 if (!Comparison.isGap(seq.getCharAt(index)))
230 value = seq.findPosition(index);
244 * white fill the space for the scale
246 g.setColor(Color.white);
247 int y = (yPos + (i * charHeight)) - (charHeight / 5);
248 // fillRect origin is top left of rectangle
249 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
255 * draw scale value, right justified within its width less half a
256 * character width padding on the right
258 int labelSpace = left ? labelWidthWest : labelWidthEast;
259 labelSpace -= charWidth / 2; // leave space to the right
260 String valueAsString = String.valueOf(value);
261 int labelLength = fm.stringWidth(valueAsString);
262 int xOffset = labelSpace - labelLength;
263 g.setColor(Color.black);
264 g.drawString(valueAsString, xOffset, y);
271 * Does a fast paint of an alignment in response to a scroll. Most of the
272 * visible region is simply copied and shifted, and then any newly visible
273 * columns or rows are drawn. The scroll may be horizontal or vertical, but
274 * not both at once. Scrolling may be the result of
276 * <li>dragging a scroll bar</li>
277 * <li>clicking in the scroll bar</li>
278 * <li>scrolling by trackpad, middle mouse button, or other device</li>
279 * <li>by moving the box in the Overview window</li>
280 * <li>programmatically to make a highlighted position visible</li>
281 * <li>pasting a block of sequences</li>
285 * columns to shift right (positive) or left (negative)
287 * rows to shift down (positive) or up (negative)
289 public void fastPaint(int horizontal, int vertical)
293 // if (horizontal != 0 && vertical != 0)
294 // throw new InvalidArgumentException();
295 if (fastpainting || img == null)
303 int charHeight = av.getCharHeight();
304 int charWidth = av.getCharWidth();
306 ViewportRanges ranges = av.getRanges();
307 int startRes = ranges.getStartRes();
308 int endRes = ranges.getEndRes();
309 int startSeq = ranges.getStartSeq();
310 int endSeq = ranges.getEndSeq();
314 if (horizontal > 0) // scrollbar pulled right, image to the left
316 transX = (endRes - startRes - horizontal) * charWidth;
317 startRes = endRes - horizontal;
319 else if (horizontal < 0)
321 endRes = startRes - horizontal;
324 if (vertical > 0) // scroll down
326 startSeq = endSeq - vertical + 1;
328 if (startSeq < ranges.getStartSeq())
329 { // ie scrolling too fast, more than a page at a time
330 startSeq = ranges.getStartSeq();
334 transY = img.getHeight() - (vertical * charHeight);
337 else if (vertical < 0) // scroll up
339 endSeq = startSeq - vertical - 1;
341 if (endSeq > ranges.getEndSeq())
343 endSeq = ranges.getEndSeq();
347 // jalview.bin.Console.errPrintln(">>> FastPaint to " + transX + " " +
349 // + horizontal + " " + vertical + " " + startRes + " " + endRes
350 // + " " + startSeq + " " + endSeq);
352 Graphics gg = img.getGraphics();
353 gg.copyArea(horizontal * charWidth, vertical * charHeight,
354 img.getWidth(), img.getHeight(), -horizontal * charWidth,
355 -vertical * charHeight);
357 /** @j2sNative xxi = this.img */
359 gg.translate(transX, transY);
360 drawPanel(seqRdr, gg, startRes, endRes, startSeq, endSeq, 0);
361 gg.translate(-transX, -transY);
364 // Call repaint on alignment panel so that repaints from other alignment
365 // panel components can be aggregated. Otherwise performance of the
366 // overview window and others may be adversely affected.
367 // jalview.bin.Console.outPrintln("SeqCanvas fastPaint() repaint()
369 av.getAlignPanel().repaint();
372 fastpainting = false;
377 public void paintComponent(Graphics g)
380 int charHeight = av.getCharHeight();
381 int charWidth = av.getCharWidth();
383 int width = getWidth();
384 int height = getHeight();
386 width -= (width % charWidth);
387 height -= (height % charHeight);
389 // BH 2019 can't possibly fastPaint if either width or height is 0
391 if (width == 0 || height == 0)
396 ViewportRanges ranges = av.getRanges();
397 int startRes = ranges.getStartRes();
398 int startSeq = ranges.getStartSeq();
399 int endRes = ranges.getEndRes();
400 int endSeq = ranges.getEndSeq();
402 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
403 // repaint() requests in unpredictable ways. In this case, the issue was
404 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
405 // repaint request preceded two full requests, thus resulting
406 // in a full request for paint. In constrast, in JavaScript, the three
407 // requests were bundled together into one, so the fastPaint flag was
408 // still present for the second and third request.
410 // This resulted in incomplete painting.
412 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
413 // in PaintRefresher when the target to be painted is one of those two
418 // An initial idea; can be removed once we determine this issue is closed:
419 // if (av.isFastPaintDisabled())
421 // fastPaint = false;
427 || (vis = getVisibleRect()).width != (clip = g
428 .getClipBounds()).width
429 || vis.height != clip.height))
431 g.drawImage(img, 0, 0, this);
432 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
438 // img is a cached version of the last view we drew.
439 // If we have no img or the size has changed, make a new one.
441 if (img == null || width != img.getWidth()
442 || height != img.getHeight())
444 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
447 Graphics2D gg = (Graphics2D) img.getGraphics();
448 gg.setFont(av.getFont());
452 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
453 RenderingHints.VALUE_ANTIALIAS_ON);
456 gg.setColor(Color.white);
457 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
459 if (av.getWrapAlignment())
461 drawWrappedPanel(seqRdr, gg, getWidth(), getHeight(),
462 ranges.getStartRes());
466 drawPanel(seqRdr, gg, startRes, endRes, startSeq, endSeq, 0);
469 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
471 g.drawImage(img, 0, 0, this);
477 drawCursor(g, startRes, endRes, startSeq, endSeq);
482 * Draw an alignment panel for printing
485 * Graphics object to draw with
487 * start residue of print area
489 * end residue of print area
491 * start sequence of print area
493 * end sequence of print area
495 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
496 int startSeq, int endSeq)
498 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
499 drawPanel(localSeqR, g1, startRes, endRes, startSeq, endSeq, 0);
501 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
505 * Draw a wrapped alignment panel for printing
508 * Graphics object to draw with
510 * width of drawing area
511 * @param canvasHeight
512 * height of drawing area
514 * start residue of print area
516 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
517 int canvasHeight, int startRes)
519 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
520 drawWrappedPanel(localSeqR, g, canvasWidth, canvasHeight, startRes);
522 SequenceGroup group = av.getSelectionGroup();
525 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
531 * Returns the visible width of the canvas in residues, after allowing for
532 * East or West scales (if shown)
535 * the width in pixels (possibly including scales)
539 public int getWrappedCanvasWidth(int canvasWidth)
541 int charWidth = av.getCharWidth();
543 FontMetrics fm = getFontMetrics(av.getFont());
547 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
549 labelWidth = getLabelWidth(fm);
552 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
554 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
556 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
559 public int getMinimumWrappedCanvasWidth()
561 int charWidth = av.getCharWidth();
562 FontMetrics fm = getFontMetrics(av.getFont());
564 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
566 labelWidth = getLabelWidth(fm);
568 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
569 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
570 return labelWidthEast + labelWidthWest + charWidth;
574 * Returns a pixel width sufficient to show the largest sequence coordinate
575 * (end position) in the alignment, calculated as the FontMetrics width of
576 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
577 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
578 * half a character width space on either side.
583 protected int getLabelWidth(FontMetrics fm)
586 * find the biggest sequence end position we need to show
587 * (note this is not necessarily the sequence length)
590 AlignmentI alignment = av.getAlignment();
591 for (int i = 0; i < alignment.getHeight(); i++)
593 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
597 for (int i = maxWidth; i > 0; i /= 10)
602 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
606 * Draws as many widths of a wrapped alignment as can fit in the visible
611 * available width in pixels
612 * @param canvasHeight
613 * available height in pixels
615 * the first column (0...) of the alignment to draw
617 public void drawWrappedPanel(SequenceRenderer seqRdr, Graphics g,
618 int canvasWidth, int canvasHeight, final int startColumn)
620 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
623 av.setWrappedWidth(wrappedWidthInResidues);
625 ViewportRanges ranges = av.getRanges();
626 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
628 // we need to call this again to make sure the startColumn +
629 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
631 calculateWrappedGeometry(canvasWidth, canvasHeight);
634 * draw one width at a time (excluding any scales shown),
635 * until we have run out of either alignment or vertical space available
637 int ypos = wrappedSpaceAboveAlignment;
638 int maxWidth = ranges.getVisibleAlignmentWidth();
640 int start = startColumn;
641 int currentWidth = 0;
642 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
644 int endColumn = Math.min(maxWidth,
645 start + wrappedWidthInResidues - 1);
646 drawWrappedWidth(seqRdr, g, ypos, start, endColumn, canvasHeight);
647 ypos += wrappedRepeatHeightPx;
648 start += wrappedWidthInResidues;
652 drawWrappedDecorators(g, startColumn);
656 * Calculates and saves values needed when rendering a wrapped alignment.
657 * These depend on many factors, including
659 * <li>canvas width and height</li>
660 * <li>number of visible sequences, and height of annotations if shown</li>
661 * <li>font and character width</li>
662 * <li>whether scales are shown left, right or above the alignment</li>
666 * @param canvasHeight
667 * @return the number of residue columns in each width
669 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
671 int charHeight = av.getCharHeight();
674 * vertical space in pixels between wrapped widths of alignment
675 * - one character height, or two if scale above is drawn
677 wrappedSpaceAboveAlignment = charHeight
678 * (av.getScaleAboveWrapped() ? 2 : 1);
681 * compute height in pixels of the wrapped widths
682 * - start with space above plus sequences
684 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
685 wrappedRepeatHeightPx += av.getAlignment().getHeight() * charHeight;
688 * add annotations panel height if shown
689 * also gap between sequences and annotations
691 if (av.isShowAnnotation())
693 wrappedRepeatHeightPx += getAnnotationHeight();
694 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
698 * number of visible widths (the last one may be part height),
699 * ensuring a part height includes at least one sequence
701 ViewportRanges ranges = av.getRanges();
702 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
703 int remainder = canvasHeight % wrappedRepeatHeightPx;
704 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
706 wrappedVisibleWidths++;
710 * compute width in residues; this also sets East and West label widths
712 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
713 av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
715 * limit visibleWidths to not exceed width of alignment
717 int xMax = ranges.getVisibleAlignmentWidth();
718 int startToEnd = xMax - ranges.getStartRes();
719 int maxWidths = startToEnd / wrappedWidthInResidues;
720 if (startToEnd % wrappedWidthInResidues > 0)
724 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
726 return wrappedWidthInResidues;
730 * Draws one width of a wrapped alignment, including sequences and
731 * annnotations, if shown, but not scales or hidden column markers
737 * @param canvasHeight
739 protected void drawWrappedWidth(SequenceRenderer seqRdr, Graphics g,
740 final int ypos, final int startColumn, final int endColumn,
741 final int canvasHeight)
743 ViewportRanges ranges = av.getRanges();
744 int viewportWidth = ranges.getViewportWidth();
746 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
749 * move right before drawing by the width of the scale left (if any)
750 * plus column offset from left margin (usually zero, but may be non-zero
751 * when fast painting is drawing just a few columns)
753 int charWidth = av.getCharWidth();
754 int xOffset = labelWidthWest
755 + ((startColumn - ranges.getStartRes()) % viewportWidth)
758 g.translate(xOffset, 0);
761 * white fill the region to be drawn (so incremental fast paint doesn't
762 * scribble over an existing image)
764 g.setColor(Color.white);
765 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
766 wrappedRepeatHeightPx);
768 drawPanel(seqRdr, g, startColumn, endx, 0,
769 av.getAlignment().getHeight() - 1, ypos);
771 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
773 if (av.isShowAnnotation())
775 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
776 g.translate(0, yShift);
777 if (annotations == null)
779 annotations = new AnnotationPanel(av);
782 annotations.renderer.drawComponent(annotations, av, g, -1,
783 startColumn, endx + 1);
784 g.translate(0, -yShift);
786 g.translate(-xOffset, 0);
790 * Draws scales left, right and above (if shown), and any hidden column
791 * markers, on all widths of the wrapped alignment
796 protected void drawWrappedDecorators(Graphics g, final int startColumn)
798 int charWidth = av.getCharWidth();
800 g.setFont(av.getFont());
802 g.setColor(Color.black);
804 int ypos = wrappedSpaceAboveAlignment;
805 ViewportRanges ranges = av.getRanges();
806 int viewportWidth = ranges.getViewportWidth();
807 int maxWidth = ranges.getVisibleAlignmentWidth();
809 int startCol = startColumn;
811 while (widthsDrawn < wrappedVisibleWidths)
813 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
815 if (av.getScaleLeftWrapped())
817 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
820 if (av.getScaleRightWrapped())
822 int x = labelWidthWest + viewportWidth * charWidth;
825 drawVerticalScale(g, startCol, endColumn, ypos, false);
830 * white fill region of scale above and hidden column markers
831 * (to support incremental fast paint of image)
833 g.translate(labelWidthWest, 0);
834 g.setColor(Color.white);
835 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
836 viewportWidth * charWidth + labelWidthWest,
837 wrappedSpaceAboveAlignment);
838 g.setColor(Color.black);
839 g.translate(-labelWidthWest, 0);
841 g.translate(labelWidthWest, 0);
843 if (av.getScaleAboveWrapped())
845 drawNorthScale(g, startCol, endColumn, ypos);
848 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
850 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
853 g.translate(-labelWidthWest, 0);
855 ypos += wrappedRepeatHeightPx;
856 startCol += viewportWidth;
862 * Draws markers (triangles) above hidden column positions between startColumn
870 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
871 int startColumn, int endColumn)
873 int charHeight = av.getCharHeight();
874 int charWidth = av.getCharWidth();
876 g.setColor(Color.blue);
878 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
880 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
884 res = it.next() - startColumn;
886 if (res < 0 || res > endColumn - startColumn + 1)
892 * draw a downward-pointing triangle at the hidden columns location
893 * (before the following visible column)
895 int xMiddle = res * charWidth;
896 int[] xPoints = new int[] { xMiddle - charHeight / 4,
897 xMiddle + charHeight / 4, xMiddle };
898 int yTop = ypos - (charHeight / 2);
899 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
900 g.fillPolygon(xPoints, yPoints, 3);
905 * Draw a selection group over a wrapped alignment
907 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
908 int canvasWidth, int canvasHeight, int startRes)
910 // chop the wrapped alignment extent up into panel-sized blocks and treat
911 // each block as if it were a block from an unwrapped alignment
912 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
913 BasicStroke.JOIN_ROUND, 3f, new float[]
915 g.setColor(Color.RED);
917 int charWidth = av.getCharWidth();
918 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
920 int startx = startRes;
921 int maxwidth = av.getAlignment().getVisibleWidth();
922 int ypos = wrappedSpaceAboveAlignment;
924 while ((ypos <= canvasHeight) && (startx < maxwidth))
926 // set end value to be start + width, or maxwidth, whichever is smaller
927 int endx = startx + cWidth - 1;
934 g.translate(labelWidthWest, 0);
935 drawUnwrappedSelection(g, group, startx, endx, 0,
936 av.getAlignment().getHeight() - 1, ypos);
937 g.translate(-labelWidthWest, 0);
939 ypos += wrappedRepeatHeightPx;
943 g.setStroke(new BasicStroke());
947 * Answers zero if annotations are not shown, otherwise recalculates and
948 * answers the total height of all annotation rows in pixels
952 int getAnnotationHeight()
954 if (!av.isShowAnnotation())
959 if (annotations == null)
961 annotations = new AnnotationPanel(av);
964 return annotations.adjustPanelHeight();
968 * Draws the visible region of the alignment on the graphics context. If there
969 * are hidden column markers in the visible region, then each sub-region
970 * between the markers is drawn separately, followed by the hidden column
974 * - sequence renderer implementation - when null, uses the one used
975 * for rendering interactive GUI
978 * the graphics context, positioned at the first residue to be drawn
980 * offset of the first column to draw (0..)
982 * offset of the last column to draw (0..)
984 * offset of the first sequence to draw (0..)
986 * offset of the last sequence to draw (0..)
988 * vertical offset at which to draw (for wrapped alignments)
990 public void drawPanel(SequenceRenderer localSeqR, Graphics g1,
991 final int startRes, final int endRes, final int startSeq,
992 final int endSeq, final int yOffset)
994 int charHeight = av.getCharHeight();
995 int charWidth = av.getCharWidth();
997 if (localSeqR == null)
1001 if (!av.hasHiddenColumns())
1003 draw(localSeqR, g1, startRes, endRes, startSeq, endSeq, yOffset);
1011 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1012 VisibleContigsIterator regions = hidden
1013 .getVisContigsIterator(startRes, endRes + 1, true);
1015 while (regions.hasNext())
1017 int[] region = regions.next();
1018 blockEnd = region[1];
1019 blockStart = region[0];
1022 * draw up to just before the next hidden region, or the end of
1023 * the visible region, whichever comes first
1025 g1.translate(screenY * charWidth, 0);
1027 draw(localSeqR, g1, blockStart, blockEnd, startSeq, endSeq,
1031 * draw the downline of the hidden column marker (ScalePanel draws the
1032 * triangle on top) if we reached it
1034 if (av.getShowHiddenMarkers()
1035 && (regions.hasNext() || regions.endsAtHidden()))
1037 g1.setColor(Color.blue);
1039 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1040 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1041 (endSeq - startSeq + 1) * charHeight + yOffset);
1044 g1.translate(-screenY * charWidth, 0);
1045 screenY += blockEnd - blockStart + 1;
1052 * Draws a region of the visible alignment
1058 * offset of the first column in the visible region (0..)
1060 * offset of the last column in the visible region (0..)
1062 * offset of the first sequence in the visible region (0..)
1064 * offset of the last sequence in the visible region (0..)
1066 * vertical offset at which to draw (for wrapped alignments)
1068 private void draw(SequenceRenderer seqRdr, Graphics g, int startRes,
1069 int endRes, int startSeq, int endSeq, int offset)
1071 int charHeight = av.getCharHeight();
1072 int charWidth = av.getCharWidth();
1074 g.setFont(av.getFont());
1075 seqRdr.prepare(g, av.isRenderGaps());
1079 // / First draw the sequences
1080 // ///////////////////////////
1081 for (int i = startSeq; i <= endSeq; i++)
1083 nextSeq = av.getAlignment().getSequenceAt(i);
1084 if (nextSeq == null)
1086 // occasionally, a race condition occurs such that the alignment row is
1090 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1091 startRes, endRes, offset + ((i - startSeq) * charHeight));
1093 if (av.isShowSequenceFeatures())
1095 fr.drawSequence(g, nextSeq, startRes, endRes,
1096 offset + ((i - startSeq) * charHeight), false);
1100 * highlight search Results once sequence has been drawn
1102 if (av.hasSearchResults())
1104 SearchResultsI searchResults = av.getSearchResults();
1105 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1107 if (visibleResults != null)
1109 for (int r = 0; r < visibleResults.length; r += 2)
1111 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1112 visibleResults[r + 1],
1113 (visibleResults[r] - startRes) * charWidth,
1114 offset + ((i - startSeq) * charHeight));
1120 if (av.getSelectionGroup() != null
1121 || av.getAlignment().getGroups().size() > 0)
1123 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1129 * Draws the outlines of any groups defined on the alignment (excluding the
1130 * current selection group, if any)
1139 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1140 int startSeq, int endSeq, int offset)
1142 Graphics2D g = (Graphics2D) g1;
1144 SequenceGroup group = null;
1145 int groupIndex = -1;
1147 if (av.getAlignment().getGroups().size() > 0)
1149 group = av.getAlignment().getGroups().get(0);
1157 g.setColor(group.getOutlineColour());
1158 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1162 if (groupIndex >= av.getAlignment().getGroups().size())
1166 group = av.getAlignment().getGroups().get(groupIndex);
1167 } while (groupIndex < av.getAlignment().getGroups().size());
1172 * Draws the outline of the current selection group (if any)
1180 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1181 int startSeq, int endSeq)
1183 SequenceGroup group = av.getSelectionGroup();
1189 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1190 BasicStroke.JOIN_ROUND, 3f, new float[]
1192 g.setColor(Color.RED);
1193 if (!av.getWrapAlignment())
1195 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1200 drawWrappedSelection(g, group, getWidth(), getHeight(),
1201 av.getRanges().getStartRes());
1203 g.setStroke(new BasicStroke());
1207 * Draw the cursor as a separate image and overlay
1210 * start residue of area to draw cursor in
1212 * end residue of area to draw cursor in
1214 * start sequence of area to draw cursor in
1216 * end sequence of are to draw cursor in
1217 * @return a transparent image of the same size as the sequence canvas, with
1218 * the cursor drawn on it, if any
1220 private void drawCursor(Graphics g, int startRes, int endRes,
1221 int startSeq, int endSeq)
1223 // convert the cursorY into a position on the visible alignment
1224 int cursor_ypos = cursorY;
1226 // don't do work unless we have to
1227 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1231 int startx = startRes;
1234 // convert the cursorX into a position on the visible alignment
1235 int cursor_xpos = av.getAlignment().getHiddenColumns()
1236 .absoluteToVisibleColumn(cursorX);
1238 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1241 if (av.getWrapAlignment())
1243 // work out the correct offsets for the cursor
1244 int charHeight = av.getCharHeight();
1245 int charWidth = av.getCharWidth();
1246 int canvasWidth = getWidth();
1247 int canvasHeight = getHeight();
1249 // height gap above each panel
1250 int hgap = charHeight;
1251 if (av.getScaleAboveWrapped())
1256 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1258 int cHeight = av.getAlignment().getHeight() * charHeight;
1260 endx = startx + cWidth - 1;
1261 int ypos = hgap; // vertical offset
1263 // iterate down the wrapped panels
1264 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1266 // update vertical offset
1267 ypos += cHeight + getAnnotationHeight() + hgap;
1269 // update horizontal offset
1271 endx = startx + cWidth - 1;
1274 xoffset = labelWidthWest;
1277 // now check if cursor is within range for x values
1278 if (cursor_xpos >= startx && cursor_xpos <= endx)
1280 // get the character the cursor is drawn at
1281 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1282 char s = seq.getCharAt(cursorX);
1284 seqRdr.drawCursor(g, s,
1285 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1286 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1293 * Draw a selection group over an unwrapped alignment
1296 * graphics object to draw with
1300 * start residue of area to draw
1302 * end residue of area to draw
1304 * start sequence of area to draw
1306 * end sequence of area to draw
1308 * vertical offset (used when called from wrapped alignment code)
1310 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1311 int startRes, int endRes, int startSeq, int endSeq, int offset)
1313 int charWidth = av.getCharWidth();
1315 if (!av.hasHiddenColumns())
1317 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1322 // package into blocks of visible columns
1327 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1328 VisibleContigsIterator regions = hidden
1329 .getVisContigsIterator(startRes, endRes + 1, true);
1330 while (regions.hasNext())
1332 int[] region = regions.next();
1333 blockEnd = region[1];
1334 blockStart = region[0];
1336 g.translate(screenY * charWidth, 0);
1337 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1340 g.translate(-screenY * charWidth, 0);
1341 screenY += blockEnd - blockStart + 1;
1347 * Draws part of a selection group outline
1355 * @param verticalOffset
1357 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1358 int startRes, int endRes, int startSeq, int endSeq,
1361 int charHeight = av.getCharHeight();
1362 int charWidth = av.getCharWidth();
1363 int visWidth = (endRes - startRes + 1) * charWidth;
1367 boolean inGroup = false;
1372 List<SequenceI> seqs = group.getSequences(null);
1374 // position of start residue of group relative to startRes, in pixels
1375 int sx = (group.getStartRes() - startRes) * charWidth;
1377 // width of group in pixels
1378 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1381 if (!(sx + xwidth < 0 || sx > visWidth))
1383 for (i = startSeq; i <= endSeq; i++)
1385 sy = verticalOffset + (i - startSeq) * charHeight;
1387 if ((sx <= (endRes - startRes) * charWidth)
1388 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1391 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1393 bottom = sy + charHeight;
1398 if (((top == -1) && (i == 0)) || !seqs
1399 .contains(av.getAlignment().getSequenceAt(i - 1)))
1410 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1411 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1413 // reset top and bottom
1421 sy = verticalOffset + ((i - startSeq) * charHeight);
1422 drawVerticals(g, sx, xwidth, visWidth, oldY,
1423 bottom == -1 ? sy : bottom);
1424 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1430 * Draw horizontal selection group boundaries at top and bottom positions
1433 * graphics object to draw on
1439 * visWidth maximum available width
1441 * position to draw top of group at
1443 * position to draw bottom of group at
1445 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1446 int visWidth, int top, int bottom)
1456 // don't let width extend beyond current block, or group extent
1458 if (startx + width >= visWidth)
1460 width = visWidth - startx;
1465 g.drawLine(startx, top, startx + width, top);
1470 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1475 * Draw vertical lines at sx and sx+xwidth providing they lie within
1479 * graphics object to draw on
1485 * visWidth maximum available width
1491 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1494 // if start position is visible, draw vertical line to left of
1496 if (sx >= 0 && sx < visWidth)
1498 g.drawLine(sx, oldY, sx, sy - 1);
1501 // if end position is visible, draw vertical line to right of
1503 if (sx + xwidth < visWidth)
1505 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy - 1);
1510 * Highlights search results in the visible region by rendering as white text
1511 * on a black background. Any previous highlighting is removed. Answers true
1512 * if any highlight was left on the visible alignment (so status bar should be
1513 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1514 * so allows the next repaint to update the whole display.
1519 public boolean highlightSearchResults(SearchResultsI results)
1521 return highlightSearchResults(results, false);
1526 * Highlights search results in the visible region by rendering as white text
1527 * on a black background. Any previous highlighting is removed. Answers true
1528 * if any highlight was left on the visible alignment (so status bar should be
1529 * set to match), else false.
1531 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1532 * highlighted regions are modified. This speeds up highlighting across linked
1535 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1536 * a wrapped alignment had to be scrolled to show the highlighted region, then
1537 * it should be fully redrawn, otherwise a fast paint can be performed. This
1538 * argument could be removed if fast paint of scrolled wrapped alignment is
1539 * coded in future (JAL-2609).
1542 * @param doFastPaint
1543 * if true, sets a flag so the next repaint only redraws the modified
1547 public boolean highlightSearchResults(SearchResultsI results,
1548 boolean doFastPaint)
1554 boolean wrapped = av.getWrapAlignment();
1557 fastPaint = doFastPaint;
1558 fastpainting = fastPaint;
1561 * to avoid redrawing the whole visible region, we instead
1562 * redraw just the minimal regions to remove previous highlights
1565 SearchResultsI previous = av.getSearchResults();
1566 av.setSearchResults(results);
1567 boolean redrawn = false;
1568 boolean drawn = false;
1571 redrawn = drawMappedPositionsWrapped(previous);
1572 drawn = drawMappedPositionsWrapped(results);
1577 redrawn = drawMappedPositions(previous);
1578 drawn = drawMappedPositions(results);
1583 * if highlights were either removed or added, repaint
1591 * return true only if highlights were added
1597 fastpainting = false;
1602 * Redraws the minimal rectangle in the visible region (if any) that includes
1603 * mapped positions of the given search results. Whether or not positions are
1604 * highlighted depends on the SearchResults set on the Viewport. This allows
1605 * this method to be called to either clear or set highlighting. Answers true
1606 * if any positions were drawn (in which case a repaint is still required),
1612 protected boolean drawMappedPositions(SearchResultsI results)
1614 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1620 * calculate the minimal rectangle to redraw that
1621 * includes both new and existing search results
1623 int firstSeq = Integer.MAX_VALUE;
1625 int firstCol = Integer.MAX_VALUE;
1627 boolean matchFound = false;
1629 ViewportRanges ranges = av.getRanges();
1630 int firstVisibleColumn = ranges.getStartRes();
1631 int lastVisibleColumn = ranges.getEndRes();
1632 AlignmentI alignment = av.getAlignment();
1633 if (av.hasHiddenColumns())
1635 firstVisibleColumn = alignment.getHiddenColumns()
1636 .visibleToAbsoluteColumn(firstVisibleColumn);
1637 lastVisibleColumn = alignment.getHiddenColumns()
1638 .visibleToAbsoluteColumn(lastVisibleColumn);
1641 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1642 .getEndSeq(); seqNo++)
1644 SequenceI seq = alignment.getSequenceAt(seqNo);
1646 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1648 if (visibleResults != null)
1650 for (int i = 0; i < visibleResults.length - 1; i += 2)
1652 int firstMatchedColumn = visibleResults[i];
1653 int lastMatchedColumn = visibleResults[i + 1];
1654 if (firstMatchedColumn <= lastVisibleColumn
1655 && lastMatchedColumn >= firstVisibleColumn)
1658 * found a search results match in the visible region -
1659 * remember the first and last sequence matched, and the first
1660 * and last visible columns in the matched positions
1663 firstSeq = Math.min(firstSeq, seqNo);
1664 lastSeq = Math.max(lastSeq, seqNo);
1665 firstMatchedColumn = Math.max(firstMatchedColumn,
1666 firstVisibleColumn);
1667 lastMatchedColumn = Math.min(lastMatchedColumn,
1669 firstCol = Math.min(firstCol, firstMatchedColumn);
1670 lastCol = Math.max(lastCol, lastMatchedColumn);
1678 if (av.hasHiddenColumns())
1680 firstCol = alignment.getHiddenColumns()
1681 .absoluteToVisibleColumn(firstCol);
1682 lastCol = alignment.getHiddenColumns()
1683 .absoluteToVisibleColumn(lastCol);
1685 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1686 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1687 Graphics gg = img.getGraphics();
1688 gg.translate(transX, transY);
1689 drawPanel(seqRdr, gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1690 gg.translate(-transX, -transY);
1698 public void propertyChange(PropertyChangeEvent evt)
1700 String eventName = evt.getPropertyName();
1701 // jalview.bin.Console.errPrintln(">>SeqCanvas propertyChange " +
1703 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1709 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1712 // jalview.bin.Console.errPrintln("!!!! fastPaint false from
1719 if (eventName.equals(ViewportRanges.STARTRES)
1720 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1722 // Make sure we're not trying to draw a panel
1723 // larger than the visible window
1724 if (eventName.equals(ViewportRanges.STARTRES))
1726 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1730 scrollX = ((int[]) evt.getNewValue())[0]
1731 - ((int[]) evt.getOldValue())[0];
1733 ViewportRanges vpRanges = av.getRanges();
1735 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1736 if (scrollX > range)
1740 else if (scrollX < -range)
1745 // Both scrolling and resizing change viewport ranges: scrolling changes
1746 // both start and end points, but resize only changes end values.
1747 // Here we only want to fastpaint on a scroll, with resize using a normal
1748 // paint, so scroll events are identified as changes to the horizontal or
1749 // vertical start value.
1750 if (eventName.equals(ViewportRanges.STARTRES))
1752 if (av.getWrapAlignment())
1754 fastPaintWrapped(scrollX);
1758 fastPaint(scrollX, 0);
1761 else if (eventName.equals(ViewportRanges.STARTSEQ))
1764 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1766 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1768 if (av.getWrapAlignment())
1770 fastPaintWrapped(scrollX);
1774 fastPaint(scrollX, 0);
1777 else if (eventName.equals(ViewportRanges.STARTSEQ))
1780 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1782 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1784 if (av.getWrapAlignment())
1786 fastPaintWrapped(scrollX);
1792 * Does a minimal update of the image for a scroll movement. This method
1793 * handles scroll movements of up to one width of the wrapped alignment (one
1794 * click in the vertical scrollbar). Larger movements (for example after a
1795 * scroll to highlight a mapped position) trigger a full redraw instead.
1798 * number of positions scrolled (right if positive, left if negative)
1800 protected void fastPaintWrapped(int scrollX)
1802 ViewportRanges ranges = av.getRanges();
1804 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1807 * shift of one view width or more is
1808 * overcomplicated to handle in this method
1815 if (fastpainting || img == null)
1821 fastpainting = true;
1826 Graphics gg = img.getGraphics();
1828 calculateWrappedGeometry(getWidth(), getHeight());
1831 * relocate the regions of the alignment that are still visible
1833 shiftWrappedAlignment(-scrollX);
1836 * add new columns (sequence, annotation)
1837 * - at top left if scrollX < 0
1838 * - at right of last two widths if scrollX > 0
1842 int startRes = ranges.getStartRes();
1843 drawWrappedWidth(seqRdr, gg, wrappedSpaceAboveAlignment, startRes,
1844 startRes - scrollX - 1, getHeight());
1848 fastPaintWrappedAddRight(scrollX);
1852 * draw all scales (if shown) and hidden column markers
1854 drawWrappedDecorators(gg, ranges.getStartRes());
1861 fastpainting = false;
1866 * Draws the specified number of columns at the 'end' (bottom right) of a
1867 * wrapped alignment view, including sequences and annotations if shown, but
1868 * not scales. Also draws the same number of columns at the right hand end of
1869 * the second last width shown, if the last width is not full height (so
1870 * cannot simply be copied from the graphics image).
1874 protected void fastPaintWrappedAddRight(int columns)
1881 Graphics gg = img.getGraphics();
1883 ViewportRanges ranges = av.getRanges();
1884 int viewportWidth = ranges.getViewportWidth();
1885 int charWidth = av.getCharWidth();
1888 * draw full height alignment in the second last row, last columns, if the
1889 * last row was not full height
1891 int visibleWidths = wrappedVisibleWidths;
1892 int canvasHeight = getHeight();
1893 boolean lastWidthPartHeight = (wrappedVisibleWidths
1894 * wrappedRepeatHeightPx) > canvasHeight;
1896 if (lastWidthPartHeight)
1898 int widthsAbove = Math.max(0, visibleWidths - 2);
1899 int ypos = wrappedRepeatHeightPx * widthsAbove
1900 + wrappedSpaceAboveAlignment;
1901 int endRes = ranges.getEndRes();
1902 endRes += widthsAbove * viewportWidth;
1903 int startRes = endRes - columns;
1904 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1908 * white fill first to erase annotations
1911 gg.translate(xOffset, 0);
1912 gg.setColor(Color.white);
1913 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1914 wrappedRepeatHeightPx);
1915 gg.translate(-xOffset, 0);
1917 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1922 * draw newly visible columns in last wrapped width (none if we
1923 * have reached the end of the alignment)
1924 * y-offset for drawing last width is height of widths above,
1927 int widthsAbove = visibleWidths - 1;
1928 int ypos = wrappedRepeatHeightPx * widthsAbove
1929 + wrappedSpaceAboveAlignment;
1930 int endRes = ranges.getEndRes();
1931 endRes += widthsAbove * viewportWidth;
1932 int startRes = endRes - columns + 1;
1935 * white fill first to erase annotations
1937 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1939 gg.translate(xOffset, 0);
1940 gg.setColor(Color.white);
1941 int width = viewportWidth * charWidth - xOffset;
1942 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1943 gg.translate(-xOffset, 0);
1945 gg.setFont(av.getFont());
1946 gg.setColor(Color.black);
1948 if (startRes < ranges.getVisibleAlignmentWidth())
1950 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1954 * and finally, white fill any space below the visible alignment
1956 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1957 if (heightBelow > 0)
1959 gg.setColor(Color.white);
1960 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1966 * Shifts the visible alignment by the specified number of columns - left if
1967 * negative, right if positive. Copies and moves sequences and annotations (if
1968 * shown). Scales, hidden column markers and any newly visible columns must be
1973 protected void shiftWrappedAlignment(int positions)
1980 Graphics gg = img.getGraphics();
1982 int charWidth = av.getCharWidth();
1984 int canvasHeight = getHeight();
1985 ViewportRanges ranges = av.getRanges();
1986 int viewportWidth = ranges.getViewportWidth();
1987 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1989 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1990 int xMax = ranges.getVisibleAlignmentWidth();
1995 * shift right (after scroll left)
1996 * for each wrapped width (starting with the last), copy (width-positions)
1997 * columns from the left margin to the right margin, and copy positions
1998 * columns from the right margin of the row above (if any) to the
1999 * left margin of the current row
2003 * get y-offset of last wrapped width, first row of sequences
2005 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2006 y += wrappedSpaceAboveAlignment;
2007 int copyFromLeftStart = labelWidthWest;
2008 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2013 * shift 'widthToCopy' residues by 'positions' places to the right
2015 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2016 positions * charWidth, 0);
2020 * copy 'positions' residue from the row above (right hand end)
2021 * to this row's left hand end
2023 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2024 positions * charWidth, heightToCopy, -widthToCopy,
2025 wrappedRepeatHeightPx);
2028 y -= wrappedRepeatHeightPx;
2034 * shift left (after scroll right)
2035 * for each wrapped width (starting with the first), copy (width-positions)
2036 * columns from the right margin to the left margin, and copy positions
2037 * columns from the left margin of the row below (if any) to the
2038 * right margin of the current row
2040 int xpos = av.getRanges().getStartRes();
2041 int y = wrappedSpaceAboveAlignment;
2042 int copyFromRightStart = labelWidthWest - positions * charWidth;
2044 while (y < canvasHeight)
2046 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2047 positions * charWidth, 0);
2048 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2049 && (xpos + viewportWidth <= xMax))
2051 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2052 -positions * charWidth, heightToCopy, widthToCopy,
2053 -wrappedRepeatHeightPx);
2055 y += wrappedRepeatHeightPx;
2056 xpos += viewportWidth;
2063 * Redraws any positions in the search results in the visible region of a
2064 * wrapped alignment. Any highlights are drawn depending on the search results
2065 * set on the Viewport, not the <code>results</code> argument. This allows
2066 * this method to be called either to clear highlights (passing the previous
2067 * search results), or to draw new highlights.
2072 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2074 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2078 int charHeight = av.getCharHeight();
2080 boolean matchFound = false;
2082 calculateWrappedGeometry(getWidth(), getHeight());
2083 int wrappedWidth = av.getWrappedWidth();
2084 int wrappedHeight = wrappedRepeatHeightPx;
2086 ViewportRanges ranges = av.getRanges();
2087 int canvasHeight = getHeight();
2088 int repeats = canvasHeight / wrappedHeight;
2089 if (canvasHeight / wrappedHeight > 0)
2094 int firstVisibleColumn = ranges.getStartRes();
2095 int lastVisibleColumn = ranges.getStartRes()
2096 + repeats * ranges.getViewportWidth() - 1;
2098 AlignmentI alignment = av.getAlignment();
2099 if (av.hasHiddenColumns())
2101 firstVisibleColumn = alignment.getHiddenColumns()
2102 .visibleToAbsoluteColumn(firstVisibleColumn);
2103 lastVisibleColumn = alignment.getHiddenColumns()
2104 .visibleToAbsoluteColumn(lastVisibleColumn);
2107 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2109 Graphics gg = img.getGraphics();
2111 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2112 .getEndSeq(); seqNo++)
2114 SequenceI seq = alignment.getSequenceAt(seqNo);
2116 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2118 if (visibleResults != null)
2120 for (int i = 0; i < visibleResults.length - 1; i += 2)
2122 int firstMatchedColumn = visibleResults[i];
2123 int lastMatchedColumn = visibleResults[i + 1];
2124 if (firstMatchedColumn <= lastVisibleColumn
2125 && lastMatchedColumn >= firstVisibleColumn)
2128 * found a search results match in the visible region
2130 firstMatchedColumn = Math.max(firstMatchedColumn,
2131 firstVisibleColumn);
2132 lastMatchedColumn = Math.min(lastMatchedColumn,
2136 * draw each mapped position separately (as contiguous positions may
2137 * wrap across lines)
2139 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2141 int displayColumn = mappedPos;
2142 if (av.hasHiddenColumns())
2144 displayColumn = alignment.getHiddenColumns()
2145 .absoluteToVisibleColumn(displayColumn);
2149 * transX: offset from left edge of canvas to residue position
2151 int transX = labelWidthWest
2152 + ((displayColumn - ranges.getStartRes())
2153 % wrappedWidth) * av.getCharWidth();
2156 * transY: offset from top edge of canvas to residue position
2158 int transY = gapHeight;
2159 transY += (displayColumn - ranges.getStartRes())
2160 / wrappedWidth * wrappedHeight;
2161 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2164 * yOffset is from graphics origin to start of visible region
2166 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2167 if (transY < getHeight())
2170 gg.translate(transX, transY);
2171 drawPanel(seqRdr, gg, displayColumn, displayColumn, seqNo,
2173 gg.translate(-transX, -transY);
2187 * Answers the width in pixels of the left scale labels (0 if not shown)
2191 int getLabelWidthWest()
2193 return labelWidthWest;