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;
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;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static final String ZEROS = "0000000000";
59 final FeatureRenderer fr;
69 private final SequenceRenderer seqRdr;
71 private boolean fastPaint = false;
73 private boolean fastpainting = false;
75 private AnnotationPanel annotations;
78 * measurements for drawing a wrapped alignment
80 private int labelWidthEast; // label right width in pixels if shown
82 private int labelWidthWest; // label left width in pixels if shown
84 private int wrappedSpaceAboveAlignment; // gap between widths
86 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
88 private int wrappedVisibleWidths; // number of wrapped widths displayed
90 private Graphics2D gg;
93 * Creates a new SeqCanvas object.
97 public SeqCanvas(AlignmentPanel ap)
100 fr = new FeatureRenderer(ap);
101 seqRdr = new SequenceRenderer(av);
102 setLayout(new BorderLayout());
103 PaintRefresher.Register(this, av.getSequenceSetId());
104 setBackground(Color.white);
106 av.getRanges().addPropertyChangeListener(this);
109 public SequenceRenderer getSequenceRenderer()
114 public FeatureRenderer getFeatureRenderer()
120 * Draws the scale above a region of a wrapped alignment, consisting of a
121 * column number every major interval (10 columns).
124 * the graphics context to draw on, positioned at the start (bottom
125 * left) of the line on which to draw any scale marks
127 * start alignment column (0..)
129 * end alignment column (0..)
131 * y offset to draw at
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
135 int charHeight = av.getCharHeight();
136 int charWidth = av.getCharWidth();
139 * white fill the scale space (for the fastPaint case)
141 g.setColor(Color.white);
142 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
143 charHeight * 3 / 2 + 2);
144 g.setColor(Color.black);
146 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
148 for (ScaleMark mark : marks)
150 int mpos = mark.column; // (i - startx - 1)
155 String mstring = mark.text;
161 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
165 * draw a tick mark below the column number, centred on the column;
166 * height of tick mark is 4 pixels less than half a character
168 int xpos = (mpos * charWidth) + (charWidth / 2);
169 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
175 * Draw the scale to the left or right of a wrapped alignment
178 * graphics context, positioned at the start of the scale to be drawn
180 * first column of wrapped width (0.. excluding any hidden columns)
182 * last column of wrapped width (0.. excluding any hidden columns)
184 * vertical offset at which to begin the scale
186 * if true, scale is left of residues, if false, scale is right
188 void drawVerticalScale(Graphics g, final int startx, final int endx,
189 final int ypos, final boolean left)
191 int charHeight = av.getCharHeight();
192 int charWidth = av.getCharWidth();
194 int yPos = ypos + charHeight;
198 if (av.hasHiddenColumns())
200 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
201 startX = hiddenColumns.adjustForHiddenColumns(startx);
202 endX = hiddenColumns.adjustForHiddenColumns(endx);
204 FontMetrics fm = getFontMetrics(av.getFont());
206 for (int i = 0; i < av.getAlignment().getHeight(); i++)
208 SequenceI seq = av.getAlignment().getSequenceAt(i);
211 * find sequence position of first non-gapped position -
212 * to the right if scale left, to the left if scale right
214 int index = left ? startX : endX;
216 while (index >= startX && index <= endX)
218 if (!Comparison.isGap(seq.getCharAt(index)))
220 value = seq.findPosition(index);
234 * white fill the space for the scale
236 g.setColor(Color.white);
237 int y = (yPos + (i * charHeight)) - (charHeight / 5);
238 // fillRect origin is top left of rectangle
239 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
245 * draw scale value, right justified within its width less half a
246 * character width padding on the right
248 int labelSpace = left ? labelWidthWest : labelWidthEast;
249 labelSpace -= charWidth / 2; // leave space to the right
250 String valueAsString = String.valueOf(value);
251 int labelLength = fm.stringWidth(valueAsString);
252 int xOffset = labelSpace - labelLength;
253 g.setColor(Color.black);
254 g.drawString(valueAsString, xOffset, y);
260 * Does a fast paint of an alignment in response to a scroll. Most of the
261 * visible region is simply copied and shifted, and then any newly visible
262 * columns or rows are drawn. The scroll may be horizontal or vertical, but
263 * not both at once. Scrolling may be the result of
265 * <li>dragging a scroll bar</li>
266 * <li>clicking in the scroll bar</li>
267 * <li>scrolling by trackpad, middle mouse button, or other device</li>
268 * <li>by moving the box in the Overview window</li>
269 * <li>programmatically to make a highlighted position visible</li>
273 * columns to shift right (positive) or left (negative)
275 * rows to shift down (positive) or up (negative)
277 public void fastPaint(int horizontal, int vertical)
279 if (fastpainting || gg == null || img == null)
288 int charHeight = av.getCharHeight();
289 int charWidth = av.getCharWidth();
291 ViewportRanges ranges = av.getRanges();
292 int startRes = ranges.getStartRes();
293 int endRes = ranges.getEndRes();
294 int startSeq = ranges.getStartSeq();
295 int endSeq = ranges.getEndSeq();
299 gg.copyArea(horizontal * charWidth, vertical * charHeight,
300 img.getWidth(), img.getHeight(), -horizontal * charWidth,
301 -vertical * charHeight);
303 if (horizontal > 0) // scrollbar pulled right, image to the left
305 transX = (endRes - startRes - horizontal) * charWidth;
306 startRes = endRes - horizontal;
308 else if (horizontal < 0)
310 endRes = startRes - horizontal;
312 else if (vertical > 0) // scroll down
314 startSeq = endSeq - vertical;
316 if (startSeq < ranges.getStartSeq())
317 { // ie scrolling too fast, more than a page at a time
318 startSeq = ranges.getStartSeq();
322 transY = img.getHeight() - ((vertical + 1) * charHeight);
325 else if (vertical < 0)
327 endSeq = startSeq - vertical;
329 if (endSeq > ranges.getEndSeq())
331 endSeq = ranges.getEndSeq();
335 gg.translate(transX, transY);
336 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
337 gg.translate(-transX, -transY);
342 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
351 int charHeight = av.getCharHeight();
352 int charWidth = av.getCharWidth();
354 ViewportRanges ranges = av.getRanges();
356 int width = getWidth();
357 int height = getHeight();
359 width -= (width % charWidth);
360 height -= (height % charHeight);
362 // selectImage is the selection group outline image
363 BufferedImage selectImage = drawSelectionGroup(
364 ranges.getStartRes(), ranges.getEndRes(),
365 ranges.getStartSeq(), ranges.getEndSeq());
367 BufferedImage cursorImage = drawCursor(ranges.getStartRes(),
368 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
370 if ((img != null) && (fastPaint
371 || (getVisibleRect().width != g.getClipBounds().width)
372 || (getVisibleRect().height != g.getClipBounds().height)))
374 BufferedImage lcimg = buildLocalImage(selectImage, cursorImage);
375 g.drawImage(lcimg, 0, 0, this);
378 else if ((width > 0) && (height > 0))
380 // img is a cached version of the last view we drew, if any
381 // if we have no img or the size has changed, make a new one
382 if (img == null || width != img.getWidth()
383 || height != img.getHeight())
390 gg = (Graphics2D) img.getGraphics();
391 gg.setFont(av.getFont());
396 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
397 RenderingHints.VALUE_ANTIALIAS_ON);
400 gg.setColor(Color.white);
401 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
403 if (av.getWrapAlignment())
405 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
409 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
410 ranges.getStartSeq(), ranges.getEndSeq(), 0);
413 // lcimg is a local *copy* of img which we'll draw selectImage on top of
414 BufferedImage lcimg = buildLocalImage(selectImage, cursorImage);
415 g.drawImage(lcimg, 0, 0, this);
420 * Draw an alignment panel for printing
423 * Graphics object to draw with
425 * start residue of print area
427 * end residue of print area
429 * start sequence of print area
431 * end sequence of print area
433 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
434 int startSeq, int endSeq)
436 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
438 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
440 if (selectImage != null)
442 ((Graphics2D) g1).setComposite(AlphaComposite
443 .getInstance(AlphaComposite.SRC_OVER));
444 g1.drawImage(selectImage, 0, 0, this);
449 * Draw a wrapped alignment panel for printing
452 * Graphics object to draw with
454 * width of drawing area
455 * @param canvasHeight
456 * height of drawing area
458 * start residue of print area
460 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
461 int canvasHeight, int startRes)
463 SequenceGroup group = av.getSelectionGroup();
465 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
469 BufferedImage selectImage = null;
472 selectImage = new BufferedImage(canvasWidth, canvasHeight,
473 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
474 } catch (OutOfMemoryError er)
477 System.err.println("Print image OutOfMemory Error.\n" + er);
478 new OOMWarning("Creating wrapped alignment image for printing", er);
480 if (selectImage != null)
482 Graphics2D g2 = selectImage.createGraphics();
483 setupSelectionGroup(g2, selectImage);
484 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
488 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
489 g.drawImage(selectImage, 0, 0, this);
496 * Make a local image by combining the cached image img
499 private BufferedImage buildLocalImage(BufferedImage selectImage,
500 BufferedImage cursorImage)
502 // clone the cached image
503 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
505 Graphics2D g2d = lcimg.createGraphics();
506 g2d.drawImage(img, 0, 0, null);
508 // overlay selection group on lcimg
509 if (selectImage != null)
512 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
513 g2d.drawImage(selectImage, 0, 0, this);
515 // overlay cursor on lcimg
516 if (cursorImage != null)
518 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
519 g2d.drawImage(cursorImage, 0, 0, this);
527 * Set up a buffered image of the correct height and size for the sequence canvas
529 private BufferedImage setupImage()
531 BufferedImage lcimg = null;
533 int charWidth = av.getCharWidth();
534 int charHeight = av.getCharHeight();
536 int width = getWidth();
537 int height = getHeight();
539 width -= (width % charWidth);
540 height -= (height % charHeight);
542 if ((width < 1) || (height < 1))
549 lcimg = new BufferedImage(width, height,
550 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
551 } catch (OutOfMemoryError er)
555 "Group image OutOfMemory Redraw Error.\n" + er);
556 new OOMWarning("Creating alignment image for display", er);
565 * Returns the visible width of the canvas in residues, after allowing for
566 * East or West scales (if shown)
569 * the width in pixels (possibly including scales)
573 public int getWrappedCanvasWidth(int canvasWidth)
575 int charWidth = av.getCharWidth();
577 FontMetrics fm = getFontMetrics(av.getFont());
581 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
583 labelWidth = getLabelWidth(fm);
586 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
588 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
590 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
594 * Returns a pixel width sufficient to show the largest sequence coordinate
595 * (end position) in the alignment, calculated as the FontMetrics width of
596 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
597 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
598 * half a character width space on either side.
603 protected int getLabelWidth(FontMetrics fm)
606 * find the biggest sequence end position we need to show
607 * (note this is not necessarily the sequence length)
610 AlignmentI alignment = av.getAlignment();
611 for (int i = 0; i < alignment.getHeight(); i++)
613 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
617 for (int i = maxWidth; i > 0; i /= 10)
622 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
626 * Draws as many widths of a wrapped alignment as can fit in the visible
631 * available width in pixels
632 * @param canvasHeight
633 * available height in pixels
635 * the first column (0...) of the alignment to draw
637 public void drawWrappedPanel(Graphics g, int canvasWidth,
638 int canvasHeight, final int startColumn)
640 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
643 av.setWrappedWidth(wrappedWidthInResidues);
645 ViewportRanges ranges = av.getRanges();
646 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
649 * draw one width at a time (including any scales or annotation shown),
650 * until we have run out of either alignment or vertical space available
652 int ypos = wrappedSpaceAboveAlignment;
653 int maxWidth = ranges.getVisibleAlignmentWidth();
655 int start = startColumn;
656 int currentWidth = 0;
657 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
660 .min(maxWidth, start + wrappedWidthInResidues - 1);
661 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
662 ypos += wrappedRepeatHeightPx;
663 start += wrappedWidthInResidues;
667 drawWrappedDecorators(g, startColumn);
671 * Calculates and saves values needed when rendering a wrapped alignment.
672 * These depend on many factors, including
674 * <li>canvas width and height</li>
675 * <li>number of visible sequences, and height of annotations if shown</li>
676 * <li>font and character width</li>
677 * <li>whether scales are shown left, right or above the alignment</li>
681 * @param canvasHeight
682 * @return the number of residue columns in each width
684 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
686 int charHeight = av.getCharHeight();
689 * vertical space in pixels between wrapped widths of alignment
690 * - one character height, or two if scale above is drawn
692 wrappedSpaceAboveAlignment = charHeight
693 * (av.getScaleAboveWrapped() ? 2 : 1);
696 * height in pixels of the wrapped widths
698 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
700 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
702 // add annotations panel height if shown
703 wrappedRepeatHeightPx += getAnnotationHeight();
706 * number of visible widths (the last one may be part height),
707 * ensuring a part height includes at least one sequence
709 ViewportRanges ranges = av.getRanges();
710 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
711 int remainder = canvasHeight % wrappedRepeatHeightPx;
712 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
714 wrappedVisibleWidths++;
718 * compute width in residues; this also sets East and West label widths
720 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
723 * limit visibleWidths to not exceed width of alignment
725 int xMax = ranges.getVisibleAlignmentWidth();
726 int startToEnd = xMax - ranges.getStartRes();
727 int maxWidths = startToEnd / wrappedWidthInResidues;
728 if (startToEnd % wrappedWidthInResidues > 0)
732 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
734 return wrappedWidthInResidues;
738 * Draws one width of a wrapped alignment, including sequences and
739 * annnotations, if shown, but not scales or hidden column markers
745 * @param canvasHeight
747 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
748 int endColumn, int canvasHeight)
750 ViewportRanges ranges = av.getRanges();
751 int viewportWidth = ranges.getViewportWidth();
753 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
756 * move right before drawing by the width of the scale left (if any)
757 * plus column offset from left margin (usually zero, but may be non-zero
758 * when fast painting is drawing just a few columns)
760 int charWidth = av.getCharWidth();
761 int xOffset = labelWidthWest
762 + ((startColumn - ranges.getStartRes()) % viewportWidth)
764 g.translate(xOffset, 0);
766 // When printing we have an extra clipped region,
767 // the Printable page which we need to account for here
768 Shape clip = g.getClip();
772 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
776 g.setClip(0, (int) clip.getBounds().getY(),
777 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
781 * white fill the region to be drawn (so incremental fast paint doesn't
782 * scribble over an existing image)
784 gg.setColor(Color.white);
785 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
786 wrappedRepeatHeightPx);
788 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
791 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
793 if (av.isShowAnnotation())
795 g.translate(0, cHeight + ypos + 3);
796 if (annotations == null)
798 annotations = new AnnotationPanel(av);
801 annotations.renderer.drawComponent(annotations, av, g, -1,
802 startColumn, endx + 1);
803 g.translate(0, -cHeight - ypos - 3);
806 g.translate(-xOffset, 0);
810 * Draws scales left, right and above (if shown), and any hidden column
811 * markers, on all widths of the wrapped alignment
816 protected void drawWrappedDecorators(Graphics g, final int startColumn)
818 int charWidth = av.getCharWidth();
820 g.setFont(av.getFont());
821 g.setColor(Color.black);
823 int ypos = wrappedSpaceAboveAlignment;
824 ViewportRanges ranges = av.getRanges();
825 int viewportWidth = ranges.getViewportWidth();
826 int maxWidth = ranges.getVisibleAlignmentWidth();
828 int startCol = startColumn;
830 while (widthsDrawn < wrappedVisibleWidths)
832 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
834 if (av.getScaleLeftWrapped())
836 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
839 if (av.getScaleRightWrapped())
841 int x = labelWidthWest + viewportWidth * charWidth;
843 drawVerticalScale(g, startCol, endColumn, ypos, false);
848 * white fill region of scale above and hidden column markers
849 * (to support incremental fast paint of image)
851 g.translate(labelWidthWest, 0);
852 g.setColor(Color.white);
853 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
854 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
855 g.setColor(Color.black);
856 g.translate(-labelWidthWest, 0);
858 g.translate(labelWidthWest, 0);
860 if (av.getScaleAboveWrapped())
862 drawNorthScale(g, startCol, endColumn, ypos);
865 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
867 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
870 g.translate(-labelWidthWest, 0);
872 ypos += wrappedRepeatHeightPx;
873 startCol += viewportWidth;
879 * Draws markers (triangles) above hidden column positions between startColumn
887 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
888 int startColumn, int endColumn)
890 int charHeight = av.getCharHeight();
891 int charWidth = av.getCharWidth();
893 g.setColor(Color.blue);
894 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
895 List<Integer> positions = hidden.findHiddenRegionPositions();
896 for (int pos : positions)
898 int res = pos - startColumn;
900 if (res < 0 || res > endColumn - startColumn + 1)
906 * draw a downward-pointing triangle at the hidden columns location
907 * (before the following visible column)
909 int xMiddle = res * charWidth;
910 int[] xPoints = new int[] { xMiddle - charHeight / 4,
911 xMiddle + charHeight / 4, xMiddle };
912 int yTop = ypos - (charHeight / 2);
913 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
914 g.fillPolygon(xPoints, yPoints, 3);
919 * Draw a selection group over a wrapped alignment
921 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
923 int canvasHeight, int startRes)
925 int charHeight = av.getCharHeight();
926 int charWidth = av.getCharWidth();
928 // height gap above each panel
929 int hgap = charHeight;
930 if (av.getScaleAboveWrapped())
935 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
937 int cHeight = av.getAlignment().getHeight() * charHeight;
939 int startx = startRes;
941 int ypos = hgap; // vertical offset
942 int maxwidth = av.getAlignment().getWidth();
944 if (av.hasHiddenColumns())
946 maxwidth = av.getAlignment().getHiddenColumns()
947 .findColumnPosition(maxwidth);
950 // chop the wrapped alignment extent up into panel-sized blocks and treat
951 // each block as if it were a block from an unwrapped alignment
952 while ((ypos <= canvasHeight) && (startx < maxwidth))
954 // set end value to be start + width, or maxwidth, whichever is smaller
955 endx = startx + cWidth - 1;
962 g.translate(labelWidthWest, 0);
964 drawUnwrappedSelection(g, group, startx, endx, 0,
965 av.getAlignment().getHeight() - 1,
968 g.translate(-labelWidthWest, 0);
970 // update vertical offset
971 ypos += cHeight + getAnnotationHeight() + hgap;
973 // update horizontal offset
978 int getAnnotationHeight()
980 if (!av.isShowAnnotation())
985 if (annotations == null)
987 annotations = new AnnotationPanel(av);
990 return annotations.adjustPanelHeight();
994 * Draws the visible region of the alignment on the graphics context. If there
995 * are hidden column markers in the visible region, then each sub-region
996 * between the markers is drawn separately, followed by the hidden column
1000 * the graphics context, positioned at the first residue to be drawn
1002 * offset of the first column to draw (0..)
1004 * offset of the last column to draw (0..)
1006 * offset of the first sequence to draw (0..)
1008 * offset of the last sequence to draw (0..)
1010 * vertical offset at which to draw (for wrapped alignments)
1012 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1013 final int startSeq, final int endSeq, final int yOffset)
1015 int charHeight = av.getCharHeight();
1016 int charWidth = av.getCharWidth();
1018 if (!av.hasHiddenColumns())
1020 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1025 final int screenYMax = endRes - startRes;
1026 int blockStart = startRes;
1027 int blockEnd = endRes;
1029 for (int[] region : av.getAlignment().getHiddenColumns()
1030 .getHiddenColumnsCopy())
1032 int hideStart = region[0];
1033 int hideEnd = region[1];
1035 if (hideStart <= blockStart)
1037 blockStart += (hideEnd - hideStart) + 1;
1042 * draw up to just before the next hidden region, or the end of
1043 * the visible region, whichever comes first
1045 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1048 g1.translate(screenY * charWidth, 0);
1050 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1053 * draw the downline of the hidden column marker (ScalePanel draws the
1054 * triangle on top) if we reached it
1056 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1058 g1.setColor(Color.blue);
1060 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1061 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1062 (endSeq - startSeq + 1) * charHeight + yOffset);
1065 g1.translate(-screenY * charWidth, 0);
1066 screenY += blockEnd - blockStart + 1;
1067 blockStart = hideEnd + 1;
1069 if (screenY > screenYMax)
1071 // already rendered last block
1076 if (screenY <= screenYMax)
1078 // remaining visible region to render
1079 blockEnd = blockStart + screenYMax - screenY;
1080 g1.translate(screenY * charWidth, 0);
1081 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1083 g1.translate(-screenY * charWidth, 0);
1090 * Draws a region of the visible alignment
1094 * offset of the first column in the visible region (0..)
1096 * offset of the last column in the visible region (0..)
1098 * offset of the first sequence in the visible region (0..)
1100 * offset of the last sequence in the visible region (0..)
1102 * vertical offset at which to draw (for wrapped alignments)
1104 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1105 int endSeq, int offset)
1107 int charHeight = av.getCharHeight();
1108 int charWidth = av.getCharWidth();
1110 g.setFont(av.getFont());
1111 seqRdr.prepare(g, av.isRenderGaps());
1115 // / First draw the sequences
1116 // ///////////////////////////
1117 for (int i = startSeq; i <= endSeq; i++)
1119 nextSeq = av.getAlignment().getSequenceAt(i);
1120 if (nextSeq == null)
1122 // occasionally, a race condition occurs such that the alignment row is
1126 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1127 startRes, endRes, offset + ((i - startSeq) * charHeight));
1129 if (av.isShowSequenceFeatures())
1131 fr.drawSequence(g, nextSeq, startRes, endRes,
1132 offset + ((i - startSeq) * charHeight), false);
1136 * highlight search Results once sequence has been drawn
1138 if (av.hasSearchResults())
1140 SearchResultsI searchResults = av.getSearchResults();
1141 int[] visibleResults = searchResults.getResults(nextSeq,
1143 if (visibleResults != null)
1145 for (int r = 0; r < visibleResults.length; r += 2)
1147 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1148 visibleResults[r + 1], (visibleResults[r] - startRes)
1150 + ((i - startSeq) * charHeight));
1155 /* if (av.cursorMode && cursorY == i && cursorX >= startRes
1156 && cursorX <= endRes)
1158 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1159 offset + ((i - startSeq) * charHeight));
1163 if (av.getSelectionGroup() != null
1164 || av.getAlignment().getGroups().size() > 0)
1166 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1171 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1172 int startSeq, int endSeq, int offset)
1174 Graphics2D g = (Graphics2D) g1;
1176 // ///////////////////////////////////
1177 // Now outline any areas if necessary
1178 // ///////////////////////////////////
1180 SequenceGroup group = null;
1181 int groupIndex = -1;
1183 if (av.getAlignment().getGroups().size() > 0)
1185 group = av.getAlignment().getGroups().get(0);
1191 g.setStroke(new BasicStroke());
1192 g.setColor(group.getOutlineColour());
1196 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1201 g.setStroke(new BasicStroke());
1203 if (groupIndex >= av.getAlignment().getGroups().size())
1208 group = av.getAlignment().getGroups().get(groupIndex);
1210 } while (groupIndex < av.getAlignment().getGroups().size());
1218 * Draw the selection group as a separate image and overlay
1220 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1221 int startSeq, int endSeq)
1223 // get a new image of the correct size
1224 BufferedImage selectionImage = setupImage();
1226 if (selectionImage == null)
1231 SequenceGroup group = av.getSelectionGroup();
1238 // set up drawing colour
1239 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1241 setupSelectionGroup(g, selectionImage);
1243 if (!av.getWrapAlignment())
1245 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1250 drawWrappedSelection(g, group, getWidth(), getHeight(),
1251 av.getRanges().getStartRes());
1255 return selectionImage;
1259 * Draw the cursor as a separate image and overlay
1262 * start residue of area to draw cursor in
1264 * end residue of area to draw cursor in
1266 * start sequence of area to draw cursor in
1268 * end sequence of are to draw cursor in
1269 * @return a transparent image of the same size as the sequence canvas, with
1270 * the cursor drawn on it, if any
1272 private BufferedImage drawCursor(int startRes, int endRes, int startSeq,
1275 // define our cursor image
1276 BufferedImage cursorImage = null;
1278 // don't do work unless we have to
1279 if (av.cursorMode && cursorY >= startSeq && cursorY <= endSeq)
1283 int startx = startRes;
1285 if (av.getWrapAlignment())
1287 // work out the correct offsets for the cursor
1288 int charHeight = av.getCharHeight();
1289 int charWidth = av.getCharWidth();
1290 int canvasWidth = getWidth();
1291 int canvasHeight = getHeight();
1293 // height gap above each panel
1294 int hgap = charHeight;
1295 if (av.getScaleAboveWrapped())
1300 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1302 int cHeight = av.getAlignment().getHeight() * charHeight;
1304 endx = startx + cWidth - 1;
1305 int ypos = hgap; // vertical offset
1307 // iterate down the wrapped panels
1308 while ((ypos <= canvasHeight) && (endx < cursorX))
1310 // update vertical offset
1311 ypos += cHeight + getAnnotationHeight() + hgap;
1313 // update horizontal offset
1315 endx = startx + cWidth - 1;
1318 xoffset = labelWidthWest;
1321 // now check if cursor is within range for x values
1322 if (cursorX >= startx && cursorX <= endx)
1324 // get a new image of the correct size
1325 cursorImage = setupImage();
1326 Graphics2D g = (Graphics2D) cursorImage.getGraphics();
1328 // get the character the cursor is drawn at
1329 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1330 char s = seq.getCharAt(cursorX);
1332 seqRdr.drawCursor(g, s,
1333 xoffset + (cursorX - startx) * av.getCharWidth(),
1334 yoffset + (cursorY - startSeq) * av.getCharHeight());
1344 * Set up graphics for selection group
1346 private void setupSelectionGroup(Graphics2D g,
1347 BufferedImage selectionImage)
1349 // set background to transparent
1350 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1351 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1353 // set up foreground to draw red dashed line
1354 g.setComposite(AlphaComposite.Src);
1355 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1356 BasicStroke.JOIN_ROUND, 3f, new float[]
1358 g.setColor(Color.RED);
1362 * Draw a selection group over an unwrapped alignment
1363 * @param g graphics object to draw with
1364 * @param group selection group
1365 * @param startRes start residue of area to draw
1366 * @param endRes end residue of area to draw
1367 * @param startSeq start sequence of area to draw
1368 * @param endSeq end sequence of area to draw
1369 * @param offset vertical offset (used when called from wrapped alignment code)
1371 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1372 int startRes, int endRes, int startSeq, int endSeq, int offset)
1374 int charWidth = av.getCharWidth();
1376 if (!av.hasHiddenColumns())
1378 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1383 // package into blocks of visible columns
1385 int blockStart = startRes;
1386 int blockEnd = endRes;
1388 for (int[] region : av.getAlignment().getHiddenColumns()
1389 .getHiddenColumnsCopy())
1391 int hideStart = region[0];
1392 int hideEnd = region[1];
1394 if (hideStart <= blockStart)
1396 blockStart += (hideEnd - hideStart) + 1;
1400 blockEnd = hideStart - 1;
1402 g.translate(screenY * charWidth, 0);
1403 drawPartialGroupOutline(g, group,
1404 blockStart, blockEnd, startSeq, endSeq, offset);
1406 g.translate(-screenY * charWidth, 0);
1407 screenY += blockEnd - blockStart + 1;
1408 blockStart = hideEnd + 1;
1410 if (screenY > (endRes - startRes))
1412 // already rendered last block
1417 if (screenY <= (endRes - startRes))
1419 // remaining visible region to render
1420 blockEnd = blockStart + (endRes - startRes) - screenY;
1421 g.translate(screenY * charWidth, 0);
1422 drawPartialGroupOutline(g, group,
1423 blockStart, blockEnd, startSeq, endSeq, offset);
1425 g.translate(-screenY * charWidth, 0);
1431 * Draw the selection group as a separate image and overlay
1433 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1434 int startRes, int endRes, int startSeq, int endSeq,
1437 int charHeight = av.getCharHeight();
1438 int charWidth = av.getCharWidth();
1440 int visWidth = (endRes - startRes + 1) * charWidth;
1444 boolean inGroup = false;
1452 for (i = startSeq; i <= endSeq; i++)
1454 // position of start residue of group relative to startRes, in pixels
1455 sx = (group.getStartRes() - startRes) * charWidth;
1457 // width of group in pixels
1458 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1461 sy = verticalOffset + (i - startSeq) * charHeight;
1463 if (sx + xwidth < 0 || sx > visWidth)
1468 if ((sx <= (endRes - startRes) * charWidth)
1469 && group.getSequences(null)
1470 .contains(av.getAlignment().getSequenceAt(i)))
1472 if ((bottom == -1) && !group.getSequences(null)
1473 .contains(av.getAlignment().getSequenceAt(i + 1)))
1475 bottom = sy + charHeight;
1480 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1481 .contains(av.getAlignment().getSequenceAt(i - 1)))
1494 // if start position is visible, draw vertical line to left of
1496 if (sx >= 0 && sx < visWidth)
1498 g.drawLine(sx, oldY, sx, sy);
1501 // if end position is visible, draw vertical line to right of
1503 if (sx + xwidth < visWidth)
1505 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1514 // don't let width extend beyond current block, or group extent
1516 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1518 xwidth = (endRes - startRes + 1) * charWidth - sx;
1521 // draw horizontal line at top of group
1524 g.drawLine(sx, top, sx + xwidth, top);
1528 // draw horizontal line at bottom of group
1531 g.drawLine(sx, bottom, sx + xwidth, bottom);
1542 sy = verticalOffset + ((i - startSeq) * charHeight);
1543 if (sx >= 0 && sx < visWidth)
1545 g.drawLine(sx, oldY, sx, sy);
1548 if (sx + xwidth < visWidth)
1550 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1559 if (sx + xwidth > visWidth)
1563 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1565 xwidth = (endRes - startRes + 1) * charWidth;
1570 g.drawLine(sx, top, sx + xwidth, top);
1576 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1585 * Highlights search results in the visible region by rendering as white text
1586 * on a black background. Any previous highlighting is removed. Answers true
1587 * if any highlight was left on the visible alignment (so status bar should be
1588 * set to match), else false.
1590 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1591 * alignment had to be scrolled to show the highlighted region, then it should
1592 * be fully redrawn, otherwise a fast paint can be performed. This argument
1593 * could be removed if fast paint of scrolled wrapped alignment is coded in
1594 * future (JAL-2609).
1597 * @param noFastPaint
1600 public boolean highlightSearchResults(SearchResultsI results,
1601 boolean noFastPaint)
1607 boolean wrapped = av.getWrapAlignment();
1610 fastPaint = !noFastPaint;
1611 fastpainting = fastPaint;
1614 * to avoid redrawing the whole visible region, we instead
1615 * redraw just the minimal regions to remove previous highlights
1618 SearchResultsI previous = av.getSearchResults();
1619 av.setSearchResults(results);
1620 boolean redrawn = false;
1621 boolean drawn = false;
1624 redrawn = drawMappedPositionsWrapped(previous);
1625 drawn = drawMappedPositionsWrapped(results);
1630 redrawn = drawMappedPositions(previous);
1631 drawn = drawMappedPositions(results);
1636 * if highlights were either removed or added, repaint
1644 * return true only if highlights were added
1650 fastpainting = false;
1655 * Redraws the minimal rectangle in the visible region (if any) that includes
1656 * mapped positions of the given search results. Whether or not positions are
1657 * highlighted depends on the SearchResults set on the Viewport. This allows
1658 * this method to be called to either clear or set highlighting. Answers true
1659 * if any positions were drawn (in which case a repaint is still required),
1665 protected boolean drawMappedPositions(SearchResultsI results)
1667 if (results == null)
1673 * calculate the minimal rectangle to redraw that
1674 * includes both new and existing search results
1676 int firstSeq = Integer.MAX_VALUE;
1678 int firstCol = Integer.MAX_VALUE;
1680 boolean matchFound = false;
1682 ViewportRanges ranges = av.getRanges();
1683 int firstVisibleColumn = ranges.getStartRes();
1684 int lastVisibleColumn = ranges.getEndRes();
1685 AlignmentI alignment = av.getAlignment();
1686 if (av.hasHiddenColumns())
1688 firstVisibleColumn = alignment.getHiddenColumns()
1689 .adjustForHiddenColumns(firstVisibleColumn);
1690 lastVisibleColumn = alignment.getHiddenColumns()
1691 .adjustForHiddenColumns(lastVisibleColumn);
1694 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1695 .getEndSeq(); seqNo++)
1697 SequenceI seq = alignment.getSequenceAt(seqNo);
1699 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1701 if (visibleResults != null)
1703 for (int i = 0; i < visibleResults.length - 1; i += 2)
1705 int firstMatchedColumn = visibleResults[i];
1706 int lastMatchedColumn = visibleResults[i + 1];
1707 if (firstMatchedColumn <= lastVisibleColumn
1708 && lastMatchedColumn >= firstVisibleColumn)
1711 * found a search results match in the visible region -
1712 * remember the first and last sequence matched, and the first
1713 * and last visible columns in the matched positions
1716 firstSeq = Math.min(firstSeq, seqNo);
1717 lastSeq = Math.max(lastSeq, seqNo);
1718 firstMatchedColumn = Math.max(firstMatchedColumn,
1719 firstVisibleColumn);
1720 lastMatchedColumn = Math.min(lastMatchedColumn,
1722 firstCol = Math.min(firstCol, firstMatchedColumn);
1723 lastCol = Math.max(lastCol, lastMatchedColumn);
1731 if (av.hasHiddenColumns())
1733 firstCol = alignment.getHiddenColumns()
1734 .findColumnPosition(firstCol);
1735 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1737 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1738 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1739 gg.translate(transX, transY);
1740 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1741 gg.translate(-transX, -transY);
1748 public void propertyChange(PropertyChangeEvent evt)
1750 String eventName = evt.getPropertyName();
1752 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1760 if (eventName.equals(ViewportRanges.STARTRES))
1762 // Make sure we're not trying to draw a panel
1763 // larger than the visible window
1764 ViewportRanges vpRanges = av.getRanges();
1765 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1766 int range = vpRanges.getViewportWidth();
1767 if (scrollX > range)
1771 else if (scrollX < -range)
1777 // Both scrolling and resizing change viewport ranges: scrolling changes
1778 // both start and end points, but resize only changes end values.
1779 // Here we only want to fastpaint on a scroll, with resize using a normal
1780 // paint, so scroll events are identified as changes to the horizontal or
1781 // vertical start value.
1783 // scroll - startres and endres both change
1784 if (eventName.equals(ViewportRanges.STARTRES))
1786 if (av.getWrapAlignment())
1788 fastPaintWrapped(scrollX);
1792 fastPaint(scrollX, 0);
1795 else if (eventName.equals(ViewportRanges.STARTSEQ))
1798 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1803 * Does a minimal update of the image for a scroll movement. This method
1804 * handles scroll movements of up to one width of the wrapped alignment (one
1805 * click in the vertical scrollbar). Larger movements (for example after a
1806 * scroll to highlight a mapped position) trigger a full redraw instead.
1809 * number of positions scrolled (right if positive, left if negative)
1811 protected void fastPaintWrapped(int scrollX)
1813 ViewportRanges ranges = av.getRanges();
1815 if (Math.abs(scrollX) > ranges.getViewportWidth())
1818 * shift of more than one view width is
1819 * overcomplicated to handle in this method
1826 if (fastpainting || gg == null)
1832 fastpainting = true;
1836 calculateWrappedGeometry(getWidth(), getHeight());
1839 * relocate the regions of the alignment that are still visible
1841 shiftWrappedAlignment(-scrollX);
1844 * add new columns (sequence, annotation)
1845 * - at top left if scrollX < 0
1846 * - at right of last two widths if scrollX > 0
1850 int startRes = ranges.getStartRes();
1851 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1852 - scrollX - 1, getHeight());
1856 fastPaintWrappedAddRight(scrollX);
1860 * draw all scales (if shown) and hidden column markers
1862 drawWrappedDecorators(gg, ranges.getStartRes());
1867 fastpainting = false;
1872 * Draws the specified number of columns at the 'end' (bottom right) of a
1873 * wrapped alignment view, including sequences and annotations if shown, but
1874 * not scales. Also draws the same number of columns at the right hand end of
1875 * the second last width shown, if the last width is not full height (so
1876 * cannot simply be copied from the graphics image).
1880 protected void fastPaintWrappedAddRight(int columns)
1887 ViewportRanges ranges = av.getRanges();
1888 int viewportWidth = ranges.getViewportWidth();
1889 int charWidth = av.getCharWidth();
1892 * draw full height alignment in the second last row, last columns, if the
1893 * last row was not full height
1895 int visibleWidths = wrappedVisibleWidths;
1896 int canvasHeight = getHeight();
1897 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1899 if (lastWidthPartHeight)
1901 int widthsAbove = Math.max(0, visibleWidths - 2);
1902 int ypos = wrappedRepeatHeightPx * widthsAbove
1903 + wrappedSpaceAboveAlignment;
1904 int endRes = ranges.getEndRes();
1905 endRes += widthsAbove * viewportWidth;
1906 int startRes = endRes - columns;
1907 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1911 * white fill first to erase annotations
1913 gg.translate(xOffset, 0);
1914 gg.setColor(Color.white);
1915 gg.fillRect(labelWidthWest, ypos,
1916 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1917 gg.translate(-xOffset, 0);
1919 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1923 * draw newly visible columns in last wrapped width (none if we
1924 * have reached the end of the alignment)
1925 * y-offset for drawing last width is height of widths above,
1928 int widthsAbove = visibleWidths - 1;
1929 int ypos = wrappedRepeatHeightPx * widthsAbove
1930 + wrappedSpaceAboveAlignment;
1931 int endRes = ranges.getEndRes();
1932 endRes += widthsAbove * viewportWidth;
1933 int startRes = endRes - columns + 1;
1936 * white fill first to erase annotations
1938 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1940 gg.translate(xOffset, 0);
1941 gg.setColor(Color.white);
1942 int width = viewportWidth * charWidth - xOffset;
1943 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1944 gg.translate(-xOffset, 0);
1946 gg.setFont(av.getFont());
1947 gg.setColor(Color.black);
1949 if (startRes < ranges.getVisibleAlignmentWidth())
1951 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1955 * and finally, white fill any space below the visible alignment
1957 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1958 if (heightBelow > 0)
1960 gg.setColor(Color.white);
1961 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1966 * Shifts the visible alignment by the specified number of columns - left if
1967 * negative, right if positive. Copies and moves sequences and annotations (if
1968 * shown). Scales, hidden column markers and any newly visible columns must be
1973 protected void shiftWrappedAlignment(int positions)
1979 int charWidth = av.getCharWidth();
1981 int canvasHeight = getHeight();
1982 ViewportRanges ranges = av.getRanges();
1983 int viewportWidth = ranges.getViewportWidth();
1984 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1986 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1987 int xMax = ranges.getVisibleAlignmentWidth();
1992 * shift right (after scroll left)
1993 * for each wrapped width (starting with the last), copy (width-positions)
1994 * columns from the left margin to the right margin, and copy positions
1995 * columns from the right margin of the row above (if any) to the
1996 * left margin of the current row
2000 * get y-offset of last wrapped width, first row of sequences
2002 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2003 y += wrappedSpaceAboveAlignment;
2004 int copyFromLeftStart = labelWidthWest;
2005 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2009 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2010 positions * charWidth, 0);
2013 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2014 positions * charWidth, heightToCopy, -widthToCopy,
2015 wrappedRepeatHeightPx);
2018 y -= wrappedRepeatHeightPx;
2024 * shift left (after scroll right)
2025 * for each wrapped width (starting with the first), copy (width-positions)
2026 * columns from the right margin to the left margin, and copy positions
2027 * columns from the left margin of the row below (if any) to the
2028 * right margin of the current row
2030 int xpos = av.getRanges().getStartRes();
2031 int y = wrappedSpaceAboveAlignment;
2032 int copyFromRightStart = labelWidthWest - positions * charWidth;
2034 while (y < canvasHeight)
2036 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2037 positions * charWidth, 0);
2038 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2039 && (xpos + viewportWidth <= xMax))
2041 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2042 * charWidth, heightToCopy, widthToCopy,
2043 -wrappedRepeatHeightPx);
2046 y += wrappedRepeatHeightPx;
2047 xpos += viewportWidth;
2054 * Redraws any positions in the search results in the visible region of a
2055 * wrapped alignment. Any highlights are drawn depending on the search results
2056 * set on the Viewport, not the <code>results</code> argument. This allows
2057 * this method to be called either to clear highlights (passing the previous
2058 * search results), or to draw new highlights.
2063 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2065 if (results == null)
2069 int charHeight = av.getCharHeight();
2071 boolean matchFound = false;
2073 calculateWrappedGeometry(getWidth(), getHeight());
2074 int wrappedWidth = av.getWrappedWidth();
2075 int wrappedHeight = wrappedRepeatHeightPx;
2077 ViewportRanges ranges = av.getRanges();
2078 int canvasHeight = getHeight();
2079 int repeats = canvasHeight / wrappedHeight;
2080 if (canvasHeight / wrappedHeight > 0)
2085 int firstVisibleColumn = ranges.getStartRes();
2086 int lastVisibleColumn = ranges.getStartRes() + repeats
2087 * ranges.getViewportWidth() - 1;
2089 AlignmentI alignment = av.getAlignment();
2090 if (av.hasHiddenColumns())
2092 firstVisibleColumn = alignment.getHiddenColumns()
2093 .adjustForHiddenColumns(firstVisibleColumn);
2094 lastVisibleColumn = alignment.getHiddenColumns()
2095 .adjustForHiddenColumns(lastVisibleColumn);
2098 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2100 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2101 .getEndSeq(); seqNo++)
2103 SequenceI seq = alignment.getSequenceAt(seqNo);
2105 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2107 if (visibleResults != null)
2109 for (int i = 0; i < visibleResults.length - 1; i += 2)
2111 int firstMatchedColumn = visibleResults[i];
2112 int lastMatchedColumn = visibleResults[i + 1];
2113 if (firstMatchedColumn <= lastVisibleColumn
2114 && lastMatchedColumn >= firstVisibleColumn)
2117 * found a search results match in the visible region
2119 firstMatchedColumn = Math.max(firstMatchedColumn,
2120 firstVisibleColumn);
2121 lastMatchedColumn = Math.min(lastMatchedColumn,
2125 * draw each mapped position separately (as contiguous positions may
2126 * wrap across lines)
2128 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2130 int displayColumn = mappedPos;
2131 if (av.hasHiddenColumns())
2133 displayColumn = alignment.getHiddenColumns()
2134 .findColumnPosition(displayColumn);
2138 * transX: offset from left edge of canvas to residue position
2140 int transX = labelWidthWest
2141 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2142 * av.getCharWidth();
2145 * transY: offset from top edge of canvas to residue position
2147 int transY = gapHeight;
2148 transY += (displayColumn - ranges.getStartRes())
2149 / wrappedWidth * wrappedHeight;
2150 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2153 * yOffset is from graphics origin to start of visible region
2155 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2156 if (transY < getHeight())
2159 gg.translate(transX, transY);
2160 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2162 gg.translate(-transX, -transY);
2174 * Answers the width in pixels of the left scale labels (0 if not shown)
2178 int getLabelWidthWest()
2180 return labelWidthWest;