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;
46 import java.util.function.Consumer;
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
343 // overview window and others may be adversely affected.
344 av.getAlignPanel().repaint();
347 fastpainting = false;
352 public void paintComponent(Graphics g)
354 super.paintComponent(g);
356 int charHeight = av.getCharHeight();
357 int charWidth = av.getCharWidth();
359 ViewportRanges ranges = av.getRanges();
361 int width = getWidth();
362 int height = getHeight();
364 width -= (width % charWidth);
365 height -= (height % charHeight);
367 // selectImage is the selection group outline image
368 BufferedImage selectImage = drawSelectionGroup(
369 ranges.getStartRes(), ranges.getEndRes(),
370 ranges.getStartSeq(), ranges.getEndSeq());
372 if ((img != null) && (fastPaint
373 || (getVisibleRect().width != g.getClipBounds().width)
374 || (getVisibleRect().height != g.getClipBounds().height)))
376 BufferedImage lcimg = buildLocalImage(selectImage);
377 g.drawImage(lcimg, 0, 0, this);
380 else if ((width > 0) && (height > 0))
382 // img is a cached version of the last view we drew, if any
383 // if we have no img or the size has changed, make a new one
384 if (img == null || width != img.getWidth()
385 || height != img.getHeight())
392 gg = (Graphics2D) img.getGraphics();
393 gg.setFont(av.getFont());
398 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
399 RenderingHints.VALUE_ANTIALIAS_ON);
402 gg.setColor(Color.white);
403 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
405 if (av.getWrapAlignment())
407 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
411 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
412 ranges.getStartSeq(), ranges.getEndSeq(), 0);
415 // lcimg is a local *copy* of img which we'll draw selectImage on top of
416 BufferedImage lcimg = buildLocalImage(selectImage);
417 g.drawImage(lcimg, 0, 0, this);
422 * Draw an alignment panel for printing
425 * Graphics object to draw with
427 * start residue of print area
429 * end residue of print area
431 * start sequence of print area
433 * end sequence of print area
435 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
436 int startSeq, int endSeq)
438 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
440 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
442 if (selectImage != null)
444 ((Graphics2D) g1).setComposite(AlphaComposite
445 .getInstance(AlphaComposite.SRC_OVER));
446 g1.drawImage(selectImage, 0, 0, this);
451 * Draw a wrapped alignment panel for printing
454 * Graphics object to draw with
456 * width of drawing area
457 * @param canvasHeight
458 * height of drawing area
460 * start residue of print area
462 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
463 int canvasHeight, int startRes)
465 SequenceGroup group = av.getSelectionGroup();
467 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
471 BufferedImage selectImage = null;
474 selectImage = new BufferedImage(canvasWidth, canvasHeight,
475 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
476 } catch (OutOfMemoryError er)
479 System.err.println("Print image OutOfMemory Error.\n" + er);
480 new OOMWarning("Creating wrapped alignment image for printing", er);
482 if (selectImage != null)
484 Graphics2D g2 = selectImage.createGraphics();
485 setupSelectionGroup(g2, selectImage);
486 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
490 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
491 g.drawImage(selectImage, 0, 0, this);
498 * Make a local image by combining the cached image img
501 private BufferedImage buildLocalImage(BufferedImage selectImage)
503 // clone the cached image
504 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
507 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
509 Graphics2D g2d = lcimg.createGraphics();
510 g2d.drawImage(img, 0, 0, null);
512 // overlay selection group on lcimg
513 if (selectImage != null)
516 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
517 g2d.drawImage(selectImage, 0, 0, this);
525 * Set up a buffered image of the correct height and size for the sequence canvas
527 private BufferedImage setupImage()
529 BufferedImage lcimg = null;
531 int charWidth = av.getCharWidth();
532 int charHeight = av.getCharHeight();
534 int width = getWidth();
535 int height = getHeight();
537 width -= (width % charWidth);
538 height -= (height % charHeight);
540 if ((width < 1) || (height < 1))
547 lcimg = new BufferedImage(width, height,
548 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
549 } catch (OutOfMemoryError er)
553 "Group image OutOfMemory Redraw Error.\n" + er);
554 new OOMWarning("Creating alignment image for display", er);
563 * Returns the visible width of the canvas in residues, after allowing for
564 * East or West scales (if shown)
567 * the width in pixels (possibly including scales)
571 public int getWrappedCanvasWidth(int canvasWidth)
573 int charWidth = av.getCharWidth();
575 FontMetrics fm = getFontMetrics(av.getFont());
579 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
581 labelWidth = getLabelWidth(fm);
584 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
586 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
588 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
592 * Returns a pixel width sufficient to show the largest sequence coordinate
593 * (end position) in the alignment, calculated as the FontMetrics width of
594 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
595 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
596 * half a character width space on either side.
601 protected int getLabelWidth(FontMetrics fm)
604 * find the biggest sequence end position we need to show
605 * (note this is not necessarily the sequence length)
608 AlignmentI alignment = av.getAlignment();
609 for (int i = 0; i < alignment.getHeight(); i++)
611 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
615 for (int i = maxWidth; i > 0; i /= 10)
620 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
624 * Draws as many widths of a wrapped alignment as can fit in the visible
629 * available width in pixels
630 * @param canvasHeight
631 * available height in pixels
633 * the first column (0...) of the alignment to draw
635 public void drawWrappedPanel(Graphics g, int canvasWidth,
636 int canvasHeight, final int startColumn)
638 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
641 av.setWrappedWidth(wrappedWidthInResidues);
643 ViewportRanges ranges = av.getRanges();
644 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
647 * draw one width at a time (including any scales or annotation shown),
648 * until we have run out of either alignment or vertical space available
650 int ypos = wrappedSpaceAboveAlignment;
651 int maxWidth = ranges.getVisibleAlignmentWidth();
653 int start = startColumn;
654 int currentWidth = 0;
655 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
658 .min(maxWidth, start + wrappedWidthInResidues - 1);
659 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
660 ypos += wrappedRepeatHeightPx;
661 start += wrappedWidthInResidues;
665 drawWrappedDecorators(g, startColumn);
669 * Calculates and saves values needed when rendering a wrapped alignment.
670 * These depend on many factors, including
672 * <li>canvas width and height</li>
673 * <li>number of visible sequences, and height of annotations if shown</li>
674 * <li>font and character width</li>
675 * <li>whether scales are shown left, right or above the alignment</li>
679 * @param canvasHeight
680 * @return the number of residue columns in each width
682 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
684 int charHeight = av.getCharHeight();
687 * vertical space in pixels between wrapped widths of alignment
688 * - one character height, or two if scale above is drawn
690 wrappedSpaceAboveAlignment = charHeight
691 * (av.getScaleAboveWrapped() ? 2 : 1);
694 * height in pixels of the wrapped widths
696 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
698 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
700 // add annotations panel height if shown
701 wrappedRepeatHeightPx += getAnnotationHeight();
704 * number of visible widths (the last one may be part height),
705 * ensuring a part height includes at least one sequence
707 ViewportRanges ranges = av.getRanges();
708 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
709 int remainder = canvasHeight % wrappedRepeatHeightPx;
710 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
712 wrappedVisibleWidths++;
716 * compute width in residues; this also sets East and West label widths
718 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
721 * limit visibleWidths to not exceed width of alignment
723 int xMax = ranges.getVisibleAlignmentWidth();
724 int startToEnd = xMax - ranges.getStartRes();
725 int maxWidths = startToEnd / wrappedWidthInResidues;
726 if (startToEnd % wrappedWidthInResidues > 0)
730 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
732 return wrappedWidthInResidues;
736 * Draws one width of a wrapped alignment, including sequences and
737 * annnotations, if shown, but not scales or hidden column markers
743 * @param canvasHeight
745 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
746 int endColumn, int canvasHeight)
748 ViewportRanges ranges = av.getRanges();
749 int viewportWidth = ranges.getViewportWidth();
751 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
754 * move right before drawing by the width of the scale left (if any)
755 * plus column offset from left margin (usually zero, but may be non-zero
756 * when fast painting is drawing just a few columns)
758 int charWidth = av.getCharWidth();
759 int xOffset = labelWidthWest
760 + ((startColumn - ranges.getStartRes()) % viewportWidth)
762 g.translate(xOffset, 0);
764 // When printing we have an extra clipped region,
765 // the Printable page which we need to account for here
766 Shape clip = g.getClip();
770 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
774 g.setClip(0, (int) clip.getBounds().getY(),
775 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
779 * white fill the region to be drawn (so incremental fast paint doesn't
780 * scribble over an existing image)
782 gg.setColor(Color.white);
783 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
784 wrappedRepeatHeightPx);
786 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
789 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
791 if (av.isShowAnnotation())
793 g.translate(0, cHeight + ypos + 3);
794 if (annotations == null)
796 annotations = new AnnotationPanel(av);
799 annotations.renderer.drawComponent(annotations, av, g, -1,
800 startColumn, endx + 1);
801 g.translate(0, -cHeight - ypos - 3);
804 g.translate(-xOffset, 0);
808 * Draws scales left, right and above (if shown), and any hidden column
809 * markers, on all widths of the wrapped alignment
814 protected void drawWrappedDecorators(Graphics g, final int startColumn)
816 int charWidth = av.getCharWidth();
818 g.setFont(av.getFont());
819 g.setColor(Color.black);
821 int ypos = wrappedSpaceAboveAlignment;
822 ViewportRanges ranges = av.getRanges();
823 int viewportWidth = ranges.getViewportWidth();
824 int maxWidth = ranges.getVisibleAlignmentWidth();
826 int startCol = startColumn;
828 while (widthsDrawn < wrappedVisibleWidths)
830 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
832 if (av.getScaleLeftWrapped())
834 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
837 if (av.getScaleRightWrapped())
839 int x = labelWidthWest + viewportWidth * charWidth;
841 drawVerticalScale(g, startCol, endColumn, ypos, false);
846 * white fill region of scale above and hidden column markers
847 * (to support incremental fast paint of image)
849 g.translate(labelWidthWest, 0);
850 g.setColor(Color.white);
851 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
852 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
853 g.setColor(Color.black);
854 g.translate(-labelWidthWest, 0);
856 g.translate(labelWidthWest, 0);
858 if (av.getScaleAboveWrapped())
860 drawNorthScale(g, startCol, endColumn, ypos);
863 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
865 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
868 g.translate(-labelWidthWest, 0);
870 ypos += wrappedRepeatHeightPx;
871 startCol += viewportWidth;
877 * Draws markers (triangles) above hidden column positions between startColumn
885 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
886 int startColumn, int endColumn)
888 int charHeight = av.getCharHeight();
889 int charWidth = av.getCharWidth();
891 g.setColor(Color.blue);
892 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
893 List<Integer> positions = hidden.findHiddenRegionPositions();
894 for (int pos : positions)
896 int res = pos - startColumn;
898 if (res < 0 || res > endColumn - startColumn + 1)
904 * draw a downward-pointing triangle at the hidden columns location
905 * (before the following visible column)
907 int xMiddle = res * charWidth;
908 int[] xPoints = new int[] { xMiddle - charHeight / 4,
909 xMiddle + charHeight / 4, xMiddle };
910 int yTop = ypos - (charHeight / 2);
911 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
912 g.fillPolygon(xPoints, yPoints, 3);
917 * Draw a selection group over a wrapped alignment
919 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
921 int canvasHeight, int startRes)
923 int charHeight = av.getCharHeight();
924 int charWidth = av.getCharWidth();
926 // height gap above each panel
927 int hgap = charHeight;
928 if (av.getScaleAboveWrapped())
933 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
935 int cHeight = av.getAlignment().getHeight() * charHeight;
937 int startx = startRes;
939 int ypos = hgap; // vertical offset
940 int maxwidth = av.getAlignment().getWidth();
942 if (av.hasHiddenColumns())
944 maxwidth = av.getAlignment().getHiddenColumns()
945 .findColumnPosition(maxwidth);
948 // chop the wrapped alignment extent up into panel-sized blocks and treat
949 // each block as if it were a block from an unwrapped alignment
950 while ((ypos <= canvasHeight) && (startx < maxwidth))
952 // set end value to be start + width, or maxwidth, whichever is smaller
953 endx = startx + cWidth - 1;
960 g.translate(labelWidthWest, 0);
962 drawUnwrappedSelection(g, group, startx, endx, 0,
963 av.getAlignment().getHeight() - 1,
966 g.translate(-labelWidthWest, 0);
968 // update vertical offset
969 ypos += cHeight + getAnnotationHeight() + hgap;
971 // update horizontal offset
976 int getAnnotationHeight()
978 if (!av.isShowAnnotation())
983 if (annotations == null)
985 annotations = new AnnotationPanel(av);
988 return annotations.adjustPanelHeight();
992 * Draws the visible region of the alignment on the graphics context. If there
993 * are hidden column markers in the visible region, then each sub-region
994 * between the markers is drawn separately, followed by the hidden column
998 * the graphics context, positioned at the first residue to be drawn
1000 * offset of the first column to draw (0..)
1002 * offset of the last column to draw (0..)
1004 * offset of the first sequence to draw (0..)
1006 * offset of the last sequence to draw (0..)
1008 * vertical offset at which to draw (for wrapped alignments)
1010 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1011 final int startSeq, final int endSeq, final int yOffset)
1013 int charHeight = av.getCharHeight();
1014 int charWidth = av.getCharWidth();
1016 if (!av.hasHiddenColumns())
1018 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1023 final int screenYMax = endRes - startRes;
1024 int blockStart = startRes;
1025 int blockEnd = endRes;
1027 for (int[] region : av.getAlignment().getHiddenColumns()
1028 .getHiddenColumnsCopy())
1030 int hideStart = region[0];
1031 int hideEnd = region[1];
1033 if (hideStart <= blockStart)
1035 blockStart += (hideEnd - hideStart) + 1;
1040 * draw up to just before the next hidden region, or the end of
1041 * the visible region, whichever comes first
1043 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1046 g1.translate(screenY * charWidth, 0);
1048 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1051 * draw the downline of the hidden column marker (ScalePanel draws the
1052 * triangle on top) if we reached it
1054 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1056 g1.setColor(Color.blue);
1058 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1059 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1060 (endSeq - startSeq + 1) * charHeight + yOffset);
1063 g1.translate(-screenY * charWidth, 0);
1064 screenY += blockEnd - blockStart + 1;
1065 blockStart = hideEnd + 1;
1067 if (screenY > screenYMax)
1069 // already rendered last block
1074 if (screenY <= screenYMax)
1076 // remaining visible region to render
1077 blockEnd = blockStart + screenYMax - screenY;
1078 g1.translate(screenY * charWidth, 0);
1079 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1081 g1.translate(-screenY * charWidth, 0);
1088 * Draws a region of the visible alignment
1092 * offset of the first column in the visible region (0..)
1094 * offset of the last column in the visible region (0..)
1096 * offset of the first sequence in the visible region (0..)
1098 * offset of the last sequence in the visible region (0..)
1100 * vertical offset at which to draw (for wrapped alignments)
1102 private void draw(final Graphics g, final int startRes, final int endRes,
1103 final int startSeq, final int endSeq, final int offset)
1105 g.setFont(av.getFont());
1106 seqRdr.prepare(g, av.isRenderGaps());
1108 // First draw the sequences
1109 av.getAlignment().forEachSequence(new Consumer<SequenceI>()
1114 public void accept(SequenceI s)
1116 int heightPosition = offset + ((i - startSeq) * av.getCharHeight());
1117 drawSequence(s, g, startRes, endRes, heightPosition, i);
1120 }, startSeq, endSeq + 1);
1122 // now selection groups
1123 if (av.getSelectionGroup() != null
1124 || av.getAlignment().getGroups().size() > 0)
1126 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1132 * Draw a single sequence
1135 * the next sequence to draw
1139 * offset of the first column in the visible region (0..)
1141 * offset of the last column in the visible region (0..)
1142 * @param heightPosition
1143 * vertical location of the sequence in the alignment
1147 private void drawSequence(SequenceI nextSeq, Graphics g, int startRes,
1148 int endRes, int heightPosition, int i)
1150 int charWidth = av.getCharWidth();
1152 if (nextSeq == null)
1154 // occasionally, a race condition occurs such that the alignment row is
1156 // TODO Don't think this will happen any more?
1159 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1160 startRes, endRes, heightPosition);
1162 if (av.isShowSequenceFeatures())
1164 fr.drawSequence(g, nextSeq, startRes, endRes, heightPosition, false);
1168 * highlight search Results once sequence has been drawn
1170 if (av.hasSearchResults())
1172 SearchResultsI searchResults = av.getSearchResults();
1173 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1175 if (visibleResults != null)
1177 for (int r = 0; r < visibleResults.length; r += 2)
1179 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1180 visibleResults[r + 1],
1181 (visibleResults[r] - startRes) * charWidth,
1187 if (av.cursorMode && cursorY == i && cursorX >= startRes
1188 && cursorX <= endRes)
1190 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1195 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1196 int startSeq, int endSeq, int offset)
1198 Graphics2D g = (Graphics2D) g1;
1200 // ///////////////////////////////////
1201 // Now outline any areas if necessary
1202 // ///////////////////////////////////
1204 SequenceGroup group = null;
1205 int groupIndex = -1;
1207 if (av.getAlignment().getGroups().size() > 0)
1209 group = av.getAlignment().getGroups().get(0);
1215 g.setStroke(new BasicStroke());
1216 g.setColor(group.getOutlineColour());
1220 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1225 g.setStroke(new BasicStroke());
1227 if (groupIndex >= av.getAlignment().getGroups().size())
1232 group = av.getAlignment().getGroups().get(groupIndex);
1234 } while (groupIndex < av.getAlignment().getGroups().size());
1242 * Draw the selection group as a separate image and overlay
1244 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1245 int startSeq, int endSeq)
1247 // get a new image of the correct size
1248 BufferedImage selectionImage = setupImage();
1250 if (selectionImage == null)
1255 SequenceGroup group = av.getSelectionGroup();
1262 // set up drawing colour
1263 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1265 setupSelectionGroup(g, selectionImage);
1267 if (!av.getWrapAlignment())
1269 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1274 drawWrappedSelection(g, group, getWidth(), getHeight(),
1275 av.getRanges().getStartRes());
1279 return selectionImage;
1283 * Set up graphics for selection group
1285 private void setupSelectionGroup(Graphics2D g,
1286 BufferedImage selectionImage)
1288 // set background to transparent
1289 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1290 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1292 // set up foreground to draw red dashed line
1293 g.setComposite(AlphaComposite.Src);
1294 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1295 BasicStroke.JOIN_ROUND, 3f, new float[]
1297 g.setColor(Color.RED);
1301 * Draw a selection group over an unwrapped alignment
1302 * @param g graphics object to draw with
1303 * @param group selection group
1304 * @param startRes start residue of area to draw
1305 * @param endRes end residue of area to draw
1306 * @param startSeq start sequence of area to draw
1307 * @param endSeq end sequence of area to draw
1308 * @param offset vertical offset (used when called from wrapped alignment code)
1310 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1311 int startRes, int endRes, int startSeq, int endSeq, int offset)
1313 int charWidth = av.getCharWidth();
1315 if (!av.hasHiddenColumns())
1317 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1322 // package into blocks of visible columns
1324 int blockStart = startRes;
1325 int blockEnd = endRes;
1327 for (int[] region : av.getAlignment().getHiddenColumns()
1328 .getHiddenColumnsCopy())
1330 int hideStart = region[0];
1331 int hideEnd = region[1];
1333 if (hideStart <= blockStart)
1335 blockStart += (hideEnd - hideStart) + 1;
1339 blockEnd = hideStart - 1;
1341 g.translate(screenY * charWidth, 0);
1342 drawPartialGroupOutline(g, group,
1343 blockStart, blockEnd, startSeq, endSeq, offset);
1345 g.translate(-screenY * charWidth, 0);
1346 screenY += blockEnd - blockStart + 1;
1347 blockStart = hideEnd + 1;
1349 if (screenY > (endRes - startRes))
1351 // already rendered last block
1356 if (screenY <= (endRes - startRes))
1358 // remaining visible region to render
1359 blockEnd = blockStart + (endRes - startRes) - screenY;
1360 g.translate(screenY * charWidth, 0);
1361 drawPartialGroupOutline(g, group,
1362 blockStart, blockEnd, startSeq, endSeq, offset);
1364 g.translate(-screenY * charWidth, 0);
1370 * Draw the selection group as a separate image and overlay
1372 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1373 int startRes, int endRes, int startSeq, int endSeq,
1376 int charHeight = av.getCharHeight();
1377 int charWidth = av.getCharWidth();
1379 int visWidth = (endRes - startRes + 1) * charWidth;
1383 boolean inGroup = false;
1391 for (i = startSeq; i <= endSeq; i++)
1393 // position of start residue of group relative to startRes, in pixels
1394 sx = (group.getStartRes() - startRes) * charWidth;
1396 // width of group in pixels
1397 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1400 sy = verticalOffset + (i - startSeq) * charHeight;
1402 if (sx + xwidth < 0 || sx > visWidth)
1407 if ((sx <= (endRes - startRes) * charWidth)
1408 && group.getSequences(null)
1409 .contains(av.getAlignment().getSequenceAt(i)))
1411 if ((bottom == -1) && !group.getSequences(null)
1412 .contains(av.getAlignment().getSequenceAt(i + 1)))
1414 bottom = sy + charHeight;
1419 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1420 .contains(av.getAlignment().getSequenceAt(i - 1)))
1433 // if start position is visible, draw vertical line to left of
1435 if (sx >= 0 && sx < visWidth)
1437 g.drawLine(sx, oldY, sx, sy);
1440 // if end position is visible, draw vertical line to right of
1442 if (sx + xwidth < visWidth)
1444 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1453 // don't let width extend beyond current block, or group extent
1455 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1457 xwidth = (endRes - startRes + 1) * charWidth - sx;
1460 // draw horizontal line at top of group
1463 g.drawLine(sx, top, sx + xwidth, top);
1467 // draw horizontal line at bottom of group
1470 g.drawLine(sx, bottom, sx + xwidth, bottom);
1481 sy = verticalOffset + ((i - startSeq) * charHeight);
1482 if (sx >= 0 && sx < visWidth)
1484 g.drawLine(sx, oldY, sx, sy);
1487 if (sx + xwidth < visWidth)
1489 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1498 if (sx + xwidth > visWidth)
1502 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1504 xwidth = (endRes - startRes + 1) * charWidth;
1509 g.drawLine(sx, top, sx + xwidth, top);
1515 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1524 * Highlights search results in the visible region by rendering as white text
1525 * on a black background. Any previous highlighting is removed. Answers true
1526 * if any highlight was left on the visible alignment (so status bar should be
1527 * set to match), else false.
1529 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1530 * alignment had to be scrolled to show the highlighted region, then it should
1531 * be fully redrawn, otherwise a fast paint can be performed. This argument
1532 * could be removed if fast paint of scrolled wrapped alignment is coded in
1533 * future (JAL-2609).
1536 * @param noFastPaint
1539 public boolean highlightSearchResults(SearchResultsI results,
1540 boolean noFastPaint)
1546 boolean wrapped = av.getWrapAlignment();
1549 fastPaint = !noFastPaint;
1550 fastpainting = fastPaint;
1553 * to avoid redrawing the whole visible region, we instead
1554 * redraw just the minimal regions to remove previous highlights
1557 SearchResultsI previous = av.getSearchResults();
1558 av.setSearchResults(results);
1559 boolean redrawn = false;
1560 boolean drawn = false;
1563 redrawn = drawMappedPositionsWrapped(previous);
1564 drawn = drawMappedPositionsWrapped(results);
1569 redrawn = drawMappedPositions(previous);
1570 drawn = drawMappedPositions(results);
1575 * if highlights were either removed or added, repaint
1583 * return true only if highlights were added
1589 fastpainting = false;
1594 * Redraws the minimal rectangle in the visible region (if any) that includes
1595 * mapped positions of the given search results. Whether or not positions are
1596 * highlighted depends on the SearchResults set on the Viewport. This allows
1597 * this method to be called to either clear or set highlighting. Answers true
1598 * if any positions were drawn (in which case a repaint is still required),
1604 protected boolean drawMappedPositions(SearchResultsI results)
1606 if (results == null)
1612 * calculate the minimal rectangle to redraw that
1613 * includes both new and existing search results
1615 int firstSeq = Integer.MAX_VALUE;
1617 int firstCol = Integer.MAX_VALUE;
1619 boolean matchFound = false;
1621 ViewportRanges ranges = av.getRanges();
1622 int firstVisibleColumn = ranges.getStartRes();
1623 int lastVisibleColumn = ranges.getEndRes();
1624 AlignmentI alignment = av.getAlignment();
1625 if (av.hasHiddenColumns())
1627 firstVisibleColumn = alignment.getHiddenColumns()
1628 .adjustForHiddenColumns(firstVisibleColumn);
1629 lastVisibleColumn = alignment.getHiddenColumns()
1630 .adjustForHiddenColumns(lastVisibleColumn);
1633 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1634 .getEndSeq(); seqNo++)
1636 SequenceI seq = alignment.getSequenceAt(seqNo);
1638 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1640 if (visibleResults != null)
1642 for (int i = 0; i < visibleResults.length - 1; i += 2)
1644 int firstMatchedColumn = visibleResults[i];
1645 int lastMatchedColumn = visibleResults[i + 1];
1646 if (firstMatchedColumn <= lastVisibleColumn
1647 && lastMatchedColumn >= firstVisibleColumn)
1650 * found a search results match in the visible region -
1651 * remember the first and last sequence matched, and the first
1652 * and last visible columns in the matched positions
1655 firstSeq = Math.min(firstSeq, seqNo);
1656 lastSeq = Math.max(lastSeq, seqNo);
1657 firstMatchedColumn = Math.max(firstMatchedColumn,
1658 firstVisibleColumn);
1659 lastMatchedColumn = Math.min(lastMatchedColumn,
1661 firstCol = Math.min(firstCol, firstMatchedColumn);
1662 lastCol = Math.max(lastCol, lastMatchedColumn);
1670 if (av.hasHiddenColumns())
1672 firstCol = alignment.getHiddenColumns()
1673 .findColumnPosition(firstCol);
1674 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1676 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1677 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1678 gg.translate(transX, transY);
1679 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1680 gg.translate(-transX, -transY);
1687 public void propertyChange(PropertyChangeEvent evt)
1689 String eventName = evt.getPropertyName();
1691 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1699 if (eventName.equals(ViewportRanges.STARTRES)
1700 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1702 // Make sure we're not trying to draw a panel
1703 // larger than the visible window
1704 if (eventName.equals(ViewportRanges.STARTRES))
1706 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1710 scrollX = ((int[]) evt.getNewValue())[0]
1711 - ((int[]) evt.getOldValue())[0];
1713 ViewportRanges vpRanges = av.getRanges();
1715 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1716 if (scrollX > range)
1720 else if (scrollX < -range)
1726 // Both scrolling and resizing change viewport ranges: scrolling changes
1727 // both start and end points, but resize only changes end values.
1728 // Here we only want to fastpaint on a scroll, with resize using a normal
1729 // paint, so scroll events are identified as changes to the horizontal or
1730 // vertical start value.
1731 if (eventName.equals(ViewportRanges.STARTRES))
1733 if (av.getWrapAlignment())
1735 fastPaintWrapped(scrollX);
1739 fastPaint(scrollX, 0);
1742 else if (eventName.equals(ViewportRanges.STARTSEQ))
1745 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1747 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1749 fastPaint(scrollX, 0);
1750 // bizarrely, we only need to scroll on the x value here as fastpaint
1751 // copies the full height of the image anyway. Passing in the y value
1752 // causes nasty repaint artefacts, which only disappear on a full
1758 * Does a minimal update of the image for a scroll movement. This method
1759 * handles scroll movements of up to one width of the wrapped alignment (one
1760 * click in the vertical scrollbar). Larger movements (for example after a
1761 * scroll to highlight a mapped position) trigger a full redraw instead.
1764 * number of positions scrolled (right if positive, left if negative)
1766 protected void fastPaintWrapped(int scrollX)
1768 ViewportRanges ranges = av.getRanges();
1770 if (Math.abs(scrollX) > ranges.getViewportWidth())
1773 * shift of more than one view width is
1774 * overcomplicated to handle in this method
1781 if (fastpainting || gg == null)
1787 fastpainting = true;
1791 calculateWrappedGeometry(getWidth(), getHeight());
1794 * relocate the regions of the alignment that are still visible
1796 shiftWrappedAlignment(-scrollX);
1799 * add new columns (sequence, annotation)
1800 * - at top left if scrollX < 0
1801 * - at right of last two widths if scrollX > 0
1805 int startRes = ranges.getStartRes();
1806 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1807 - scrollX - 1, getHeight());
1811 fastPaintWrappedAddRight(scrollX);
1815 * draw all scales (if shown) and hidden column markers
1817 drawWrappedDecorators(gg, ranges.getStartRes());
1822 fastpainting = false;
1827 * Draws the specified number of columns at the 'end' (bottom right) of a
1828 * wrapped alignment view, including sequences and annotations if shown, but
1829 * not scales. Also draws the same number of columns at the right hand end of
1830 * the second last width shown, if the last width is not full height (so
1831 * cannot simply be copied from the graphics image).
1835 protected void fastPaintWrappedAddRight(int columns)
1842 ViewportRanges ranges = av.getRanges();
1843 int viewportWidth = ranges.getViewportWidth();
1844 int charWidth = av.getCharWidth();
1847 * draw full height alignment in the second last row, last columns, if the
1848 * last row was not full height
1850 int visibleWidths = wrappedVisibleWidths;
1851 int canvasHeight = getHeight();
1852 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1854 if (lastWidthPartHeight)
1856 int widthsAbove = Math.max(0, visibleWidths - 2);
1857 int ypos = wrappedRepeatHeightPx * widthsAbove
1858 + wrappedSpaceAboveAlignment;
1859 int endRes = ranges.getEndRes();
1860 endRes += widthsAbove * viewportWidth;
1861 int startRes = endRes - columns;
1862 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1866 * white fill first to erase annotations
1868 gg.translate(xOffset, 0);
1869 gg.setColor(Color.white);
1870 gg.fillRect(labelWidthWest, ypos,
1871 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1872 gg.translate(-xOffset, 0);
1874 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1878 * draw newly visible columns in last wrapped width (none if we
1879 * have reached the end of the alignment)
1880 * y-offset for drawing last width is height of widths above,
1883 int widthsAbove = visibleWidths - 1;
1884 int ypos = wrappedRepeatHeightPx * widthsAbove
1885 + wrappedSpaceAboveAlignment;
1886 int endRes = ranges.getEndRes();
1887 endRes += widthsAbove * viewportWidth;
1888 int startRes = endRes - columns + 1;
1891 * white fill first to erase annotations
1893 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1895 gg.translate(xOffset, 0);
1896 gg.setColor(Color.white);
1897 int width = viewportWidth * charWidth - xOffset;
1898 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1899 gg.translate(-xOffset, 0);
1901 gg.setFont(av.getFont());
1902 gg.setColor(Color.black);
1904 if (startRes < ranges.getVisibleAlignmentWidth())
1906 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1910 * and finally, white fill any space below the visible alignment
1912 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1913 if (heightBelow > 0)
1915 gg.setColor(Color.white);
1916 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1921 * Shifts the visible alignment by the specified number of columns - left if
1922 * negative, right if positive. Copies and moves sequences and annotations (if
1923 * shown). Scales, hidden column markers and any newly visible columns must be
1928 protected void shiftWrappedAlignment(int positions)
1934 int charWidth = av.getCharWidth();
1936 int canvasHeight = getHeight();
1937 ViewportRanges ranges = av.getRanges();
1938 int viewportWidth = ranges.getViewportWidth();
1939 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1941 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1942 int xMax = ranges.getVisibleAlignmentWidth();
1947 * shift right (after scroll left)
1948 * for each wrapped width (starting with the last), copy (width-positions)
1949 * columns from the left margin to the right margin, and copy positions
1950 * columns from the right margin of the row above (if any) to the
1951 * left margin of the current row
1955 * get y-offset of last wrapped width, first row of sequences
1957 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1958 y += wrappedSpaceAboveAlignment;
1959 int copyFromLeftStart = labelWidthWest;
1960 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1964 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1965 positions * charWidth, 0);
1968 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1969 positions * charWidth, heightToCopy, -widthToCopy,
1970 wrappedRepeatHeightPx);
1973 y -= wrappedRepeatHeightPx;
1979 * shift left (after scroll right)
1980 * for each wrapped width (starting with the first), copy (width-positions)
1981 * columns from the right margin to the left margin, and copy positions
1982 * columns from the left margin of the row below (if any) to the
1983 * right margin of the current row
1985 int xpos = av.getRanges().getStartRes();
1986 int y = wrappedSpaceAboveAlignment;
1987 int copyFromRightStart = labelWidthWest - positions * charWidth;
1989 while (y < canvasHeight)
1991 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1992 positions * charWidth, 0);
1993 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1994 && (xpos + viewportWidth <= xMax))
1996 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1997 * charWidth, heightToCopy, widthToCopy,
1998 -wrappedRepeatHeightPx);
2001 y += wrappedRepeatHeightPx;
2002 xpos += viewportWidth;
2009 * Redraws any positions in the search results in the visible region of a
2010 * wrapped alignment. Any highlights are drawn depending on the search results
2011 * set on the Viewport, not the <code>results</code> argument. This allows
2012 * this method to be called either to clear highlights (passing the previous
2013 * search results), or to draw new highlights.
2018 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2020 if (results == null)
2024 int charHeight = av.getCharHeight();
2026 boolean matchFound = false;
2028 calculateWrappedGeometry(getWidth(), getHeight());
2029 int wrappedWidth = av.getWrappedWidth();
2030 int wrappedHeight = wrappedRepeatHeightPx;
2032 ViewportRanges ranges = av.getRanges();
2033 int canvasHeight = getHeight();
2034 int repeats = canvasHeight / wrappedHeight;
2035 if (canvasHeight / wrappedHeight > 0)
2040 int firstVisibleColumn = ranges.getStartRes();
2041 int lastVisibleColumn = ranges.getStartRes() + repeats
2042 * ranges.getViewportWidth() - 1;
2044 AlignmentI alignment = av.getAlignment();
2045 if (av.hasHiddenColumns())
2047 firstVisibleColumn = alignment.getHiddenColumns()
2048 .adjustForHiddenColumns(firstVisibleColumn);
2049 lastVisibleColumn = alignment.getHiddenColumns()
2050 .adjustForHiddenColumns(lastVisibleColumn);
2053 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2055 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2056 .getEndSeq(); seqNo++)
2058 SequenceI seq = alignment.getSequenceAt(seqNo);
2060 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2062 if (visibleResults != null)
2064 for (int i = 0; i < visibleResults.length - 1; i += 2)
2066 int firstMatchedColumn = visibleResults[i];
2067 int lastMatchedColumn = visibleResults[i + 1];
2068 if (firstMatchedColumn <= lastVisibleColumn
2069 && lastMatchedColumn >= firstVisibleColumn)
2072 * found a search results match in the visible region
2074 firstMatchedColumn = Math.max(firstMatchedColumn,
2075 firstVisibleColumn);
2076 lastMatchedColumn = Math.min(lastMatchedColumn,
2080 * draw each mapped position separately (as contiguous positions may
2081 * wrap across lines)
2083 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2085 int displayColumn = mappedPos;
2086 if (av.hasHiddenColumns())
2088 displayColumn = alignment.getHiddenColumns()
2089 .findColumnPosition(displayColumn);
2093 * transX: offset from left edge of canvas to residue position
2095 int transX = labelWidthWest
2096 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2097 * av.getCharWidth();
2100 * transY: offset from top edge of canvas to residue position
2102 int transY = gapHeight;
2103 transY += (displayColumn - ranges.getStartRes())
2104 / wrappedWidth * wrappedHeight;
2105 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2108 * yOffset is from graphics origin to start of visible region
2110 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2111 if (transY < getHeight())
2114 gg.translate(transX, transY);
2115 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2117 gg.translate(-transX, -transY);
2129 * Answers the width in pixels of the left scale labels (0 if not shown)
2133 int getLabelWidthWest()
2135 return labelWidthWest;