2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.util.Comparison;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.AlphaComposite;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.List;
47 import javax.swing.JComponent;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static final String ZEROS = "0000000000";
59 final FeatureRenderer fr;
69 private final SequenceRenderer seqRdr;
71 private boolean fastPaint = false;
73 private boolean fastpainting = false;
75 private AnnotationPanel annotations;
78 * measurements for drawing a wrapped alignment
80 private int labelWidthEast; // label right width in pixels if shown
82 private int labelWidthWest; // label left width in pixels if shown
84 private int wrappedSpaceAboveAlignment; // gap between widths
86 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
88 private int wrappedVisibleWidths; // number of wrapped widths displayed
90 private Graphics2D gg;
93 * Creates a new SeqCanvas object.
97 public SeqCanvas(AlignmentPanel ap)
100 fr = new FeatureRenderer(ap);
101 seqRdr = new SequenceRenderer(av);
102 setLayout(new BorderLayout());
103 PaintRefresher.Register(this, av.getSequenceSetId());
104 setBackground(Color.white);
106 av.getRanges().addPropertyChangeListener(this);
109 public SequenceRenderer getSequenceRenderer()
114 public FeatureRenderer getFeatureRenderer()
120 * Draws the scale above a region of a wrapped alignment, consisting of a
121 * column number every major interval (10 columns).
124 * the graphics context to draw on, positioned at the start (bottom
125 * left) of the line on which to draw any scale marks
127 * start alignment column (0..)
129 * end alignment column (0..)
131 * y offset to draw at
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
135 int charHeight = av.getCharHeight();
136 int charWidth = av.getCharWidth();
139 * white fill the scale space (for the fastPaint case)
141 g.setColor(Color.white);
142 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
143 charHeight * 3 / 2 + 2);
144 g.setColor(Color.black);
146 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
148 for (ScaleMark mark : marks)
150 int mpos = mark.column; // (i - startx - 1)
155 String mstring = mark.text;
161 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
165 * draw a tick mark below the column number, centred on the column;
166 * height of tick mark is 4 pixels less than half a character
168 int xpos = (mpos * charWidth) + (charWidth / 2);
169 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
175 * Draw the scale to the left or right of a wrapped alignment
178 * graphics context, positioned at the start of the scale to be drawn
180 * first column of wrapped width (0.. excluding any hidden columns)
182 * last column of wrapped width (0.. excluding any hidden columns)
184 * vertical offset at which to begin the scale
186 * if true, scale is left of residues, if false, scale is right
188 void drawVerticalScale(Graphics g, final int startx, final int endx,
189 final int ypos, final boolean left)
191 int charHeight = av.getCharHeight();
192 int charWidth = av.getCharWidth();
194 int yPos = ypos + charHeight;
198 if (av.hasHiddenColumns())
200 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
201 startX = hiddenColumns.adjustForHiddenColumns(startx);
202 endX = hiddenColumns.adjustForHiddenColumns(endx);
204 FontMetrics fm = getFontMetrics(av.getFont());
206 for (int i = 0; i < av.getAlignment().getHeight(); i++)
208 SequenceI seq = av.getAlignment().getSequenceAt(i);
211 * find sequence position of first non-gapped position -
212 * to the right if scale left, to the left if scale right
214 int index = left ? startX : endX;
216 while (index >= startX && index <= endX)
218 if (!Comparison.isGap(seq.getCharAt(index)))
220 value = seq.findPosition(index);
234 * white fill the space for the scale
236 g.setColor(Color.white);
237 int y = (yPos + (i * charHeight)) - (charHeight / 5);
238 // fillRect origin is top left of rectangle
239 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
245 * draw scale value, right justified within its width less half a
246 * character width padding on the right
248 int labelSpace = left ? labelWidthWest : labelWidthEast;
249 labelSpace -= charWidth / 2; // leave space to the right
250 String valueAsString = String.valueOf(value);
251 int labelLength = fm.stringWidth(valueAsString);
252 int xOffset = labelSpace - labelLength;
253 g.setColor(Color.black);
254 g.drawString(valueAsString, xOffset, y);
260 * Does a fast paint of an alignment in response to a scroll. Most of the
261 * visible region is simply copied and shifted, and then any newly visible
262 * columns or rows are drawn. The scroll may be horizontal or vertical, but
263 * not both at once. Scrolling may be the result of
265 * <li>dragging a scroll bar</li>
266 * <li>clicking in the scroll bar</li>
267 * <li>scrolling by trackpad, middle mouse button, or other device</li>
268 * <li>by moving the box in the Overview window</li>
269 * <li>programmatically to make a highlighted position visible</li>
273 * columns to shift right (positive) or left (negative)
275 * rows to shift down (positive) or up (negative)
277 public void fastPaint(int horizontal, int vertical)
279 if (fastpainting || gg == null || img == null)
288 int charHeight = av.getCharHeight();
289 int charWidth = av.getCharWidth();
291 ViewportRanges ranges = av.getRanges();
292 int startRes = ranges.getStartRes();
293 int endRes = ranges.getEndRes();
294 int startSeq = ranges.getStartSeq();
295 int endSeq = ranges.getEndSeq();
299 gg.copyArea(horizontal * charWidth, vertical * charHeight,
300 img.getWidth(), img.getHeight(), -horizontal * charWidth,
301 -vertical * charHeight);
303 if (horizontal > 0) // scrollbar pulled right, image to the left
305 transX = (endRes - startRes - horizontal) * charWidth;
306 startRes = endRes - horizontal;
308 else if (horizontal < 0)
310 endRes = startRes - horizontal;
313 if (vertical > 0) // scroll down
315 startSeq = endSeq - vertical;
317 if (startSeq < ranges.getStartSeq())
318 { // ie scrolling too fast, more than a page at a time
319 startSeq = ranges.getStartSeq();
323 transY = img.getHeight() - ((vertical + 1) * charHeight);
326 else if (vertical < 0)
328 endSeq = startSeq - vertical;
330 if (endSeq > ranges.getEndSeq())
332 endSeq = ranges.getEndSeq();
336 gg.translate(transX, transY);
337 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
338 gg.translate(-transX, -transY);
343 fastpainting = false;
348 public void paintComponent(Graphics g)
350 super.paintComponent(g);
352 int charHeight = av.getCharHeight();
353 int charWidth = av.getCharWidth();
355 ViewportRanges ranges = av.getRanges();
357 int width = getWidth();
358 int height = getHeight();
360 width -= (width % charWidth);
361 height -= (height % charHeight);
363 // selectImage is the selection group outline image
364 BufferedImage selectImage = drawSelectionGroup(
365 ranges.getStartRes(), ranges.getEndRes(),
366 ranges.getStartSeq(), ranges.getEndSeq());
368 if ((img != null) && (fastPaint
369 || (getVisibleRect().width != g.getClipBounds().width)
370 || (getVisibleRect().height != g.getClipBounds().height)))
372 BufferedImage lcimg = buildLocalImage(selectImage);
373 g.drawImage(lcimg, 0, 0, this);
376 else if ((width > 0) && (height > 0))
378 // img is a cached version of the last view we drew, if any
379 // if we have no img or the size has changed, make a new one
380 if (img == null || width != img.getWidth()
381 || height != img.getHeight())
388 gg = (Graphics2D) img.getGraphics();
389 gg.setFont(av.getFont());
394 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
395 RenderingHints.VALUE_ANTIALIAS_ON);
398 gg.setColor(Color.white);
399 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
401 if (av.getWrapAlignment())
403 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
407 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
408 ranges.getStartSeq(), ranges.getEndSeq(), 0);
411 // lcimg is a local *copy* of img which we'll draw selectImage on top of
412 BufferedImage lcimg = buildLocalImage(selectImage);
413 g.drawImage(lcimg, 0, 0, this);
419 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
420 ranges.getStartSeq(), ranges.getEndSeq());
425 * Draw an alignment panel for printing
428 * Graphics object to draw with
430 * start residue of print area
432 * end residue of print area
434 * start sequence of print area
436 * end sequence of print area
438 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
439 int startSeq, int endSeq)
441 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
443 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
445 if (selectImage != null)
447 ((Graphics2D) g1).setComposite(AlphaComposite
448 .getInstance(AlphaComposite.SRC_OVER));
449 g1.drawImage(selectImage, 0, 0, this);
454 * Draw a wrapped alignment panel for printing
457 * Graphics object to draw with
459 * width of drawing area
460 * @param canvasHeight
461 * height of drawing area
463 * start residue of print area
465 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
466 int canvasHeight, int startRes)
468 SequenceGroup group = av.getSelectionGroup();
470 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
474 BufferedImage selectImage = null;
477 selectImage = new BufferedImage(canvasWidth, canvasHeight,
478 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
479 } catch (OutOfMemoryError er)
482 System.err.println("Print image OutOfMemory Error.\n" + er);
483 new OOMWarning("Creating wrapped alignment image for printing", er);
485 if (selectImage != null)
487 Graphics2D g2 = selectImage.createGraphics();
488 setupSelectionGroup(g2, selectImage);
489 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
493 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
494 g.drawImage(selectImage, 0, 0, this);
501 * Make a local image by combining the cached image img
504 private BufferedImage buildLocalImage(BufferedImage selectImage)
506 // clone the cached image
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);
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 g.setColor(Color.white);
784 g.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));
1155 if (av.getSelectionGroup() != null
1156 || av.getAlignment().getGroups().size() > 0)
1158 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1163 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1164 int startSeq, int endSeq, int offset)
1166 Graphics2D g = (Graphics2D) g1;
1168 // ///////////////////////////////////
1169 // Now outline any areas if necessary
1170 // ///////////////////////////////////
1172 SequenceGroup group = null;
1173 int groupIndex = -1;
1175 if (av.getAlignment().getGroups().size() > 0)
1177 group = av.getAlignment().getGroups().get(0);
1183 g.setStroke(new BasicStroke());
1184 g.setColor(group.getOutlineColour());
1188 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1193 g.setStroke(new BasicStroke());
1195 if (groupIndex >= av.getAlignment().getGroups().size())
1200 group = av.getAlignment().getGroups().get(groupIndex);
1202 } while (groupIndex < av.getAlignment().getGroups().size());
1210 * Draw the selection group as a separate image and overlay
1212 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1213 int startSeq, int endSeq)
1215 // get a new image of the correct size
1216 BufferedImage selectionImage = setupImage();
1218 if (selectionImage == null)
1223 SequenceGroup group = av.getSelectionGroup();
1230 // set up drawing colour
1231 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1233 setupSelectionGroup(g, selectionImage);
1235 if (!av.getWrapAlignment())
1237 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1242 drawWrappedSelection(g, group, getWidth(), getHeight(),
1243 av.getRanges().getStartRes());
1247 return selectionImage;
1251 * Draw the cursor as a separate image and overlay
1254 * start residue of area to draw cursor in
1256 * end residue of area to draw cursor in
1258 * start sequence of area to draw cursor in
1260 * end sequence of are to draw cursor in
1261 * @return a transparent image of the same size as the sequence canvas, with
1262 * the cursor drawn on it, if any
1264 private void drawCursor(Graphics g, int startRes, int endRes,
1268 // convert the cursorY into a position on the visible alignment
1269 int cursor_ypos = cursorY;
1271 // don't do work unless we have to
1272 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1276 int startx = startRes;
1279 // convert the cursorX into a position on the visible alignment
1280 int cursor_xpos = av.getAlignment().getHiddenColumns()
1281 .findColumnPosition(cursorX);
1283 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1286 if (av.getWrapAlignment())
1288 // work out the correct offsets for the cursor
1289 int charHeight = av.getCharHeight();
1290 int charWidth = av.getCharWidth();
1291 int canvasWidth = getWidth();
1292 int canvasHeight = getHeight();
1294 // height gap above each panel
1295 int hgap = charHeight;
1296 if (av.getScaleAboveWrapped())
1301 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1303 int cHeight = av.getAlignment().getHeight() * charHeight;
1305 endx = startx + cWidth - 1;
1306 int ypos = hgap; // vertical offset
1308 // iterate down the wrapped panels
1309 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1311 // update vertical offset
1312 ypos += cHeight + getAnnotationHeight() + hgap;
1314 // update horizontal offset
1316 endx = startx + cWidth - 1;
1319 xoffset = labelWidthWest;
1322 // now check if cursor is within range for x values
1323 if (cursor_xpos >= startx && cursor_xpos <= endx)
1325 // get the character the cursor is drawn at
1326 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1327 char s = seq.getCharAt(cursorX);
1329 seqRdr.drawCursor(g, s,
1330 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1331 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1339 * Set up graphics for selection group
1341 private void setupSelectionGroup(Graphics2D g,
1342 BufferedImage selectionImage)
1344 // set background to transparent
1345 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1346 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1348 // set up foreground to draw red dashed line
1349 g.setComposite(AlphaComposite.Src);
1350 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1351 BasicStroke.JOIN_ROUND, 3f, new float[]
1353 g.setColor(Color.RED);
1357 * Draw a selection group over an unwrapped alignment
1358 * @param g graphics object to draw with
1359 * @param group selection group
1360 * @param startRes start residue of area to draw
1361 * @param endRes end residue of area to draw
1362 * @param startSeq start sequence of area to draw
1363 * @param endSeq end sequence of area to draw
1364 * @param offset vertical offset (used when called from wrapped alignment code)
1366 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1367 int startRes, int endRes, int startSeq, int endSeq, int offset)
1369 int charWidth = av.getCharWidth();
1371 if (!av.hasHiddenColumns())
1373 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1378 // package into blocks of visible columns
1380 int blockStart = startRes;
1381 int blockEnd = endRes;
1383 for (int[] region : av.getAlignment().getHiddenColumns()
1384 .getHiddenColumnsCopy())
1386 int hideStart = region[0];
1387 int hideEnd = region[1];
1389 if (hideStart <= blockStart)
1391 blockStart += (hideEnd - hideStart) + 1;
1395 blockEnd = hideStart - 1;
1397 g.translate(screenY * charWidth, 0);
1398 drawPartialGroupOutline(g, group,
1399 blockStart, blockEnd, startSeq, endSeq, offset);
1401 g.translate(-screenY * charWidth, 0);
1402 screenY += blockEnd - blockStart + 1;
1403 blockStart = hideEnd + 1;
1405 if (screenY > (endRes - startRes))
1407 // already rendered last block
1412 if (screenY <= (endRes - startRes))
1414 // remaining visible region to render
1415 blockEnd = blockStart + (endRes - startRes) - screenY;
1416 g.translate(screenY * charWidth, 0);
1417 drawPartialGroupOutline(g, group,
1418 blockStart, blockEnd, startSeq, endSeq, offset);
1420 g.translate(-screenY * charWidth, 0);
1426 * Draw the selection group as a separate image and overlay
1428 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1429 int startRes, int endRes, int startSeq, int endSeq,
1432 int charHeight = av.getCharHeight();
1433 int charWidth = av.getCharWidth();
1434 int visWidth = (endRes - startRes + 1) * charWidth;
1438 boolean inGroup = false;
1443 List<SequenceI> seqs = group.getSequences(null);
1445 // position of start residue of group relative to startRes, in pixels
1446 int sx = (group.getStartRes() - startRes) * charWidth;
1448 // width of group in pixels
1449 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1452 if (!(sx + xwidth < 0 || sx > visWidth))
1454 for (i = startSeq; i <= endSeq; i++)
1456 sy = verticalOffset + (i - startSeq) * charHeight;
1458 if ((sx <= (endRes - startRes) * charWidth)
1459 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1462 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1464 bottom = sy + charHeight;
1469 if (((top == -1) && (i == 0)) || !seqs
1470 .contains(av.getAlignment().getSequenceAt(i - 1)))
1481 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1482 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1484 // reset top and bottom
1492 sy = verticalOffset + ((i - startSeq) * charHeight);
1493 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1494 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1500 * Draw horizontal selection group boundaries at top and bottom positions
1503 * graphics object to draw on
1509 * visWidth maximum available width
1511 * position to draw top of group at
1513 * position to draw bottom of group at
1515 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1516 int visWidth, int top, int bottom)
1526 // don't let width extend beyond current block, or group extent
1528 if (startx + width >= visWidth)
1530 width = visWidth - startx;
1535 g.drawLine(startx, top, startx + width, top);
1540 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1545 * Draw vertical lines at sx and sx+xwidth providing they lie within
1549 * graphics object to draw on
1555 * visWidth maximum available width
1561 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1564 // if start position is visible, draw vertical line to left of
1566 if (sx >= 0 && sx < visWidth)
1568 g.drawLine(sx, oldY, sx, sy);
1571 // if end position is visible, draw vertical line to right of
1573 if (sx + xwidth < visWidth)
1575 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1580 * Highlights search results in the visible region by rendering as white text
1581 * on a black background. Any previous highlighting is removed. Answers true
1582 * if any highlight was left on the visible alignment (so status bar should be
1583 * set to match), else false.
1585 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1586 * alignment had to be scrolled to show the highlighted region, then it should
1587 * be fully redrawn, otherwise a fast paint can be performed. This argument
1588 * could be removed if fast paint of scrolled wrapped alignment is coded in
1589 * future (JAL-2609).
1592 * @param noFastPaint
1595 public boolean highlightSearchResults(SearchResultsI results,
1596 boolean noFastPaint)
1602 boolean wrapped = av.getWrapAlignment();
1605 fastPaint = !noFastPaint;
1606 fastpainting = fastPaint;
1609 * to avoid redrawing the whole visible region, we instead
1610 * redraw just the minimal regions to remove previous highlights
1613 SearchResultsI previous = av.getSearchResults();
1614 av.setSearchResults(results);
1615 boolean redrawn = false;
1616 boolean drawn = false;
1619 redrawn = drawMappedPositionsWrapped(previous);
1620 drawn = drawMappedPositionsWrapped(results);
1625 redrawn = drawMappedPositions(previous);
1626 drawn = drawMappedPositions(results);
1631 * if highlights were either removed or added, repaint
1639 * return true only if highlights were added
1645 fastpainting = false;
1650 * Redraws the minimal rectangle in the visible region (if any) that includes
1651 * mapped positions of the given search results. Whether or not positions are
1652 * highlighted depends on the SearchResults set on the Viewport. This allows
1653 * this method to be called to either clear or set highlighting. Answers true
1654 * if any positions were drawn (in which case a repaint is still required),
1660 protected boolean drawMappedPositions(SearchResultsI results)
1662 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1668 * calculate the minimal rectangle to redraw that
1669 * includes both new and existing search results
1671 int firstSeq = Integer.MAX_VALUE;
1673 int firstCol = Integer.MAX_VALUE;
1675 boolean matchFound = false;
1677 ViewportRanges ranges = av.getRanges();
1678 int firstVisibleColumn = ranges.getStartRes();
1679 int lastVisibleColumn = ranges.getEndRes();
1680 AlignmentI alignment = av.getAlignment();
1681 if (av.hasHiddenColumns())
1683 firstVisibleColumn = alignment.getHiddenColumns()
1684 .adjustForHiddenColumns(firstVisibleColumn);
1685 lastVisibleColumn = alignment.getHiddenColumns()
1686 .adjustForHiddenColumns(lastVisibleColumn);
1689 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1690 .getEndSeq(); seqNo++)
1692 SequenceI seq = alignment.getSequenceAt(seqNo);
1694 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1696 if (visibleResults != null)
1698 for (int i = 0; i < visibleResults.length - 1; i += 2)
1700 int firstMatchedColumn = visibleResults[i];
1701 int lastMatchedColumn = visibleResults[i + 1];
1702 if (firstMatchedColumn <= lastVisibleColumn
1703 && lastMatchedColumn >= firstVisibleColumn)
1706 * found a search results match in the visible region -
1707 * remember the first and last sequence matched, and the first
1708 * and last visible columns in the matched positions
1711 firstSeq = Math.min(firstSeq, seqNo);
1712 lastSeq = Math.max(lastSeq, seqNo);
1713 firstMatchedColumn = Math.max(firstMatchedColumn,
1714 firstVisibleColumn);
1715 lastMatchedColumn = Math.min(lastMatchedColumn,
1717 firstCol = Math.min(firstCol, firstMatchedColumn);
1718 lastCol = Math.max(lastCol, lastMatchedColumn);
1726 if (av.hasHiddenColumns())
1728 firstCol = alignment.getHiddenColumns()
1729 .findColumnPosition(firstCol);
1730 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1732 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1733 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1734 gg.translate(transX, transY);
1735 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1736 gg.translate(-transX, -transY);
1743 public void propertyChange(PropertyChangeEvent evt)
1745 String eventName = evt.getPropertyName();
1747 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1753 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1761 if (eventName.equals(ViewportRanges.STARTRES)
1762 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1764 // Make sure we're not trying to draw a panel
1765 // larger than the visible window
1766 if (eventName.equals(ViewportRanges.STARTRES))
1768 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1772 scrollX = ((int[]) evt.getNewValue())[0]
1773 - ((int[]) evt.getOldValue())[0];
1775 ViewportRanges vpRanges = av.getRanges();
1777 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1778 if (scrollX > range)
1782 else if (scrollX < -range)
1787 // Both scrolling and resizing change viewport ranges: scrolling changes
1788 // both start and end points, but resize only changes end values.
1789 // Here we only want to fastpaint on a scroll, with resize using a normal
1790 // paint, so scroll events are identified as changes to the horizontal or
1791 // vertical start value.
1792 if (eventName.equals(ViewportRanges.STARTRES))
1794 if (av.getWrapAlignment())
1796 fastPaintWrapped(scrollX);
1800 fastPaint(scrollX, 0);
1803 else if (eventName.equals(ViewportRanges.STARTSEQ))
1806 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1808 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1810 if (av.getWrapAlignment())
1812 fastPaintWrapped(scrollX);
1816 fastPaint(scrollX, 0);
1818 // bizarrely, we only need to scroll on the x value here as fastpaint
1819 // copies the full height of the image anyway. Passing in the y value
1820 // causes nasty repaint artefacts, which only disappear on a full
1826 * Does a minimal update of the image for a scroll movement. This method
1827 * handles scroll movements of up to one width of the wrapped alignment (one
1828 * click in the vertical scrollbar). Larger movements (for example after a
1829 * scroll to highlight a mapped position) trigger a full redraw instead.
1832 * number of positions scrolled (right if positive, left if negative)
1834 protected void fastPaintWrapped(int scrollX)
1836 ViewportRanges ranges = av.getRanges();
1838 // if (Math.abs(scrollX) > ranges.getViewportWidth())
1839 // JAL-2836, 2836 temporarily removed wrapped fastpaint for release 2.10.3
1843 * shift of more than one view width is
1844 * overcomplicated to handle in this method
1851 if (fastpainting || gg == null)
1857 fastpainting = true;
1861 calculateWrappedGeometry(getWidth(), getHeight());
1864 * relocate the regions of the alignment that are still visible
1866 shiftWrappedAlignment(-scrollX);
1869 * add new columns (sequence, annotation)
1870 * - at top left if scrollX < 0
1871 * - at right of last two widths if scrollX > 0
1875 int startRes = ranges.getStartRes();
1876 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1877 - scrollX - 1, getHeight());
1881 fastPaintWrappedAddRight(scrollX);
1885 * draw all scales (if shown) and hidden column markers
1887 drawWrappedDecorators(gg, ranges.getStartRes());
1892 fastpainting = false;
1897 * Draws the specified number of columns at the 'end' (bottom right) of a
1898 * wrapped alignment view, including sequences and annotations if shown, but
1899 * not scales. Also draws the same number of columns at the right hand end of
1900 * the second last width shown, if the last width is not full height (so
1901 * cannot simply be copied from the graphics image).
1905 protected void fastPaintWrappedAddRight(int columns)
1912 ViewportRanges ranges = av.getRanges();
1913 int viewportWidth = ranges.getViewportWidth();
1914 int charWidth = av.getCharWidth();
1917 * draw full height alignment in the second last row, last columns, if the
1918 * last row was not full height
1920 int visibleWidths = wrappedVisibleWidths;
1921 int canvasHeight = getHeight();
1922 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1924 if (lastWidthPartHeight)
1926 int widthsAbove = Math.max(0, visibleWidths - 2);
1927 int ypos = wrappedRepeatHeightPx * widthsAbove
1928 + wrappedSpaceAboveAlignment;
1929 int endRes = ranges.getEndRes();
1930 endRes += widthsAbove * viewportWidth;
1931 int startRes = endRes - columns;
1932 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1936 * white fill first to erase annotations
1938 gg.translate(xOffset, 0);
1939 gg.setColor(Color.white);
1940 gg.fillRect(labelWidthWest, ypos,
1941 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1942 gg.translate(-xOffset, 0);
1944 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1948 * draw newly visible columns in last wrapped width (none if we
1949 * have reached the end of the alignment)
1950 * y-offset for drawing last width is height of widths above,
1953 int widthsAbove = visibleWidths - 1;
1954 int ypos = wrappedRepeatHeightPx * widthsAbove
1955 + wrappedSpaceAboveAlignment;
1956 int endRes = ranges.getEndRes();
1957 endRes += widthsAbove * viewportWidth;
1958 int startRes = endRes - columns + 1;
1961 * white fill first to erase annotations
1963 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1965 gg.translate(xOffset, 0);
1966 gg.setColor(Color.white);
1967 int width = viewportWidth * charWidth - xOffset;
1968 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1969 gg.translate(-xOffset, 0);
1971 gg.setFont(av.getFont());
1972 gg.setColor(Color.black);
1974 if (startRes < ranges.getVisibleAlignmentWidth())
1976 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1980 * and finally, white fill any space below the visible alignment
1982 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1983 if (heightBelow > 0)
1985 gg.setColor(Color.white);
1986 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1991 * Shifts the visible alignment by the specified number of columns - left if
1992 * negative, right if positive. Copies and moves sequences and annotations (if
1993 * shown). Scales, hidden column markers and any newly visible columns must be
1998 protected void shiftWrappedAlignment(int positions)
2004 int charWidth = av.getCharWidth();
2006 int canvasHeight = getHeight();
2007 ViewportRanges ranges = av.getRanges();
2008 int viewportWidth = ranges.getViewportWidth();
2009 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2011 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2012 int xMax = ranges.getVisibleAlignmentWidth();
2017 * shift right (after scroll left)
2018 * for each wrapped width (starting with the last), copy (width-positions)
2019 * columns from the left margin to the right margin, and copy positions
2020 * columns from the right margin of the row above (if any) to the
2021 * left margin of the current row
2025 * get y-offset of last wrapped width, first row of sequences
2027 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2028 y += wrappedSpaceAboveAlignment;
2029 int copyFromLeftStart = labelWidthWest;
2030 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2034 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2035 positions * charWidth, 0);
2038 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2039 positions * charWidth, heightToCopy, -widthToCopy,
2040 wrappedRepeatHeightPx);
2043 y -= wrappedRepeatHeightPx;
2049 * shift left (after scroll right)
2050 * for each wrapped width (starting with the first), copy (width-positions)
2051 * columns from the right margin to the left margin, and copy positions
2052 * columns from the left margin of the row below (if any) to the
2053 * right margin of the current row
2055 int xpos = av.getRanges().getStartRes();
2056 int y = wrappedSpaceAboveAlignment;
2057 int copyFromRightStart = labelWidthWest - positions * charWidth;
2059 while (y < canvasHeight)
2061 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2062 positions * charWidth, 0);
2063 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2064 && (xpos + viewportWidth <= xMax))
2066 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2067 * charWidth, heightToCopy, widthToCopy,
2068 -wrappedRepeatHeightPx);
2071 y += wrappedRepeatHeightPx;
2072 xpos += viewportWidth;
2079 * Redraws any positions in the search results in the visible region of a
2080 * wrapped alignment. Any highlights are drawn depending on the search results
2081 * set on the Viewport, not the <code>results</code> argument. This allows
2082 * this method to be called either to clear highlights (passing the previous
2083 * search results), or to draw new highlights.
2088 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2090 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
2094 int charHeight = av.getCharHeight();
2096 boolean matchFound = false;
2098 calculateWrappedGeometry(getWidth(), getHeight());
2099 int wrappedWidth = av.getWrappedWidth();
2100 int wrappedHeight = wrappedRepeatHeightPx;
2102 ViewportRanges ranges = av.getRanges();
2103 int canvasHeight = getHeight();
2104 int repeats = canvasHeight / wrappedHeight;
2105 if (canvasHeight / wrappedHeight > 0)
2110 int firstVisibleColumn = ranges.getStartRes();
2111 int lastVisibleColumn = ranges.getStartRes() + repeats
2112 * ranges.getViewportWidth() - 1;
2114 AlignmentI alignment = av.getAlignment();
2115 if (av.hasHiddenColumns())
2117 firstVisibleColumn = alignment.getHiddenColumns()
2118 .adjustForHiddenColumns(firstVisibleColumn);
2119 lastVisibleColumn = alignment.getHiddenColumns()
2120 .adjustForHiddenColumns(lastVisibleColumn);
2123 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2125 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2126 .getEndSeq(); seqNo++)
2128 SequenceI seq = alignment.getSequenceAt(seqNo);
2130 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2132 if (visibleResults != null)
2134 for (int i = 0; i < visibleResults.length - 1; i += 2)
2136 int firstMatchedColumn = visibleResults[i];
2137 int lastMatchedColumn = visibleResults[i + 1];
2138 if (firstMatchedColumn <= lastVisibleColumn
2139 && lastMatchedColumn >= firstVisibleColumn)
2142 * found a search results match in the visible region
2144 firstMatchedColumn = Math.max(firstMatchedColumn,
2145 firstVisibleColumn);
2146 lastMatchedColumn = Math.min(lastMatchedColumn,
2150 * draw each mapped position separately (as contiguous positions may
2151 * wrap across lines)
2153 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2155 int displayColumn = mappedPos;
2156 if (av.hasHiddenColumns())
2158 displayColumn = alignment.getHiddenColumns()
2159 .findColumnPosition(displayColumn);
2163 * transX: offset from left edge of canvas to residue position
2165 int transX = labelWidthWest
2166 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2167 * av.getCharWidth();
2170 * transY: offset from top edge of canvas to residue position
2172 int transY = gapHeight;
2173 transY += (displayColumn - ranges.getStartRes())
2174 / wrappedWidth * wrappedHeight;
2175 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2178 * yOffset is from graphics origin to start of visible region
2180 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2181 if (transY < getHeight())
2184 gg.translate(transX, transY);
2185 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2187 gg.translate(-transX, -transY);
2199 * Answers the width in pixels of the left scale labels (0 if not shown)
2203 int getLabelWidthWest()
2205 return labelWidthWest;