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);
415 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
416 ranges.getStartSeq(), ranges.getEndSeq());
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)
501 // clone the cached image
502 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
504 Graphics2D g2d = lcimg.createGraphics();
505 g2d.drawImage(img, 0, 0, null);
507 // overlay selection group on lcimg
508 if (selectImage != null)
511 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
512 g2d.drawImage(selectImage, 0, 0, this);
521 * Set up a buffered image of the correct height and size for the sequence canvas
523 private BufferedImage setupImage()
525 BufferedImage lcimg = null;
527 int charWidth = av.getCharWidth();
528 int charHeight = av.getCharHeight();
530 int width = getWidth();
531 int height = getHeight();
533 width -= (width % charWidth);
534 height -= (height % charHeight);
536 if ((width < 1) || (height < 1))
543 lcimg = new BufferedImage(width, height,
544 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
545 } catch (OutOfMemoryError er)
549 "Group image OutOfMemory Redraw Error.\n" + er);
550 new OOMWarning("Creating alignment image for display", er);
559 * Returns the visible width of the canvas in residues, after allowing for
560 * East or West scales (if shown)
563 * the width in pixels (possibly including scales)
567 public int getWrappedCanvasWidth(int canvasWidth)
569 int charWidth = av.getCharWidth();
571 FontMetrics fm = getFontMetrics(av.getFont());
575 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
577 labelWidth = getLabelWidth(fm);
580 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
582 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
584 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
588 * Returns a pixel width sufficient to show the largest sequence coordinate
589 * (end position) in the alignment, calculated as the FontMetrics width of
590 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
591 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
592 * half a character width space on either side.
597 protected int getLabelWidth(FontMetrics fm)
600 * find the biggest sequence end position we need to show
601 * (note this is not necessarily the sequence length)
604 AlignmentI alignment = av.getAlignment();
605 for (int i = 0; i < alignment.getHeight(); i++)
607 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
611 for (int i = maxWidth; i > 0; i /= 10)
616 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
620 * Draws as many widths of a wrapped alignment as can fit in the visible
625 * available width in pixels
626 * @param canvasHeight
627 * available height in pixels
629 * the first column (0...) of the alignment to draw
631 public void drawWrappedPanel(Graphics g, int canvasWidth,
632 int canvasHeight, final int startColumn)
634 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
637 av.setWrappedWidth(wrappedWidthInResidues);
639 ViewportRanges ranges = av.getRanges();
640 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
643 * draw one width at a time (including any scales or annotation shown),
644 * until we have run out of either alignment or vertical space available
646 int ypos = wrappedSpaceAboveAlignment;
647 int maxWidth = ranges.getVisibleAlignmentWidth();
649 int start = startColumn;
650 int currentWidth = 0;
651 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
654 .min(maxWidth, start + wrappedWidthInResidues - 1);
655 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
656 ypos += wrappedRepeatHeightPx;
657 start += wrappedWidthInResidues;
661 drawWrappedDecorators(g, startColumn);
665 * Calculates and saves values needed when rendering a wrapped alignment.
666 * These depend on many factors, including
668 * <li>canvas width and height</li>
669 * <li>number of visible sequences, and height of annotations if shown</li>
670 * <li>font and character width</li>
671 * <li>whether scales are shown left, right or above the alignment</li>
675 * @param canvasHeight
676 * @return the number of residue columns in each width
678 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
680 int charHeight = av.getCharHeight();
683 * vertical space in pixels between wrapped widths of alignment
684 * - one character height, or two if scale above is drawn
686 wrappedSpaceAboveAlignment = charHeight
687 * (av.getScaleAboveWrapped() ? 2 : 1);
690 * height in pixels of the wrapped widths
692 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
694 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
696 // add annotations panel height if shown
697 wrappedRepeatHeightPx += getAnnotationHeight();
700 * number of visible widths (the last one may be part height),
701 * ensuring a part height includes at least one sequence
703 ViewportRanges ranges = av.getRanges();
704 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
705 int remainder = canvasHeight % wrappedRepeatHeightPx;
706 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
708 wrappedVisibleWidths++;
712 * compute width in residues; this also sets East and West label widths
714 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
717 * limit visibleWidths to not exceed width of alignment
719 int xMax = ranges.getVisibleAlignmentWidth();
720 int startToEnd = xMax - ranges.getStartRes();
721 int maxWidths = startToEnd / wrappedWidthInResidues;
722 if (startToEnd % wrappedWidthInResidues > 0)
726 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
728 return wrappedWidthInResidues;
732 * Draws one width of a wrapped alignment, including sequences and
733 * annnotations, if shown, but not scales or hidden column markers
739 * @param canvasHeight
741 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
742 int endColumn, int canvasHeight)
744 ViewportRanges ranges = av.getRanges();
745 int viewportWidth = ranges.getViewportWidth();
747 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
750 * move right before drawing by the width of the scale left (if any)
751 * plus column offset from left margin (usually zero, but may be non-zero
752 * when fast painting is drawing just a few columns)
754 int charWidth = av.getCharWidth();
755 int xOffset = labelWidthWest
756 + ((startColumn - ranges.getStartRes()) % viewportWidth)
758 g.translate(xOffset, 0);
760 // When printing we have an extra clipped region,
761 // the Printable page which we need to account for here
762 Shape clip = g.getClip();
766 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
770 g.setClip(0, (int) clip.getBounds().getY(),
771 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
775 * white fill the region to be drawn (so incremental fast paint doesn't
776 * scribble over an existing image)
778 gg.setColor(Color.white);
779 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
780 wrappedRepeatHeightPx);
782 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
785 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
787 if (av.isShowAnnotation())
789 g.translate(0, cHeight + ypos + 3);
790 if (annotations == null)
792 annotations = new AnnotationPanel(av);
795 annotations.renderer.drawComponent(annotations, av, g, -1,
796 startColumn, endx + 1);
797 g.translate(0, -cHeight - ypos - 3);
800 g.translate(-xOffset, 0);
804 * Draws scales left, right and above (if shown), and any hidden column
805 * markers, on all widths of the wrapped alignment
810 protected void drawWrappedDecorators(Graphics g, final int startColumn)
812 int charWidth = av.getCharWidth();
814 g.setFont(av.getFont());
815 g.setColor(Color.black);
817 int ypos = wrappedSpaceAboveAlignment;
818 ViewportRanges ranges = av.getRanges();
819 int viewportWidth = ranges.getViewportWidth();
820 int maxWidth = ranges.getVisibleAlignmentWidth();
822 int startCol = startColumn;
824 while (widthsDrawn < wrappedVisibleWidths)
826 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
828 if (av.getScaleLeftWrapped())
830 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
833 if (av.getScaleRightWrapped())
835 int x = labelWidthWest + viewportWidth * charWidth;
837 drawVerticalScale(g, startCol, endColumn, ypos, false);
842 * white fill region of scale above and hidden column markers
843 * (to support incremental fast paint of image)
845 g.translate(labelWidthWest, 0);
846 g.setColor(Color.white);
847 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
848 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
849 g.setColor(Color.black);
850 g.translate(-labelWidthWest, 0);
852 g.translate(labelWidthWest, 0);
854 if (av.getScaleAboveWrapped())
856 drawNorthScale(g, startCol, endColumn, ypos);
859 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
861 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
864 g.translate(-labelWidthWest, 0);
866 ypos += wrappedRepeatHeightPx;
867 startCol += viewportWidth;
873 * Draws markers (triangles) above hidden column positions between startColumn
881 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
882 int startColumn, int endColumn)
884 int charHeight = av.getCharHeight();
885 int charWidth = av.getCharWidth();
887 g.setColor(Color.blue);
888 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
889 List<Integer> positions = hidden.findHiddenRegionPositions();
890 for (int pos : positions)
892 int res = pos - startColumn;
894 if (res < 0 || res > endColumn - startColumn + 1)
900 * draw a downward-pointing triangle at the hidden columns location
901 * (before the following visible column)
903 int xMiddle = res * charWidth;
904 int[] xPoints = new int[] { xMiddle - charHeight / 4,
905 xMiddle + charHeight / 4, xMiddle };
906 int yTop = ypos - (charHeight / 2);
907 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
908 g.fillPolygon(xPoints, yPoints, 3);
913 * Draw a selection group over a wrapped alignment
915 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
917 int canvasHeight, int startRes)
919 int charHeight = av.getCharHeight();
920 int charWidth = av.getCharWidth();
922 // height gap above each panel
923 int hgap = charHeight;
924 if (av.getScaleAboveWrapped())
929 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
931 int cHeight = av.getAlignment().getHeight() * charHeight;
933 int startx = startRes;
935 int ypos = hgap; // vertical offset
936 int maxwidth = av.getAlignment().getWidth();
938 if (av.hasHiddenColumns())
940 maxwidth = av.getAlignment().getHiddenColumns()
941 .findColumnPosition(maxwidth);
944 // chop the wrapped alignment extent up into panel-sized blocks and treat
945 // each block as if it were a block from an unwrapped alignment
946 while ((ypos <= canvasHeight) && (startx < maxwidth))
948 // set end value to be start + width, or maxwidth, whichever is smaller
949 endx = startx + cWidth - 1;
956 g.translate(labelWidthWest, 0);
958 drawUnwrappedSelection(g, group, startx, endx, 0,
959 av.getAlignment().getHeight() - 1,
962 g.translate(-labelWidthWest, 0);
964 // update vertical offset
965 ypos += cHeight + getAnnotationHeight() + hgap;
967 // update horizontal offset
972 int getAnnotationHeight()
974 if (!av.isShowAnnotation())
979 if (annotations == null)
981 annotations = new AnnotationPanel(av);
984 return annotations.adjustPanelHeight();
988 * Draws the visible region of the alignment on the graphics context. If there
989 * are hidden column markers in the visible region, then each sub-region
990 * between the markers is drawn separately, followed by the hidden column
994 * the graphics context, positioned at the first residue to be drawn
996 * offset of the first column to draw (0..)
998 * offset of the last column to draw (0..)
1000 * offset of the first sequence to draw (0..)
1002 * offset of the last sequence to draw (0..)
1004 * vertical offset at which to draw (for wrapped alignments)
1006 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1007 final int startSeq, final int endSeq, final int yOffset)
1009 int charHeight = av.getCharHeight();
1010 int charWidth = av.getCharWidth();
1012 if (!av.hasHiddenColumns())
1014 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1019 final int screenYMax = endRes - startRes;
1020 int blockStart = startRes;
1021 int blockEnd = endRes;
1023 for (int[] region : av.getAlignment().getHiddenColumns()
1024 .getHiddenColumnsCopy())
1026 int hideStart = region[0];
1027 int hideEnd = region[1];
1029 if (hideStart <= blockStart)
1031 blockStart += (hideEnd - hideStart) + 1;
1036 * draw up to just before the next hidden region, or the end of
1037 * the visible region, whichever comes first
1039 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1042 g1.translate(screenY * charWidth, 0);
1044 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1047 * draw the downline of the hidden column marker (ScalePanel draws the
1048 * triangle on top) if we reached it
1050 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1052 g1.setColor(Color.blue);
1054 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1055 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1056 (endSeq - startSeq + 1) * charHeight + yOffset);
1059 g1.translate(-screenY * charWidth, 0);
1060 screenY += blockEnd - blockStart + 1;
1061 blockStart = hideEnd + 1;
1063 if (screenY > screenYMax)
1065 // already rendered last block
1070 if (screenY <= screenYMax)
1072 // remaining visible region to render
1073 blockEnd = blockStart + screenYMax - screenY;
1074 g1.translate(screenY * charWidth, 0);
1075 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1077 g1.translate(-screenY * charWidth, 0);
1084 * Draws a region of the visible alignment
1088 * offset of the first column in the visible region (0..)
1090 * offset of the last column in the visible region (0..)
1092 * offset of the first sequence in the visible region (0..)
1094 * offset of the last sequence in the visible region (0..)
1096 * vertical offset at which to draw (for wrapped alignments)
1098 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1099 int endSeq, int offset)
1101 int charHeight = av.getCharHeight();
1102 int charWidth = av.getCharWidth();
1104 g.setFont(av.getFont());
1105 seqRdr.prepare(g, av.isRenderGaps());
1109 // / First draw the sequences
1110 // ///////////////////////////
1111 for (int i = startSeq; i <= endSeq; i++)
1113 nextSeq = av.getAlignment().getSequenceAt(i);
1114 if (nextSeq == null)
1116 // occasionally, a race condition occurs such that the alignment row is
1120 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1121 startRes, endRes, offset + ((i - startSeq) * charHeight));
1123 if (av.isShowSequenceFeatures())
1125 fr.drawSequence(g, nextSeq, startRes, endRes,
1126 offset + ((i - startSeq) * charHeight), false);
1130 * highlight search Results once sequence has been drawn
1132 if (av.hasSearchResults())
1134 SearchResultsI searchResults = av.getSearchResults();
1135 int[] visibleResults = searchResults.getResults(nextSeq,
1137 if (visibleResults != null)
1139 for (int r = 0; r < visibleResults.length; r += 2)
1141 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1142 visibleResults[r + 1], (visibleResults[r] - startRes)
1144 + ((i - startSeq) * charHeight));
1150 if (av.getSelectionGroup() != null
1151 || av.getAlignment().getGroups().size() > 0)
1153 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1158 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1159 int startSeq, int endSeq, int offset)
1161 Graphics2D g = (Graphics2D) g1;
1163 // ///////////////////////////////////
1164 // Now outline any areas if necessary
1165 // ///////////////////////////////////
1167 SequenceGroup group = null;
1168 int groupIndex = -1;
1170 if (av.getAlignment().getGroups().size() > 0)
1172 group = av.getAlignment().getGroups().get(0);
1178 g.setStroke(new BasicStroke());
1179 g.setColor(group.getOutlineColour());
1183 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1188 g.setStroke(new BasicStroke());
1190 if (groupIndex >= av.getAlignment().getGroups().size())
1195 group = av.getAlignment().getGroups().get(groupIndex);
1197 } while (groupIndex < av.getAlignment().getGroups().size());
1205 * Draw the selection group as a separate image and overlay
1207 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1208 int startSeq, int endSeq)
1210 // get a new image of the correct size
1211 BufferedImage selectionImage = setupImage();
1213 if (selectionImage == null)
1218 SequenceGroup group = av.getSelectionGroup();
1225 // set up drawing colour
1226 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1228 setupSelectionGroup(g, selectionImage);
1230 if (!av.getWrapAlignment())
1232 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1237 drawWrappedSelection(g, group, getWidth(), getHeight(),
1238 av.getRanges().getStartRes());
1242 return selectionImage;
1246 * Draw the cursor as a separate image and overlay
1249 * start residue of area to draw cursor in
1251 * end residue of area to draw cursor in
1253 * start sequence of area to draw cursor in
1255 * end sequence of are to draw cursor in
1256 * @return a transparent image of the same size as the sequence canvas, with
1257 * the cursor drawn on it, if any
1259 private void drawCursor(Graphics g, int startRes, int endRes,
1263 // convert the cursorY into a position on the visible alignment
1264 int cursor_ypos = av.getAlignment().getHiddenSequences()
1265 .findIndexWithoutHiddenSeqs(cursorY);
1267 // don't do work unless we have to
1268 if (av.cursorMode && cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1272 int startx = startRes;
1275 // convert the cursorX into a position on the visible alignment
1276 int cursor_xpos = av.getAlignment().getHiddenColumns()
1277 .findColumnPosition(cursorX);
1279 if (av.getWrapAlignment())
1281 // work out the correct offsets for the cursor
1282 int charHeight = av.getCharHeight();
1283 int charWidth = av.getCharWidth();
1284 int canvasWidth = getWidth();
1285 int canvasHeight = getHeight();
1287 // height gap above each panel
1288 int hgap = charHeight;
1289 if (av.getScaleAboveWrapped())
1294 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1296 int cHeight = av.getAlignment().getHeight() * charHeight;
1298 endx = startx + cWidth - 1;
1299 int ypos = hgap; // vertical offset
1301 // iterate down the wrapped panels
1302 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1304 // update vertical offset
1305 ypos += cHeight + getAnnotationHeight() + hgap;
1307 // update horizontal offset
1309 endx = startx + cWidth - 1;
1312 xoffset = labelWidthWest;
1315 // now check if cursor is within range for x values
1316 if (cursor_xpos >= startx && cursor_xpos <= endx)
1318 // get the character the cursor is drawn at
1319 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1320 char s = seq.getCharAt(cursorX);
1322 seqRdr.drawCursor(g, s,
1323 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1324 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1332 * Set up graphics for selection group
1334 private void setupSelectionGroup(Graphics2D g,
1335 BufferedImage selectionImage)
1337 // set background to transparent
1338 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1339 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1341 // set up foreground to draw red dashed line
1342 g.setComposite(AlphaComposite.Src);
1343 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1344 BasicStroke.JOIN_ROUND, 3f, new float[]
1346 g.setColor(Color.RED);
1350 * Draw a selection group over an unwrapped alignment
1351 * @param g graphics object to draw with
1352 * @param group selection group
1353 * @param startRes start residue of area to draw
1354 * @param endRes end residue of area to draw
1355 * @param startSeq start sequence of area to draw
1356 * @param endSeq end sequence of area to draw
1357 * @param offset vertical offset (used when called from wrapped alignment code)
1359 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1360 int startRes, int endRes, int startSeq, int endSeq, int offset)
1362 int charWidth = av.getCharWidth();
1364 if (!av.hasHiddenColumns())
1366 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1371 // package into blocks of visible columns
1373 int blockStart = startRes;
1374 int blockEnd = endRes;
1376 for (int[] region : av.getAlignment().getHiddenColumns()
1377 .getHiddenColumnsCopy())
1379 int hideStart = region[0];
1380 int hideEnd = region[1];
1382 if (hideStart <= blockStart)
1384 blockStart += (hideEnd - hideStart) + 1;
1388 blockEnd = hideStart - 1;
1390 g.translate(screenY * charWidth, 0);
1391 drawPartialGroupOutline(g, group,
1392 blockStart, blockEnd, startSeq, endSeq, offset);
1394 g.translate(-screenY * charWidth, 0);
1395 screenY += blockEnd - blockStart + 1;
1396 blockStart = hideEnd + 1;
1398 if (screenY > (endRes - startRes))
1400 // already rendered last block
1405 if (screenY <= (endRes - startRes))
1407 // remaining visible region to render
1408 blockEnd = blockStart + (endRes - startRes) - screenY;
1409 g.translate(screenY * charWidth, 0);
1410 drawPartialGroupOutline(g, group,
1411 blockStart, blockEnd, startSeq, endSeq, offset);
1413 g.translate(-screenY * charWidth, 0);
1419 * Draw the selection group as a separate image and overlay
1421 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1422 int startRes, int endRes, int startSeq, int endSeq,
1425 int charHeight = av.getCharHeight();
1426 int charWidth = av.getCharWidth();
1428 int visWidth = (endRes - startRes + 1) * charWidth;
1432 boolean inGroup = false;
1440 for (i = startSeq; i <= endSeq; i++)
1442 // position of start residue of group relative to startRes, in pixels
1443 sx = (group.getStartRes() - startRes) * charWidth;
1445 // width of group in pixels
1446 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1449 sy = verticalOffset + (i - startSeq) * charHeight;
1451 if (sx + xwidth < 0 || sx > visWidth)
1456 if ((sx <= (endRes - startRes) * charWidth)
1457 && group.getSequences(null)
1458 .contains(av.getAlignment().getSequenceAt(i)))
1460 if ((bottom == -1) && !group.getSequences(null)
1461 .contains(av.getAlignment().getSequenceAt(i + 1)))
1463 bottom = sy + charHeight;
1468 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1469 .contains(av.getAlignment().getSequenceAt(i - 1)))
1482 // if start position is visible, draw vertical line to left of
1484 if (sx >= 0 && sx < visWidth)
1486 g.drawLine(sx, oldY, sx, sy);
1489 // if end position is visible, draw vertical line to right of
1491 if (sx + xwidth < visWidth)
1493 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1502 // don't let width extend beyond current block, or group extent
1504 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1506 xwidth = (endRes - startRes + 1) * charWidth - sx;
1509 // draw horizontal line at top of group
1512 g.drawLine(sx, top, sx + xwidth, top);
1516 // draw horizontal line at bottom of group
1519 g.drawLine(sx, bottom, sx + xwidth, bottom);
1530 sy = verticalOffset + ((i - startSeq) * charHeight);
1531 if (sx >= 0 && sx < visWidth)
1533 g.drawLine(sx, oldY, sx, sy);
1536 if (sx + xwidth < visWidth)
1538 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1547 if (sx + xwidth > visWidth)
1551 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1553 xwidth = (endRes - startRes + 1) * charWidth;
1558 g.drawLine(sx, top, sx + xwidth, top);
1564 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1573 * Highlights search results in the visible region by rendering as white text
1574 * on a black background. Any previous highlighting is removed. Answers true
1575 * if any highlight was left on the visible alignment (so status bar should be
1576 * set to match), else false.
1578 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1579 * alignment had to be scrolled to show the highlighted region, then it should
1580 * be fully redrawn, otherwise a fast paint can be performed. This argument
1581 * could be removed if fast paint of scrolled wrapped alignment is coded in
1582 * future (JAL-2609).
1585 * @param noFastPaint
1588 public boolean highlightSearchResults(SearchResultsI results,
1589 boolean noFastPaint)
1595 boolean wrapped = av.getWrapAlignment();
1598 fastPaint = !noFastPaint;
1599 fastpainting = fastPaint;
1602 * to avoid redrawing the whole visible region, we instead
1603 * redraw just the minimal regions to remove previous highlights
1606 SearchResultsI previous = av.getSearchResults();
1607 av.setSearchResults(results);
1608 boolean redrawn = false;
1609 boolean drawn = false;
1612 redrawn = drawMappedPositionsWrapped(previous);
1613 drawn = drawMappedPositionsWrapped(results);
1618 redrawn = drawMappedPositions(previous);
1619 drawn = drawMappedPositions(results);
1624 * if highlights were either removed or added, repaint
1632 * return true only if highlights were added
1638 fastpainting = false;
1643 * Redraws the minimal rectangle in the visible region (if any) that includes
1644 * mapped positions of the given search results. Whether or not positions are
1645 * highlighted depends on the SearchResults set on the Viewport. This allows
1646 * this method to be called to either clear or set highlighting. Answers true
1647 * if any positions were drawn (in which case a repaint is still required),
1653 protected boolean drawMappedPositions(SearchResultsI results)
1655 if (results == null)
1661 * calculate the minimal rectangle to redraw that
1662 * includes both new and existing search results
1664 int firstSeq = Integer.MAX_VALUE;
1666 int firstCol = Integer.MAX_VALUE;
1668 boolean matchFound = false;
1670 ViewportRanges ranges = av.getRanges();
1671 int firstVisibleColumn = ranges.getStartRes();
1672 int lastVisibleColumn = ranges.getEndRes();
1673 AlignmentI alignment = av.getAlignment();
1674 if (av.hasHiddenColumns())
1676 firstVisibleColumn = alignment.getHiddenColumns()
1677 .adjustForHiddenColumns(firstVisibleColumn);
1678 lastVisibleColumn = alignment.getHiddenColumns()
1679 .adjustForHiddenColumns(lastVisibleColumn);
1682 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1683 .getEndSeq(); seqNo++)
1685 SequenceI seq = alignment.getSequenceAt(seqNo);
1687 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1689 if (visibleResults != null)
1691 for (int i = 0; i < visibleResults.length - 1; i += 2)
1693 int firstMatchedColumn = visibleResults[i];
1694 int lastMatchedColumn = visibleResults[i + 1];
1695 if (firstMatchedColumn <= lastVisibleColumn
1696 && lastMatchedColumn >= firstVisibleColumn)
1699 * found a search results match in the visible region -
1700 * remember the first and last sequence matched, and the first
1701 * and last visible columns in the matched positions
1704 firstSeq = Math.min(firstSeq, seqNo);
1705 lastSeq = Math.max(lastSeq, seqNo);
1706 firstMatchedColumn = Math.max(firstMatchedColumn,
1707 firstVisibleColumn);
1708 lastMatchedColumn = Math.min(lastMatchedColumn,
1710 firstCol = Math.min(firstCol, firstMatchedColumn);
1711 lastCol = Math.max(lastCol, lastMatchedColumn);
1719 if (av.hasHiddenColumns())
1721 firstCol = alignment.getHiddenColumns()
1722 .findColumnPosition(firstCol);
1723 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1725 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1726 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1727 gg.translate(transX, transY);
1728 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1729 gg.translate(-transX, -transY);
1736 public void propertyChange(PropertyChangeEvent evt)
1738 String eventName = evt.getPropertyName();
1740 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1748 if (eventName.equals(ViewportRanges.STARTRES))
1750 // Make sure we're not trying to draw a panel
1751 // larger than the visible window
1752 ViewportRanges vpRanges = av.getRanges();
1753 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1754 int range = vpRanges.getViewportWidth();
1755 if (scrollX > range)
1759 else if (scrollX < -range)
1765 // Both scrolling and resizing change viewport ranges: scrolling changes
1766 // both start and end points, but resize only changes end values.
1767 // Here we only want to fastpaint on a scroll, with resize using a normal
1768 // paint, so scroll events are identified as changes to the horizontal or
1769 // vertical start value.
1771 // scroll - startres and endres both change
1772 if (eventName.equals(ViewportRanges.STARTRES))
1774 if (av.getWrapAlignment())
1776 fastPaintWrapped(scrollX);
1780 fastPaint(scrollX, 0);
1783 else if (eventName.equals(ViewportRanges.STARTSEQ))
1786 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1791 * Does a minimal update of the image for a scroll movement. This method
1792 * handles scroll movements of up to one width of the wrapped alignment (one
1793 * click in the vertical scrollbar). Larger movements (for example after a
1794 * scroll to highlight a mapped position) trigger a full redraw instead.
1797 * number of positions scrolled (right if positive, left if negative)
1799 protected void fastPaintWrapped(int scrollX)
1801 ViewportRanges ranges = av.getRanges();
1803 if (Math.abs(scrollX) > ranges.getViewportWidth())
1806 * shift of more than one view width is
1807 * overcomplicated to handle in this method
1814 if (fastpainting || gg == null)
1820 fastpainting = true;
1824 calculateWrappedGeometry(getWidth(), getHeight());
1827 * relocate the regions of the alignment that are still visible
1829 shiftWrappedAlignment(-scrollX);
1832 * add new columns (sequence, annotation)
1833 * - at top left if scrollX < 0
1834 * - at right of last two widths if scrollX > 0
1838 int startRes = ranges.getStartRes();
1839 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1840 - scrollX - 1, getHeight());
1844 fastPaintWrappedAddRight(scrollX);
1848 * draw all scales (if shown) and hidden column markers
1850 drawWrappedDecorators(gg, ranges.getStartRes());
1855 fastpainting = false;
1860 * Draws the specified number of columns at the 'end' (bottom right) of a
1861 * wrapped alignment view, including sequences and annotations if shown, but
1862 * not scales. Also draws the same number of columns at the right hand end of
1863 * the second last width shown, if the last width is not full height (so
1864 * cannot simply be copied from the graphics image).
1868 protected void fastPaintWrappedAddRight(int columns)
1875 ViewportRanges ranges = av.getRanges();
1876 int viewportWidth = ranges.getViewportWidth();
1877 int charWidth = av.getCharWidth();
1880 * draw full height alignment in the second last row, last columns, if the
1881 * last row was not full height
1883 int visibleWidths = wrappedVisibleWidths;
1884 int canvasHeight = getHeight();
1885 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1887 if (lastWidthPartHeight)
1889 int widthsAbove = Math.max(0, visibleWidths - 2);
1890 int ypos = wrappedRepeatHeightPx * widthsAbove
1891 + wrappedSpaceAboveAlignment;
1892 int endRes = ranges.getEndRes();
1893 endRes += widthsAbove * viewportWidth;
1894 int startRes = endRes - columns;
1895 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1899 * white fill first to erase annotations
1901 gg.translate(xOffset, 0);
1902 gg.setColor(Color.white);
1903 gg.fillRect(labelWidthWest, ypos,
1904 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1905 gg.translate(-xOffset, 0);
1907 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1911 * draw newly visible columns in last wrapped width (none if we
1912 * have reached the end of the alignment)
1913 * y-offset for drawing last width is height of widths above,
1916 int widthsAbove = visibleWidths - 1;
1917 int ypos = wrappedRepeatHeightPx * widthsAbove
1918 + wrappedSpaceAboveAlignment;
1919 int endRes = ranges.getEndRes();
1920 endRes += widthsAbove * viewportWidth;
1921 int startRes = endRes - columns + 1;
1924 * white fill first to erase annotations
1926 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1928 gg.translate(xOffset, 0);
1929 gg.setColor(Color.white);
1930 int width = viewportWidth * charWidth - xOffset;
1931 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1932 gg.translate(-xOffset, 0);
1934 gg.setFont(av.getFont());
1935 gg.setColor(Color.black);
1937 if (startRes < ranges.getVisibleAlignmentWidth())
1939 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1943 * and finally, white fill any space below the visible alignment
1945 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1946 if (heightBelow > 0)
1948 gg.setColor(Color.white);
1949 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1954 * Shifts the visible alignment by the specified number of columns - left if
1955 * negative, right if positive. Copies and moves sequences and annotations (if
1956 * shown). Scales, hidden column markers and any newly visible columns must be
1961 protected void shiftWrappedAlignment(int positions)
1967 int charWidth = av.getCharWidth();
1969 int canvasHeight = getHeight();
1970 ViewportRanges ranges = av.getRanges();
1971 int viewportWidth = ranges.getViewportWidth();
1972 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1974 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1975 int xMax = ranges.getVisibleAlignmentWidth();
1980 * shift right (after scroll left)
1981 * for each wrapped width (starting with the last), copy (width-positions)
1982 * columns from the left margin to the right margin, and copy positions
1983 * columns from the right margin of the row above (if any) to the
1984 * left margin of the current row
1988 * get y-offset of last wrapped width, first row of sequences
1990 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1991 y += wrappedSpaceAboveAlignment;
1992 int copyFromLeftStart = labelWidthWest;
1993 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1997 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1998 positions * charWidth, 0);
2001 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2002 positions * charWidth, heightToCopy, -widthToCopy,
2003 wrappedRepeatHeightPx);
2006 y -= wrappedRepeatHeightPx;
2012 * shift left (after scroll right)
2013 * for each wrapped width (starting with the first), copy (width-positions)
2014 * columns from the right margin to the left margin, and copy positions
2015 * columns from the left margin of the row below (if any) to the
2016 * right margin of the current row
2018 int xpos = av.getRanges().getStartRes();
2019 int y = wrappedSpaceAboveAlignment;
2020 int copyFromRightStart = labelWidthWest - positions * charWidth;
2022 while (y < canvasHeight)
2024 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2025 positions * charWidth, 0);
2026 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2027 && (xpos + viewportWidth <= xMax))
2029 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2030 * charWidth, heightToCopy, widthToCopy,
2031 -wrappedRepeatHeightPx);
2034 y += wrappedRepeatHeightPx;
2035 xpos += viewportWidth;
2042 * Redraws any positions in the search results in the visible region of a
2043 * wrapped alignment. Any highlights are drawn depending on the search results
2044 * set on the Viewport, not the <code>results</code> argument. This allows
2045 * this method to be called either to clear highlights (passing the previous
2046 * search results), or to draw new highlights.
2051 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2053 if (results == null)
2057 int charHeight = av.getCharHeight();
2059 boolean matchFound = false;
2061 calculateWrappedGeometry(getWidth(), getHeight());
2062 int wrappedWidth = av.getWrappedWidth();
2063 int wrappedHeight = wrappedRepeatHeightPx;
2065 ViewportRanges ranges = av.getRanges();
2066 int canvasHeight = getHeight();
2067 int repeats = canvasHeight / wrappedHeight;
2068 if (canvasHeight / wrappedHeight > 0)
2073 int firstVisibleColumn = ranges.getStartRes();
2074 int lastVisibleColumn = ranges.getStartRes() + repeats
2075 * ranges.getViewportWidth() - 1;
2077 AlignmentI alignment = av.getAlignment();
2078 if (av.hasHiddenColumns())
2080 firstVisibleColumn = alignment.getHiddenColumns()
2081 .adjustForHiddenColumns(firstVisibleColumn);
2082 lastVisibleColumn = alignment.getHiddenColumns()
2083 .adjustForHiddenColumns(lastVisibleColumn);
2086 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2088 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2089 .getEndSeq(); seqNo++)
2091 SequenceI seq = alignment.getSequenceAt(seqNo);
2093 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2095 if (visibleResults != null)
2097 for (int i = 0; i < visibleResults.length - 1; i += 2)
2099 int firstMatchedColumn = visibleResults[i];
2100 int lastMatchedColumn = visibleResults[i + 1];
2101 if (firstMatchedColumn <= lastVisibleColumn
2102 && lastMatchedColumn >= firstVisibleColumn)
2105 * found a search results match in the visible region
2107 firstMatchedColumn = Math.max(firstMatchedColumn,
2108 firstVisibleColumn);
2109 lastMatchedColumn = Math.min(lastMatchedColumn,
2113 * draw each mapped position separately (as contiguous positions may
2114 * wrap across lines)
2116 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2118 int displayColumn = mappedPos;
2119 if (av.hasHiddenColumns())
2121 displayColumn = alignment.getHiddenColumns()
2122 .findColumnPosition(displayColumn);
2126 * transX: offset from left edge of canvas to residue position
2128 int transX = labelWidthWest
2129 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2130 * av.getCharWidth();
2133 * transY: offset from top edge of canvas to residue position
2135 int transY = gapHeight;
2136 transY += (displayColumn - ranges.getStartRes())
2137 / wrappedWidth * wrappedHeight;
2138 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2141 * yOffset is from graphics origin to start of visible region
2143 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2144 if (transY < getHeight())
2147 gg.translate(transX, transY);
2148 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2150 gg.translate(-transX, -transY);
2162 * Answers the width in pixels of the left scale labels (0 if not shown)
2166 int getLabelWidthWest()
2168 return labelWidthWest;