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 final int charHeight = av.getCharHeight();
198 final int charWidth = av.getCharWidth();
200 final int yPos = ypos + charHeight;
204 AlignmentI alignment = av.getAlignment();
205 if (av.hasHiddenColumns())
207 HiddenColumns hiddenColumns = alignment.getHiddenColumns();
208 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
209 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
211 FontMetrics fm = getFontMetrics(av.getFont());
214 * white fill the space for the scale for correct 'fast paint'
215 * (repainting only changed regions of the image)
216 * (fillRect origin is top left of rectangle)
218 g.setColor(Color.white);
219 g.fillRect(0, ypos, left ? labelWidthWest : labelWidthEast,
220 (alignment.getHeight() * charHeight));
222 for (int i = 0; i < alignment.getHeight(); i++)
224 SequenceI seq = alignment.getSequenceAt(i);
227 * find sequence position of first non-gapped position -
228 * to the right if scale left, to the left if scale right
230 int index = left ? startX : endX;
232 while (index >= startX && index <= endX)
234 if (!Comparison.isGap(seq.getCharAt(index)))
236 value = seq.findPosition(index);
252 * draw scale value, right justified within its width less half a
253 * character width padding on the right
255 int labelSpace = left ? labelWidthWest : labelWidthEast;
256 labelSpace -= charWidth / 2; // leave space to the right
257 String valueAsString = String.valueOf(value);
258 int labelLength = fm.stringWidth(valueAsString);
259 int xOffset = labelSpace - labelLength;
260 g.setColor(Color.black);
261 int y = (yPos + (i * charHeight)) - (charHeight / 5);
262 g.drawString(valueAsString, xOffset, y);
268 * Does a fast paint of an alignment in response to a scroll. Most of the
269 * visible region is simply copied and shifted, and then any newly visible
270 * columns or rows are drawn. The scroll may be horizontal or vertical, but
271 * not both at once. Scrolling may be the result of
273 * <li>dragging a scroll bar</li>
274 * <li>clicking in the scroll bar</li>
275 * <li>scrolling by trackpad, middle mouse button, or other device</li>
276 * <li>by moving the box in the Overview window</li>
277 * <li>programmatically to make a highlighted position visible</li>
281 * columns to shift right (positive) or left (negative)
283 * rows to shift down (positive) or up (negative)
285 public void fastPaint(int horizontal, int vertical)
287 if (fastpainting || gg == null || img == null)
296 int charHeight = av.getCharHeight();
297 int charWidth = av.getCharWidth();
299 ViewportRanges ranges = av.getRanges();
300 int startRes = ranges.getStartRes();
301 int endRes = ranges.getEndRes();
302 int startSeq = ranges.getStartSeq();
303 int endSeq = ranges.getEndSeq();
307 gg.copyArea(horizontal * charWidth, vertical * charHeight,
308 img.getWidth(), img.getHeight(), -horizontal * charWidth,
309 -vertical * charHeight);
311 if (horizontal > 0) // scrollbar pulled right, image to the left
313 transX = (endRes - startRes - horizontal) * charWidth;
314 startRes = endRes - horizontal;
316 else if (horizontal < 0)
318 endRes = startRes - horizontal;
321 if (vertical > 0) // scroll down
323 startSeq = endSeq - vertical;
325 if (startSeq < ranges.getStartSeq())
326 { // ie scrolling too fast, more than a page at a time
327 startSeq = ranges.getStartSeq();
331 transY = img.getHeight() - ((vertical + 1) * charHeight);
334 else if (vertical < 0)
336 endSeq = startSeq - vertical;
338 if (endSeq > ranges.getEndSeq())
340 endSeq = ranges.getEndSeq();
344 gg.translate(transX, transY);
345 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
346 gg.translate(-transX, -transY);
348 // Call repaint on alignment panel so that repaints from other alignment
349 // panel components can be aggregated. Otherwise performance of the
350 // overview window and others may be adversely affected.
351 av.getAlignPanel().repaint();
354 fastpainting = false;
359 public void paintComponent(Graphics g)
361 super.paintComponent(g);
363 int charHeight = av.getCharHeight();
364 int charWidth = av.getCharWidth();
366 ViewportRanges ranges = av.getRanges();
368 int width = getWidth();
369 int height = getHeight();
371 width -= (width % charWidth);
372 height -= (height % charHeight);
374 if ((img != null) && (fastPaint
375 || (getVisibleRect().width != g.getClipBounds().width)
376 || (getVisibleRect().height != g.getClipBounds().height)))
378 g.drawImage(img, 0, 0, this);
380 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
381 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
385 else if (width > 0 && height > 0)
388 * img is a cached version of the last view we drew, if any
389 * if we have no img or the size has changed, make a new one
391 if (img == null || width != img.getWidth()
392 || height != img.getHeight())
394 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
395 gg = (Graphics2D) img.getGraphics();
396 gg.setFont(av.getFont());
401 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
402 RenderingHints.VALUE_ANTIALIAS_ON);
405 gg.setColor(Color.white);
406 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
408 if (av.getWrapAlignment())
410 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
414 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
415 ranges.getStartSeq(), ranges.getEndSeq(), 0);
418 drawSelectionGroup(gg, ranges.getStartRes(),
419 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
421 g.drawImage(img, 0, 0, this);
426 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
427 ranges.getStartSeq(), ranges.getEndSeq());
432 * Draw an alignment panel for printing
435 * Graphics object to draw with
437 * start residue of print area
439 * end residue of print area
441 * start sequence of print area
443 * end sequence of print area
445 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
446 int startSeq, int endSeq)
448 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
450 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
455 * Draw a wrapped alignment panel for printing
458 * Graphics object to draw with
460 * width of drawing area
461 * @param canvasHeight
462 * height of drawing area
464 * start residue of print area
466 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
467 int canvasHeight, int startRes)
469 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
471 SequenceGroup group = av.getSelectionGroup();
474 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
480 * Returns the visible width of the canvas in residues, after allowing for
481 * East or West scales (if shown)
484 * the width in pixels (possibly including scales)
488 public int getWrappedCanvasWidth(int canvasWidth)
490 int charWidth = av.getCharWidth();
492 FontMetrics fm = getFontMetrics(av.getFont());
496 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
498 labelWidth = getLabelWidth(fm);
501 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
503 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
505 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
509 * Returns a pixel width sufficient to show the largest sequence coordinate
510 * (end position) in the alignment, calculated as the FontMetrics width of
511 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
512 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
513 * half a character width space on either side.
518 protected int getLabelWidth(FontMetrics fm)
521 * find the biggest sequence end position we need to show
522 * (note this is not necessarily the sequence length)
525 AlignmentI alignment = av.getAlignment();
526 for (int i = 0; i < alignment.getHeight(); i++)
528 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
532 for (int i = maxWidth; i > 0; i /= 10)
537 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
541 * Draws as many widths of a wrapped alignment as can fit in the visible
546 * available width in pixels
547 * @param canvasHeight
548 * available height in pixels
550 * the first column (0...) of the alignment to draw
552 public void drawWrappedPanel(Graphics g, int canvasWidth,
553 int canvasHeight, final int startColumn)
555 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
558 av.setWrappedWidth(wrappedWidthInResidues);
560 ViewportRanges ranges = av.getRanges();
561 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
563 // we need to call this again to make sure the startColumn +
564 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
566 calculateWrappedGeometry(canvasWidth, canvasHeight);
569 * draw one width at a time (excluding any scales shown),
570 * until we have run out of either alignment or vertical space available
572 int ypos = wrappedSpaceAboveAlignment;
573 int maxWidth = ranges.getVisibleAlignmentWidth();
575 int start = startColumn;
576 int currentWidth = 0;
577 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
580 .min(maxWidth, start + wrappedWidthInResidues - 1);
581 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
582 ypos += wrappedRepeatHeightPx;
583 start += wrappedWidthInResidues;
587 drawWrappedDecorators(g, startColumn);
591 * Calculates and saves values needed when rendering a wrapped alignment.
592 * These depend on many factors, including
594 * <li>canvas width and height</li>
595 * <li>number of visible sequences, and height of annotations if shown</li>
596 * <li>font and character width</li>
597 * <li>whether scales are shown left, right or above the alignment</li>
601 * @param canvasHeight
602 * @return the number of residue columns in each width
604 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
606 int charHeight = av.getCharHeight();
609 * vertical space in pixels between wrapped widths of alignment
610 * - one character height, or two if scale above is drawn
612 wrappedSpaceAboveAlignment = charHeight
613 * (av.getScaleAboveWrapped() ? 2 : 1);
616 * compute height in pixels of the wrapped widths
617 * - start with space above plus sequences
619 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
620 wrappedRepeatHeightPx += av.getAlignment().getHeight()
624 * add annotations panel height if shown
625 * also gap between sequences and annotations
627 if (av.isShowAnnotation())
629 wrappedRepeatHeightPx += getAnnotationHeight();
630 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
634 * number of visible widths (the last one may be part height),
635 * ensuring a part height includes at least one sequence
637 ViewportRanges ranges = av.getRanges();
638 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
639 int remainder = canvasHeight % wrappedRepeatHeightPx;
640 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
642 wrappedVisibleWidths++;
646 * compute width in residues; this also sets East and West label widths
648 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
651 * limit visibleWidths to not exceed width of alignment
653 int xMax = ranges.getVisibleAlignmentWidth();
654 int startToEnd = xMax - ranges.getStartRes();
655 int maxWidths = startToEnd / wrappedWidthInResidues;
656 if (startToEnd % wrappedWidthInResidues > 0)
660 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
662 return wrappedWidthInResidues;
666 * Draws one width of a wrapped alignment, including sequences and
667 * annnotations, if shown, but not scales or hidden column markers
673 * @param canvasHeight
675 protected void drawWrappedWidth(Graphics g, final int ypos,
676 final int startColumn, final int endColumn,
677 final int canvasHeight)
679 ViewportRanges ranges = av.getRanges();
680 int viewportWidth = ranges.getViewportWidth();
682 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
685 * move right before drawing by the width of the scale left (if any)
686 * plus column offset from left margin (usually zero, but may be non-zero
687 * when fast painting is drawing just a few columns)
689 int charWidth = av.getCharWidth();
690 int xOffset = labelWidthWest
691 + ((startColumn - ranges.getStartRes()) % viewportWidth)
693 g.translate(xOffset, 0);
695 // When printing we have an extra clipped region,
696 // the Printable page which we need to account for here
697 Shape clip = g.getClip();
701 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
705 g.setClip(0, (int) clip.getBounds().getY(),
706 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
710 * white fill the region to be drawn (so incremental fast paint doesn't
711 * scribble over an existing image)
713 g.setColor(Color.white);
714 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
715 wrappedRepeatHeightPx);
717 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
720 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
722 if (av.isShowAnnotation())
724 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
725 g.translate(0, yShift);
726 if (annotations == null)
728 annotations = new AnnotationPanel(av);
731 annotations.renderer.drawComponent(annotations, av, g, -1,
732 startColumn, endx + 1);
733 g.translate(0, -yShift);
736 g.translate(-xOffset, 0);
740 * Draws scales left, right and above (if shown), and any hidden column
741 * markers, on all widths of the wrapped alignment
746 protected void drawWrappedDecorators(Graphics g, final int startColumn)
748 int charWidth = av.getCharWidth();
750 g.setFont(av.getFont());
751 g.setColor(Color.black);
753 int ypos = wrappedSpaceAboveAlignment;
754 ViewportRanges ranges = av.getRanges();
755 int viewportWidth = ranges.getViewportWidth();
756 int maxWidth = ranges.getVisibleAlignmentWidth();
758 int startCol = startColumn;
760 while (widthsDrawn < wrappedVisibleWidths)
762 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
764 if (av.getScaleLeftWrapped())
766 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
769 if (av.getScaleRightWrapped())
771 int x = labelWidthWest + viewportWidth * charWidth;
773 drawVerticalScale(g, startCol, endColumn, ypos, false);
778 * white fill region of scale above and hidden column markers
779 * (to support incremental fast paint of image)
781 g.translate(labelWidthWest, 0);
782 g.setColor(Color.white);
783 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
784 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
785 g.setColor(Color.black);
786 g.translate(-labelWidthWest, 0);
788 g.translate(labelWidthWest, 0);
790 if (av.getScaleAboveWrapped())
792 drawNorthScale(g, startCol, endColumn, ypos);
795 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
797 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
800 g.translate(-labelWidthWest, 0);
802 ypos += wrappedRepeatHeightPx;
803 startCol += viewportWidth;
809 * Draws markers (triangles) above hidden column positions between startColumn
817 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
818 int startColumn, int endColumn)
820 int charHeight = av.getCharHeight();
821 int charWidth = av.getCharWidth();
823 g.setColor(Color.blue);
825 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
827 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
831 res = it.next() - startColumn;
833 if (res < 0 || res > endColumn - startColumn + 1)
839 * draw a downward-pointing triangle at the hidden columns location
840 * (before the following visible column)
842 int xMiddle = res * charWidth;
843 int[] xPoints = new int[] { xMiddle - charHeight / 4,
844 xMiddle + charHeight / 4, xMiddle };
845 int yTop = ypos - (charHeight / 2);
846 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
847 g.fillPolygon(xPoints, yPoints, 3);
852 * Draw a selection group over a wrapped alignment
854 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
856 int canvasHeight, int startRes)
858 // chop the wrapped alignment extent up into panel-sized blocks and treat
859 // each block as if it were a block from an unwrapped alignment
860 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
861 BasicStroke.JOIN_ROUND, 3f, new float[]
863 g.setColor(Color.RED);
865 int charWidth = av.getCharWidth();
866 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
868 int startx = startRes;
869 int maxwidth = av.getAlignment().getVisibleWidth();
870 int ypos = wrappedSpaceAboveAlignment;
872 while ((ypos <= canvasHeight) && (startx < maxwidth))
874 // set end value to be start + width, or maxwidth, whichever is smaller
875 int endx = startx + cWidth - 1;
882 g.translate(labelWidthWest, 0);
883 drawUnwrappedSelection(g, group, startx, endx, 0,
884 av.getAlignment().getHeight() - 1,
886 g.translate(-labelWidthWest, 0);
888 ypos += wrappedRepeatHeightPx;
892 g.setStroke(new BasicStroke());
896 * Answers zero if annotations are not shown, otherwise recalculates and answers
897 * the total height of all annotation rows in pixels
901 int getAnnotationHeight()
903 if (!av.isShowAnnotation())
908 if (annotations == null)
910 annotations = new AnnotationPanel(av);
913 return annotations.adjustPanelHeight();
917 * Draws the visible region of the alignment on the graphics context. If there
918 * are hidden column markers in the visible region, then each sub-region
919 * between the markers is drawn separately, followed by the hidden column
923 * the graphics context, positioned at the first residue to be drawn
925 * offset of the first column to draw (0..)
927 * offset of the last column to draw (0..)
929 * offset of the first sequence to draw (0..)
931 * offset of the last sequence to draw (0..)
933 * vertical offset at which to draw (for wrapped alignments)
935 public void drawPanel(Graphics g1, final int startRes, final int endRes,
936 final int startSeq, final int endSeq, final int yOffset)
938 int charHeight = av.getCharHeight();
939 int charWidth = av.getCharWidth();
941 if (!av.hasHiddenColumns())
943 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
951 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
952 VisibleContigsIterator regions = hidden
953 .getVisContigsIterator(startRes, endRes + 1, true);
955 while (regions.hasNext())
957 int[] region = regions.next();
958 blockEnd = region[1];
959 blockStart = region[0];
962 * draw up to just before the next hidden region, or the end of
963 * the visible region, whichever comes first
965 g1.translate(screenY * charWidth, 0);
967 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
970 * draw the downline of the hidden column marker (ScalePanel draws the
971 * triangle on top) if we reached it
973 if (av.getShowHiddenMarkers()
974 && (regions.hasNext() || regions.endsAtHidden()))
976 g1.setColor(Color.blue);
978 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
979 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
980 (endSeq - startSeq + 1) * charHeight + yOffset);
983 g1.translate(-screenY * charWidth, 0);
984 screenY += blockEnd - blockStart + 1;
991 * Draws a region of the visible alignment
995 * offset of the first column in the visible region (0..)
997 * offset of the last column in the visible region (0..)
999 * offset of the first sequence in the visible region (0..)
1001 * offset of the last sequence in the visible region (0..)
1003 * vertical offset at which to draw (for wrapped alignments)
1005 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1006 int endSeq, int offset)
1008 int charHeight = av.getCharHeight();
1009 int charWidth = av.getCharWidth();
1011 g.setFont(av.getFont());
1012 seqRdr.prepare(g, av.isRenderGaps());
1016 // / First draw the sequences
1017 // ///////////////////////////
1018 for (int i = startSeq; i <= endSeq; i++)
1020 nextSeq = av.getAlignment().getSequenceAt(i);
1021 if (nextSeq == null)
1023 // occasionally, a race condition occurs such that the alignment row is
1027 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1028 startRes, endRes, offset + ((i - startSeq) * charHeight));
1030 if (av.isShowSequenceFeatures())
1032 fr.drawSequence(g, nextSeq, startRes, endRes,
1033 offset + ((i - startSeq) * charHeight), false);
1037 * highlight search Results once sequence has been drawn
1039 if (av.hasSearchResults())
1041 SearchResultsI searchResults = av.getSearchResults();
1042 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1044 if (visibleResults != null)
1046 for (int r = 0; r < visibleResults.length; r += 2)
1048 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1049 visibleResults[r + 1],
1050 (visibleResults[r] - startRes) * charWidth,
1051 offset + ((i - startSeq) * charHeight));
1057 if (av.getSelectionGroup() != null
1058 || av.getAlignment().getGroups().size() > 0)
1060 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1066 * Draws the outlines of any groups defined on the alignment (excluding the
1067 * current selection group, if any)
1076 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1077 int startSeq, int endSeq, int offset)
1079 Graphics2D g = (Graphics2D) g1;
1081 SequenceGroup group = null;
1082 int groupIndex = -1;
1084 if (av.getAlignment().getGroups().size() > 0)
1086 group = av.getAlignment().getGroups().get(0);
1094 g.setColor(group.getOutlineColour());
1096 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1101 if (groupIndex >= av.getAlignment().getGroups().size())
1106 group = av.getAlignment().getGroups().get(groupIndex);
1108 } while (groupIndex < av.getAlignment().getGroups().size());
1115 * Draws the outline of the current selection group (if any)
1123 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1124 int startSeq, int endSeq)
1126 SequenceGroup group = av.getSelectionGroup();
1132 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1133 BasicStroke.JOIN_ROUND, 3f, new float[]
1135 g.setColor(Color.RED);
1136 if (!av.getWrapAlignment())
1138 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1143 drawWrappedSelection(g, group, getWidth(), getHeight(),
1144 av.getRanges().getStartRes());
1146 g.setStroke(new BasicStroke());
1150 * Draw the cursor as a separate image and overlay
1153 * start residue of area to draw cursor in
1155 * end residue of area to draw cursor in
1157 * start sequence of area to draw cursor in
1159 * end sequence of are to draw cursor in
1160 * @return a transparent image of the same size as the sequence canvas, with
1161 * the cursor drawn on it, if any
1163 private void drawCursor(Graphics g, int startRes, int endRes,
1167 // convert the cursorY into a position on the visible alignment
1168 int cursor_ypos = cursorY;
1170 // don't do work unless we have to
1171 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1175 int startx = startRes;
1178 // convert the cursorX into a position on the visible alignment
1179 int cursor_xpos = av.getAlignment().getHiddenColumns()
1180 .absoluteToVisibleColumn(cursorX);
1182 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1185 if (av.getWrapAlignment())
1187 // work out the correct offsets for the cursor
1188 int charHeight = av.getCharHeight();
1189 int charWidth = av.getCharWidth();
1190 int canvasWidth = getWidth();
1191 int canvasHeight = getHeight();
1193 // height gap above each panel
1194 int hgap = charHeight;
1195 if (av.getScaleAboveWrapped())
1200 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1202 int cHeight = av.getAlignment().getHeight() * charHeight;
1204 endx = startx + cWidth - 1;
1205 int ypos = hgap; // vertical offset
1207 // iterate down the wrapped panels
1208 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1210 // update vertical offset
1211 ypos += cHeight + getAnnotationHeight() + hgap;
1213 // update horizontal offset
1215 endx = startx + cWidth - 1;
1218 xoffset = labelWidthWest;
1221 // now check if cursor is within range for x values
1222 if (cursor_xpos >= startx && cursor_xpos <= endx)
1224 // get the character the cursor is drawn at
1225 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1226 char s = seq.getCharAt(cursorX);
1228 seqRdr.drawCursor(g, s,
1229 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1230 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1238 * Draw a selection group over an unwrapped alignment
1241 * graphics object to draw with
1245 * start residue of area to draw
1247 * end residue of area to draw
1249 * start sequence of area to draw
1251 * end sequence of area to draw
1253 * vertical offset (used when called from wrapped alignment code)
1255 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1256 int startRes, int endRes, int startSeq, int endSeq, int offset)
1258 int charWidth = av.getCharWidth();
1260 if (!av.hasHiddenColumns())
1262 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1267 // package into blocks of visible columns
1272 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1273 VisibleContigsIterator regions = hidden
1274 .getVisContigsIterator(startRes, endRes + 1, true);
1275 while (regions.hasNext())
1277 int[] region = regions.next();
1278 blockEnd = region[1];
1279 blockStart = region[0];
1281 g.translate(screenY * charWidth, 0);
1282 drawPartialGroupOutline(g, group,
1283 blockStart, blockEnd, startSeq, endSeq, offset);
1285 g.translate(-screenY * charWidth, 0);
1286 screenY += blockEnd - blockStart + 1;
1292 * Draws part of a selection group outline
1300 * @param verticalOffset
1302 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1303 int startRes, int endRes, int startSeq, int endSeq,
1306 int charHeight = av.getCharHeight();
1307 int charWidth = av.getCharWidth();
1308 int visWidth = (endRes - startRes + 1) * charWidth;
1312 boolean inGroup = false;
1317 List<SequenceI> seqs = group.getSequences(null);
1319 // position of start residue of group relative to startRes, in pixels
1320 int sx = (group.getStartRes() - startRes) * charWidth;
1322 // width of group in pixels
1323 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1326 if (!(sx + xwidth < 0 || sx > visWidth))
1328 for (i = startSeq; i <= endSeq; i++)
1330 sy = verticalOffset + (i - startSeq) * charHeight;
1332 if ((sx <= (endRes - startRes) * charWidth)
1333 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1336 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1338 bottom = sy + charHeight;
1343 if (((top == -1) && (i == 0)) || !seqs
1344 .contains(av.getAlignment().getSequenceAt(i - 1)))
1355 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1356 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1358 // reset top and bottom
1366 sy = verticalOffset + ((i - startSeq) * charHeight);
1367 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1368 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1374 * Draw horizontal selection group boundaries at top and bottom positions
1377 * graphics object to draw on
1383 * visWidth maximum available width
1385 * position to draw top of group at
1387 * position to draw bottom of group at
1389 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1390 int visWidth, int top, int bottom)
1400 // don't let width extend beyond current block, or group extent
1402 if (startx + width >= visWidth)
1404 width = visWidth - startx;
1409 g.drawLine(startx, top, startx + width, top);
1414 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1419 * Draw vertical lines at sx and sx+xwidth providing they lie within
1423 * graphics object to draw on
1429 * visWidth maximum available width
1435 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1438 // if start position is visible, draw vertical line to left of
1440 if (sx >= 0 && sx < visWidth)
1442 g.drawLine(sx, oldY, sx, sy);
1445 // if end position is visible, draw vertical line to right of
1447 if (sx + xwidth < visWidth)
1449 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1454 * Highlights search results in the visible region by rendering as white text
1455 * on a black background. Any previous highlighting is removed. Answers true
1456 * if any highlight was left on the visible alignment (so status bar should be
1457 * set to match), else false.
1459 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1460 * alignment had to be scrolled to show the highlighted region, then it should
1461 * be fully redrawn, otherwise a fast paint can be performed. This argument
1462 * could be removed if fast paint of scrolled wrapped alignment is coded in
1463 * future (JAL-2609).
1466 * @param noFastPaint
1469 public boolean highlightSearchResults(SearchResultsI results,
1470 boolean noFastPaint)
1476 boolean wrapped = av.getWrapAlignment();
1479 fastPaint = !noFastPaint;
1480 fastpainting = fastPaint;
1483 * to avoid redrawing the whole visible region, we instead
1484 * redraw just the minimal regions to remove previous highlights
1487 SearchResultsI previous = av.getSearchResults();
1488 av.setSearchResults(results);
1489 boolean redrawn = false;
1490 boolean drawn = false;
1493 redrawn = drawMappedPositionsWrapped(previous);
1494 drawn = drawMappedPositionsWrapped(results);
1499 redrawn = drawMappedPositions(previous);
1500 drawn = drawMappedPositions(results);
1505 * if highlights were either removed or added, repaint
1513 * return true only if highlights were added
1519 fastpainting = false;
1524 * Redraws the minimal rectangle in the visible region (if any) that includes
1525 * mapped positions of the given search results. Whether or not positions are
1526 * highlighted depends on the SearchResults set on the Viewport. This allows
1527 * this method to be called to either clear or set highlighting. Answers true
1528 * if any positions were drawn (in which case a repaint is still required),
1534 protected boolean drawMappedPositions(SearchResultsI results)
1536 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1542 * calculate the minimal rectangle to redraw that
1543 * includes both new and existing search results
1545 int firstSeq = Integer.MAX_VALUE;
1547 int firstCol = Integer.MAX_VALUE;
1549 boolean matchFound = false;
1551 ViewportRanges ranges = av.getRanges();
1552 int firstVisibleColumn = ranges.getStartRes();
1553 int lastVisibleColumn = ranges.getEndRes();
1554 AlignmentI alignment = av.getAlignment();
1555 if (av.hasHiddenColumns())
1557 firstVisibleColumn = alignment.getHiddenColumns()
1558 .visibleToAbsoluteColumn(firstVisibleColumn);
1559 lastVisibleColumn = alignment.getHiddenColumns()
1560 .visibleToAbsoluteColumn(lastVisibleColumn);
1563 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1564 .getEndSeq(); seqNo++)
1566 SequenceI seq = alignment.getSequenceAt(seqNo);
1568 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1570 if (visibleResults != null)
1572 for (int i = 0; i < visibleResults.length - 1; i += 2)
1574 int firstMatchedColumn = visibleResults[i];
1575 int lastMatchedColumn = visibleResults[i + 1];
1576 if (firstMatchedColumn <= lastVisibleColumn
1577 && lastMatchedColumn >= firstVisibleColumn)
1580 * found a search results match in the visible region -
1581 * remember the first and last sequence matched, and the first
1582 * and last visible columns in the matched positions
1585 firstSeq = Math.min(firstSeq, seqNo);
1586 lastSeq = Math.max(lastSeq, seqNo);
1587 firstMatchedColumn = Math.max(firstMatchedColumn,
1588 firstVisibleColumn);
1589 lastMatchedColumn = Math.min(lastMatchedColumn,
1591 firstCol = Math.min(firstCol, firstMatchedColumn);
1592 lastCol = Math.max(lastCol, lastMatchedColumn);
1600 if (av.hasHiddenColumns())
1602 firstCol = alignment.getHiddenColumns()
1603 .absoluteToVisibleColumn(firstCol);
1604 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1606 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1607 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1608 gg.translate(transX, transY);
1609 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1610 gg.translate(-transX, -transY);
1617 public void propertyChange(PropertyChangeEvent evt)
1619 String eventName = evt.getPropertyName();
1621 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1627 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1635 if (eventName.equals(ViewportRanges.STARTRES)
1636 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1638 // Make sure we're not trying to draw a panel
1639 // larger than the visible window
1640 if (eventName.equals(ViewportRanges.STARTRES))
1642 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1646 scrollX = ((int[]) evt.getNewValue())[0]
1647 - ((int[]) evt.getOldValue())[0];
1649 ViewportRanges vpRanges = av.getRanges();
1651 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1652 if (scrollX > range)
1656 else if (scrollX < -range)
1661 // Both scrolling and resizing change viewport ranges: scrolling changes
1662 // both start and end points, but resize only changes end values.
1663 // Here we only want to fastpaint on a scroll, with resize using a normal
1664 // paint, so scroll events are identified as changes to the horizontal or
1665 // vertical start value.
1666 if (eventName.equals(ViewportRanges.STARTRES))
1668 if (av.getWrapAlignment())
1670 fastPaintWrapped(scrollX);
1674 fastPaint(scrollX, 0);
1677 else if (eventName.equals(ViewportRanges.STARTSEQ))
1680 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1682 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1684 if (av.getWrapAlignment())
1686 fastPaintWrapped(scrollX);
1690 fastPaint(scrollX, 0);
1693 else if (eventName.equals(ViewportRanges.STARTSEQ))
1696 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1698 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1700 if (av.getWrapAlignment())
1702 fastPaintWrapped(scrollX);
1708 * Does a minimal update of the image for a scroll movement. This method
1709 * handles scroll movements of up to one width of the wrapped alignment (one
1710 * click in the vertical scrollbar). Larger movements (for example after a
1711 * scroll to highlight a mapped position) trigger a full redraw instead.
1714 * number of positions scrolled (right if positive, left if negative)
1716 protected void fastPaintWrapped(int scrollX)
1718 ViewportRanges ranges = av.getRanges();
1720 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1723 * shift of one view width or more is
1724 * overcomplicated to handle in this method
1731 if (fastpainting || gg == null)
1737 fastpainting = true;
1741 calculateWrappedGeometry(getWidth(), getHeight());
1744 * relocate the regions of the alignment that are still visible
1746 shiftWrappedAlignment(-scrollX);
1749 * add new columns (sequence, annotation)
1750 * - at top left if scrollX < 0
1751 * - at right of last two widths if scrollX > 0
1755 int startRes = ranges.getStartRes();
1756 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1757 - scrollX - 1, getHeight());
1761 fastPaintWrappedAddRight(scrollX);
1765 * draw all scales (if shown) and hidden column markers
1767 drawWrappedDecorators(gg, ranges.getStartRes());
1772 fastpainting = false;
1777 * Draws the specified number of columns at the 'end' (bottom right) of a
1778 * wrapped alignment view, including sequences and annotations if shown, but
1779 * not scales. Also draws the same number of columns at the right hand end of
1780 * the second last width shown, if the last width is not full height (so
1781 * cannot simply be copied from the graphics image).
1785 protected void fastPaintWrappedAddRight(int columns)
1792 ViewportRanges ranges = av.getRanges();
1793 int viewportWidth = ranges.getViewportWidth();
1794 int charWidth = av.getCharWidth();
1797 * draw full height alignment in the second last row, last columns, if the
1798 * last row was not full height
1800 int visibleWidths = wrappedVisibleWidths;
1801 int canvasHeight = getHeight();
1802 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1804 if (lastWidthPartHeight)
1806 int widthsAbove = Math.max(0, visibleWidths - 2);
1807 int ypos = wrappedRepeatHeightPx * widthsAbove
1808 + wrappedSpaceAboveAlignment;
1809 int endRes = ranges.getEndRes();
1810 endRes += widthsAbove * viewportWidth;
1811 int startRes = endRes - columns;
1812 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1816 * white fill first to erase annotations
1818 gg.translate(xOffset, 0);
1819 gg.setColor(Color.white);
1820 gg.fillRect(labelWidthWest, ypos,
1821 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1822 gg.translate(-xOffset, 0);
1824 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1828 * draw newly visible columns in last wrapped width (none if we
1829 * have reached the end of the alignment)
1830 * y-offset for drawing last width is height of widths above,
1833 int widthsAbove = visibleWidths - 1;
1834 int ypos = wrappedRepeatHeightPx * widthsAbove
1835 + wrappedSpaceAboveAlignment;
1836 int endRes = ranges.getEndRes();
1837 endRes += widthsAbove * viewportWidth;
1838 int startRes = endRes - columns + 1;
1841 * white fill first to erase annotations
1843 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1845 gg.translate(xOffset, 0);
1846 gg.setColor(Color.white);
1847 int width = viewportWidth * charWidth - xOffset;
1848 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1849 gg.translate(-xOffset, 0);
1851 gg.setFont(av.getFont());
1852 gg.setColor(Color.black);
1854 if (startRes < ranges.getVisibleAlignmentWidth())
1856 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1860 * and finally, white fill any space below the visible alignment
1862 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1863 if (heightBelow > 0)
1865 gg.setColor(Color.white);
1866 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1871 * Shifts the visible alignment by the specified number of columns - left if
1872 * negative, right if positive. Copies and moves sequences and annotations (if
1873 * shown). Scales, hidden column markers and any newly visible columns must be
1878 protected void shiftWrappedAlignment(int positions)
1884 int charWidth = av.getCharWidth();
1886 int canvasHeight = getHeight();
1887 ViewportRanges ranges = av.getRanges();
1888 int viewportWidth = ranges.getViewportWidth();
1889 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1891 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1892 int xMax = ranges.getVisibleAlignmentWidth();
1897 * shift right (after scroll left)
1898 * for each wrapped width (starting with the last), copy (width-positions)
1899 * columns from the left margin to the right margin, and copy positions
1900 * columns from the right margin of the row above (if any) to the
1901 * left margin of the current row
1905 * get y-offset of last wrapped width, first row of sequences
1907 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1908 y += wrappedSpaceAboveAlignment;
1909 int copyFromLeftStart = labelWidthWest;
1910 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1915 * shift 'widthToCopy' residues by 'positions' places to the right
1917 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1918 positions * charWidth, 0);
1922 * copy 'positions' residue from the row above (right hand end)
1923 * to this row's left hand end
1925 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1926 positions * charWidth, heightToCopy, -widthToCopy,
1927 wrappedRepeatHeightPx);
1930 y -= wrappedRepeatHeightPx;
1936 * shift left (after scroll right)
1937 * for each wrapped width (starting with the first), copy (width-positions)
1938 * columns from the right margin to the left margin, and copy positions
1939 * columns from the left margin of the row below (if any) to the
1940 * right margin of the current row
1942 int xpos = av.getRanges().getStartRes();
1943 int y = wrappedSpaceAboveAlignment;
1944 int copyFromRightStart = labelWidthWest - positions * charWidth;
1946 while (y < canvasHeight)
1948 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1949 positions * charWidth, 0);
1950 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1951 && (xpos + viewportWidth <= xMax))
1953 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1954 * charWidth, heightToCopy, widthToCopy,
1955 -wrappedRepeatHeightPx);
1958 y += wrappedRepeatHeightPx;
1959 xpos += viewportWidth;
1966 * Redraws any positions in the search results in the visible region of a
1967 * wrapped alignment. Any highlights are drawn depending on the search results
1968 * set on the Viewport, not the <code>results</code> argument. This allows
1969 * this method to be called either to clear highlights (passing the previous
1970 * search results), or to draw new highlights.
1975 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1977 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1981 int charHeight = av.getCharHeight();
1983 boolean matchFound = false;
1985 calculateWrappedGeometry(getWidth(), getHeight());
1986 int wrappedWidth = av.getWrappedWidth();
1987 int wrappedHeight = wrappedRepeatHeightPx;
1989 ViewportRanges ranges = av.getRanges();
1990 int canvasHeight = getHeight();
1991 int repeats = canvasHeight / wrappedHeight;
1992 if (canvasHeight / wrappedHeight > 0)
1997 int firstVisibleColumn = ranges.getStartRes();
1998 int lastVisibleColumn = ranges.getStartRes() + repeats
1999 * ranges.getViewportWidth() - 1;
2001 AlignmentI alignment = av.getAlignment();
2002 if (av.hasHiddenColumns())
2004 firstVisibleColumn = alignment.getHiddenColumns()
2005 .visibleToAbsoluteColumn(firstVisibleColumn);
2006 lastVisibleColumn = alignment.getHiddenColumns()
2007 .visibleToAbsoluteColumn(lastVisibleColumn);
2010 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2012 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2013 .getEndSeq(); seqNo++)
2015 SequenceI seq = alignment.getSequenceAt(seqNo);
2017 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2019 if (visibleResults != null)
2021 for (int i = 0; i < visibleResults.length - 1; i += 2)
2023 int firstMatchedColumn = visibleResults[i];
2024 int lastMatchedColumn = visibleResults[i + 1];
2025 if (firstMatchedColumn <= lastVisibleColumn
2026 && lastMatchedColumn >= firstVisibleColumn)
2029 * found a search results match in the visible region
2031 firstMatchedColumn = Math.max(firstMatchedColumn,
2032 firstVisibleColumn);
2033 lastMatchedColumn = Math.min(lastMatchedColumn,
2037 * draw each mapped position separately (as contiguous positions may
2038 * wrap across lines)
2040 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2042 int displayColumn = mappedPos;
2043 if (av.hasHiddenColumns())
2045 displayColumn = alignment.getHiddenColumns()
2046 .absoluteToVisibleColumn(displayColumn);
2050 * transX: offset from left edge of canvas to residue position
2052 int transX = labelWidthWest
2053 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2054 * av.getCharWidth();
2057 * transY: offset from top edge of canvas to residue position
2059 int transY = gapHeight;
2060 transY += (displayColumn - ranges.getStartRes())
2061 / wrappedWidth * wrappedHeight;
2062 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2065 * yOffset is from graphics origin to start of visible region
2067 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2068 if (transY < getHeight())
2071 gg.translate(transX, transY);
2072 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2074 gg.translate(-transX, -transY);
2086 * Answers the width in pixels of the left scale labels (0 if not shown)
2090 int getLabelWidthWest()
2092 return labelWidthWest;