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.RenderingHints;
42 import java.awt.Shape;
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.JComponent;
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 public class SeqCanvas extends JComponent implements ViewportListenerI
59 * pixels gap between sequences and annotations when in wrapped mode
61 static final int SEQS_ANNOTATION_GAP = 3;
63 private static final String ZEROS = "0000000000";
65 final FeatureRenderer fr;
75 private final SequenceRenderer seqRdr;
77 private boolean fastPaint = false;
79 private boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 private int labelWidthEast; // label right width in pixels if shown
88 private int labelWidthWest; // label left width in pixels if shown
90 int wrappedSpaceAboveAlignment; // gap between widths
92 int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
96 private Graphics2D gg;
99 * Creates a new SeqCanvas object.
103 public SeqCanvas(AlignmentPanel ap)
106 fr = new FeatureRenderer(ap);
107 seqRdr = new SequenceRenderer(av);
108 setLayout(new BorderLayout());
109 PaintRefresher.Register(this, av.getSequenceSetId());
110 setBackground(Color.white);
112 av.getRanges().addPropertyChangeListener(this);
115 public SequenceRenderer getSequenceRenderer()
120 public FeatureRenderer getFeatureRenderer()
126 * Draws the scale above a region of a wrapped alignment, consisting of a
127 * column number every major interval (10 columns).
130 * the graphics context to draw on, positioned at the start (bottom
131 * left) of the line on which to draw any scale marks
133 * start alignment column (0..)
135 * end alignment column (0..)
137 * y offset to draw at
139 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
141 int charHeight = av.getCharHeight();
142 int charWidth = av.getCharWidth();
145 * white fill the scale space (for the fastPaint case)
147 g.setColor(Color.white);
148 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
149 charHeight * 3 / 2 + 2);
150 g.setColor(Color.black);
152 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
154 for (ScaleMark mark : marks)
156 int mpos = mark.column; // (i - startx - 1)
161 String mstring = mark.text;
167 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
171 * draw a tick mark below the column number, centred on the column;
172 * height of tick mark is 4 pixels less than half a character
174 int xpos = (mpos * charWidth) + (charWidth / 2);
175 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
181 * Draw the scale to the left or right of a wrapped alignment
184 * graphics context, positioned at the start of the scale to be drawn
186 * first column of wrapped width (0.. excluding any hidden columns)
188 * last column of wrapped width (0.. excluding any hidden columns)
190 * vertical offset at which to begin the scale
192 * if true, scale is left of residues, if false, scale is right
194 void drawVerticalScale(Graphics g, final int startx, final int endx,
195 final int ypos, final boolean left)
197 int charHeight = av.getCharHeight();
198 int charWidth = av.getCharWidth();
200 int yPos = ypos + charHeight;
204 if (av.hasHiddenColumns())
206 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
207 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
208 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
210 FontMetrics fm = getFontMetrics(av.getFont());
212 for (int i = 0; i < av.getAlignment().getHeight(); i++)
214 SequenceI seq = av.getAlignment().getSequenceAt(i);
217 * find sequence position of first non-gapped position -
218 * to the right if scale left, to the left if scale right
220 int index = left ? startX : endX;
222 while (index >= startX && index <= endX)
224 if (!Comparison.isGap(seq.getCharAt(index)))
226 value = seq.findPosition(index);
240 * white fill the space for the scale
242 g.setColor(Color.white);
243 int y = (yPos + (i * charHeight)) - (charHeight / 5);
244 // fillRect origin is top left of rectangle
245 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
251 * draw scale value, right justified within its width less half a
252 * character width padding on the right
254 int labelSpace = left ? labelWidthWest : labelWidthEast;
255 labelSpace -= charWidth / 2; // leave space to the right
256 String valueAsString = String.valueOf(value);
257 int labelLength = fm.stringWidth(valueAsString);
258 int xOffset = labelSpace - labelLength;
259 g.setColor(Color.black);
260 g.drawString(valueAsString, xOffset, y);
266 * Does a fast paint of an alignment in response to a scroll. Most of the
267 * visible region is simply copied and shifted, and then any newly visible
268 * columns or rows are drawn. The scroll may be horizontal or vertical, but
269 * not both at once. Scrolling may be the result of
271 * <li>dragging a scroll bar</li>
272 * <li>clicking in the scroll bar</li>
273 * <li>scrolling by trackpad, middle mouse button, or other device</li>
274 * <li>by moving the box in the Overview window</li>
275 * <li>programmatically to make a highlighted position visible</li>
279 * columns to shift right (positive) or left (negative)
281 * rows to shift down (positive) or up (negative)
283 public void fastPaint(int horizontal, int vertical)
285 if (fastpainting || gg == null || img == null)
294 int charHeight = av.getCharHeight();
295 int charWidth = av.getCharWidth();
297 ViewportRanges ranges = av.getRanges();
298 int startRes = ranges.getStartRes();
299 int endRes = ranges.getEndRes();
300 int startSeq = ranges.getStartSeq();
301 int endSeq = ranges.getEndSeq();
305 gg.copyArea(horizontal * charWidth, vertical * charHeight,
306 img.getWidth(), img.getHeight(), -horizontal * charWidth,
307 -vertical * charHeight);
309 if (horizontal > 0) // scrollbar pulled right, image to the left
311 transX = (endRes - startRes - horizontal) * charWidth;
312 startRes = endRes - horizontal;
314 else if (horizontal < 0)
316 endRes = startRes - horizontal;
319 if (vertical > 0) // scroll down
321 startSeq = endSeq - vertical;
323 if (startSeq < ranges.getStartSeq())
324 { // ie scrolling too fast, more than a page at a time
325 startSeq = ranges.getStartSeq();
329 transY = img.getHeight() - ((vertical + 1) * charHeight);
332 else if (vertical < 0)
334 endSeq = startSeq - vertical;
336 if (endSeq > ranges.getEndSeq())
338 endSeq = ranges.getEndSeq();
342 gg.translate(transX, transY);
343 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
344 gg.translate(-transX, -transY);
346 // Call repaint on alignment panel so that repaints from other alignment
347 // panel components can be aggregated. Otherwise performance of the
348 // overview window and others may be adversely affected.
349 av.getAlignPanel().repaint();
352 fastpainting = false;
357 public void paintComponent(Graphics g)
359 super.paintComponent(g);
361 int charHeight = av.getCharHeight();
362 int charWidth = av.getCharWidth();
364 ViewportRanges ranges = av.getRanges();
366 int width = getWidth();
367 int height = getHeight();
369 width -= (width % charWidth);
370 height -= (height % charHeight);
372 if ((img != null) && (fastPaint
373 || (getVisibleRect().width != g.getClipBounds().width)
374 || (getVisibleRect().height != g.getClipBounds().height)))
376 g.drawImage(img, 0, 0, this);
378 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
379 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
383 else if (width > 0 && height > 0)
386 * img is a cached version of the last view we drew, if any
387 * if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
392 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 drawSelectionGroup(gg, ranges.getStartRes(),
417 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
419 g.drawImage(img, 0, 0, this);
424 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
425 ranges.getStartSeq(), ranges.getEndSeq());
430 * Draw an alignment panel for printing
433 * Graphics object to draw with
435 * start residue of print area
437 * end residue of print area
439 * start sequence of print area
441 * end sequence of print area
443 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
444 int startSeq, int endSeq)
446 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
448 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
453 * Draw a wrapped alignment panel for printing
456 * Graphics object to draw with
458 * width of drawing area
459 * @param canvasHeight
460 * height of drawing area
462 * start residue of print area
464 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
465 int canvasHeight, int startRes)
467 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
469 SequenceGroup group = av.getSelectionGroup();
472 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
478 * Returns the visible width of the canvas in residues, after allowing for
479 * East or West scales (if shown)
482 * the width in pixels (possibly including scales)
486 public int getWrappedCanvasWidth(int canvasWidth)
488 int charWidth = av.getCharWidth();
490 FontMetrics fm = getFontMetrics(av.getFont());
494 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
496 labelWidth = getLabelWidth(fm);
499 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
501 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
503 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
507 * Returns a pixel width sufficient to show the largest sequence coordinate
508 * (end position) in the alignment, calculated as the FontMetrics width of
509 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
510 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
511 * half a character width space on either side.
516 protected int getLabelWidth(FontMetrics fm)
519 * find the biggest sequence end position we need to show
520 * (note this is not necessarily the sequence length)
523 AlignmentI alignment = av.getAlignment();
524 for (int i = 0; i < alignment.getHeight(); i++)
526 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
530 for (int i = maxWidth; i > 0; i /= 10)
535 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
539 * Draws as many widths of a wrapped alignment as can fit in the visible
544 * available width in pixels
545 * @param canvasHeight
546 * available height in pixels
548 * the first column (0...) of the alignment to draw
550 public void drawWrappedPanel(Graphics g, int canvasWidth,
551 int canvasHeight, final int startColumn)
553 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
556 av.setWrappedWidth(wrappedWidthInResidues);
558 ViewportRanges ranges = av.getRanges();
559 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
561 // we need to call this again to make sure the startColumn +
562 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
564 calculateWrappedGeometry(canvasWidth, canvasHeight);
567 * draw one width at a time (excluding any scales shown),
568 * until we have run out of either alignment or vertical space available
570 int ypos = wrappedSpaceAboveAlignment;
571 int maxWidth = ranges.getVisibleAlignmentWidth();
573 int start = startColumn;
574 int currentWidth = 0;
575 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
578 .min(maxWidth, start + wrappedWidthInResidues - 1);
579 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
580 ypos += wrappedRepeatHeightPx;
581 start += wrappedWidthInResidues;
585 drawWrappedDecorators(g, startColumn);
589 * Calculates and saves values needed when rendering a wrapped alignment.
590 * These depend on many factors, including
592 * <li>canvas width and height</li>
593 * <li>number of visible sequences, and height of annotations if shown</li>
594 * <li>font and character width</li>
595 * <li>whether scales are shown left, right or above the alignment</li>
599 * @param canvasHeight
600 * @return the number of residue columns in each width
602 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
604 int charHeight = av.getCharHeight();
607 * vertical space in pixels between wrapped widths of alignment
608 * - one character height, or two if scale above is drawn
610 wrappedSpaceAboveAlignment = charHeight
611 * (av.getScaleAboveWrapped() ? 2 : 1);
614 * compute height in pixels of the wrapped widths
615 * - start with space above plus sequences
617 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
618 wrappedRepeatHeightPx += av.getAlignment().getHeight()
622 * add annotations panel height if shown
623 * also gap between sequences and annotations
625 if (av.isShowAnnotation())
627 wrappedRepeatHeightPx += getAnnotationHeight();
628 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
632 * number of visible widths (the last one may be part height),
633 * ensuring a part height includes at least one sequence
635 ViewportRanges ranges = av.getRanges();
636 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
637 int remainder = canvasHeight % wrappedRepeatHeightPx;
638 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
640 wrappedVisibleWidths++;
644 * compute width in residues; this also sets East and West label widths
646 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
649 * limit visibleWidths to not exceed width of alignment
651 int xMax = ranges.getVisibleAlignmentWidth();
652 int startToEnd = xMax - ranges.getStartRes();
653 int maxWidths = startToEnd / wrappedWidthInResidues;
654 if (startToEnd % wrappedWidthInResidues > 0)
658 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
660 return wrappedWidthInResidues;
664 * Draws one width of a wrapped alignment, including sequences and
665 * annnotations, if shown, but not scales or hidden column markers
671 * @param canvasHeight
673 protected void drawWrappedWidth(Graphics g, final int ypos,
674 final int startColumn, final int endColumn,
675 final int canvasHeight)
677 ViewportRanges ranges = av.getRanges();
678 int viewportWidth = ranges.getViewportWidth();
680 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
683 * move right before drawing by the width of the scale left (if any)
684 * plus column offset from left margin (usually zero, but may be non-zero
685 * when fast painting is drawing just a few columns)
687 int charWidth = av.getCharWidth();
688 int xOffset = labelWidthWest
689 + ((startColumn - ranges.getStartRes()) % viewportWidth)
691 g.translate(xOffset, 0);
693 // When printing we have an extra clipped region,
694 // the Printable page which we need to account for here
695 Shape clip = g.getClip();
699 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
703 g.setClip(0, (int) clip.getBounds().getY(),
704 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
708 * white fill the region to be drawn (so incremental fast paint doesn't
709 * scribble over an existing image)
711 g.setColor(Color.white);
712 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
713 wrappedRepeatHeightPx);
715 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
718 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
720 if (av.isShowAnnotation())
722 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
723 g.translate(0, yShift);
724 if (annotations == null)
726 annotations = new AnnotationPanel(av);
729 annotations.renderer.drawComponent(annotations, av, g, -1,
730 startColumn, endx + 1);
731 g.translate(0, -yShift);
734 g.translate(-xOffset, 0);
738 * Draws scales left, right and above (if shown), and any hidden column
739 * markers, on all widths of the wrapped alignment
744 protected void drawWrappedDecorators(Graphics g, final int startColumn)
746 int charWidth = av.getCharWidth();
748 g.setFont(av.getFont());
749 g.setColor(Color.black);
751 int ypos = wrappedSpaceAboveAlignment;
752 ViewportRanges ranges = av.getRanges();
753 int viewportWidth = ranges.getViewportWidth();
754 int maxWidth = ranges.getVisibleAlignmentWidth();
756 int startCol = startColumn;
758 while (widthsDrawn < wrappedVisibleWidths)
760 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
762 if (av.getScaleLeftWrapped())
764 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
767 if (av.getScaleRightWrapped())
769 int x = labelWidthWest + viewportWidth * charWidth;
771 drawVerticalScale(g, startCol, endColumn, ypos, false);
776 * white fill region of scale above and hidden column markers
777 * (to support incremental fast paint of image)
779 g.translate(labelWidthWest, 0);
780 g.setColor(Color.white);
781 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
782 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
783 g.setColor(Color.black);
784 g.translate(-labelWidthWest, 0);
786 g.translate(labelWidthWest, 0);
788 if (av.getScaleAboveWrapped())
790 drawNorthScale(g, startCol, endColumn, ypos);
793 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
795 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
798 g.translate(-labelWidthWest, 0);
800 ypos += wrappedRepeatHeightPx;
801 startCol += viewportWidth;
807 * Draws markers (triangles) above hidden column positions between startColumn
815 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
816 int startColumn, int endColumn)
818 int charHeight = av.getCharHeight();
819 int charWidth = av.getCharWidth();
821 g.setColor(Color.blue);
823 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
825 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
829 res = it.next() - startColumn;
831 if (res < 0 || res > endColumn - startColumn + 1)
837 * draw a downward-pointing triangle at the hidden columns location
838 * (before the following visible column)
840 int xMiddle = res * charWidth;
841 int[] xPoints = new int[] { xMiddle - charHeight / 4,
842 xMiddle + charHeight / 4, xMiddle };
843 int yTop = ypos - (charHeight / 2);
844 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
845 g.fillPolygon(xPoints, yPoints, 3);
850 * Draw a selection group over a wrapped alignment
852 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
854 int canvasHeight, int startRes)
856 int charHeight = av.getCharHeight();
857 int charWidth = av.getCharWidth();
859 // height gap above each panel
860 int hgap = charHeight;
861 if (av.getScaleAboveWrapped())
866 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
868 int cHeight = av.getAlignment().getHeight() * charHeight;
870 int startx = startRes;
872 int ypos = hgap; // vertical offset
873 int maxwidth = av.getAlignment().getVisibleWidth();
875 // chop the wrapped alignment extent up into panel-sized blocks and treat
876 // each block as if it were a block from an unwrapped alignment
877 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
878 BasicStroke.JOIN_ROUND, 3f, new float[]
880 g.setColor(Color.RED);
881 while ((ypos <= canvasHeight) && (startx < maxwidth))
883 // set end value to be start + width, or maxwidth, whichever is smaller
884 endx = startx + cWidth - 1;
891 g.translate(labelWidthWest, 0);
893 drawUnwrappedSelection(g, group, startx, endx, 0,
894 av.getAlignment().getHeight() - 1,
897 g.translate(-labelWidthWest, 0);
899 // update vertical offset
900 ypos += cHeight + getAnnotationHeight() + hgap;
902 // update horizontal offset
905 g.setStroke(new BasicStroke());
908 int getAnnotationHeight()
910 if (!av.isShowAnnotation())
915 if (annotations == null)
917 annotations = new AnnotationPanel(av);
920 return annotations.adjustPanelHeight();
924 * Draws the visible region of the alignment on the graphics context. If there
925 * are hidden column markers in the visible region, then each sub-region
926 * between the markers is drawn separately, followed by the hidden column
930 * the graphics context, positioned at the first residue to be drawn
932 * offset of the first column to draw (0..)
934 * offset of the last column to draw (0..)
936 * offset of the first sequence to draw (0..)
938 * offset of the last sequence to draw (0..)
940 * vertical offset at which to draw (for wrapped alignments)
942 public void drawPanel(Graphics g1, final int startRes, final int endRes,
943 final int startSeq, final int endSeq, final int yOffset)
945 int charHeight = av.getCharHeight();
946 int charWidth = av.getCharWidth();
948 if (!av.hasHiddenColumns())
950 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
958 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
959 VisibleContigsIterator regions = hidden
960 .getVisContigsIterator(startRes, endRes + 1, true);
962 while (regions.hasNext())
964 int[] region = regions.next();
965 blockEnd = region[1];
966 blockStart = region[0];
969 * draw up to just before the next hidden region, or the end of
970 * the visible region, whichever comes first
972 g1.translate(screenY * charWidth, 0);
974 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
977 * draw the downline of the hidden column marker (ScalePanel draws the
978 * triangle on top) if we reached it
980 if (av.getShowHiddenMarkers()
981 && (regions.hasNext() || regions.endsAtHidden()))
983 g1.setColor(Color.blue);
985 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
986 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
987 (endSeq - startSeq + 1) * charHeight + yOffset);
990 g1.translate(-screenY * charWidth, 0);
991 screenY += blockEnd - blockStart + 1;
998 * Draws a region of the visible alignment
1002 * offset of the first column in the visible region (0..)
1004 * offset of the last column in the visible region (0..)
1006 * offset of the first sequence in the visible region (0..)
1008 * offset of the last sequence in the visible region (0..)
1010 * vertical offset at which to draw (for wrapped alignments)
1012 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1013 int endSeq, int offset)
1015 int charHeight = av.getCharHeight();
1016 int charWidth = av.getCharWidth();
1018 g.setFont(av.getFont());
1019 seqRdr.prepare(g, av.isRenderGaps());
1023 // / First draw the sequences
1024 // ///////////////////////////
1025 for (int i = startSeq; i <= endSeq; i++)
1027 nextSeq = av.getAlignment().getSequenceAt(i);
1028 if (nextSeq == null)
1030 // occasionally, a race condition occurs such that the alignment row is
1034 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1035 startRes, endRes, offset + ((i - startSeq) * charHeight));
1037 if (av.isShowSequenceFeatures())
1039 fr.drawSequence(g, nextSeq, startRes, endRes,
1040 offset + ((i - startSeq) * charHeight), false);
1044 * highlight search Results once sequence has been drawn
1046 if (av.hasSearchResults())
1048 SearchResultsI searchResults = av.getSearchResults();
1049 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1051 if (visibleResults != null)
1053 for (int r = 0; r < visibleResults.length; r += 2)
1055 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1056 visibleResults[r + 1],
1057 (visibleResults[r] - startRes) * charWidth,
1058 offset + ((i - startSeq) * charHeight));
1064 if (av.getSelectionGroup() != null
1065 || av.getAlignment().getGroups().size() > 0)
1067 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1073 * Draws the outlines of any groups defined on the alignment (excluding the
1074 * current selection group, if any)
1083 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1084 int startSeq, int endSeq, int offset)
1086 Graphics2D g = (Graphics2D) g1;
1088 SequenceGroup group = null;
1089 int groupIndex = -1;
1091 if (av.getAlignment().getGroups().size() > 0)
1093 group = av.getAlignment().getGroups().get(0);
1101 g.setColor(group.getOutlineColour());
1103 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1108 if (groupIndex >= av.getAlignment().getGroups().size())
1113 group = av.getAlignment().getGroups().get(groupIndex);
1115 } while (groupIndex < av.getAlignment().getGroups().size());
1122 * Draws the outline of the current selection group (if any)
1130 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1131 int startSeq, int endSeq)
1133 SequenceGroup group = av.getSelectionGroup();
1139 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1140 BasicStroke.JOIN_ROUND, 3f, new float[]
1142 g.setColor(Color.RED);
1143 if (!av.getWrapAlignment())
1145 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1150 drawWrappedSelection(g, group, getWidth(), getHeight(),
1151 av.getRanges().getStartRes());
1153 g.setStroke(new BasicStroke());
1157 * Draw the cursor as a separate image and overlay
1160 * start residue of area to draw cursor in
1162 * end residue of area to draw cursor in
1164 * start sequence of area to draw cursor in
1166 * end sequence of are to draw cursor in
1167 * @return a transparent image of the same size as the sequence canvas, with
1168 * the cursor drawn on it, if any
1170 private void drawCursor(Graphics g, int startRes, int endRes,
1174 // convert the cursorY into a position on the visible alignment
1175 int cursor_ypos = cursorY;
1177 // don't do work unless we have to
1178 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1182 int startx = startRes;
1185 // convert the cursorX into a position on the visible alignment
1186 int cursor_xpos = av.getAlignment().getHiddenColumns()
1187 .absoluteToVisibleColumn(cursorX);
1189 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1192 if (av.getWrapAlignment())
1194 // work out the correct offsets for the cursor
1195 int charHeight = av.getCharHeight();
1196 int charWidth = av.getCharWidth();
1197 int canvasWidth = getWidth();
1198 int canvasHeight = getHeight();
1200 // height gap above each panel
1201 int hgap = charHeight;
1202 if (av.getScaleAboveWrapped())
1207 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1209 int cHeight = av.getAlignment().getHeight() * charHeight;
1211 endx = startx + cWidth - 1;
1212 int ypos = hgap; // vertical offset
1214 // iterate down the wrapped panels
1215 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1217 // update vertical offset
1218 ypos += cHeight + getAnnotationHeight() + hgap;
1220 // update horizontal offset
1222 endx = startx + cWidth - 1;
1225 xoffset = labelWidthWest;
1228 // now check if cursor is within range for x values
1229 if (cursor_xpos >= startx && cursor_xpos <= endx)
1231 // get the character the cursor is drawn at
1232 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1233 char s = seq.getCharAt(cursorX);
1235 seqRdr.drawCursor(g, s,
1236 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1237 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1245 * Draw a selection group over an unwrapped alignment
1248 * graphics object to draw with
1252 * start residue of area to draw
1254 * end residue of area to draw
1256 * start sequence of area to draw
1258 * end sequence of area to draw
1260 * vertical offset (used when called from wrapped alignment code)
1262 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1263 int startRes, int endRes, int startSeq, int endSeq, int offset)
1265 int charWidth = av.getCharWidth();
1267 if (!av.hasHiddenColumns())
1269 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1274 // package into blocks of visible columns
1279 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1280 VisibleContigsIterator regions = hidden
1281 .getVisContigsIterator(startRes, endRes + 1, true);
1282 while (regions.hasNext())
1284 int[] region = regions.next();
1285 blockEnd = region[1];
1286 blockStart = region[0];
1288 g.translate(screenY * charWidth, 0);
1289 drawPartialGroupOutline(g, group,
1290 blockStart, blockEnd, startSeq, endSeq, offset);
1292 g.translate(-screenY * charWidth, 0);
1293 screenY += blockEnd - blockStart + 1;
1299 * Draws part of a selection group outline
1307 * @param verticalOffset
1309 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1310 int startRes, int endRes, int startSeq, int endSeq,
1313 int charHeight = av.getCharHeight();
1314 int charWidth = av.getCharWidth();
1315 int visWidth = (endRes - startRes + 1) * charWidth;
1319 boolean inGroup = false;
1324 List<SequenceI> seqs = group.getSequences(null);
1326 // position of start residue of group relative to startRes, in pixels
1327 int sx = (group.getStartRes() - startRes) * charWidth;
1329 // width of group in pixels
1330 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1333 if (!(sx + xwidth < 0 || sx > visWidth))
1335 for (i = startSeq; i <= endSeq; i++)
1337 sy = verticalOffset + (i - startSeq) * charHeight;
1339 if ((sx <= (endRes - startRes) * charWidth)
1340 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1343 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1345 bottom = sy + charHeight;
1350 if (((top == -1) && (i == 0)) || !seqs
1351 .contains(av.getAlignment().getSequenceAt(i - 1)))
1362 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1363 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1365 // reset top and bottom
1373 sy = verticalOffset + ((i - startSeq) * charHeight);
1374 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1375 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1381 * Draw horizontal selection group boundaries at top and bottom positions
1384 * graphics object to draw on
1390 * visWidth maximum available width
1392 * position to draw top of group at
1394 * position to draw bottom of group at
1396 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1397 int visWidth, int top, int bottom)
1407 // don't let width extend beyond current block, or group extent
1409 if (startx + width >= visWidth)
1411 width = visWidth - startx;
1416 g.drawLine(startx, top, startx + width, top);
1421 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1426 * Draw vertical lines at sx and sx+xwidth providing they lie within
1430 * graphics object to draw on
1436 * visWidth maximum available width
1442 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1445 // if start position is visible, draw vertical line to left of
1447 if (sx >= 0 && sx < visWidth)
1449 g.drawLine(sx, oldY, sx, sy);
1452 // if end position is visible, draw vertical line to right of
1454 if (sx + xwidth < visWidth)
1456 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1461 * Highlights search results in the visible region by rendering as white text
1462 * on a black background. Any previous highlighting is removed. Answers true
1463 * if any highlight was left on the visible alignment (so status bar should be
1464 * set to match), else false.
1466 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1467 * alignment had to be scrolled to show the highlighted region, then it should
1468 * be fully redrawn, otherwise a fast paint can be performed. This argument
1469 * could be removed if fast paint of scrolled wrapped alignment is coded in
1470 * future (JAL-2609).
1473 * @param noFastPaint
1476 public boolean highlightSearchResults(SearchResultsI results,
1477 boolean noFastPaint)
1483 boolean wrapped = av.getWrapAlignment();
1486 fastPaint = !noFastPaint;
1487 fastpainting = fastPaint;
1490 * to avoid redrawing the whole visible region, we instead
1491 * redraw just the minimal regions to remove previous highlights
1494 SearchResultsI previous = av.getSearchResults();
1495 av.setSearchResults(results);
1496 boolean redrawn = false;
1497 boolean drawn = false;
1500 redrawn = drawMappedPositionsWrapped(previous);
1501 drawn = drawMappedPositionsWrapped(results);
1506 redrawn = drawMappedPositions(previous);
1507 drawn = drawMappedPositions(results);
1512 * if highlights were either removed or added, repaint
1520 * return true only if highlights were added
1526 fastpainting = false;
1531 * Redraws the minimal rectangle in the visible region (if any) that includes
1532 * mapped positions of the given search results. Whether or not positions are
1533 * highlighted depends on the SearchResults set on the Viewport. This allows
1534 * this method to be called to either clear or set highlighting. Answers true
1535 * if any positions were drawn (in which case a repaint is still required),
1541 protected boolean drawMappedPositions(SearchResultsI results)
1543 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1549 * calculate the minimal rectangle to redraw that
1550 * includes both new and existing search results
1552 int firstSeq = Integer.MAX_VALUE;
1554 int firstCol = Integer.MAX_VALUE;
1556 boolean matchFound = false;
1558 ViewportRanges ranges = av.getRanges();
1559 int firstVisibleColumn = ranges.getStartRes();
1560 int lastVisibleColumn = ranges.getEndRes();
1561 AlignmentI alignment = av.getAlignment();
1562 if (av.hasHiddenColumns())
1564 firstVisibleColumn = alignment.getHiddenColumns()
1565 .visibleToAbsoluteColumn(firstVisibleColumn);
1566 lastVisibleColumn = alignment.getHiddenColumns()
1567 .visibleToAbsoluteColumn(lastVisibleColumn);
1570 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1571 .getEndSeq(); seqNo++)
1573 SequenceI seq = alignment.getSequenceAt(seqNo);
1575 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1577 if (visibleResults != null)
1579 for (int i = 0; i < visibleResults.length - 1; i += 2)
1581 int firstMatchedColumn = visibleResults[i];
1582 int lastMatchedColumn = visibleResults[i + 1];
1583 if (firstMatchedColumn <= lastVisibleColumn
1584 && lastMatchedColumn >= firstVisibleColumn)
1587 * found a search results match in the visible region -
1588 * remember the first and last sequence matched, and the first
1589 * and last visible columns in the matched positions
1592 firstSeq = Math.min(firstSeq, seqNo);
1593 lastSeq = Math.max(lastSeq, seqNo);
1594 firstMatchedColumn = Math.max(firstMatchedColumn,
1595 firstVisibleColumn);
1596 lastMatchedColumn = Math.min(lastMatchedColumn,
1598 firstCol = Math.min(firstCol, firstMatchedColumn);
1599 lastCol = Math.max(lastCol, lastMatchedColumn);
1607 if (av.hasHiddenColumns())
1609 firstCol = alignment.getHiddenColumns()
1610 .absoluteToVisibleColumn(firstCol);
1611 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1613 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1614 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1615 gg.translate(transX, transY);
1616 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1617 gg.translate(-transX, -transY);
1624 public void propertyChange(PropertyChangeEvent evt)
1626 String eventName = evt.getPropertyName();
1628 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1634 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1642 if (eventName.equals(ViewportRanges.STARTRES)
1643 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1645 // Make sure we're not trying to draw a panel
1646 // larger than the visible window
1647 if (eventName.equals(ViewportRanges.STARTRES))
1649 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1653 scrollX = ((int[]) evt.getNewValue())[0]
1654 - ((int[]) evt.getOldValue())[0];
1656 ViewportRanges vpRanges = av.getRanges();
1658 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1659 if (scrollX > range)
1663 else if (scrollX < -range)
1668 // Both scrolling and resizing change viewport ranges: scrolling changes
1669 // both start and end points, but resize only changes end values.
1670 // Here we only want to fastpaint on a scroll, with resize using a normal
1671 // paint, so scroll events are identified as changes to the horizontal or
1672 // vertical start value.
1673 if (eventName.equals(ViewportRanges.STARTRES))
1675 if (av.getWrapAlignment())
1677 fastPaintWrapped(scrollX);
1681 fastPaint(scrollX, 0);
1684 else if (eventName.equals(ViewportRanges.STARTSEQ))
1687 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1689 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1691 if (av.getWrapAlignment())
1693 fastPaintWrapped(scrollX);
1697 fastPaint(scrollX, 0);
1700 else if (eventName.equals(ViewportRanges.STARTSEQ))
1703 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1705 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1707 if (av.getWrapAlignment())
1709 fastPaintWrapped(scrollX);
1715 * Does a minimal update of the image for a scroll movement. This method
1716 * handles scroll movements of up to one width of the wrapped alignment (one
1717 * click in the vertical scrollbar). Larger movements (for example after a
1718 * scroll to highlight a mapped position) trigger a full redraw instead.
1721 * number of positions scrolled (right if positive, left if negative)
1723 protected void fastPaintWrapped(int scrollX)
1725 ViewportRanges ranges = av.getRanges();
1727 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1730 * shift of one view width or more is
1731 * overcomplicated to handle in this method
1738 if (fastpainting || gg == null)
1744 fastpainting = true;
1748 calculateWrappedGeometry(getWidth(), getHeight());
1751 * relocate the regions of the alignment that are still visible
1753 shiftWrappedAlignment(-scrollX);
1756 * add new columns (sequence, annotation)
1757 * - at top left if scrollX < 0
1758 * - at right of last two widths if scrollX > 0
1762 int startRes = ranges.getStartRes();
1763 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1764 - scrollX - 1, getHeight());
1768 fastPaintWrappedAddRight(scrollX);
1772 * draw all scales (if shown) and hidden column markers
1774 drawWrappedDecorators(gg, ranges.getStartRes());
1779 fastpainting = false;
1784 * Draws the specified number of columns at the 'end' (bottom right) of a
1785 * wrapped alignment view, including sequences and annotations if shown, but
1786 * not scales. Also draws the same number of columns at the right hand end of
1787 * the second last width shown, if the last width is not full height (so
1788 * cannot simply be copied from the graphics image).
1792 protected void fastPaintWrappedAddRight(int columns)
1799 ViewportRanges ranges = av.getRanges();
1800 int viewportWidth = ranges.getViewportWidth();
1801 int charWidth = av.getCharWidth();
1804 * draw full height alignment in the second last row, last columns, if the
1805 * last row was not full height
1807 int visibleWidths = wrappedVisibleWidths;
1808 int canvasHeight = getHeight();
1809 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1811 if (lastWidthPartHeight)
1813 int widthsAbove = Math.max(0, visibleWidths - 2);
1814 int ypos = wrappedRepeatHeightPx * widthsAbove
1815 + wrappedSpaceAboveAlignment;
1816 int endRes = ranges.getEndRes();
1817 endRes += widthsAbove * viewportWidth;
1818 int startRes = endRes - columns;
1819 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1823 * white fill first to erase annotations
1825 gg.translate(xOffset, 0);
1826 gg.setColor(Color.white);
1827 gg.fillRect(labelWidthWest, ypos,
1828 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1829 gg.translate(-xOffset, 0);
1831 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1835 * draw newly visible columns in last wrapped width (none if we
1836 * have reached the end of the alignment)
1837 * y-offset for drawing last width is height of widths above,
1840 int widthsAbove = visibleWidths - 1;
1841 int ypos = wrappedRepeatHeightPx * widthsAbove
1842 + wrappedSpaceAboveAlignment;
1843 int endRes = ranges.getEndRes();
1844 endRes += widthsAbove * viewportWidth;
1845 int startRes = endRes - columns + 1;
1848 * white fill first to erase annotations
1850 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1852 gg.translate(xOffset, 0);
1853 gg.setColor(Color.white);
1854 int width = viewportWidth * charWidth - xOffset;
1855 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1856 gg.translate(-xOffset, 0);
1858 gg.setFont(av.getFont());
1859 gg.setColor(Color.black);
1861 if (startRes < ranges.getVisibleAlignmentWidth())
1863 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1867 * and finally, white fill any space below the visible alignment
1869 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1870 if (heightBelow > 0)
1872 gg.setColor(Color.white);
1873 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1878 * Shifts the visible alignment by the specified number of columns - left if
1879 * negative, right if positive. Copies and moves sequences and annotations (if
1880 * shown). Scales, hidden column markers and any newly visible columns must be
1885 protected void shiftWrappedAlignment(int positions)
1891 int charWidth = av.getCharWidth();
1893 int canvasHeight = getHeight();
1894 ViewportRanges ranges = av.getRanges();
1895 int viewportWidth = ranges.getViewportWidth();
1896 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1898 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1899 int xMax = ranges.getVisibleAlignmentWidth();
1904 * shift right (after scroll left)
1905 * for each wrapped width (starting with the last), copy (width-positions)
1906 * columns from the left margin to the right margin, and copy positions
1907 * columns from the right margin of the row above (if any) to the
1908 * left margin of the current row
1912 * get y-offset of last wrapped width, first row of sequences
1914 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1915 y += wrappedSpaceAboveAlignment;
1916 int copyFromLeftStart = labelWidthWest;
1917 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1922 * shift 'widthToCopy' residues by 'positions' places to the right
1924 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1925 positions * charWidth, 0);
1929 * copy 'positions' residue from the row above (right hand end)
1930 * to this row's left hand end
1932 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1933 positions * charWidth, heightToCopy, -widthToCopy,
1934 wrappedRepeatHeightPx);
1937 y -= wrappedRepeatHeightPx;
1943 * shift left (after scroll right)
1944 * for each wrapped width (starting with the first), copy (width-positions)
1945 * columns from the right margin to the left margin, and copy positions
1946 * columns from the left margin of the row below (if any) to the
1947 * right margin of the current row
1949 int xpos = av.getRanges().getStartRes();
1950 int y = wrappedSpaceAboveAlignment;
1951 int copyFromRightStart = labelWidthWest - positions * charWidth;
1953 while (y < canvasHeight)
1955 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1956 positions * charWidth, 0);
1957 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1958 && (xpos + viewportWidth <= xMax))
1960 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1961 * charWidth, heightToCopy, widthToCopy,
1962 -wrappedRepeatHeightPx);
1965 y += wrappedRepeatHeightPx;
1966 xpos += viewportWidth;
1973 * Redraws any positions in the search results in the visible region of a
1974 * wrapped alignment. Any highlights are drawn depending on the search results
1975 * set on the Viewport, not the <code>results</code> argument. This allows
1976 * this method to be called either to clear highlights (passing the previous
1977 * search results), or to draw new highlights.
1982 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1984 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1988 int charHeight = av.getCharHeight();
1990 boolean matchFound = false;
1992 calculateWrappedGeometry(getWidth(), getHeight());
1993 int wrappedWidth = av.getWrappedWidth();
1994 int wrappedHeight = wrappedRepeatHeightPx;
1996 ViewportRanges ranges = av.getRanges();
1997 int canvasHeight = getHeight();
1998 int repeats = canvasHeight / wrappedHeight;
1999 if (canvasHeight / wrappedHeight > 0)
2004 int firstVisibleColumn = ranges.getStartRes();
2005 int lastVisibleColumn = ranges.getStartRes() + repeats
2006 * ranges.getViewportWidth() - 1;
2008 AlignmentI alignment = av.getAlignment();
2009 if (av.hasHiddenColumns())
2011 firstVisibleColumn = alignment.getHiddenColumns()
2012 .visibleToAbsoluteColumn(firstVisibleColumn);
2013 lastVisibleColumn = alignment.getHiddenColumns()
2014 .visibleToAbsoluteColumn(lastVisibleColumn);
2017 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2019 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2020 .getEndSeq(); seqNo++)
2022 SequenceI seq = alignment.getSequenceAt(seqNo);
2024 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2026 if (visibleResults != null)
2028 for (int i = 0; i < visibleResults.length - 1; i += 2)
2030 int firstMatchedColumn = visibleResults[i];
2031 int lastMatchedColumn = visibleResults[i + 1];
2032 if (firstMatchedColumn <= lastVisibleColumn
2033 && lastMatchedColumn >= firstVisibleColumn)
2036 * found a search results match in the visible region
2038 firstMatchedColumn = Math.max(firstMatchedColumn,
2039 firstVisibleColumn);
2040 lastMatchedColumn = Math.min(lastMatchedColumn,
2044 * draw each mapped position separately (as contiguous positions may
2045 * wrap across lines)
2047 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2049 int displayColumn = mappedPos;
2050 if (av.hasHiddenColumns())
2052 displayColumn = alignment.getHiddenColumns()
2053 .absoluteToVisibleColumn(displayColumn);
2057 * transX: offset from left edge of canvas to residue position
2059 int transX = labelWidthWest
2060 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2061 * av.getCharWidth();
2064 * transY: offset from top edge of canvas to residue position
2066 int transY = gapHeight;
2067 transY += (displayColumn - ranges.getStartRes())
2068 / wrappedWidth * wrappedHeight;
2069 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2072 * yOffset is from graphics origin to start of visible region
2074 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2075 if (transY < getHeight())
2078 gg.translate(transX, transY);
2079 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2081 gg.translate(-transX, -transY);
2093 * Answers the width in pixels of the left scale labels (0 if not shown)
2097 int getLabelWidthWest()
2099 return labelWidthWest;