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;
1280 int startx = startRes;
1282 if (av.getWrapAlignment())
1284 // work out the correct offsets for the cursor
1285 int charHeight = av.getCharHeight();
1286 int charWidth = av.getCharWidth();
1287 int canvasWidth = getWidth();
1288 int canvasHeight = getHeight();
1290 // height gap above each panel
1291 int hgap = charHeight;
1292 if (av.getScaleAboveWrapped())
1297 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1299 int cHeight = av.getAlignment().getHeight() * charHeight;
1301 endx = startx + cWidth - 1;
1302 int ypos = hgap; // vertical offset
1304 // iterate down the wrapped panels
1305 while ((ypos <= canvasHeight) && (endx < cursorX))
1307 // update vertical offset
1308 ypos += cHeight + getAnnotationHeight() + hgap;
1310 // update horizontal offset
1312 endx = startx + cWidth - 1;
1315 xoffset = labelWidthWest;
1318 if (av.cursorMode && cursorY >= startSeq && cursorY <= endSeq
1319 && cursorX >= startx && cursorX <= endx)
1321 // get a new image of the correct size
1322 cursorImage = setupImage();
1323 Graphics2D g = (Graphics2D) cursorImage.getGraphics();
1325 // get the character the cursor is drawn at
1326 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1327 char s = seq.getCharAt(cursorX);
1329 seqRdr.drawCursor(g, s,
1330 xoffset + (cursorX - startx) * av.getCharWidth(),
1331 yoffset + (cursorY - startSeq) * av.getCharHeight());
1340 * Set up graphics for selection group
1342 private void setupSelectionGroup(Graphics2D g,
1343 BufferedImage selectionImage)
1345 // set background to transparent
1346 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1347 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1349 // set up foreground to draw red dashed line
1350 g.setComposite(AlphaComposite.Src);
1351 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1352 BasicStroke.JOIN_ROUND, 3f, new float[]
1354 g.setColor(Color.RED);
1358 * Draw a selection group over an unwrapped alignment
1359 * @param g graphics object to draw with
1360 * @param group selection group
1361 * @param startRes start residue of area to draw
1362 * @param endRes end residue of area to draw
1363 * @param startSeq start sequence of area to draw
1364 * @param endSeq end sequence of area to draw
1365 * @param offset vertical offset (used when called from wrapped alignment code)
1367 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1368 int startRes, int endRes, int startSeq, int endSeq, int offset)
1370 int charWidth = av.getCharWidth();
1372 if (!av.hasHiddenColumns())
1374 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1379 // package into blocks of visible columns
1381 int blockStart = startRes;
1382 int blockEnd = endRes;
1384 for (int[] region : av.getAlignment().getHiddenColumns()
1385 .getHiddenColumnsCopy())
1387 int hideStart = region[0];
1388 int hideEnd = region[1];
1390 if (hideStart <= blockStart)
1392 blockStart += (hideEnd - hideStart) + 1;
1396 blockEnd = hideStart - 1;
1398 g.translate(screenY * charWidth, 0);
1399 drawPartialGroupOutline(g, group,
1400 blockStart, blockEnd, startSeq, endSeq, offset);
1402 g.translate(-screenY * charWidth, 0);
1403 screenY += blockEnd - blockStart + 1;
1404 blockStart = hideEnd + 1;
1406 if (screenY > (endRes - startRes))
1408 // already rendered last block
1413 if (screenY <= (endRes - startRes))
1415 // remaining visible region to render
1416 blockEnd = blockStart + (endRes - startRes) - screenY;
1417 g.translate(screenY * charWidth, 0);
1418 drawPartialGroupOutline(g, group,
1419 blockStart, blockEnd, startSeq, endSeq, offset);
1421 g.translate(-screenY * charWidth, 0);
1427 * Draw the selection group as a separate image and overlay
1429 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1430 int startRes, int endRes, int startSeq, int endSeq,
1433 int charHeight = av.getCharHeight();
1434 int charWidth = av.getCharWidth();
1436 int visWidth = (endRes - startRes + 1) * charWidth;
1440 boolean inGroup = false;
1448 for (i = startSeq; i <= endSeq; i++)
1450 // position of start residue of group relative to startRes, in pixels
1451 sx = (group.getStartRes() - startRes) * charWidth;
1453 // width of group in pixels
1454 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1457 sy = verticalOffset + (i - startSeq) * charHeight;
1459 if (sx + xwidth < 0 || sx > visWidth)
1464 if ((sx <= (endRes - startRes) * charWidth)
1465 && group.getSequences(null)
1466 .contains(av.getAlignment().getSequenceAt(i)))
1468 if ((bottom == -1) && !group.getSequences(null)
1469 .contains(av.getAlignment().getSequenceAt(i + 1)))
1471 bottom = sy + charHeight;
1476 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1477 .contains(av.getAlignment().getSequenceAt(i - 1)))
1490 // if start position is visible, draw vertical line to left of
1492 if (sx >= 0 && sx < visWidth)
1494 g.drawLine(sx, oldY, sx, sy);
1497 // if end position is visible, draw vertical line to right of
1499 if (sx + xwidth < visWidth)
1501 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1510 // don't let width extend beyond current block, or group extent
1512 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1514 xwidth = (endRes - startRes + 1) * charWidth - sx;
1517 // draw horizontal line at top of group
1520 g.drawLine(sx, top, sx + xwidth, top);
1524 // draw horizontal line at bottom of group
1527 g.drawLine(sx, bottom, sx + xwidth, bottom);
1538 sy = verticalOffset + ((i - startSeq) * charHeight);
1539 if (sx >= 0 && sx < visWidth)
1541 g.drawLine(sx, oldY, sx, sy);
1544 if (sx + xwidth < visWidth)
1546 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1555 if (sx + xwidth > visWidth)
1559 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1561 xwidth = (endRes - startRes + 1) * charWidth;
1566 g.drawLine(sx, top, sx + xwidth, top);
1572 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1581 * Highlights search results in the visible region by rendering as white text
1582 * on a black background. Any previous highlighting is removed. Answers true
1583 * if any highlight was left on the visible alignment (so status bar should be
1584 * set to match), else false.
1586 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1587 * alignment had to be scrolled to show the highlighted region, then it should
1588 * be fully redrawn, otherwise a fast paint can be performed. This argument
1589 * could be removed if fast paint of scrolled wrapped alignment is coded in
1590 * future (JAL-2609).
1593 * @param noFastPaint
1596 public boolean highlightSearchResults(SearchResultsI results,
1597 boolean noFastPaint)
1603 boolean wrapped = av.getWrapAlignment();
1606 fastPaint = !noFastPaint;
1607 fastpainting = fastPaint;
1610 * to avoid redrawing the whole visible region, we instead
1611 * redraw just the minimal regions to remove previous highlights
1614 SearchResultsI previous = av.getSearchResults();
1615 av.setSearchResults(results);
1616 boolean redrawn = false;
1617 boolean drawn = false;
1620 redrawn = drawMappedPositionsWrapped(previous);
1621 drawn = drawMappedPositionsWrapped(results);
1626 redrawn = drawMappedPositions(previous);
1627 drawn = drawMappedPositions(results);
1632 * if highlights were either removed or added, repaint
1640 * return true only if highlights were added
1646 fastpainting = false;
1651 * Redraws the minimal rectangle in the visible region (if any) that includes
1652 * mapped positions of the given search results. Whether or not positions are
1653 * highlighted depends on the SearchResults set on the Viewport. This allows
1654 * this method to be called to either clear or set highlighting. Answers true
1655 * if any positions were drawn (in which case a repaint is still required),
1661 protected boolean drawMappedPositions(SearchResultsI results)
1663 if (results == null)
1669 * calculate the minimal rectangle to redraw that
1670 * includes both new and existing search results
1672 int firstSeq = Integer.MAX_VALUE;
1674 int firstCol = Integer.MAX_VALUE;
1676 boolean matchFound = false;
1678 ViewportRanges ranges = av.getRanges();
1679 int firstVisibleColumn = ranges.getStartRes();
1680 int lastVisibleColumn = ranges.getEndRes();
1681 AlignmentI alignment = av.getAlignment();
1682 if (av.hasHiddenColumns())
1684 firstVisibleColumn = alignment.getHiddenColumns()
1685 .adjustForHiddenColumns(firstVisibleColumn);
1686 lastVisibleColumn = alignment.getHiddenColumns()
1687 .adjustForHiddenColumns(lastVisibleColumn);
1690 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1691 .getEndSeq(); seqNo++)
1693 SequenceI seq = alignment.getSequenceAt(seqNo);
1695 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1697 if (visibleResults != null)
1699 for (int i = 0; i < visibleResults.length - 1; i += 2)
1701 int firstMatchedColumn = visibleResults[i];
1702 int lastMatchedColumn = visibleResults[i + 1];
1703 if (firstMatchedColumn <= lastVisibleColumn
1704 && lastMatchedColumn >= firstVisibleColumn)
1707 * found a search results match in the visible region -
1708 * remember the first and last sequence matched, and the first
1709 * and last visible columns in the matched positions
1712 firstSeq = Math.min(firstSeq, seqNo);
1713 lastSeq = Math.max(lastSeq, seqNo);
1714 firstMatchedColumn = Math.max(firstMatchedColumn,
1715 firstVisibleColumn);
1716 lastMatchedColumn = Math.min(lastMatchedColumn,
1718 firstCol = Math.min(firstCol, firstMatchedColumn);
1719 lastCol = Math.max(lastCol, lastMatchedColumn);
1727 if (av.hasHiddenColumns())
1729 firstCol = alignment.getHiddenColumns()
1730 .findColumnPosition(firstCol);
1731 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1733 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1734 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1735 gg.translate(transX, transY);
1736 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1737 gg.translate(-transX, -transY);
1744 public void propertyChange(PropertyChangeEvent evt)
1746 String eventName = evt.getPropertyName();
1748 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1756 if (eventName.equals(ViewportRanges.STARTRES))
1758 // Make sure we're not trying to draw a panel
1759 // larger than the visible window
1760 ViewportRanges vpRanges = av.getRanges();
1761 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1762 int range = vpRanges.getViewportWidth();
1763 if (scrollX > range)
1767 else if (scrollX < -range)
1773 // Both scrolling and resizing change viewport ranges: scrolling changes
1774 // both start and end points, but resize only changes end values.
1775 // Here we only want to fastpaint on a scroll, with resize using a normal
1776 // paint, so scroll events are identified as changes to the horizontal or
1777 // vertical start value.
1779 // scroll - startres and endres both change
1780 if (eventName.equals(ViewportRanges.STARTRES))
1782 if (av.getWrapAlignment())
1784 fastPaintWrapped(scrollX);
1788 fastPaint(scrollX, 0);
1791 else if (eventName.equals(ViewportRanges.STARTSEQ))
1794 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1799 * Does a minimal update of the image for a scroll movement. This method
1800 * handles scroll movements of up to one width of the wrapped alignment (one
1801 * click in the vertical scrollbar). Larger movements (for example after a
1802 * scroll to highlight a mapped position) trigger a full redraw instead.
1805 * number of positions scrolled (right if positive, left if negative)
1807 protected void fastPaintWrapped(int scrollX)
1809 ViewportRanges ranges = av.getRanges();
1811 if (Math.abs(scrollX) > ranges.getViewportWidth())
1814 * shift of more than one view width is
1815 * overcomplicated to handle in this method
1822 if (fastpainting || gg == null)
1828 fastpainting = true;
1832 calculateWrappedGeometry(getWidth(), getHeight());
1835 * relocate the regions of the alignment that are still visible
1837 shiftWrappedAlignment(-scrollX);
1840 * add new columns (sequence, annotation)
1841 * - at top left if scrollX < 0
1842 * - at right of last two widths if scrollX > 0
1846 int startRes = ranges.getStartRes();
1847 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1848 - scrollX - 1, getHeight());
1852 fastPaintWrappedAddRight(scrollX);
1856 * draw all scales (if shown) and hidden column markers
1858 drawWrappedDecorators(gg, ranges.getStartRes());
1863 fastpainting = false;
1868 * Draws the specified number of columns at the 'end' (bottom right) of a
1869 * wrapped alignment view, including sequences and annotations if shown, but
1870 * not scales. Also draws the same number of columns at the right hand end of
1871 * the second last width shown, if the last width is not full height (so
1872 * cannot simply be copied from the graphics image).
1876 protected void fastPaintWrappedAddRight(int columns)
1883 ViewportRanges ranges = av.getRanges();
1884 int viewportWidth = ranges.getViewportWidth();
1885 int charWidth = av.getCharWidth();
1888 * draw full height alignment in the second last row, last columns, if the
1889 * last row was not full height
1891 int visibleWidths = wrappedVisibleWidths;
1892 int canvasHeight = getHeight();
1893 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1895 if (lastWidthPartHeight)
1897 int widthsAbove = Math.max(0, visibleWidths - 2);
1898 int ypos = wrappedRepeatHeightPx * widthsAbove
1899 + wrappedSpaceAboveAlignment;
1900 int endRes = ranges.getEndRes();
1901 endRes += widthsAbove * viewportWidth;
1902 int startRes = endRes - columns;
1903 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1907 * white fill first to erase annotations
1909 gg.translate(xOffset, 0);
1910 gg.setColor(Color.white);
1911 gg.fillRect(labelWidthWest, ypos,
1912 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1913 gg.translate(-xOffset, 0);
1915 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1919 * draw newly visible columns in last wrapped width (none if we
1920 * have reached the end of the alignment)
1921 * y-offset for drawing last width is height of widths above,
1924 int widthsAbove = visibleWidths - 1;
1925 int ypos = wrappedRepeatHeightPx * widthsAbove
1926 + wrappedSpaceAboveAlignment;
1927 int endRes = ranges.getEndRes();
1928 endRes += widthsAbove * viewportWidth;
1929 int startRes = endRes - columns + 1;
1932 * white fill first to erase annotations
1934 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1936 gg.translate(xOffset, 0);
1937 gg.setColor(Color.white);
1938 int width = viewportWidth * charWidth - xOffset;
1939 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1940 gg.translate(-xOffset, 0);
1942 gg.setFont(av.getFont());
1943 gg.setColor(Color.black);
1945 if (startRes < ranges.getVisibleAlignmentWidth())
1947 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1951 * and finally, white fill any space below the visible alignment
1953 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1954 if (heightBelow > 0)
1956 gg.setColor(Color.white);
1957 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1962 * Shifts the visible alignment by the specified number of columns - left if
1963 * negative, right if positive. Copies and moves sequences and annotations (if
1964 * shown). Scales, hidden column markers and any newly visible columns must be
1969 protected void shiftWrappedAlignment(int positions)
1975 int charWidth = av.getCharWidth();
1977 int canvasHeight = getHeight();
1978 ViewportRanges ranges = av.getRanges();
1979 int viewportWidth = ranges.getViewportWidth();
1980 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1982 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1983 int xMax = ranges.getVisibleAlignmentWidth();
1988 * shift right (after scroll left)
1989 * for each wrapped width (starting with the last), copy (width-positions)
1990 * columns from the left margin to the right margin, and copy positions
1991 * columns from the right margin of the row above (if any) to the
1992 * left margin of the current row
1996 * get y-offset of last wrapped width, first row of sequences
1998 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1999 y += wrappedSpaceAboveAlignment;
2000 int copyFromLeftStart = labelWidthWest;
2001 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2005 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2006 positions * charWidth, 0);
2009 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2010 positions * charWidth, heightToCopy, -widthToCopy,
2011 wrappedRepeatHeightPx);
2014 y -= wrappedRepeatHeightPx;
2020 * shift left (after scroll right)
2021 * for each wrapped width (starting with the first), copy (width-positions)
2022 * columns from the right margin to the left margin, and copy positions
2023 * columns from the left margin of the row below (if any) to the
2024 * right margin of the current row
2026 int xpos = av.getRanges().getStartRes();
2027 int y = wrappedSpaceAboveAlignment;
2028 int copyFromRightStart = labelWidthWest - positions * charWidth;
2030 while (y < canvasHeight)
2032 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2033 positions * charWidth, 0);
2034 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2035 && (xpos + viewportWidth <= xMax))
2037 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2038 * charWidth, heightToCopy, widthToCopy,
2039 -wrappedRepeatHeightPx);
2042 y += wrappedRepeatHeightPx;
2043 xpos += viewportWidth;
2050 * Redraws any positions in the search results in the visible region of a
2051 * wrapped alignment. Any highlights are drawn depending on the search results
2052 * set on the Viewport, not the <code>results</code> argument. This allows
2053 * this method to be called either to clear highlights (passing the previous
2054 * search results), or to draw new highlights.
2059 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2061 if (results == null)
2065 int charHeight = av.getCharHeight();
2067 boolean matchFound = false;
2069 calculateWrappedGeometry(getWidth(), getHeight());
2070 int wrappedWidth = av.getWrappedWidth();
2071 int wrappedHeight = wrappedRepeatHeightPx;
2073 ViewportRanges ranges = av.getRanges();
2074 int canvasHeight = getHeight();
2075 int repeats = canvasHeight / wrappedHeight;
2076 if (canvasHeight / wrappedHeight > 0)
2081 int firstVisibleColumn = ranges.getStartRes();
2082 int lastVisibleColumn = ranges.getStartRes() + repeats
2083 * ranges.getViewportWidth() - 1;
2085 AlignmentI alignment = av.getAlignment();
2086 if (av.hasHiddenColumns())
2088 firstVisibleColumn = alignment.getHiddenColumns()
2089 .adjustForHiddenColumns(firstVisibleColumn);
2090 lastVisibleColumn = alignment.getHiddenColumns()
2091 .adjustForHiddenColumns(lastVisibleColumn);
2094 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2096 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2097 .getEndSeq(); seqNo++)
2099 SequenceI seq = alignment.getSequenceAt(seqNo);
2101 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2103 if (visibleResults != null)
2105 for (int i = 0; i < visibleResults.length - 1; i += 2)
2107 int firstMatchedColumn = visibleResults[i];
2108 int lastMatchedColumn = visibleResults[i + 1];
2109 if (firstMatchedColumn <= lastVisibleColumn
2110 && lastMatchedColumn >= firstVisibleColumn)
2113 * found a search results match in the visible region
2115 firstMatchedColumn = Math.max(firstMatchedColumn,
2116 firstVisibleColumn);
2117 lastMatchedColumn = Math.min(lastMatchedColumn,
2121 * draw each mapped position separately (as contiguous positions may
2122 * wrap across lines)
2124 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2126 int displayColumn = mappedPos;
2127 if (av.hasHiddenColumns())
2129 displayColumn = alignment.getHiddenColumns()
2130 .findColumnPosition(displayColumn);
2134 * transX: offset from left edge of canvas to residue position
2136 int transX = labelWidthWest
2137 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2138 * av.getCharWidth();
2141 * transY: offset from top edge of canvas to residue position
2143 int transY = gapHeight;
2144 transY += (displayColumn - ranges.getStartRes())
2145 / wrappedWidth * wrappedHeight;
2146 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2149 * yOffset is from graphics origin to start of visible region
2151 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2152 if (transY < getHeight())
2155 gg.translate(transX, transY);
2156 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2158 gg.translate(-transX, -transY);
2170 * Answers the width in pixels of the left scale labels (0 if not shown)
2174 int getLabelWidthWest()
2176 return labelWidthWest;