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.Transparency;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.List;
48 import javax.swing.JComponent;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 public class SeqCanvas extends JComponent implements ViewportListenerI
58 private static final String ZEROS = "0000000000";
60 final FeatureRenderer fr;
70 private final SequenceRenderer seqRdr;
72 private boolean fastPaint = false;
74 private boolean fastpainting = false;
76 private AnnotationPanel annotations;
79 * measurements for drawing a wrapped alignment
81 private int labelWidthEast; // label right width in pixels if shown
83 private int labelWidthWest; // label left width in pixels if shown
85 private int wrappedSpaceAboveAlignment; // gap between widths
87 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
89 private int wrappedVisibleWidths; // number of wrapped widths displayed
91 private Graphics2D gg;
94 * Creates a new SeqCanvas object.
98 public SeqCanvas(AlignmentPanel ap)
101 fr = new FeatureRenderer(ap);
102 seqRdr = new SequenceRenderer(av);
103 setLayout(new BorderLayout());
104 PaintRefresher.Register(this, av.getSequenceSetId());
105 setBackground(Color.white);
107 av.getRanges().addPropertyChangeListener(this);
110 public SequenceRenderer getSequenceRenderer()
115 public FeatureRenderer getFeatureRenderer()
121 * Draws the scale above a region of a wrapped alignment, consisting of a
122 * column number every major interval (10 columns).
125 * the graphics context to draw on, positioned at the start (bottom
126 * left) of the line on which to draw any scale marks
128 * start alignment column (0..)
130 * end alignment column (0..)
132 * y offset to draw at
134 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 int charHeight = av.getCharHeight();
137 int charWidth = av.getCharWidth();
140 * white fill the scale space (for the fastPaint case)
142 g.setColor(Color.white);
143 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
144 charHeight * 3 / 2 + 2);
145 g.setColor(Color.black);
147 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
149 for (ScaleMark mark : marks)
151 int mpos = mark.column; // (i - startx - 1)
156 String mstring = mark.text;
162 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
166 * draw a tick mark below the column number, centred on the column;
167 * height of tick mark is 4 pixels less than half a character
169 int xpos = (mpos * charWidth) + (charWidth / 2);
170 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
176 * Draw the scale to the left or right of a wrapped alignment
179 * graphics context, positioned at the start of the scale to be drawn
181 * first column of wrapped width (0.. excluding any hidden columns)
183 * last column of wrapped width (0.. excluding any hidden columns)
185 * vertical offset at which to begin the scale
187 * if true, scale is left of residues, if false, scale is right
189 void drawVerticalScale(Graphics g, final int startx, final int endx,
190 final int ypos, final boolean left)
192 int charHeight = av.getCharHeight();
193 int charWidth = av.getCharWidth();
195 int yPos = ypos + charHeight;
199 if (av.hasHiddenColumns())
201 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
202 startX = hiddenColumns.adjustForHiddenColumns(startx);
203 endX = hiddenColumns.adjustForHiddenColumns(endx);
205 FontMetrics fm = getFontMetrics(av.getFont());
207 for (int i = 0; i < av.getAlignment().getHeight(); i++)
209 SequenceI seq = av.getAlignment().getSequenceAt(i);
212 * find sequence position of first non-gapped position -
213 * to the right if scale left, to the left if scale right
215 int index = left ? startX : endX;
217 while (index >= startX && index <= endX)
219 if (!Comparison.isGap(seq.getCharAt(index)))
221 value = seq.findPosition(index);
235 * white fill the space for the scale
237 g.setColor(Color.white);
238 int y = (yPos + (i * charHeight)) - (charHeight / 5);
239 // fillRect origin is top left of rectangle
240 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
246 * draw scale value, right justified within its width less half a
247 * character width padding on the right
249 int labelSpace = left ? labelWidthWest : labelWidthEast;
250 labelSpace -= charWidth / 2; // leave space to the right
251 String valueAsString = String.valueOf(value);
252 int labelLength = fm.stringWidth(valueAsString);
253 int xOffset = labelSpace - labelLength;
254 g.setColor(Color.black);
255 g.drawString(valueAsString, xOffset, y);
261 * Does a fast paint of an alignment in response to a scroll. Most of the
262 * visible region is simply copied and shifted, and then any newly visible
263 * columns or rows are drawn. The scroll may be horizontal or vertical, but
264 * not both at once. Scrolling may be the result of
266 * <li>dragging a scroll bar</li>
267 * <li>clicking in the scroll bar</li>
268 * <li>scrolling by trackpad, middle mouse button, or other device</li>
269 * <li>by moving the box in the Overview window</li>
270 * <li>programmatically to make a highlighted position visible</li>
274 * columns to shift right (positive) or left (negative)
276 * rows to shift down (positive) or up (negative)
278 public void fastPaint(int horizontal, int vertical)
280 if (fastpainting || gg == null || img == null)
289 int charHeight = av.getCharHeight();
290 int charWidth = av.getCharWidth();
292 ViewportRanges ranges = av.getRanges();
293 int startRes = ranges.getStartRes();
294 int endRes = ranges.getEndRes();
295 int startSeq = ranges.getStartSeq();
296 int endSeq = ranges.getEndSeq();
300 gg.copyArea(horizontal * charWidth, vertical * charHeight,
301 img.getWidth(), img.getHeight(), -horizontal * charWidth,
302 -vertical * charHeight);
304 if (horizontal > 0) // scrollbar pulled right, image to the left
306 transX = (endRes - startRes - horizontal) * charWidth;
307 startRes = endRes - horizontal;
309 else if (horizontal < 0)
311 endRes = startRes - horizontal;
314 if (vertical > 0) // scroll down
316 startSeq = endSeq - vertical;
318 if (startSeq < ranges.getStartSeq())
319 { // ie scrolling too fast, more than a page at a time
320 startSeq = ranges.getStartSeq();
324 transY = img.getHeight() - ((vertical + 1) * charHeight);
327 else if (vertical < 0)
329 endSeq = startSeq - vertical;
331 if (endSeq > ranges.getEndSeq())
333 endSeq = ranges.getEndSeq();
337 gg.translate(transX, transY);
338 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
339 gg.translate(-transX, -transY);
341 // Call repaint on alignment panel so that repaints from other alignment
342 // panel components can be aggregated. Otherwise performance of the
344 // window and others may be adversely affected.
345 av.getAlignPanel().repaint();
348 fastpainting = false;
353 public void paintComponent(Graphics g)
355 super.paintComponent(g);
357 int charHeight = av.getCharHeight();
358 int charWidth = av.getCharWidth();
360 ViewportRanges ranges = av.getRanges();
362 int width = getWidth();
363 int height = getHeight();
365 width -= (width % charWidth);
366 height -= (height % charHeight);
368 // selectImage is the selection group outline image
369 BufferedImage selectImage = drawSelectionGroup(
370 ranges.getStartRes(), ranges.getEndRes(),
371 ranges.getStartSeq(), ranges.getEndSeq());
373 if ((img != null) && (fastPaint
374 || (getVisibleRect().width != g.getClipBounds().width)
375 || (getVisibleRect().height != g.getClipBounds().height)))
377 BufferedImage lcimg = buildLocalImage(selectImage);
378 g.drawImage(lcimg, 0, 0, this);
381 else if ((width > 0) && (height > 0))
383 // img is a cached version of the last view we drew, if any
384 // if we have no img or the size has changed, make a new one
385 if (img == null || width != img.getWidth()
386 || height != img.getHeight())
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 // lcimg is a local *copy* of img which we'll draw selectImage on top of
417 BufferedImage lcimg = buildLocalImage(selectImage);
418 g.drawImage(lcimg, 0, 0, this);
423 * Draw an alignment panel for printing
426 * Graphics object to draw with
428 * start residue of print area
430 * end residue of print area
432 * start sequence of print area
434 * end sequence of print area
436 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
437 int startSeq, int endSeq)
439 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
441 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
443 if (selectImage != null)
445 ((Graphics2D) g1).setComposite(AlphaComposite
446 .getInstance(AlphaComposite.SRC_OVER));
447 g1.drawImage(selectImage, 0, 0, this);
452 * Draw a wrapped alignment panel for printing
455 * Graphics object to draw with
457 * width of drawing area
458 * @param canvasHeight
459 * height of drawing area
461 * start residue of print area
463 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
464 int canvasHeight, int startRes)
466 SequenceGroup group = av.getSelectionGroup();
468 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
472 BufferedImage selectImage = null;
475 selectImage = new BufferedImage(canvasWidth, canvasHeight,
476 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
477 } catch (OutOfMemoryError er)
480 System.err.println("Print image OutOfMemory Error.\n" + er);
481 new OOMWarning("Creating wrapped alignment image for printing", er);
483 if (selectImage != null)
485 Graphics2D g2 = selectImage.createGraphics();
486 setupSelectionGroup(g2, selectImage);
487 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
491 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
492 g.drawImage(selectImage, 0, 0, this);
499 * Make a local image by combining the cached image img
502 private BufferedImage buildLocalImage(BufferedImage selectImage)
504 // clone the cached image
505 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
508 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
510 Graphics2D g2d = lcimg.createGraphics();
511 g2d.drawImage(img, 0, 0, null);
513 // overlay selection group on lcimg
514 if (selectImage != null)
517 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
518 g2d.drawImage(selectImage, 0, 0, this);
526 * Set up a buffered image of the correct height and size for the sequence canvas
528 private BufferedImage setupImage()
530 BufferedImage lcimg = null;
532 int charWidth = av.getCharWidth();
533 int charHeight = av.getCharHeight();
535 int width = getWidth();
536 int height = getHeight();
538 width -= (width % charWidth);
539 height -= (height % charHeight);
541 if ((width < 1) || (height < 1))
548 lcimg = new BufferedImage(width, height,
549 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
550 } catch (OutOfMemoryError er)
554 "Group image OutOfMemory Redraw Error.\n" + er);
555 new OOMWarning("Creating alignment image for display", er);
564 * Returns the visible width of the canvas in residues, after allowing for
565 * East or West scales (if shown)
568 * the width in pixels (possibly including scales)
572 public int getWrappedCanvasWidth(int canvasWidth)
574 int charWidth = av.getCharWidth();
576 FontMetrics fm = getFontMetrics(av.getFont());
580 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
582 labelWidth = getLabelWidth(fm);
585 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
587 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
589 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
593 * Returns a pixel width sufficient to show the largest sequence coordinate
594 * (end position) in the alignment, calculated as the FontMetrics width of
595 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
596 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
597 * half a character width space on either side.
602 protected int getLabelWidth(FontMetrics fm)
605 * find the biggest sequence end position we need to show
606 * (note this is not necessarily the sequence length)
609 AlignmentI alignment = av.getAlignment();
610 for (int i = 0; i < alignment.getHeight(); i++)
612 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
616 for (int i = maxWidth; i > 0; i /= 10)
621 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
625 * Draws as many widths of a wrapped alignment as can fit in the visible
630 * available width in pixels
631 * @param canvasHeight
632 * available height in pixels
634 * the first column (0...) of the alignment to draw
636 public void drawWrappedPanel(Graphics g, int canvasWidth,
637 int canvasHeight, final int startColumn)
639 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
642 av.setWrappedWidth(wrappedWidthInResidues);
644 ViewportRanges ranges = av.getRanges();
645 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
648 * draw one width at a time (including any scales or annotation shown),
649 * until we have run out of either alignment or vertical space available
651 int ypos = wrappedSpaceAboveAlignment;
652 int maxWidth = ranges.getVisibleAlignmentWidth();
654 int start = startColumn;
655 int currentWidth = 0;
656 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
659 .min(maxWidth, start + wrappedWidthInResidues - 1);
660 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
661 ypos += wrappedRepeatHeightPx;
662 start += wrappedWidthInResidues;
666 drawWrappedDecorators(g, startColumn);
670 * Calculates and saves values needed when rendering a wrapped alignment.
671 * These depend on many factors, including
673 * <li>canvas width and height</li>
674 * <li>number of visible sequences, and height of annotations if shown</li>
675 * <li>font and character width</li>
676 * <li>whether scales are shown left, right or above the alignment</li>
680 * @param canvasHeight
681 * @return the number of residue columns in each width
683 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
685 int charHeight = av.getCharHeight();
688 * vertical space in pixels between wrapped widths of alignment
689 * - one character height, or two if scale above is drawn
691 wrappedSpaceAboveAlignment = charHeight
692 * (av.getScaleAboveWrapped() ? 2 : 1);
695 * height in pixels of the wrapped widths
697 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
699 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
701 // add annotations panel height if shown
702 wrappedRepeatHeightPx += getAnnotationHeight();
705 * number of visible widths (the last one may be part height),
706 * ensuring a part height includes at least one sequence
708 ViewportRanges ranges = av.getRanges();
709 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
710 int remainder = canvasHeight % wrappedRepeatHeightPx;
711 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
713 wrappedVisibleWidths++;
717 * compute width in residues; this also sets East and West label widths
719 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
722 * limit visibleWidths to not exceed width of alignment
724 int xMax = ranges.getVisibleAlignmentWidth();
725 int startToEnd = xMax - ranges.getStartRes();
726 int maxWidths = startToEnd / wrappedWidthInResidues;
727 if (startToEnd % wrappedWidthInResidues > 0)
731 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
733 return wrappedWidthInResidues;
737 * Draws one width of a wrapped alignment, including sequences and
738 * annnotations, if shown, but not scales or hidden column markers
744 * @param canvasHeight
746 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
747 int endColumn, int canvasHeight)
749 ViewportRanges ranges = av.getRanges();
750 int viewportWidth = ranges.getViewportWidth();
752 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
755 * move right before drawing by the width of the scale left (if any)
756 * plus column offset from left margin (usually zero, but may be non-zero
757 * when fast painting is drawing just a few columns)
759 int charWidth = av.getCharWidth();
760 int xOffset = labelWidthWest
761 + ((startColumn - ranges.getStartRes()) % viewportWidth)
763 g.translate(xOffset, 0);
765 // When printing we have an extra clipped region,
766 // the Printable page which we need to account for here
767 Shape clip = g.getClip();
771 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
775 g.setClip(0, (int) clip.getBounds().getY(),
776 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
780 * white fill the region to be drawn (so incremental fast paint doesn't
781 * scribble over an existing image)
783 gg.setColor(Color.white);
784 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
785 wrappedRepeatHeightPx);
787 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
790 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
792 if (av.isShowAnnotation())
794 g.translate(0, cHeight + ypos + 3);
795 if (annotations == null)
797 annotations = new AnnotationPanel(av);
800 annotations.renderer.drawComponent(annotations, av, g, -1,
801 startColumn, endx + 1);
802 g.translate(0, -cHeight - ypos - 3);
805 g.translate(-xOffset, 0);
809 * Draws scales left, right and above (if shown), and any hidden column
810 * markers, on all widths of the wrapped alignment
815 protected void drawWrappedDecorators(Graphics g, final int startColumn)
817 int charWidth = av.getCharWidth();
819 g.setFont(av.getFont());
820 g.setColor(Color.black);
822 int ypos = wrappedSpaceAboveAlignment;
823 ViewportRanges ranges = av.getRanges();
824 int viewportWidth = ranges.getViewportWidth();
825 int maxWidth = ranges.getVisibleAlignmentWidth();
827 int startCol = startColumn;
829 while (widthsDrawn < wrappedVisibleWidths)
831 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
833 if (av.getScaleLeftWrapped())
835 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
838 if (av.getScaleRightWrapped())
840 int x = labelWidthWest + viewportWidth * charWidth;
842 drawVerticalScale(g, startCol, endColumn, ypos, false);
847 * white fill region of scale above and hidden column markers
848 * (to support incremental fast paint of image)
850 g.translate(labelWidthWest, 0);
851 g.setColor(Color.white);
852 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
853 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
854 g.setColor(Color.black);
855 g.translate(-labelWidthWest, 0);
857 g.translate(labelWidthWest, 0);
859 if (av.getScaleAboveWrapped())
861 drawNorthScale(g, startCol, endColumn, ypos);
864 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
866 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
869 g.translate(-labelWidthWest, 0);
871 ypos += wrappedRepeatHeightPx;
872 startCol += viewportWidth;
878 * Draws markers (triangles) above hidden column positions between startColumn
886 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
887 int startColumn, int endColumn)
889 int charHeight = av.getCharHeight();
890 int charWidth = av.getCharWidth();
892 g.setColor(Color.blue);
893 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
894 List<Integer> positions = hidden.findHiddenRegionPositions();
895 for (int pos : positions)
897 int res = pos - startColumn;
899 if (res < 0 || res > endColumn - startColumn + 1)
905 * draw a downward-pointing triangle at the hidden columns location
906 * (before the following visible column)
908 int xMiddle = res * charWidth;
909 int[] xPoints = new int[] { xMiddle - charHeight / 4,
910 xMiddle + charHeight / 4, xMiddle };
911 int yTop = ypos - (charHeight / 2);
912 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
913 g.fillPolygon(xPoints, yPoints, 3);
918 * Draw a selection group over a wrapped alignment
920 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
922 int canvasHeight, int startRes)
924 int charHeight = av.getCharHeight();
925 int charWidth = av.getCharWidth();
927 // height gap above each panel
928 int hgap = charHeight;
929 if (av.getScaleAboveWrapped())
934 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
936 int cHeight = av.getAlignment().getHeight() * charHeight;
938 int startx = startRes;
940 int ypos = hgap; // vertical offset
941 int maxwidth = av.getAlignment().getWidth();
943 if (av.hasHiddenColumns())
945 maxwidth = av.getAlignment().getHiddenColumns()
946 .findColumnPosition(maxwidth);
949 // chop the wrapped alignment extent up into panel-sized blocks and treat
950 // each block as if it were a block from an unwrapped alignment
951 while ((ypos <= canvasHeight) && (startx < maxwidth))
953 // set end value to be start + width, or maxwidth, whichever is smaller
954 endx = startx + cWidth - 1;
961 g.translate(labelWidthWest, 0);
963 drawUnwrappedSelection(g, group, startx, endx, 0,
964 av.getAlignment().getHeight() - 1,
967 g.translate(-labelWidthWest, 0);
969 // update vertical offset
970 ypos += cHeight + getAnnotationHeight() + hgap;
972 // update horizontal offset
977 int getAnnotationHeight()
979 if (!av.isShowAnnotation())
984 if (annotations == null)
986 annotations = new AnnotationPanel(av);
989 return annotations.adjustPanelHeight();
993 * Draws the visible region of the alignment on the graphics context. If there
994 * are hidden column markers in the visible region, then each sub-region
995 * between the markers is drawn separately, followed by the hidden column
999 * the graphics context, positioned at the first residue to be drawn
1001 * offset of the first column to draw (0..)
1003 * offset of the last column to draw (0..)
1005 * offset of the first sequence to draw (0..)
1007 * offset of the last sequence to draw (0..)
1009 * vertical offset at which to draw (for wrapped alignments)
1011 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1012 final int startSeq, final int endSeq, final int yOffset)
1014 int charHeight = av.getCharHeight();
1015 int charWidth = av.getCharWidth();
1017 if (!av.hasHiddenColumns())
1019 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1024 final int screenYMax = endRes - startRes;
1025 int blockStart = startRes;
1026 int blockEnd = endRes;
1028 for (int[] region : av.getAlignment().getHiddenColumns()
1029 .getHiddenColumnsCopy())
1031 int hideStart = region[0];
1032 int hideEnd = region[1];
1034 if (hideStart <= blockStart)
1036 blockStart += (hideEnd - hideStart) + 1;
1041 * draw up to just before the next hidden region, or the end of
1042 * the visible region, whichever comes first
1044 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1047 g1.translate(screenY * charWidth, 0);
1049 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1052 * draw the downline of the hidden column marker (ScalePanel draws the
1053 * triangle on top) if we reached it
1055 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1057 g1.setColor(Color.blue);
1059 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1060 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1061 (endSeq - startSeq + 1) * charHeight + yOffset);
1064 g1.translate(-screenY * charWidth, 0);
1065 screenY += blockEnd - blockStart + 1;
1066 blockStart = hideEnd + 1;
1068 if (screenY > screenYMax)
1070 // already rendered last block
1075 if (screenY <= screenYMax)
1077 // remaining visible region to render
1078 blockEnd = blockStart + screenYMax - screenY;
1079 g1.translate(screenY * charWidth, 0);
1080 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1082 g1.translate(-screenY * charWidth, 0);
1089 * Draws a region of the visible alignment
1093 * offset of the first column in the visible region (0..)
1095 * offset of the last column in the visible region (0..)
1097 * offset of the first sequence in the visible region (0..)
1099 * offset of the last sequence in the visible region (0..)
1101 * vertical offset at which to draw (for wrapped alignments)
1103 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1104 int endSeq, int offset)
1106 int charHeight = av.getCharHeight();
1107 int charWidth = av.getCharWidth();
1109 g.setFont(av.getFont());
1110 seqRdr.prepare(g, av.isRenderGaps());
1114 // / First draw the sequences
1115 // ///////////////////////////
1116 for (int i = startSeq; i <= endSeq; i++)
1118 nextSeq = av.getAlignment().getSequenceAt(i);
1119 if (nextSeq == null)
1121 // occasionally, a race condition occurs such that the alignment row is
1125 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1126 startRes, endRes, offset + ((i - startSeq) * charHeight));
1128 if (av.isShowSequenceFeatures())
1130 fr.drawSequence(g, nextSeq, startRes, endRes,
1131 offset + ((i - startSeq) * charHeight), false);
1135 * highlight search Results once sequence has been drawn
1137 if (av.hasSearchResults())
1139 SearchResultsI searchResults = av.getSearchResults();
1140 int[] visibleResults = searchResults.getResults(nextSeq,
1142 if (visibleResults != null)
1144 for (int r = 0; r < visibleResults.length; r += 2)
1146 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1147 visibleResults[r + 1], (visibleResults[r] - startRes)
1149 + ((i - startSeq) * charHeight));
1154 if (av.cursorMode && cursorY == i && cursorX >= startRes
1155 && cursorX <= endRes)
1157 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1158 offset + ((i - startSeq) * charHeight));
1162 if (av.getSelectionGroup() != null
1163 || av.getAlignment().getGroups().size() > 0)
1165 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1170 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1171 int startSeq, int endSeq, int offset)
1173 Graphics2D g = (Graphics2D) g1;
1175 // ///////////////////////////////////
1176 // Now outline any areas if necessary
1177 // ///////////////////////////////////
1179 SequenceGroup group = null;
1180 int groupIndex = -1;
1182 if (av.getAlignment().getGroups().size() > 0)
1184 group = av.getAlignment().getGroups().get(0);
1190 g.setStroke(new BasicStroke());
1191 g.setColor(group.getOutlineColour());
1195 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1200 g.setStroke(new BasicStroke());
1202 if (groupIndex >= av.getAlignment().getGroups().size())
1207 group = av.getAlignment().getGroups().get(groupIndex);
1209 } while (groupIndex < av.getAlignment().getGroups().size());
1217 * Draw the selection group as a separate image and overlay
1219 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1220 int startSeq, int endSeq)
1222 // get a new image of the correct size
1223 BufferedImage selectionImage = setupImage();
1225 if (selectionImage == null)
1230 SequenceGroup group = av.getSelectionGroup();
1237 // set up drawing colour
1238 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1240 setupSelectionGroup(g, selectionImage);
1242 if (!av.getWrapAlignment())
1244 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1249 drawWrappedSelection(g, group, getWidth(), getHeight(),
1250 av.getRanges().getStartRes());
1254 return selectionImage;
1258 * Set up graphics for selection group
1260 private void setupSelectionGroup(Graphics2D g,
1261 BufferedImage selectionImage)
1263 // set background to transparent
1264 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1265 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1267 // set up foreground to draw red dashed line
1268 g.setComposite(AlphaComposite.Src);
1269 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1270 BasicStroke.JOIN_ROUND, 3f, new float[]
1272 g.setColor(Color.RED);
1276 * Draw a selection group over an unwrapped alignment
1277 * @param g graphics object to draw with
1278 * @param group selection group
1279 * @param startRes start residue of area to draw
1280 * @param endRes end residue of area to draw
1281 * @param startSeq start sequence of area to draw
1282 * @param endSeq end sequence of area to draw
1283 * @param offset vertical offset (used when called from wrapped alignment code)
1285 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1286 int startRes, int endRes, int startSeq, int endSeq, int offset)
1288 int charWidth = av.getCharWidth();
1290 if (!av.hasHiddenColumns())
1292 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1297 // package into blocks of visible columns
1299 int blockStart = startRes;
1300 int blockEnd = endRes;
1302 for (int[] region : av.getAlignment().getHiddenColumns()
1303 .getHiddenColumnsCopy())
1305 int hideStart = region[0];
1306 int hideEnd = region[1];
1308 if (hideStart <= blockStart)
1310 blockStart += (hideEnd - hideStart) + 1;
1314 blockEnd = hideStart - 1;
1316 g.translate(screenY * charWidth, 0);
1317 drawPartialGroupOutline(g, group,
1318 blockStart, blockEnd, startSeq, endSeq, offset);
1320 g.translate(-screenY * charWidth, 0);
1321 screenY += blockEnd - blockStart + 1;
1322 blockStart = hideEnd + 1;
1324 if (screenY > (endRes - startRes))
1326 // already rendered last block
1331 if (screenY <= (endRes - startRes))
1333 // remaining visible region to render
1334 blockEnd = blockStart + (endRes - startRes) - screenY;
1335 g.translate(screenY * charWidth, 0);
1336 drawPartialGroupOutline(g, group,
1337 blockStart, blockEnd, startSeq, endSeq, offset);
1339 g.translate(-screenY * charWidth, 0);
1345 * Draw the selection group as a separate image and overlay
1347 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1348 int startRes, int endRes, int startSeq, int endSeq,
1351 int charHeight = av.getCharHeight();
1352 int charWidth = av.getCharWidth();
1354 int visWidth = (endRes - startRes + 1) * charWidth;
1358 boolean inGroup = false;
1366 for (i = startSeq; i <= endSeq; i++)
1368 // position of start residue of group relative to startRes, in pixels
1369 sx = (group.getStartRes() - startRes) * charWidth;
1371 // width of group in pixels
1372 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1375 sy = verticalOffset + (i - startSeq) * charHeight;
1377 if (sx + xwidth < 0 || sx > visWidth)
1382 if ((sx <= (endRes - startRes) * charWidth)
1383 && group.getSequences(null)
1384 .contains(av.getAlignment().getSequenceAt(i)))
1386 if ((bottom == -1) && !group.getSequences(null)
1387 .contains(av.getAlignment().getSequenceAt(i + 1)))
1389 bottom = sy + charHeight;
1394 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1395 .contains(av.getAlignment().getSequenceAt(i - 1)))
1408 // if start position is visible, draw vertical line to left of
1410 if (sx >= 0 && sx < visWidth)
1412 g.drawLine(sx, oldY, sx, sy);
1415 // if end position is visible, draw vertical line to right of
1417 if (sx + xwidth < visWidth)
1419 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1428 // don't let width extend beyond current block, or group extent
1430 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1432 xwidth = (endRes - startRes + 1) * charWidth - sx;
1435 // draw horizontal line at top of group
1438 g.drawLine(sx, top, sx + xwidth, top);
1442 // draw horizontal line at bottom of group
1445 g.drawLine(sx, bottom, sx + xwidth, bottom);
1456 sy = verticalOffset + ((i - startSeq) * charHeight);
1457 if (sx >= 0 && sx < visWidth)
1459 g.drawLine(sx, oldY, sx, sy);
1462 if (sx + xwidth < visWidth)
1464 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1473 if (sx + xwidth > visWidth)
1477 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1479 xwidth = (endRes - startRes + 1) * charWidth;
1484 g.drawLine(sx, top, sx + xwidth, top);
1490 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1499 * Highlights search results in the visible region by rendering as white text
1500 * on a black background. Any previous highlighting is removed. Answers true
1501 * if any highlight was left on the visible alignment (so status bar should be
1502 * set to match), else false.
1504 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1505 * alignment had to be scrolled to show the highlighted region, then it should
1506 * be fully redrawn, otherwise a fast paint can be performed. This argument
1507 * could be removed if fast paint of scrolled wrapped alignment is coded in
1508 * future (JAL-2609).
1511 * @param noFastPaint
1514 public boolean highlightSearchResults(SearchResultsI results,
1515 boolean noFastPaint)
1521 boolean wrapped = av.getWrapAlignment();
1524 fastPaint = !noFastPaint;
1525 fastpainting = fastPaint;
1528 * to avoid redrawing the whole visible region, we instead
1529 * redraw just the minimal regions to remove previous highlights
1532 SearchResultsI previous = av.getSearchResults();
1533 av.setSearchResults(results);
1534 boolean redrawn = false;
1535 boolean drawn = false;
1538 redrawn = drawMappedPositionsWrapped(previous);
1539 drawn = drawMappedPositionsWrapped(results);
1544 redrawn = drawMappedPositions(previous);
1545 drawn = drawMappedPositions(results);
1550 * if highlights were either removed or added, repaint
1558 * return true only if highlights were added
1564 fastpainting = false;
1569 * Redraws the minimal rectangle in the visible region (if any) that includes
1570 * mapped positions of the given search results. Whether or not positions are
1571 * highlighted depends on the SearchResults set on the Viewport. This allows
1572 * this method to be called to either clear or set highlighting. Answers true
1573 * if any positions were drawn (in which case a repaint is still required),
1579 protected boolean drawMappedPositions(SearchResultsI results)
1581 if (results == null)
1587 * calculate the minimal rectangle to redraw that
1588 * includes both new and existing search results
1590 int firstSeq = Integer.MAX_VALUE;
1592 int firstCol = Integer.MAX_VALUE;
1594 boolean matchFound = false;
1596 ViewportRanges ranges = av.getRanges();
1597 int firstVisibleColumn = ranges.getStartRes();
1598 int lastVisibleColumn = ranges.getEndRes();
1599 AlignmentI alignment = av.getAlignment();
1600 if (av.hasHiddenColumns())
1602 firstVisibleColumn = alignment.getHiddenColumns()
1603 .adjustForHiddenColumns(firstVisibleColumn);
1604 lastVisibleColumn = alignment.getHiddenColumns()
1605 .adjustForHiddenColumns(lastVisibleColumn);
1608 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1609 .getEndSeq(); seqNo++)
1611 SequenceI seq = alignment.getSequenceAt(seqNo);
1613 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1615 if (visibleResults != null)
1617 for (int i = 0; i < visibleResults.length - 1; i += 2)
1619 int firstMatchedColumn = visibleResults[i];
1620 int lastMatchedColumn = visibleResults[i + 1];
1621 if (firstMatchedColumn <= lastVisibleColumn
1622 && lastMatchedColumn >= firstVisibleColumn)
1625 * found a search results match in the visible region -
1626 * remember the first and last sequence matched, and the first
1627 * and last visible columns in the matched positions
1630 firstSeq = Math.min(firstSeq, seqNo);
1631 lastSeq = Math.max(lastSeq, seqNo);
1632 firstMatchedColumn = Math.max(firstMatchedColumn,
1633 firstVisibleColumn);
1634 lastMatchedColumn = Math.min(lastMatchedColumn,
1636 firstCol = Math.min(firstCol, firstMatchedColumn);
1637 lastCol = Math.max(lastCol, lastMatchedColumn);
1645 if (av.hasHiddenColumns())
1647 firstCol = alignment.getHiddenColumns()
1648 .findColumnPosition(firstCol);
1649 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1651 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1652 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1653 gg.translate(transX, transY);
1654 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1655 gg.translate(-transX, -transY);
1662 public void propertyChange(PropertyChangeEvent evt)
1664 String eventName = evt.getPropertyName();
1666 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1674 if (eventName.equals(ViewportRanges.STARTRES)
1675 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1677 // Make sure we're not trying to draw a panel
1678 // larger than the visible window
1679 if (eventName.equals(ViewportRanges.STARTRES))
1681 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1685 scrollX = ((int[]) evt.getNewValue())[0]
1686 - ((int[]) evt.getOldValue())[0];
1688 ViewportRanges vpRanges = av.getRanges();
1690 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1691 if (scrollX > range)
1695 else if (scrollX < -range)
1700 // Both scrolling and resizing change viewport ranges: scrolling changes
1701 // both start and end points, but resize only changes end values.
1702 // Here we only want to fastpaint on a scroll, with resize using a normal
1703 // paint, so scroll events are identified as changes to the horizontal or
1704 // vertical start value.
1705 if (eventName.equals(ViewportRanges.STARTRES))
1707 if (av.getWrapAlignment())
1709 fastPaintWrapped(scrollX);
1713 fastPaint(scrollX, 0);
1716 else if (eventName.equals(ViewportRanges.STARTSEQ))
1719 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1721 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1723 fastPaint(scrollX, 0);
1724 // bizarrely, we only need to scroll on the x value here as fastpaint
1725 // copies the full height of the image anyway. Passing in the y value
1726 // causes nasty repaint artefacts, which only disappear on a full
1733 * Does a minimal update of the image for a scroll movement. This method
1734 * handles scroll movements of up to one width of the wrapped alignment (one
1735 * click in the vertical scrollbar). Larger movements (for example after a
1736 * scroll to highlight a mapped position) trigger a full redraw instead.
1739 * number of positions scrolled (right if positive, left if negative)
1741 protected void fastPaintWrapped(int scrollX)
1743 ViewportRanges ranges = av.getRanges();
1745 if (Math.abs(scrollX) > ranges.getViewportWidth())
1748 * shift of more than one view width is
1749 * overcomplicated to handle in this method
1756 if (fastpainting || gg == null)
1762 fastpainting = true;
1766 calculateWrappedGeometry(getWidth(), getHeight());
1769 * relocate the regions of the alignment that are still visible
1771 shiftWrappedAlignment(-scrollX);
1774 * add new columns (sequence, annotation)
1775 * - at top left if scrollX < 0
1776 * - at right of last two widths if scrollX > 0
1780 int startRes = ranges.getStartRes();
1781 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1782 - scrollX - 1, getHeight());
1786 fastPaintWrappedAddRight(scrollX);
1790 * draw all scales (if shown) and hidden column markers
1792 drawWrappedDecorators(gg, ranges.getStartRes());
1797 fastpainting = false;
1802 * Draws the specified number of columns at the 'end' (bottom right) of a
1803 * wrapped alignment view, including sequences and annotations if shown, but
1804 * not scales. Also draws the same number of columns at the right hand end of
1805 * the second last width shown, if the last width is not full height (so
1806 * cannot simply be copied from the graphics image).
1810 protected void fastPaintWrappedAddRight(int columns)
1817 ViewportRanges ranges = av.getRanges();
1818 int viewportWidth = ranges.getViewportWidth();
1819 int charWidth = av.getCharWidth();
1822 * draw full height alignment in the second last row, last columns, if the
1823 * last row was not full height
1825 int visibleWidths = wrappedVisibleWidths;
1826 int canvasHeight = getHeight();
1827 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1829 if (lastWidthPartHeight)
1831 int widthsAbove = Math.max(0, visibleWidths - 2);
1832 int ypos = wrappedRepeatHeightPx * widthsAbove
1833 + wrappedSpaceAboveAlignment;
1834 int endRes = ranges.getEndRes();
1835 endRes += widthsAbove * viewportWidth;
1836 int startRes = endRes - columns;
1837 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1841 * white fill first to erase annotations
1843 gg.translate(xOffset, 0);
1844 gg.setColor(Color.white);
1845 gg.fillRect(labelWidthWest, ypos,
1846 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1847 gg.translate(-xOffset, 0);
1849 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1853 * draw newly visible columns in last wrapped width (none if we
1854 * have reached the end of the alignment)
1855 * y-offset for drawing last width is height of widths above,
1858 int widthsAbove = visibleWidths - 1;
1859 int ypos = wrappedRepeatHeightPx * widthsAbove
1860 + wrappedSpaceAboveAlignment;
1861 int endRes = ranges.getEndRes();
1862 endRes += widthsAbove * viewportWidth;
1863 int startRes = endRes - columns + 1;
1866 * white fill first to erase annotations
1868 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1870 gg.translate(xOffset, 0);
1871 gg.setColor(Color.white);
1872 int width = viewportWidth * charWidth - xOffset;
1873 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1874 gg.translate(-xOffset, 0);
1876 gg.setFont(av.getFont());
1877 gg.setColor(Color.black);
1879 if (startRes < ranges.getVisibleAlignmentWidth())
1881 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1885 * and finally, white fill any space below the visible alignment
1887 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1888 if (heightBelow > 0)
1890 gg.setColor(Color.white);
1891 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1896 * Shifts the visible alignment by the specified number of columns - left if
1897 * negative, right if positive. Copies and moves sequences and annotations (if
1898 * shown). Scales, hidden column markers and any newly visible columns must be
1903 protected void shiftWrappedAlignment(int positions)
1909 int charWidth = av.getCharWidth();
1911 int canvasHeight = getHeight();
1912 ViewportRanges ranges = av.getRanges();
1913 int viewportWidth = ranges.getViewportWidth();
1914 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1916 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1917 int xMax = ranges.getVisibleAlignmentWidth();
1922 * shift right (after scroll left)
1923 * for each wrapped width (starting with the last), copy (width-positions)
1924 * columns from the left margin to the right margin, and copy positions
1925 * columns from the right margin of the row above (if any) to the
1926 * left margin of the current row
1930 * get y-offset of last wrapped width, first row of sequences
1932 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1933 y += wrappedSpaceAboveAlignment;
1934 int copyFromLeftStart = labelWidthWest;
1935 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1939 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1940 positions * charWidth, 0);
1943 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1944 positions * charWidth, heightToCopy, -widthToCopy,
1945 wrappedRepeatHeightPx);
1948 y -= wrappedRepeatHeightPx;
1954 * shift left (after scroll right)
1955 * for each wrapped width (starting with the first), copy (width-positions)
1956 * columns from the right margin to the left margin, and copy positions
1957 * columns from the left margin of the row below (if any) to the
1958 * right margin of the current row
1960 int xpos = av.getRanges().getStartRes();
1961 int y = wrappedSpaceAboveAlignment;
1962 int copyFromRightStart = labelWidthWest - positions * charWidth;
1964 while (y < canvasHeight)
1966 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1967 positions * charWidth, 0);
1968 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1969 && (xpos + viewportWidth <= xMax))
1971 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1972 * charWidth, heightToCopy, widthToCopy,
1973 -wrappedRepeatHeightPx);
1976 y += wrappedRepeatHeightPx;
1977 xpos += viewportWidth;
1984 * Redraws any positions in the search results in the visible region of a
1985 * wrapped alignment. Any highlights are drawn depending on the search results
1986 * set on the Viewport, not the <code>results</code> argument. This allows
1987 * this method to be called either to clear highlights (passing the previous
1988 * search results), or to draw new highlights.
1993 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1995 if (results == null)
1999 int charHeight = av.getCharHeight();
2001 boolean matchFound = false;
2003 calculateWrappedGeometry(getWidth(), getHeight());
2004 int wrappedWidth = av.getWrappedWidth();
2005 int wrappedHeight = wrappedRepeatHeightPx;
2007 ViewportRanges ranges = av.getRanges();
2008 int canvasHeight = getHeight();
2009 int repeats = canvasHeight / wrappedHeight;
2010 if (canvasHeight / wrappedHeight > 0)
2015 int firstVisibleColumn = ranges.getStartRes();
2016 int lastVisibleColumn = ranges.getStartRes() + repeats
2017 * ranges.getViewportWidth() - 1;
2019 AlignmentI alignment = av.getAlignment();
2020 if (av.hasHiddenColumns())
2022 firstVisibleColumn = alignment.getHiddenColumns()
2023 .adjustForHiddenColumns(firstVisibleColumn);
2024 lastVisibleColumn = alignment.getHiddenColumns()
2025 .adjustForHiddenColumns(lastVisibleColumn);
2028 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2030 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2031 .getEndSeq(); seqNo++)
2033 SequenceI seq = alignment.getSequenceAt(seqNo);
2035 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2037 if (visibleResults != null)
2039 for (int i = 0; i < visibleResults.length - 1; i += 2)
2041 int firstMatchedColumn = visibleResults[i];
2042 int lastMatchedColumn = visibleResults[i + 1];
2043 if (firstMatchedColumn <= lastVisibleColumn
2044 && lastMatchedColumn >= firstVisibleColumn)
2047 * found a search results match in the visible region
2049 firstMatchedColumn = Math.max(firstMatchedColumn,
2050 firstVisibleColumn);
2051 lastMatchedColumn = Math.min(lastMatchedColumn,
2055 * draw each mapped position separately (as contiguous positions may
2056 * wrap across lines)
2058 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2060 int displayColumn = mappedPos;
2061 if (av.hasHiddenColumns())
2063 displayColumn = alignment.getHiddenColumns()
2064 .findColumnPosition(displayColumn);
2068 * transX: offset from left edge of canvas to residue position
2070 int transX = labelWidthWest
2071 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2072 * av.getCharWidth();
2075 * transY: offset from top edge of canvas to residue position
2077 int transY = gapHeight;
2078 transY += (displayColumn - ranges.getStartRes())
2079 / wrappedWidth * wrappedHeight;
2080 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2083 * yOffset is from graphics origin to start of visible region
2085 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2086 if (transY < getHeight())
2089 gg.translate(transX, transY);
2090 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2092 gg.translate(-transX, -transY);
2104 * Answers the width in pixels of the left scale labels (0 if not shown)
2108 int getLabelWidthWest()
2110 return labelWidthWest;