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 if ((img != null) && (fastPaint
368 || (getVisibleRect().width != g.getClipBounds().width)
369 || (getVisibleRect().height != g.getClipBounds().height)))
371 BufferedImage lcimg = buildLocalImage(selectImage);
372 g.drawImage(lcimg, 0, 0, this);
375 else if ((width > 0) && (height > 0))
377 // img is a cached version of the last view we drew, if any
378 // if we have no img or the size has changed, make a new one
379 if (img == null || width != img.getWidth()
380 || height != img.getHeight())
387 gg = (Graphics2D) img.getGraphics();
388 gg.setFont(av.getFont());
393 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
394 RenderingHints.VALUE_ANTIALIAS_ON);
397 gg.setColor(Color.white);
398 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
400 if (av.getWrapAlignment())
402 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
406 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
407 ranges.getStartSeq(), ranges.getEndSeq(), 0);
410 // lcimg is a local *copy* of img which we'll draw selectImage on top of
411 BufferedImage lcimg = buildLocalImage(selectImage);
412 g.drawImage(lcimg, 0, 0, this);
418 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
419 ranges.getStartSeq(), ranges.getEndSeq());
424 * Draw an alignment panel for printing
427 * Graphics object to draw with
429 * start residue of print area
431 * end residue of print area
433 * start sequence of print area
435 * end sequence of print area
437 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
438 int startSeq, int endSeq)
440 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
442 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
444 if (selectImage != null)
446 ((Graphics2D) g1).setComposite(AlphaComposite
447 .getInstance(AlphaComposite.SRC_OVER));
448 g1.drawImage(selectImage, 0, 0, this);
453 * Draw a wrapped alignment panel for printing
456 * Graphics object to draw with
458 * width of drawing area
459 * @param canvasHeight
460 * height of drawing area
462 * start residue of print area
464 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
465 int canvasHeight, int startRes)
467 SequenceGroup group = av.getSelectionGroup();
469 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
473 BufferedImage selectImage = null;
476 selectImage = new BufferedImage(canvasWidth, canvasHeight,
477 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
478 } catch (OutOfMemoryError er)
481 System.err.println("Print image OutOfMemory Error.\n" + er);
482 new OOMWarning("Creating wrapped alignment image for printing", er);
484 if (selectImage != null)
486 Graphics2D g2 = selectImage.createGraphics();
487 setupSelectionGroup(g2, selectImage);
488 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
492 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
493 g.drawImage(selectImage, 0, 0, this);
500 * Make a local image by combining the cached image img
503 private BufferedImage buildLocalImage(BufferedImage selectImage)
505 // clone the cached image
506 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
508 Graphics2D g2d = lcimg.createGraphics();
509 g2d.drawImage(img, 0, 0, null);
511 // overlay selection group on lcimg
512 if (selectImage != null)
515 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
516 g2d.drawImage(selectImage, 0, 0, this);
525 * Set up a buffered image of the correct height and size for the sequence canvas
527 private BufferedImage setupImage()
529 BufferedImage lcimg = null;
531 int charWidth = av.getCharWidth();
532 int charHeight = av.getCharHeight();
534 int width = getWidth();
535 int height = getHeight();
537 width -= (width % charWidth);
538 height -= (height % charHeight);
540 if ((width < 1) || (height < 1))
547 lcimg = new BufferedImage(width, height,
548 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
549 } catch (OutOfMemoryError er)
553 "Group image OutOfMemory Redraw Error.\n" + er);
554 new OOMWarning("Creating alignment image for display", er);
563 * Returns the visible width of the canvas in residues, after allowing for
564 * East or West scales (if shown)
567 * the width in pixels (possibly including scales)
571 public int getWrappedCanvasWidth(int canvasWidth)
573 int charWidth = av.getCharWidth();
575 FontMetrics fm = getFontMetrics(av.getFont());
579 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
581 labelWidth = getLabelWidth(fm);
584 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
586 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
588 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
592 * Returns a pixel width sufficient to show the largest sequence coordinate
593 * (end position) in the alignment, calculated as the FontMetrics width of
594 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
595 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
596 * half a character width space on either side.
601 protected int getLabelWidth(FontMetrics fm)
604 * find the biggest sequence end position we need to show
605 * (note this is not necessarily the sequence length)
608 AlignmentI alignment = av.getAlignment();
609 for (int i = 0; i < alignment.getHeight(); i++)
611 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
615 for (int i = maxWidth; i > 0; i /= 10)
620 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
624 * Draws as many widths of a wrapped alignment as can fit in the visible
629 * available width in pixels
630 * @param canvasHeight
631 * available height in pixels
633 * the first column (0...) of the alignment to draw
635 public void drawWrappedPanel(Graphics g, int canvasWidth,
636 int canvasHeight, final int startColumn)
638 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
641 av.setWrappedWidth(wrappedWidthInResidues);
643 ViewportRanges ranges = av.getRanges();
644 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
647 * draw one width at a time (including any scales or annotation shown),
648 * until we have run out of either alignment or vertical space available
650 int ypos = wrappedSpaceAboveAlignment;
651 int maxWidth = ranges.getVisibleAlignmentWidth();
653 int start = startColumn;
654 int currentWidth = 0;
655 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
658 .min(maxWidth, start + wrappedWidthInResidues - 1);
659 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
660 ypos += wrappedRepeatHeightPx;
661 start += wrappedWidthInResidues;
665 drawWrappedDecorators(g, startColumn);
669 * Calculates and saves values needed when rendering a wrapped alignment.
670 * These depend on many factors, including
672 * <li>canvas width and height</li>
673 * <li>number of visible sequences, and height of annotations if shown</li>
674 * <li>font and character width</li>
675 * <li>whether scales are shown left, right or above the alignment</li>
679 * @param canvasHeight
680 * @return the number of residue columns in each width
682 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
684 int charHeight = av.getCharHeight();
687 * vertical space in pixels between wrapped widths of alignment
688 * - one character height, or two if scale above is drawn
690 wrappedSpaceAboveAlignment = charHeight
691 * (av.getScaleAboveWrapped() ? 2 : 1);
694 * height in pixels of the wrapped widths
696 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
698 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
700 // add annotations panel height if shown
701 wrappedRepeatHeightPx += getAnnotationHeight();
704 * number of visible widths (the last one may be part height),
705 * ensuring a part height includes at least one sequence
707 ViewportRanges ranges = av.getRanges();
708 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
709 int remainder = canvasHeight % wrappedRepeatHeightPx;
710 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
712 wrappedVisibleWidths++;
716 * compute width in residues; this also sets East and West label widths
718 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
721 * limit visibleWidths to not exceed width of alignment
723 int xMax = ranges.getVisibleAlignmentWidth();
724 int startToEnd = xMax - ranges.getStartRes();
725 int maxWidths = startToEnd / wrappedWidthInResidues;
726 if (startToEnd % wrappedWidthInResidues > 0)
730 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
732 return wrappedWidthInResidues;
736 * Draws one width of a wrapped alignment, including sequences and
737 * annnotations, if shown, but not scales or hidden column markers
743 * @param canvasHeight
745 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
746 int endColumn, int canvasHeight)
748 ViewportRanges ranges = av.getRanges();
749 int viewportWidth = ranges.getViewportWidth();
751 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
754 * move right before drawing by the width of the scale left (if any)
755 * plus column offset from left margin (usually zero, but may be non-zero
756 * when fast painting is drawing just a few columns)
758 int charWidth = av.getCharWidth();
759 int xOffset = labelWidthWest
760 + ((startColumn - ranges.getStartRes()) % viewportWidth)
762 g.translate(xOffset, 0);
764 // When printing we have an extra clipped region,
765 // the Printable page which we need to account for here
766 Shape clip = g.getClip();
770 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
774 g.setClip(0, (int) clip.getBounds().getY(),
775 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
779 * white fill the region to be drawn (so incremental fast paint doesn't
780 * scribble over an existing image)
782 gg.setColor(Color.white);
783 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
784 wrappedRepeatHeightPx);
786 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
789 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
791 if (av.isShowAnnotation())
793 g.translate(0, cHeight + ypos + 3);
794 if (annotations == null)
796 annotations = new AnnotationPanel(av);
799 annotations.renderer.drawComponent(annotations, av, g, -1,
800 startColumn, endx + 1);
801 g.translate(0, -cHeight - ypos - 3);
804 g.translate(-xOffset, 0);
808 * Draws scales left, right and above (if shown), and any hidden column
809 * markers, on all widths of the wrapped alignment
814 protected void drawWrappedDecorators(Graphics g, final int startColumn)
816 int charWidth = av.getCharWidth();
818 g.setFont(av.getFont());
819 g.setColor(Color.black);
821 int ypos = wrappedSpaceAboveAlignment;
822 ViewportRanges ranges = av.getRanges();
823 int viewportWidth = ranges.getViewportWidth();
824 int maxWidth = ranges.getVisibleAlignmentWidth();
826 int startCol = startColumn;
828 while (widthsDrawn < wrappedVisibleWidths)
830 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
832 if (av.getScaleLeftWrapped())
834 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
837 if (av.getScaleRightWrapped())
839 int x = labelWidthWest + viewportWidth * charWidth;
841 drawVerticalScale(g, startCol, endColumn, ypos, false);
846 * white fill region of scale above and hidden column markers
847 * (to support incremental fast paint of image)
849 g.translate(labelWidthWest, 0);
850 g.setColor(Color.white);
851 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
852 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
853 g.setColor(Color.black);
854 g.translate(-labelWidthWest, 0);
856 g.translate(labelWidthWest, 0);
858 if (av.getScaleAboveWrapped())
860 drawNorthScale(g, startCol, endColumn, ypos);
863 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
865 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
868 g.translate(-labelWidthWest, 0);
870 ypos += wrappedRepeatHeightPx;
871 startCol += viewportWidth;
877 * Draws markers (triangles) above hidden column positions between startColumn
885 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
886 int startColumn, int endColumn)
888 int charHeight = av.getCharHeight();
889 int charWidth = av.getCharWidth();
891 g.setColor(Color.blue);
892 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
893 List<Integer> positions = hidden.findHiddenRegionPositions();
894 for (int pos : positions)
896 int res = pos - startColumn;
898 if (res < 0 || res > endColumn - startColumn + 1)
904 * draw a downward-pointing triangle at the hidden columns location
905 * (before the following visible column)
907 int xMiddle = res * charWidth;
908 int[] xPoints = new int[] { xMiddle - charHeight / 4,
909 xMiddle + charHeight / 4, xMiddle };
910 int yTop = ypos - (charHeight / 2);
911 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
912 g.fillPolygon(xPoints, yPoints, 3);
917 * Draw a selection group over a wrapped alignment
919 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
921 int canvasHeight, int startRes)
923 int charHeight = av.getCharHeight();
924 int charWidth = av.getCharWidth();
926 // height gap above each panel
927 int hgap = charHeight;
928 if (av.getScaleAboveWrapped())
933 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
935 int cHeight = av.getAlignment().getHeight() * charHeight;
937 int startx = startRes;
939 int ypos = hgap; // vertical offset
940 int maxwidth = av.getAlignment().getWidth();
942 if (av.hasHiddenColumns())
944 maxwidth = av.getAlignment().getHiddenColumns()
945 .findColumnPosition(maxwidth);
948 // chop the wrapped alignment extent up into panel-sized blocks and treat
949 // each block as if it were a block from an unwrapped alignment
950 while ((ypos <= canvasHeight) && (startx < maxwidth))
952 // set end value to be start + width, or maxwidth, whichever is smaller
953 endx = startx + cWidth - 1;
960 g.translate(labelWidthWest, 0);
962 drawUnwrappedSelection(g, group, startx, endx, 0,
963 av.getAlignment().getHeight() - 1,
966 g.translate(-labelWidthWest, 0);
968 // update vertical offset
969 ypos += cHeight + getAnnotationHeight() + hgap;
971 // update horizontal offset
976 int getAnnotationHeight()
978 if (!av.isShowAnnotation())
983 if (annotations == null)
985 annotations = new AnnotationPanel(av);
988 return annotations.adjustPanelHeight();
992 * Draws the visible region of the alignment on the graphics context. If there
993 * are hidden column markers in the visible region, then each sub-region
994 * between the markers is drawn separately, followed by the hidden column
998 * the graphics context, positioned at the first residue to be drawn
1000 * offset of the first column to draw (0..)
1002 * offset of the last column to draw (0..)
1004 * offset of the first sequence to draw (0..)
1006 * offset of the last sequence to draw (0..)
1008 * vertical offset at which to draw (for wrapped alignments)
1010 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1011 final int startSeq, final int endSeq, final int yOffset)
1013 int charHeight = av.getCharHeight();
1014 int charWidth = av.getCharWidth();
1016 if (!av.hasHiddenColumns())
1018 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1023 final int screenYMax = endRes - startRes;
1024 int blockStart = startRes;
1025 int blockEnd = endRes;
1027 for (int[] region : av.getAlignment().getHiddenColumns()
1028 .getHiddenColumnsCopy())
1030 int hideStart = region[0];
1031 int hideEnd = region[1];
1033 if (hideStart <= blockStart)
1035 blockStart += (hideEnd - hideStart) + 1;
1040 * draw up to just before the next hidden region, or the end of
1041 * the visible region, whichever comes first
1043 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1046 g1.translate(screenY * charWidth, 0);
1048 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1051 * draw the downline of the hidden column marker (ScalePanel draws the
1052 * triangle on top) if we reached it
1054 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1056 g1.setColor(Color.blue);
1058 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1059 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1060 (endSeq - startSeq + 1) * charHeight + yOffset);
1063 g1.translate(-screenY * charWidth, 0);
1064 screenY += blockEnd - blockStart + 1;
1065 blockStart = hideEnd + 1;
1067 if (screenY > screenYMax)
1069 // already rendered last block
1074 if (screenY <= screenYMax)
1076 // remaining visible region to render
1077 blockEnd = blockStart + screenYMax - screenY;
1078 g1.translate(screenY * charWidth, 0);
1079 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1081 g1.translate(-screenY * charWidth, 0);
1088 * Draws a region of the visible alignment
1092 * offset of the first column in the visible region (0..)
1094 * offset of the last column in the visible region (0..)
1096 * offset of the first sequence in the visible region (0..)
1098 * offset of the last sequence in the visible region (0..)
1100 * vertical offset at which to draw (for wrapped alignments)
1102 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1103 int endSeq, int offset)
1105 int charHeight = av.getCharHeight();
1106 int charWidth = av.getCharWidth();
1108 g.setFont(av.getFont());
1109 seqRdr.prepare(g, av.isRenderGaps());
1113 // / First draw the sequences
1114 // ///////////////////////////
1115 for (int i = startSeq; i <= endSeq; i++)
1117 nextSeq = av.getAlignment().getSequenceAt(i);
1118 if (nextSeq == null)
1120 // occasionally, a race condition occurs such that the alignment row is
1124 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1125 startRes, endRes, offset + ((i - startSeq) * charHeight));
1127 if (av.isShowSequenceFeatures())
1129 fr.drawSequence(g, nextSeq, startRes, endRes,
1130 offset + ((i - startSeq) * charHeight), false);
1134 * highlight search Results once sequence has been drawn
1136 if (av.hasSearchResults())
1138 SearchResultsI searchResults = av.getSearchResults();
1139 int[] visibleResults = searchResults.getResults(nextSeq,
1141 if (visibleResults != null)
1143 for (int r = 0; r < visibleResults.length; r += 2)
1145 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1146 visibleResults[r + 1], (visibleResults[r] - startRes)
1148 + ((i - startSeq) * charHeight));
1154 if (av.getSelectionGroup() != null
1155 || av.getAlignment().getGroups().size() > 0)
1157 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1162 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1163 int startSeq, int endSeq, int offset)
1165 Graphics2D g = (Graphics2D) g1;
1167 // ///////////////////////////////////
1168 // Now outline any areas if necessary
1169 // ///////////////////////////////////
1171 SequenceGroup group = null;
1172 int groupIndex = -1;
1174 if (av.getAlignment().getGroups().size() > 0)
1176 group = av.getAlignment().getGroups().get(0);
1182 g.setStroke(new BasicStroke());
1183 g.setColor(group.getOutlineColour());
1187 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1192 g.setStroke(new BasicStroke());
1194 if (groupIndex >= av.getAlignment().getGroups().size())
1199 group = av.getAlignment().getGroups().get(groupIndex);
1201 } while (groupIndex < av.getAlignment().getGroups().size());
1209 * Draw the selection group as a separate image and overlay
1211 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1212 int startSeq, int endSeq)
1214 // get a new image of the correct size
1215 BufferedImage selectionImage = setupImage();
1217 if (selectionImage == null)
1222 SequenceGroup group = av.getSelectionGroup();
1229 // set up drawing colour
1230 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1232 setupSelectionGroup(g, selectionImage);
1234 if (!av.getWrapAlignment())
1236 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1241 drawWrappedSelection(g, group, getWidth(), getHeight(),
1242 av.getRanges().getStartRes());
1246 return selectionImage;
1250 * Draw the cursor as a separate image and overlay
1253 * start residue of area to draw cursor in
1255 * end residue of area to draw cursor in
1257 * start sequence of area to draw cursor in
1259 * end sequence of are to draw cursor in
1260 * @return a transparent image of the same size as the sequence canvas, with
1261 * the cursor drawn on it, if any
1263 private void drawCursor(Graphics g, int startRes, int endRes,
1267 // convert the cursorY into a position on the visible alignment
1268 int cursor_ypos = av.getAlignment().getHiddenSequences()
1269 .findIndexWithoutHiddenSeqs(cursorY);
1271 // don't do work unless we have to
1272 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1276 int startx = startRes;
1279 // convert the cursorX into a position on the visible alignment
1280 int cursor_xpos = av.getAlignment().getHiddenColumns()
1281 .findColumnPosition(cursorX);
1283 if (av.getWrapAlignment())
1285 // work out the correct offsets for the cursor
1286 int charHeight = av.getCharHeight();
1287 int charWidth = av.getCharWidth();
1288 int canvasWidth = getWidth();
1289 int canvasHeight = getHeight();
1291 // height gap above each panel
1292 int hgap = charHeight;
1293 if (av.getScaleAboveWrapped())
1298 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1300 int cHeight = av.getAlignment().getHeight() * charHeight;
1302 endx = startx + cWidth - 1;
1303 int ypos = hgap; // vertical offset
1305 // iterate down the wrapped panels
1306 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1308 // update vertical offset
1309 ypos += cHeight + getAnnotationHeight() + hgap;
1311 // update horizontal offset
1313 endx = startx + cWidth - 1;
1316 xoffset = labelWidthWest;
1319 // now check if cursor is within range for x values
1320 if (cursor_xpos >= startx && cursor_xpos <= endx)
1322 // get the character the cursor is drawn at
1323 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1324 char s = seq.getCharAt(cursorX);
1326 seqRdr.drawCursor(g, s,
1327 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1328 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1335 * Set up graphics for selection group
1337 private void setupSelectionGroup(Graphics2D g,
1338 BufferedImage selectionImage)
1340 // set background to transparent
1341 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1342 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1344 // set up foreground to draw red dashed line
1345 g.setComposite(AlphaComposite.Src);
1346 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1347 BasicStroke.JOIN_ROUND, 3f, new float[]
1349 g.setColor(Color.RED);
1353 * Draw a selection group over an unwrapped alignment
1354 * @param g graphics object to draw with
1355 * @param group selection group
1356 * @param startRes start residue of area to draw
1357 * @param endRes end residue of area to draw
1358 * @param startSeq start sequence of area to draw
1359 * @param endSeq end sequence of area to draw
1360 * @param offset vertical offset (used when called from wrapped alignment code)
1362 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1363 int startRes, int endRes, int startSeq, int endSeq, int offset)
1365 int charWidth = av.getCharWidth();
1367 if (!av.hasHiddenColumns())
1369 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1374 // package into blocks of visible columns
1376 int blockStart = startRes;
1377 int blockEnd = endRes;
1379 for (int[] region : av.getAlignment().getHiddenColumns()
1380 .getHiddenColumnsCopy())
1382 int hideStart = region[0];
1383 int hideEnd = region[1];
1385 if (hideStart <= blockStart)
1387 blockStart += (hideEnd - hideStart) + 1;
1391 blockEnd = hideStart - 1;
1393 g.translate(screenY * charWidth, 0);
1394 drawPartialGroupOutline(g, group,
1395 blockStart, blockEnd, startSeq, endSeq, offset);
1397 g.translate(-screenY * charWidth, 0);
1398 screenY += blockEnd - blockStart + 1;
1399 blockStart = hideEnd + 1;
1401 if (screenY > (endRes - startRes))
1403 // already rendered last block
1408 if (screenY <= (endRes - startRes))
1410 // remaining visible region to render
1411 blockEnd = blockStart + (endRes - startRes) - screenY;
1412 g.translate(screenY * charWidth, 0);
1413 drawPartialGroupOutline(g, group,
1414 blockStart, blockEnd, startSeq, endSeq, offset);
1416 g.translate(-screenY * charWidth, 0);
1422 * Draw the selection group as a separate image and overlay
1424 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1425 int startRes, int endRes, int startSeq, int endSeq,
1428 int charHeight = av.getCharHeight();
1429 int charWidth = av.getCharWidth();
1431 int visWidth = (endRes - startRes + 1) * charWidth;
1435 boolean inGroup = false;
1443 for (i = startSeq; i <= endSeq; i++)
1445 // position of start residue of group relative to startRes, in pixels
1446 sx = (group.getStartRes() - startRes) * charWidth;
1448 // width of group in pixels
1449 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1452 sy = verticalOffset + (i - startSeq) * charHeight;
1454 if (sx + xwidth < 0 || sx > visWidth)
1459 if ((sx <= (endRes - startRes) * charWidth)
1460 && group.getSequences(null)
1461 .contains(av.getAlignment().getSequenceAt(i)))
1463 if ((bottom == -1) && !group.getSequences(null)
1464 .contains(av.getAlignment().getSequenceAt(i + 1)))
1466 bottom = sy + charHeight;
1471 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1472 .contains(av.getAlignment().getSequenceAt(i - 1)))
1485 // if start position is visible, draw vertical line to left of
1487 if (sx >= 0 && sx < visWidth)
1489 g.drawLine(sx, oldY, sx, sy);
1492 // if end position is visible, draw vertical line to right of
1494 if (sx + xwidth < visWidth)
1496 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1505 // don't let width extend beyond current block, or group extent
1507 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1509 xwidth = (endRes - startRes + 1) * charWidth - sx;
1512 // draw horizontal line at top of group
1515 g.drawLine(sx, top, sx + xwidth, top);
1519 // draw horizontal line at bottom of group
1522 g.drawLine(sx, bottom, sx + xwidth, bottom);
1533 sy = verticalOffset + ((i - startSeq) * charHeight);
1534 if (sx >= 0 && sx < visWidth)
1536 g.drawLine(sx, oldY, sx, sy);
1539 if (sx + xwidth < visWidth)
1541 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1550 if (sx + xwidth > visWidth)
1554 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1556 xwidth = (endRes - startRes + 1) * charWidth;
1561 g.drawLine(sx, top, sx + xwidth, top);
1567 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1576 * Highlights search results in the visible region by rendering as white text
1577 * on a black background. Any previous highlighting is removed. Answers true
1578 * if any highlight was left on the visible alignment (so status bar should be
1579 * set to match), else false.
1581 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1582 * alignment had to be scrolled to show the highlighted region, then it should
1583 * be fully redrawn, otherwise a fast paint can be performed. This argument
1584 * could be removed if fast paint of scrolled wrapped alignment is coded in
1585 * future (JAL-2609).
1588 * @param noFastPaint
1591 public boolean highlightSearchResults(SearchResultsI results,
1592 boolean noFastPaint)
1598 boolean wrapped = av.getWrapAlignment();
1601 fastPaint = !noFastPaint;
1602 fastpainting = fastPaint;
1605 * to avoid redrawing the whole visible region, we instead
1606 * redraw just the minimal regions to remove previous highlights
1609 SearchResultsI previous = av.getSearchResults();
1610 av.setSearchResults(results);
1611 boolean redrawn = false;
1612 boolean drawn = false;
1615 redrawn = drawMappedPositionsWrapped(previous);
1616 drawn = drawMappedPositionsWrapped(results);
1621 redrawn = drawMappedPositions(previous);
1622 drawn = drawMappedPositions(results);
1627 * if highlights were either removed or added, repaint
1635 * return true only if highlights were added
1641 fastpainting = false;
1646 * Redraws the minimal rectangle in the visible region (if any) that includes
1647 * mapped positions of the given search results. Whether or not positions are
1648 * highlighted depends on the SearchResults set on the Viewport. This allows
1649 * this method to be called to either clear or set highlighting. Answers true
1650 * if any positions were drawn (in which case a repaint is still required),
1656 protected boolean drawMappedPositions(SearchResultsI results)
1658 if (results == null)
1664 * calculate the minimal rectangle to redraw that
1665 * includes both new and existing search results
1667 int firstSeq = Integer.MAX_VALUE;
1669 int firstCol = Integer.MAX_VALUE;
1671 boolean matchFound = false;
1673 ViewportRanges ranges = av.getRanges();
1674 int firstVisibleColumn = ranges.getStartRes();
1675 int lastVisibleColumn = ranges.getEndRes();
1676 AlignmentI alignment = av.getAlignment();
1677 if (av.hasHiddenColumns())
1679 firstVisibleColumn = alignment.getHiddenColumns()
1680 .adjustForHiddenColumns(firstVisibleColumn);
1681 lastVisibleColumn = alignment.getHiddenColumns()
1682 .adjustForHiddenColumns(lastVisibleColumn);
1685 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1686 .getEndSeq(); seqNo++)
1688 SequenceI seq = alignment.getSequenceAt(seqNo);
1690 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1692 if (visibleResults != null)
1694 for (int i = 0; i < visibleResults.length - 1; i += 2)
1696 int firstMatchedColumn = visibleResults[i];
1697 int lastMatchedColumn = visibleResults[i + 1];
1698 if (firstMatchedColumn <= lastVisibleColumn
1699 && lastMatchedColumn >= firstVisibleColumn)
1702 * found a search results match in the visible region -
1703 * remember the first and last sequence matched, and the first
1704 * and last visible columns in the matched positions
1707 firstSeq = Math.min(firstSeq, seqNo);
1708 lastSeq = Math.max(lastSeq, seqNo);
1709 firstMatchedColumn = Math.max(firstMatchedColumn,
1710 firstVisibleColumn);
1711 lastMatchedColumn = Math.min(lastMatchedColumn,
1713 firstCol = Math.min(firstCol, firstMatchedColumn);
1714 lastCol = Math.max(lastCol, lastMatchedColumn);
1722 if (av.hasHiddenColumns())
1724 firstCol = alignment.getHiddenColumns()
1725 .findColumnPosition(firstCol);
1726 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1728 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1729 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1730 gg.translate(transX, transY);
1731 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1732 gg.translate(-transX, -transY);
1739 public void propertyChange(PropertyChangeEvent evt)
1741 String eventName = evt.getPropertyName();
1743 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1751 if (eventName.equals(ViewportRanges.STARTRES))
1753 // Make sure we're not trying to draw a panel
1754 // larger than the visible window
1755 ViewportRanges vpRanges = av.getRanges();
1756 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1757 int range = vpRanges.getViewportWidth();
1758 if (scrollX > range)
1762 else if (scrollX < -range)
1768 // Both scrolling and resizing change viewport ranges: scrolling changes
1769 // both start and end points, but resize only changes end values.
1770 // Here we only want to fastpaint on a scroll, with resize using a normal
1771 // paint, so scroll events are identified as changes to the horizontal or
1772 // vertical start value.
1774 // scroll - startres and endres both change
1775 if (eventName.equals(ViewportRanges.STARTRES))
1777 if (av.getWrapAlignment())
1779 fastPaintWrapped(scrollX);
1783 fastPaint(scrollX, 0);
1786 else if (eventName.equals(ViewportRanges.STARTSEQ))
1789 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1794 * Does a minimal update of the image for a scroll movement. This method
1795 * handles scroll movements of up to one width of the wrapped alignment (one
1796 * click in the vertical scrollbar). Larger movements (for example after a
1797 * scroll to highlight a mapped position) trigger a full redraw instead.
1800 * number of positions scrolled (right if positive, left if negative)
1802 protected void fastPaintWrapped(int scrollX)
1804 ViewportRanges ranges = av.getRanges();
1806 if (Math.abs(scrollX) > ranges.getViewportWidth())
1809 * shift of more than one view width is
1810 * overcomplicated to handle in this method
1817 if (fastpainting || gg == null)
1823 fastpainting = true;
1827 calculateWrappedGeometry(getWidth(), getHeight());
1830 * relocate the regions of the alignment that are still visible
1832 shiftWrappedAlignment(-scrollX);
1835 * add new columns (sequence, annotation)
1836 * - at top left if scrollX < 0
1837 * - at right of last two widths if scrollX > 0
1841 int startRes = ranges.getStartRes();
1842 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1843 - scrollX - 1, getHeight());
1847 fastPaintWrappedAddRight(scrollX);
1851 * draw all scales (if shown) and hidden column markers
1853 drawWrappedDecorators(gg, ranges.getStartRes());
1858 fastpainting = false;
1863 * Draws the specified number of columns at the 'end' (bottom right) of a
1864 * wrapped alignment view, including sequences and annotations if shown, but
1865 * not scales. Also draws the same number of columns at the right hand end of
1866 * the second last width shown, if the last width is not full height (so
1867 * cannot simply be copied from the graphics image).
1871 protected void fastPaintWrappedAddRight(int columns)
1878 ViewportRanges ranges = av.getRanges();
1879 int viewportWidth = ranges.getViewportWidth();
1880 int charWidth = av.getCharWidth();
1883 * draw full height alignment in the second last row, last columns, if the
1884 * last row was not full height
1886 int visibleWidths = wrappedVisibleWidths;
1887 int canvasHeight = getHeight();
1888 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1890 if (lastWidthPartHeight)
1892 int widthsAbove = Math.max(0, visibleWidths - 2);
1893 int ypos = wrappedRepeatHeightPx * widthsAbove
1894 + wrappedSpaceAboveAlignment;
1895 int endRes = ranges.getEndRes();
1896 endRes += widthsAbove * viewportWidth;
1897 int startRes = endRes - columns;
1898 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1902 * white fill first to erase annotations
1904 gg.translate(xOffset, 0);
1905 gg.setColor(Color.white);
1906 gg.fillRect(labelWidthWest, ypos,
1907 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1908 gg.translate(-xOffset, 0);
1910 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1914 * draw newly visible columns in last wrapped width (none if we
1915 * have reached the end of the alignment)
1916 * y-offset for drawing last width is height of widths above,
1919 int widthsAbove = visibleWidths - 1;
1920 int ypos = wrappedRepeatHeightPx * widthsAbove
1921 + wrappedSpaceAboveAlignment;
1922 int endRes = ranges.getEndRes();
1923 endRes += widthsAbove * viewportWidth;
1924 int startRes = endRes - columns + 1;
1927 * white fill first to erase annotations
1929 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1931 gg.translate(xOffset, 0);
1932 gg.setColor(Color.white);
1933 int width = viewportWidth * charWidth - xOffset;
1934 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1935 gg.translate(-xOffset, 0);
1937 gg.setFont(av.getFont());
1938 gg.setColor(Color.black);
1940 if (startRes < ranges.getVisibleAlignmentWidth())
1942 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1946 * and finally, white fill any space below the visible alignment
1948 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1949 if (heightBelow > 0)
1951 gg.setColor(Color.white);
1952 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1957 * Shifts the visible alignment by the specified number of columns - left if
1958 * negative, right if positive. Copies and moves sequences and annotations (if
1959 * shown). Scales, hidden column markers and any newly visible columns must be
1964 protected void shiftWrappedAlignment(int positions)
1970 int charWidth = av.getCharWidth();
1972 int canvasHeight = getHeight();
1973 ViewportRanges ranges = av.getRanges();
1974 int viewportWidth = ranges.getViewportWidth();
1975 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1977 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1978 int xMax = ranges.getVisibleAlignmentWidth();
1983 * shift right (after scroll left)
1984 * for each wrapped width (starting with the last), copy (width-positions)
1985 * columns from the left margin to the right margin, and copy positions
1986 * columns from the right margin of the row above (if any) to the
1987 * left margin of the current row
1991 * get y-offset of last wrapped width, first row of sequences
1993 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1994 y += wrappedSpaceAboveAlignment;
1995 int copyFromLeftStart = labelWidthWest;
1996 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2000 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2001 positions * charWidth, 0);
2004 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2005 positions * charWidth, heightToCopy, -widthToCopy,
2006 wrappedRepeatHeightPx);
2009 y -= wrappedRepeatHeightPx;
2015 * shift left (after scroll right)
2016 * for each wrapped width (starting with the first), copy (width-positions)
2017 * columns from the right margin to the left margin, and copy positions
2018 * columns from the left margin of the row below (if any) to the
2019 * right margin of the current row
2021 int xpos = av.getRanges().getStartRes();
2022 int y = wrappedSpaceAboveAlignment;
2023 int copyFromRightStart = labelWidthWest - positions * charWidth;
2025 while (y < canvasHeight)
2027 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2028 positions * charWidth, 0);
2029 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2030 && (xpos + viewportWidth <= xMax))
2032 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2033 * charWidth, heightToCopy, widthToCopy,
2034 -wrappedRepeatHeightPx);
2037 y += wrappedRepeatHeightPx;
2038 xpos += viewportWidth;
2045 * Redraws any positions in the search results in the visible region of a
2046 * wrapped alignment. Any highlights are drawn depending on the search results
2047 * set on the Viewport, not the <code>results</code> argument. This allows
2048 * this method to be called either to clear highlights (passing the previous
2049 * search results), or to draw new highlights.
2054 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2056 if (results == null)
2060 int charHeight = av.getCharHeight();
2062 boolean matchFound = false;
2064 calculateWrappedGeometry(getWidth(), getHeight());
2065 int wrappedWidth = av.getWrappedWidth();
2066 int wrappedHeight = wrappedRepeatHeightPx;
2068 ViewportRanges ranges = av.getRanges();
2069 int canvasHeight = getHeight();
2070 int repeats = canvasHeight / wrappedHeight;
2071 if (canvasHeight / wrappedHeight > 0)
2076 int firstVisibleColumn = ranges.getStartRes();
2077 int lastVisibleColumn = ranges.getStartRes() + repeats
2078 * ranges.getViewportWidth() - 1;
2080 AlignmentI alignment = av.getAlignment();
2081 if (av.hasHiddenColumns())
2083 firstVisibleColumn = alignment.getHiddenColumns()
2084 .adjustForHiddenColumns(firstVisibleColumn);
2085 lastVisibleColumn = alignment.getHiddenColumns()
2086 .adjustForHiddenColumns(lastVisibleColumn);
2089 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2091 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2092 .getEndSeq(); seqNo++)
2094 SequenceI seq = alignment.getSequenceAt(seqNo);
2096 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2098 if (visibleResults != null)
2100 for (int i = 0; i < visibleResults.length - 1; i += 2)
2102 int firstMatchedColumn = visibleResults[i];
2103 int lastMatchedColumn = visibleResults[i + 1];
2104 if (firstMatchedColumn <= lastVisibleColumn
2105 && lastMatchedColumn >= firstVisibleColumn)
2108 * found a search results match in the visible region
2110 firstMatchedColumn = Math.max(firstMatchedColumn,
2111 firstVisibleColumn);
2112 lastMatchedColumn = Math.min(lastMatchedColumn,
2116 * draw each mapped position separately (as contiguous positions may
2117 * wrap across lines)
2119 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2121 int displayColumn = mappedPos;
2122 if (av.hasHiddenColumns())
2124 displayColumn = alignment.getHiddenColumns()
2125 .findColumnPosition(displayColumn);
2129 * transX: offset from left edge of canvas to residue position
2131 int transX = labelWidthWest
2132 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2133 * av.getCharWidth();
2136 * transY: offset from top edge of canvas to residue position
2138 int transY = gapHeight;
2139 transY += (displayColumn - ranges.getStartRes())
2140 / wrappedWidth * wrappedHeight;
2141 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2144 * yOffset is from graphics origin to start of visible region
2146 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2147 if (transY < getHeight())
2150 gg.translate(transX, transY);
2151 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2153 gg.translate(-transX, -transY);
2165 * Answers the width in pixels of the left scale labels (0 if not shown)
2169 int getLabelWidthWest()
2171 return labelWidthWest;