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;
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 + 1) * charHeight);
337 else if (vertical < 0)
339 endSeq = startSeq - vertical;
341 if (endSeq > ranges.getEndSeq())
343 endSeq = ranges.getEndSeq();
347 // jalview.bin.Console.errPrintln(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
356 /** @j2sNative xxi = this.img */
358 gg.translate(transX, transY);
359 drawPanel(seqRdr, gg, startRes, endRes, startSeq, endSeq, 0);
360 gg.translate(-transX, -transY);
363 // Call repaint on alignment panel so that repaints from other alignment
364 // panel components can be aggregated. Otherwise performance of the
365 // overview window and others may be adversely affected.
366 // jalview.bin.Console.outPrintln("SeqCanvas fastPaint() repaint() request...");
367 av.getAlignPanel().repaint();
370 fastpainting = false;
375 public void paintComponent(Graphics g)
378 int charHeight = av.getCharHeight();
379 int charWidth = av.getCharWidth();
381 int width = getWidth();
382 int height = getHeight();
384 width -= (width % charWidth);
385 height -= (height % charHeight);
387 // BH 2019 can't possibly fastPaint if either width or height is 0
389 if (width == 0 || height == 0)
394 ViewportRanges ranges = av.getRanges();
395 int startRes = ranges.getStartRes();
396 int startSeq = ranges.getStartSeq();
397 int endRes = ranges.getEndRes();
398 int endSeq = ranges.getEndSeq();
400 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
401 // repaint() requests in unpredictable ways. In this case, the issue was
402 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
403 // repaint request preceded two full requests, thus resulting
404 // in a full request for paint. In constrast, in JavaScript, the three
405 // requests were bundled together into one, so the fastPaint flag was
406 // still present for the second and third request.
408 // This resulted in incomplete painting.
410 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
411 // in PaintRefresher when the target to be painted is one of those two
416 // An initial idea; can be removed once we determine this issue is closed:
417 // if (av.isFastPaintDisabled())
419 // fastPaint = false;
425 || (vis = getVisibleRect()).width != (clip = g
426 .getClipBounds()).width
427 || vis.height != clip.height))
429 g.drawImage(img, 0, 0, this);
430 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
436 // img is a cached version of the last view we drew.
437 // If we have no img or the size has changed, make a new one.
439 if (img == null || width != img.getWidth()
440 || height != img.getHeight())
442 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
445 Graphics2D gg = (Graphics2D) img.getGraphics();
446 gg.setFont(av.getFont());
450 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
451 RenderingHints.VALUE_ANTIALIAS_ON);
454 gg.setColor(Color.white);
455 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
457 if (av.getWrapAlignment())
459 drawWrappedPanel(seqRdr, gg, getWidth(), getHeight(), ranges.getStartRes());
463 drawPanel(seqRdr,gg, startRes, endRes, startSeq, endSeq, 0);
466 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
468 g.drawImage(img, 0, 0, this);
474 drawCursor(g, startRes, endRes, startSeq, endSeq);
479 * Draw an alignment panel for printing
482 * Graphics object to draw with
484 * start residue of print area
486 * end residue of print area
488 * start sequence of print area
490 * end sequence of print area
492 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
493 int startSeq, int endSeq)
495 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
496 drawPanel(localSeqR,g1, startRes, endRes, startSeq, endSeq, 0);
498 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
502 * Draw a wrapped alignment panel for printing
505 * Graphics object to draw with
507 * width of drawing area
508 * @param canvasHeight
509 * height of drawing area
511 * start residue of print area
513 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
514 int canvasHeight, int startRes)
516 SequenceRenderer localSeqR = new jalview.gui.SequenceRenderer(av);
517 drawWrappedPanel(localSeqR, g, canvasWidth, canvasHeight, startRes);
519 SequenceGroup group = av.getSelectionGroup();
522 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
528 * Returns the visible width of the canvas in residues, after allowing for
529 * East or West scales (if shown)
532 * the width in pixels (possibly including scales)
536 public int getWrappedCanvasWidth(int canvasWidth)
538 int charWidth = av.getCharWidth();
540 FontMetrics fm = getFontMetrics(av.getFont());
544 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
546 labelWidth = getLabelWidth(fm);
549 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
551 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
553 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
556 public int getMinimumWrappedCanvasWidth()
558 int charWidth = av.getCharWidth();
559 FontMetrics fm = getFontMetrics(av.getFont());
561 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
563 labelWidth = getLabelWidth(fm);
565 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
566 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
567 return labelWidthEast + labelWidthWest + charWidth;
571 * Returns a pixel width sufficient to show the largest sequence coordinate
572 * (end position) in the alignment, calculated as the FontMetrics width of
573 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
574 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
575 * half a character width space on either side.
580 protected int getLabelWidth(FontMetrics fm)
583 * find the biggest sequence end position we need to show
584 * (note this is not necessarily the sequence length)
587 AlignmentI alignment = av.getAlignment();
588 for (int i = 0; i < alignment.getHeight(); i++)
590 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
594 for (int i = maxWidth; i > 0; i /= 10)
599 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
603 * Draws as many widths of a wrapped alignment as can fit in the visible
608 * available width in pixels
609 * @param canvasHeight
610 * available height in pixels
612 * the first column (0...) of the alignment to draw
614 public void drawWrappedPanel(SequenceRenderer seqRdr, Graphics g, int canvasWidth,
615 int canvasHeight, final int startColumn)
617 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
620 av.setWrappedWidth(wrappedWidthInResidues);
622 ViewportRanges ranges = av.getRanges();
623 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
625 // we need to call this again to make sure the startColumn +
626 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
628 calculateWrappedGeometry(canvasWidth, canvasHeight);
631 * draw one width at a time (excluding any scales shown),
632 * until we have run out of either alignment or vertical space available
634 int ypos = wrappedSpaceAboveAlignment;
635 int maxWidth = ranges.getVisibleAlignmentWidth();
637 int start = startColumn;
638 int currentWidth = 0;
639 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
641 int endColumn = Math.min(maxWidth,
642 start + wrappedWidthInResidues - 1);
643 drawWrappedWidth(seqRdr, g, ypos, start, endColumn, canvasHeight);
644 ypos += wrappedRepeatHeightPx;
645 start += wrappedWidthInResidues;
649 drawWrappedDecorators(g, startColumn);
653 * Calculates and saves values needed when rendering a wrapped alignment.
654 * These depend on many factors, including
656 * <li>canvas width and height</li>
657 * <li>number of visible sequences, and height of annotations if shown</li>
658 * <li>font and character width</li>
659 * <li>whether scales are shown left, right or above the alignment</li>
663 * @param canvasHeight
664 * @return the number of residue columns in each width
666 protected 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 wrappedRepeatHeightPx += 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);
710 av.setWrappedWidth(wrappedWidthInResidues); // update model accordingly
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(SequenceRenderer seqRdr, 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(seqRdr, 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);
902 * Draw a selection group over a wrapped alignment
904 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
905 int canvasWidth, int canvasHeight, int startRes)
907 // chop the wrapped alignment extent up into panel-sized blocks and treat
908 // each block as if it were a block from an unwrapped alignment
909 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
910 BasicStroke.JOIN_ROUND, 3f, new float[]
912 g.setColor(Color.RED);
914 int charWidth = av.getCharWidth();
915 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
917 int startx = startRes;
918 int maxwidth = av.getAlignment().getVisibleWidth();
919 int ypos = wrappedSpaceAboveAlignment;
921 while ((ypos <= canvasHeight) && (startx < maxwidth))
923 // set end value to be start + width, or maxwidth, whichever is smaller
924 int endx = startx + cWidth - 1;
931 g.translate(labelWidthWest, 0);
932 drawUnwrappedSelection(g, group, startx, endx, 0,
933 av.getAlignment().getHeight() - 1, ypos);
934 g.translate(-labelWidthWest, 0);
936 ypos += wrappedRepeatHeightPx;
940 g.setStroke(new BasicStroke());
944 * Answers zero if annotations are not shown, otherwise recalculates and
945 * answers the total height of all annotation rows in pixels
949 int getAnnotationHeight()
951 if (!av.isShowAnnotation())
956 if (annotations == null)
958 annotations = new AnnotationPanel(av);
961 return annotations.adjustPanelHeight();
965 * Draws the visible region of the alignment on the graphics context. If there
966 * are hidden column markers in the visible region, then each sub-region
967 * between the markers is drawn separately, followed by the hidden column
969 * @param localSeqR - sequence renderer implementation - when null, uses the one used for rendering interactive GUI
972 * the graphics context, positioned at the first residue to be drawn
974 * offset of the first column to draw (0..)
976 * offset of the last column to draw (0..)
978 * offset of the first sequence to draw (0..)
980 * offset of the last sequence to draw (0..)
982 * vertical offset at which to draw (for wrapped alignments)
984 public void drawPanel(SequenceRenderer localSeqR, Graphics g1, final int startRes, final int endRes,
985 final int startSeq, final int endSeq, final int yOffset)
987 int charHeight = av.getCharHeight();
988 int charWidth = av.getCharWidth();
994 if (!av.hasHiddenColumns())
996 draw(localSeqR, g1, startRes, endRes, startSeq, endSeq, yOffset);
1004 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1005 VisibleContigsIterator regions = hidden
1006 .getVisContigsIterator(startRes, endRes + 1, true);
1008 while (regions.hasNext())
1010 int[] region = regions.next();
1011 blockEnd = region[1];
1012 blockStart = region[0];
1015 * draw up to just before the next hidden region, or the end of
1016 * the visible region, whichever comes first
1018 g1.translate(screenY * charWidth, 0);
1020 draw(localSeqR, g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1023 * draw the downline of the hidden column marker (ScalePanel draws the
1024 * triangle on top) if we reached it
1026 if (av.getShowHiddenMarkers()
1027 && (regions.hasNext() || regions.endsAtHidden()))
1029 g1.setColor(Color.blue);
1031 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1032 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1033 (endSeq - startSeq + 1) * charHeight + yOffset);
1036 g1.translate(-screenY * charWidth, 0);
1037 screenY += blockEnd - blockStart + 1;
1044 * Draws a region of the visible alignment
1049 * offset of the first column in the visible region (0..)
1051 * offset of the last column in the visible region (0..)
1053 * offset of the first sequence in the visible region (0..)
1055 * offset of the last sequence in the visible region (0..)
1057 * vertical offset at which to draw (for wrapped alignments)
1059 private void draw(SequenceRenderer seqRdr, Graphics g, int startRes, int endRes, int startSeq,
1060 int endSeq, int offset)
1062 int charHeight = av.getCharHeight();
1063 int charWidth = av.getCharWidth();
1065 g.setFont(av.getFont());
1066 seqRdr.prepare(g, av.isRenderGaps());
1070 // / First draw the sequences
1071 // ///////////////////////////
1072 for (int i = startSeq; i <= endSeq; i++)
1074 nextSeq = av.getAlignment().getSequenceAt(i);
1075 if (nextSeq == null)
1077 // occasionally, a race condition occurs such that the alignment row is
1081 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1082 startRes, endRes, offset + ((i - startSeq) * charHeight));
1084 if (av.isShowSequenceFeatures())
1086 fr.drawSequence(g, nextSeq, startRes, endRes,
1087 offset + ((i - startSeq) * charHeight), false);
1091 * highlight search Results once sequence has been drawn
1093 if (av.hasSearchResults())
1095 SearchResultsI searchResults = av.getSearchResults();
1096 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1098 if (visibleResults != null)
1100 for (int r = 0; r < visibleResults.length; r += 2)
1102 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1103 visibleResults[r + 1],
1104 (visibleResults[r] - startRes) * charWidth,
1105 offset + ((i - startSeq) * charHeight));
1111 if (av.getSelectionGroup() != null
1112 || av.getAlignment().getGroups().size() > 0)
1114 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1120 * Draws the outlines of any groups defined on the alignment (excluding the
1121 * current selection group, if any)
1130 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1131 int startSeq, int endSeq, int offset)
1133 Graphics2D g = (Graphics2D) g1;
1135 SequenceGroup group = null;
1136 int groupIndex = -1;
1138 if (av.getAlignment().getGroups().size() > 0)
1140 group = av.getAlignment().getGroups().get(0);
1148 g.setColor(group.getOutlineColour());
1149 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1153 if (groupIndex >= av.getAlignment().getGroups().size())
1157 group = av.getAlignment().getGroups().get(groupIndex);
1158 } while (groupIndex < av.getAlignment().getGroups().size());
1163 * Draws the outline of the current selection group (if any)
1171 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1172 int startSeq, int endSeq)
1174 SequenceGroup group = av.getSelectionGroup();
1180 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1181 BasicStroke.JOIN_ROUND, 3f, new float[]
1183 g.setColor(Color.RED);
1184 if (!av.getWrapAlignment())
1186 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1191 drawWrappedSelection(g, group, getWidth(), getHeight(),
1192 av.getRanges().getStartRes());
1194 g.setStroke(new BasicStroke());
1198 * Draw the cursor as a separate image and overlay
1201 * start residue of area to draw cursor in
1203 * end residue of area to draw cursor in
1205 * start sequence of area to draw cursor in
1207 * end sequence of are to draw cursor in
1208 * @return a transparent image of the same size as the sequence canvas, with
1209 * the cursor drawn on it, if any
1211 private void drawCursor(Graphics g, int startRes, int endRes,
1212 int startSeq, int endSeq)
1214 // convert the cursorY into a position on the visible alignment
1215 int cursor_ypos = cursorY;
1217 // don't do work unless we have to
1218 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1222 int startx = startRes;
1225 // convert the cursorX into a position on the visible alignment
1226 int cursor_xpos = av.getAlignment().getHiddenColumns()
1227 .absoluteToVisibleColumn(cursorX);
1229 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1232 if (av.getWrapAlignment())
1234 // work out the correct offsets for the cursor
1235 int charHeight = av.getCharHeight();
1236 int charWidth = av.getCharWidth();
1237 int canvasWidth = getWidth();
1238 int canvasHeight = getHeight();
1240 // height gap above each panel
1241 int hgap = charHeight;
1242 if (av.getScaleAboveWrapped())
1247 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1249 int cHeight = av.getAlignment().getHeight() * charHeight;
1251 endx = startx + cWidth - 1;
1252 int ypos = hgap; // vertical offset
1254 // iterate down the wrapped panels
1255 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1257 // update vertical offset
1258 ypos += cHeight + getAnnotationHeight() + hgap;
1260 // update horizontal offset
1262 endx = startx + cWidth - 1;
1265 xoffset = labelWidthWest;
1268 // now check if cursor is within range for x values
1269 if (cursor_xpos >= startx && cursor_xpos <= endx)
1271 // get the character the cursor is drawn at
1272 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1273 char s = seq.getCharAt(cursorX);
1275 seqRdr.drawCursor(g, s,
1276 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1277 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1284 * Draw a selection group over an unwrapped alignment
1287 * graphics object to draw with
1291 * start residue of area to draw
1293 * end residue of area to draw
1295 * start sequence of area to draw
1297 * end sequence of area to draw
1299 * vertical offset (used when called from wrapped alignment code)
1301 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1302 int startRes, int endRes, int startSeq, int endSeq, int offset)
1304 int charWidth = av.getCharWidth();
1306 if (!av.hasHiddenColumns())
1308 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1313 // package into blocks of visible columns
1318 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1319 VisibleContigsIterator regions = hidden
1320 .getVisContigsIterator(startRes, endRes + 1, true);
1321 while (regions.hasNext())
1323 int[] region = regions.next();
1324 blockEnd = region[1];
1325 blockStart = region[0];
1327 g.translate(screenY * charWidth, 0);
1328 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1331 g.translate(-screenY * charWidth, 0);
1332 screenY += blockEnd - blockStart + 1;
1338 * Draws part of a selection group outline
1346 * @param verticalOffset
1348 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1349 int startRes, int endRes, int startSeq, int endSeq,
1352 int charHeight = av.getCharHeight();
1353 int charWidth = av.getCharWidth();
1354 int visWidth = (endRes - startRes + 1) * charWidth;
1358 boolean inGroup = false;
1363 List<SequenceI> seqs = group.getSequences(null);
1365 // position of start residue of group relative to startRes, in pixels
1366 int sx = (group.getStartRes() - startRes) * charWidth;
1368 // width of group in pixels
1369 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1372 if (!(sx + xwidth < 0 || sx > visWidth))
1374 for (i = startSeq; i <= endSeq; i++)
1376 sy = verticalOffset + (i - startSeq) * charHeight;
1378 if ((sx <= (endRes - startRes) * charWidth)
1379 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1382 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1384 bottom = sy + charHeight;
1389 if (((top == -1) && (i == 0)) || !seqs
1390 .contains(av.getAlignment().getSequenceAt(i - 1)))
1401 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1402 drawHorizontals(g, sx, xwidth, visWidth, top, bottom+1);
1404 // reset top and bottom
1412 sy = verticalOffset + ((i - startSeq) * charHeight);
1413 drawVerticals(g, sx, xwidth, visWidth, oldY, bottom);
1414 drawHorizontals(g, sx, xwidth, visWidth, top, bottom+1);
1420 * Draw horizontal selection group boundaries at top and bottom positions
1423 * graphics object to draw on
1429 * visWidth maximum available width
1431 * position to draw top of group at
1433 * position to draw bottom of group at
1435 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1436 int visWidth, int top, int bottom)
1446 // don't let width extend beyond current block, or group extent
1448 if (startx + width >= visWidth)
1450 width = visWidth - startx;
1455 g.drawLine(startx, top, startx + width, top);
1460 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1465 * Draw vertical lines at sx and sx+xwidth providing they lie within
1469 * graphics object to draw on
1475 * visWidth maximum available width
1481 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1484 // if start position is visible, draw vertical line to left of
1486 if (sx >= 0 && sx < visWidth)
1488 g.drawLine(sx, oldY, sx, sy);
1491 // if end position is visible, draw vertical line to right of
1493 if (sx + xwidth < visWidth)
1495 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1500 * Highlights search results in the visible region by rendering as white text
1501 * on a black background. Any previous highlighting is removed. Answers true
1502 * if any highlight was left on the visible alignment (so status bar should be
1503 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1504 * so allows the next repaint to update the whole display.
1509 public boolean highlightSearchResults(SearchResultsI results)
1511 return highlightSearchResults(results, false);
1516 * Highlights search results in the visible region by rendering as white text
1517 * on a black background. Any previous highlighting is removed. Answers true
1518 * if any highlight was left on the visible alignment (so status bar should be
1519 * set to match), else false.
1521 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1522 * highlighted regions are modified. This speeds up highlighting across linked
1525 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1526 * a wrapped alignment had to be scrolled to show the highlighted region, then
1527 * it should be fully redrawn, otherwise a fast paint can be performed. This
1528 * argument could be removed if fast paint of scrolled wrapped alignment is
1529 * coded in future (JAL-2609).
1532 * @param doFastPaint
1533 * if true, sets a flag so the next repaint only redraws the modified
1537 public boolean highlightSearchResults(SearchResultsI results,
1538 boolean doFastPaint)
1544 boolean wrapped = av.getWrapAlignment();
1547 fastPaint = doFastPaint;
1548 fastpainting = fastPaint;
1551 * to avoid redrawing the whole visible region, we instead
1552 * redraw just the minimal regions to remove previous highlights
1555 SearchResultsI previous = av.getSearchResults();
1556 av.setSearchResults(results);
1557 boolean redrawn = false;
1558 boolean drawn = false;
1561 redrawn = drawMappedPositionsWrapped(previous);
1562 drawn = drawMappedPositionsWrapped(results);
1567 redrawn = drawMappedPositions(previous);
1568 drawn = drawMappedPositions(results);
1573 * if highlights were either removed or added, repaint
1581 * return true only if highlights were added
1587 fastpainting = false;
1592 * Redraws the minimal rectangle in the visible region (if any) that includes
1593 * mapped positions of the given search results. Whether or not positions are
1594 * highlighted depends on the SearchResults set on the Viewport. This allows
1595 * this method to be called to either clear or set highlighting. Answers true
1596 * if any positions were drawn (in which case a repaint is still required),
1602 protected boolean drawMappedPositions(SearchResultsI results)
1604 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1610 * calculate the minimal rectangle to redraw that
1611 * includes both new and existing search results
1613 int firstSeq = Integer.MAX_VALUE;
1615 int firstCol = Integer.MAX_VALUE;
1617 boolean matchFound = false;
1619 ViewportRanges ranges = av.getRanges();
1620 int firstVisibleColumn = ranges.getStartRes();
1621 int lastVisibleColumn = ranges.getEndRes();
1622 AlignmentI alignment = av.getAlignment();
1623 if (av.hasHiddenColumns())
1625 firstVisibleColumn = alignment.getHiddenColumns()
1626 .visibleToAbsoluteColumn(firstVisibleColumn);
1627 lastVisibleColumn = alignment.getHiddenColumns()
1628 .visibleToAbsoluteColumn(lastVisibleColumn);
1631 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1632 .getEndSeq(); seqNo++)
1634 SequenceI seq = alignment.getSequenceAt(seqNo);
1636 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1638 if (visibleResults != null)
1640 for (int i = 0; i < visibleResults.length - 1; i += 2)
1642 int firstMatchedColumn = visibleResults[i];
1643 int lastMatchedColumn = visibleResults[i + 1];
1644 if (firstMatchedColumn <= lastVisibleColumn
1645 && lastMatchedColumn >= firstVisibleColumn)
1648 * found a search results match in the visible region -
1649 * remember the first and last sequence matched, and the first
1650 * and last visible columns in the matched positions
1653 firstSeq = Math.min(firstSeq, seqNo);
1654 lastSeq = Math.max(lastSeq, seqNo);
1655 firstMatchedColumn = Math.max(firstMatchedColumn,
1656 firstVisibleColumn);
1657 lastMatchedColumn = Math.min(lastMatchedColumn,
1659 firstCol = Math.min(firstCol, firstMatchedColumn);
1660 lastCol = Math.max(lastCol, lastMatchedColumn);
1668 if (av.hasHiddenColumns())
1670 firstCol = alignment.getHiddenColumns()
1671 .absoluteToVisibleColumn(firstCol);
1672 lastCol = alignment.getHiddenColumns()
1673 .absoluteToVisibleColumn(lastCol);
1675 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1676 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1677 Graphics gg = img.getGraphics();
1678 gg.translate(transX, transY);
1679 drawPanel(seqRdr, gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1680 gg.translate(-transX, -transY);
1688 public void propertyChange(PropertyChangeEvent evt)
1690 String eventName = evt.getPropertyName();
1691 // jalview.bin.Console.errPrintln(">>SeqCanvas propertyChange " + eventName);
1692 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1698 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1701 // jalview.bin.Console.errPrintln("!!!! fastPaint false from MOVE_VIEWPORT");
1707 if (eventName.equals(ViewportRanges.STARTRES)
1708 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1710 // Make sure we're not trying to draw a panel
1711 // larger than the visible window
1712 if (eventName.equals(ViewportRanges.STARTRES))
1714 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1718 scrollX = ((int[]) evt.getNewValue())[0]
1719 - ((int[]) evt.getOldValue())[0];
1721 ViewportRanges vpRanges = av.getRanges();
1723 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1724 if (scrollX > range)
1728 else if (scrollX < -range)
1733 // Both scrolling and resizing change viewport ranges: scrolling changes
1734 // both start and end points, but resize only changes end values.
1735 // Here we only want to fastpaint on a scroll, with resize using a normal
1736 // paint, so scroll events are identified as changes to the horizontal or
1737 // vertical start value.
1738 if (eventName.equals(ViewportRanges.STARTRES))
1740 if (av.getWrapAlignment())
1742 fastPaintWrapped(scrollX);
1746 fastPaint(scrollX, 0);
1749 else if (eventName.equals(ViewportRanges.STARTSEQ))
1752 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1754 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1756 if (av.getWrapAlignment())
1758 fastPaintWrapped(scrollX);
1762 fastPaint(scrollX, 0);
1765 else if (eventName.equals(ViewportRanges.STARTSEQ))
1768 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1770 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1772 if (av.getWrapAlignment())
1774 fastPaintWrapped(scrollX);
1780 * Does a minimal update of the image for a scroll movement. This method
1781 * handles scroll movements of up to one width of the wrapped alignment (one
1782 * click in the vertical scrollbar). Larger movements (for example after a
1783 * scroll to highlight a mapped position) trigger a full redraw instead.
1786 * number of positions scrolled (right if positive, left if negative)
1788 protected void fastPaintWrapped(int scrollX)
1790 ViewportRanges ranges = av.getRanges();
1792 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1795 * shift of one view width or more is
1796 * overcomplicated to handle in this method
1803 if (fastpainting || img == null)
1809 fastpainting = true;
1814 Graphics gg = img.getGraphics();
1816 calculateWrappedGeometry(getWidth(), getHeight());
1819 * relocate the regions of the alignment that are still visible
1821 shiftWrappedAlignment(-scrollX);
1824 * add new columns (sequence, annotation)
1825 * - at top left if scrollX < 0
1826 * - at right of last two widths if scrollX > 0
1830 int startRes = ranges.getStartRes();
1831 drawWrappedWidth(seqRdr,gg, wrappedSpaceAboveAlignment, startRes,
1832 startRes - scrollX - 1, getHeight());
1836 fastPaintWrappedAddRight(scrollX);
1840 * draw all scales (if shown) and hidden column markers
1842 drawWrappedDecorators(gg, ranges.getStartRes());
1849 fastpainting = false;
1854 * Draws the specified number of columns at the 'end' (bottom right) of a
1855 * wrapped alignment view, including sequences and annotations if shown, but
1856 * not scales. Also draws the same number of columns at the right hand end of
1857 * the second last width shown, if the last width is not full height (so
1858 * cannot simply be copied from the graphics image).
1862 protected void fastPaintWrappedAddRight(int columns)
1869 Graphics gg = img.getGraphics();
1871 ViewportRanges ranges = av.getRanges();
1872 int viewportWidth = ranges.getViewportWidth();
1873 int charWidth = av.getCharWidth();
1876 * draw full height alignment in the second last row, last columns, if the
1877 * last row was not full height
1879 int visibleWidths = wrappedVisibleWidths;
1880 int canvasHeight = getHeight();
1881 boolean lastWidthPartHeight = (wrappedVisibleWidths
1882 * wrappedRepeatHeightPx) > canvasHeight;
1884 if (lastWidthPartHeight)
1886 int widthsAbove = Math.max(0, visibleWidths - 2);
1887 int ypos = wrappedRepeatHeightPx * widthsAbove
1888 + wrappedSpaceAboveAlignment;
1889 int endRes = ranges.getEndRes();
1890 endRes += widthsAbove * viewportWidth;
1891 int startRes = endRes - columns;
1892 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1896 * white fill first to erase annotations
1899 gg.translate(xOffset, 0);
1900 gg.setColor(Color.white);
1901 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1902 wrappedRepeatHeightPx);
1903 gg.translate(-xOffset, 0);
1905 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1910 * draw newly visible columns in last wrapped width (none if we
1911 * have reached the end of the alignment)
1912 * y-offset for drawing last width is height of widths above,
1915 int widthsAbove = visibleWidths - 1;
1916 int ypos = wrappedRepeatHeightPx * widthsAbove
1917 + wrappedSpaceAboveAlignment;
1918 int endRes = ranges.getEndRes();
1919 endRes += widthsAbove * viewportWidth;
1920 int startRes = endRes - columns + 1;
1923 * white fill first to erase annotations
1925 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1927 gg.translate(xOffset, 0);
1928 gg.setColor(Color.white);
1929 int width = viewportWidth * charWidth - xOffset;
1930 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1931 gg.translate(-xOffset, 0);
1933 gg.setFont(av.getFont());
1934 gg.setColor(Color.black);
1936 if (startRes < ranges.getVisibleAlignmentWidth())
1938 drawWrappedWidth(seqRdr, gg, ypos, startRes, endRes, canvasHeight);
1942 * and finally, white fill any space below the visible alignment
1944 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1945 if (heightBelow > 0)
1947 gg.setColor(Color.white);
1948 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1954 * Shifts the visible alignment by the specified number of columns - left if
1955 * negative, right if positive. Copies and moves sequences and annotations (if
1956 * shown). Scales, hidden column markers and any newly visible columns must be
1961 protected void shiftWrappedAlignment(int positions)
1968 Graphics gg = img.getGraphics();
1970 int charWidth = av.getCharWidth();
1972 int canvasHeight = getHeight();
1973 ViewportRanges ranges = av.getRanges();
1974 int viewportWidth = ranges.getViewportWidth();
1975 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1977 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1978 int xMax = ranges.getVisibleAlignmentWidth();
1983 * shift right (after scroll left)
1984 * for each wrapped width (starting with the last), copy (width-positions)
1985 * columns from the left margin to the right margin, and copy positions
1986 * columns from the right margin of the row above (if any) to the
1987 * left margin of the current row
1991 * get y-offset of last wrapped width, first row of sequences
1993 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1994 y += wrappedSpaceAboveAlignment;
1995 int copyFromLeftStart = labelWidthWest;
1996 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2001 * shift 'widthToCopy' residues by 'positions' places to the right
2003 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2004 positions * charWidth, 0);
2008 * copy 'positions' residue from the row above (right hand end)
2009 * to this row's left hand end
2011 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2012 positions * charWidth, heightToCopy, -widthToCopy,
2013 wrappedRepeatHeightPx);
2016 y -= wrappedRepeatHeightPx;
2022 * shift left (after scroll right)
2023 * for each wrapped width (starting with the first), copy (width-positions)
2024 * columns from the right margin to the left margin, and copy positions
2025 * columns from the left margin of the row below (if any) to the
2026 * right margin of the current row
2028 int xpos = av.getRanges().getStartRes();
2029 int y = wrappedSpaceAboveAlignment;
2030 int copyFromRightStart = labelWidthWest - positions * charWidth;
2032 while (y < canvasHeight)
2034 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2035 positions * charWidth, 0);
2036 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2037 && (xpos + viewportWidth <= xMax))
2039 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2040 -positions * charWidth, heightToCopy, widthToCopy,
2041 -wrappedRepeatHeightPx);
2043 y += wrappedRepeatHeightPx;
2044 xpos += viewportWidth;
2051 * Redraws any positions in the search results in the visible region of a
2052 * wrapped alignment. Any highlights are drawn depending on the search results
2053 * set on the Viewport, not the <code>results</code> argument. This allows
2054 * this method to be called either to clear highlights (passing the previous
2055 * search results), or to draw new highlights.
2060 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2062 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2066 int charHeight = av.getCharHeight();
2068 boolean matchFound = false;
2070 calculateWrappedGeometry(getWidth(), getHeight());
2071 int wrappedWidth = av.getWrappedWidth();
2072 int wrappedHeight = wrappedRepeatHeightPx;
2074 ViewportRanges ranges = av.getRanges();
2075 int canvasHeight = getHeight();
2076 int repeats = canvasHeight / wrappedHeight;
2077 if (canvasHeight / wrappedHeight > 0)
2082 int firstVisibleColumn = ranges.getStartRes();
2083 int lastVisibleColumn = ranges.getStartRes()
2084 + repeats * ranges.getViewportWidth() - 1;
2086 AlignmentI alignment = av.getAlignment();
2087 if (av.hasHiddenColumns())
2089 firstVisibleColumn = alignment.getHiddenColumns()
2090 .visibleToAbsoluteColumn(firstVisibleColumn);
2091 lastVisibleColumn = alignment.getHiddenColumns()
2092 .visibleToAbsoluteColumn(lastVisibleColumn);
2095 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2097 Graphics gg = img.getGraphics();
2099 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2100 .getEndSeq(); seqNo++)
2102 SequenceI seq = alignment.getSequenceAt(seqNo);
2104 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2106 if (visibleResults != null)
2108 for (int i = 0; i < visibleResults.length - 1; i += 2)
2110 int firstMatchedColumn = visibleResults[i];
2111 int lastMatchedColumn = visibleResults[i + 1];
2112 if (firstMatchedColumn <= lastVisibleColumn
2113 && lastMatchedColumn >= firstVisibleColumn)
2116 * found a search results match in the visible region
2118 firstMatchedColumn = Math.max(firstMatchedColumn,
2119 firstVisibleColumn);
2120 lastMatchedColumn = Math.min(lastMatchedColumn,
2124 * draw each mapped position separately (as contiguous positions may
2125 * wrap across lines)
2127 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2129 int displayColumn = mappedPos;
2130 if (av.hasHiddenColumns())
2132 displayColumn = alignment.getHiddenColumns()
2133 .absoluteToVisibleColumn(displayColumn);
2137 * transX: offset from left edge of canvas to residue position
2139 int transX = labelWidthWest
2140 + ((displayColumn - ranges.getStartRes())
2141 % wrappedWidth) * av.getCharWidth();
2144 * transY: offset from top edge of canvas to residue position
2146 int transY = gapHeight;
2147 transY += (displayColumn - ranges.getStartRes())
2148 / wrappedWidth * wrappedHeight;
2149 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2152 * yOffset is from graphics origin to start of visible region
2154 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2155 if (transY < getHeight())
2158 gg.translate(transX, transY);
2159 drawPanel(seqRdr,gg, displayColumn, displayColumn, seqNo, seqNo,
2161 gg.translate(-transX, -transY);
2175 * Answers the width in pixels of the left scale labels (0 if not shown)
2179 int getLabelWidthWest()
2181 return labelWidthWest;