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
58 private static final String ZEROS = "0000000000";
60 final FeatureRenderer fr;
70 private final SequenceRenderer seqRdr;
72 private boolean fastPaint = false;
74 private boolean fastpainting = false;
76 private AnnotationPanel annotations;
79 * measurements for drawing a wrapped alignment
81 private int labelWidthEast; // label right width in pixels if shown
83 private int labelWidthWest; // label left width in pixels if shown
85 private int wrappedSpaceAboveAlignment; // gap between widths
87 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
89 private int wrappedVisibleWidths; // number of wrapped widths displayed
91 private Graphics2D gg;
94 * Creates a new SeqCanvas object.
98 public SeqCanvas(AlignmentPanel ap)
101 fr = new FeatureRenderer(ap);
102 seqRdr = new SequenceRenderer(av);
103 setLayout(new BorderLayout());
104 PaintRefresher.Register(this, av.getSequenceSetId());
105 setBackground(Color.white);
107 av.getRanges().addPropertyChangeListener(this);
110 public SequenceRenderer getSequenceRenderer()
115 public FeatureRenderer getFeatureRenderer()
121 * Draws the scale above a region of a wrapped alignment, consisting of a
122 * column number every major interval (10 columns).
125 * the graphics context to draw on, positioned at the start (bottom
126 * left) of the line on which to draw any scale marks
128 * start alignment column (0..)
130 * end alignment column (0..)
132 * y offset to draw at
134 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 int charHeight = av.getCharHeight();
137 int charWidth = av.getCharWidth();
140 * white fill the scale space (for the fastPaint case)
142 g.setColor(Color.white);
143 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
144 charHeight * 3 / 2 + 2);
145 g.setColor(Color.black);
147 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
149 for (ScaleMark mark : marks)
151 int mpos = mark.column; // (i - startx - 1)
156 String mstring = mark.text;
162 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
166 * draw a tick mark below the column number, centred on the column;
167 * height of tick mark is 4 pixels less than half a character
169 int xpos = (mpos * charWidth) + (charWidth / 2);
170 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
176 * Draw the scale to the left or right of a wrapped alignment
179 * graphics context, positioned at the start of the scale to be drawn
181 * first column of wrapped width (0.. excluding any hidden columns)
183 * last column of wrapped width (0.. excluding any hidden columns)
185 * vertical offset at which to begin the scale
187 * if true, scale is left of residues, if false, scale is right
189 void drawVerticalScale(Graphics g, final int startx, final int endx,
190 final int ypos, final boolean left)
192 int charHeight = av.getCharHeight();
193 int charWidth = av.getCharWidth();
195 int yPos = ypos + charHeight;
199 if (av.hasHiddenColumns())
201 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
202 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
203 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
205 FontMetrics fm = getFontMetrics(av.getFont());
207 for (int i = 0; i < av.getAlignment().getHeight(); i++)
209 SequenceI seq = av.getAlignment().getSequenceAt(i);
212 * find sequence position of first non-gapped position -
213 * to the right if scale left, to the left if scale right
215 int index = left ? startX : endX;
217 while (index >= startX && index <= endX)
219 if (!Comparison.isGap(seq.getCharAt(index)))
221 value = seq.findPosition(index);
235 * white fill the space for the scale
237 g.setColor(Color.white);
238 int y = (yPos + (i * charHeight)) - (charHeight / 5);
239 // fillRect origin is top left of rectangle
240 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
246 * draw scale value, right justified within its width less half a
247 * character width padding on the right
249 int labelSpace = left ? labelWidthWest : labelWidthEast;
250 labelSpace -= charWidth / 2; // leave space to the right
251 String valueAsString = String.valueOf(value);
252 int labelLength = fm.stringWidth(valueAsString);
253 int xOffset = labelSpace - labelLength;
254 g.setColor(Color.black);
255 g.drawString(valueAsString, xOffset, y);
261 * Does a fast paint of an alignment in response to a scroll. Most of the
262 * visible region is simply copied and shifted, and then any newly visible
263 * columns or rows are drawn. The scroll may be horizontal or vertical, but
264 * not both at once. Scrolling may be the result of
266 * <li>dragging a scroll bar</li>
267 * <li>clicking in the scroll bar</li>
268 * <li>scrolling by trackpad, middle mouse button, or other device</li>
269 * <li>by moving the box in the Overview window</li>
270 * <li>programmatically to make a highlighted position visible</li>
274 * columns to shift right (positive) or left (negative)
276 * rows to shift down (positive) or up (negative)
278 public void fastPaint(int horizontal, int vertical)
280 if (fastpainting || gg == null || img == null)
289 int charHeight = av.getCharHeight();
290 int charWidth = av.getCharWidth();
292 ViewportRanges ranges = av.getRanges();
293 int startRes = ranges.getStartRes();
294 int endRes = ranges.getEndRes();
295 int startSeq = ranges.getStartSeq();
296 int endSeq = ranges.getEndSeq();
300 gg.copyArea(horizontal * charWidth, vertical * charHeight,
301 img.getWidth(), img.getHeight(), -horizontal * charWidth,
302 -vertical * charHeight);
304 if (horizontal > 0) // scrollbar pulled right, image to the left
306 transX = (endRes - startRes - horizontal) * charWidth;
307 startRes = endRes - horizontal;
309 else if (horizontal < 0)
311 endRes = startRes - horizontal;
314 if (vertical > 0) // scroll down
316 startSeq = endSeq - vertical;
318 if (startSeq < ranges.getStartSeq())
319 { // ie scrolling too fast, more than a page at a time
320 startSeq = ranges.getStartSeq();
324 transY = img.getHeight() - ((vertical + 1) * charHeight);
327 else if (vertical < 0)
329 endSeq = startSeq - vertical;
331 if (endSeq > ranges.getEndSeq())
333 endSeq = ranges.getEndSeq();
337 gg.translate(transX, transY);
338 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
339 gg.translate(-transX, -transY);
341 // Call repaint on alignment panel so that repaints from other alignment
342 // panel components can be aggregated. Otherwise performance of the
343 // overview window and others may be adversely affected.
344 av.getAlignPanel().repaint();
347 fastpainting = false;
352 public void paintComponent(Graphics g)
354 super.paintComponent(g);
356 int charHeight = av.getCharHeight();
357 int charWidth = av.getCharWidth();
359 ViewportRanges ranges = av.getRanges();
361 int width = getWidth();
362 int height = getHeight();
364 width -= (width % charWidth);
365 height -= (height % charHeight);
367 if ((img != null) && (fastPaint
368 || (getVisibleRect().width != g.getClipBounds().width)
369 || (getVisibleRect().height != g.getClipBounds().height)))
371 g.drawImage(img, 0, 0, this);
373 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
374 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
378 else if (width > 0 && height > 0)
381 * img is a cached version of the last view we drew, if any
382 * if we have no img or the size has changed, make a new one
384 if (img == null || width != img.getWidth()
385 || height != img.getHeight())
387 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
388 gg = (Graphics2D) img.getGraphics();
389 gg.setFont(av.getFont());
394 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
395 RenderingHints.VALUE_ANTIALIAS_ON);
398 gg.setColor(Color.white);
399 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
401 if (av.getWrapAlignment())
403 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
407 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
408 ranges.getStartSeq(), ranges.getEndSeq(), 0);
411 drawSelectionGroup(gg, ranges.getStartRes(),
412 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
414 g.drawImage(img, 0, 0, this);
419 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
420 ranges.getStartSeq(), ranges.getEndSeq());
425 * Draw an alignment panel for printing
428 * Graphics object to draw with
430 * start residue of print area
432 * end residue of print area
434 * start sequence of print area
436 * end sequence of print area
438 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
439 int startSeq, int endSeq)
441 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
443 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
448 * Draw a wrapped alignment panel for printing
451 * Graphics object to draw with
453 * width of drawing area
454 * @param canvasHeight
455 * height of drawing area
457 * start residue of print area
459 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
460 int canvasHeight, int startRes)
462 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
464 SequenceGroup group = av.getSelectionGroup();
467 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
473 * Returns the visible width of the canvas in residues, after allowing for
474 * East or West scales (if shown)
477 * the width in pixels (possibly including scales)
481 public int getWrappedCanvasWidth(int canvasWidth)
483 int charWidth = av.getCharWidth();
485 FontMetrics fm = getFontMetrics(av.getFont());
489 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
491 labelWidth = getLabelWidth(fm);
494 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
496 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
498 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
502 * Returns a pixel width sufficient to show the largest sequence coordinate
503 * (end position) in the alignment, calculated as the FontMetrics width of
504 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
505 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
506 * half a character width space on either side.
511 protected int getLabelWidth(FontMetrics fm)
514 * find the biggest sequence end position we need to show
515 * (note this is not necessarily the sequence length)
518 AlignmentI alignment = av.getAlignment();
519 for (int i = 0; i < alignment.getHeight(); i++)
521 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
525 for (int i = maxWidth; i > 0; i /= 10)
530 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
534 * Draws as many widths of a wrapped alignment as can fit in the visible
539 * available width in pixels
540 * @param canvasHeight
541 * available height in pixels
543 * the first column (0...) of the alignment to draw
545 public void drawWrappedPanel(Graphics g, int canvasWidth,
546 int canvasHeight, final int startColumn)
548 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
551 av.setWrappedWidth(wrappedWidthInResidues);
553 ViewportRanges ranges = av.getRanges();
554 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
556 // we need to call this again to make sure the startColumn +
557 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
559 calculateWrappedGeometry(canvasWidth, canvasHeight);
562 * draw one width at a time (excluding any scales or annotation shown),
563 * until we have run out of either alignment or vertical space available
565 int ypos = wrappedSpaceAboveAlignment;
566 int maxWidth = ranges.getVisibleAlignmentWidth();
568 int start = startColumn;
569 int currentWidth = 0;
570 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
573 .min(maxWidth, start + wrappedWidthInResidues - 1);
574 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
575 ypos += wrappedRepeatHeightPx;
576 start += wrappedWidthInResidues;
580 drawWrappedDecorators(g, startColumn);
584 * Calculates and saves values needed when rendering a wrapped alignment.
585 * These depend on many factors, including
587 * <li>canvas width and height</li>
588 * <li>number of visible sequences, and height of annotations if shown</li>
589 * <li>font and character width</li>
590 * <li>whether scales are shown left, right or above the alignment</li>
594 * @param canvasHeight
595 * @return the number of residue columns in each width
597 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
599 int charHeight = av.getCharHeight();
602 * vertical space in pixels between wrapped widths of alignment
603 * - one character height, or two if scale above is drawn
605 wrappedSpaceAboveAlignment = charHeight
606 * (av.getScaleAboveWrapped() ? 2 : 1);
609 * height in pixels of the wrapped widths
611 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
613 wrappedRepeatHeightPx += av.getAlignment().getHeight()
615 // add annotations panel height if shown
616 wrappedRepeatHeightPx += getAnnotationHeight();
619 * number of visible widths (the last one may be part height),
620 * ensuring a part height includes at least one sequence
622 ViewportRanges ranges = av.getRanges();
623 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
624 int remainder = canvasHeight % wrappedRepeatHeightPx;
625 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
627 wrappedVisibleWidths++;
631 * compute width in residues; this also sets East and West label widths
633 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
636 * limit visibleWidths to not exceed width of alignment
638 int xMax = ranges.getVisibleAlignmentWidth();
639 int startToEnd = xMax - ranges.getStartRes();
640 int maxWidths = startToEnd / wrappedWidthInResidues;
641 if (startToEnd % wrappedWidthInResidues > 0)
645 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
647 return wrappedWidthInResidues;
651 * Draws one width of a wrapped alignment, including sequences and
652 * annnotations, if shown, but not scales or hidden column markers
658 * @param canvasHeight
660 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
661 int endColumn, int canvasHeight)
663 ViewportRanges ranges = av.getRanges();
664 int viewportWidth = ranges.getViewportWidth();
666 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
669 * move right before drawing by the width of the scale left (if any)
670 * plus column offset from left margin (usually zero, but may be non-zero
671 * when fast painting is drawing just a few columns)
673 int charWidth = av.getCharWidth();
674 int xOffset = labelWidthWest
675 + ((startColumn - ranges.getStartRes()) % viewportWidth)
677 g.translate(xOffset, 0);
679 // When printing we have an extra clipped region,
680 // the Printable page which we need to account for here
681 Shape clip = g.getClip();
685 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
689 g.setClip(0, (int) clip.getBounds().getY(),
690 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
694 * white fill the region to be drawn (so incremental fast paint doesn't
695 * scribble over an existing image)
697 g.setColor(Color.white);
698 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
699 wrappedRepeatHeightPx);
701 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
704 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
706 if (av.isShowAnnotation())
708 g.translate(0, cHeight + ypos + 3);
709 if (annotations == null)
711 annotations = new AnnotationPanel(av);
714 annotations.renderer.drawComponent(annotations, av, g, -1,
715 startColumn, endx + 1);
716 g.translate(0, -cHeight - ypos - 3);
719 g.translate(-xOffset, 0);
723 * Draws scales left, right and above (if shown), and any hidden column
724 * markers, on all widths of the wrapped alignment
729 protected void drawWrappedDecorators(Graphics g, final int startColumn)
731 int charWidth = av.getCharWidth();
733 g.setFont(av.getFont());
734 g.setColor(Color.black);
736 int ypos = wrappedSpaceAboveAlignment;
737 ViewportRanges ranges = av.getRanges();
738 int viewportWidth = ranges.getViewportWidth();
739 int maxWidth = ranges.getVisibleAlignmentWidth();
741 int startCol = startColumn;
743 while (widthsDrawn < wrappedVisibleWidths)
745 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
747 if (av.getScaleLeftWrapped())
749 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
752 if (av.getScaleRightWrapped())
754 int x = labelWidthWest + viewportWidth * charWidth;
756 drawVerticalScale(g, startCol, endColumn, ypos, false);
761 * white fill region of scale above and hidden column markers
762 * (to support incremental fast paint of image)
764 g.translate(labelWidthWest, 0);
765 g.setColor(Color.white);
766 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
767 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
768 g.setColor(Color.black);
769 g.translate(-labelWidthWest, 0);
771 g.translate(labelWidthWest, 0);
773 if (av.getScaleAboveWrapped())
775 drawNorthScale(g, startCol, endColumn, ypos);
778 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
780 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
783 g.translate(-labelWidthWest, 0);
785 ypos += wrappedRepeatHeightPx;
786 startCol += viewportWidth;
792 * Draws markers (triangles) above hidden column positions between startColumn
800 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
801 int startColumn, int endColumn)
803 int charHeight = av.getCharHeight();
804 int charWidth = av.getCharWidth();
806 g.setColor(Color.blue);
808 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
810 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
814 res = it.next() - startColumn;
816 if (res < 0 || res > endColumn - startColumn + 1)
822 * draw a downward-pointing triangle at the hidden columns location
823 * (before the following visible column)
825 int xMiddle = res * charWidth;
826 int[] xPoints = new int[] { xMiddle - charHeight / 4,
827 xMiddle + charHeight / 4, xMiddle };
828 int yTop = ypos - (charHeight / 2);
829 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
830 g.fillPolygon(xPoints, yPoints, 3);
835 * Draw a selection group over a wrapped alignment
837 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
839 int canvasHeight, int startRes)
841 int charHeight = av.getCharHeight();
842 int charWidth = av.getCharWidth();
844 // height gap above each panel
845 int hgap = charHeight;
846 if (av.getScaleAboveWrapped())
851 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
853 int cHeight = av.getAlignment().getHeight() * charHeight;
855 int startx = startRes;
857 int ypos = hgap; // vertical offset
858 int maxwidth = av.getAlignment().getWidth();
860 if (av.hasHiddenColumns())
862 maxwidth = av.getAlignment().getHiddenColumns()
863 .absoluteToVisibleColumn(maxwidth);
866 // chop the wrapped alignment extent up into panel-sized blocks and treat
867 // each block as if it were a block from an unwrapped alignment
868 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
869 BasicStroke.JOIN_ROUND, 3f, new float[]
871 g.setColor(Color.RED);
872 while ((ypos <= canvasHeight) && (startx < maxwidth))
874 // set end value to be start + width, or maxwidth, whichever is smaller
875 endx = startx + cWidth - 1;
882 g.translate(labelWidthWest, 0);
884 drawUnwrappedSelection(g, group, startx, endx, 0,
885 av.getAlignment().getHeight() - 1,
888 g.translate(-labelWidthWest, 0);
890 // update vertical offset
891 ypos += cHeight + getAnnotationHeight() + hgap;
893 // update horizontal offset
896 g.setStroke(new BasicStroke());
899 int getAnnotationHeight()
901 if (!av.isShowAnnotation())
906 if (annotations == null)
908 annotations = new AnnotationPanel(av);
911 return annotations.adjustPanelHeight();
915 * Draws the visible region of the alignment on the graphics context. If there
916 * are hidden column markers in the visible region, then each sub-region
917 * between the markers is drawn separately, followed by the hidden column
921 * the graphics context, positioned at the first residue to be drawn
923 * offset of the first column to draw (0..)
925 * offset of the last column to draw (0..)
927 * offset of the first sequence to draw (0..)
929 * offset of the last sequence to draw (0..)
931 * vertical offset at which to draw (for wrapped alignments)
933 public void drawPanel(Graphics g1, final int startRes, final int endRes,
934 final int startSeq, final int endSeq, final int yOffset)
936 int charHeight = av.getCharHeight();
937 int charWidth = av.getCharWidth();
939 if (!av.hasHiddenColumns())
941 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
949 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
950 VisibleContigsIterator regions = hidden
951 .getVisContigsIterator(startRes, endRes + 1, true);
953 while (regions.hasNext())
955 int[] region = regions.next();
956 blockEnd = region[1];
957 blockStart = region[0];
960 * draw up to just before the next hidden region, or the end of
961 * the visible region, whichever comes first
963 g1.translate(screenY * charWidth, 0);
965 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
968 * draw the downline of the hidden column marker (ScalePanel draws the
969 * triangle on top) if we reached it
971 if (av.getShowHiddenMarkers()
972 && (regions.hasNext() || regions.endsAtHidden()))
974 g1.setColor(Color.blue);
976 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
977 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
978 (endSeq - startSeq + 1) * charHeight + yOffset);
981 g1.translate(-screenY * charWidth, 0);
982 screenY += blockEnd - blockStart + 1;
989 * Draws a region of the visible alignment
993 * offset of the first column in the visible region (0..)
995 * offset of the last column in the visible region (0..)
997 * offset of the first sequence in the visible region (0..)
999 * offset of the last sequence in the visible region (0..)
1001 * vertical offset at which to draw (for wrapped alignments)
1003 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1004 int endSeq, int offset)
1006 int charHeight = av.getCharHeight();
1007 int charWidth = av.getCharWidth();
1009 g.setFont(av.getFont());
1010 seqRdr.setGraphics(g);
1014 // / First draw the sequences
1015 // ///////////////////////////
1016 boolean drawGaps = av.isRenderGaps();
1017 for (int i = startSeq; i <= endSeq; i++)
1019 nextSeq = av.getAlignment().getSequenceAt(i);
1020 if (nextSeq == null)
1022 // occasionally, a race condition occurs such that the alignment row is
1026 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1027 startRes, endRes, offset + ((i - startSeq) * charHeight), drawGaps);
1029 if (av.isShowSequenceFeatures())
1031 fr.drawSequence(g, nextSeq, startRes, endRes,
1032 offset + ((i - startSeq) * charHeight), false);
1036 * highlight search Results once sequence has been drawn
1038 if (av.hasSearchResults())
1040 SearchResultsI searchResults = av.getSearchResults();
1041 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1043 if (visibleResults != null)
1045 for (int r = 0; r < visibleResults.length; r += 2)
1047 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1048 visibleResults[r + 1],
1049 (visibleResults[r] - startRes) * charWidth,
1050 offset + ((i - startSeq) * charHeight));
1056 if (av.getSelectionGroup() != null
1057 || av.getAlignment().getGroups().size() > 0)
1059 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1065 * Draws the outlines of any groups defined on the alignment (excluding the
1066 * current selection group, if any)
1075 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1076 int startSeq, int endSeq, int offset)
1078 Graphics2D g = (Graphics2D) g1;
1080 SequenceGroup group = null;
1081 int groupIndex = -1;
1083 if (av.getAlignment().getGroups().size() > 0)
1085 group = av.getAlignment().getGroups().get(0);
1093 g.setColor(group.getOutlineColour());
1095 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1100 if (groupIndex >= av.getAlignment().getGroups().size())
1105 group = av.getAlignment().getGroups().get(groupIndex);
1107 } while (groupIndex < av.getAlignment().getGroups().size());
1114 * Draws the outline of the current selection group (if any)
1122 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1123 int startSeq, int endSeq)
1125 SequenceGroup group = av.getSelectionGroup();
1131 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1132 BasicStroke.JOIN_ROUND, 3f, new float[]
1134 g.setColor(Color.RED);
1135 if (!av.getWrapAlignment())
1137 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1142 drawWrappedSelection(g, group, getWidth(), getHeight(),
1143 av.getRanges().getStartRes());
1145 g.setStroke(new BasicStroke());
1149 * Draw the cursor as a separate image and overlay
1152 * start residue of area to draw cursor in
1154 * end residue of area to draw cursor in
1156 * start sequence of area to draw cursor in
1158 * end sequence of are to draw cursor in
1159 * @return a transparent image of the same size as the sequence canvas, with
1160 * the cursor drawn on it, if any
1162 private void drawCursor(Graphics g, int startRes, int endRes,
1166 // convert the cursorY into a position on the visible alignment
1167 int cursor_ypos = cursorY;
1169 // don't do work unless we have to
1170 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1174 int startx = startRes;
1177 // convert the cursorX into a position on the visible alignment
1178 int cursor_xpos = av.getAlignment().getHiddenColumns()
1179 .absoluteToVisibleColumn(cursorX);
1181 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1184 if (av.getWrapAlignment())
1186 // work out the correct offsets for the cursor
1187 int charHeight = av.getCharHeight();
1188 int charWidth = av.getCharWidth();
1189 int canvasWidth = getWidth();
1190 int canvasHeight = getHeight();
1192 // height gap above each panel
1193 int hgap = charHeight;
1194 if (av.getScaleAboveWrapped())
1199 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1201 int cHeight = av.getAlignment().getHeight() * charHeight;
1203 endx = startx + cWidth - 1;
1204 int ypos = hgap; // vertical offset
1206 // iterate down the wrapped panels
1207 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1209 // update vertical offset
1210 ypos += cHeight + getAnnotationHeight() + hgap;
1212 // update horizontal offset
1214 endx = startx + cWidth - 1;
1217 xoffset = labelWidthWest;
1220 // now check if cursor is within range for x values
1221 if (cursor_xpos >= startx && cursor_xpos <= endx)
1223 // get the character the cursor is drawn at
1224 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1225 char s = seq.getCharAt(cursorX);
1227 seqRdr.drawCursor(g, s,
1228 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1229 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1237 * Draw a selection group over an unwrapped alignment
1240 * graphics object to draw with
1244 * start residue of area to draw
1246 * end residue of area to draw
1248 * start sequence of area to draw
1250 * end sequence of area to draw
1252 * vertical offset (used when called from wrapped alignment code)
1254 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1255 int startRes, int endRes, int startSeq, int endSeq, int offset)
1257 int charWidth = av.getCharWidth();
1259 if (!av.hasHiddenColumns())
1261 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1266 // package into blocks of visible columns
1271 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1272 VisibleContigsIterator regions = hidden
1273 .getVisContigsIterator(startRes, endRes + 1, true);
1274 while (regions.hasNext())
1276 int[] region = regions.next();
1277 blockEnd = region[1];
1278 blockStart = region[0];
1280 g.translate(screenY * charWidth, 0);
1281 drawPartialGroupOutline(g, group,
1282 blockStart, blockEnd, startSeq, endSeq, offset);
1284 g.translate(-screenY * charWidth, 0);
1285 screenY += blockEnd - blockStart + 1;
1291 * Draws part of a selection group outline
1299 * @param verticalOffset
1301 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1302 int startRes, int endRes, int startSeq, int endSeq,
1305 int charHeight = av.getCharHeight();
1306 int charWidth = av.getCharWidth();
1307 int visWidth = (endRes - startRes + 1) * charWidth;
1311 boolean inGroup = false;
1316 List<SequenceI> seqs = group.getSequences(null);
1318 // position of start residue of group relative to startRes, in pixels
1319 int sx = (group.getStartRes() - startRes) * charWidth;
1321 // width of group in pixels
1322 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1325 if (!(sx + xwidth < 0 || sx > visWidth))
1327 for (i = startSeq; i <= endSeq; i++)
1329 sy = verticalOffset + (i - startSeq) * charHeight;
1331 if ((sx <= (endRes - startRes) * charWidth)
1332 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1335 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1337 bottom = sy + charHeight;
1342 if (((top == -1) && (i == 0)) || !seqs
1343 .contains(av.getAlignment().getSequenceAt(i - 1)))
1354 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1355 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1357 // reset top and bottom
1365 sy = verticalOffset + ((i - startSeq) * charHeight);
1366 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1367 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1373 * Draw horizontal selection group boundaries at top and bottom positions
1376 * graphics object to draw on
1382 * visWidth maximum available width
1384 * position to draw top of group at
1386 * position to draw bottom of group at
1388 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1389 int visWidth, int top, int bottom)
1399 // don't let width extend beyond current block, or group extent
1401 if (startx + width >= visWidth)
1403 width = visWidth - startx;
1408 g.drawLine(startx, top, startx + width, top);
1413 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1418 * Draw vertical lines at sx and sx+xwidth providing they lie within
1422 * graphics object to draw on
1428 * visWidth maximum available width
1434 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1437 // if start position is visible, draw vertical line to left of
1439 if (sx >= 0 && sx < visWidth)
1441 g.drawLine(sx, oldY, sx, sy);
1444 // if end position is visible, draw vertical line to right of
1446 if (sx + xwidth < visWidth)
1448 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1453 * Highlights search results in the visible region by rendering as white text
1454 * on a black background. Any previous highlighting is removed. Answers true
1455 * if any highlight was left on the visible alignment (so status bar should be
1456 * set to match), else false.
1458 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1459 * alignment had to be scrolled to show the highlighted region, then it should
1460 * be fully redrawn, otherwise a fast paint can be performed. This argument
1461 * could be removed if fast paint of scrolled wrapped alignment is coded in
1462 * future (JAL-2609).
1465 * @param noFastPaint
1468 public boolean highlightSearchResults(SearchResultsI results,
1469 boolean noFastPaint)
1475 boolean wrapped = av.getWrapAlignment();
1478 fastPaint = !noFastPaint;
1479 fastpainting = fastPaint;
1482 * to avoid redrawing the whole visible region, we instead
1483 * redraw just the minimal regions to remove previous highlights
1486 SearchResultsI previous = av.getSearchResults();
1487 av.setSearchResults(results);
1488 boolean redrawn = false;
1489 boolean drawn = false;
1492 redrawn = drawMappedPositionsWrapped(previous);
1493 drawn = drawMappedPositionsWrapped(results);
1498 redrawn = drawMappedPositions(previous);
1499 drawn = drawMappedPositions(results);
1504 * if highlights were either removed or added, repaint
1512 * return true only if highlights were added
1518 fastpainting = false;
1523 * Redraws the minimal rectangle in the visible region (if any) that includes
1524 * mapped positions of the given search results. Whether or not positions are
1525 * highlighted depends on the SearchResults set on the Viewport. This allows
1526 * this method to be called to either clear or set highlighting. Answers true
1527 * if any positions were drawn (in which case a repaint is still required),
1533 protected boolean drawMappedPositions(SearchResultsI results)
1535 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1541 * calculate the minimal rectangle to redraw that
1542 * includes both new and existing search results
1544 int firstSeq = Integer.MAX_VALUE;
1546 int firstCol = Integer.MAX_VALUE;
1548 boolean matchFound = false;
1550 ViewportRanges ranges = av.getRanges();
1551 int firstVisibleColumn = ranges.getStartRes();
1552 int lastVisibleColumn = ranges.getEndRes();
1553 AlignmentI alignment = av.getAlignment();
1554 if (av.hasHiddenColumns())
1556 firstVisibleColumn = alignment.getHiddenColumns()
1557 .visibleToAbsoluteColumn(firstVisibleColumn);
1558 lastVisibleColumn = alignment.getHiddenColumns()
1559 .visibleToAbsoluteColumn(lastVisibleColumn);
1562 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1563 .getEndSeq(); seqNo++)
1565 SequenceI seq = alignment.getSequenceAt(seqNo);
1567 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1569 if (visibleResults != null)
1571 for (int i = 0; i < visibleResults.length - 1; i += 2)
1573 int firstMatchedColumn = visibleResults[i];
1574 int lastMatchedColumn = visibleResults[i + 1];
1575 if (firstMatchedColumn <= lastVisibleColumn
1576 && lastMatchedColumn >= firstVisibleColumn)
1579 * found a search results match in the visible region -
1580 * remember the first and last sequence matched, and the first
1581 * and last visible columns in the matched positions
1584 firstSeq = Math.min(firstSeq, seqNo);
1585 lastSeq = Math.max(lastSeq, seqNo);
1586 firstMatchedColumn = Math.max(firstMatchedColumn,
1587 firstVisibleColumn);
1588 lastMatchedColumn = Math.min(lastMatchedColumn,
1590 firstCol = Math.min(firstCol, firstMatchedColumn);
1591 lastCol = Math.max(lastCol, lastMatchedColumn);
1599 if (av.hasHiddenColumns())
1601 firstCol = alignment.getHiddenColumns()
1602 .absoluteToVisibleColumn(firstCol);
1603 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1605 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1606 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1607 gg.translate(transX, transY);
1608 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1609 gg.translate(-transX, -transY);
1616 public void propertyChange(PropertyChangeEvent evt)
1618 String eventName = evt.getPropertyName();
1620 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1626 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1634 if (eventName.equals(ViewportRanges.STARTRES)
1635 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1637 // Make sure we're not trying to draw a panel
1638 // larger than the visible window
1639 if (eventName.equals(ViewportRanges.STARTRES))
1641 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1645 scrollX = ((int[]) evt.getNewValue())[0]
1646 - ((int[]) evt.getOldValue())[0];
1648 ViewportRanges vpRanges = av.getRanges();
1650 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1651 if (scrollX > range)
1655 else if (scrollX < -range)
1660 // Both scrolling and resizing change viewport ranges: scrolling changes
1661 // both start and end points, but resize only changes end values.
1662 // Here we only want to fastpaint on a scroll, with resize using a normal
1663 // paint, so scroll events are identified as changes to the horizontal or
1664 // vertical start value.
1665 if (eventName.equals(ViewportRanges.STARTRES))
1667 if (av.getWrapAlignment())
1669 fastPaintWrapped(scrollX);
1673 fastPaint(scrollX, 0);
1676 else if (eventName.equals(ViewportRanges.STARTSEQ))
1679 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1681 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1683 if (av.getWrapAlignment())
1685 fastPaintWrapped(scrollX);
1689 fastPaint(scrollX, 0);
1692 else if (eventName.equals(ViewportRanges.STARTSEQ))
1695 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1697 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1699 if (av.getWrapAlignment())
1701 fastPaintWrapped(scrollX);
1707 * Does a minimal update of the image for a scroll movement. This method
1708 * handles scroll movements of up to one width of the wrapped alignment (one
1709 * click in the vertical scrollbar). Larger movements (for example after a
1710 * scroll to highlight a mapped position) trigger a full redraw instead.
1713 * number of positions scrolled (right if positive, left if negative)
1715 protected void fastPaintWrapped(int scrollX)
1717 ViewportRanges ranges = av.getRanges();
1719 if (Math.abs(scrollX) > ranges.getViewportWidth())
1722 * shift of more than one view width is
1723 * overcomplicated to handle in this method
1730 if (fastpainting || gg == null)
1736 fastpainting = true;
1740 calculateWrappedGeometry(getWidth(), getHeight());
1743 * relocate the regions of the alignment that are still visible
1745 shiftWrappedAlignment(-scrollX);
1748 * add new columns (sequence, annotation)
1749 * - at top left if scrollX < 0
1750 * - at right of last two widths if scrollX > 0
1754 int startRes = ranges.getStartRes();
1755 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1756 - scrollX - 1, getHeight());
1760 fastPaintWrappedAddRight(scrollX);
1764 * draw all scales (if shown) and hidden column markers
1766 drawWrappedDecorators(gg, ranges.getStartRes());
1771 fastpainting = false;
1776 * Draws the specified number of columns at the 'end' (bottom right) of a
1777 * wrapped alignment view, including sequences and annotations if shown, but
1778 * not scales. Also draws the same number of columns at the right hand end of
1779 * the second last width shown, if the last width is not full height (so
1780 * cannot simply be copied from the graphics image).
1784 protected void fastPaintWrappedAddRight(int columns)
1791 ViewportRanges ranges = av.getRanges();
1792 int viewportWidth = ranges.getViewportWidth();
1793 int charWidth = av.getCharWidth();
1796 * draw full height alignment in the second last row, last columns, if the
1797 * last row was not full height
1799 int visibleWidths = wrappedVisibleWidths;
1800 int canvasHeight = getHeight();
1801 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1803 if (lastWidthPartHeight)
1805 int widthsAbove = Math.max(0, visibleWidths - 2);
1806 int ypos = wrappedRepeatHeightPx * widthsAbove
1807 + wrappedSpaceAboveAlignment;
1808 int endRes = ranges.getEndRes();
1809 endRes += widthsAbove * viewportWidth;
1810 int startRes = endRes - columns;
1811 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1815 * white fill first to erase annotations
1817 gg.translate(xOffset, 0);
1818 gg.setColor(Color.white);
1819 gg.fillRect(labelWidthWest, ypos,
1820 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1821 gg.translate(-xOffset, 0);
1823 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1827 * draw newly visible columns in last wrapped width (none if we
1828 * have reached the end of the alignment)
1829 * y-offset for drawing last width is height of widths above,
1832 int widthsAbove = visibleWidths - 1;
1833 int ypos = wrappedRepeatHeightPx * widthsAbove
1834 + wrappedSpaceAboveAlignment;
1835 int endRes = ranges.getEndRes();
1836 endRes += widthsAbove * viewportWidth;
1837 int startRes = endRes - columns + 1;
1840 * white fill first to erase annotations
1842 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1844 gg.translate(xOffset, 0);
1845 gg.setColor(Color.white);
1846 int width = viewportWidth * charWidth - xOffset;
1847 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1848 gg.translate(-xOffset, 0);
1850 gg.setFont(av.getFont());
1851 gg.setColor(Color.black);
1853 if (startRes < ranges.getVisibleAlignmentWidth())
1855 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1859 * and finally, white fill any space below the visible alignment
1861 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1862 if (heightBelow > 0)
1864 gg.setColor(Color.white);
1865 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1870 * Shifts the visible alignment by the specified number of columns - left if
1871 * negative, right if positive. Copies and moves sequences and annotations (if
1872 * shown). Scales, hidden column markers and any newly visible columns must be
1877 protected void shiftWrappedAlignment(int positions)
1883 int charWidth = av.getCharWidth();
1885 int canvasHeight = getHeight();
1886 ViewportRanges ranges = av.getRanges();
1887 int viewportWidth = ranges.getViewportWidth();
1888 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1890 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1891 int xMax = ranges.getVisibleAlignmentWidth();
1896 * shift right (after scroll left)
1897 * for each wrapped width (starting with the last), copy (width-positions)
1898 * columns from the left margin to the right margin, and copy positions
1899 * columns from the right margin of the row above (if any) to the
1900 * left margin of the current row
1904 * get y-offset of last wrapped width, first row of sequences
1906 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1907 y += wrappedSpaceAboveAlignment;
1908 int copyFromLeftStart = labelWidthWest;
1909 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1913 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1914 positions * charWidth, 0);
1917 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1918 positions * charWidth, heightToCopy, -widthToCopy,
1919 wrappedRepeatHeightPx);
1922 y -= wrappedRepeatHeightPx;
1928 * shift left (after scroll right)
1929 * for each wrapped width (starting with the first), copy (width-positions)
1930 * columns from the right margin to the left margin, and copy positions
1931 * columns from the left margin of the row below (if any) to the
1932 * right margin of the current row
1934 int xpos = av.getRanges().getStartRes();
1935 int y = wrappedSpaceAboveAlignment;
1936 int copyFromRightStart = labelWidthWest - positions * charWidth;
1938 while (y < canvasHeight)
1940 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1941 positions * charWidth, 0);
1942 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1943 && (xpos + viewportWidth <= xMax))
1945 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1946 * charWidth, heightToCopy, widthToCopy,
1947 -wrappedRepeatHeightPx);
1950 y += wrappedRepeatHeightPx;
1951 xpos += viewportWidth;
1958 * Redraws any positions in the search results in the visible region of a
1959 * wrapped alignment. Any highlights are drawn depending on the search results
1960 * set on the Viewport, not the <code>results</code> argument. This allows
1961 * this method to be called either to clear highlights (passing the previous
1962 * search results), or to draw new highlights.
1967 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1969 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1973 int charHeight = av.getCharHeight();
1975 boolean matchFound = false;
1977 calculateWrappedGeometry(getWidth(), getHeight());
1978 int wrappedWidth = av.getWrappedWidth();
1979 int wrappedHeight = wrappedRepeatHeightPx;
1981 ViewportRanges ranges = av.getRanges();
1982 int canvasHeight = getHeight();
1983 int repeats = canvasHeight / wrappedHeight;
1984 if (canvasHeight / wrappedHeight > 0)
1989 int firstVisibleColumn = ranges.getStartRes();
1990 int lastVisibleColumn = ranges.getStartRes() + repeats
1991 * ranges.getViewportWidth() - 1;
1993 AlignmentI alignment = av.getAlignment();
1994 if (av.hasHiddenColumns())
1996 firstVisibleColumn = alignment.getHiddenColumns()
1997 .visibleToAbsoluteColumn(firstVisibleColumn);
1998 lastVisibleColumn = alignment.getHiddenColumns()
1999 .visibleToAbsoluteColumn(lastVisibleColumn);
2002 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2004 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2005 .getEndSeq(); seqNo++)
2007 SequenceI seq = alignment.getSequenceAt(seqNo);
2009 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2011 if (visibleResults != null)
2013 for (int i = 0; i < visibleResults.length - 1; i += 2)
2015 int firstMatchedColumn = visibleResults[i];
2016 int lastMatchedColumn = visibleResults[i + 1];
2017 if (firstMatchedColumn <= lastVisibleColumn
2018 && lastMatchedColumn >= firstVisibleColumn)
2021 * found a search results match in the visible region
2023 firstMatchedColumn = Math.max(firstMatchedColumn,
2024 firstVisibleColumn);
2025 lastMatchedColumn = Math.min(lastMatchedColumn,
2029 * draw each mapped position separately (as contiguous positions may
2030 * wrap across lines)
2032 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2034 int displayColumn = mappedPos;
2035 if (av.hasHiddenColumns())
2037 displayColumn = alignment.getHiddenColumns()
2038 .absoluteToVisibleColumn(displayColumn);
2042 * transX: offset from left edge of canvas to residue position
2044 int transX = labelWidthWest
2045 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2046 * av.getCharWidth();
2049 * transY: offset from top edge of canvas to residue position
2051 int transY = gapHeight;
2052 transY += (displayColumn - ranges.getStartRes())
2053 / wrappedWidth * wrappedHeight;
2054 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2057 * yOffset is from graphics origin to start of visible region
2059 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2060 if (transY < getHeight())
2063 gg.translate(transX, transY);
2064 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2066 gg.translate(-transX, -transY);
2078 * Answers the width in pixels of the left scale labels (0 if not shown)
2082 int getLabelWidthWest()
2084 return labelWidthWest;