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 width = getWidth();
531 int height = getHeight();
533 width -= (width % charWidth);
534 height -= (height % charHeight);
536 if ((width < 1) || (height < 1))
543 lcimg = new BufferedImage(width, height,
544 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
545 } catch (OutOfMemoryError er)
549 "Group image OutOfMemory Redraw Error.\n" + er);
550 new OOMWarning("Creating alignment image for display", er);
559 * Returns the visible width of the canvas in residues, after allowing for
560 * East or West scales (if shown)
563 * the width in pixels (possibly including scales)
567 public int getWrappedCanvasWidth(int canvasWidth)
569 int charWidth = av.getCharWidth();
571 FontMetrics fm = getFontMetrics(av.getFont());
576 if (av.getScaleRightWrapped())
578 labelWidthEast = getLabelWidth(fm);
581 if (av.getScaleLeftWrapped())
583 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
587 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
591 * Returns a pixel width suitable for showing the largest sequence coordinate
592 * (end position) in the alignment. Returns 2 plus the number of decimal
593 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
598 protected int getLabelWidth(FontMetrics fm)
601 * find the biggest sequence end position we need to show
602 * (note this is not necessarily the sequence length)
605 AlignmentI alignment = av.getAlignment();
606 for (int i = 0; i < alignment.getHeight(); i++)
608 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
612 for (int i = maxWidth; i > 0; i /= 10)
617 return fm.stringWidth(ZEROS.substring(0, length));
621 * Draws as many widths of a wrapped alignment as can fit in the visible
626 * available width in pixels
627 * @param canvasHeight
628 * available height in pixels
630 * the first column (0...) of the alignment to draw
632 public void drawWrappedPanel(Graphics g, int canvasWidth,
633 int canvasHeight, final int startColumn)
635 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
638 av.setWrappedWidth(wrappedWidthInResidues);
640 ViewportRanges ranges = av.getRanges();
641 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
644 * draw one width at a time (including any scales or annotation shown),
645 * until we have run out of either alignment or vertical space available
647 int ypos = wrappedSpaceAboveAlignment;
648 int maxWidth = ranges.getVisibleAlignmentWidth();
650 int start = startColumn;
651 int currentWidth = 0;
652 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
655 .min(maxWidth, start + wrappedWidthInResidues - 1);
656 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
657 ypos += wrappedRepeatHeightPx;
658 start += wrappedWidthInResidues;
662 drawWrappedDecorators(g, startColumn);
666 * Calculates and saves values needed when rendering a wrapped alignment.
667 * These depend on many factors, including
669 * <li>canvas width and height</li>
670 * <li>number of visible sequences, and height of annotations if shown</li>
671 * <li>font and character width</li>
672 * <li>whether scales are shown left, right or above the alignment</li>
676 * @param canvasHeight
677 * @return the number of residue columns in each width
679 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
681 int charHeight = av.getCharHeight();
682 int charWidth = av.getCharWidth();
685 * width of labels in pixels left and right (if shown)
688 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
690 FontMetrics fm = getFontMetrics(av.getFont());
691 labelWidth = getLabelWidth(fm);
693 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
694 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
697 * vertical space in pixels between wrapped widths of alignment
698 * - one character height, or two if scale above is drawn
700 wrappedSpaceAboveAlignment = charHeight
701 * (av.getScaleAboveWrapped() ? 2 : 1);
704 * height in pixels of the wrapped widths
706 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
708 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
710 // add annotations panel height if shown
711 wrappedRepeatHeightPx += getAnnotationHeight();
714 * number of residue columns we can show in each row;
715 * this is just canvas width less scale left and right (if shown),
716 * as a whole multiple of character widths
718 int wrappedWidthInResidues = (canvasWidth - labelWidthEast
719 - labelWidthWest) / charWidth;
722 * number of visible widths (the last one may be part height),
723 * ensuring a part height includes at least one sequence
725 ViewportRanges ranges = av.getRanges();
726 int xMax = ranges.getVisibleAlignmentWidth();
727 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
728 int remainder = canvasHeight % wrappedRepeatHeightPx;
729 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
731 wrappedVisibleWidths++;
735 * limit visibleWidths to not exceed width of alignment
737 int maxWidths = (xMax - ranges.getStartRes()) / wrappedWidthInResidues;
738 if (xMax % wrappedWidthInResidues > 0)
742 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
744 return wrappedWidthInResidues;
748 * Draws one width of a wrapped alignment, including sequences and
749 * annnotations, if shown, but not scales or hidden column markers
755 * @param canvasHeight
757 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
758 int endColumn, int canvasHeight)
760 int charHeight = av.getCharHeight();
761 int charWidth = av.getCharWidth();
763 ViewportRanges ranges = av.getRanges();
764 int viewportWidth = ranges.getViewportWidth();
766 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
769 * move right before drawing by the width of the scale left (if any)
770 * plus column offset from left margin (usually zero, but may be non-zero
771 * when fast painting is drawing just a few columns)
773 int xOffset = labelWidthWest
774 + ((startColumn - ranges.getStartRes()) % viewportWidth)
776 g.translate(xOffset, 0);
778 // When printing we have an extra clipped region,
779 // the Printable page which we need to account for here
780 Shape clip = g.getClip();
784 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
788 g.setClip(0, (int) clip.getBounds().getY(), viewportWidth * charWidth,
789 (int) clip.getBounds().getHeight());
793 * white fill the region to be drawn (so incremental fast paint doesn't
794 * scribble over an existing image)
796 gg.setColor(Color.white);
797 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
798 wrappedRepeatHeightPx);
800 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
803 int cHeight = av.getAlignment().getHeight() * charHeight;
805 if (av.isShowAnnotation())
807 g.translate(0, cHeight + ypos + 3);
808 if (annotations == null)
810 annotations = new AnnotationPanel(av);
813 annotations.renderer.drawComponent(annotations, av, g, -1,
814 startColumn, endx + 1);
815 g.translate(0, -cHeight - ypos - 3);
818 g.translate(-xOffset, 0);
822 * Draws scales left, right and above (if shown), and any hidden column
823 * markers, on all widths of the wrapped alignment
828 protected void drawWrappedDecorators(Graphics g, int startColumn)
830 int charWidth = av.getCharWidth();
832 g.setFont(av.getFont());
833 g.setColor(Color.black);
835 int ypos = wrappedSpaceAboveAlignment;
836 ViewportRanges ranges = av.getRanges();
837 int viewportWidth = ranges.getViewportWidth();
838 int maxWidth = ranges.getVisibleAlignmentWidth();
840 while (widthsDrawn < wrappedVisibleWidths)
842 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
844 if (av.getScaleLeftWrapped())
846 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
849 if (av.getScaleRightWrapped())
851 int x = labelWidthWest + viewportWidth * charWidth;
853 drawVerticalScale(g, startColumn, endColumn, ypos, false);
858 * white fill region of scale above and hidden column markers
859 * (to support incremental fast paint of image)
861 g.setColor(Color.white);
862 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
863 viewportWidth * charWidth + labelWidthWest,
864 wrappedSpaceAboveAlignment);
865 g.setColor(Color.black);
867 g.translate(labelWidthWest, 0);
869 if (av.getScaleAboveWrapped())
871 drawNorthScale(g, startColumn, endColumn, ypos);
874 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
876 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
879 g.translate(-labelWidthWest, 0);
881 ypos += wrappedRepeatHeightPx;
882 startColumn += viewportWidth;
893 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
894 int startColumn, int endColumn)
896 int charHeight = av.getCharHeight();
897 int charWidth = av.getCharWidth();
899 g.setColor(Color.blue);
900 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
901 List<Integer> positions = hidden.findHiddenRegionPositions();
902 for (int pos : positions)
904 int res = pos - startColumn;
906 if (res < 0 || res > endColumn - startColumn)
912 * draw a downward-pointing triangle at the hidden columns location
913 * (before the following visible column)
915 int xMiddle = res * charWidth;
916 int[] xPoints = new int[] { xMiddle - charHeight / 4,
917 xMiddle + charHeight / 4, xMiddle };
918 int yTop = ypos - (charHeight / 2);
919 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
920 g.fillPolygon(xPoints, yPoints, 3);
925 * Draw a selection group over a wrapped alignment
927 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
929 int canvasHeight, int startRes)
931 // height gap above each panel
932 int hgap = charHeight;
933 if (av.getScaleAboveWrapped())
938 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
940 int cHeight = av.getAlignment().getHeight() * charHeight;
942 int startx = startRes;
944 int ypos = hgap; // vertical offset
945 int maxwidth = av.getAlignment().getWidth();
947 if (av.hasHiddenColumns())
949 maxwidth = av.getAlignment().getHiddenColumns()
950 .findColumnPosition(maxwidth);
953 // chop the wrapped alignment extent up into panel-sized blocks and treat
954 // each block as if it were a block from an unwrapped alignment
955 while ((ypos <= canvasHeight) && (startx < maxwidth))
957 // set end value to be start + width, or maxwidth, whichever is smaller
958 endx = startx + cWidth - 1;
965 g.translate(labelWidthWest, 0);
967 drawUnwrappedSelection(g, group, startx, endx, 0,
968 av.getAlignment().getHeight() - 1,
971 g.translate(-labelWidthWest, 0);
973 // update vertical offset
974 ypos += cHeight + getAnnotationHeight() + hgap;
976 // update horizontal offset
981 int getAnnotationHeight()
983 if (!av.isShowAnnotation())
988 if (annotations == null)
990 annotations = new AnnotationPanel(av);
993 return annotations.adjustPanelHeight();
997 * Draws the visible region of the alignment on the graphics context. If there
998 * are hidden column markers in the visible region, then each sub-region
999 * between the markers is drawn separately, followed by the hidden column
1003 * the graphics context, positioned at the first residue to be drawn
1005 * offset of the first column to draw (0..)
1007 * offset of the last column to draw (0..)
1009 * offset of the first sequence to draw (0..)
1011 * offset of the last sequence to draw (0..)
1013 * vertical offset at which to draw (for wrapped alignments)
1015 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1016 final int startSeq, final int endSeq, final int yOffset)
1018 int charHeight = av.getCharHeight();
1019 int charWidth = av.getCharWidth();
1021 if (!av.hasHiddenColumns())
1023 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1028 final int screenYMax = endRes - startRes;
1029 int blockStart = startRes;
1030 int blockEnd = endRes;
1032 for (int[] region : av.getAlignment().getHiddenColumns()
1033 .getHiddenColumnsCopy())
1035 int hideStart = region[0];
1036 int hideEnd = region[1];
1038 if (hideStart <= blockStart)
1040 blockStart += (hideEnd - hideStart) + 1;
1045 * draw up to just before the next hidden region, or the end of
1046 * the visible region, whichever comes first
1048 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1051 g1.translate(screenY * charWidth, 0);
1053 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1056 * draw the downline of the hidden column marker (ScalePanel draws the
1057 * triangle on top) if we reached it
1059 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1061 g1.setColor(Color.blue);
1063 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1064 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1065 (endSeq - startSeq + 1) * charHeight + yOffset);
1068 g1.translate(-screenY * charWidth, 0);
1069 screenY += blockEnd - blockStart + 1;
1070 blockStart = hideEnd + 1;
1072 if (screenY > screenYMax)
1074 // already rendered last block
1079 if (screenY <= screenYMax)
1081 // remaining visible region to render
1082 blockEnd = blockStart + screenYMax - screenY;
1083 g1.translate(screenY * charWidth, 0);
1084 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1086 g1.translate(-screenY * charWidth, 0);
1093 * Draws a region of the visible alignment
1097 * offset of the first column in the visible region (0..)
1099 * offset of the last column in the visible region (0..)
1101 * offset of the first sequence in the visible region (0..)
1103 * offset of the last sequence in the visible region (0..)
1105 * vertical offset at which to draw (for wrapped alignments)
1107 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1108 int endSeq, int offset)
1110 int charHeight = av.getCharHeight();
1111 int charWidth = av.getCharWidth();
1113 g.setFont(av.getFont());
1114 seqRdr.prepare(g, av.isRenderGaps());
1118 // / First draw the sequences
1119 // ///////////////////////////
1120 for (int i = startSeq; i <= endSeq; i++)
1122 nextSeq = av.getAlignment().getSequenceAt(i);
1123 if (nextSeq == null)
1125 // occasionally, a race condition occurs such that the alignment row is
1129 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1130 startRes, endRes, offset + ((i - startSeq) * charHeight));
1132 if (av.isShowSequenceFeatures())
1134 fr.drawSequence(g, nextSeq, startRes, endRes,
1135 offset + ((i - startSeq) * charHeight), false);
1139 * highlight search Results once sequence has been drawn
1141 if (av.hasSearchResults())
1143 SearchResultsI searchResults = av.getSearchResults();
1144 int[] visibleResults = searchResults.getResults(nextSeq,
1146 if (visibleResults != null)
1148 for (int r = 0; r < visibleResults.length; r += 2)
1150 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1151 visibleResults[r + 1], (visibleResults[r] - startRes)
1153 + ((i - startSeq) * charHeight));
1158 if (av.cursorMode && cursorY == i && cursorX >= startRes
1159 && cursorX <= endRes)
1161 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1162 offset + ((i - startSeq) * charHeight));
1166 if (av.getSelectionGroup() != null
1167 || av.getAlignment().getGroups().size() > 0)
1169 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1174 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1175 int startSeq, int endSeq, int offset)
1177 Graphics2D g = (Graphics2D) g1;
1179 // ///////////////////////////////////
1180 // Now outline any areas if necessary
1181 // ///////////////////////////////////
1183 SequenceGroup group = null;
1184 int groupIndex = -1;
1186 if (av.getAlignment().getGroups().size() > 0)
1188 group = av.getAlignment().getGroups().get(0);
1194 g.setStroke(new BasicStroke());
1195 g.setColor(group.getOutlineColour());
1199 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1204 g.setStroke(new BasicStroke());
1206 if (groupIndex >= av.getAlignment().getGroups().size())
1211 group = av.getAlignment().getGroups().get(groupIndex);
1213 } while (groupIndex < av.getAlignment().getGroups().size());
1221 * Draw the selection group as a separate image and overlay
1223 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1224 int startSeq, int endSeq)
1226 // get a new image of the correct size
1227 BufferedImage selectionImage = setupImage();
1229 if (selectionImage == null)
1234 SequenceGroup group = av.getSelectionGroup();
1241 // set up drawing colour
1242 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1244 setupSelectionGroup(g, selectionImage);
1246 if (!av.getWrapAlignment())
1248 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1253 drawWrappedSelection(g, group, getWidth(), getHeight(),
1254 av.getRanges().getStartRes());
1258 return selectionImage;
1262 * Set up graphics for selection group
1264 private void setupSelectionGroup(Graphics2D g,
1265 BufferedImage selectionImage)
1267 // set background to transparent
1268 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1269 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1271 // set up foreground to draw red dashed line
1272 g.setComposite(AlphaComposite.Src);
1273 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1274 BasicStroke.JOIN_ROUND, 3f, new float[]
1276 g.setColor(Color.RED);
1280 * Draw a selection group over an unwrapped alignment
1281 * @param g graphics object to draw with
1282 * @param group selection group
1283 * @param startRes start residue of area to draw
1284 * @param endRes end residue of area to draw
1285 * @param startSeq start sequence of area to draw
1286 * @param endSeq end sequence of area to draw
1287 * @param offset vertical offset (used when called from wrapped alignment code)
1289 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1290 int startRes, int endRes, int startSeq, int endSeq, int offset)
1292 int charWidth = av.getCharWidth();
1294 if (!av.hasHiddenColumns())
1296 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1301 // package into blocks of visible columns
1303 int blockStart = startRes;
1304 int blockEnd = endRes;
1306 for (int[] region : av.getAlignment().getHiddenColumns()
1307 .getHiddenColumnsCopy())
1309 int hideStart = region[0];
1310 int hideEnd = region[1];
1312 if (hideStart <= blockStart)
1314 blockStart += (hideEnd - hideStart) + 1;
1318 blockEnd = hideStart - 1;
1320 g.translate(screenY * charWidth, 0);
1321 drawPartialGroupOutline(g, group,
1322 blockStart, blockEnd, startSeq, endSeq, offset);
1324 g.translate(-screenY * charWidth, 0);
1325 screenY += blockEnd - blockStart + 1;
1326 blockStart = hideEnd + 1;
1328 if (screenY > (endRes - startRes))
1330 // already rendered last block
1335 if (screenY <= (endRes - startRes))
1337 // remaining visible region to render
1338 blockEnd = blockStart + (endRes - startRes) - screenY;
1339 g.translate(screenY * charWidth, 0);
1340 drawPartialGroupOutline(g, group,
1341 blockStart, blockEnd, startSeq, endSeq, offset);
1343 g.translate(-screenY * charWidth, 0);
1349 * Draw the selection group as a separate image and overlay
1351 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1352 int startRes, int endRes, int startSeq, int endSeq,
1355 int charHeight = av.getCharHeight();
1356 int charWidth = av.getCharWidth();
1358 int visWidth = (endRes - startRes + 1) * charWidth;
1362 boolean inGroup = false;
1370 for (i = startSeq; i <= endSeq; i++)
1372 // position of start residue of group relative to startRes, in pixels
1373 sx = (group.getStartRes() - startRes) * charWidth;
1375 // width of group in pixels
1376 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1379 sy = verticalOffset + (i - startSeq) * charHeight;
1381 if (sx + xwidth < 0 || sx > visWidth)
1386 if ((sx <= (endRes - startRes) * charWidth)
1387 && group.getSequences(null)
1388 .contains(av.getAlignment().getSequenceAt(i)))
1390 if ((bottom == -1) && !group.getSequences(null)
1391 .contains(av.getAlignment().getSequenceAt(i + 1)))
1393 bottom = sy + charHeight;
1398 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1399 .contains(av.getAlignment().getSequenceAt(i - 1)))
1412 // if start position is visible, draw vertical line to left of
1414 if (sx >= 0 && sx < visWidth)
1416 g.drawLine(sx, oldY, sx, sy);
1419 // if end position is visible, draw vertical line to right of
1421 if (sx + xwidth < visWidth)
1423 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1432 // don't let width extend beyond current block, or group extent
1434 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1436 xwidth = (endRes - startRes + 1) * charWidth - sx;
1439 // draw horizontal line at top of group
1442 g.drawLine(sx, top, sx + xwidth, top);
1446 // draw horizontal line at bottom of group
1449 g.drawLine(sx, bottom, sx + xwidth, bottom);
1460 sy = verticalOffset + ((i - startSeq) * charHeight);
1461 if (sx >= 0 && sx < visWidth)
1463 g.drawLine(sx, oldY, sx, sy);
1466 if (sx + xwidth < visWidth)
1468 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1477 if (sx + xwidth > visWidth)
1481 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1483 xwidth = (endRes - startRes + 1) * charWidth;
1488 g.drawLine(sx, top, sx + xwidth, top);
1494 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1503 * Highlights search results in the visible region by rendering as white text
1504 * on a black background. Any previous highlighting is removed. Answers true
1505 * if any highlight was left on the visible alignment (so status bar should be
1506 * set to match), else false.
1508 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1509 * alignment had to be scrolled to show the highlighted region, then it should
1510 * be fully redrawn, otherwise a fast paint can be performed. This argument
1511 * could be removed if fast paint of scrolled wrapped alignment is coded in
1512 * future (JAL-2609).
1515 * @param noFastPaint
1518 public boolean highlightSearchResults(SearchResultsI results,
1519 boolean noFastPaint)
1525 boolean wrapped = av.getWrapAlignment();
1528 fastPaint = !noFastPaint;
1529 fastpainting = fastPaint;
1532 * to avoid redrawing the whole visible region, we instead
1533 * redraw just the minimal regions to remove previous highlights
1536 SearchResultsI previous = av.getSearchResults();
1537 av.setSearchResults(results);
1538 boolean redrawn = false;
1539 boolean drawn = false;
1542 redrawn = drawMappedPositionsWrapped(previous);
1543 drawn = drawMappedPositionsWrapped(results);
1548 redrawn = drawMappedPositions(previous);
1549 drawn = drawMappedPositions(results);
1554 * if highlights were either removed or added, repaint
1562 * return true only if highlights were added
1568 fastpainting = false;
1573 * Redraws the minimal rectangle in the visible region (if any) that includes
1574 * mapped positions of the given search results. Whether or not positions are
1575 * highlighted depends on the SearchResults set on the Viewport. This allows
1576 * this method to be called to either clear or set highlighting. Answers true
1577 * if any positions were drawn (in which case a repaint is still required),
1583 protected boolean drawMappedPositions(SearchResultsI results)
1585 if (results == null)
1591 * calculate the minimal rectangle to redraw that
1592 * includes both new and existing search results
1594 int firstSeq = Integer.MAX_VALUE;
1596 int firstCol = Integer.MAX_VALUE;
1598 boolean matchFound = false;
1600 ViewportRanges ranges = av.getRanges();
1601 int firstVisibleColumn = ranges.getStartRes();
1602 int lastVisibleColumn = ranges.getEndRes();
1603 AlignmentI alignment = av.getAlignment();
1604 if (av.hasHiddenColumns())
1606 firstVisibleColumn = alignment.getHiddenColumns()
1607 .adjustForHiddenColumns(firstVisibleColumn);
1608 lastVisibleColumn = alignment.getHiddenColumns()
1609 .adjustForHiddenColumns(lastVisibleColumn);
1612 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1613 .getEndSeq(); seqNo++)
1615 SequenceI seq = alignment.getSequenceAt(seqNo);
1617 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1619 if (visibleResults != null)
1621 for (int i = 0; i < visibleResults.length - 1; i += 2)
1623 int firstMatchedColumn = visibleResults[i];
1624 int lastMatchedColumn = visibleResults[i + 1];
1625 if (firstMatchedColumn <= lastVisibleColumn
1626 && lastMatchedColumn >= firstVisibleColumn)
1629 * found a search results match in the visible region -
1630 * remember the first and last sequence matched, and the first
1631 * and last visible columns in the matched positions
1634 firstSeq = Math.min(firstSeq, seqNo);
1635 lastSeq = Math.max(lastSeq, seqNo);
1636 firstMatchedColumn = Math.max(firstMatchedColumn,
1637 firstVisibleColumn);
1638 lastMatchedColumn = Math.min(lastMatchedColumn,
1640 firstCol = Math.min(firstCol, firstMatchedColumn);
1641 lastCol = Math.max(lastCol, lastMatchedColumn);
1649 if (av.hasHiddenColumns())
1651 firstCol = alignment.getHiddenColumns()
1652 .findColumnPosition(firstCol);
1653 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1655 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1656 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1657 gg.translate(transX, transY);
1658 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1659 gg.translate(-transX, -transY);
1666 public void propertyChange(PropertyChangeEvent evt)
1668 String eventName = evt.getPropertyName();
1670 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1675 else if (eventName.equals(ViewportRanges.STARTRES))
1678 if (eventName.equals(ViewportRanges.STARTRES))
1680 // Make sure we're not trying to draw a panel
1681 // larger than the visible window
1682 ViewportRanges vpRanges = av.getRanges();
1683 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1684 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1685 if (scrollX > range)
1689 else if (scrollX < -range)
1694 // Both scrolling and resizing change viewport ranges: scrolling changes
1695 // both start and end points, but resize only changes end values.
1696 // Here we only want to fastpaint on a scroll, with resize using a normal
1697 // paint, so scroll events are identified as changes to the horizontal or
1698 // vertical start value.
1700 // scroll - startres and endres both change
1701 if (av.getWrapAlignment())
1703 fastPaintWrapped(scrollX);
1707 fastPaint(scrollX, 0);
1710 else if (eventName.equals(ViewportRanges.STARTSEQ))
1713 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1719 * Does a minimal update of the image for a scroll movement. This method
1720 * handles scroll movements of up to one width of the wrapped alignment (one
1721 * click in the vertical scrollbar). Larger movements (for example after a
1722 * scroll to highlight a mapped position) trigger a full redraw instead.
1725 * number of positions scrolled (right if positive, left if negative)
1727 protected void fastPaintWrapped(int scrollX)
1729 ViewportRanges ranges = av.getRanges();
1731 if (Math.abs(scrollX) > ranges.getViewportWidth())
1734 * shift of more than one view width is
1735 * overcomplicated to handle in this method
1742 if (fastpainting || gg == null)
1748 fastpainting = true;
1752 calculateWrappedGeometry(getWidth(), getHeight());
1755 * relocate the regions of the alignment that are still visible
1757 shiftWrappedAlignment(-scrollX);
1760 * add new columns (sequence, annotation)
1761 * - at top left if scrollX < 0
1762 * - at right of last two widths if scrollX > 0
1766 int startRes = ranges.getStartRes();
1767 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1768 - scrollX - 1, getHeight());
1772 fastPaintWrappedAddRight(scrollX);
1776 * draw all scales (if shown) and hidden column markers
1778 drawWrappedDecorators(gg, ranges.getStartRes());
1783 fastpainting = false;
1788 * Draws the specified number of columns at the 'end' (bottom right) of a
1789 * wrapped alignment view, including sequences and annotations if shown, but
1790 * not scales. Also draws the same number of columns at the right hand end of
1791 * the second last width shown, if the last width is not full height (so
1792 * cannot simply be copied from the graphics image).
1796 protected void fastPaintWrappedAddRight(int columns)
1803 ViewportRanges ranges = av.getRanges();
1804 int viewportWidth = ranges.getViewportWidth();
1805 int charWidth = av.getCharWidth();
1808 * draw full height alignment in the second last row, last columns, if the
1809 * last row was not full height
1811 int visibleWidths = wrappedVisibleWidths;
1812 int canvasHeight = getHeight();
1813 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1815 if (lastWidthPartHeight)
1817 int widthsAbove = Math.max(0, visibleWidths - 2);
1818 int ypos = wrappedRepeatHeightPx * widthsAbove
1819 + wrappedSpaceAboveAlignment;
1820 int endRes = ranges.getEndRes();
1821 endRes += widthsAbove * viewportWidth;
1822 int startRes = endRes - columns;
1823 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1827 * white fill first to erase annotations
1829 gg.translate(xOffset, 0);
1830 gg.setColor(Color.white);
1831 gg.fillRect(labelWidthWest, ypos,
1832 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1833 gg.translate(-xOffset, 0);
1835 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1839 * draw newly visible columns in last wrapped width (none if we
1840 * have reached the end of the alignment)
1841 * y-offset for drawing last width is height of widths above,
1844 int widthsAbove = visibleWidths - 1;
1845 int ypos = wrappedRepeatHeightPx * widthsAbove
1846 + wrappedSpaceAboveAlignment;
1847 int endRes = ranges.getEndRes();
1848 endRes += widthsAbove * viewportWidth;
1849 int startRes = endRes - columns + 1;
1852 * white fill first to erase annotations
1854 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1856 gg.translate(xOffset, 0);
1857 gg.setColor(Color.white);
1858 int width = viewportWidth * charWidth - xOffset;
1859 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1860 gg.translate(-xOffset, 0);
1862 gg.setFont(av.getFont());
1863 gg.setColor(Color.black);
1865 if (startRes < ranges.getVisibleAlignmentWidth())
1867 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1871 * and finally, white fill any space below the visible alignment
1873 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1874 if (heightBelow > 0)
1876 gg.setColor(Color.white);
1877 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1882 * Shifts the visible alignment by the specified number of columns - left if
1883 * negative, right if positive. Copies and moves sequences and annotations (if
1884 * shown). Scales, hidden column markers and any newly visible columns must be
1889 protected void shiftWrappedAlignment(int positions)
1895 int charWidth = av.getCharWidth();
1897 int canvasHeight = getHeight();
1898 ViewportRanges ranges = av.getRanges();
1899 int viewportWidth = ranges.getViewportWidth();
1900 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1902 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1903 int xMax = ranges.getVisibleAlignmentWidth();
1908 * shift right (after scroll left)
1909 * for each wrapped width (starting with the last), copy (width-positions)
1910 * columns from the left margin to the right margin, and copy positions
1911 * columns from the right margin of the row above (if any) to the
1912 * left margin of the current row
1916 * get y-offset of last wrapped width, first row of sequences
1918 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1919 y += wrappedSpaceAboveAlignment;
1920 int copyFromLeftStart = labelWidthWest;
1921 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1925 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1926 positions * charWidth, 0);
1929 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1930 positions * charWidth, heightToCopy, -widthToCopy,
1931 wrappedRepeatHeightPx);
1934 y -= wrappedRepeatHeightPx;
1940 * shift left (after scroll right)
1941 * for each wrapped width (starting with the first), copy (width-positions)
1942 * columns from the right margin to the left margin, and copy positions
1943 * columns from the left margin of the row below (if any) to the
1944 * right margin of the current row
1946 int xpos = av.getRanges().getStartRes();
1947 int y = wrappedSpaceAboveAlignment;
1948 int copyFromRightStart = labelWidthWest - positions * charWidth;
1950 while (y < canvasHeight)
1952 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1953 positions * charWidth, 0);
1954 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1955 && (xpos + viewportWidth <= xMax))
1957 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1958 * charWidth, heightToCopy, widthToCopy,
1959 -wrappedRepeatHeightPx);
1962 y += wrappedRepeatHeightPx;
1963 xpos += viewportWidth;
1970 * Redraws any positions in the search results in the visible region of a
1971 * wrapped alignment. Any highlights are drawn depending on the search results
1972 * set on the Viewport, not the <code>results</code> argument. This allows
1973 * this method to be called either to clear highlights (passing the previous
1974 * search results), or to draw new highlights.
1979 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1981 if (results == null)
1985 int charHeight = av.getCharHeight();
1987 boolean matchFound = false;
1989 calculateWrappedGeometry(getWidth(), getHeight());
1990 int wrappedWidth = av.getWrappedWidth();
1991 int wrappedHeight = wrappedRepeatHeightPx;
1993 ViewportRanges ranges = av.getRanges();
1994 int canvasHeight = getHeight();
1995 int repeats = canvasHeight / wrappedHeight;
1996 if (canvasHeight / wrappedHeight > 0)
2001 int firstVisibleColumn = ranges.getStartRes();
2002 int lastVisibleColumn = ranges.getStartRes() + repeats
2003 * ranges.getViewportWidth() - 1;
2005 AlignmentI alignment = av.getAlignment();
2006 if (av.hasHiddenColumns())
2008 firstVisibleColumn = alignment.getHiddenColumns()
2009 .adjustForHiddenColumns(firstVisibleColumn);
2010 lastVisibleColumn = alignment.getHiddenColumns()
2011 .adjustForHiddenColumns(lastVisibleColumn);
2014 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2016 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2017 .getEndSeq(); seqNo++)
2019 SequenceI seq = alignment.getSequenceAt(seqNo);
2021 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2023 if (visibleResults != null)
2025 for (int i = 0; i < visibleResults.length - 1; i += 2)
2027 int firstMatchedColumn = visibleResults[i];
2028 int lastMatchedColumn = visibleResults[i + 1];
2029 if (firstMatchedColumn <= lastVisibleColumn
2030 && lastMatchedColumn >= firstVisibleColumn)
2033 * found a search results match in the visible region
2035 firstMatchedColumn = Math.max(firstMatchedColumn,
2036 firstVisibleColumn);
2037 lastMatchedColumn = Math.min(lastMatchedColumn,
2041 * draw each mapped position separately (as contiguous positions may
2042 * wrap across lines)
2044 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2046 int displayColumn = mappedPos;
2047 if (av.hasHiddenColumns())
2049 displayColumn = alignment.getHiddenColumns()
2050 .findColumnPosition(displayColumn);
2054 * transX: offset from left edge of canvas to residue position
2056 int transX = labelWidthWest
2057 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2058 * av.getCharWidth();
2061 * transY: offset from top edge of canvas to residue position
2063 int transY = gapHeight;
2064 transY += (displayColumn - ranges.getStartRes())
2065 / wrappedWidth * wrappedHeight;
2066 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2069 * yOffset is from graphics origin to start of visible region
2071 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2072 if (transY < getHeight())
2075 gg.translate(transX, transY);
2076 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2078 gg.translate(-transX, -transY);