2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JComponent;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 public class SeqCanvas extends JComponent implements ViewportListenerI
59 * pixels gap between sequences and annotations when in wrapped mode
61 static final int SEQS_ANNOTATION_GAP = 3;
63 private static final String ZEROS = "0000000000";
65 final FeatureRenderer fr;
75 private final SequenceRenderer seqRdr;
77 private boolean fastPaint = false;
79 private boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 private int labelWidthEast; // label right width in pixels if shown
88 private int labelWidthWest; // label left width in pixels if shown
90 private int wrappedSpaceAboveAlignment; // gap between widths
92 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
96 private Graphics2D gg;
99 * Creates a new SeqCanvas object.
103 public SeqCanvas(AlignmentPanel ap)
106 fr = new FeatureRenderer(ap);
107 seqRdr = new SequenceRenderer(av);
108 setLayout(new BorderLayout());
109 PaintRefresher.Register(this, av.getSequenceSetId());
110 setBackground(Color.white);
112 av.getRanges().addPropertyChangeListener(this);
115 public SequenceRenderer getSequenceRenderer()
120 public FeatureRenderer getFeatureRenderer()
126 * Draws the scale above a region of a wrapped alignment, consisting of a
127 * column number every major interval (10 columns).
130 * the graphics context to draw on, positioned at the start (bottom
131 * left) of the line on which to draw any scale marks
133 * start alignment column (0..)
135 * end alignment column (0..)
137 * y offset to draw at
139 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
141 int charHeight = av.getCharHeight();
142 int charWidth = av.getCharWidth();
145 * white fill the scale space (for the fastPaint case)
147 g.setColor(Color.white);
148 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
149 charHeight * 3 / 2 + 2);
150 g.setColor(Color.black);
152 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
154 for (ScaleMark mark : marks)
156 int mpos = mark.column; // (i - startx - 1)
161 String mstring = mark.text;
167 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
171 * draw a tick mark below the column number, centred on the column;
172 * height of tick mark is 4 pixels less than half a character
174 int xpos = (mpos * charWidth) + (charWidth / 2);
175 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
181 * Draw the scale to the left or right of a wrapped alignment
184 * graphics context, positioned at the start of the scale to be drawn
186 * first column of wrapped width (0.. excluding any hidden columns)
188 * last column of wrapped width (0.. excluding any hidden columns)
190 * vertical offset at which to begin the scale
192 * if true, scale is left of residues, if false, scale is right
194 void drawVerticalScale(Graphics g, final int startx, final int endx,
195 final int ypos, final boolean left)
197 int charHeight = av.getCharHeight();
198 int charWidth = av.getCharWidth();
200 int yPos = ypos + charHeight;
204 if (av.hasHiddenColumns())
206 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
207 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
208 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
210 FontMetrics fm = getFontMetrics(av.getFont());
212 for (int i = 0; i < av.getAlignment().getHeight(); i++)
214 SequenceI seq = av.getAlignment().getSequenceAt(i);
217 * find sequence position of first non-gapped position -
218 * to the right if scale left, to the left if scale right
220 int index = left ? startX : endX;
222 while (index >= startX && index <= endX)
224 if (!Comparison.isGap(seq.getCharAt(index)))
226 value = seq.findPosition(index);
240 * white fill the space for the scale
242 g.setColor(Color.white);
243 int y = (yPos + (i * charHeight)) - (charHeight / 5);
244 // fillRect origin is top left of rectangle
245 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
251 * draw scale value, right justified within its width less half a
252 * character width padding on the right
254 int labelSpace = left ? labelWidthWest : labelWidthEast;
255 labelSpace -= charWidth / 2; // leave space to the right
256 String valueAsString = String.valueOf(value);
257 int labelLength = fm.stringWidth(valueAsString);
258 int xOffset = labelSpace - labelLength;
259 g.setColor(Color.black);
260 g.drawString(valueAsString, xOffset, y);
266 * Does a fast paint of an alignment in response to a scroll. Most of the
267 * visible region is simply copied and shifted, and then any newly visible
268 * columns or rows are drawn. The scroll may be horizontal or vertical, but
269 * not both at once. Scrolling may be the result of
271 * <li>dragging a scroll bar</li>
272 * <li>clicking in the scroll bar</li>
273 * <li>scrolling by trackpad, middle mouse button, or other device</li>
274 * <li>by moving the box in the Overview window</li>
275 * <li>programmatically to make a highlighted position visible</li>
279 * columns to shift right (positive) or left (negative)
281 * rows to shift down (positive) or up (negative)
283 public void fastPaint(int horizontal, int vertical)
285 if (fastpainting || gg == null || img == null)
294 int charHeight = av.getCharHeight();
295 int charWidth = av.getCharWidth();
297 ViewportRanges ranges = av.getRanges();
298 int startRes = ranges.getStartRes();
299 int endRes = ranges.getEndRes();
300 int startSeq = ranges.getStartSeq();
301 int endSeq = ranges.getEndSeq();
305 gg.copyArea(horizontal * charWidth, vertical * charHeight,
306 img.getWidth(), img.getHeight(), -horizontal * charWidth,
307 -vertical * charHeight);
309 if (horizontal > 0) // scrollbar pulled right, image to the left
311 transX = (endRes - startRes - horizontal) * charWidth;
312 startRes = endRes - horizontal;
314 else if (horizontal < 0)
316 endRes = startRes - horizontal;
319 if (vertical > 0) // scroll down
321 startSeq = endSeq - vertical;
323 if (startSeq < ranges.getStartSeq())
324 { // ie scrolling too fast, more than a page at a time
325 startSeq = ranges.getStartSeq();
329 transY = img.getHeight() - ((vertical + 1) * charHeight);
332 else if (vertical < 0)
334 endSeq = startSeq - vertical;
336 if (endSeq > ranges.getEndSeq())
338 endSeq = ranges.getEndSeq();
342 gg.translate(transX, transY);
343 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
344 gg.translate(-transX, -transY);
346 // Call repaint on alignment panel so that repaints from other alignment
347 // panel components can be aggregated. Otherwise performance of the
348 // overview window and others may be adversely affected.
349 av.getAlignPanel().repaint();
352 fastpainting = false;
357 public void paintComponent(Graphics g)
359 super.paintComponent(g);
361 int charHeight = av.getCharHeight();
362 int charWidth = av.getCharWidth();
364 ViewportRanges ranges = av.getRanges();
366 int width = getWidth();
367 int height = getHeight();
369 width -= (width % charWidth);
370 height -= (height % charHeight);
372 if ((img != null) && (fastPaint
373 || (getVisibleRect().width != g.getClipBounds().width)
374 || (getVisibleRect().height != g.getClipBounds().height)))
376 g.drawImage(img, 0, 0, this);
378 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
379 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
383 else if (width > 0 && height > 0)
386 * img is a cached version of the last view we drew, if any
387 * if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
392 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 drawSelectionGroup(gg, ranges.getStartRes(),
417 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
419 g.drawImage(img, 0, 0, this);
424 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
425 ranges.getStartSeq(), ranges.getEndSeq());
430 * Draw an alignment panel for printing
433 * Graphics object to draw with
435 * start residue of print area
437 * end residue of print area
439 * start sequence of print area
441 * end sequence of print area
443 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
444 int startSeq, int endSeq)
446 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
448 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
453 * Draw a wrapped alignment panel for printing
456 * Graphics object to draw with
458 * width of drawing area
459 * @param canvasHeight
460 * height of drawing area
462 * start residue of print area
464 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
465 int canvasHeight, int startRes)
467 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
469 SequenceGroup group = av.getSelectionGroup();
472 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
478 * Returns the visible width of the canvas in residues, after allowing for
479 * East or West scales (if shown)
482 * the width in pixels (possibly including scales)
486 public int getWrappedCanvasWidth(int canvasWidth)
488 int charWidth = av.getCharWidth();
490 FontMetrics fm = getFontMetrics(av.getFont());
494 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
496 labelWidth = getLabelWidth(fm);
499 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
501 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
503 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
507 * Returns a pixel width sufficient to show the largest sequence coordinate
508 * (end position) in the alignment, calculated as the FontMetrics width of
509 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
510 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
511 * half a character width space on either side.
516 protected int getLabelWidth(FontMetrics fm)
519 * find the biggest sequence end position we need to show
520 * (note this is not necessarily the sequence length)
523 AlignmentI alignment = av.getAlignment();
524 for (int i = 0; i < alignment.getHeight(); i++)
526 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
530 for (int i = maxWidth; i > 0; i /= 10)
535 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
539 * Draws as many widths of a wrapped alignment as can fit in the visible
544 * available width in pixels
545 * @param canvasHeight
546 * available height in pixels
548 * the first column (0...) of the alignment to draw
550 public void drawWrappedPanel(Graphics g, int canvasWidth,
551 int canvasHeight, final int startColumn)
553 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
556 av.setWrappedWidth(wrappedWidthInResidues);
558 ViewportRanges ranges = av.getRanges();
559 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
561 // we need to call this again to make sure the startColumn +
562 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
564 calculateWrappedGeometry(canvasWidth, canvasHeight);
567 * draw one width at a time (excluding any scales or annotation shown),
568 * until we have run out of either alignment or vertical space available
570 int ypos = wrappedSpaceAboveAlignment;
571 int maxWidth = ranges.getVisibleAlignmentWidth();
573 int start = startColumn;
574 int currentWidth = 0;
575 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
578 .min(maxWidth, start + wrappedWidthInResidues - 1);
579 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
580 ypos += wrappedRepeatHeightPx;
581 start += wrappedWidthInResidues;
585 drawWrappedDecorators(g, startColumn);
589 * Calculates and saves values needed when rendering a wrapped alignment.
590 * These depend on many factors, including
592 * <li>canvas width and height</li>
593 * <li>number of visible sequences, and height of annotations if shown</li>
594 * <li>font and character width</li>
595 * <li>whether scales are shown left, right or above the alignment</li>
599 * @param canvasHeight
600 * @return the number of residue columns in each width
602 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
604 int charHeight = av.getCharHeight();
607 * vertical space in pixels between wrapped widths of alignment
608 * - one character height, or two if scale above is drawn
610 wrappedSpaceAboveAlignment = charHeight
611 * (av.getScaleAboveWrapped() ? 2 : 1);
614 * height in pixels of the wrapped widths
616 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
618 wrappedRepeatHeightPx += av.getAlignment().getHeight()
620 // add annotations panel height if shown
621 wrappedRepeatHeightPx += getAnnotationHeight();
624 * number of visible widths (the last one may be part height),
625 * ensuring a part height includes at least one sequence
627 ViewportRanges ranges = av.getRanges();
628 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
629 int remainder = canvasHeight % wrappedRepeatHeightPx;
630 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
632 wrappedVisibleWidths++;
636 * compute width in residues; this also sets East and West label widths
638 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
641 * limit visibleWidths to not exceed width of alignment
643 int xMax = ranges.getVisibleAlignmentWidth();
644 int startToEnd = xMax - ranges.getStartRes();
645 int maxWidths = startToEnd / wrappedWidthInResidues;
646 if (startToEnd % wrappedWidthInResidues > 0)
650 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
652 return wrappedWidthInResidues;
656 * Draws one width of a wrapped alignment, including sequences and
657 * annnotations, if shown, but not scales or hidden column markers
663 * @param canvasHeight
665 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
666 int endColumn, int canvasHeight)
668 ViewportRanges ranges = av.getRanges();
669 int viewportWidth = ranges.getViewportWidth();
671 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
674 * move right before drawing by the width of the scale left (if any)
675 * plus column offset from left margin (usually zero, but may be non-zero
676 * when fast painting is drawing just a few columns)
678 int charWidth = av.getCharWidth();
679 int xOffset = labelWidthWest
680 + ((startColumn - ranges.getStartRes()) % viewportWidth)
682 g.translate(xOffset, 0);
684 // When printing we have an extra clipped region,
685 // the Printable page which we need to account for here
686 Shape clip = g.getClip();
690 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
694 g.setClip(0, (int) clip.getBounds().getY(),
695 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
699 * white fill the region to be drawn (so incremental fast paint doesn't
700 * scribble over an existing image)
702 g.setColor(Color.white);
703 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
704 wrappedRepeatHeightPx);
706 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
709 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
711 if (av.isShowAnnotation())
713 g.translate(0, cHeight + ypos + SEQS_ANNOTATION_GAP);
714 if (annotations == null)
716 annotations = new AnnotationPanel(av);
719 annotations.renderer.drawComponent(annotations, av, g, -1,
720 startColumn, endx + 1);
721 g.translate(0, -cHeight - ypos - SEQS_ANNOTATION_GAP);
724 g.translate(-xOffset, 0);
728 * Draws scales left, right and above (if shown), and any hidden column
729 * markers, on all widths of the wrapped alignment
734 protected void drawWrappedDecorators(Graphics g, final int startColumn)
736 int charWidth = av.getCharWidth();
738 g.setFont(av.getFont());
739 g.setColor(Color.black);
741 int ypos = wrappedSpaceAboveAlignment;
742 ViewportRanges ranges = av.getRanges();
743 int viewportWidth = ranges.getViewportWidth();
744 int maxWidth = ranges.getVisibleAlignmentWidth();
746 int startCol = startColumn;
748 while (widthsDrawn < wrappedVisibleWidths)
750 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
752 if (av.getScaleLeftWrapped())
754 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
757 if (av.getScaleRightWrapped())
759 int x = labelWidthWest + viewportWidth * charWidth;
761 drawVerticalScale(g, startCol, endColumn, ypos, false);
766 * white fill region of scale above and hidden column markers
767 * (to support incremental fast paint of image)
769 g.translate(labelWidthWest, 0);
770 g.setColor(Color.white);
771 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
772 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
773 g.setColor(Color.black);
774 g.translate(-labelWidthWest, 0);
776 g.translate(labelWidthWest, 0);
778 if (av.getScaleAboveWrapped())
780 drawNorthScale(g, startCol, endColumn, ypos);
783 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
785 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
788 g.translate(-labelWidthWest, 0);
790 ypos += wrappedRepeatHeightPx;
791 startCol += viewportWidth;
797 * Draws markers (triangles) above hidden column positions between startColumn
805 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
806 int startColumn, int endColumn)
808 int charHeight = av.getCharHeight();
809 int charWidth = av.getCharWidth();
811 g.setColor(Color.blue);
813 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
815 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
819 res = it.next() - startColumn;
821 if (res < 0 || res > endColumn - startColumn + 1)
827 * draw a downward-pointing triangle at the hidden columns location
828 * (before the following visible column)
830 int xMiddle = res * charWidth;
831 int[] xPoints = new int[] { xMiddle - charHeight / 4,
832 xMiddle + charHeight / 4, xMiddle };
833 int yTop = ypos - (charHeight / 2);
834 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
835 g.fillPolygon(xPoints, yPoints, 3);
840 * Draw a selection group over a wrapped alignment
842 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
844 int canvasHeight, int startRes)
846 int charHeight = av.getCharHeight();
847 int charWidth = av.getCharWidth();
849 // height gap above each panel
850 int hgap = charHeight;
851 if (av.getScaleAboveWrapped())
856 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
858 int cHeight = av.getAlignment().getHeight() * charHeight;
860 int startx = startRes;
862 int ypos = hgap; // vertical offset
863 int maxwidth = av.getAlignment().getWidth();
865 if (av.hasHiddenColumns())
867 maxwidth = av.getAlignment().getHiddenColumns()
868 .absoluteToVisibleColumn(maxwidth);
871 // chop the wrapped alignment extent up into panel-sized blocks and treat
872 // each block as if it were a block from an unwrapped alignment
873 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
874 BasicStroke.JOIN_ROUND, 3f, new float[]
876 g.setColor(Color.RED);
877 while ((ypos <= canvasHeight) && (startx < maxwidth))
879 // set end value to be start + width, or maxwidth, whichever is smaller
880 endx = startx + cWidth - 1;
887 g.translate(labelWidthWest, 0);
889 drawUnwrappedSelection(g, group, startx, endx, 0,
890 av.getAlignment().getHeight() - 1,
893 g.translate(-labelWidthWest, 0);
895 // update vertical offset
896 ypos += cHeight + getAnnotationHeight() + hgap;
898 // update horizontal offset
901 g.setStroke(new BasicStroke());
904 int getAnnotationHeight()
906 if (!av.isShowAnnotation())
911 if (annotations == null)
913 annotations = new AnnotationPanel(av);
916 return annotations.adjustPanelHeight();
920 * Draws the visible region of the alignment on the graphics context. If there
921 * are hidden column markers in the visible region, then each sub-region
922 * between the markers is drawn separately, followed by the hidden column
926 * the graphics context, positioned at the first residue to be drawn
928 * offset of the first column to draw (0..)
930 * offset of the last column to draw (0..)
932 * offset of the first sequence to draw (0..)
934 * offset of the last sequence to draw (0..)
936 * vertical offset at which to draw (for wrapped alignments)
938 public void drawPanel(Graphics g1, final int startRes, final int endRes,
939 final int startSeq, final int endSeq, final int yOffset)
941 int charHeight = av.getCharHeight();
942 int charWidth = av.getCharWidth();
944 if (!av.hasHiddenColumns())
946 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
954 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
955 VisibleContigsIterator regions = hidden
956 .getVisContigsIterator(startRes, endRes + 1, true);
958 while (regions.hasNext())
960 int[] region = regions.next();
961 blockEnd = region[1];
962 blockStart = region[0];
965 * draw up to just before the next hidden region, or the end of
966 * the visible region, whichever comes first
968 g1.translate(screenY * charWidth, 0);
970 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
973 * draw the downline of the hidden column marker (ScalePanel draws the
974 * triangle on top) if we reached it
976 if (av.getShowHiddenMarkers()
977 && (regions.hasNext() || regions.endsAtHidden()))
979 g1.setColor(Color.blue);
981 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
982 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
983 (endSeq - startSeq + 1) * charHeight + yOffset);
986 g1.translate(-screenY * charWidth, 0);
987 screenY += blockEnd - blockStart + 1;
994 * Draws a region of the visible alignment
998 * offset of the first column in the visible region (0..)
1000 * offset of the last column in the visible region (0..)
1002 * offset of the first sequence in the visible region (0..)
1004 * offset of the last sequence in the visible region (0..)
1006 * vertical offset at which to draw (for wrapped alignments)
1008 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1009 int endSeq, int offset)
1011 int charHeight = av.getCharHeight();
1012 int charWidth = av.getCharWidth();
1014 g.setFont(av.getFont());
1015 seqRdr.prepare(g, av.isRenderGaps());
1019 // / First draw the sequences
1020 // ///////////////////////////
1021 for (int i = startSeq; i <= endSeq; i++)
1023 nextSeq = av.getAlignment().getSequenceAt(i);
1024 if (nextSeq == null)
1026 // occasionally, a race condition occurs such that the alignment row is
1030 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1031 startRes, endRes, offset + ((i - startSeq) * charHeight));
1033 if (av.isShowSequenceFeatures())
1035 fr.drawSequence(g, nextSeq, startRes, endRes,
1036 offset + ((i - startSeq) * charHeight), false);
1040 * highlight search Results once sequence has been drawn
1042 if (av.hasSearchResults())
1044 SearchResultsI searchResults = av.getSearchResults();
1045 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1047 if (visibleResults != null)
1049 for (int r = 0; r < visibleResults.length; r += 2)
1051 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1052 visibleResults[r + 1],
1053 (visibleResults[r] - startRes) * charWidth,
1054 offset + ((i - startSeq) * charHeight));
1060 if (av.getSelectionGroup() != null
1061 || av.getAlignment().getGroups().size() > 0)
1063 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1069 * Draws the outlines of any groups defined on the alignment (excluding the
1070 * current selection group, if any)
1079 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1080 int startSeq, int endSeq, int offset)
1082 Graphics2D g = (Graphics2D) g1;
1084 SequenceGroup group = null;
1085 int groupIndex = -1;
1087 if (av.getAlignment().getGroups().size() > 0)
1089 group = av.getAlignment().getGroups().get(0);
1097 g.setColor(group.getOutlineColour());
1099 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1104 if (groupIndex >= av.getAlignment().getGroups().size())
1109 group = av.getAlignment().getGroups().get(groupIndex);
1111 } while (groupIndex < av.getAlignment().getGroups().size());
1118 * Draws the outline of the current selection group (if any)
1126 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1127 int startSeq, int endSeq)
1129 SequenceGroup group = av.getSelectionGroup();
1135 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1136 BasicStroke.JOIN_ROUND, 3f, new float[]
1138 g.setColor(Color.RED);
1139 if (!av.getWrapAlignment())
1141 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1146 drawWrappedSelection(g, group, getWidth(), getHeight(),
1147 av.getRanges().getStartRes());
1149 g.setStroke(new BasicStroke());
1153 * Draw the cursor as a separate image and overlay
1156 * start residue of area to draw cursor in
1158 * end residue of area to draw cursor in
1160 * start sequence of area to draw cursor in
1162 * end sequence of are to draw cursor in
1163 * @return a transparent image of the same size as the sequence canvas, with
1164 * the cursor drawn on it, if any
1166 private void drawCursor(Graphics g, int startRes, int endRes,
1170 // convert the cursorY into a position on the visible alignment
1171 int cursor_ypos = cursorY;
1173 // don't do work unless we have to
1174 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1178 int startx = startRes;
1181 // convert the cursorX into a position on the visible alignment
1182 int cursor_xpos = av.getAlignment().getHiddenColumns()
1183 .absoluteToVisibleColumn(cursorX);
1185 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1188 if (av.getWrapAlignment())
1190 // work out the correct offsets for the cursor
1191 int charHeight = av.getCharHeight();
1192 int charWidth = av.getCharWidth();
1193 int canvasWidth = getWidth();
1194 int canvasHeight = getHeight();
1196 // height gap above each panel
1197 int hgap = charHeight;
1198 if (av.getScaleAboveWrapped())
1203 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1205 int cHeight = av.getAlignment().getHeight() * charHeight;
1207 endx = startx + cWidth - 1;
1208 int ypos = hgap; // vertical offset
1210 // iterate down the wrapped panels
1211 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1213 // update vertical offset
1214 ypos += cHeight + getAnnotationHeight() + hgap;
1216 // update horizontal offset
1218 endx = startx + cWidth - 1;
1221 xoffset = labelWidthWest;
1224 // now check if cursor is within range for x values
1225 if (cursor_xpos >= startx && cursor_xpos <= endx)
1227 // get the character the cursor is drawn at
1228 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1229 char s = seq.getCharAt(cursorX);
1231 seqRdr.drawCursor(g, s,
1232 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1233 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1241 * Draw a selection group over an unwrapped alignment
1244 * graphics object to draw with
1248 * start residue of area to draw
1250 * end residue of area to draw
1252 * start sequence of area to draw
1254 * end sequence of area to draw
1256 * vertical offset (used when called from wrapped alignment code)
1258 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1259 int startRes, int endRes, int startSeq, int endSeq, int offset)
1261 int charWidth = av.getCharWidth();
1263 if (!av.hasHiddenColumns())
1265 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1270 // package into blocks of visible columns
1275 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1276 VisibleContigsIterator regions = hidden
1277 .getVisContigsIterator(startRes, endRes + 1, true);
1278 while (regions.hasNext())
1280 int[] region = regions.next();
1281 blockEnd = region[1];
1282 blockStart = region[0];
1284 g.translate(screenY * charWidth, 0);
1285 drawPartialGroupOutline(g, group,
1286 blockStart, blockEnd, startSeq, endSeq, offset);
1288 g.translate(-screenY * charWidth, 0);
1289 screenY += blockEnd - blockStart + 1;
1295 * Draws part of a selection group outline
1303 * @param verticalOffset
1305 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1306 int startRes, int endRes, int startSeq, int endSeq,
1309 int charHeight = av.getCharHeight();
1310 int charWidth = av.getCharWidth();
1311 int visWidth = (endRes - startRes + 1) * charWidth;
1315 boolean inGroup = false;
1320 List<SequenceI> seqs = group.getSequences(null);
1322 // position of start residue of group relative to startRes, in pixels
1323 int sx = (group.getStartRes() - startRes) * charWidth;
1325 // width of group in pixels
1326 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1329 if (!(sx + xwidth < 0 || sx > visWidth))
1331 for (i = startSeq; i <= endSeq; i++)
1333 sy = verticalOffset + (i - startSeq) * charHeight;
1335 if ((sx <= (endRes - startRes) * charWidth)
1336 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1339 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1341 bottom = sy + charHeight;
1346 if (((top == -1) && (i == 0)) || !seqs
1347 .contains(av.getAlignment().getSequenceAt(i - 1)))
1358 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1359 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1361 // reset top and bottom
1369 sy = verticalOffset + ((i - startSeq) * charHeight);
1370 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1371 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1377 * Draw horizontal selection group boundaries at top and bottom positions
1380 * graphics object to draw on
1386 * visWidth maximum available width
1388 * position to draw top of group at
1390 * position to draw bottom of group at
1392 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1393 int visWidth, int top, int bottom)
1403 // don't let width extend beyond current block, or group extent
1405 if (startx + width >= visWidth)
1407 width = visWidth - startx;
1412 g.drawLine(startx, top, startx + width, top);
1417 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1422 * Draw vertical lines at sx and sx+xwidth providing they lie within
1426 * graphics object to draw on
1432 * visWidth maximum available width
1438 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1441 // if start position is visible, draw vertical line to left of
1443 if (sx >= 0 && sx < visWidth)
1445 g.drawLine(sx, oldY, sx, sy);
1448 // if end position is visible, draw vertical line to right of
1450 if (sx + xwidth < visWidth)
1452 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1457 * Highlights search results in the visible region by rendering as white text
1458 * on a black background. Any previous highlighting is removed. Answers true
1459 * if any highlight was left on the visible alignment (so status bar should be
1460 * set to match), else false.
1462 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1463 * alignment had to be scrolled to show the highlighted region, then it should
1464 * be fully redrawn, otherwise a fast paint can be performed. This argument
1465 * could be removed if fast paint of scrolled wrapped alignment is coded in
1466 * future (JAL-2609).
1469 * @param noFastPaint
1472 public boolean highlightSearchResults(SearchResultsI results,
1473 boolean noFastPaint)
1479 boolean wrapped = av.getWrapAlignment();
1482 fastPaint = !noFastPaint;
1483 fastpainting = fastPaint;
1486 * to avoid redrawing the whole visible region, we instead
1487 * redraw just the minimal regions to remove previous highlights
1490 SearchResultsI previous = av.getSearchResults();
1491 av.setSearchResults(results);
1492 boolean redrawn = false;
1493 boolean drawn = false;
1496 redrawn = drawMappedPositionsWrapped(previous);
1497 drawn = drawMappedPositionsWrapped(results);
1502 redrawn = drawMappedPositions(previous);
1503 drawn = drawMappedPositions(results);
1508 * if highlights were either removed or added, repaint
1516 * return true only if highlights were added
1522 fastpainting = false;
1527 * Redraws the minimal rectangle in the visible region (if any) that includes
1528 * mapped positions of the given search results. Whether or not positions are
1529 * highlighted depends on the SearchResults set on the Viewport. This allows
1530 * this method to be called to either clear or set highlighting. Answers true
1531 * if any positions were drawn (in which case a repaint is still required),
1537 protected boolean drawMappedPositions(SearchResultsI results)
1539 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1545 * calculate the minimal rectangle to redraw that
1546 * includes both new and existing search results
1548 int firstSeq = Integer.MAX_VALUE;
1550 int firstCol = Integer.MAX_VALUE;
1552 boolean matchFound = false;
1554 ViewportRanges ranges = av.getRanges();
1555 int firstVisibleColumn = ranges.getStartRes();
1556 int lastVisibleColumn = ranges.getEndRes();
1557 AlignmentI alignment = av.getAlignment();
1558 if (av.hasHiddenColumns())
1560 firstVisibleColumn = alignment.getHiddenColumns()
1561 .visibleToAbsoluteColumn(firstVisibleColumn);
1562 lastVisibleColumn = alignment.getHiddenColumns()
1563 .visibleToAbsoluteColumn(lastVisibleColumn);
1566 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1567 .getEndSeq(); seqNo++)
1569 SequenceI seq = alignment.getSequenceAt(seqNo);
1571 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1573 if (visibleResults != null)
1575 for (int i = 0; i < visibleResults.length - 1; i += 2)
1577 int firstMatchedColumn = visibleResults[i];
1578 int lastMatchedColumn = visibleResults[i + 1];
1579 if (firstMatchedColumn <= lastVisibleColumn
1580 && lastMatchedColumn >= firstVisibleColumn)
1583 * found a search results match in the visible region -
1584 * remember the first and last sequence matched, and the first
1585 * and last visible columns in the matched positions
1588 firstSeq = Math.min(firstSeq, seqNo);
1589 lastSeq = Math.max(lastSeq, seqNo);
1590 firstMatchedColumn = Math.max(firstMatchedColumn,
1591 firstVisibleColumn);
1592 lastMatchedColumn = Math.min(lastMatchedColumn,
1594 firstCol = Math.min(firstCol, firstMatchedColumn);
1595 lastCol = Math.max(lastCol, lastMatchedColumn);
1603 if (av.hasHiddenColumns())
1605 firstCol = alignment.getHiddenColumns()
1606 .absoluteToVisibleColumn(firstCol);
1607 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1609 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1610 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1611 gg.translate(transX, transY);
1612 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1613 gg.translate(-transX, -transY);
1620 public void propertyChange(PropertyChangeEvent evt)
1622 String eventName = evt.getPropertyName();
1624 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1630 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1638 if (eventName.equals(ViewportRanges.STARTRES)
1639 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1641 // Make sure we're not trying to draw a panel
1642 // larger than the visible window
1643 if (eventName.equals(ViewportRanges.STARTRES))
1645 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1649 scrollX = ((int[]) evt.getNewValue())[0]
1650 - ((int[]) evt.getOldValue())[0];
1652 ViewportRanges vpRanges = av.getRanges();
1654 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1655 if (scrollX > range)
1659 else if (scrollX < -range)
1664 // Both scrolling and resizing change viewport ranges: scrolling changes
1665 // both start and end points, but resize only changes end values.
1666 // Here we only want to fastpaint on a scroll, with resize using a normal
1667 // paint, so scroll events are identified as changes to the horizontal or
1668 // vertical start value.
1669 if (eventName.equals(ViewportRanges.STARTRES))
1671 if (av.getWrapAlignment())
1673 fastPaintWrapped(scrollX);
1677 fastPaint(scrollX, 0);
1680 else if (eventName.equals(ViewportRanges.STARTSEQ))
1683 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1685 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1687 if (av.getWrapAlignment())
1689 fastPaintWrapped(scrollX);
1693 fastPaint(scrollX, 0);
1696 else if (eventName.equals(ViewportRanges.STARTSEQ))
1699 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1701 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1703 if (av.getWrapAlignment())
1705 fastPaintWrapped(scrollX);
1711 * Does a minimal update of the image for a scroll movement. This method
1712 * handles scroll movements of up to one width of the wrapped alignment (one
1713 * click in the vertical scrollbar). Larger movements (for example after a
1714 * scroll to highlight a mapped position) trigger a full redraw instead.
1717 * number of positions scrolled (right if positive, left if negative)
1719 protected void fastPaintWrapped(int scrollX)
1721 ViewportRanges ranges = av.getRanges();
1723 if (Math.abs(scrollX) > ranges.getViewportWidth())
1726 * shift of more than one view width is
1727 * overcomplicated to handle in this method
1734 if (fastpainting || gg == null)
1740 fastpainting = true;
1744 calculateWrappedGeometry(getWidth(), getHeight());
1747 * relocate the regions of the alignment that are still visible
1749 shiftWrappedAlignment(-scrollX);
1752 * add new columns (sequence, annotation)
1753 * - at top left if scrollX < 0
1754 * - at right of last two widths if scrollX > 0
1758 int startRes = ranges.getStartRes();
1759 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1760 - scrollX - 1, getHeight());
1764 fastPaintWrappedAddRight(scrollX);
1768 * draw all scales (if shown) and hidden column markers
1770 drawWrappedDecorators(gg, ranges.getStartRes());
1775 fastpainting = false;
1780 * Draws the specified number of columns at the 'end' (bottom right) of a
1781 * wrapped alignment view, including sequences and annotations if shown, but
1782 * not scales. Also draws the same number of columns at the right hand end of
1783 * the second last width shown, if the last width is not full height (so
1784 * cannot simply be copied from the graphics image).
1788 protected void fastPaintWrappedAddRight(int columns)
1795 ViewportRanges ranges = av.getRanges();
1796 int viewportWidth = ranges.getViewportWidth();
1797 int charWidth = av.getCharWidth();
1800 * draw full height alignment in the second last row, last columns, if the
1801 * last row was not full height
1803 int visibleWidths = wrappedVisibleWidths;
1804 int canvasHeight = getHeight();
1805 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1807 if (lastWidthPartHeight)
1809 int widthsAbove = Math.max(0, visibleWidths - 2);
1810 int ypos = wrappedRepeatHeightPx * widthsAbove
1811 + wrappedSpaceAboveAlignment;
1812 int endRes = ranges.getEndRes();
1813 endRes += widthsAbove * viewportWidth;
1814 int startRes = endRes - columns;
1815 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1819 * white fill first to erase annotations
1821 gg.translate(xOffset, 0);
1822 gg.setColor(Color.white);
1823 gg.fillRect(labelWidthWest, ypos,
1824 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1825 gg.translate(-xOffset, 0);
1827 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1831 * draw newly visible columns in last wrapped width (none if we
1832 * have reached the end of the alignment)
1833 * y-offset for drawing last width is height of widths above,
1836 int widthsAbove = visibleWidths - 1;
1837 int ypos = wrappedRepeatHeightPx * widthsAbove
1838 + wrappedSpaceAboveAlignment;
1839 int endRes = ranges.getEndRes();
1840 endRes += widthsAbove * viewportWidth;
1841 int startRes = endRes - columns + 1;
1844 * white fill first to erase annotations
1846 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1848 gg.translate(xOffset, 0);
1849 gg.setColor(Color.white);
1850 int width = viewportWidth * charWidth - xOffset;
1851 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1852 gg.translate(-xOffset, 0);
1854 gg.setFont(av.getFont());
1855 gg.setColor(Color.black);
1857 if (startRes < ranges.getVisibleAlignmentWidth())
1859 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1863 * and finally, white fill any space below the visible alignment
1865 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1866 if (heightBelow > 0)
1868 gg.setColor(Color.white);
1869 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1874 * Shifts the visible alignment by the specified number of columns - left if
1875 * negative, right if positive. Copies and moves sequences and annotations (if
1876 * shown). Scales, hidden column markers and any newly visible columns must be
1881 protected void shiftWrappedAlignment(int positions)
1887 int charWidth = av.getCharWidth();
1889 int canvasHeight = getHeight();
1890 ViewportRanges ranges = av.getRanges();
1891 int viewportWidth = ranges.getViewportWidth();
1892 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1894 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1895 int xMax = ranges.getVisibleAlignmentWidth();
1900 * shift right (after scroll left)
1901 * for each wrapped width (starting with the last), copy (width-positions)
1902 * columns from the left margin to the right margin, and copy positions
1903 * columns from the right margin of the row above (if any) to the
1904 * left margin of the current row
1908 * get y-offset of last wrapped width, first row of sequences
1910 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1911 y += wrappedSpaceAboveAlignment;
1912 int copyFromLeftStart = labelWidthWest;
1913 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1917 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1918 positions * charWidth, 0);
1921 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1922 positions * charWidth, heightToCopy, -widthToCopy,
1923 wrappedRepeatHeightPx);
1926 y -= wrappedRepeatHeightPx;
1932 * shift left (after scroll right)
1933 * for each wrapped width (starting with the first), copy (width-positions)
1934 * columns from the right margin to the left margin, and copy positions
1935 * columns from the left margin of the row below (if any) to the
1936 * right margin of the current row
1938 int xpos = av.getRanges().getStartRes();
1939 int y = wrappedSpaceAboveAlignment;
1940 int copyFromRightStart = labelWidthWest - positions * charWidth;
1942 while (y < canvasHeight)
1944 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1945 positions * charWidth, 0);
1946 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1947 && (xpos + viewportWidth <= xMax))
1949 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1950 * charWidth, heightToCopy, widthToCopy,
1951 -wrappedRepeatHeightPx);
1954 y += wrappedRepeatHeightPx;
1955 xpos += viewportWidth;
1962 * Redraws any positions in the search results in the visible region of a
1963 * wrapped alignment. Any highlights are drawn depending on the search results
1964 * set on the Viewport, not the <code>results</code> argument. This allows
1965 * this method to be called either to clear highlights (passing the previous
1966 * search results), or to draw new highlights.
1971 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1973 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1977 int charHeight = av.getCharHeight();
1979 boolean matchFound = false;
1981 calculateWrappedGeometry(getWidth(), getHeight());
1982 int wrappedWidth = av.getWrappedWidth();
1983 int wrappedHeight = wrappedRepeatHeightPx;
1985 ViewportRanges ranges = av.getRanges();
1986 int canvasHeight = getHeight();
1987 int repeats = canvasHeight / wrappedHeight;
1988 if (canvasHeight / wrappedHeight > 0)
1993 int firstVisibleColumn = ranges.getStartRes();
1994 int lastVisibleColumn = ranges.getStartRes() + repeats
1995 * ranges.getViewportWidth() - 1;
1997 AlignmentI alignment = av.getAlignment();
1998 if (av.hasHiddenColumns())
2000 firstVisibleColumn = alignment.getHiddenColumns()
2001 .visibleToAbsoluteColumn(firstVisibleColumn);
2002 lastVisibleColumn = alignment.getHiddenColumns()
2003 .visibleToAbsoluteColumn(lastVisibleColumn);
2006 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2008 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2009 .getEndSeq(); seqNo++)
2011 SequenceI seq = alignment.getSequenceAt(seqNo);
2013 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2015 if (visibleResults != null)
2017 for (int i = 0; i < visibleResults.length - 1; i += 2)
2019 int firstMatchedColumn = visibleResults[i];
2020 int lastMatchedColumn = visibleResults[i + 1];
2021 if (firstMatchedColumn <= lastVisibleColumn
2022 && lastMatchedColumn >= firstVisibleColumn)
2025 * found a search results match in the visible region
2027 firstMatchedColumn = Math.max(firstMatchedColumn,
2028 firstVisibleColumn);
2029 lastMatchedColumn = Math.min(lastMatchedColumn,
2033 * draw each mapped position separately (as contiguous positions may
2034 * wrap across lines)
2036 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2038 int displayColumn = mappedPos;
2039 if (av.hasHiddenColumns())
2041 displayColumn = alignment.getHiddenColumns()
2042 .absoluteToVisibleColumn(displayColumn);
2046 * transX: offset from left edge of canvas to residue position
2048 int transX = labelWidthWest
2049 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2050 * av.getCharWidth();
2053 * transY: offset from top edge of canvas to residue position
2055 int transY = gapHeight;
2056 transY += (displayColumn - ranges.getStartRes())
2057 / wrappedWidth * wrappedHeight;
2058 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2061 * yOffset is from graphics origin to start of visible region
2063 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2064 if (transY < getHeight())
2067 gg.translate(transX, transY);
2068 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2070 gg.translate(-transX, -transY);
2082 * Answers the width in pixels of the left scale labels (0 if not shown)
2086 int getLabelWidthWest()
2088 return labelWidthWest;