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.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.util.Comparison;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.AlphaComposite; // ok
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.List;
47 import javax.swing.JComponent;
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static String ZEROS = "0000000000";
59 final FeatureRenderer fr;
61 final SequenceRenderer seqRdr; //ok
73 boolean fastPaint = false;
75 boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 int labelWidthWest; // label left width in pixels if shown
88 private int labelWidthEast; // label right width in pixels if shown
90 private int wrappedSpaceAboveAlignment; // gap between widths
92 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 * Creates a new SeqCanvas object.
102 public SeqCanvas(AlignmentPanel ap)
105 fr = new FeatureRenderer(ap);
106 seqRdr = new SequenceRenderer(av); //ok
107 setLayout(new BorderLayout());
108 PaintRefresher.Register(this, av.getSequenceSetId());
109 setBackground(Color.white);
111 av.getRanges().addPropertyChangeListener(this);
114 public SequenceRenderer getSequenceRenderer()
119 public FeatureRenderer getFeatureRenderer()
125 * Draws the scale above a region of a wrapped alignment, consisting of a
126 * column number every major interval (10 columns).
129 * the graphics context to draw on, positioned at the start (bottom
130 * left) of the line on which to draw any scale marks
132 * start alignment column (0..)
134 * end alignment column (0..)
136 * y offset to draw at
138 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
140 int charHeight = av.getCharHeight();
141 int charWidth = av.getCharWidth();
144 * white fill the scale space (for the fastPaint case)
146 g.setColor(Color.white);
147 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
148 charHeight * 3 / 2 + 2);
149 g.setColor(Color.black);
151 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
153 for (ScaleMark mark : marks)
155 int mpos = mark.column; // (i - startx - 1)
160 String mstring = mark.text;
166 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
170 * draw a tick mark below the column number, centred on the column;
171 * height of tick mark is 4 pixels less than half a character
173 int xpos = (mpos * charWidth) + (charWidth / 2);
174 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
180 * Draw the scale to the left or right of a wrapped alignment
183 * graphics context, positioned at the start of the scale to be drawn
185 * first column of wrapped width (0.. excluding any hidden columns)
187 * last column of wrapped width (0.. excluding any hidden columns)
189 * vertical offset at which to begin the scale
191 * if true, scale is left of residues, if false, scale is right
193 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
196 int charHeight = av.getCharHeight();
197 int charWidth = av.getCharWidth();
201 if (av.hasHiddenColumns())
203 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
204 startx = hiddenColumns.adjustForHiddenColumns(startx);
205 endx = hiddenColumns.adjustForHiddenColumns(endx);
207 FontMetrics fm = getFontMetrics(av.getFont());
209 for (int i = 0; i < av.getAlignment().getHeight(); i++)
211 SequenceI seq = av.getAlignment().getSequenceAt(i);
214 * find sequence position of first non-gapped position -
215 * to the right if scale left, to the left if scale right
217 int index = left ? startx : endx;
219 while (index >= startx && index <= endx)
221 if (!Comparison.isGap(seq.getCharAt(index)))
223 value = seq.findPosition(index);
237 * white fill the space for the scale
239 g.setColor(Color.white);
240 int y = (ypos + (i * charHeight)) - (charHeight / 5);
241 // fillRect origin is top left of rectangle
242 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
249 * draw scale value, right justified, with half a character width
250 * separation from the sequence data
252 String valueAsString = String.valueOf(value);
253 int justify = fm.stringWidth(valueAsString) + charWidth;
254 int xpos = left ? labelWidthWest - justify + charWidth / 2
255 : labelWidthEast - justify + charWidth / 2;
256 g.setColor(Color.black);
257 g.drawString(valueAsString, xpos, y);
264 * Does a fast paint of an alignment in response to a scroll. Most of the
265 * visible region is simply copied and shifted, and then any newly visible
266 * columns or rows are drawn. The scroll may be horizontal or vertical, but
267 * not both at once. Scrolling may be the result of
269 * <li>dragging a scroll bar</li>
270 * <li>clicking in the scroll bar</li>
271 * <li>scrolling by trackpad, middle mouse button, or other device</li>
272 * <li>by moving the box in the Overview window</li>
273 * <li>programmatically to make a highlighted position visible</li>
277 * columns to shift right (positive) or left (negative)
279 * rows to shift down (positive) or up (negative)
281 public void fastPaint(int horizontal, int vertical)
283 if (fastpainting || gg == null || img == null) //ok
292 int charHeight = av.getCharHeight();
293 int charWidth = av.getCharWidth();
295 ViewportRanges ranges = av.getRanges();
296 int startRes = ranges.getStartRes();
297 int endRes = ranges.getEndRes();
298 int startSeq = ranges.getStartSeq();
299 int endSeq = ranges.getEndSeq();
303 gg.copyArea(horizontal * charWidth, vertical * charHeight,
304 img.getWidth(), img.getHeight(), -horizontal * charWidth,
305 -vertical * charHeight);
307 if (horizontal > 0) // scrollbar pulled right, image to the left
309 transX = (endRes - startRes - horizontal) * charWidth;
310 startRes = endRes - horizontal;
312 else if (horizontal < 0)
314 endRes = startRes - horizontal;
316 else if (vertical > 0) // scroll down
318 startSeq = endSeq - vertical;
320 if (startSeq < ranges.getStartSeq())
321 { // ie scrolling too fast, more than a page at a time
322 startSeq = ranges.getStartSeq();
326 transY = img.getHeight() - ((vertical + 1) * charHeight);
329 else if (vertical < 0)
331 endSeq = startSeq - vertical;
333 if (endSeq > ranges.getEndSeq())
335 endSeq = ranges.getEndSeq();
339 gg.translate(transX, transY);
340 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
341 gg.translate(-transX, -transY);
346 fastpainting = false;
353 public void paintComponent(Graphics g)
355 super.paintComponent(g);
357 int charHeight = av.getCharHeight();
358 int charWidth = av.getCharWidth();
360 ViewportRanges ranges = av.getRanges();
362 int width = getWidth();
363 int height = getHeight();
365 width -= (width % charWidth);
366 height -= (height % charHeight);
368 // selectImage is the selection group outline image
369 BufferedImage selectImage = drawSelectionGroup(
370 ranges.getStartRes(), ranges.getEndRes(),
371 ranges.getStartSeq(), ranges.getEndSeq());
373 if ((img != null) && (fastPaint
374 || (getVisibleRect().width != g.getClipBounds().width)
375 || (getVisibleRect().height != g.getClipBounds().height)))
377 BufferedImage lcimg = buildLocalImage(selectImage);
378 g.drawImage(lcimg, 0, 0, this);
381 else if ((width > 0) && (height > 0))
383 // img is a cached version of the last view we drew, if any
384 // if we have no img or the size has changed, make a new one
385 if (img == null || width != img.getWidth()
386 || height != img.getHeight())
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 // lcimg is a local *copy* of img which we'll draw selectImage on top of
417 BufferedImage lcimg = buildLocalImage(selectImage);
418 g.drawImage(lcimg, 0, 0, this);
424 * Draw an alignment panel for printing
427 * Graphics object to draw with
429 * start residue of print area
431 * end residue of print area
433 * start sequence of print area
435 * end sequence of print area
437 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
438 int startSeq, int endSeq)
440 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
442 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
444 if (selectImage != null)
446 ((Graphics2D) g1).setComposite(AlphaComposite
447 .getInstance(AlphaComposite.SRC_OVER));
448 g1.drawImage(selectImage, 0, 0, this);
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 SequenceGroup group = av.getSelectionGroup();
469 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
473 BufferedImage selectImage = null;
476 selectImage = new BufferedImage(canvasWidth, canvasHeight,
477 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
478 } catch (OutOfMemoryError er)
481 System.err.println("Print image OutOfMemory Error.\n" + er);
482 new OOMWarning("Creating wrapped alignment image for printing", er);
484 if (selectImage != null)
486 Graphics2D g2 = selectImage.createGraphics();
487 setupSelectionGroup(g2, selectImage);
488 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
492 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
493 g.drawImage(selectImage, 0, 0, this);
500 * Make a local image by combining the cached image img
503 private BufferedImage buildLocalImage(BufferedImage selectImage)
505 // clone the cached image
506 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
508 Graphics2D g2d = lcimg.createGraphics();
509 g2d.drawImage(img, 0, 0, null);
511 // overlay selection group on lcimg
512 if (selectImage != null)
515 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
516 g2d.drawImage(selectImage, 0, 0, this);
524 * Set up a buffered image of the correct height and size for the sequence canvas
526 private BufferedImage setupImage()
528 BufferedImage lcimg = null;
530 int charWidth = av.getCharWidth();
531 int charHeight = av.getCharHeight();
533 int width = getWidth();
534 int height = getHeight();
536 width -= (width % charWidth);
537 height -= (height % charHeight);
539 if ((width < 1) || (height < 1))
546 lcimg = new BufferedImage(width, height,
547 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
548 } catch (OutOfMemoryError er)
552 "Group image OutOfMemory Redraw Error.\n" + er);
553 new OOMWarning("Creating alignment image for display", er);
562 * Returns the visible width of the canvas in residues, after allowing for
563 * East or West scales (if shown)
566 * the width in pixels (possibly including scales)
570 public int getWrappedCanvasWidth(int canvasWidth)
572 int charWidth = av.getCharWidth();
574 FontMetrics fm = getFontMetrics(av.getFont());
579 if (av.getScaleRightWrapped())
581 labelWidthEast = getLabelWidth(fm);
584 if (av.getScaleLeftWrapped())
586 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
590 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
594 * Returns a pixel width suitable for showing the largest sequence coordinate
595 * (end position) in the alignment. Returns 2 plus the number of decimal
596 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
601 protected int getLabelWidth(FontMetrics fm)
604 * find the biggest sequence end position we need to show
605 * (note this is not necessarily the sequence length)
608 AlignmentI alignment = av.getAlignment();
609 for (int i = 0; i < alignment.getHeight(); i++)
611 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
615 for (int i = maxWidth; i > 0; i /= 10)
620 return fm.stringWidth(ZEROS.substring(0, length));
624 * Draws as many widths of a wrapped alignment as can fit in the visible
629 * available width in pixels
630 * @param canvasHeight
631 * available height in pixels
633 * the first column (0...) of the alignment to draw
635 public void drawWrappedPanel(Graphics g, int canvasWidth,
636 int canvasHeight, final int startColumn)
638 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
641 av.setWrappedWidth(wrappedWidthInResidues);
643 ViewportRanges ranges = av.getRanges();
644 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
647 * draw one width at a time (including any scales or annotation shown),
648 * until we have run out of either alignment or vertical space available
650 int ypos = wrappedSpaceAboveAlignment;
651 int maxWidth = ranges.getVisibleAlignmentWidth();
653 int start = startColumn;
654 int currentWidth = 0;
655 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
658 .min(maxWidth, start + wrappedWidthInResidues - 1);
659 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
660 ypos += wrappedRepeatHeightPx;
661 start += wrappedWidthInResidues;
665 drawWrappedDecorators(g, startColumn);
669 * Calculates and saves values needed when rendering a wrapped alignment.
670 * These depend on many factors, including
672 * <li>canvas width and height</li>
673 * <li>number of visible sequences, and height of annotations if shown</li>
674 * <li>font and character width</li>
675 * <li>whether scales are shown left, right or above the alignment</li>
679 * @param canvasHeight
680 * @return the number of residue columns in each width
682 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
684 int charHeight = av.getCharHeight();
685 int charWidth = av.getCharWidth();
688 * width of labels in pixels left and right (if shown)
691 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
693 FontMetrics fm = getFontMetrics(av.getFont());
694 labelWidth = getLabelWidth(fm);
696 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
697 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
700 * vertical space in pixels between wrapped widths of alignment
701 * - one character height, or two if scale above is drawn
703 wrappedSpaceAboveAlignment = charHeight
704 * (av.getScaleAboveWrapped() ? 2 : 1);
707 * height in pixels of the wrapped widths
709 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
711 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
713 // add annotations panel height if shown
714 wrappedRepeatHeightPx += getAnnotationHeight();
717 * number of residue columns we can show in each row;
718 * this is just canvas width less scale left and right (if shown),
719 * as a whole multiple of character widths
721 int wrappedWidthInResidues = (canvasWidth - labelWidthEast
722 - labelWidthWest) / charWidth;
725 * number of visible widths (the last one may be part height),
726 * ensuring a part height includes at least one sequence
728 ViewportRanges ranges = av.getRanges();
729 int xMax = ranges.getVisibleAlignmentWidth();
730 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
731 int remainder = canvasHeight % wrappedRepeatHeightPx;
732 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
734 wrappedVisibleWidths++;
738 * limit visibleWidths to not exceed width of alignment
740 int maxWidths = (xMax - ranges.getStartRes()) / wrappedWidthInResidues;
741 if (xMax % wrappedWidthInResidues > 0)
745 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
747 return wrappedWidthInResidues;
751 * Draws one width of a wrapped alignment, including sequences and
752 * annnotations, if shown, but not scales or hidden column markers
758 * @param canvasHeight
760 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
761 int endColumn, int canvasHeight)
763 int charHeight = av.getCharHeight();
764 int charWidth = av.getCharWidth();
766 ViewportRanges ranges = av.getRanges();
767 int viewportWidth = ranges.getViewportWidth();
769 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
772 * move right before drawing by the width of the scale left (if any)
773 * plus column offset from left margin (usually zero, but may be non-zero
774 * when fast painting is drawing just a few columns)
776 int xOffset = labelWidthWest
777 + ((startColumn - ranges.getStartRes()) % viewportWidth)
779 g.translate(xOffset, 0);
781 // When printing we have an extra clipped region,
782 // the Printable page which we need to account for here
783 Shape clip = g.getClip();
787 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
791 g.setClip(0, (int) clip.getBounds().getY(),
792 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
796 * white fill the region to be drawn (so incremental fast paint doesn't
797 * scribble over an existing image)
799 gg.setColor(Color.white);
800 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
801 wrappedRepeatHeightPx);
803 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
806 int cHeight = av.getAlignment().getHeight() * charHeight;
808 if (av.isShowAnnotation())
810 g.translate(0, cHeight + ypos + 3);
811 if (annotations == null)
813 annotations = new AnnotationPanel(av);
816 annotations.renderer.drawComponent(annotations, av, g, -1,
817 startColumn, endx + 1);
818 g.translate(0, -cHeight - ypos - 3);
821 g.translate(-xOffset, 0);
825 * Draws scales left, right and above (if shown), and any hidden column
826 * markers, on all widths of the wrapped alignment
831 protected void drawWrappedDecorators(Graphics g, int startColumn)
833 int charWidth = av.getCharWidth();
835 g.setFont(av.getFont());
836 g.setColor(Color.black);
838 int ypos = wrappedSpaceAboveAlignment;
839 ViewportRanges ranges = av.getRanges();
840 int viewportWidth = ranges.getViewportWidth();
841 int maxWidth = ranges.getVisibleAlignmentWidth();
843 while (widthsDrawn < wrappedVisibleWidths)
845 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
847 if (av.getScaleLeftWrapped())
849 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
852 if (av.getScaleRightWrapped())
854 int x = labelWidthWest + viewportWidth * charWidth;
856 drawVerticalScale(g, startColumn, endColumn, ypos, false);
861 * white fill region of scale above and hidden column markers
862 * (to support incremental fast paint of image)
864 g.setColor(Color.white);
865 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
866 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
867 g.setColor(Color.black);
869 g.translate(labelWidthWest, 0);
871 if (av.getScaleAboveWrapped())
873 drawNorthScale(g, startColumn, endColumn, ypos);
876 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
878 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
881 g.translate(-labelWidthWest, 0);
883 ypos += wrappedRepeatHeightPx;
884 startColumn += viewportWidth;
895 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
896 int startColumn, int endColumn)
898 int charHeight = av.getCharHeight();
899 int charWidth = av.getCharWidth();
901 g.setColor(Color.blue);
902 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
903 List<Integer> positions = hidden.findHiddenRegionPositions();
904 for (int pos : positions)
906 int res = pos - startColumn;
908 if (res < 0 || res > endColumn - startColumn)
914 * draw a downward-pointing triangle at the hidden columns location
915 * (before the following visible column)
917 int xMiddle = res * charWidth;
918 int[] xPoints = new int[] { xMiddle - charHeight / 4,
919 xMiddle + charHeight / 4, xMiddle };
920 int yTop = ypos - (charHeight / 2);
921 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
922 g.fillPolygon(xPoints, yPoints, 3);
927 * Draw a selection group over a wrapped alignment
929 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
931 int canvasHeight, int startRes)
933 int charHeight = av.getCharHeight();
934 int charWidth = av.getCharWidth();
936 // height gap above each panel
937 int hgap = charHeight;
938 if (av.getScaleAboveWrapped())
943 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
945 int cHeight = av.getAlignment().getHeight() * charHeight;
947 int startx = startRes;
949 int ypos = hgap; // vertical offset
950 int maxwidth = av.getAlignment().getWidth();
952 if (av.hasHiddenColumns())
954 maxwidth = av.getAlignment().getHiddenColumns()
955 .findColumnPosition(maxwidth);
958 // chop the wrapped alignment extent up into panel-sized blocks and treat
959 // each block as if it were a block from an unwrapped alignment
960 while ((ypos <= canvasHeight) && (startx < maxwidth))
962 // set end value to be start + width, or maxwidth, whichever is smaller
963 endx = startx + cWidth - 1;
970 g.translate(labelWidthWest, 0);
972 drawUnwrappedSelection(g, group, startx, endx, 0,
973 av.getAlignment().getHeight() - 1,
976 g.translate(-labelWidthWest, 0);
978 // update vertical offset
979 ypos += cHeight + getAnnotationHeight() + hgap;
981 // update horizontal offset
986 int getAnnotationHeight()
988 if (!av.isShowAnnotation())
993 if (annotations == null)
995 annotations = new AnnotationPanel(av);
998 return annotations.adjustPanelHeight();
1002 * Draws the visible region of the alignment on the graphics context. If there
1003 * are hidden column markers in the visible region, then each sub-region
1004 * between the markers is drawn separately, followed by the hidden column
1008 * the graphics context, positioned at the first residue to be drawn
1010 * offset of the first column to draw (0..)
1012 * offset of the last column to draw (0..)
1014 * offset of the first sequence to draw (0..)
1016 * offset of the last sequence to draw (0..)
1018 * vertical offset at which to draw (for wrapped alignments)
1020 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1021 final int startSeq, final int endSeq, final int yOffset)
1023 int charHeight = av.getCharHeight();
1024 int charWidth = av.getCharWidth();
1026 if (!av.hasHiddenColumns())
1028 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1033 final int screenYMax = endRes - startRes;
1034 int blockStart = startRes;
1035 int blockEnd = endRes;
1037 for (int[] region : av.getAlignment().getHiddenColumns()
1038 .getHiddenColumnsCopy())
1040 int hideStart = region[0];
1041 int hideEnd = region[1];
1043 if (hideStart <= blockStart)
1045 blockStart += (hideEnd - hideStart) + 1;
1050 * draw up to just before the next hidden region, or the end of
1051 * the visible region, whichever comes first
1053 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1056 g1.translate(screenY * charWidth, 0);
1058 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1061 * draw the downline of the hidden column marker (ScalePanel draws the
1062 * triangle on top) if we reached it
1064 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1066 g1.setColor(Color.blue);
1068 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1069 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1070 (endSeq - startSeq + 1) * charHeight + yOffset);
1073 g1.translate(-screenY * charWidth, 0);
1074 screenY += blockEnd - blockStart + 1;
1075 blockStart = hideEnd + 1;
1077 if (screenY > screenYMax)
1079 // already rendered last block
1084 if (screenY <= screenYMax)
1086 // remaining visible region to render
1087 blockEnd = blockStart + screenYMax - screenY;
1088 g1.translate(screenY * charWidth, 0);
1089 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1091 g1.translate(-screenY * charWidth, 0);
1098 * Draws a region of the visible alignment
1102 * offset of the first column in the visible region (0..)
1104 * offset of the last column in the visible region (0..)
1106 * offset of the first sequence in the visible region (0..)
1108 * offset of the last sequence in the visible region (0..)
1110 * vertical offset at which to draw (for wrapped alignments)
1112 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1113 int endSeq, int offset)
1115 int charHeight = av.getCharHeight();
1116 int charWidth = av.getCharWidth();
1118 g.setFont(av.getFont());
1119 seqRdr.prepare(g, av.isRenderGaps());
1123 // / First draw the sequences
1124 // ///////////////////////////
1125 for (int i = startSeq; i <= endSeq; i++)
1127 nextSeq = av.getAlignment().getSequenceAt(i);
1128 if (nextSeq == null)
1130 // occasionally, a race condition occurs such that the alignment row is
1134 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1135 startRes, endRes, offset + ((i - startSeq) * charHeight));
1137 if (av.isShowSequenceFeatures())
1139 fr.drawSequence(g, nextSeq, startRes, endRes,
1140 offset + ((i - startSeq) * charHeight), false);
1144 * highlight search Results once sequence has been drawn
1146 if (av.hasSearchResults())
1148 SearchResultsI searchResults = av.getSearchResults();
1149 int[] visibleResults = searchResults.getResults(nextSeq,
1151 if (visibleResults != null)
1153 for (int r = 0; r < visibleResults.length; r += 2)
1155 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1156 visibleResults[r + 1], (visibleResults[r] - startRes)
1158 + ((i - startSeq) * charHeight));
1163 if (av.cursorMode && cursorY == i && cursorX >= startRes
1164 && cursorX <= endRes)
1166 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1167 offset + ((i - startSeq) * charHeight));
1171 if (av.getSelectionGroup() != null
1172 || av.getAlignment().getGroups().size() > 0)
1174 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1179 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1180 int startSeq, int endSeq, int offset)
1182 Graphics2D g = (Graphics2D) g1;
1184 // ///////////////////////////////////
1185 // Now outline any areas if necessary
1186 // ///////////////////////////////////
1188 SequenceGroup group = null;
1189 int groupIndex = -1;
1191 if (av.getAlignment().getGroups().size() > 0)
1193 group = av.getAlignment().getGroups().get(0);
1199 g.setStroke(new BasicStroke());
1200 g.setColor(group.getOutlineColour());
1204 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1209 g.setStroke(new BasicStroke());
1211 if (groupIndex >= av.getAlignment().getGroups().size())
1216 group = av.getAlignment().getGroups().get(groupIndex);
1218 } while (groupIndex < av.getAlignment().getGroups().size());
1226 * Draw the selection group as a separate image and overlay
1228 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1229 int startSeq, int endSeq)
1231 // get a new image of the correct size
1232 BufferedImage selectionImage = setupImage();
1234 if (selectionImage == null)
1239 SequenceGroup group = av.getSelectionGroup();
1246 // set up drawing colour
1247 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1249 setupSelectionGroup(g, selectionImage);
1251 if (!av.getWrapAlignment())
1253 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1258 drawWrappedSelection(g, group, getWidth(), getHeight(),
1259 av.getRanges().getStartRes());
1263 return selectionImage;
1267 * Set up graphics for selection group
1269 private void setupSelectionGroup(Graphics2D g,
1270 BufferedImage selectionImage)
1272 // set background to transparent
1273 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1274 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1276 // set up foreground to draw red dashed line
1277 g.setComposite(AlphaComposite.Src);
1278 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1279 BasicStroke.JOIN_ROUND, 3f, new float[]
1281 g.setColor(Color.RED);
1285 * Draw a selection group over an unwrapped alignment
1286 * @param g graphics object to draw with
1287 * @param group selection group
1288 * @param startRes start residue of area to draw
1289 * @param endRes end residue of area to draw
1290 * @param startSeq start sequence of area to draw
1291 * @param endSeq end sequence of area to draw
1292 * @param offset vertical offset (used when called from wrapped alignment code)
1294 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1295 int startRes, int endRes, int startSeq, int endSeq, int offset)
1297 int charWidth = av.getCharWidth();
1299 if (!av.hasHiddenColumns())
1301 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1306 // package into blocks of visible columns
1308 int blockStart = startRes;
1309 int blockEnd = endRes;
1311 for (int[] region : av.getAlignment().getHiddenColumns()
1312 .getHiddenColumnsCopy())
1314 int hideStart = region[0];
1315 int hideEnd = region[1];
1317 if (hideStart <= blockStart)
1319 blockStart += (hideEnd - hideStart) + 1;
1323 blockEnd = hideStart - 1;
1325 g.translate(screenY * charWidth, 0);
1326 drawPartialGroupOutline(g, group,
1327 blockStart, blockEnd, startSeq, endSeq, offset);
1329 g.translate(-screenY * charWidth, 0);
1330 screenY += blockEnd - blockStart + 1;
1331 blockStart = hideEnd + 1;
1333 if (screenY > (endRes - startRes))
1335 // already rendered last block
1340 if (screenY <= (endRes - startRes))
1342 // remaining visible region to render
1343 blockEnd = blockStart + (endRes - startRes) - screenY;
1344 g.translate(screenY * charWidth, 0);
1345 drawPartialGroupOutline(g, group,
1346 blockStart, blockEnd, startSeq, endSeq, offset);
1348 g.translate(-screenY * charWidth, 0);
1354 * Draw the selection group as a separate image and overlay
1356 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1357 int startRes, int endRes, int startSeq, int endSeq,
1360 int charHeight = av.getCharHeight();
1361 int charWidth = av.getCharWidth();
1363 int visWidth = (endRes - startRes + 1) * charWidth;
1367 boolean inGroup = false;
1375 for (i = startSeq; i <= endSeq; i++)
1377 // position of start residue of group relative to startRes, in pixels
1378 sx = (group.getStartRes() - startRes) * charWidth;
1380 // width of group in pixels
1381 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1384 sy = verticalOffset + (i - startSeq) * charHeight;
1386 if (sx + xwidth < 0 || sx > visWidth)
1391 if ((sx <= (endRes - startRes) * charWidth)
1392 && group.getSequences(null)
1393 .contains(av.getAlignment().getSequenceAt(i)))
1395 if ((bottom == -1) && !group.getSequences(null)
1396 .contains(av.getAlignment().getSequenceAt(i + 1)))
1398 bottom = sy + charHeight;
1403 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1404 .contains(av.getAlignment().getSequenceAt(i - 1)))
1417 // if start position is visible, draw vertical line to left of
1419 if (sx >= 0 && sx < visWidth)
1421 g.drawLine(sx, oldY, sx, sy);
1424 // if end position is visible, draw vertical line to right of
1426 if (sx + xwidth < visWidth)
1428 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1437 // don't let width extend beyond current block, or group extent
1439 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1441 xwidth = (endRes - startRes + 1) * charWidth - sx;
1444 // draw horizontal line at top of group
1447 g.drawLine(sx, top, sx + xwidth, top);
1451 // draw horizontal line at bottom of group
1454 g.drawLine(sx, bottom, sx + xwidth, bottom);
1465 sy = verticalOffset + ((i - startSeq) * charHeight);
1466 if (sx >= 0 && sx < visWidth)
1468 g.drawLine(sx, oldY, sx, sy);
1471 if (sx + xwidth < visWidth)
1473 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1482 if (sx + xwidth > visWidth)
1486 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1488 xwidth = (endRes - startRes + 1) * charWidth;
1493 g.drawLine(sx, top, sx + xwidth, top);
1499 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1508 * Highlights search results in the visible region by rendering as white text
1509 * on a black background. Any previous highlighting is removed. Answers true
1510 * if any highlight was left on the visible alignment (so status bar should be
1511 * set to match), else false.
1513 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1514 * alignment had to be scrolled to show the highlighted region, then it should
1515 * be fully redrawn, otherwise a fast paint can be performed. This argument
1516 * could be removed if fast paint of scrolled wrapped alignment is coded in
1517 * future (JAL-2609).
1520 * @param noFastPaint
1523 public boolean highlightSearchResults(SearchResultsI results,
1524 boolean noFastPaint)
1530 boolean wrapped = av.getWrapAlignment();
1533 fastPaint = !noFastPaint;
1534 fastpainting = fastPaint;
1537 * to avoid redrawing the whole visible region, we instead
1538 * redraw just the minimal regions to remove previous highlights
1541 SearchResultsI previous = av.getSearchResults();
1542 av.setSearchResults(results);
1543 boolean redrawn = false;
1544 boolean drawn = false;
1547 redrawn = drawMappedPositionsWrapped(previous);
1548 drawn = drawMappedPositionsWrapped(results);
1553 redrawn = drawMappedPositions(previous);
1554 drawn = drawMappedPositions(results);
1559 * if highlights were either removed or added, repaint
1567 * return true only if highlights were added
1573 fastpainting = false;
1578 * Redraws the minimal rectangle in the visible region (if any) that includes
1579 * mapped positions of the given search results. Whether or not positions are
1580 * highlighted depends on the SearchResults set on the Viewport. This allows
1581 * this method to be called to either clear or set highlighting. Answers true
1582 * if any positions were drawn (in which case a repaint is still required),
1588 protected boolean drawMappedPositions(SearchResultsI results)
1590 if (results == null)
1596 * calculate the minimal rectangle to redraw that
1597 * includes both new and existing search results
1599 int firstSeq = Integer.MAX_VALUE;
1601 int firstCol = Integer.MAX_VALUE;
1603 boolean matchFound = false;
1605 ViewportRanges ranges = av.getRanges();
1606 int firstVisibleColumn = ranges.getStartRes();
1607 int lastVisibleColumn = ranges.getEndRes();
1608 AlignmentI alignment = av.getAlignment();
1609 if (av.hasHiddenColumns())
1611 firstVisibleColumn = alignment.getHiddenColumns()
1612 .adjustForHiddenColumns(firstVisibleColumn);
1613 lastVisibleColumn = alignment.getHiddenColumns()
1614 .adjustForHiddenColumns(lastVisibleColumn);
1617 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1618 .getEndSeq(); seqNo++)
1620 SequenceI seq = alignment.getSequenceAt(seqNo);
1622 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1624 if (visibleResults != null)
1626 for (int i = 0; i < visibleResults.length - 1; i += 2)
1628 int firstMatchedColumn = visibleResults[i];
1629 int lastMatchedColumn = visibleResults[i + 1];
1630 if (firstMatchedColumn <= lastVisibleColumn
1631 && lastMatchedColumn >= firstVisibleColumn)
1634 * found a search results match in the visible region -
1635 * remember the first and last sequence matched, and the first
1636 * and last visible columns in the matched positions
1639 firstSeq = Math.min(firstSeq, seqNo);
1640 lastSeq = Math.max(lastSeq, seqNo);
1641 firstMatchedColumn = Math.max(firstMatchedColumn,
1642 firstVisibleColumn);
1643 lastMatchedColumn = Math.min(lastMatchedColumn,
1645 firstCol = Math.min(firstCol, firstMatchedColumn);
1646 lastCol = Math.max(lastCol, lastMatchedColumn);
1654 if (av.hasHiddenColumns())
1656 firstCol = alignment.getHiddenColumns()
1657 .findColumnPosition(firstCol);
1658 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1660 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1661 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1662 gg.translate(transX, transY);
1663 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1664 gg.translate(-transX, -transY);
1671 public void propertyChange(PropertyChangeEvent evt)
1673 String eventName = evt.getPropertyName();
1675 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1680 else if (eventName.equals(ViewportRanges.STARTRES))
1683 if (eventName.equals(ViewportRanges.STARTRES))
1685 // Make sure we're not trying to draw a panel
1686 // larger than the visible window
1687 ViewportRanges vpRanges = av.getRanges();
1688 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1689 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1690 if (scrollX > range)
1694 else if (scrollX < -range)
1699 // Both scrolling and resizing change viewport ranges: scrolling changes
1700 // both start and end points, but resize only changes end values.
1701 // Here we only want to fastpaint on a scroll, with resize using a normal
1702 // paint, so scroll events are identified as changes to the horizontal or
1703 // vertical start value.
1705 // scroll - startres and endres both change
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());
1724 * Does a minimal update of the image for a scroll movement. This method
1725 * handles scroll movements of up to one width of the wrapped alignment (one
1726 * click in the vertical scrollbar). Larger movements (for example after a
1727 * scroll to highlight a mapped position) trigger a full redraw instead.
1730 * number of positions scrolled (right if positive, left if negative)
1732 protected void fastPaintWrapped(int scrollX)
1734 ViewportRanges ranges = av.getRanges();
1736 if (Math.abs(scrollX) > ranges.getViewportWidth())
1739 * shift of more than one view width is
1740 * overcomplicated to handle in this method
1747 if (fastpainting || gg == null)
1753 fastpainting = true;
1757 calculateWrappedGeometry(getWidth(), getHeight());
1760 * relocate the regions of the alignment that are still visible
1762 shiftWrappedAlignment(-scrollX);
1765 * add new columns (sequence, annotation)
1766 * - at top left if scrollX < 0
1767 * - at right of last two widths if scrollX > 0
1771 int startRes = ranges.getStartRes();
1772 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1773 - scrollX - 1, getHeight());
1777 fastPaintWrappedAddRight(scrollX);
1781 * draw all scales (if shown) and hidden column markers
1783 drawWrappedDecorators(gg, ranges.getStartRes());
1788 fastpainting = false;
1793 * Draws the specified number of columns at the 'end' (bottom right) of a
1794 * wrapped alignment view, including sequences and annotations if shown, but
1795 * not scales. Also draws the same number of columns at the right hand end of
1796 * the second last width shown, if the last width is not full height (so
1797 * cannot simply be copied from the graphics image).
1801 protected void fastPaintWrappedAddRight(int columns)
1808 ViewportRanges ranges = av.getRanges();
1809 int viewportWidth = ranges.getViewportWidth();
1810 int charWidth = av.getCharWidth();
1813 * draw full height alignment in the second last row, last columns, if the
1814 * last row was not full height
1816 int visibleWidths = wrappedVisibleWidths;
1817 int canvasHeight = getHeight();
1818 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1820 if (lastWidthPartHeight)
1822 int widthsAbove = Math.max(0, visibleWidths - 2);
1823 int ypos = wrappedRepeatHeightPx * widthsAbove
1824 + wrappedSpaceAboveAlignment;
1825 int endRes = ranges.getEndRes();
1826 endRes += widthsAbove * viewportWidth;
1827 int startRes = endRes - columns;
1828 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1832 * white fill first to erase annotations
1834 gg.translate(xOffset, 0);
1835 gg.setColor(Color.white);
1836 gg.fillRect(labelWidthWest, ypos,
1837 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1838 gg.translate(-xOffset, 0);
1840 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1844 * draw newly visible columns in last wrapped width (none if we
1845 * have reached the end of the alignment)
1846 * y-offset for drawing last width is height of widths above,
1849 int widthsAbove = visibleWidths - 1;
1850 int ypos = wrappedRepeatHeightPx * widthsAbove
1851 + wrappedSpaceAboveAlignment;
1852 int endRes = ranges.getEndRes();
1853 endRes += widthsAbove * viewportWidth;
1854 int startRes = endRes - columns + 1;
1857 * white fill first to erase annotations
1859 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1861 gg.translate(xOffset, 0);
1862 gg.setColor(Color.white);
1863 int width = viewportWidth * charWidth - xOffset;
1864 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1865 gg.translate(-xOffset, 0);
1867 gg.setFont(av.getFont());
1868 gg.setColor(Color.black);
1870 if (startRes < ranges.getVisibleAlignmentWidth())
1872 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1876 * and finally, white fill any space below the visible alignment
1878 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1879 if (heightBelow > 0)
1881 gg.setColor(Color.white);
1882 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1887 * Shifts the visible alignment by the specified number of columns - left if
1888 * negative, right if positive. Copies and moves sequences and annotations (if
1889 * shown). Scales, hidden column markers and any newly visible columns must be
1894 protected void shiftWrappedAlignment(int positions)
1900 int charWidth = av.getCharWidth();
1902 int canvasHeight = getHeight();
1903 ViewportRanges ranges = av.getRanges();
1904 int viewportWidth = ranges.getViewportWidth();
1905 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1907 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1908 int xMax = ranges.getVisibleAlignmentWidth();
1913 * shift right (after scroll left)
1914 * for each wrapped width (starting with the last), copy (width-positions)
1915 * columns from the left margin to the right margin, and copy positions
1916 * columns from the right margin of the row above (if any) to the
1917 * left margin of the current row
1921 * get y-offset of last wrapped width, first row of sequences
1923 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1924 y += wrappedSpaceAboveAlignment;
1925 int copyFromLeftStart = labelWidthWest;
1926 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1930 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1931 positions * charWidth, 0);
1934 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1935 positions * charWidth, heightToCopy, -widthToCopy,
1936 wrappedRepeatHeightPx);
1939 y -= wrappedRepeatHeightPx;
1945 * shift left (after scroll right)
1946 * for each wrapped width (starting with the first), copy (width-positions)
1947 * columns from the right margin to the left margin, and copy positions
1948 * columns from the left margin of the row below (if any) to the
1949 * right margin of the current row
1951 int xpos = av.getRanges().getStartRes();
1952 int y = wrappedSpaceAboveAlignment;
1953 int copyFromRightStart = labelWidthWest - positions * charWidth;
1955 while (y < canvasHeight)
1957 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1958 positions * charWidth, 0);
1959 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1960 && (xpos + viewportWidth <= xMax))
1962 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1963 * charWidth, heightToCopy, widthToCopy,
1964 -wrappedRepeatHeightPx);
1967 y += wrappedRepeatHeightPx;
1968 xpos += viewportWidth;
1975 * Redraws any positions in the search results in the visible region of a
1976 * wrapped alignment. Any highlights are drawn depending on the search results
1977 * set on the Viewport, not the <code>results</code> argument. This allows
1978 * this method to be called either to clear highlights (passing the previous
1979 * search results), or to draw new highlights.
1984 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1986 if (results == null)
1990 int charHeight = av.getCharHeight();
1992 boolean matchFound = false;
1994 calculateWrappedGeometry(getWidth(), getHeight());
1995 int wrappedWidth = av.getWrappedWidth();
1996 int wrappedHeight = wrappedRepeatHeightPx;
1998 ViewportRanges ranges = av.getRanges();
1999 int canvasHeight = getHeight();
2000 int repeats = canvasHeight / wrappedHeight;
2001 if (canvasHeight / wrappedHeight > 0)
2006 int firstVisibleColumn = ranges.getStartRes();
2007 int lastVisibleColumn = ranges.getStartRes() + repeats
2008 * ranges.getViewportWidth() - 1;
2010 AlignmentI alignment = av.getAlignment();
2011 if (av.hasHiddenColumns())
2013 firstVisibleColumn = alignment.getHiddenColumns()
2014 .adjustForHiddenColumns(firstVisibleColumn);
2015 lastVisibleColumn = alignment.getHiddenColumns()
2016 .adjustForHiddenColumns(lastVisibleColumn);
2019 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2021 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2022 .getEndSeq(); seqNo++)
2024 SequenceI seq = alignment.getSequenceAt(seqNo);
2026 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2028 if (visibleResults != null)
2030 for (int i = 0; i < visibleResults.length - 1; i += 2)
2032 int firstMatchedColumn = visibleResults[i];
2033 int lastMatchedColumn = visibleResults[i + 1];
2034 if (firstMatchedColumn <= lastVisibleColumn
2035 && lastMatchedColumn >= firstVisibleColumn)
2038 * found a search results match in the visible region
2040 firstMatchedColumn = Math.max(firstMatchedColumn,
2041 firstVisibleColumn);
2042 lastMatchedColumn = Math.min(lastMatchedColumn,
2046 * draw each mapped position separately (as contiguous positions may
2047 * wrap across lines)
2049 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2051 int displayColumn = mappedPos;
2052 if (av.hasHiddenColumns())
2054 displayColumn = alignment.getHiddenColumns()
2055 .findColumnPosition(displayColumn);
2059 * transX: offset from left edge of canvas to residue position
2061 int transX = labelWidthWest
2062 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2063 * av.getCharWidth();
2066 * transY: offset from top edge of canvas to residue position
2068 int transY = gapHeight;
2069 transY += (displayColumn - ranges.getStartRes())
2070 / wrappedWidth * wrappedHeight;
2071 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2074 * yOffset is from graphics origin to start of visible region
2076 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2077 if (transY < getHeight())
2080 gg.translate(transX, transY);
2081 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2083 gg.translate(-transX, -transY);