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);
340 // Call repaint on alignment panel so that repaints from other alignment
341 // panel components can be aggregated. Otherwise performance of the
342 // overview window and others may be adversely affected.
343 av.getAlignPanel().repaint();
346 fastpainting = false;
351 public void paintComponent(Graphics g)
353 super.paintComponent(g);
355 int charHeight = av.getCharHeight();
356 int charWidth = av.getCharWidth();
358 ViewportRanges ranges = av.getRanges();
360 int width = getWidth();
361 int height = getHeight();
363 width -= (width % charWidth);
364 height -= (height % charHeight);
366 // selectImage is the selection group outline image
367 BufferedImage selectImage = drawSelectionGroup(
368 ranges.getStartRes(), ranges.getEndRes(),
369 ranges.getStartSeq(), ranges.getEndSeq());
371 if ((img != null) && (fastPaint
372 || (getVisibleRect().width != g.getClipBounds().width)
373 || (getVisibleRect().height != g.getClipBounds().height)))
375 BufferedImage lcimg = buildLocalImage(selectImage);
376 g.drawImage(lcimg, 0, 0, this);
379 else if ((width > 0) && (height > 0))
381 // img is a cached version of the last view we drew, if any
382 // if we have no img or the size has changed, make a new one
383 if (img == null || width != img.getWidth()
384 || height != img.getHeight())
391 gg = (Graphics2D) img.getGraphics();
392 gg.setFont(av.getFont());
397 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
398 RenderingHints.VALUE_ANTIALIAS_ON);
401 gg.setColor(Color.white);
402 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
404 if (av.getWrapAlignment())
406 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
410 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
411 ranges.getStartSeq(), ranges.getEndSeq(), 0);
414 // lcimg is a local *copy* of img which we'll draw selectImage on top of
415 BufferedImage lcimg = buildLocalImage(selectImage);
416 g.drawImage(lcimg, 0, 0, this);
422 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
423 ranges.getStartSeq(), ranges.getEndSeq());
428 * Draw an alignment panel for printing
431 * Graphics object to draw with
433 * start residue of print area
435 * end residue of print area
437 * start sequence of print area
439 * end sequence of print area
441 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
442 int startSeq, int endSeq)
444 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
446 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
448 if (selectImage != null)
450 ((Graphics2D) g1).setComposite(AlphaComposite
451 .getInstance(AlphaComposite.SRC_OVER));
452 g1.drawImage(selectImage, 0, 0, this);
457 * Draw a wrapped alignment panel for printing
460 * Graphics object to draw with
462 * width of drawing area
463 * @param canvasHeight
464 * height of drawing area
466 * start residue of print area
468 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
469 int canvasHeight, int startRes)
471 SequenceGroup group = av.getSelectionGroup();
473 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
477 BufferedImage selectImage = null;
480 selectImage = new BufferedImage(canvasWidth, canvasHeight,
481 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
482 } catch (OutOfMemoryError er)
485 System.err.println("Print image OutOfMemory Error.\n" + er);
486 new OOMWarning("Creating wrapped alignment image for printing", er);
488 if (selectImage != null)
490 Graphics2D g2 = selectImage.createGraphics();
491 setupSelectionGroup(g2, selectImage);
492 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
496 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
497 g.drawImage(selectImage, 0, 0, this);
504 * Make a local image by combining the cached image img
507 private BufferedImage buildLocalImage(BufferedImage selectImage)
509 // clone the cached image
510 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
513 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
515 Graphics2D g2d = lcimg.createGraphics();
516 g2d.drawImage(img, 0, 0, null);
518 // overlay selection group on lcimg
519 if (selectImage != null)
522 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
523 g2d.drawImage(selectImage, 0, 0, this);
532 * Set up a buffered image of the correct height and size for the sequence canvas
534 private BufferedImage setupImage()
536 BufferedImage lcimg = null;
538 int charWidth = av.getCharWidth();
539 int charHeight = av.getCharHeight();
541 int width = getWidth();
542 int height = getHeight();
544 width -= (width % charWidth);
545 height -= (height % charHeight);
547 if ((width < 1) || (height < 1))
554 lcimg = new BufferedImage(width, height,
555 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
556 } catch (OutOfMemoryError er)
560 "Group image OutOfMemory Redraw Error.\n" + er);
561 new OOMWarning("Creating alignment image for display", er);
570 * Returns the visible width of the canvas in residues, after allowing for
571 * East or West scales (if shown)
574 * the width in pixels (possibly including scales)
578 public int getWrappedCanvasWidth(int canvasWidth)
580 int charWidth = av.getCharWidth();
582 FontMetrics fm = getFontMetrics(av.getFont());
586 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
588 labelWidth = getLabelWidth(fm);
591 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
593 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
595 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
599 * Returns a pixel width sufficient to show the largest sequence coordinate
600 * (end position) in the alignment, calculated as the FontMetrics width of
601 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
602 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
603 * half a character width space on either side.
608 protected int getLabelWidth(FontMetrics fm)
611 * find the biggest sequence end position we need to show
612 * (note this is not necessarily the sequence length)
615 AlignmentI alignment = av.getAlignment();
616 for (int i = 0; i < alignment.getHeight(); i++)
618 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
622 for (int i = maxWidth; i > 0; i /= 10)
627 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
631 * Draws as many widths of a wrapped alignment as can fit in the visible
636 * available width in pixels
637 * @param canvasHeight
638 * available height in pixels
640 * the first column (0...) of the alignment to draw
642 public void drawWrappedPanel(Graphics g, int canvasWidth,
643 int canvasHeight, final int startColumn)
645 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
648 av.setWrappedWidth(wrappedWidthInResidues);
650 ViewportRanges ranges = av.getRanges();
651 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
654 * draw one width at a time (including any scales or annotation shown),
655 * until we have run out of either alignment or vertical space available
657 int ypos = wrappedSpaceAboveAlignment;
658 int maxWidth = ranges.getVisibleAlignmentWidth();
660 int start = startColumn;
661 int currentWidth = 0;
662 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
665 .min(maxWidth, start + wrappedWidthInResidues - 1);
666 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
667 ypos += wrappedRepeatHeightPx;
668 start += wrappedWidthInResidues;
672 drawWrappedDecorators(g, startColumn);
676 * Calculates and saves values needed when rendering a wrapped alignment.
677 * These depend on many factors, including
679 * <li>canvas width and height</li>
680 * <li>number of visible sequences, and height of annotations if shown</li>
681 * <li>font and character width</li>
682 * <li>whether scales are shown left, right or above the alignment</li>
686 * @param canvasHeight
687 * @return the number of residue columns in each width
689 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
691 int charHeight = av.getCharHeight();
694 * vertical space in pixels between wrapped widths of alignment
695 * - one character height, or two if scale above is drawn
697 wrappedSpaceAboveAlignment = charHeight
698 * (av.getScaleAboveWrapped() ? 2 : 1);
701 * height in pixels of the wrapped widths
703 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
705 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
707 // add annotations panel height if shown
708 wrappedRepeatHeightPx += getAnnotationHeight();
711 * number of visible widths (the last one may be part height),
712 * ensuring a part height includes at least one sequence
714 ViewportRanges ranges = av.getRanges();
715 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
716 int remainder = canvasHeight % wrappedRepeatHeightPx;
717 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
719 wrappedVisibleWidths++;
723 * compute width in residues; this also sets East and West label widths
725 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
728 * limit visibleWidths to not exceed width of alignment
730 int xMax = ranges.getVisibleAlignmentWidth();
731 int startToEnd = xMax - ranges.getStartRes();
732 int maxWidths = startToEnd / wrappedWidthInResidues;
733 if (startToEnd % wrappedWidthInResidues > 0)
737 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
739 return wrappedWidthInResidues;
743 * Draws one width of a wrapped alignment, including sequences and
744 * annnotations, if shown, but not scales or hidden column markers
750 * @param canvasHeight
752 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
753 int endColumn, int canvasHeight)
755 ViewportRanges ranges = av.getRanges();
756 int viewportWidth = ranges.getViewportWidth();
758 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
761 * move right before drawing by the width of the scale left (if any)
762 * plus column offset from left margin (usually zero, but may be non-zero
763 * when fast painting is drawing just a few columns)
765 int charWidth = av.getCharWidth();
766 int xOffset = labelWidthWest
767 + ((startColumn - ranges.getStartRes()) % viewportWidth)
769 g.translate(xOffset, 0);
771 // When printing we have an extra clipped region,
772 // the Printable page which we need to account for here
773 Shape clip = g.getClip();
777 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
781 g.setClip(0, (int) clip.getBounds().getY(),
782 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
786 * white fill the region to be drawn (so incremental fast paint doesn't
787 * scribble over an existing image)
789 g.setColor(Color.white);
790 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
791 wrappedRepeatHeightPx);
793 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
796 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
798 if (av.isShowAnnotation())
800 g.translate(0, cHeight + ypos + 3);
801 if (annotations == null)
803 annotations = new AnnotationPanel(av);
806 annotations.renderer.drawComponent(annotations, av, g, -1,
807 startColumn, endx + 1);
808 g.translate(0, -cHeight - ypos - 3);
811 g.translate(-xOffset, 0);
815 * Draws scales left, right and above (if shown), and any hidden column
816 * markers, on all widths of the wrapped alignment
821 protected void drawWrappedDecorators(Graphics g, final int startColumn)
823 int charWidth = av.getCharWidth();
825 g.setFont(av.getFont());
826 g.setColor(Color.black);
828 int ypos = wrappedSpaceAboveAlignment;
829 ViewportRanges ranges = av.getRanges();
830 int viewportWidth = ranges.getViewportWidth();
831 int maxWidth = ranges.getVisibleAlignmentWidth();
833 int startCol = startColumn;
835 while (widthsDrawn < wrappedVisibleWidths)
837 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
839 if (av.getScaleLeftWrapped())
841 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
844 if (av.getScaleRightWrapped())
846 int x = labelWidthWest + viewportWidth * charWidth;
848 drawVerticalScale(g, startCol, endColumn, ypos, false);
853 * white fill region of scale above and hidden column markers
854 * (to support incremental fast paint of image)
856 g.translate(labelWidthWest, 0);
857 g.setColor(Color.white);
858 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
859 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
860 g.setColor(Color.black);
861 g.translate(-labelWidthWest, 0);
863 g.translate(labelWidthWest, 0);
865 if (av.getScaleAboveWrapped())
867 drawNorthScale(g, startCol, endColumn, ypos);
870 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
872 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
875 g.translate(-labelWidthWest, 0);
877 ypos += wrappedRepeatHeightPx;
878 startCol += viewportWidth;
884 * Draws markers (triangles) above hidden column positions between startColumn
892 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
893 int startColumn, int endColumn)
895 int charHeight = av.getCharHeight();
896 int charWidth = av.getCharWidth();
898 g.setColor(Color.blue);
899 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
900 List<Integer> positions = hidden.findHiddenRegionPositions();
901 for (int pos : positions)
903 int res = pos - startColumn;
905 if (res < 0 || res > endColumn - startColumn + 1)
911 * draw a downward-pointing triangle at the hidden columns location
912 * (before the following visible column)
914 int xMiddle = res * charWidth;
915 int[] xPoints = new int[] { xMiddle - charHeight / 4,
916 xMiddle + charHeight / 4, xMiddle };
917 int yTop = ypos - (charHeight / 2);
918 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
919 g.fillPolygon(xPoints, yPoints, 3);
924 * Draw a selection group over a wrapped alignment
926 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
928 int canvasHeight, int startRes)
930 int charHeight = av.getCharHeight();
931 int charWidth = av.getCharWidth();
933 // height gap above each panel
934 int hgap = charHeight;
935 if (av.getScaleAboveWrapped())
940 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
942 int cHeight = av.getAlignment().getHeight() * charHeight;
944 int startx = startRes;
946 int ypos = hgap; // vertical offset
947 int maxwidth = av.getAlignment().getWidth();
949 if (av.hasHiddenColumns())
951 maxwidth = av.getAlignment().getHiddenColumns()
952 .findColumnPosition(maxwidth);
955 // chop the wrapped alignment extent up into panel-sized blocks and treat
956 // each block as if it were a block from an unwrapped alignment
957 while ((ypos <= canvasHeight) && (startx < maxwidth))
959 // set end value to be start + width, or maxwidth, whichever is smaller
960 endx = startx + cWidth - 1;
967 g.translate(labelWidthWest, 0);
969 drawUnwrappedSelection(g, group, startx, endx, 0,
970 av.getAlignment().getHeight() - 1,
973 g.translate(-labelWidthWest, 0);
975 // update vertical offset
976 ypos += cHeight + getAnnotationHeight() + hgap;
978 // update horizontal offset
983 int getAnnotationHeight()
985 if (!av.isShowAnnotation())
990 if (annotations == null)
992 annotations = new AnnotationPanel(av);
995 return annotations.adjustPanelHeight();
999 * Draws the visible region of the alignment on the graphics context. If there
1000 * are hidden column markers in the visible region, then each sub-region
1001 * between the markers is drawn separately, followed by the hidden column
1005 * the graphics context, positioned at the first residue to be drawn
1007 * offset of the first column to draw (0..)
1009 * offset of the last column to draw (0..)
1011 * offset of the first sequence to draw (0..)
1013 * offset of the last sequence to draw (0..)
1015 * vertical offset at which to draw (for wrapped alignments)
1017 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1018 final int startSeq, final int endSeq, final int yOffset)
1020 int charHeight = av.getCharHeight();
1021 int charWidth = av.getCharWidth();
1023 if (!av.hasHiddenColumns())
1025 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1030 final int screenYMax = endRes - startRes;
1031 int blockStart = startRes;
1032 int blockEnd = endRes;
1034 for (int[] region : av.getAlignment().getHiddenColumns()
1035 .getHiddenColumnsCopy())
1037 int hideStart = region[0];
1038 int hideEnd = region[1];
1040 if (hideStart <= blockStart)
1042 blockStart += (hideEnd - hideStart) + 1;
1047 * draw up to just before the next hidden region, or the end of
1048 * the visible region, whichever comes first
1050 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1053 g1.translate(screenY * charWidth, 0);
1055 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1058 * draw the downline of the hidden column marker (ScalePanel draws the
1059 * triangle on top) if we reached it
1061 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1063 g1.setColor(Color.blue);
1065 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1066 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1067 (endSeq - startSeq + 1) * charHeight + yOffset);
1070 g1.translate(-screenY * charWidth, 0);
1071 screenY += blockEnd - blockStart + 1;
1072 blockStart = hideEnd + 1;
1074 if (screenY > screenYMax)
1076 // already rendered last block
1081 if (screenY <= screenYMax)
1083 // remaining visible region to render
1084 blockEnd = blockStart + screenYMax - screenY;
1085 g1.translate(screenY * charWidth, 0);
1086 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1088 g1.translate(-screenY * charWidth, 0);
1095 * Draws a region of the visible alignment
1099 * offset of the first column in the visible region (0..)
1101 * offset of the last column in the visible region (0..)
1103 * offset of the first sequence in the visible region (0..)
1105 * offset of the last sequence in the visible region (0..)
1107 * vertical offset at which to draw (for wrapped alignments)
1109 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1110 int endSeq, int offset)
1112 int charHeight = av.getCharHeight();
1113 int charWidth = av.getCharWidth();
1115 g.setFont(av.getFont());
1116 seqRdr.prepare(g, av.isRenderGaps());
1120 // / First draw the sequences
1121 // ///////////////////////////
1122 for (int i = startSeq; i <= endSeq; i++)
1124 nextSeq = av.getAlignment().getSequenceAt(i);
1125 if (nextSeq == null)
1127 // occasionally, a race condition occurs such that the alignment row is
1131 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1132 startRes, endRes, offset + ((i - startSeq) * charHeight));
1134 if (av.isShowSequenceFeatures())
1136 fr.drawSequence(g, nextSeq, startRes, endRes,
1137 offset + ((i - startSeq) * charHeight), false);
1141 * highlight search Results once sequence has been drawn
1143 if (av.hasSearchResults())
1145 SearchResultsI searchResults = av.getSearchResults();
1146 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1148 if (visibleResults != null)
1150 for (int r = 0; r < visibleResults.length; r += 2)
1152 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1153 visibleResults[r + 1],
1154 (visibleResults[r] - startRes) * charWidth,
1155 offset + ((i - startSeq) * charHeight));
1161 if (av.getSelectionGroup() != null
1162 || av.getAlignment().getGroups().size() > 0)
1164 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1169 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1170 int startSeq, int endSeq, int offset)
1172 Graphics2D g = (Graphics2D) g1;
1174 // ///////////////////////////////////
1175 // Now outline any areas if necessary
1176 // ///////////////////////////////////
1178 SequenceGroup group = null;
1179 int groupIndex = -1;
1181 if (av.getAlignment().getGroups().size() > 0)
1183 group = av.getAlignment().getGroups().get(0);
1189 g.setStroke(new BasicStroke());
1190 g.setColor(group.getOutlineColour());
1194 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1199 g.setStroke(new BasicStroke());
1201 if (groupIndex >= av.getAlignment().getGroups().size())
1206 group = av.getAlignment().getGroups().get(groupIndex);
1208 } while (groupIndex < av.getAlignment().getGroups().size());
1216 * Draw the selection group as a separate image and overlay
1218 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1219 int startSeq, int endSeq)
1221 // get a new image of the correct size
1222 BufferedImage selectionImage = setupImage();
1224 if (selectionImage == null)
1229 SequenceGroup group = av.getSelectionGroup();
1236 // set up drawing colour
1237 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1239 setupSelectionGroup(g, selectionImage);
1241 if (!av.getWrapAlignment())
1243 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1248 drawWrappedSelection(g, group, getWidth(), getHeight(),
1249 av.getRanges().getStartRes());
1253 return selectionImage;
1257 * Draw the cursor as a separate image and overlay
1260 * start residue of area to draw cursor in
1262 * end residue of area to draw cursor in
1264 * start sequence of area to draw cursor in
1266 * end sequence of are to draw cursor in
1267 * @return a transparent image of the same size as the sequence canvas, with
1268 * the cursor drawn on it, if any
1270 private void drawCursor(Graphics g, int startRes, int endRes,
1274 // convert the cursorY into a position on the visible alignment
1275 int cursor_ypos = cursorY;
1277 // don't do work unless we have to
1278 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1282 int startx = startRes;
1285 // convert the cursorX into a position on the visible alignment
1286 int cursor_xpos = av.getAlignment().getHiddenColumns()
1287 .findColumnPosition(cursorX);
1289 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1292 if (av.getWrapAlignment())
1294 // work out the correct offsets for the cursor
1295 int charHeight = av.getCharHeight();
1296 int charWidth = av.getCharWidth();
1297 int canvasWidth = getWidth();
1298 int canvasHeight = getHeight();
1300 // height gap above each panel
1301 int hgap = charHeight;
1302 if (av.getScaleAboveWrapped())
1307 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1309 int cHeight = av.getAlignment().getHeight() * charHeight;
1311 endx = startx + cWidth - 1;
1312 int ypos = hgap; // vertical offset
1314 // iterate down the wrapped panels
1315 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1317 // update vertical offset
1318 ypos += cHeight + getAnnotationHeight() + hgap;
1320 // update horizontal offset
1322 endx = startx + cWidth - 1;
1325 xoffset = labelWidthWest;
1328 // now check if cursor is within range for x values
1329 if (cursor_xpos >= startx && cursor_xpos <= endx)
1331 // get the character the cursor is drawn at
1332 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1333 char s = seq.getCharAt(cursorX);
1335 seqRdr.drawCursor(g, s,
1336 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1337 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1345 * Set up graphics for selection group
1347 private void setupSelectionGroup(Graphics2D g,
1348 BufferedImage selectionImage)
1350 // set background to transparent
1351 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1352 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1354 // set up foreground to draw red dashed line
1355 g.setComposite(AlphaComposite.Src);
1356 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1357 BasicStroke.JOIN_ROUND, 3f, new float[]
1359 g.setColor(Color.RED);
1363 * Draw a selection group over an unwrapped alignment
1364 * @param g graphics object to draw with
1365 * @param group selection group
1366 * @param startRes start residue of area to draw
1367 * @param endRes end residue of area to draw
1368 * @param startSeq start sequence of area to draw
1369 * @param endSeq end sequence of area to draw
1370 * @param offset vertical offset (used when called from wrapped alignment code)
1372 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1373 int startRes, int endRes, int startSeq, int endSeq, int offset)
1375 int charWidth = av.getCharWidth();
1377 if (!av.hasHiddenColumns())
1379 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1384 // package into blocks of visible columns
1386 int blockStart = startRes;
1387 int blockEnd = endRes;
1389 for (int[] region : av.getAlignment().getHiddenColumns()
1390 .getHiddenColumnsCopy())
1392 int hideStart = region[0];
1393 int hideEnd = region[1];
1395 if (hideStart <= blockStart)
1397 blockStart += (hideEnd - hideStart) + 1;
1401 blockEnd = hideStart - 1;
1403 g.translate(screenY * charWidth, 0);
1404 drawPartialGroupOutline(g, group,
1405 blockStart, blockEnd, startSeq, endSeq, offset);
1407 g.translate(-screenY * charWidth, 0);
1408 screenY += blockEnd - blockStart + 1;
1409 blockStart = hideEnd + 1;
1411 if (screenY > (endRes - startRes))
1413 // already rendered last block
1418 if (screenY <= (endRes - startRes))
1420 // remaining visible region to render
1421 blockEnd = blockStart + (endRes - startRes) - screenY;
1422 g.translate(screenY * charWidth, 0);
1423 drawPartialGroupOutline(g, group,
1424 blockStart, blockEnd, startSeq, endSeq, offset);
1426 g.translate(-screenY * charWidth, 0);
1432 * Draw the selection group as a separate image and overlay
1434 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1435 int startRes, int endRes, int startSeq, int endSeq,
1438 int charHeight = av.getCharHeight();
1439 int charWidth = av.getCharWidth();
1440 int visWidth = (endRes - startRes + 1) * charWidth;
1444 boolean inGroup = false;
1449 List<SequenceI> seqs = group.getSequences(null);
1451 // position of start residue of group relative to startRes, in pixels
1452 int sx = (group.getStartRes() - startRes) * charWidth;
1454 // width of group in pixels
1455 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1458 if (!(sx + xwidth < 0 || sx > visWidth))
1460 for (i = startSeq; i <= endSeq; i++)
1462 sy = verticalOffset + (i - startSeq) * charHeight;
1464 if ((sx <= (endRes - startRes) * charWidth)
1465 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1468 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1470 bottom = sy + charHeight;
1475 if (((top == -1) && (i == 0)) || !seqs
1476 .contains(av.getAlignment().getSequenceAt(i - 1)))
1487 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1488 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1490 // reset top and bottom
1498 sy = verticalOffset + ((i - startSeq) * charHeight);
1499 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1500 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1506 * Draw horizontal selection group boundaries at top and bottom positions
1509 * graphics object to draw on
1515 * visWidth maximum available width
1517 * position to draw top of group at
1519 * position to draw bottom of group at
1521 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1522 int visWidth, int top, int bottom)
1532 // don't let width extend beyond current block, or group extent
1534 if (startx + width >= visWidth)
1536 width = visWidth - startx;
1541 g.drawLine(startx, top, startx + width, top);
1546 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1551 * Draw vertical lines at sx and sx+xwidth providing they lie within
1555 * graphics object to draw on
1561 * visWidth maximum available width
1567 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1570 // if start position is visible, draw vertical line to left of
1572 if (sx >= 0 && sx < visWidth)
1574 g.drawLine(sx, oldY, sx, sy);
1577 // if end position is visible, draw vertical line to right of
1579 if (sx + xwidth < visWidth)
1581 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1586 * Highlights search results in the visible region by rendering as white text
1587 * on a black background. Any previous highlighting is removed. Answers true
1588 * if any highlight was left on the visible alignment (so status bar should be
1589 * set to match), else false.
1591 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1592 * alignment had to be scrolled to show the highlighted region, then it should
1593 * be fully redrawn, otherwise a fast paint can be performed. This argument
1594 * could be removed if fast paint of scrolled wrapped alignment is coded in
1595 * future (JAL-2609).
1598 * @param noFastPaint
1601 public boolean highlightSearchResults(SearchResultsI results,
1602 boolean noFastPaint)
1608 boolean wrapped = av.getWrapAlignment();
1611 fastPaint = !noFastPaint;
1612 fastpainting = fastPaint;
1615 * to avoid redrawing the whole visible region, we instead
1616 * redraw just the minimal regions to remove previous highlights
1619 SearchResultsI previous = av.getSearchResults();
1620 av.setSearchResults(results);
1621 boolean redrawn = false;
1622 boolean drawn = false;
1625 redrawn = drawMappedPositionsWrapped(previous);
1626 drawn = drawMappedPositionsWrapped(results);
1631 redrawn = drawMappedPositions(previous);
1632 drawn = drawMappedPositions(results);
1637 * if highlights were either removed or added, repaint
1645 * return true only if highlights were added
1651 fastpainting = false;
1656 * Redraws the minimal rectangle in the visible region (if any) that includes
1657 * mapped positions of the given search results. Whether or not positions are
1658 * highlighted depends on the SearchResults set on the Viewport. This allows
1659 * this method to be called to either clear or set highlighting. Answers true
1660 * if any positions were drawn (in which case a repaint is still required),
1666 protected boolean drawMappedPositions(SearchResultsI results)
1668 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1674 * calculate the minimal rectangle to redraw that
1675 * includes both new and existing search results
1677 int firstSeq = Integer.MAX_VALUE;
1679 int firstCol = Integer.MAX_VALUE;
1681 boolean matchFound = false;
1683 ViewportRanges ranges = av.getRanges();
1684 int firstVisibleColumn = ranges.getStartRes();
1685 int lastVisibleColumn = ranges.getEndRes();
1686 AlignmentI alignment = av.getAlignment();
1687 if (av.hasHiddenColumns())
1689 firstVisibleColumn = alignment.getHiddenColumns()
1690 .adjustForHiddenColumns(firstVisibleColumn);
1691 lastVisibleColumn = alignment.getHiddenColumns()
1692 .adjustForHiddenColumns(lastVisibleColumn);
1695 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1696 .getEndSeq(); seqNo++)
1698 SequenceI seq = alignment.getSequenceAt(seqNo);
1700 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1702 if (visibleResults != null)
1704 for (int i = 0; i < visibleResults.length - 1; i += 2)
1706 int firstMatchedColumn = visibleResults[i];
1707 int lastMatchedColumn = visibleResults[i + 1];
1708 if (firstMatchedColumn <= lastVisibleColumn
1709 && lastMatchedColumn >= firstVisibleColumn)
1712 * found a search results match in the visible region -
1713 * remember the first and last sequence matched, and the first
1714 * and last visible columns in the matched positions
1717 firstSeq = Math.min(firstSeq, seqNo);
1718 lastSeq = Math.max(lastSeq, seqNo);
1719 firstMatchedColumn = Math.max(firstMatchedColumn,
1720 firstVisibleColumn);
1721 lastMatchedColumn = Math.min(lastMatchedColumn,
1723 firstCol = Math.min(firstCol, firstMatchedColumn);
1724 lastCol = Math.max(lastCol, lastMatchedColumn);
1732 if (av.hasHiddenColumns())
1734 firstCol = alignment.getHiddenColumns()
1735 .findColumnPosition(firstCol);
1736 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1738 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1739 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1740 gg.translate(transX, transY);
1741 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1742 gg.translate(-transX, -transY);
1749 public void propertyChange(PropertyChangeEvent evt)
1751 String eventName = evt.getPropertyName();
1753 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1759 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1767 if (eventName.equals(ViewportRanges.STARTRES)
1768 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1770 // Make sure we're not trying to draw a panel
1771 // larger than the visible window
1772 if (eventName.equals(ViewportRanges.STARTRES))
1774 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1778 scrollX = ((int[]) evt.getNewValue())[0]
1779 - ((int[]) evt.getOldValue())[0];
1781 ViewportRanges vpRanges = av.getRanges();
1783 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1784 if (scrollX > range)
1788 else if (scrollX < -range)
1793 // Both scrolling and resizing change viewport ranges: scrolling changes
1794 // both start and end points, but resize only changes end values.
1795 // Here we only want to fastpaint on a scroll, with resize using a normal
1796 // paint, so scroll events are identified as changes to the horizontal or
1797 // vertical start value.
1798 if (eventName.equals(ViewportRanges.STARTRES))
1800 if (av.getWrapAlignment())
1802 fastPaintWrapped(scrollX);
1806 fastPaint(scrollX, 0);
1809 else if (eventName.equals(ViewportRanges.STARTSEQ))
1812 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1814 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1816 if (av.getWrapAlignment())
1818 fastPaintWrapped(scrollX);
1822 fastPaint(scrollX, 0);
1824 // bizarrely, we only need to scroll on the x value here as fastpaint
1825 // copies the full height of the image anyway. Passing in the y value
1826 // causes nasty repaint artefacts, which only disappear on a full
1832 * Does a minimal update of the image for a scroll movement. This method
1833 * handles scroll movements of up to one width of the wrapped alignment (one
1834 * click in the vertical scrollbar). Larger movements (for example after a
1835 * scroll to highlight a mapped position) trigger a full redraw instead.
1838 * number of positions scrolled (right if positive, left if negative)
1840 protected void fastPaintWrapped(int scrollX)
1842 ViewportRanges ranges = av.getRanges();
1844 if (Math.abs(scrollX) > ranges.getViewportWidth())
1847 * shift of more than one view width is
1848 * overcomplicated to handle in this method
1855 if (fastpainting || gg == null)
1861 fastpainting = true;
1865 calculateWrappedGeometry(getWidth(), getHeight());
1868 * relocate the regions of the alignment that are still visible
1870 shiftWrappedAlignment(-scrollX);
1873 * add new columns (sequence, annotation)
1874 * - at top left if scrollX < 0
1875 * - at right of last two widths if scrollX > 0
1879 int startRes = ranges.getStartRes();
1880 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1881 - scrollX - 1, getHeight());
1885 fastPaintWrappedAddRight(scrollX);
1889 * draw all scales (if shown) and hidden column markers
1891 drawWrappedDecorators(gg, ranges.getStartRes());
1896 fastpainting = false;
1901 * Draws the specified number of columns at the 'end' (bottom right) of a
1902 * wrapped alignment view, including sequences and annotations if shown, but
1903 * not scales. Also draws the same number of columns at the right hand end of
1904 * the second last width shown, if the last width is not full height (so
1905 * cannot simply be copied from the graphics image).
1909 protected void fastPaintWrappedAddRight(int columns)
1916 ViewportRanges ranges = av.getRanges();
1917 int viewportWidth = ranges.getViewportWidth();
1918 int charWidth = av.getCharWidth();
1921 * draw full height alignment in the second last row, last columns, if the
1922 * last row was not full height
1924 int visibleWidths = wrappedVisibleWidths;
1925 int canvasHeight = getHeight();
1926 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1928 if (lastWidthPartHeight)
1930 int widthsAbove = Math.max(0, visibleWidths - 2);
1931 int ypos = wrappedRepeatHeightPx * widthsAbove
1932 + wrappedSpaceAboveAlignment;
1933 int endRes = ranges.getEndRes();
1934 endRes += widthsAbove * viewportWidth;
1935 int startRes = endRes - columns;
1936 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1940 * white fill first to erase annotations
1942 gg.translate(xOffset, 0);
1943 gg.setColor(Color.white);
1944 gg.fillRect(labelWidthWest, ypos,
1945 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1946 gg.translate(-xOffset, 0);
1948 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1952 * draw newly visible columns in last wrapped width (none if we
1953 * have reached the end of the alignment)
1954 * y-offset for drawing last width is height of widths above,
1957 int widthsAbove = visibleWidths - 1;
1958 int ypos = wrappedRepeatHeightPx * widthsAbove
1959 + wrappedSpaceAboveAlignment;
1960 int endRes = ranges.getEndRes();
1961 endRes += widthsAbove * viewportWidth;
1962 int startRes = endRes - columns + 1;
1965 * white fill first to erase annotations
1967 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1969 gg.translate(xOffset, 0);
1970 gg.setColor(Color.white);
1971 int width = viewportWidth * charWidth - xOffset;
1972 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1973 gg.translate(-xOffset, 0);
1975 gg.setFont(av.getFont());
1976 gg.setColor(Color.black);
1978 if (startRes < ranges.getVisibleAlignmentWidth())
1980 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1984 * and finally, white fill any space below the visible alignment
1986 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1987 if (heightBelow > 0)
1989 gg.setColor(Color.white);
1990 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1995 * Shifts the visible alignment by the specified number of columns - left if
1996 * negative, right if positive. Copies and moves sequences and annotations (if
1997 * shown). Scales, hidden column markers and any newly visible columns must be
2002 protected void shiftWrappedAlignment(int positions)
2008 int charWidth = av.getCharWidth();
2010 int canvasHeight = getHeight();
2011 ViewportRanges ranges = av.getRanges();
2012 int viewportWidth = ranges.getViewportWidth();
2013 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2015 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2016 int xMax = ranges.getVisibleAlignmentWidth();
2021 * shift right (after scroll left)
2022 * for each wrapped width (starting with the last), copy (width-positions)
2023 * columns from the left margin to the right margin, and copy positions
2024 * columns from the right margin of the row above (if any) to the
2025 * left margin of the current row
2029 * get y-offset of last wrapped width, first row of sequences
2031 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2032 y += wrappedSpaceAboveAlignment;
2033 int copyFromLeftStart = labelWidthWest;
2034 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2038 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2039 positions * charWidth, 0);
2042 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2043 positions * charWidth, heightToCopy, -widthToCopy,
2044 wrappedRepeatHeightPx);
2047 y -= wrappedRepeatHeightPx;
2053 * shift left (after scroll right)
2054 * for each wrapped width (starting with the first), copy (width-positions)
2055 * columns from the right margin to the left margin, and copy positions
2056 * columns from the left margin of the row below (if any) to the
2057 * right margin of the current row
2059 int xpos = av.getRanges().getStartRes();
2060 int y = wrappedSpaceAboveAlignment;
2061 int copyFromRightStart = labelWidthWest - positions * charWidth;
2063 while (y < canvasHeight)
2065 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2066 positions * charWidth, 0);
2067 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2068 && (xpos + viewportWidth <= xMax))
2070 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2071 * charWidth, heightToCopy, widthToCopy,
2072 -wrappedRepeatHeightPx);
2075 y += wrappedRepeatHeightPx;
2076 xpos += viewportWidth;
2083 * Redraws any positions in the search results in the visible region of a
2084 * wrapped alignment. Any highlights are drawn depending on the search results
2085 * set on the Viewport, not the <code>results</code> argument. This allows
2086 * this method to be called either to clear highlights (passing the previous
2087 * search results), or to draw new highlights.
2092 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2094 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
2098 int charHeight = av.getCharHeight();
2100 boolean matchFound = false;
2102 calculateWrappedGeometry(getWidth(), getHeight());
2103 int wrappedWidth = av.getWrappedWidth();
2104 int wrappedHeight = wrappedRepeatHeightPx;
2106 ViewportRanges ranges = av.getRanges();
2107 int canvasHeight = getHeight();
2108 int repeats = canvasHeight / wrappedHeight;
2109 if (canvasHeight / wrappedHeight > 0)
2114 int firstVisibleColumn = ranges.getStartRes();
2115 int lastVisibleColumn = ranges.getStartRes() + repeats
2116 * ranges.getViewportWidth() - 1;
2118 AlignmentI alignment = av.getAlignment();
2119 if (av.hasHiddenColumns())
2121 firstVisibleColumn = alignment.getHiddenColumns()
2122 .adjustForHiddenColumns(firstVisibleColumn);
2123 lastVisibleColumn = alignment.getHiddenColumns()
2124 .adjustForHiddenColumns(lastVisibleColumn);
2127 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2129 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2130 .getEndSeq(); seqNo++)
2132 SequenceI seq = alignment.getSequenceAt(seqNo);
2134 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2136 if (visibleResults != null)
2138 for (int i = 0; i < visibleResults.length - 1; i += 2)
2140 int firstMatchedColumn = visibleResults[i];
2141 int lastMatchedColumn = visibleResults[i + 1];
2142 if (firstMatchedColumn <= lastVisibleColumn
2143 && lastMatchedColumn >= firstVisibleColumn)
2146 * found a search results match in the visible region
2148 firstMatchedColumn = Math.max(firstMatchedColumn,
2149 firstVisibleColumn);
2150 lastMatchedColumn = Math.min(lastMatchedColumn,
2154 * draw each mapped position separately (as contiguous positions may
2155 * wrap across lines)
2157 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2159 int displayColumn = mappedPos;
2160 if (av.hasHiddenColumns())
2162 displayColumn = alignment.getHiddenColumns()
2163 .findColumnPosition(displayColumn);
2167 * transX: offset from left edge of canvas to residue position
2169 int transX = labelWidthWest
2170 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2171 * av.getCharWidth();
2174 * transY: offset from top edge of canvas to residue position
2176 int transY = gapHeight;
2177 transY += (displayColumn - ranges.getStartRes())
2178 / wrappedWidth * wrappedHeight;
2179 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2182 * yOffset is from graphics origin to start of visible region
2184 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2185 if (transY < getHeight())
2188 gg.translate(transX, transY);
2189 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2191 gg.translate(-transX, -transY);
2203 * Answers the width in pixels of the left scale labels (0 if not shown)
2207 int getLabelWidthWest()
2209 return labelWidthWest;