2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JComponent;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 public class SeqCanvas extends JComponent implements ViewportListenerI
59 * pixels gap between sequences and annotations when in wrapped mode
61 static final int SEQS_ANNOTATION_GAP = 3;
63 private static final String ZEROS = "0000000000";
65 final FeatureRenderer fr;
75 private final SequenceRenderer seqRdr;
77 private boolean fastPaint = false;
79 private boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 private int labelWidthEast; // label right width in pixels if shown
88 private int labelWidthWest; // label left width in pixels if shown
90 int wrappedSpaceAboveAlignment; // gap between widths
92 int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
96 private Graphics2D gg;
99 * Creates a new SeqCanvas object.
103 public SeqCanvas(AlignmentPanel ap)
106 fr = new FeatureRenderer(ap);
107 seqRdr = new SequenceRenderer(av);
108 setLayout(new BorderLayout());
109 PaintRefresher.Register(this, av.getSequenceSetId());
110 setBackground(Color.white);
112 av.getRanges().addPropertyChangeListener(this);
115 public SequenceRenderer getSequenceRenderer()
120 public FeatureRenderer getFeatureRenderer()
126 * Draws the scale above a region of a wrapped alignment, consisting of a
127 * column number every major interval (10 columns).
130 * the graphics context to draw on, positioned at the start (bottom
131 * left) of the line on which to draw any scale marks
133 * start alignment column (0..)
135 * end alignment column (0..)
137 * y offset to draw at
139 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
141 int charHeight = av.getCharHeight();
142 int charWidth = av.getCharWidth();
145 * white fill the scale space (for the fastPaint case)
147 g.setColor(Color.white);
148 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
149 charHeight * 3 / 2 + 2);
150 g.setColor(Color.black);
152 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
154 for (ScaleMark mark : marks)
156 int mpos = mark.column; // (i - startx - 1)
161 String mstring = mark.text;
167 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
171 * draw a tick mark below the column number, centred on the column;
172 * height of tick mark is 4 pixels less than half a character
174 int xpos = (mpos * charWidth) + (charWidth / 2);
175 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
181 * Draw the scale to the left or right of a wrapped alignment
184 * graphics context, positioned at the start of the scale to be drawn
186 * first column of wrapped width (0.. excluding any hidden columns)
188 * last column of wrapped width (0.. excluding any hidden columns)
190 * vertical offset at which to begin the scale
192 * if true, scale is left of residues, if false, scale is right
194 void drawVerticalScale(Graphics g, final int startx, final int endx,
195 final int ypos, final boolean left)
197 int charHeight = av.getCharHeight();
198 int charWidth = av.getCharWidth();
200 int yPos = ypos + charHeight;
204 if (av.hasHiddenColumns())
206 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
207 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
208 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
210 FontMetrics fm = getFontMetrics(av.getFont());
212 for (int i = 0; i < av.getAlignment().getHeight(); i++)
214 SequenceI seq = av.getAlignment().getSequenceAt(i);
217 * find sequence position of first non-gapped position -
218 * to the right if scale left, to the left if scale right
220 int index = left ? startX : endX;
222 while (index >= startX && index <= endX)
224 if (!Comparison.isGap(seq.getCharAt(index)))
226 value = seq.findPosition(index);
240 * white fill the space for the scale
242 g.setColor(Color.white);
243 int y = (yPos + (i * charHeight)) - (charHeight / 5);
244 // fillRect origin is top left of rectangle
245 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
251 * draw scale value, right justified within its width less half a
252 * character width padding on the right
254 int labelSpace = left ? labelWidthWest : labelWidthEast;
255 labelSpace -= charWidth / 2; // leave space to the right
256 String valueAsString = String.valueOf(value);
257 int labelLength = fm.stringWidth(valueAsString);
258 int xOffset = labelSpace - labelLength;
259 g.setColor(Color.black);
260 g.drawString(valueAsString, xOffset, y);
266 * Does a fast paint of an alignment in response to a scroll. Most of the
267 * visible region is simply copied and shifted, and then any newly visible
268 * columns or rows are drawn. The scroll may be horizontal or vertical, but
269 * not both at once. Scrolling may be the result of
271 * <li>dragging a scroll bar</li>
272 * <li>clicking in the scroll bar</li>
273 * <li>scrolling by trackpad, middle mouse button, or other device</li>
274 * <li>by moving the box in the Overview window</li>
275 * <li>programmatically to make a highlighted position visible</li>
279 * columns to shift right (positive) or left (negative)
281 * rows to shift down (positive) or up (negative)
283 public void fastPaint(int horizontal, int vertical)
285 if (fastpainting || gg == null || img == null)
294 int charHeight = av.getCharHeight();
295 int charWidth = av.getCharWidth();
297 ViewportRanges ranges = av.getRanges();
298 int startRes = ranges.getStartRes();
299 int endRes = ranges.getEndRes();
300 int startSeq = ranges.getStartSeq();
301 int endSeq = ranges.getEndSeq();
305 gg.copyArea(horizontal * charWidth, vertical * charHeight,
306 img.getWidth(), img.getHeight(), -horizontal * charWidth,
307 -vertical * charHeight);
309 if (horizontal > 0) // scrollbar pulled right, image to the left
311 transX = (endRes - startRes - horizontal) * charWidth;
312 startRes = endRes - horizontal;
314 else if (horizontal < 0)
316 endRes = startRes - horizontal;
319 if (vertical > 0) // scroll down
321 startSeq = endSeq - vertical;
323 if (startSeq < ranges.getStartSeq())
324 { // ie scrolling too fast, more than a page at a time
325 startSeq = ranges.getStartSeq();
329 transY = img.getHeight() - ((vertical + 1) * charHeight);
332 else if (vertical < 0)
334 endSeq = startSeq - vertical;
336 if (endSeq > ranges.getEndSeq())
338 endSeq = ranges.getEndSeq();
342 gg.translate(transX, transY);
343 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
344 gg.translate(-transX, -transY);
346 // Call repaint on alignment panel so that repaints from other alignment
347 // panel components can be aggregated. Otherwise performance of the
348 // overview window and others may be adversely affected.
349 av.getAlignPanel().repaint();
352 fastpainting = false;
357 public void paintComponent(Graphics g)
359 super.paintComponent(g);
361 int charHeight = av.getCharHeight();
362 int charWidth = av.getCharWidth();
364 ViewportRanges ranges = av.getRanges();
366 int width = getWidth();
367 int height = getHeight();
369 width -= (width % charWidth);
370 height -= (height % charHeight);
372 if ((img != null) && (fastPaint
373 || (getVisibleRect().width != g.getClipBounds().width)
374 || (getVisibleRect().height != g.getClipBounds().height)))
376 g.drawImage(img, 0, 0, this);
378 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
379 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
383 else if (width > 0 && height > 0)
386 * img is a cached version of the last view we drew, if any
387 * if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
392 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 drawSelectionGroup(gg, ranges.getStartRes(),
417 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
419 g.drawImage(img, 0, 0, this);
424 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
425 ranges.getStartSeq(), ranges.getEndSeq());
430 * Draw an alignment panel for printing
433 * Graphics object to draw with
435 * start residue of print area
437 * end residue of print area
439 * start sequence of print area
441 * end sequence of print area
443 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
444 int startSeq, int endSeq)
446 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
448 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
453 * Draw a wrapped alignment panel for printing
456 * Graphics object to draw with
458 * width of drawing area
459 * @param canvasHeight
460 * height of drawing area
462 * start residue of print area
464 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
465 int canvasHeight, int startRes)
467 int heightDrawn = drawWrappedPanel(g, canvasWidth, canvasHeight,
470 SequenceGroup group = av.getSelectionGroup();
473 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
478 * shift graphics (0, 0) to below the region drawn
480 g.translate(0, heightDrawn);
484 * Returns the visible width of the canvas in residues, after allowing for
485 * East or West scales (if shown)
488 * the width in pixels (possibly including scales)
492 public int getWrappedCanvasWidth(int canvasWidth)
494 int charWidth = av.getCharWidth();
496 FontMetrics fm = getFontMetrics(av.getFont());
500 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
502 labelWidth = getLabelWidth(fm);
505 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
507 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
509 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
513 * Returns a pixel width sufficient to show the largest sequence coordinate
514 * (end position) in the alignment, calculated as the FontMetrics width of
515 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
516 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
517 * half a character width space on either side.
522 protected int getLabelWidth(FontMetrics fm)
525 * find the biggest sequence end position we need to show
526 * (note this is not necessarily the sequence length)
529 AlignmentI alignment = av.getAlignment();
530 for (int i = 0; i < alignment.getHeight(); i++)
532 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
536 for (int i = maxWidth; i > 0; i /= 10)
541 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
545 * Draws as many widths of a wrapped alignment as can fit in the visible window,
546 * and returns the height drawn in pixels
550 * available width in pixels
551 * @param canvasHeight
552 * available height in pixels
554 * the first column (0...) of the alignment to draw
558 public int drawWrappedPanel(Graphics g, int canvasWidth,
559 int canvasHeight, final int startColumn)
561 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
564 av.setWrappedWidth(wrappedWidthInResidues);
566 ViewportRanges ranges = av.getRanges();
567 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
569 // we need to call this again to make sure the startColumn +
570 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
572 calculateWrappedGeometry(canvasWidth, canvasHeight);
575 * draw one width at a time (excluding any scales shown),
576 * until we have run out of either alignment or vertical space available
578 int ypos = wrappedSpaceAboveAlignment;
579 int maxWidth = ranges.getVisibleAlignmentWidth();
581 int start = startColumn;
582 int currentWidth = 0;
583 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
586 .min(maxWidth, start + wrappedWidthInResidues - 1);
587 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
588 ypos += wrappedRepeatHeightPx;
589 start += wrappedWidthInResidues;
593 drawWrappedDecorators(g, startColumn);
599 * Calculates and saves values needed when rendering a wrapped alignment.
600 * These depend on many factors, including
602 * <li>canvas width and height</li>
603 * <li>number of visible sequences, and height of annotations if shown</li>
604 * <li>font and character width</li>
605 * <li>whether scales are shown left, right or above the alignment</li>
609 * @param canvasHeight
610 * @return the number of residue columns in each width
612 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
614 int charHeight = av.getCharHeight();
617 * vertical space in pixels between wrapped widths of alignment
618 * - one character height, or two if scale above is drawn
620 wrappedSpaceAboveAlignment = charHeight
621 * (av.getScaleAboveWrapped() ? 2 : 1);
624 * compute height in pixels of the wrapped widths
625 * - start with space above plus sequences
627 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
628 wrappedRepeatHeightPx += av.getAlignment().getHeight()
632 * add annotations panel height if shown
633 * also gap between sequences and annotations
635 if (av.isShowAnnotation())
637 wrappedRepeatHeightPx += getAnnotationHeight();
638 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
642 * number of visible widths (the last one may be part height),
643 * ensuring a part height includes at least one sequence
645 ViewportRanges ranges = av.getRanges();
646 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
647 int remainder = canvasHeight % wrappedRepeatHeightPx;
648 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
650 wrappedVisibleWidths++;
654 * compute width in residues; this also sets East and West label widths
656 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
659 * limit visibleWidths to not exceed width of alignment
661 int xMax = ranges.getVisibleAlignmentWidth();
662 int startToEnd = xMax - ranges.getStartRes();
663 int maxWidths = startToEnd / wrappedWidthInResidues;
664 if (startToEnd % wrappedWidthInResidues > 0)
668 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
670 return wrappedWidthInResidues;
674 * Draws one width of a wrapped alignment, including sequences and
675 * annnotations, if shown, but not scales or hidden column markers
681 * @param canvasHeight
683 protected void drawWrappedWidth(Graphics g, final int ypos,
684 final int startColumn, final int endColumn,
685 final int canvasHeight)
687 ViewportRanges ranges = av.getRanges();
688 int viewportWidth = ranges.getViewportWidth();
690 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
693 * move right before drawing by the width of the scale left (if any)
694 * plus column offset from left margin (usually zero, but may be non-zero
695 * when fast painting is drawing just a few columns)
697 int charWidth = av.getCharWidth();
698 int xOffset = labelWidthWest
699 + ((startColumn - ranges.getStartRes()) % viewportWidth)
701 g.translate(xOffset, 0);
703 // When printing we have an extra clipped region,
704 // the Printable page which we need to account for here
705 Shape clip = g.getClip();
709 // g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
713 g.setClip(0, (int) clip.getBounds().getY(),
714 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
718 * white fill the region to be drawn (so incremental fast paint doesn't
719 * scribble over an existing image)
721 g.setColor(Color.white);
722 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
723 wrappedRepeatHeightPx);
725 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
728 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
730 if (av.isShowAnnotation())
732 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
733 g.translate(0, yShift);
734 if (annotations == null)
736 annotations = new AnnotationPanel(av);
739 annotations.renderer.drawComponent(annotations, av, g, -1,
740 startColumn, endx + 1);
741 g.translate(0, -yShift);
749 g.translate(-xOffset, 0);
753 * Draws scales left, right and above (if shown), and any hidden column
754 * markers, on all widths of the wrapped alignment
759 protected void drawWrappedDecorators(Graphics g, final int startColumn)
761 int charWidth = av.getCharWidth();
763 g.setFont(av.getFont());
764 g.setColor(Color.black);
766 int ypos = wrappedSpaceAboveAlignment;
767 ViewportRanges ranges = av.getRanges();
768 int viewportWidth = ranges.getViewportWidth();
769 int maxWidth = ranges.getVisibleAlignmentWidth();
771 int startCol = startColumn;
773 while (widthsDrawn < wrappedVisibleWidths)
775 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
777 if (av.getScaleLeftWrapped())
779 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
782 if (av.getScaleRightWrapped())
784 int x = labelWidthWest + viewportWidth * charWidth;
786 drawVerticalScale(g, startCol, endColumn, ypos, false);
791 * white fill region of scale above and hidden column markers
792 * (to support incremental fast paint of image)
794 g.translate(labelWidthWest, 0);
795 g.setColor(Color.white);
796 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
797 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
798 g.setColor(Color.black);
799 g.translate(-labelWidthWest, 0);
801 g.translate(labelWidthWest, 0);
803 if (av.getScaleAboveWrapped())
805 drawNorthScale(g, startCol, endColumn, ypos);
808 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
810 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
813 g.translate(-labelWidthWest, 0);
815 ypos += wrappedRepeatHeightPx;
816 startCol += viewportWidth;
822 * Draws markers (triangles) above hidden column positions between startColumn
830 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
831 int startColumn, int endColumn)
833 int charHeight = av.getCharHeight();
834 int charWidth = av.getCharWidth();
836 g.setColor(Color.blue);
838 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
840 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
844 res = it.next() - startColumn;
846 if (res < 0 || res > endColumn - startColumn + 1)
852 * draw a downward-pointing triangle at the hidden columns location
853 * (before the following visible column)
855 int xMiddle = res * charWidth;
856 int[] xPoints = new int[] { xMiddle - charHeight / 4,
857 xMiddle + charHeight / 4, xMiddle };
858 int yTop = ypos - (charHeight / 2);
859 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
860 g.fillPolygon(xPoints, yPoints, 3);
865 * Draw a selection group over a wrapped alignment
867 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
869 int canvasHeight, int startRes)
871 int charHeight = av.getCharHeight();
872 int charWidth = av.getCharWidth();
874 // height gap above each panel
875 int hgap = charHeight;
876 if (av.getScaleAboveWrapped())
881 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
883 int cHeight = av.getAlignment().getHeight() * charHeight;
885 int startx = startRes;
887 int ypos = hgap; // vertical offset
888 int maxwidth = av.getAlignment().getVisibleWidth();
890 // chop the wrapped alignment extent up into panel-sized blocks and treat
891 // each block as if it were a block from an unwrapped alignment
892 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
893 BasicStroke.JOIN_ROUND, 3f, new float[]
895 g.setColor(Color.RED);
896 while ((ypos <= canvasHeight) && (startx < maxwidth))
898 // set end value to be start + width, or maxwidth, whichever is smaller
899 endx = startx + cWidth - 1;
906 g.translate(labelWidthWest, 0);
908 drawUnwrappedSelection(g, group, startx, endx, 0,
909 av.getAlignment().getHeight() - 1,
912 g.translate(-labelWidthWest, 0);
914 // update vertical offset
915 ypos += cHeight + getAnnotationHeight() + hgap;
917 // update horizontal offset
920 g.setStroke(new BasicStroke());
923 int getAnnotationHeight()
925 if (!av.isShowAnnotation())
930 if (annotations == null)
932 annotations = new AnnotationPanel(av);
935 return annotations.adjustPanelHeight();
939 * Draws the visible region of the alignment on the graphics context. If there
940 * are hidden column markers in the visible region, then each sub-region
941 * between the markers is drawn separately, followed by the hidden column
945 * the graphics context, positioned at the first residue to be drawn
947 * offset of the first column to draw (0..)
949 * offset of the last column to draw (0..)
951 * offset of the first sequence to draw (0..)
953 * offset of the last sequence to draw (0..)
955 * vertical offset at which to draw (for wrapped alignments)
957 public void drawPanel(Graphics g1, final int startRes, final int endRes,
958 final int startSeq, final int endSeq, final int yOffset)
960 int charHeight = av.getCharHeight();
961 int charWidth = av.getCharWidth();
963 if (!av.hasHiddenColumns())
965 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
973 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
974 VisibleContigsIterator regions = hidden
975 .getVisContigsIterator(startRes, endRes + 1, true);
977 while (regions.hasNext())
979 int[] region = regions.next();
980 blockEnd = region[1];
981 blockStart = region[0];
984 * draw up to just before the next hidden region, or the end of
985 * the visible region, whichever comes first
987 g1.translate(screenY * charWidth, 0);
989 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
992 * draw the downline of the hidden column marker (ScalePanel draws the
993 * triangle on top) if we reached it
995 if (av.getShowHiddenMarkers()
996 && (regions.hasNext() || regions.endsAtHidden()))
998 g1.setColor(Color.blue);
1000 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1001 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1002 (endSeq - startSeq + 1) * charHeight + yOffset);
1005 g1.translate(-screenY * charWidth, 0);
1006 screenY += blockEnd - blockStart + 1;
1013 * Draws a region of the visible alignment
1017 * offset of the first column in the visible region (0..)
1019 * offset of the last column in the visible region (0..)
1021 * offset of the first sequence in the visible region (0..)
1023 * offset of the last sequence in the visible region (0..)
1025 * vertical offset at which to draw (for wrapped alignments)
1027 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1028 int endSeq, int offset)
1030 int charHeight = av.getCharHeight();
1031 int charWidth = av.getCharWidth();
1033 g.setFont(av.getFont());
1034 seqRdr.prepare(g, av.isRenderGaps());
1038 // / First draw the sequences
1039 // ///////////////////////////
1040 for (int i = startSeq; i <= endSeq; i++)
1042 nextSeq = av.getAlignment().getSequenceAt(i);
1043 if (nextSeq == null)
1045 // occasionally, a race condition occurs such that the alignment row is
1049 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1050 startRes, endRes, offset + ((i - startSeq) * charHeight));
1052 if (av.isShowSequenceFeatures())
1054 fr.drawSequence(g, nextSeq, startRes, endRes,
1055 offset + ((i - startSeq) * charHeight), false);
1059 * highlight search Results once sequence has been drawn
1061 if (av.hasSearchResults())
1063 SearchResultsI searchResults = av.getSearchResults();
1064 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1066 if (visibleResults != null)
1068 for (int r = 0; r < visibleResults.length; r += 2)
1070 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1071 visibleResults[r + 1],
1072 (visibleResults[r] - startRes) * charWidth,
1073 offset + ((i - startSeq) * charHeight));
1079 if (av.getSelectionGroup() != null
1080 || av.getAlignment().getGroups().size() > 0)
1082 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1088 * Draws the outlines of any groups defined on the alignment (excluding the
1089 * current selection group, if any)
1098 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1099 int startSeq, int endSeq, int offset)
1101 Graphics2D g = (Graphics2D) g1;
1103 SequenceGroup group = null;
1104 int groupIndex = -1;
1106 if (av.getAlignment().getGroups().size() > 0)
1108 group = av.getAlignment().getGroups().get(0);
1116 g.setColor(group.getOutlineColour());
1118 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1123 if (groupIndex >= av.getAlignment().getGroups().size())
1128 group = av.getAlignment().getGroups().get(groupIndex);
1130 } while (groupIndex < av.getAlignment().getGroups().size());
1137 * Draws the outline of the current selection group (if any)
1145 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1146 int startSeq, int endSeq)
1148 SequenceGroup group = av.getSelectionGroup();
1154 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1155 BasicStroke.JOIN_ROUND, 3f, new float[]
1157 g.setColor(Color.RED);
1158 if (!av.getWrapAlignment())
1160 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1165 drawWrappedSelection(g, group, getWidth(), getHeight(),
1166 av.getRanges().getStartRes());
1168 g.setStroke(new BasicStroke());
1172 * Draw the cursor as a separate image and overlay
1175 * start residue of area to draw cursor in
1177 * end residue of area to draw cursor in
1179 * start sequence of area to draw cursor in
1181 * end sequence of are to draw cursor in
1182 * @return a transparent image of the same size as the sequence canvas, with
1183 * the cursor drawn on it, if any
1185 private void drawCursor(Graphics g, int startRes, int endRes,
1189 // convert the cursorY into a position on the visible alignment
1190 int cursor_ypos = cursorY;
1192 // don't do work unless we have to
1193 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1197 int startx = startRes;
1200 // convert the cursorX into a position on the visible alignment
1201 int cursor_xpos = av.getAlignment().getHiddenColumns()
1202 .absoluteToVisibleColumn(cursorX);
1204 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1207 if (av.getWrapAlignment())
1209 // work out the correct offsets for the cursor
1210 int charHeight = av.getCharHeight();
1211 int charWidth = av.getCharWidth();
1212 int canvasWidth = getWidth();
1213 int canvasHeight = getHeight();
1215 // height gap above each panel
1216 int hgap = charHeight;
1217 if (av.getScaleAboveWrapped())
1222 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1224 int cHeight = av.getAlignment().getHeight() * charHeight;
1226 endx = startx + cWidth - 1;
1227 int ypos = hgap; // vertical offset
1229 // iterate down the wrapped panels
1230 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1232 // update vertical offset
1233 ypos += cHeight + getAnnotationHeight() + hgap;
1235 // update horizontal offset
1237 endx = startx + cWidth - 1;
1240 xoffset = labelWidthWest;
1243 // now check if cursor is within range for x values
1244 if (cursor_xpos >= startx && cursor_xpos <= endx)
1246 // get the character the cursor is drawn at
1247 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1248 char s = seq.getCharAt(cursorX);
1250 seqRdr.drawCursor(g, s,
1251 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1252 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1260 * Draw a selection group over an unwrapped alignment
1263 * graphics object to draw with
1267 * start residue of area to draw
1269 * end residue of area to draw
1271 * start sequence of area to draw
1273 * end sequence of area to draw
1275 * vertical offset (used when called from wrapped alignment code)
1277 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1278 int startRes, int endRes, int startSeq, int endSeq, int offset)
1280 int charWidth = av.getCharWidth();
1282 if (!av.hasHiddenColumns())
1284 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1289 // package into blocks of visible columns
1294 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1295 VisibleContigsIterator regions = hidden
1296 .getVisContigsIterator(startRes, endRes + 1, true);
1297 while (regions.hasNext())
1299 int[] region = regions.next();
1300 blockEnd = region[1];
1301 blockStart = region[0];
1303 g.translate(screenY * charWidth, 0);
1304 drawPartialGroupOutline(g, group,
1305 blockStart, blockEnd, startSeq, endSeq, offset);
1307 g.translate(-screenY * charWidth, 0);
1308 screenY += blockEnd - blockStart + 1;
1314 * Draws part of a selection group outline
1322 * @param verticalOffset
1324 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1325 int startRes, int endRes, int startSeq, int endSeq,
1328 int charHeight = av.getCharHeight();
1329 int charWidth = av.getCharWidth();
1330 int visWidth = (endRes - startRes + 1) * charWidth;
1334 boolean inGroup = false;
1339 List<SequenceI> seqs = group.getSequences(null);
1341 // position of start residue of group relative to startRes, in pixels
1342 int sx = (group.getStartRes() - startRes) * charWidth;
1344 // width of group in pixels
1345 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1348 if (!(sx + xwidth < 0 || sx > visWidth))
1350 for (i = startSeq; i <= endSeq; i++)
1352 sy = verticalOffset + (i - startSeq) * charHeight;
1354 if ((sx <= (endRes - startRes) * charWidth)
1355 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1358 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1360 bottom = sy + charHeight;
1365 if (((top == -1) && (i == 0)) || !seqs
1366 .contains(av.getAlignment().getSequenceAt(i - 1)))
1377 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1378 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1380 // reset top and bottom
1388 sy = verticalOffset + ((i - startSeq) * charHeight);
1389 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1390 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1396 * Draw horizontal selection group boundaries at top and bottom positions
1399 * graphics object to draw on
1405 * visWidth maximum available width
1407 * position to draw top of group at
1409 * position to draw bottom of group at
1411 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1412 int visWidth, int top, int bottom)
1422 // don't let width extend beyond current block, or group extent
1424 if (startx + width >= visWidth)
1426 width = visWidth - startx;
1431 g.drawLine(startx, top, startx + width, top);
1436 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1441 * Draw vertical lines at sx and sx+xwidth providing they lie within
1445 * graphics object to draw on
1451 * visWidth maximum available width
1457 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1460 // if start position is visible, draw vertical line to left of
1462 if (sx >= 0 && sx < visWidth)
1464 g.drawLine(sx, oldY, sx, sy);
1467 // if end position is visible, draw vertical line to right of
1469 if (sx + xwidth < visWidth)
1471 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1476 * Highlights search results in the visible region by rendering as white text
1477 * on a black background. Any previous highlighting is removed. Answers true
1478 * if any highlight was left on the visible alignment (so status bar should be
1479 * set to match), else false.
1481 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1482 * alignment had to be scrolled to show the highlighted region, then it should
1483 * be fully redrawn, otherwise a fast paint can be performed. This argument
1484 * could be removed if fast paint of scrolled wrapped alignment is coded in
1485 * future (JAL-2609).
1488 * @param noFastPaint
1491 public boolean highlightSearchResults(SearchResultsI results,
1492 boolean noFastPaint)
1498 boolean wrapped = av.getWrapAlignment();
1501 fastPaint = !noFastPaint;
1502 fastpainting = fastPaint;
1505 * to avoid redrawing the whole visible region, we instead
1506 * redraw just the minimal regions to remove previous highlights
1509 SearchResultsI previous = av.getSearchResults();
1510 av.setSearchResults(results);
1511 boolean redrawn = false;
1512 boolean drawn = false;
1515 redrawn = drawMappedPositionsWrapped(previous);
1516 drawn = drawMappedPositionsWrapped(results);
1521 redrawn = drawMappedPositions(previous);
1522 drawn = drawMappedPositions(results);
1527 * if highlights were either removed or added, repaint
1535 * return true only if highlights were added
1541 fastpainting = false;
1546 * Redraws the minimal rectangle in the visible region (if any) that includes
1547 * mapped positions of the given search results. Whether or not positions are
1548 * highlighted depends on the SearchResults set on the Viewport. This allows
1549 * this method to be called to either clear or set highlighting. Answers true
1550 * if any positions were drawn (in which case a repaint is still required),
1556 protected boolean drawMappedPositions(SearchResultsI results)
1558 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1564 * calculate the minimal rectangle to redraw that
1565 * includes both new and existing search results
1567 int firstSeq = Integer.MAX_VALUE;
1569 int firstCol = Integer.MAX_VALUE;
1571 boolean matchFound = false;
1573 ViewportRanges ranges = av.getRanges();
1574 int firstVisibleColumn = ranges.getStartRes();
1575 int lastVisibleColumn = ranges.getEndRes();
1576 AlignmentI alignment = av.getAlignment();
1577 if (av.hasHiddenColumns())
1579 firstVisibleColumn = alignment.getHiddenColumns()
1580 .visibleToAbsoluteColumn(firstVisibleColumn);
1581 lastVisibleColumn = alignment.getHiddenColumns()
1582 .visibleToAbsoluteColumn(lastVisibleColumn);
1585 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1586 .getEndSeq(); seqNo++)
1588 SequenceI seq = alignment.getSequenceAt(seqNo);
1590 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1592 if (visibleResults != null)
1594 for (int i = 0; i < visibleResults.length - 1; i += 2)
1596 int firstMatchedColumn = visibleResults[i];
1597 int lastMatchedColumn = visibleResults[i + 1];
1598 if (firstMatchedColumn <= lastVisibleColumn
1599 && lastMatchedColumn >= firstVisibleColumn)
1602 * found a search results match in the visible region -
1603 * remember the first and last sequence matched, and the first
1604 * and last visible columns in the matched positions
1607 firstSeq = Math.min(firstSeq, seqNo);
1608 lastSeq = Math.max(lastSeq, seqNo);
1609 firstMatchedColumn = Math.max(firstMatchedColumn,
1610 firstVisibleColumn);
1611 lastMatchedColumn = Math.min(lastMatchedColumn,
1613 firstCol = Math.min(firstCol, firstMatchedColumn);
1614 lastCol = Math.max(lastCol, lastMatchedColumn);
1622 if (av.hasHiddenColumns())
1624 firstCol = alignment.getHiddenColumns()
1625 .absoluteToVisibleColumn(firstCol);
1626 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1628 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1629 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1630 gg.translate(transX, transY);
1631 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1632 gg.translate(-transX, -transY);
1639 public void propertyChange(PropertyChangeEvent evt)
1641 String eventName = evt.getPropertyName();
1643 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1649 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1657 if (eventName.equals(ViewportRanges.STARTRES)
1658 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1660 // Make sure we're not trying to draw a panel
1661 // larger than the visible window
1662 if (eventName.equals(ViewportRanges.STARTRES))
1664 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1668 scrollX = ((int[]) evt.getNewValue())[0]
1669 - ((int[]) evt.getOldValue())[0];
1671 ViewportRanges vpRanges = av.getRanges();
1673 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1674 if (scrollX > range)
1678 else if (scrollX < -range)
1683 // Both scrolling and resizing change viewport ranges: scrolling changes
1684 // both start and end points, but resize only changes end values.
1685 // Here we only want to fastpaint on a scroll, with resize using a normal
1686 // paint, so scroll events are identified as changes to the horizontal or
1687 // vertical start value.
1688 if (eventName.equals(ViewportRanges.STARTRES))
1690 if (av.getWrapAlignment())
1692 fastPaintWrapped(scrollX);
1696 fastPaint(scrollX, 0);
1699 else if (eventName.equals(ViewportRanges.STARTSEQ))
1702 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1704 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1706 if (av.getWrapAlignment())
1708 fastPaintWrapped(scrollX);
1712 fastPaint(scrollX, 0);
1715 else if (eventName.equals(ViewportRanges.STARTSEQ))
1718 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1720 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1722 if (av.getWrapAlignment())
1724 fastPaintWrapped(scrollX);
1730 * Does a minimal update of the image for a scroll movement. This method
1731 * handles scroll movements of up to one width of the wrapped alignment (one
1732 * click in the vertical scrollbar). Larger movements (for example after a
1733 * scroll to highlight a mapped position) trigger a full redraw instead.
1736 * number of positions scrolled (right if positive, left if negative)
1738 protected void fastPaintWrapped(int scrollX)
1740 ViewportRanges ranges = av.getRanges();
1742 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1745 * shift of one view width or more is
1746 * overcomplicated to handle in this method
1753 if (fastpainting || gg == null)
1759 fastpainting = true;
1763 calculateWrappedGeometry(getWidth(), getHeight());
1766 * relocate the regions of the alignment that are still visible
1768 shiftWrappedAlignment(-scrollX);
1771 * add new columns (sequence, annotation)
1772 * - at top left if scrollX < 0
1773 * - at right of last two widths if scrollX > 0
1777 int startRes = ranges.getStartRes();
1778 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1779 - scrollX - 1, getHeight());
1783 fastPaintWrappedAddRight(scrollX);
1787 * draw all scales (if shown) and hidden column markers
1789 drawWrappedDecorators(gg, ranges.getStartRes());
1794 fastpainting = false;
1799 * Draws the specified number of columns at the 'end' (bottom right) of a
1800 * wrapped alignment view, including sequences and annotations if shown, but
1801 * not scales. Also draws the same number of columns at the right hand end of
1802 * the second last width shown, if the last width is not full height (so
1803 * cannot simply be copied from the graphics image).
1807 protected void fastPaintWrappedAddRight(int columns)
1814 ViewportRanges ranges = av.getRanges();
1815 int viewportWidth = ranges.getViewportWidth();
1816 int charWidth = av.getCharWidth();
1819 * draw full height alignment in the second last row, last columns, if the
1820 * last row was not full height
1822 int visibleWidths = wrappedVisibleWidths;
1823 int canvasHeight = getHeight();
1824 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1826 if (lastWidthPartHeight)
1828 int widthsAbove = Math.max(0, visibleWidths - 2);
1829 int ypos = wrappedRepeatHeightPx * widthsAbove
1830 + wrappedSpaceAboveAlignment;
1831 int endRes = ranges.getEndRes();
1832 endRes += widthsAbove * viewportWidth;
1833 int startRes = endRes - columns;
1834 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1838 * white fill first to erase annotations
1840 gg.translate(xOffset, 0);
1841 gg.setColor(Color.white);
1842 gg.fillRect(labelWidthWest, ypos,
1843 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1844 gg.translate(-xOffset, 0);
1846 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1850 * draw newly visible columns in last wrapped width (none if we
1851 * have reached the end of the alignment)
1852 * y-offset for drawing last width is height of widths above,
1855 int widthsAbove = visibleWidths - 1;
1856 int ypos = wrappedRepeatHeightPx * widthsAbove
1857 + wrappedSpaceAboveAlignment;
1858 int endRes = ranges.getEndRes();
1859 endRes += widthsAbove * viewportWidth;
1860 int startRes = endRes - columns + 1;
1863 * white fill first to erase annotations
1865 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1867 gg.translate(xOffset, 0);
1868 gg.setColor(Color.white);
1869 int width = viewportWidth * charWidth - xOffset;
1870 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1871 gg.translate(-xOffset, 0);
1873 gg.setFont(av.getFont());
1874 gg.setColor(Color.black);
1876 if (startRes < ranges.getVisibleAlignmentWidth())
1878 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1882 * and finally, white fill any space below the visible alignment
1884 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1885 if (heightBelow > 0)
1887 gg.setColor(Color.white);
1888 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1893 * Shifts the visible alignment by the specified number of columns - left if
1894 * negative, right if positive. Copies and moves sequences and annotations (if
1895 * shown). Scales, hidden column markers and any newly visible columns must be
1900 protected void shiftWrappedAlignment(int positions)
1906 int charWidth = av.getCharWidth();
1908 int canvasHeight = getHeight();
1909 ViewportRanges ranges = av.getRanges();
1910 int viewportWidth = ranges.getViewportWidth();
1911 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1913 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1914 int xMax = ranges.getVisibleAlignmentWidth();
1919 * shift right (after scroll left)
1920 * for each wrapped width (starting with the last), copy (width-positions)
1921 * columns from the left margin to the right margin, and copy positions
1922 * columns from the right margin of the row above (if any) to the
1923 * left margin of the current row
1927 * get y-offset of last wrapped width, first row of sequences
1929 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1930 y += wrappedSpaceAboveAlignment;
1931 int copyFromLeftStart = labelWidthWest;
1932 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1937 * shift 'widthToCopy' residues by 'positions' places to the right
1939 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1940 positions * charWidth, 0);
1944 * copy 'positions' residue from the row above (right hand end)
1945 * to this row's left hand end
1947 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1948 positions * charWidth, heightToCopy, -widthToCopy,
1949 wrappedRepeatHeightPx);
1952 y -= wrappedRepeatHeightPx;
1958 * shift left (after scroll right)
1959 * for each wrapped width (starting with the first), copy (width-positions)
1960 * columns from the right margin to the left margin, and copy positions
1961 * columns from the left margin of the row below (if any) to the
1962 * right margin of the current row
1964 int xpos = av.getRanges().getStartRes();
1965 int y = wrappedSpaceAboveAlignment;
1966 int copyFromRightStart = labelWidthWest - positions * charWidth;
1968 while (y < canvasHeight)
1970 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1971 positions * charWidth, 0);
1972 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1973 && (xpos + viewportWidth <= xMax))
1975 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1976 * charWidth, heightToCopy, widthToCopy,
1977 -wrappedRepeatHeightPx);
1980 y += wrappedRepeatHeightPx;
1981 xpos += viewportWidth;
1988 * Redraws any positions in the search results in the visible region of a
1989 * wrapped alignment. Any highlights are drawn depending on the search results
1990 * set on the Viewport, not the <code>results</code> argument. This allows
1991 * this method to be called either to clear highlights (passing the previous
1992 * search results), or to draw new highlights.
1997 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1999 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
2003 int charHeight = av.getCharHeight();
2005 boolean matchFound = false;
2007 calculateWrappedGeometry(getWidth(), getHeight());
2008 int wrappedWidth = av.getWrappedWidth();
2009 int wrappedHeight = wrappedRepeatHeightPx;
2011 ViewportRanges ranges = av.getRanges();
2012 int canvasHeight = getHeight();
2013 int repeats = canvasHeight / wrappedHeight;
2014 if (canvasHeight / wrappedHeight > 0)
2019 int firstVisibleColumn = ranges.getStartRes();
2020 int lastVisibleColumn = ranges.getStartRes() + repeats
2021 * ranges.getViewportWidth() - 1;
2023 AlignmentI alignment = av.getAlignment();
2024 if (av.hasHiddenColumns())
2026 firstVisibleColumn = alignment.getHiddenColumns()
2027 .visibleToAbsoluteColumn(firstVisibleColumn);
2028 lastVisibleColumn = alignment.getHiddenColumns()
2029 .visibleToAbsoluteColumn(lastVisibleColumn);
2032 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2034 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2035 .getEndSeq(); seqNo++)
2037 SequenceI seq = alignment.getSequenceAt(seqNo);
2039 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2041 if (visibleResults != null)
2043 for (int i = 0; i < visibleResults.length - 1; i += 2)
2045 int firstMatchedColumn = visibleResults[i];
2046 int lastMatchedColumn = visibleResults[i + 1];
2047 if (firstMatchedColumn <= lastVisibleColumn
2048 && lastMatchedColumn >= firstVisibleColumn)
2051 * found a search results match in the visible region
2053 firstMatchedColumn = Math.max(firstMatchedColumn,
2054 firstVisibleColumn);
2055 lastMatchedColumn = Math.min(lastMatchedColumn,
2059 * draw each mapped position separately (as contiguous positions may
2060 * wrap across lines)
2062 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2064 int displayColumn = mappedPos;
2065 if (av.hasHiddenColumns())
2067 displayColumn = alignment.getHiddenColumns()
2068 .absoluteToVisibleColumn(displayColumn);
2072 * transX: offset from left edge of canvas to residue position
2074 int transX = labelWidthWest
2075 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2076 * av.getCharWidth();
2079 * transY: offset from top edge of canvas to residue position
2081 int transY = gapHeight;
2082 transY += (displayColumn - ranges.getStartRes())
2083 / wrappedWidth * wrappedHeight;
2084 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2087 * yOffset is from graphics origin to start of visible region
2089 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2090 if (transY < getHeight())
2093 gg.translate(transX, transY);
2094 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2096 gg.translate(-transX, -transY);
2108 * Answers the width in pixels of the left scale labels (0 if not shown)
2112 int getLabelWidthWest()
2114 return labelWidthWest;