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 * vertical gap in pixels 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 * Draws a wrapped alignment panel for printing. After drawing, the graphics
454 * origin is shifted down by the height of the image drawn.
457 * Graphics object to draw with
459 * width of drawing area
460 * @param canvasHeight
461 * height of drawing area
463 * start residue of print area
465 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
466 int canvasHeight, int startRes)
468 int heightDrawn = drawWrappedPanel(g, canvasWidth, canvasHeight,
471 SequenceGroup group = av.getSelectionGroup();
474 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
479 * shift graphics (0, 0) to below the region drawn
481 g.translate(0, heightDrawn);
485 * Returns the visible width of the canvas in residues, after allowing for
486 * East or West scales (if shown)
489 * the width in pixels (possibly including scales)
493 public int getWrappedCanvasWidth(int canvasWidth)
495 int charWidth = av.getCharWidth();
497 FontMetrics fm = getFontMetrics(av.getFont());
501 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
503 labelWidth = getLabelWidth(fm);
506 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
508 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
510 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
514 * Returns a pixel width sufficient to show the largest sequence coordinate
515 * (end position) in the alignment, calculated as the FontMetrics width of
516 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
517 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
518 * half a character width space on either side.
523 protected int getLabelWidth(FontMetrics fm)
526 * find the biggest sequence end position we need to show
527 * (note this is not necessarily the sequence length)
530 AlignmentI alignment = av.getAlignment();
531 for (int i = 0; i < alignment.getHeight(); i++)
533 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
537 for (int i = maxWidth; i > 0; i /= 10)
542 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
546 * Draws as many widths of a wrapped alignment as can fit in the visible window,
547 * and returns the height drawn in pixels
551 * available width in pixels
552 * @param canvasHeight
553 * available height in pixels
555 * the first column (0...) of the alignment to draw
559 public int drawWrappedPanel(Graphics g, int canvasWidth,
560 int canvasHeight, final int startColumn)
562 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
565 av.setWrappedWidth(wrappedWidthInResidues);
567 ViewportRanges ranges = av.getRanges();
568 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
570 // we need to call this again to make sure the startColumn +
571 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
573 calculateWrappedGeometry(canvasWidth, canvasHeight);
576 * draw one width at a time (excluding any scales shown),
577 * until we have run out of either alignment or vertical space available
579 int ypos = wrappedSpaceAboveAlignment;
580 int maxWidth = ranges.getVisibleAlignmentWidth();
582 int start = startColumn;
583 int currentWidth = 0;
584 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
587 .min(maxWidth, start + wrappedWidthInResidues - 1);
588 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
589 ypos += wrappedRepeatHeightPx;
590 start += wrappedWidthInResidues;
594 drawWrappedDecorators(g, startColumn);
600 * Calculates and saves values needed when rendering a wrapped alignment.
601 * These depend on many factors, including
603 * <li>canvas width and height</li>
604 * <li>number of visible sequences, and height of annotations if shown</li>
605 * <li>font and character width</li>
606 * <li>whether scales are shown left, right or above the alignment</li>
610 * @param canvasHeight
611 * @return the number of residue columns in each width
613 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
615 int charHeight = av.getCharHeight();
618 * vertical space in pixels between wrapped widths of alignment
619 * - one character height, or two if scale above is drawn
621 wrappedSpaceAboveAlignment = charHeight
622 * (av.getScaleAboveWrapped() ? 2 : 1);
625 * compute height in pixels of the wrapped widths
626 * - start with space above plus sequences
628 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
629 wrappedRepeatHeightPx += av.getAlignment().getHeight()
633 * add annotations panel height if shown
634 * also gap between sequences and annotations
636 if (av.isShowAnnotation())
638 wrappedRepeatHeightPx += getAnnotationHeight();
639 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
643 * number of visible widths (the last one may be part height),
644 * ensuring a part height includes at least one sequence
646 ViewportRanges ranges = av.getRanges();
647 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
648 int remainder = canvasHeight % wrappedRepeatHeightPx;
649 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
651 wrappedVisibleWidths++;
655 * compute width in residues; this also sets East and West label widths
657 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
660 * limit visibleWidths to not exceed width of alignment
662 int xMax = ranges.getVisibleAlignmentWidth();
663 int startToEnd = xMax - ranges.getStartRes();
664 int maxWidths = startToEnd / wrappedWidthInResidues;
665 if (startToEnd % wrappedWidthInResidues > 0)
669 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
671 return wrappedWidthInResidues;
675 * Draws one width of a wrapped alignment, including sequences and
676 * annnotations, if shown, but not scales or hidden column markers
682 * @param canvasHeight
684 protected void drawWrappedWidth(Graphics g, final int ypos,
685 final int startColumn, final int endColumn,
686 final int canvasHeight)
688 ViewportRanges ranges = av.getRanges();
689 int viewportWidth = ranges.getViewportWidth();
691 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
694 * move right before drawing by the width of the scale left (if any)
695 * plus column offset from left margin (usually zero, but may be non-zero
696 * when fast painting is drawing just a few columns)
698 int charWidth = av.getCharWidth();
699 int xOffset = labelWidthWest
700 + ((startColumn - ranges.getStartRes()) % viewportWidth)
702 g.translate(xOffset, 0);
704 // When printing we have an extra clipped region,
705 // the Printable page which we need to account for here
706 Shape clip = g.getClip();
710 g.setClip(0, (int) clip.getBounds().getY(),
711 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
715 * white fill the region to be drawn (so incremental fast paint doesn't
716 * scribble over an existing image)
718 g.setColor(Color.white);
719 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
720 wrappedRepeatHeightPx);
722 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
725 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
727 if (av.isShowAnnotation())
729 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
730 g.translate(0, yShift);
731 if (annotations == null)
733 annotations = new AnnotationPanel(av);
736 annotations.renderer.drawComponent(annotations, av, g, -1,
737 startColumn, endx + 1);
738 g.translate(0, -yShift);
746 g.translate(-xOffset, 0);
750 * Draws scales left, right and above (if shown), and any hidden column
751 * markers, on all widths of the wrapped alignment
756 protected void drawWrappedDecorators(Graphics g, final int startColumn)
758 int charWidth = av.getCharWidth();
760 g.setFont(av.getFont());
761 g.setColor(Color.black);
763 int ypos = wrappedSpaceAboveAlignment;
764 ViewportRanges ranges = av.getRanges();
765 int viewportWidth = ranges.getViewportWidth();
766 int maxWidth = ranges.getVisibleAlignmentWidth();
768 int startCol = startColumn;
770 while (widthsDrawn < wrappedVisibleWidths)
772 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
774 if (av.getScaleLeftWrapped())
776 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
779 if (av.getScaleRightWrapped())
781 int x = labelWidthWest + viewportWidth * charWidth;
783 drawVerticalScale(g, startCol, endColumn, ypos, false);
788 * white fill region of scale above and hidden column markers
789 * (to support incremental fast paint of image)
791 g.translate(labelWidthWest, 0);
792 g.setColor(Color.white);
793 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
794 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
795 g.setColor(Color.black);
796 g.translate(-labelWidthWest, 0);
798 g.translate(labelWidthWest, 0);
800 if (av.getScaleAboveWrapped())
802 drawNorthScale(g, startCol, endColumn, ypos);
805 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
807 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
810 g.translate(-labelWidthWest, 0);
812 ypos += wrappedRepeatHeightPx;
813 startCol += viewportWidth;
819 * Draws markers (triangles) above hidden column positions between startColumn
827 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
828 int startColumn, int endColumn)
830 int charHeight = av.getCharHeight();
831 int charWidth = av.getCharWidth();
833 g.setColor(Color.blue);
835 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
837 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
841 res = it.next() - startColumn;
843 if (res < 0 || res > endColumn - startColumn + 1)
849 * draw a downward-pointing triangle at the hidden columns location
850 * (before the following visible column)
852 int xMiddle = res * charWidth;
853 int[] xPoints = new int[] { xMiddle - charHeight / 4,
854 xMiddle + charHeight / 4, xMiddle };
855 int yTop = ypos - (charHeight / 2);
856 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
857 g.fillPolygon(xPoints, yPoints, 3);
862 * Draw a selection group over a wrapped alignment
864 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
866 int canvasHeight, int startRes)
868 // chop the wrapped alignment extent up into panel-sized blocks and treat
869 // each block as if it were a block from an unwrapped alignment
870 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
871 BasicStroke.JOIN_ROUND, 3f, new float[]
873 g.setColor(Color.RED);
875 int charWidth = av.getCharWidth();
876 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
878 int startx = startRes;
879 int maxwidth = av.getAlignment().getVisibleWidth();
880 int ypos = wrappedSpaceAboveAlignment;
882 while ((ypos <= canvasHeight) && (startx < maxwidth))
884 // set end value to be start + width, or maxwidth, whichever is smaller
885 int endx = startx + cWidth - 1;
892 g.translate(labelWidthWest, 0);
893 drawUnwrappedSelection(g, group, startx, endx, 0,
894 av.getAlignment().getHeight() - 1,
896 g.translate(-labelWidthWest, 0);
898 ypos += wrappedRepeatHeightPx;
902 g.setStroke(new BasicStroke());
906 * Answers zero if annotations are not shown, otherwise recalculates and answers
907 * the total height of all annotation rows in pixels
911 int getAnnotationHeight()
913 if (!av.isShowAnnotation())
918 if (annotations == null)
920 annotations = new AnnotationPanel(av);
923 return annotations.adjustPanelHeight();
927 * Draws the visible region of the alignment on the graphics context. If there
928 * are hidden column markers in the visible region, then each sub-region
929 * between the markers is drawn separately, followed by the hidden column
933 * the graphics context, positioned at the first residue to be drawn
935 * offset of the first column to draw (0..)
937 * offset of the last column to draw (0..)
939 * offset of the first sequence to draw (0..)
941 * offset of the last sequence to draw (0..)
943 * vertical offset at which to draw (for wrapped alignments)
945 public void drawPanel(Graphics g1, final int startRes, final int endRes,
946 final int startSeq, final int endSeq, final int yOffset)
948 int charHeight = av.getCharHeight();
949 int charWidth = av.getCharWidth();
951 if (!av.hasHiddenColumns())
953 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
961 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
962 VisibleContigsIterator regions = hidden
963 .getVisContigsIterator(startRes, endRes + 1, true);
965 while (regions.hasNext())
967 int[] region = regions.next();
968 blockEnd = region[1];
969 blockStart = region[0];
972 * draw up to just before the next hidden region, or the end of
973 * the visible region, whichever comes first
975 g1.translate(screenY * charWidth, 0);
977 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
980 * draw the downline of the hidden column marker (ScalePanel draws the
981 * triangle on top) if we reached it
983 if (av.getShowHiddenMarkers()
984 && (regions.hasNext() || regions.endsAtHidden()))
986 g1.setColor(Color.blue);
988 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
989 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
990 (endSeq - startSeq + 1) * charHeight + yOffset);
993 g1.translate(-screenY * charWidth, 0);
994 screenY += blockEnd - blockStart + 1;
1001 * Draws a region of the visible alignment
1005 * offset of the first column in the visible region (0..)
1007 * offset of the last column in the visible region (0..)
1009 * offset of the first sequence in the visible region (0..)
1011 * offset of the last sequence in the visible region (0..)
1013 * vertical offset at which to draw (for wrapped alignments)
1015 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1016 int endSeq, int offset)
1018 int charHeight = av.getCharHeight();
1019 int charWidth = av.getCharWidth();
1021 g.setFont(av.getFont());
1022 seqRdr.prepare(g, av.isRenderGaps());
1026 // / First draw the sequences
1027 // ///////////////////////////
1028 for (int i = startSeq; i <= endSeq; i++)
1030 nextSeq = av.getAlignment().getSequenceAt(i);
1031 if (nextSeq == null)
1033 // occasionally, a race condition occurs such that the alignment row is
1037 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1038 startRes, endRes, offset + ((i - startSeq) * charHeight));
1040 if (av.isShowSequenceFeatures())
1042 fr.drawSequence(g, nextSeq, startRes, endRes,
1043 offset + ((i - startSeq) * charHeight), false);
1047 * highlight search Results once sequence has been drawn
1049 if (av.hasSearchResults())
1051 SearchResultsI searchResults = av.getSearchResults();
1052 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1054 if (visibleResults != null)
1056 for (int r = 0; r < visibleResults.length; r += 2)
1058 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1059 visibleResults[r + 1],
1060 (visibleResults[r] - startRes) * charWidth,
1061 offset + ((i - startSeq) * charHeight));
1067 if (av.getSelectionGroup() != null
1068 || av.getAlignment().getGroups().size() > 0)
1070 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1076 * Draws the outlines of any groups defined on the alignment (excluding the
1077 * current selection group, if any)
1086 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1087 int startSeq, int endSeq, int offset)
1089 Graphics2D g = (Graphics2D) g1;
1091 SequenceGroup group = null;
1092 int groupIndex = -1;
1094 if (av.getAlignment().getGroups().size() > 0)
1096 group = av.getAlignment().getGroups().get(0);
1104 g.setColor(group.getOutlineColour());
1106 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1111 if (groupIndex >= av.getAlignment().getGroups().size())
1116 group = av.getAlignment().getGroups().get(groupIndex);
1118 } while (groupIndex < av.getAlignment().getGroups().size());
1125 * Draws the outline of the current selection group (if any)
1133 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1134 int startSeq, int endSeq)
1136 SequenceGroup group = av.getSelectionGroup();
1142 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1143 BasicStroke.JOIN_ROUND, 3f, new float[]
1145 g.setColor(Color.RED);
1146 if (!av.getWrapAlignment())
1148 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1153 drawWrappedSelection(g, group, getWidth(), getHeight(),
1154 av.getRanges().getStartRes());
1156 g.setStroke(new BasicStroke());
1160 * Draw the cursor as a separate image and overlay
1163 * start residue of area to draw cursor in
1165 * end residue of area to draw cursor in
1167 * start sequence of area to draw cursor in
1169 * end sequence of are to draw cursor in
1170 * @return a transparent image of the same size as the sequence canvas, with
1171 * the cursor drawn on it, if any
1173 private void drawCursor(Graphics g, int startRes, int endRes,
1177 // convert the cursorY into a position on the visible alignment
1178 int cursor_ypos = cursorY;
1180 // don't do work unless we have to
1181 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1185 int startx = startRes;
1188 // convert the cursorX into a position on the visible alignment
1189 int cursor_xpos = av.getAlignment().getHiddenColumns()
1190 .absoluteToVisibleColumn(cursorX);
1192 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1195 if (av.getWrapAlignment())
1197 // work out the correct offsets for the cursor
1198 int charHeight = av.getCharHeight();
1199 int charWidth = av.getCharWidth();
1200 int canvasWidth = getWidth();
1201 int canvasHeight = getHeight();
1203 // height gap above each panel
1204 int hgap = charHeight;
1205 if (av.getScaleAboveWrapped())
1210 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1212 int cHeight = av.getAlignment().getHeight() * charHeight;
1214 endx = startx + cWidth - 1;
1215 int ypos = hgap; // vertical offset
1217 // iterate down the wrapped panels
1218 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1220 // update vertical offset
1221 ypos += cHeight + getAnnotationHeight() + hgap;
1223 // update horizontal offset
1225 endx = startx + cWidth - 1;
1228 xoffset = labelWidthWest;
1231 // now check if cursor is within range for x values
1232 if (cursor_xpos >= startx && cursor_xpos <= endx)
1234 // get the character the cursor is drawn at
1235 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1236 char s = seq.getCharAt(cursorX);
1238 seqRdr.drawCursor(g, s,
1239 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1240 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1248 * Draw a selection group over an unwrapped alignment
1251 * graphics object to draw with
1255 * start residue of area to draw
1257 * end residue of area to draw
1259 * start sequence of area to draw
1261 * end sequence of area to draw
1263 * vertical offset (used when called from wrapped alignment code)
1265 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1266 int startRes, int endRes, int startSeq, int endSeq, int offset)
1268 int charWidth = av.getCharWidth();
1270 if (!av.hasHiddenColumns())
1272 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1277 // package into blocks of visible columns
1282 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1283 VisibleContigsIterator regions = hidden
1284 .getVisContigsIterator(startRes, endRes + 1, true);
1285 while (regions.hasNext())
1287 int[] region = regions.next();
1288 blockEnd = region[1];
1289 blockStart = region[0];
1291 g.translate(screenY * charWidth, 0);
1292 drawPartialGroupOutline(g, group,
1293 blockStart, blockEnd, startSeq, endSeq, offset);
1295 g.translate(-screenY * charWidth, 0);
1296 screenY += blockEnd - blockStart + 1;
1302 * Draws part of a selection group outline
1310 * @param verticalOffset
1312 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1313 int startRes, int endRes, int startSeq, int endSeq,
1316 int charHeight = av.getCharHeight();
1317 int charWidth = av.getCharWidth();
1318 int visWidth = (endRes - startRes + 1) * charWidth;
1322 boolean inGroup = false;
1327 List<SequenceI> seqs = group.getSequences(null);
1329 // position of start residue of group relative to startRes, in pixels
1330 int sx = (group.getStartRes() - startRes) * charWidth;
1332 // width of group in pixels
1333 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1336 if (!(sx + xwidth < 0 || sx > visWidth))
1338 for (i = startSeq; i <= endSeq; i++)
1340 sy = verticalOffset + (i - startSeq) * charHeight;
1342 if ((sx <= (endRes - startRes) * charWidth)
1343 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1346 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1348 bottom = sy + charHeight;
1353 if (((top == -1) && (i == 0)) || !seqs
1354 .contains(av.getAlignment().getSequenceAt(i - 1)))
1365 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1366 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1368 // reset top and bottom
1376 sy = verticalOffset + ((i - startSeq) * charHeight);
1377 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1378 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1384 * Draw horizontal selection group boundaries at top and bottom positions
1387 * graphics object to draw on
1393 * visWidth maximum available width
1395 * position to draw top of group at
1397 * position to draw bottom of group at
1399 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1400 int visWidth, int top, int bottom)
1410 // don't let width extend beyond current block, or group extent
1412 if (startx + width >= visWidth)
1414 width = visWidth - startx;
1419 g.drawLine(startx, top, startx + width, top);
1424 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1429 * Draw vertical lines at sx and sx+xwidth providing they lie within
1433 * graphics object to draw on
1439 * visWidth maximum available width
1445 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1448 // if start position is visible, draw vertical line to left of
1450 if (sx >= 0 && sx < visWidth)
1452 g.drawLine(sx, oldY, sx, sy);
1455 // if end position is visible, draw vertical line to right of
1457 if (sx + xwidth < visWidth)
1459 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1464 * Highlights search results in the visible region by rendering as white text
1465 * on a black background. Any previous highlighting is removed. Answers true
1466 * if any highlight was left on the visible alignment (so status bar should be
1467 * set to match), else false.
1469 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1470 * alignment had to be scrolled to show the highlighted region, then it should
1471 * be fully redrawn, otherwise a fast paint can be performed. This argument
1472 * could be removed if fast paint of scrolled wrapped alignment is coded in
1473 * future (JAL-2609).
1476 * @param noFastPaint
1479 public boolean highlightSearchResults(SearchResultsI results,
1480 boolean noFastPaint)
1486 boolean wrapped = av.getWrapAlignment();
1489 fastPaint = !noFastPaint;
1490 fastpainting = fastPaint;
1493 * to avoid redrawing the whole visible region, we instead
1494 * redraw just the minimal regions to remove previous highlights
1497 SearchResultsI previous = av.getSearchResults();
1498 av.setSearchResults(results);
1499 boolean redrawn = false;
1500 boolean drawn = false;
1503 redrawn = drawMappedPositionsWrapped(previous);
1504 drawn = drawMappedPositionsWrapped(results);
1509 redrawn = drawMappedPositions(previous);
1510 drawn = drawMappedPositions(results);
1515 * if highlights were either removed or added, repaint
1523 * return true only if highlights were added
1529 fastpainting = false;
1534 * Redraws the minimal rectangle in the visible region (if any) that includes
1535 * mapped positions of the given search results. Whether or not positions are
1536 * highlighted depends on the SearchResults set on the Viewport. This allows
1537 * this method to be called to either clear or set highlighting. Answers true
1538 * if any positions were drawn (in which case a repaint is still required),
1544 protected boolean drawMappedPositions(SearchResultsI results)
1546 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1552 * calculate the minimal rectangle to redraw that
1553 * includes both new and existing search results
1555 int firstSeq = Integer.MAX_VALUE;
1557 int firstCol = Integer.MAX_VALUE;
1559 boolean matchFound = false;
1561 ViewportRanges ranges = av.getRanges();
1562 int firstVisibleColumn = ranges.getStartRes();
1563 int lastVisibleColumn = ranges.getEndRes();
1564 AlignmentI alignment = av.getAlignment();
1565 if (av.hasHiddenColumns())
1567 firstVisibleColumn = alignment.getHiddenColumns()
1568 .visibleToAbsoluteColumn(firstVisibleColumn);
1569 lastVisibleColumn = alignment.getHiddenColumns()
1570 .visibleToAbsoluteColumn(lastVisibleColumn);
1573 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1574 .getEndSeq(); seqNo++)
1576 SequenceI seq = alignment.getSequenceAt(seqNo);
1578 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1580 if (visibleResults != null)
1582 for (int i = 0; i < visibleResults.length - 1; i += 2)
1584 int firstMatchedColumn = visibleResults[i];
1585 int lastMatchedColumn = visibleResults[i + 1];
1586 if (firstMatchedColumn <= lastVisibleColumn
1587 && lastMatchedColumn >= firstVisibleColumn)
1590 * found a search results match in the visible region -
1591 * remember the first and last sequence matched, and the first
1592 * and last visible columns in the matched positions
1595 firstSeq = Math.min(firstSeq, seqNo);
1596 lastSeq = Math.max(lastSeq, seqNo);
1597 firstMatchedColumn = Math.max(firstMatchedColumn,
1598 firstVisibleColumn);
1599 lastMatchedColumn = Math.min(lastMatchedColumn,
1601 firstCol = Math.min(firstCol, firstMatchedColumn);
1602 lastCol = Math.max(lastCol, lastMatchedColumn);
1610 if (av.hasHiddenColumns())
1612 firstCol = alignment.getHiddenColumns()
1613 .absoluteToVisibleColumn(firstCol);
1614 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1616 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1617 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1618 gg.translate(transX, transY);
1619 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1620 gg.translate(-transX, -transY);
1627 public void propertyChange(PropertyChangeEvent evt)
1629 String eventName = evt.getPropertyName();
1631 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1637 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1645 if (eventName.equals(ViewportRanges.STARTRES)
1646 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1648 // Make sure we're not trying to draw a panel
1649 // larger than the visible window
1650 if (eventName.equals(ViewportRanges.STARTRES))
1652 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1656 scrollX = ((int[]) evt.getNewValue())[0]
1657 - ((int[]) evt.getOldValue())[0];
1659 ViewportRanges vpRanges = av.getRanges();
1661 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1662 if (scrollX > range)
1666 else if (scrollX < -range)
1671 // Both scrolling and resizing change viewport ranges: scrolling changes
1672 // both start and end points, but resize only changes end values.
1673 // Here we only want to fastpaint on a scroll, with resize using a normal
1674 // paint, so scroll events are identified as changes to the horizontal or
1675 // vertical start value.
1676 if (eventName.equals(ViewportRanges.STARTRES))
1678 if (av.getWrapAlignment())
1680 fastPaintWrapped(scrollX);
1684 fastPaint(scrollX, 0);
1687 else if (eventName.equals(ViewportRanges.STARTSEQ))
1690 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1692 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1694 if (av.getWrapAlignment())
1696 fastPaintWrapped(scrollX);
1700 fastPaint(scrollX, 0);
1703 else if (eventName.equals(ViewportRanges.STARTSEQ))
1706 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1708 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1710 if (av.getWrapAlignment())
1712 fastPaintWrapped(scrollX);
1718 * Does a minimal update of the image for a scroll movement. This method
1719 * handles scroll movements of up to one width of the wrapped alignment (one
1720 * click in the vertical scrollbar). Larger movements (for example after a
1721 * scroll to highlight a mapped position) trigger a full redraw instead.
1724 * number of positions scrolled (right if positive, left if negative)
1726 protected void fastPaintWrapped(int scrollX)
1728 ViewportRanges ranges = av.getRanges();
1730 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1733 * shift of one view width or more is
1734 * overcomplicated to handle in this method
1741 if (fastpainting || gg == null)
1747 fastpainting = true;
1751 calculateWrappedGeometry(getWidth(), getHeight());
1754 * relocate the regions of the alignment that are still visible
1756 shiftWrappedAlignment(-scrollX);
1759 * add new columns (sequence, annotation)
1760 * - at top left if scrollX < 0
1761 * - at right of last two widths if scrollX > 0
1765 int startRes = ranges.getStartRes();
1766 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1767 - scrollX - 1, getHeight());
1771 fastPaintWrappedAddRight(scrollX);
1775 * draw all scales (if shown) and hidden column markers
1777 drawWrappedDecorators(gg, ranges.getStartRes());
1782 fastpainting = false;
1787 * Draws the specified number of columns at the 'end' (bottom right) of a
1788 * wrapped alignment view, including sequences and annotations if shown, but
1789 * not scales. Also draws the same number of columns at the right hand end of
1790 * the second last width shown, if the last width is not full height (so
1791 * cannot simply be copied from the graphics image).
1795 protected void fastPaintWrappedAddRight(int columns)
1802 ViewportRanges ranges = av.getRanges();
1803 int viewportWidth = ranges.getViewportWidth();
1804 int charWidth = av.getCharWidth();
1807 * draw full height alignment in the second last row, last columns, if the
1808 * last row was not full height
1810 int visibleWidths = wrappedVisibleWidths;
1811 int canvasHeight = getHeight();
1812 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1814 if (lastWidthPartHeight)
1816 int widthsAbove = Math.max(0, visibleWidths - 2);
1817 int ypos = wrappedRepeatHeightPx * widthsAbove
1818 + wrappedSpaceAboveAlignment;
1819 int endRes = ranges.getEndRes();
1820 endRes += widthsAbove * viewportWidth;
1821 int startRes = endRes - columns;
1822 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1826 * white fill first to erase annotations
1828 gg.translate(xOffset, 0);
1829 gg.setColor(Color.white);
1830 gg.fillRect(labelWidthWest, ypos,
1831 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1832 gg.translate(-xOffset, 0);
1834 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1838 * draw newly visible columns in last wrapped width (none if we
1839 * have reached the end of the alignment)
1840 * y-offset for drawing last width is height of widths above,
1843 int widthsAbove = visibleWidths - 1;
1844 int ypos = wrappedRepeatHeightPx * widthsAbove
1845 + wrappedSpaceAboveAlignment;
1846 int endRes = ranges.getEndRes();
1847 endRes += widthsAbove * viewportWidth;
1848 int startRes = endRes - columns + 1;
1851 * white fill first to erase annotations
1853 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1855 gg.translate(xOffset, 0);
1856 gg.setColor(Color.white);
1857 int width = viewportWidth * charWidth - xOffset;
1858 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1859 gg.translate(-xOffset, 0);
1861 gg.setFont(av.getFont());
1862 gg.setColor(Color.black);
1864 if (startRes < ranges.getVisibleAlignmentWidth())
1866 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1870 * and finally, white fill any space below the visible alignment
1872 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1873 if (heightBelow > 0)
1875 gg.setColor(Color.white);
1876 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1881 * Shifts the visible alignment by the specified number of columns - left if
1882 * negative, right if positive. Copies and moves sequences and annotations (if
1883 * shown). Scales, hidden column markers and any newly visible columns must be
1888 protected void shiftWrappedAlignment(int positions)
1894 int charWidth = av.getCharWidth();
1896 int canvasHeight = getHeight();
1897 ViewportRanges ranges = av.getRanges();
1898 int viewportWidth = ranges.getViewportWidth();
1899 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1901 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1902 int xMax = ranges.getVisibleAlignmentWidth();
1907 * shift right (after scroll left)
1908 * for each wrapped width (starting with the last), copy (width-positions)
1909 * columns from the left margin to the right margin, and copy positions
1910 * columns from the right margin of the row above (if any) to the
1911 * left margin of the current row
1915 * get y-offset of last wrapped width, first row of sequences
1917 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1918 y += wrappedSpaceAboveAlignment;
1919 int copyFromLeftStart = labelWidthWest;
1920 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1925 * shift 'widthToCopy' residues by 'positions' places to the right
1927 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1928 positions * charWidth, 0);
1932 * copy 'positions' residue from the row above (right hand end)
1933 * to this row's left hand end
1935 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1936 positions * charWidth, heightToCopy, -widthToCopy,
1937 wrappedRepeatHeightPx);
1940 y -= wrappedRepeatHeightPx;
1946 * shift left (after scroll right)
1947 * for each wrapped width (starting with the first), copy (width-positions)
1948 * columns from the right margin to the left margin, and copy positions
1949 * columns from the left margin of the row below (if any) to the
1950 * right margin of the current row
1952 int xpos = av.getRanges().getStartRes();
1953 int y = wrappedSpaceAboveAlignment;
1954 int copyFromRightStart = labelWidthWest - positions * charWidth;
1956 while (y < canvasHeight)
1958 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1959 positions * charWidth, 0);
1960 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1961 && (xpos + viewportWidth <= xMax))
1963 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1964 * charWidth, heightToCopy, widthToCopy,
1965 -wrappedRepeatHeightPx);
1968 y += wrappedRepeatHeightPx;
1969 xpos += viewportWidth;
1976 * Redraws any positions in the search results in the visible region of a
1977 * wrapped alignment. Any highlights are drawn depending on the search results
1978 * set on the Viewport, not the <code>results</code> argument. This allows
1979 * this method to be called either to clear highlights (passing the previous
1980 * search results), or to draw new highlights.
1985 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1987 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1991 int charHeight = av.getCharHeight();
1993 boolean matchFound = false;
1995 calculateWrappedGeometry(getWidth(), getHeight());
1996 int wrappedWidth = av.getWrappedWidth();
1997 int wrappedHeight = wrappedRepeatHeightPx;
1999 ViewportRanges ranges = av.getRanges();
2000 int canvasHeight = getHeight();
2001 int repeats = canvasHeight / wrappedHeight;
2002 if (canvasHeight / wrappedHeight > 0)
2007 int firstVisibleColumn = ranges.getStartRes();
2008 int lastVisibleColumn = ranges.getStartRes() + repeats
2009 * ranges.getViewportWidth() - 1;
2011 AlignmentI alignment = av.getAlignment();
2012 if (av.hasHiddenColumns())
2014 firstVisibleColumn = alignment.getHiddenColumns()
2015 .visibleToAbsoluteColumn(firstVisibleColumn);
2016 lastVisibleColumn = alignment.getHiddenColumns()
2017 .visibleToAbsoluteColumn(lastVisibleColumn);
2020 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2022 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2023 .getEndSeq(); seqNo++)
2025 SequenceI seq = alignment.getSequenceAt(seqNo);
2027 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2029 if (visibleResults != null)
2031 for (int i = 0; i < visibleResults.length - 1; i += 2)
2033 int firstMatchedColumn = visibleResults[i];
2034 int lastMatchedColumn = visibleResults[i + 1];
2035 if (firstMatchedColumn <= lastVisibleColumn
2036 && lastMatchedColumn >= firstVisibleColumn)
2039 * found a search results match in the visible region
2041 firstMatchedColumn = Math.max(firstMatchedColumn,
2042 firstVisibleColumn);
2043 lastMatchedColumn = Math.min(lastMatchedColumn,
2047 * draw each mapped position separately (as contiguous positions may
2048 * wrap across lines)
2050 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2052 int displayColumn = mappedPos;
2053 if (av.hasHiddenColumns())
2055 displayColumn = alignment.getHiddenColumns()
2056 .absoluteToVisibleColumn(displayColumn);
2060 * transX: offset from left edge of canvas to residue position
2062 int transX = labelWidthWest
2063 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2064 * av.getCharWidth();
2067 * transY: offset from top edge of canvas to residue position
2069 int transY = gapHeight;
2070 transY += (displayColumn - ranges.getStartRes())
2071 / wrappedWidth * wrappedHeight;
2072 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2075 * yOffset is from graphics origin to start of visible region
2077 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2078 if (transY < getHeight())
2081 gg.translate(transX, transY);
2082 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2084 gg.translate(-transX, -transY);
2096 * Answers the width in pixels of the left scale labels (0 if not shown)
2100 int getLabelWidthWest()
2102 return labelWidthWest;