2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.Rectangle;
42 import java.awt.RenderingHints;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JPanel;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 @SuppressWarnings("serial")
57 public class SeqCanvas extends JPanel implements ViewportListenerI
60 * pixels gap between sequences and annotations when in wrapped mode
62 static final int SEQS_ANNOTATION_GAP = 3;
64 private static final String ZEROS = "0000000000";
66 final FeatureRenderer fr;
76 private final SequenceRenderer seqRdr;
78 private boolean fastPaint = false;
80 private boolean fastpainting = false;
82 private AnnotationPanel annotations;
85 * measurements for drawing a wrapped alignment
87 private int labelWidthEast; // label right width in pixels if shown
89 private int labelWidthWest; // label left width in pixels if shown
91 int wrappedSpaceAboveAlignment; // gap between widths
93 int wrappedRepeatHeightPx; // height in pixels of wrapped width
95 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 // Don't do this! Graphics handles are supposed to be transient
98 //private Graphics2D gg;
101 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
108 fr = new FeatureRenderer(ap);
109 seqRdr = new SequenceRenderer(av);
110 setLayout(new BorderLayout());
111 PaintRefresher.Register(this, av.getSequenceSetId());
112 setBackground(Color.white);
114 av.getRanges().addPropertyChangeListener(this);
117 public SequenceRenderer getSequenceRenderer()
122 public FeatureRenderer getFeatureRenderer()
128 * Draws the scale above a region of a wrapped alignment, consisting of a
129 * column number every major interval (10 columns).
132 * the graphics context to draw on, positioned at the start (bottom
133 * left) of the line on which to draw any scale marks
135 * start alignment column (0..)
137 * end alignment column (0..)
139 * y offset to draw at
141 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
143 int charHeight = av.getCharHeight();
144 int charWidth = av.getCharWidth();
147 * white fill the scale space (for the fastPaint case)
149 g.setColor(Color.white);
150 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
151 charHeight * 3 / 2 + 2);
152 g.setColor(Color.black);
154 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
156 for (ScaleMark mark : marks)
158 int mpos = mark.column; // (i - startx - 1)
163 String mstring = mark.text;
169 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
173 * draw a tick mark below the column number, centred on the column;
174 * height of tick mark is 4 pixels less than half a character
176 int xpos = (mpos * charWidth) + (charWidth / 2);
177 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
183 * Draw the scale to the left or right of a wrapped alignment
186 * graphics context, positioned at the start of the scale to be drawn
188 * first column of wrapped width (0.. excluding any hidden columns)
190 * last column of wrapped width (0.. excluding any hidden columns)
192 * vertical offset at which to begin the scale
194 * if true, scale is left of residues, if false, scale is right
196 void drawVerticalScale(Graphics g, final int startx, final int endx,
197 final int ypos, final boolean left)
199 int charHeight = av.getCharHeight();
200 int charWidth = av.getCharWidth();
202 int yPos = ypos + charHeight;
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
210 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startX : endX;
224 while (index >= startX && index <= endX)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
243 * white fill the space for the scale
245 g.setColor(Color.white);
246 int y = (yPos + (i * charHeight)) - (charHeight / 5);
247 // fillRect origin is top left of rectangle
248 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
254 * draw scale value, right justified within its width less half a
255 * character width padding on the right
257 int labelSpace = left ? labelWidthWest : labelWidthEast;
258 labelSpace -= charWidth / 2; // leave space to the right
259 String valueAsString = String.valueOf(value);
260 int labelLength = fm.stringWidth(valueAsString);
261 int xOffset = labelSpace - labelLength;
262 g.setColor(Color.black);
263 g.drawString(valueAsString, xOffset, y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
280 * <li>pasting a block of sequences</li>
284 * columns to shift right (positive) or left (negative)
286 * rows to shift down (positive) or up (negative)
288 public void fastPaint(int horizontal, int vertical)
291 // System.err.println("<<SeqCanvas fastPaint " + fastpainting + " "
292 // + horizontal + " " + vertical);
294 // if (horizontal != 0 && vertical != 0)
295 // throw new InvalidArgumentException();
296 if (fastpainting || img == null)
304 int charHeight = av.getCharHeight();
305 int charWidth = av.getCharWidth();
307 ViewportRanges ranges = av.getRanges();
308 int startRes = ranges.getStartRes();
309 int endRes = ranges.getEndRes();
310 int startSeq = ranges.getStartSeq();
311 int endSeq = ranges.getEndSeq();
315 if (horizontal > 0) // scrollbar pulled right, image to the left
317 transX = (endRes - startRes - horizontal) * charWidth;
318 startRes = endRes - horizontal;
320 else if (horizontal < 0)
322 endRes = startRes - horizontal;
325 if (vertical > 0) // scroll down
327 startSeq = endSeq - vertical;
329 if (startSeq < ranges.getStartSeq())
330 { // ie scrolling too fast, more than a page at a time
331 startSeq = ranges.getStartSeq();
335 transY = img.getHeight() - ((vertical + 1) * charHeight);
338 else if (vertical < 0)
340 endSeq = startSeq - vertical;
342 if (endSeq > ranges.getEndSeq())
344 endSeq = ranges.getEndSeq();
349 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
350 // + horizontal + " " + vertical + " " + startRes + " " + endRes
351 // + " " + startSeq + " " + endSeq);
353 Graphics gg = img.getGraphics();
354 gg.copyArea(horizontal * charWidth, vertical * charHeight,
355 img.getWidth(), img.getHeight(), -horizontal * charWidth,
356 -vertical * charHeight);
358 /** @j2sNative xxi = this.img */
360 gg.translate(transX, transY);
361 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
362 gg.translate(-transX, -transY);
365 // Call repaint on alignment panel so that repaints from other alignment
366 // panel components can be aggregated. Otherwise performance of the
367 // overview window and others may be adversely affected.
368 // System.out.println("SeqCanvas fastPaint() repaint() request...");
369 av.getAlignPanel().repaint();
372 fastpainting = false;
377 public void paintComponent(Graphics g)
379 int charHeight = av.getCharHeight();
380 int charWidth = av.getCharWidth();
382 int width = getWidth();
383 int height = getHeight();
385 width -= (width % charWidth);
386 height -= (height % charHeight);
388 // BH 2019 can't possibly fastPaint if width and height are 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();
399 if (av.isFastPaintDisabled())
404 // System.err.println(">>SeqCanvas paintComponent " + fastPaint + "\n"
405 // + getVisibleRect() + "\n" + g.getClipBounds());
406 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent "
407 // + startRes + " " + endRes + " " + startSeq + " " + endSeq);
411 || (vis = getVisibleRect()).width != (clip = g
412 .getClipBounds()).width
413 || vis.height != clip.height))
415 g.drawImage(img, 0, 0, this);
416 // System.err.println(">>>>>>>>>>>>>>>>SeqCanvas paintComponent FAST");
417 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
420 // System.out.println("SeqCanvas fast paint");
424 // System.out.println("SeqCanvas full paint");
426 * img is a cached version of the last view we drew.
427 * If we have no img or the size has changed, make a new one
429 if (img == null || width != img.getWidth()
430 || height != img.getHeight())
432 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
435 Graphics2D gg = (Graphics2D) img.getGraphics();
436 gg.setFont(av.getFont());
440 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
441 RenderingHints.VALUE_ANTIALIAS_ON);
444 gg.setColor(Color.white);
445 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
447 if (av.getWrapAlignment())
449 drawWrappedPanel(gg, width, height, ranges.getStartRes());
453 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
456 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
458 g.drawImage(img, 0, 0, this);
464 drawCursor(g, startRes, endRes, startSeq, endSeq);
469 * Draw an alignment panel for printing
472 * Graphics object to draw with
474 * start residue of print area
476 * end residue of print area
478 * start sequence of print area
480 * end sequence of print area
482 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
483 int startSeq, int endSeq)
485 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
487 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
492 * Draw a wrapped alignment panel for printing
495 * Graphics object to draw with
497 * width of drawing area
498 * @param canvasHeight
499 * height of drawing area
501 * start residue of print area
503 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
504 int canvasHeight, int startRes)
506 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
508 SequenceGroup group = av.getSelectionGroup();
511 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
517 * Returns the visible width of the canvas in residues, after allowing for
518 * East or West scales (if shown)
521 * the width in pixels (possibly including scales)
525 public int getWrappedCanvasWidth(int canvasWidth)
527 int charWidth = av.getCharWidth();
529 FontMetrics fm = getFontMetrics(av.getFont());
533 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
535 labelWidth = getLabelWidth(fm);
538 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
540 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
542 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
546 * Returns a pixel width sufficient to show the largest sequence coordinate
547 * (end position) in the alignment, calculated as the FontMetrics width of
548 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
549 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
550 * half a character width space on either side.
555 protected int getLabelWidth(FontMetrics fm)
558 * find the biggest sequence end position we need to show
559 * (note this is not necessarily the sequence length)
562 AlignmentI alignment = av.getAlignment();
563 for (int i = 0; i < alignment.getHeight(); i++)
565 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
569 for (int i = maxWidth; i > 0; i /= 10)
574 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
578 * Draws as many widths of a wrapped alignment as can fit in the visible
583 * available width in pixels
584 * @param canvasHeight
585 * available height in pixels
587 * the first column (0...) of the alignment to draw
589 public void drawWrappedPanel(Graphics g, int canvasWidth,
590 int canvasHeight, final int startColumn)
592 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
595 av.setWrappedWidth(wrappedWidthInResidues);
597 ViewportRanges ranges = av.getRanges();
598 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
600 // we need to call this again to make sure the startColumn +
601 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
603 calculateWrappedGeometry(canvasWidth, canvasHeight);
606 * draw one width at a time (excluding any scales shown),
607 * until we have run out of either alignment or vertical space available
609 int ypos = wrappedSpaceAboveAlignment;
610 int maxWidth = ranges.getVisibleAlignmentWidth();
612 int start = startColumn;
613 int currentWidth = 0;
614 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
617 .min(maxWidth, start + wrappedWidthInResidues - 1);
618 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
619 ypos += wrappedRepeatHeightPx;
620 start += wrappedWidthInResidues;
624 drawWrappedDecorators(g, startColumn);
628 * Calculates and saves values needed when rendering a wrapped alignment.
629 * These depend on many factors, including
631 * <li>canvas width and height</li>
632 * <li>number of visible sequences, and height of annotations if shown</li>
633 * <li>font and character width</li>
634 * <li>whether scales are shown left, right or above the alignment</li>
638 * @param canvasHeight
639 * @return the number of residue columns in each width
641 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
643 int charHeight = av.getCharHeight();
646 * vertical space in pixels between wrapped widths of alignment
647 * - one character height, or two if scale above is drawn
649 wrappedSpaceAboveAlignment = charHeight
650 * (av.getScaleAboveWrapped() ? 2 : 1);
653 * compute height in pixels of the wrapped widths
654 * - start with space above plus sequences
656 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
657 wrappedRepeatHeightPx += av.getAlignment().getHeight()
661 * add annotations panel height if shown
662 * also gap between sequences and annotations
664 if (av.isShowAnnotation())
666 wrappedRepeatHeightPx += getAnnotationHeight();
667 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
671 * number of visible widths (the last one may be part height),
672 * ensuring a part height includes at least one sequence
674 ViewportRanges ranges = av.getRanges();
675 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
676 int remainder = canvasHeight % wrappedRepeatHeightPx;
677 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
679 wrappedVisibleWidths++;
683 * compute width in residues; this also sets East and West label widths
685 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
688 * limit visibleWidths to not exceed width of alignment
690 int xMax = ranges.getVisibleAlignmentWidth();
691 int startToEnd = xMax - ranges.getStartRes();
692 int maxWidths = startToEnd / wrappedWidthInResidues;
693 if (startToEnd % wrappedWidthInResidues > 0)
697 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
699 return wrappedWidthInResidues;
703 * Draws one width of a wrapped alignment, including sequences and
704 * annnotations, if shown, but not scales or hidden column markers
710 * @param canvasHeight
712 protected void drawWrappedWidth(Graphics g, final int ypos,
713 final int startColumn, final int endColumn,
714 final int canvasHeight)
716 ViewportRanges ranges = av.getRanges();
717 int viewportWidth = ranges.getViewportWidth();
719 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
722 * move right before drawing by the width of the scale left (if any)
723 * plus column offset from left margin (usually zero, but may be non-zero
724 * when fast painting is drawing just a few columns)
726 int charWidth = av.getCharWidth();
727 int xOffset = labelWidthWest
728 + ((startColumn - ranges.getStartRes()) % viewportWidth)
731 g.translate(xOffset, 0);
734 * white fill the region to be drawn (so incremental fast paint doesn't
735 * scribble over an existing image)
737 g.setColor(Color.white);
738 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
739 wrappedRepeatHeightPx);
741 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
744 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
746 if (av.isShowAnnotation())
748 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
749 g.translate(0, yShift);
750 if (annotations == null)
752 annotations = new AnnotationPanel(av);
755 annotations.renderer.drawComponent(annotations, av, g, -1,
756 startColumn, endx + 1);
757 g.translate(0, -yShift);
759 g.translate(-xOffset, 0);
763 * Draws scales left, right and above (if shown), and any hidden column
764 * markers, on all widths of the wrapped alignment
769 protected void drawWrappedDecorators(Graphics g, final int startColumn)
771 int charWidth = av.getCharWidth();
773 g.setFont(av.getFont());
775 g.setColor(Color.black);
777 int ypos = wrappedSpaceAboveAlignment;
778 ViewportRanges ranges = av.getRanges();
779 int viewportWidth = ranges.getViewportWidth();
780 int maxWidth = ranges.getVisibleAlignmentWidth();
782 int startCol = startColumn;
784 while (widthsDrawn < wrappedVisibleWidths)
786 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
788 if (av.getScaleLeftWrapped())
790 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
793 if (av.getScaleRightWrapped())
795 int x = labelWidthWest + viewportWidth * charWidth;
798 drawVerticalScale(g, startCol, endColumn, ypos, false);
803 * white fill region of scale above and hidden column markers
804 * (to support incremental fast paint of image)
806 g.translate(labelWidthWest, 0);
807 g.setColor(Color.white);
808 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
809 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
810 g.setColor(Color.black);
811 g.translate(-labelWidthWest, 0);
813 g.translate(labelWidthWest, 0);
815 if (av.getScaleAboveWrapped())
817 drawNorthScale(g, startCol, endColumn, ypos);
820 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
822 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
825 g.translate(-labelWidthWest, 0);
827 ypos += wrappedRepeatHeightPx;
828 startCol += viewportWidth;
834 * Draws markers (triangles) above hidden column positions between startColumn
842 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
843 int startColumn, int endColumn)
845 int charHeight = av.getCharHeight();
846 int charWidth = av.getCharWidth();
848 g.setColor(Color.blue);
850 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
852 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
856 res = it.next() - startColumn;
858 if (res < 0 || res > endColumn - startColumn + 1)
864 * draw a downward-pointing triangle at the hidden columns location
865 * (before the following visible column)
867 int xMiddle = res * charWidth;
868 int[] xPoints = new int[] { xMiddle - charHeight / 4,
869 xMiddle + charHeight / 4, xMiddle };
870 int yTop = ypos - (charHeight / 2);
871 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
872 g.fillPolygon(xPoints, yPoints, 3);
877 * Draw a selection group over a wrapped alignment
879 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
881 int canvasHeight, int startRes)
883 int charHeight = av.getCharHeight();
884 int charWidth = av.getCharWidth();
886 // height gap above each panel
887 int hgap = charHeight;
888 if (av.getScaleAboveWrapped())
893 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
895 int cHeight = av.getAlignment().getHeight() * charHeight;
897 int startx = startRes;
899 int ypos = hgap; // vertical offset
900 int maxwidth = av.getAlignment().getVisibleWidth();
902 // chop the wrapped alignment extent up into panel-sized blocks and treat
903 // each block as if it were a block from an unwrapped alignment
904 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
905 BasicStroke.JOIN_ROUND, 3f, new float[]
907 g.setColor(Color.RED);
908 while ((ypos <= canvasHeight) && (startx < maxwidth))
910 // set end value to be start + width, or maxwidth, whichever is smaller
911 endx = startx + cWidth - 1;
918 g.translate(labelWidthWest, 0);
920 drawUnwrappedSelection(g, group, startx, endx, 0,
921 av.getAlignment().getHeight() - 1,
924 g.translate(-labelWidthWest, 0);
926 // update vertical offset
927 ypos += cHeight + getAnnotationHeight() + hgap;
929 // update horizontal offset
932 g.setStroke(new BasicStroke());
935 int getAnnotationHeight()
937 if (!av.isShowAnnotation())
942 if (annotations == null)
944 annotations = new AnnotationPanel(av);
947 return annotations.adjustPanelHeight();
951 * Draws the visible region of the alignment on the graphics context. If there
952 * are hidden column markers in the visible region, then each sub-region
953 * between the markers is drawn separately, followed by the hidden column
957 * the graphics context, positioned at the first residue to be drawn
959 * offset of the first column to draw (0..)
961 * offset of the last column to draw (0..)
963 * offset of the first sequence to draw (0..)
965 * offset of the last sequence to draw (0..)
967 * vertical offset at which to draw (for wrapped alignments)
969 public void drawPanel(Graphics g1, final int startRes, final int endRes,
970 final int startSeq, final int endSeq, final int yOffset)
972 int charHeight = av.getCharHeight();
973 int charWidth = av.getCharWidth();
975 if (!av.hasHiddenColumns())
977 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
985 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
986 VisibleContigsIterator regions = hidden
987 .getVisContigsIterator(startRes, endRes + 1, true);
989 while (regions.hasNext())
991 int[] region = regions.next();
992 blockEnd = region[1];
993 blockStart = region[0];
996 * draw up to just before the next hidden region, or the end of
997 * the visible region, whichever comes first
999 g1.translate(screenY * charWidth, 0);
1001 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1004 * draw the downline of the hidden column marker (ScalePanel draws the
1005 * triangle on top) if we reached it
1007 if (av.getShowHiddenMarkers()
1008 && (regions.hasNext() || regions.endsAtHidden()))
1010 g1.setColor(Color.blue);
1012 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1013 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1014 (endSeq - startSeq + 1) * charHeight + yOffset);
1017 g1.translate(-screenY * charWidth, 0);
1018 screenY += blockEnd - blockStart + 1;
1025 * Draws a region of the visible alignment
1029 * offset of the first column in the visible region (0..)
1031 * offset of the last column in the visible region (0..)
1033 * offset of the first sequence in the visible region (0..)
1035 * offset of the last sequence in the visible region (0..)
1037 * vertical offset at which to draw (for wrapped alignments)
1039 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1040 int endSeq, int offset)
1042 int charHeight = av.getCharHeight();
1043 int charWidth = av.getCharWidth();
1045 g.setFont(av.getFont());
1046 seqRdr.prepare(g, av.isRenderGaps());
1050 // / First draw the sequences
1051 // ///////////////////////////
1052 for (int i = startSeq; i <= endSeq; i++)
1054 nextSeq = av.getAlignment().getSequenceAt(i);
1055 if (nextSeq == null)
1057 // occasionally, a race condition occurs such that the alignment row is
1061 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1062 startRes, endRes, offset + ((i - startSeq) * charHeight));
1064 if (av.isShowSequenceFeatures())
1066 fr.drawSequence(g, nextSeq, startRes, endRes,
1067 offset + ((i - startSeq) * charHeight), false);
1071 * highlight search Results once sequence has been drawn
1073 if (av.hasSearchResults())
1075 SearchResultsI searchResults = av.getSearchResults();
1076 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1078 if (visibleResults != null)
1080 for (int r = 0; r < visibleResults.length; r += 2)
1082 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1083 visibleResults[r + 1],
1084 (visibleResults[r] - startRes) * charWidth,
1085 offset + ((i - startSeq) * charHeight));
1091 if (av.getSelectionGroup() != null
1092 || av.getAlignment().getGroups().size() > 0)
1094 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1100 * Draws the outlines of any groups defined on the alignment (excluding the
1101 * current selection group, if any)
1110 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1111 int startSeq, int endSeq, int offset)
1113 Graphics2D g = (Graphics2D) g1;
1115 SequenceGroup group = null;
1116 int groupIndex = -1;
1118 if (av.getAlignment().getGroups().size() > 0)
1120 group = av.getAlignment().getGroups().get(0);
1128 g.setColor(group.getOutlineColour());
1129 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1133 if (groupIndex >= av.getAlignment().getGroups().size())
1137 group = av.getAlignment().getGroups().get(groupIndex);
1138 } while (groupIndex < av.getAlignment().getGroups().size());
1143 * Draws the outline of the current selection group (if any)
1151 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1152 int startSeq, int endSeq)
1154 SequenceGroup group = av.getSelectionGroup();
1160 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1161 BasicStroke.JOIN_ROUND, 3f, new float[]
1163 g.setColor(Color.RED);
1164 if (!av.getWrapAlignment())
1166 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1171 drawWrappedSelection(g, group, getWidth(), getHeight(),
1172 av.getRanges().getStartRes());
1174 g.setStroke(new BasicStroke());
1178 * Draw the cursor as a separate image and overlay
1181 * start residue of area to draw cursor in
1183 * end residue of area to draw cursor in
1185 * start sequence of area to draw cursor in
1187 * end sequence of are to draw cursor in
1188 * @return a transparent image of the same size as the sequence canvas, with
1189 * the cursor drawn on it, if any
1191 private void drawCursor(Graphics g, int startRes, int endRes,
1195 // convert the cursorY into a position on the visible alignment
1196 int cursor_ypos = cursorY;
1198 // don't do work unless we have to
1199 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1203 int startx = startRes;
1206 // convert the cursorX into a position on the visible alignment
1207 int cursor_xpos = av.getAlignment().getHiddenColumns()
1208 .absoluteToVisibleColumn(cursorX);
1210 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1213 if (av.getWrapAlignment())
1215 // work out the correct offsets for the cursor
1216 int charHeight = av.getCharHeight();
1217 int charWidth = av.getCharWidth();
1218 int canvasWidth = getWidth();
1219 int canvasHeight = getHeight();
1221 // height gap above each panel
1222 int hgap = charHeight;
1223 if (av.getScaleAboveWrapped())
1228 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1230 int cHeight = av.getAlignment().getHeight() * charHeight;
1232 endx = startx + cWidth - 1;
1233 int ypos = hgap; // vertical offset
1235 // iterate down the wrapped panels
1236 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1238 // update vertical offset
1239 ypos += cHeight + getAnnotationHeight() + hgap;
1241 // update horizontal offset
1243 endx = startx + cWidth - 1;
1246 xoffset = labelWidthWest;
1249 // now check if cursor is within range for x values
1250 if (cursor_xpos >= startx && cursor_xpos <= endx)
1252 // get the character the cursor is drawn at
1253 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1254 char s = seq.getCharAt(cursorX);
1256 seqRdr.drawCursor(g, s,
1257 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1258 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1266 * Draw a selection group over an unwrapped alignment
1269 * graphics object to draw with
1273 * start residue of area to draw
1275 * end residue of area to draw
1277 * start sequence of area to draw
1279 * end sequence of area to draw
1281 * vertical offset (used when called from wrapped alignment code)
1283 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1284 int startRes, int endRes, int startSeq, int endSeq, int offset)
1286 int charWidth = av.getCharWidth();
1288 if (!av.hasHiddenColumns())
1290 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1295 // package into blocks of visible columns
1300 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1301 VisibleContigsIterator regions = hidden
1302 .getVisContigsIterator(startRes, endRes + 1, true);
1303 while (regions.hasNext())
1305 int[] region = regions.next();
1306 blockEnd = region[1];
1307 blockStart = region[0];
1309 g.translate(screenY * charWidth, 0);
1310 drawPartialGroupOutline(g, group,
1311 blockStart, blockEnd, startSeq, endSeq, offset);
1313 g.translate(-screenY * charWidth, 0);
1314 screenY += blockEnd - blockStart + 1;
1320 * Draws part of a selection group outline
1328 * @param verticalOffset
1330 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1331 int startRes, int endRes, int startSeq, int endSeq,
1334 int charHeight = av.getCharHeight();
1335 int charWidth = av.getCharWidth();
1336 int visWidth = (endRes - startRes + 1) * charWidth;
1340 boolean inGroup = false;
1345 List<SequenceI> seqs = group.getSequences(null);
1347 // position of start residue of group relative to startRes, in pixels
1348 int sx = (group.getStartRes() - startRes) * charWidth;
1350 // width of group in pixels
1351 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1354 if (!(sx + xwidth < 0 || sx > visWidth))
1356 for (i = startSeq; i <= endSeq; i++)
1358 sy = verticalOffset + (i - startSeq) * charHeight;
1360 if ((sx <= (endRes - startRes) * charWidth)
1361 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1364 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1366 bottom = sy + charHeight;
1371 if (((top == -1) && (i == 0)) || !seqs
1372 .contains(av.getAlignment().getSequenceAt(i - 1)))
1383 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1384 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1386 // reset top and bottom
1394 sy = verticalOffset + ((i - startSeq) * charHeight);
1395 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1396 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1402 * Draw horizontal selection group boundaries at top and bottom positions
1405 * graphics object to draw on
1411 * visWidth maximum available width
1413 * position to draw top of group at
1415 * position to draw bottom of group at
1417 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1418 int visWidth, int top, int bottom)
1428 // don't let width extend beyond current block, or group extent
1430 if (startx + width >= visWidth)
1432 width = visWidth - startx;
1437 g.drawLine(startx, top, startx + width, top);
1442 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1447 * Draw vertical lines at sx and sx+xwidth providing they lie within
1451 * graphics object to draw on
1457 * visWidth maximum available width
1463 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1466 // if start position is visible, draw vertical line to left of
1468 if (sx >= 0 && sx < visWidth)
1470 g.drawLine(sx, oldY, sx, sy);
1473 // if end position is visible, draw vertical line to right of
1475 if (sx + xwidth < visWidth)
1477 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1482 * Highlights search results in the visible region by rendering as white text
1483 * on a black background. Any previous highlighting is removed. Answers true
1484 * if any highlight was left on the visible alignment (so status bar should be
1485 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1486 * so allows the next repaint to update the whole display.
1491 public boolean highlightSearchResults(SearchResultsI results)
1493 return highlightSearchResults(results, false);
1498 * Highlights search results in the visible region by rendering as white text
1499 * on a black background. Any previous highlighting is removed. Answers true
1500 * if any highlight was left on the visible alignment (so status bar should be
1501 * set to match), else false.
1503 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1504 * highlighted regions are modified. This speeds up highlighting across linked
1507 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1508 * a wrapped alignment had to be scrolled to show the highlighted region, then
1509 * it should be fully redrawn, otherwise a fast paint can be performed. This
1510 * argument could be removed if fast paint of scrolled wrapped alignment is
1511 * coded in future (JAL-2609).
1514 * @param doFastPaint
1515 * if true, sets a flag so the next repaint only redraws the modified
1519 public boolean highlightSearchResults(SearchResultsI results,
1520 boolean doFastPaint)
1526 boolean wrapped = av.getWrapAlignment();
1529 fastPaint = doFastPaint;
1530 fastpainting = fastPaint;
1533 * to avoid redrawing the whole visible region, we instead
1534 * redraw just the minimal regions to remove previous highlights
1537 SearchResultsI previous = av.getSearchResults();
1538 av.setSearchResults(results);
1539 boolean redrawn = false;
1540 boolean drawn = false;
1543 redrawn = drawMappedPositionsWrapped(previous);
1544 drawn = drawMappedPositionsWrapped(results);
1549 redrawn = drawMappedPositions(previous);
1550 drawn = drawMappedPositions(results);
1555 * if highlights were either removed or added, repaint
1563 * return true only if highlights were added
1569 fastpainting = false;
1574 * Redraws the minimal rectangle in the visible region (if any) that includes
1575 * mapped positions of the given search results. Whether or not positions are
1576 * highlighted depends on the SearchResults set on the Viewport. This allows
1577 * this method to be called to either clear or set highlighting. Answers true
1578 * if any positions were drawn (in which case a repaint is still required),
1584 protected boolean drawMappedPositions(SearchResultsI results)
1586 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1592 * calculate the minimal rectangle to redraw that
1593 * includes both new and existing search results
1595 int firstSeq = Integer.MAX_VALUE;
1597 int firstCol = Integer.MAX_VALUE;
1599 boolean matchFound = false;
1601 ViewportRanges ranges = av.getRanges();
1602 int firstVisibleColumn = ranges.getStartRes();
1603 int lastVisibleColumn = ranges.getEndRes();
1604 AlignmentI alignment = av.getAlignment();
1605 if (av.hasHiddenColumns())
1607 firstVisibleColumn = alignment.getHiddenColumns()
1608 .visibleToAbsoluteColumn(firstVisibleColumn);
1609 lastVisibleColumn = alignment.getHiddenColumns()
1610 .visibleToAbsoluteColumn(lastVisibleColumn);
1613 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1614 .getEndSeq(); seqNo++)
1616 SequenceI seq = alignment.getSequenceAt(seqNo);
1618 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1620 if (visibleResults != null)
1622 for (int i = 0; i < visibleResults.length - 1; i += 2)
1624 int firstMatchedColumn = visibleResults[i];
1625 int lastMatchedColumn = visibleResults[i + 1];
1626 if (firstMatchedColumn <= lastVisibleColumn
1627 && lastMatchedColumn >= firstVisibleColumn)
1630 * found a search results match in the visible region -
1631 * remember the first and last sequence matched, and the first
1632 * and last visible columns in the matched positions
1635 firstSeq = Math.min(firstSeq, seqNo);
1636 lastSeq = Math.max(lastSeq, seqNo);
1637 firstMatchedColumn = Math.max(firstMatchedColumn,
1638 firstVisibleColumn);
1639 lastMatchedColumn = Math.min(lastMatchedColumn,
1641 firstCol = Math.min(firstCol, firstMatchedColumn);
1642 lastCol = Math.max(lastCol, lastMatchedColumn);
1650 if (av.hasHiddenColumns())
1652 firstCol = alignment.getHiddenColumns()
1653 .absoluteToVisibleColumn(firstCol);
1654 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1656 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1657 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1658 Graphics gg = img.getGraphics();
1659 gg.translate(transX, transY);
1660 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1661 gg.translate(-transX, -transY);
1669 public void propertyChange(PropertyChangeEvent evt)
1671 String eventName = evt.getPropertyName();
1672 // System.err.println(">>SeqCanvas propertyChange " + eventName);
1673 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1679 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1682 // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1688 if (eventName.equals(ViewportRanges.STARTRES)
1689 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1691 // Make sure we're not trying to draw a panel
1692 // larger than the visible window
1693 if (eventName.equals(ViewportRanges.STARTRES))
1695 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1699 scrollX = ((int[]) evt.getNewValue())[0]
1700 - ((int[]) evt.getOldValue())[0];
1702 ViewportRanges vpRanges = av.getRanges();
1704 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1705 if (scrollX > range)
1709 else if (scrollX < -range)
1714 // Both scrolling and resizing change viewport ranges: scrolling changes
1715 // both start and end points, but resize only changes end values.
1716 // Here we only want to fastpaint on a scroll, with resize using a normal
1717 // paint, so scroll events are identified as changes to the horizontal or
1718 // vertical start value.
1719 if (eventName.equals(ViewportRanges.STARTRES))
1721 if (av.getWrapAlignment())
1723 fastPaintWrapped(scrollX);
1727 fastPaint(scrollX, 0);
1730 else if (eventName.equals(ViewportRanges.STARTSEQ))
1733 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1735 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1737 if (av.getWrapAlignment())
1739 fastPaintWrapped(scrollX);
1743 fastPaint(scrollX, 0);
1746 else if (eventName.equals(ViewportRanges.STARTSEQ))
1749 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1751 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1753 if (av.getWrapAlignment())
1755 fastPaintWrapped(scrollX);
1761 * Does a minimal update of the image for a scroll movement. This method
1762 * handles scroll movements of up to one width of the wrapped alignment (one
1763 * click in the vertical scrollbar). Larger movements (for example after a
1764 * scroll to highlight a mapped position) trigger a full redraw instead.
1767 * number of positions scrolled (right if positive, left if negative)
1769 protected void fastPaintWrapped(int scrollX)
1771 ViewportRanges ranges = av.getRanges();
1773 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1776 * shift of one view width or more is
1777 * overcomplicated to handle in this method
1784 if (fastpainting || img == null)
1790 fastpainting = true;
1795 Graphics gg = img.getGraphics();
1797 calculateWrappedGeometry(getWidth(), getHeight());
1800 * relocate the regions of the alignment that are still visible
1802 shiftWrappedAlignment(-scrollX);
1805 * add new columns (sequence, annotation)
1806 * - at top left if scrollX < 0
1807 * - at right of last two widths if scrollX > 0
1811 int startRes = ranges.getStartRes();
1812 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1813 - scrollX - 1, getHeight());
1817 fastPaintWrappedAddRight(scrollX);
1821 * draw all scales (if shown) and hidden column markers
1823 drawWrappedDecorators(gg, ranges.getStartRes());
1830 fastpainting = false;
1835 * Draws the specified number of columns at the 'end' (bottom right) of a
1836 * wrapped alignment view, including sequences and annotations if shown, but
1837 * not scales. Also draws the same number of columns at the right hand end of
1838 * the second last width shown, if the last width is not full height (so
1839 * cannot simply be copied from the graphics image).
1843 protected void fastPaintWrappedAddRight(int columns)
1850 Graphics gg = img.getGraphics();
1852 ViewportRanges ranges = av.getRanges();
1853 int viewportWidth = ranges.getViewportWidth();
1854 int charWidth = av.getCharWidth();
1857 * draw full height alignment in the second last row, last columns, if the
1858 * last row was not full height
1860 int visibleWidths = wrappedVisibleWidths;
1861 int canvasHeight = getHeight();
1862 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1864 if (lastWidthPartHeight)
1866 int widthsAbove = Math.max(0, visibleWidths - 2);
1867 int ypos = wrappedRepeatHeightPx * widthsAbove
1868 + wrappedSpaceAboveAlignment;
1869 int endRes = ranges.getEndRes();
1870 endRes += widthsAbove * viewportWidth;
1871 int startRes = endRes - columns;
1872 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1876 * white fill first to erase annotations
1880 gg.translate(xOffset, 0);
1881 gg.setColor(Color.white);
1882 gg.fillRect(labelWidthWest, ypos,
1883 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1884 gg.translate(-xOffset, 0);
1886 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1891 * draw newly visible columns in last wrapped width (none if we
1892 * have reached the end of the alignment)
1893 * y-offset for drawing last width is height of widths above,
1896 int widthsAbove = visibleWidths - 1;
1897 int ypos = wrappedRepeatHeightPx * widthsAbove
1898 + wrappedSpaceAboveAlignment;
1899 int endRes = ranges.getEndRes();
1900 endRes += widthsAbove * viewportWidth;
1901 int startRes = endRes - columns + 1;
1904 * white fill first to erase annotations
1906 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1908 gg.translate(xOffset, 0);
1909 gg.setColor(Color.white);
1910 int width = viewportWidth * charWidth - xOffset;
1911 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1912 gg.translate(-xOffset, 0);
1914 gg.setFont(av.getFont());
1915 gg.setColor(Color.black);
1917 if (startRes < ranges.getVisibleAlignmentWidth())
1919 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1923 * and finally, white fill any space below the visible alignment
1925 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1926 if (heightBelow > 0)
1928 gg.setColor(Color.white);
1929 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1935 * Shifts the visible alignment by the specified number of columns - left if
1936 * negative, right if positive. Copies and moves sequences and annotations (if
1937 * shown). Scales, hidden column markers and any newly visible columns must be
1942 protected void shiftWrappedAlignment(int positions)
1949 Graphics gg = img.getGraphics();
1951 int charWidth = av.getCharWidth();
1953 int canvasHeight = getHeight();
1954 ViewportRanges ranges = av.getRanges();
1955 int viewportWidth = ranges.getViewportWidth();
1956 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1958 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1959 int xMax = ranges.getVisibleAlignmentWidth();
1964 * shift right (after scroll left)
1965 * for each wrapped width (starting with the last), copy (width-positions)
1966 * columns from the left margin to the right margin, and copy positions
1967 * columns from the right margin of the row above (if any) to the
1968 * left margin of the current row
1972 * get y-offset of last wrapped width, first row of sequences
1974 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1975 y += wrappedSpaceAboveAlignment;
1976 int copyFromLeftStart = labelWidthWest;
1977 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1982 * shift 'widthToCopy' residues by 'positions' places to the right
1984 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1985 positions * charWidth, 0);
1989 * copy 'positions' residue from the row above (right hand end)
1990 * to this row's left hand end
1992 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1993 positions * charWidth, heightToCopy, -widthToCopy,
1994 wrappedRepeatHeightPx);
1997 y -= wrappedRepeatHeightPx;
2003 * shift left (after scroll right)
2004 * for each wrapped width (starting with the first), copy (width-positions)
2005 * columns from the right margin to the left margin, and copy positions
2006 * columns from the left margin of the row below (if any) to the
2007 * right margin of the current row
2009 int xpos = av.getRanges().getStartRes();
2010 int y = wrappedSpaceAboveAlignment;
2011 int copyFromRightStart = labelWidthWest - positions * charWidth;
2013 while (y < canvasHeight)
2015 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2016 positions * charWidth, 0);
2017 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2018 && (xpos + viewportWidth <= xMax))
2020 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2021 * charWidth, heightToCopy, widthToCopy,
2022 -wrappedRepeatHeightPx);
2024 y += wrappedRepeatHeightPx;
2025 xpos += viewportWidth;
2033 * Redraws any positions in the search results in the visible region of a
2034 * wrapped alignment. Any highlights are drawn depending on the search results
2035 * set on the Viewport, not the <code>results</code> argument. This allows
2036 * this method to be called either to clear highlights (passing the previous
2037 * search results), or to draw new highlights.
2042 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2044 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2048 int charHeight = av.getCharHeight();
2050 boolean matchFound = false;
2052 calculateWrappedGeometry(getWidth(), getHeight());
2053 int wrappedWidth = av.getWrappedWidth();
2054 int wrappedHeight = wrappedRepeatHeightPx;
2056 ViewportRanges ranges = av.getRanges();
2057 int canvasHeight = getHeight();
2058 int repeats = canvasHeight / wrappedHeight;
2059 if (canvasHeight / wrappedHeight > 0)
2064 int firstVisibleColumn = ranges.getStartRes();
2065 int lastVisibleColumn = ranges.getStartRes() + repeats
2066 * ranges.getViewportWidth() - 1;
2068 AlignmentI alignment = av.getAlignment();
2069 if (av.hasHiddenColumns())
2071 firstVisibleColumn = alignment.getHiddenColumns()
2072 .visibleToAbsoluteColumn(firstVisibleColumn);
2073 lastVisibleColumn = alignment.getHiddenColumns()
2074 .visibleToAbsoluteColumn(lastVisibleColumn);
2077 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2080 Graphics gg = img.getGraphics();
2082 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2083 .getEndSeq(); seqNo++)
2085 SequenceI seq = alignment.getSequenceAt(seqNo);
2087 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2089 if (visibleResults != null)
2091 for (int i = 0; i < visibleResults.length - 1; i += 2)
2093 int firstMatchedColumn = visibleResults[i];
2094 int lastMatchedColumn = visibleResults[i + 1];
2095 if (firstMatchedColumn <= lastVisibleColumn
2096 && lastMatchedColumn >= firstVisibleColumn)
2099 * found a search results match in the visible region
2101 firstMatchedColumn = Math.max(firstMatchedColumn,
2102 firstVisibleColumn);
2103 lastMatchedColumn = Math.min(lastMatchedColumn,
2107 * draw each mapped position separately (as contiguous positions may
2108 * wrap across lines)
2110 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2112 int displayColumn = mappedPos;
2113 if (av.hasHiddenColumns())
2115 displayColumn = alignment.getHiddenColumns()
2116 .absoluteToVisibleColumn(displayColumn);
2120 * transX: offset from left edge of canvas to residue position
2122 int transX = labelWidthWest
2123 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2124 * av.getCharWidth();
2127 * transY: offset from top edge of canvas to residue position
2129 int transY = gapHeight;
2130 transY += (displayColumn - ranges.getStartRes())
2131 / wrappedWidth * wrappedHeight;
2132 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2135 * yOffset is from graphics origin to start of visible region
2137 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2138 if (transY < getHeight())
2141 gg.translate(transX, transY);
2142 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2144 gg.translate(-transX, -transY);
2158 * Answers the width in pixels of the left scale labels (0 if not shown)
2162 int getLabelWidthWest()
2164 return labelWidthWest;