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.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.AlphaComposite;
36 import java.awt.BasicStroke;
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Graphics2D;
42 import java.awt.RenderingHints;
43 import java.awt.Shape;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.Iterator;
47 import java.util.List;
49 import javax.swing.JComponent;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 public class SeqCanvas extends JComponent implements ViewportListenerI
59 private static final String ZEROS = "0000000000";
61 final FeatureRenderer fr;
71 private final SequenceRenderer seqRdr;
73 private boolean fastPaint = false;
75 private boolean fastpainting = false;
77 private AnnotationPanel annotations;
80 * measurements for drawing a wrapped alignment
82 private int labelWidthEast; // label right width in pixels if shown
84 private int labelWidthWest; // label left width in pixels if shown
86 private int wrappedSpaceAboveAlignment; // gap between widths
88 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
90 private int wrappedVisibleWidths; // number of wrapped widths displayed
92 private Graphics2D gg;
95 * Creates a new SeqCanvas object.
99 public SeqCanvas(AlignmentPanel ap)
102 fr = new FeatureRenderer(ap);
103 seqRdr = new SequenceRenderer(av);
104 setLayout(new BorderLayout());
105 PaintRefresher.Register(this, av.getSequenceSetId());
106 setBackground(Color.white);
108 av.getRanges().addPropertyChangeListener(this);
111 public SequenceRenderer getSequenceRenderer()
116 public FeatureRenderer getFeatureRenderer()
122 * Draws the scale above a region of a wrapped alignment, consisting of a
123 * column number every major interval (10 columns).
126 * the graphics context to draw on, positioned at the start (bottom
127 * left) of the line on which to draw any scale marks
129 * start alignment column (0..)
131 * end alignment column (0..)
133 * y offset to draw at
135 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
137 int charHeight = av.getCharHeight();
138 int charWidth = av.getCharWidth();
141 * white fill the scale space (for the fastPaint case)
143 g.setColor(Color.white);
144 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
145 charHeight * 3 / 2 + 2);
146 g.setColor(Color.black);
148 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
150 for (ScaleMark mark : marks)
152 int mpos = mark.column; // (i - startx - 1)
157 String mstring = mark.text;
163 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
167 * draw a tick mark below the column number, centred on the column;
168 * height of tick mark is 4 pixels less than half a character
170 int xpos = (mpos * charWidth) + (charWidth / 2);
171 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
177 * Draw the scale to the left or right of a wrapped alignment
180 * graphics context, positioned at the start of the scale to be drawn
182 * first column of wrapped width (0.. excluding any hidden columns)
184 * last column of wrapped width (0.. excluding any hidden columns)
186 * vertical offset at which to begin the scale
188 * if true, scale is left of residues, if false, scale is right
190 void drawVerticalScale(Graphics g, final int startx, final int endx,
191 final int ypos, final boolean left)
193 int charHeight = av.getCharHeight();
194 int charWidth = av.getCharWidth();
196 int yPos = ypos + charHeight;
200 if (av.hasHiddenColumns())
202 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
203 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
204 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
206 FontMetrics fm = getFontMetrics(av.getFont());
208 for (int i = 0; i < av.getAlignment().getHeight(); i++)
210 SequenceI seq = av.getAlignment().getSequenceAt(i);
213 * find sequence position of first non-gapped position -
214 * to the right if scale left, to the left if scale right
216 int index = left ? startX : endX;
218 while (index >= startX && index <= endX)
220 if (!Comparison.isGap(seq.getCharAt(index)))
222 value = seq.findPosition(index);
236 * white fill the space for the scale
238 g.setColor(Color.white);
239 int y = (yPos + (i * charHeight)) - (charHeight / 5);
240 // fillRect origin is top left of rectangle
241 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
247 * draw scale value, right justified within its width less half a
248 * character width padding on the right
250 int labelSpace = left ? labelWidthWest : labelWidthEast;
251 labelSpace -= charWidth / 2; // leave space to the right
252 String valueAsString = String.valueOf(value);
253 int labelLength = fm.stringWidth(valueAsString);
254 int xOffset = labelSpace - labelLength;
255 g.setColor(Color.black);
256 g.drawString(valueAsString, xOffset, y);
262 * Does a fast paint of an alignment in response to a scroll. Most of the
263 * visible region is simply copied and shifted, and then any newly visible
264 * columns or rows are drawn. The scroll may be horizontal or vertical, but
265 * not both at once. Scrolling may be the result of
267 * <li>dragging a scroll bar</li>
268 * <li>clicking in the scroll bar</li>
269 * <li>scrolling by trackpad, middle mouse button, or other device</li>
270 * <li>by moving the box in the Overview window</li>
271 * <li>programmatically to make a highlighted position visible</li>
275 * columns to shift right (positive) or left (negative)
277 * rows to shift down (positive) or up (negative)
279 public void fastPaint(int horizontal, int vertical)
281 if (fastpainting || gg == null || img == null)
290 int charHeight = av.getCharHeight();
291 int charWidth = av.getCharWidth();
293 ViewportRanges ranges = av.getRanges();
294 int startRes = ranges.getStartRes();
295 int endRes = ranges.getEndRes();
296 int startSeq = ranges.getStartSeq();
297 int endSeq = ranges.getEndSeq();
301 gg.copyArea(horizontal * charWidth, vertical * charHeight,
302 img.getWidth(), img.getHeight(), -horizontal * charWidth,
303 -vertical * charHeight);
305 if (horizontal > 0) // scrollbar pulled right, image to the left
307 transX = (endRes - startRes - horizontal) * charWidth;
308 startRes = endRes - horizontal;
310 else if (horizontal < 0)
312 endRes = startRes - horizontal;
315 if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
345 fastpainting = false;
350 public void paintComponent(Graphics g)
352 super.paintComponent(g);
354 int charHeight = av.getCharHeight();
355 int charWidth = av.getCharWidth();
357 ViewportRanges ranges = av.getRanges();
359 int width = getWidth();
360 int height = getHeight();
362 width -= (width % charWidth);
363 height -= (height % charHeight);
365 // selectImage is the selection group outline image
366 BufferedImage selectImage = drawSelectionGroup(
367 ranges.getStartRes(), ranges.getEndRes(),
368 ranges.getStartSeq(), ranges.getEndSeq());
370 if ((img != null) && (fastPaint
371 || (getVisibleRect().width != g.getClipBounds().width)
372 || (getVisibleRect().height != g.getClipBounds().height)))
374 BufferedImage lcimg = buildLocalImage(selectImage);
375 g.drawImage(lcimg, 0, 0, this);
378 else if ((width > 0) && (height > 0))
380 // img is a cached version of the last view we drew, if any
381 // if we have no img or the size has changed, make a new one
382 if (img == null || width != img.getWidth()
383 || height != img.getHeight())
390 gg = (Graphics2D) img.getGraphics();
391 gg.setFont(av.getFont());
396 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
397 RenderingHints.VALUE_ANTIALIAS_ON);
400 gg.setColor(Color.white);
401 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
403 if (av.getWrapAlignment())
405 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
409 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
410 ranges.getStartSeq(), ranges.getEndSeq(), 0);
413 // lcimg is a local *copy* of img which we'll draw selectImage on top of
414 BufferedImage lcimg = buildLocalImage(selectImage);
415 g.drawImage(lcimg, 0, 0, this);
421 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
422 ranges.getStartSeq(), ranges.getEndSeq());
427 * Draw an alignment panel for printing
430 * Graphics object to draw with
432 * start residue of print area
434 * end residue of print area
436 * start sequence of print area
438 * end sequence of print area
440 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
441 int startSeq, int endSeq)
443 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
445 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
447 if (selectImage != null)
449 ((Graphics2D) g1).setComposite(AlphaComposite
450 .getInstance(AlphaComposite.SRC_OVER));
451 g1.drawImage(selectImage, 0, 0, this);
456 * Draw a wrapped alignment panel for printing
459 * Graphics object to draw with
461 * width of drawing area
462 * @param canvasHeight
463 * height of drawing area
465 * start residue of print area
467 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
468 int canvasHeight, int startRes)
470 SequenceGroup group = av.getSelectionGroup();
472 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
476 BufferedImage selectImage = null;
479 selectImage = new BufferedImage(canvasWidth, canvasHeight,
480 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
481 } catch (OutOfMemoryError er)
484 System.err.println("Print image OutOfMemory Error.\n" + er);
485 new OOMWarning("Creating wrapped alignment image for printing", er);
487 if (selectImage != null)
489 Graphics2D g2 = selectImage.createGraphics();
490 setupSelectionGroup(g2, selectImage);
491 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
495 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
496 g.drawImage(selectImage, 0, 0, this);
503 * Make a local image by combining the cached image img
506 private BufferedImage buildLocalImage(BufferedImage selectImage)
508 // clone the cached image
509 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
511 Graphics2D g2d = lcimg.createGraphics();
512 g2d.drawImage(img, 0, 0, null);
514 // overlay selection group on lcimg
515 if (selectImage != null)
518 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
519 g2d.drawImage(selectImage, 0, 0, this);
528 * Set up a buffered image of the correct height and size for the sequence canvas
530 private BufferedImage setupImage()
532 BufferedImage lcimg = null;
534 int charWidth = av.getCharWidth();
535 int charHeight = av.getCharHeight();
537 int width = getWidth();
538 int height = getHeight();
540 width -= (width % charWidth);
541 height -= (height % charHeight);
543 if ((width < 1) || (height < 1))
550 lcimg = new BufferedImage(width, height,
551 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
552 } catch (OutOfMemoryError er)
556 "Group image OutOfMemory Redraw Error.\n" + er);
557 new OOMWarning("Creating alignment image for display", er);
566 * Returns the visible width of the canvas in residues, after allowing for
567 * East or West scales (if shown)
570 * the width in pixels (possibly including scales)
574 public int getWrappedCanvasWidth(int canvasWidth)
576 int charWidth = av.getCharWidth();
578 FontMetrics fm = getFontMetrics(av.getFont());
582 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
584 labelWidth = getLabelWidth(fm);
587 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
589 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
591 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
595 * Returns a pixel width sufficient to show the largest sequence coordinate
596 * (end position) in the alignment, calculated as the FontMetrics width of
597 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
598 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
599 * half a character width space on either side.
604 protected int getLabelWidth(FontMetrics fm)
607 * find the biggest sequence end position we need to show
608 * (note this is not necessarily the sequence length)
611 AlignmentI alignment = av.getAlignment();
612 for (int i = 0; i < alignment.getHeight(); i++)
614 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
618 for (int i = maxWidth; i > 0; i /= 10)
623 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
627 * Draws as many widths of a wrapped alignment as can fit in the visible
632 * available width in pixels
633 * @param canvasHeight
634 * available height in pixels
636 * the first column (0...) of the alignment to draw
638 public void drawWrappedPanel(Graphics g, int canvasWidth,
639 int canvasHeight, final int startColumn)
641 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
644 av.setWrappedWidth(wrappedWidthInResidues);
646 ViewportRanges ranges = av.getRanges();
647 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
650 * draw one width at a time (including any scales or annotation shown),
651 * until we have run out of either alignment or vertical space available
653 int ypos = wrappedSpaceAboveAlignment;
654 int maxWidth = ranges.getVisibleAlignmentWidth();
656 int start = startColumn;
657 int currentWidth = 0;
658 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
661 .min(maxWidth, start + wrappedWidthInResidues - 1);
662 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
663 ypos += wrappedRepeatHeightPx;
664 start += wrappedWidthInResidues;
668 drawWrappedDecorators(g, startColumn);
672 * Calculates and saves values needed when rendering a wrapped alignment.
673 * These depend on many factors, including
675 * <li>canvas width and height</li>
676 * <li>number of visible sequences, and height of annotations if shown</li>
677 * <li>font and character width</li>
678 * <li>whether scales are shown left, right or above the alignment</li>
682 * @param canvasHeight
683 * @return the number of residue columns in each width
685 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
687 int charHeight = av.getCharHeight();
690 * vertical space in pixels between wrapped widths of alignment
691 * - one character height, or two if scale above is drawn
693 wrappedSpaceAboveAlignment = charHeight
694 * (av.getScaleAboveWrapped() ? 2 : 1);
697 * height in pixels of the wrapped widths
699 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
701 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
703 // add annotations panel height if shown
704 wrappedRepeatHeightPx += getAnnotationHeight();
707 * number of visible widths (the last one may be part height),
708 * ensuring a part height includes at least one sequence
710 ViewportRanges ranges = av.getRanges();
711 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
712 int remainder = canvasHeight % wrappedRepeatHeightPx;
713 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
715 wrappedVisibleWidths++;
719 * compute width in residues; this also sets East and West label widths
721 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
724 * limit visibleWidths to not exceed width of alignment
726 int xMax = ranges.getVisibleAlignmentWidth();
727 int startToEnd = xMax - ranges.getStartRes();
728 int maxWidths = startToEnd / wrappedWidthInResidues;
729 if (startToEnd % wrappedWidthInResidues > 0)
733 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
735 return wrappedWidthInResidues;
739 * Draws one width of a wrapped alignment, including sequences and
740 * annnotations, if shown, but not scales or hidden column markers
746 * @param canvasHeight
748 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
749 int endColumn, int canvasHeight)
751 ViewportRanges ranges = av.getRanges();
752 int viewportWidth = ranges.getViewportWidth();
754 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
757 * move right before drawing by the width of the scale left (if any)
758 * plus column offset from left margin (usually zero, but may be non-zero
759 * when fast painting is drawing just a few columns)
761 int charWidth = av.getCharWidth();
762 int xOffset = labelWidthWest
763 + ((startColumn - ranges.getStartRes()) % viewportWidth)
765 g.translate(xOffset, 0);
767 // When printing we have an extra clipped region,
768 // the Printable page which we need to account for here
769 Shape clip = g.getClip();
773 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
777 g.setClip(0, (int) clip.getBounds().getY(),
778 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
782 * white fill the region to be drawn (so incremental fast paint doesn't
783 * scribble over an existing image)
785 gg.setColor(Color.white);
786 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
787 wrappedRepeatHeightPx);
789 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
792 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
794 if (av.isShowAnnotation())
796 g.translate(0, cHeight + ypos + 3);
797 if (annotations == null)
799 annotations = new AnnotationPanel(av);
802 annotations.renderer.drawComponent(annotations, av, g, -1,
803 startColumn, endx + 1);
804 g.translate(0, -cHeight - ypos - 3);
807 g.translate(-xOffset, 0);
811 * Draws scales left, right and above (if shown), and any hidden column
812 * markers, on all widths of the wrapped alignment
817 protected void drawWrappedDecorators(Graphics g, final int startColumn)
819 int charWidth = av.getCharWidth();
821 g.setFont(av.getFont());
822 g.setColor(Color.black);
824 int ypos = wrappedSpaceAboveAlignment;
825 ViewportRanges ranges = av.getRanges();
826 int viewportWidth = ranges.getViewportWidth();
827 int maxWidth = ranges.getVisibleAlignmentWidth();
829 int startCol = startColumn;
831 while (widthsDrawn < wrappedVisibleWidths)
833 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
835 if (av.getScaleLeftWrapped())
837 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
840 if (av.getScaleRightWrapped())
842 int x = labelWidthWest + viewportWidth * charWidth;
844 drawVerticalScale(g, startCol, endColumn, ypos, false);
849 * white fill region of scale above and hidden column markers
850 * (to support incremental fast paint of image)
852 g.translate(labelWidthWest, 0);
853 g.setColor(Color.white);
854 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
855 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
856 g.setColor(Color.black);
857 g.translate(-labelWidthWest, 0);
859 g.translate(labelWidthWest, 0);
861 if (av.getScaleAboveWrapped())
863 drawNorthScale(g, startCol, endColumn, ypos);
866 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
868 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
871 g.translate(-labelWidthWest, 0);
873 ypos += wrappedRepeatHeightPx;
874 startCol += viewportWidth;
880 * Draws markers (triangles) above hidden column positions between startColumn
888 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
889 int startColumn, int endColumn)
891 int charHeight = av.getCharHeight();
892 int charWidth = av.getCharWidth();
894 g.setColor(Color.blue);
896 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
898 Iterator<Integer> it = hidden.getBoundedStartIterator(startColumn,
902 res = it.next() - startColumn;
904 if (res < 0 || res > endColumn - startColumn + 1)
910 * draw a downward-pointing triangle at the hidden columns location
911 * (before the following visible column)
913 int xMiddle = res * charWidth;
914 int[] xPoints = new int[] { xMiddle - charHeight / 4,
915 xMiddle + charHeight / 4, xMiddle };
916 int yTop = ypos - (charHeight / 2);
917 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
918 g.fillPolygon(xPoints, yPoints, 3);
923 * Draw a selection group over a wrapped alignment
925 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
927 int canvasHeight, int startRes)
929 int charHeight = av.getCharHeight();
930 int charWidth = av.getCharWidth();
932 // height gap above each panel
933 int hgap = charHeight;
934 if (av.getScaleAboveWrapped())
939 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
941 int cHeight = av.getAlignment().getHeight() * charHeight;
943 int startx = startRes;
945 int ypos = hgap; // vertical offset
946 int maxwidth = av.getAlignment().getWidth();
948 if (av.hasHiddenColumns())
950 maxwidth = av.getAlignment().getHiddenColumns()
951 .absoluteToVisibleColumn(maxwidth);
954 // chop the wrapped alignment extent up into panel-sized blocks and treat
955 // each block as if it were a block from an unwrapped alignment
956 while ((ypos <= canvasHeight) && (startx < maxwidth))
958 // set end value to be start + width, or maxwidth, whichever is smaller
959 endx = startx + cWidth - 1;
966 g.translate(labelWidthWest, 0);
968 drawUnwrappedSelection(g, group, startx, endx, 0,
969 av.getAlignment().getHeight() - 1,
972 g.translate(-labelWidthWest, 0);
974 // update vertical offset
975 ypos += cHeight + getAnnotationHeight() + hgap;
977 // update horizontal offset
982 int getAnnotationHeight()
984 if (!av.isShowAnnotation())
989 if (annotations == null)
991 annotations = new AnnotationPanel(av);
994 return annotations.adjustPanelHeight();
998 * Draws the visible region of the alignment on the graphics context. If there
999 * are hidden column markers in the visible region, then each sub-region
1000 * between the markers is drawn separately, followed by the hidden column
1004 * the graphics context, positioned at the first residue to be drawn
1006 * offset of the first column to draw (0..)
1008 * offset of the last column to draw (0..)
1010 * offset of the first sequence to draw (0..)
1012 * offset of the last sequence to draw (0..)
1014 * vertical offset at which to draw (for wrapped alignments)
1016 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1017 final int startSeq, final int endSeq, final int yOffset)
1019 int charHeight = av.getCharHeight();
1020 int charWidth = av.getCharWidth();
1022 if (!av.hasHiddenColumns())
1024 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1032 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1033 VisibleContigsIterator regions = hidden
1034 .getVisContigsIterator(startRes, endRes + 1, true);
1036 while (regions.hasNext())
1038 int[] region = regions.next();
1039 blockEnd = region[1];
1040 blockStart = region[0];
1043 * draw up to just before the next hidden region, or the end of
1044 * the visible region, whichever comes first
1046 g1.translate(screenY * charWidth, 0);
1048 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1051 * draw the downline of the hidden column marker (ScalePanel draws the
1052 * triangle on top) if we reached it
1054 if (av.getShowHiddenMarkers()
1055 && (regions.hasNext() || regions.endsAtHidden()))
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;
1072 * Draws a region of the visible alignment
1076 * offset of the first column in the visible region (0..)
1078 * offset of the last column in the visible region (0..)
1080 * offset of the first sequence in the visible region (0..)
1082 * offset of the last sequence in the visible region (0..)
1084 * vertical offset at which to draw (for wrapped alignments)
1086 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1087 int endSeq, int offset)
1089 int charHeight = av.getCharHeight();
1090 int charWidth = av.getCharWidth();
1092 g.setFont(av.getFont());
1093 seqRdr.prepare(g, av.isRenderGaps());
1097 // / First draw the sequences
1098 // ///////////////////////////
1099 for (int i = startSeq; i <= endSeq; i++)
1101 nextSeq = av.getAlignment().getSequenceAt(i);
1102 if (nextSeq == null)
1104 // occasionally, a race condition occurs such that the alignment row is
1108 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1109 startRes, endRes, offset + ((i - startSeq) * charHeight));
1111 if (av.isShowSequenceFeatures())
1113 fr.drawSequence(g, nextSeq, startRes, endRes,
1114 offset + ((i - startSeq) * charHeight), false);
1118 * highlight search Results once sequence has been drawn
1120 if (av.hasSearchResults())
1122 SearchResultsI searchResults = av.getSearchResults();
1123 int[] visibleResults = searchResults.getResults(nextSeq,
1125 if (visibleResults != null)
1127 for (int r = 0; r < visibleResults.length; r += 2)
1129 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1130 visibleResults[r + 1], (visibleResults[r] - startRes)
1132 + ((i - startSeq) * charHeight));
1138 if (av.getSelectionGroup() != null
1139 || av.getAlignment().getGroups().size() > 0)
1141 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1146 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1147 int startSeq, int endSeq, int offset)
1149 Graphics2D g = (Graphics2D) g1;
1151 // ///////////////////////////////////
1152 // Now outline any areas if necessary
1153 // ///////////////////////////////////
1155 SequenceGroup group = null;
1156 int groupIndex = -1;
1158 if (av.getAlignment().getGroups().size() > 0)
1160 group = av.getAlignment().getGroups().get(0);
1166 g.setStroke(new BasicStroke());
1167 g.setColor(group.getOutlineColour());
1171 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1176 g.setStroke(new BasicStroke());
1178 if (groupIndex >= av.getAlignment().getGroups().size())
1183 group = av.getAlignment().getGroups().get(groupIndex);
1185 } while (groupIndex < av.getAlignment().getGroups().size());
1193 * Draw the selection group as a separate image and overlay
1195 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1196 int startSeq, int endSeq)
1198 // get a new image of the correct size
1199 BufferedImage selectionImage = setupImage();
1201 if (selectionImage == null)
1206 SequenceGroup group = av.getSelectionGroup();
1213 // set up drawing colour
1214 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1216 setupSelectionGroup(g, selectionImage);
1218 if (!av.getWrapAlignment())
1220 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1225 drawWrappedSelection(g, group, getWidth(), getHeight(),
1226 av.getRanges().getStartRes());
1230 return selectionImage;
1234 * Draw the cursor as a separate image and overlay
1237 * start residue of area to draw cursor in
1239 * end residue of area to draw cursor in
1241 * start sequence of area to draw cursor in
1243 * end sequence of are to draw cursor in
1244 * @return a transparent image of the same size as the sequence canvas, with
1245 * the cursor drawn on it, if any
1247 private void drawCursor(Graphics g, int startRes, int endRes,
1251 // convert the cursorY into a position on the visible alignment
1252 int cursor_ypos = cursorY;
1254 // don't do work unless we have to
1255 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1259 int startx = startRes;
1262 // convert the cursorX into a position on the visible alignment
1263 int cursor_xpos = av.getAlignment().getHiddenColumns()
1264 .absoluteToVisibleColumn(cursorX);
1266 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1269 if (av.getWrapAlignment())
1271 // work out the correct offsets for the cursor
1272 int charHeight = av.getCharHeight();
1273 int charWidth = av.getCharWidth();
1274 int canvasWidth = getWidth();
1275 int canvasHeight = getHeight();
1277 // height gap above each panel
1278 int hgap = charHeight;
1279 if (av.getScaleAboveWrapped())
1284 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1286 int cHeight = av.getAlignment().getHeight() * charHeight;
1288 endx = startx + cWidth - 1;
1289 int ypos = hgap; // vertical offset
1291 // iterate down the wrapped panels
1292 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1294 // update vertical offset
1295 ypos += cHeight + getAnnotationHeight() + hgap;
1297 // update horizontal offset
1299 endx = startx + cWidth - 1;
1302 xoffset = labelWidthWest;
1305 // now check if cursor is within range for x values
1306 if (cursor_xpos >= startx && cursor_xpos <= endx)
1308 // get the character the cursor is drawn at
1309 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1310 char s = seq.getCharAt(cursorX);
1312 seqRdr.drawCursor(g, s,
1313 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1314 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1322 * Set up graphics for selection group
1324 private void setupSelectionGroup(Graphics2D g,
1325 BufferedImage selectionImage)
1327 // set background to transparent
1328 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1329 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1331 // set up foreground to draw red dashed line
1332 g.setComposite(AlphaComposite.Src);
1333 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1334 BasicStroke.JOIN_ROUND, 3f, new float[]
1336 g.setColor(Color.RED);
1340 * Draw a selection group over an unwrapped alignment
1341 * @param g graphics object to draw with
1342 * @param group selection group
1343 * @param startRes start residue of area to draw
1344 * @param endRes end residue of area to draw
1345 * @param startSeq start sequence of area to draw
1346 * @param endSeq end sequence of area to draw
1347 * @param offset vertical offset (used when called from wrapped alignment code)
1349 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1350 int startRes, int endRes, int startSeq, int endSeq, int offset)
1352 int charWidth = av.getCharWidth();
1354 if (!av.hasHiddenColumns())
1356 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1361 // package into blocks of visible columns
1366 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1367 VisibleContigsIterator regions = hidden
1368 .getVisContigsIterator(startRes, endRes + 1, true);
1369 while (regions.hasNext())
1371 int[] region = regions.next();
1372 blockEnd = region[1];
1373 blockStart = region[0];
1375 g.translate(screenY * charWidth, 0);
1376 drawPartialGroupOutline(g, group,
1377 blockStart, blockEnd, startSeq, endSeq, offset);
1379 g.translate(-screenY * charWidth, 0);
1380 screenY += blockEnd - blockStart + 1;
1386 * Draw the selection group as a separate image and overlay
1388 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1389 int startRes, int endRes, int startSeq, int endSeq,
1392 int charHeight = av.getCharHeight();
1393 int charWidth = av.getCharWidth();
1395 int visWidth = (endRes - startRes + 1) * charWidth;
1399 boolean inGroup = false;
1407 for (i = startSeq; i <= endSeq; i++)
1409 // position of start residue of group relative to startRes, in pixels
1410 sx = (group.getStartRes() - startRes) * charWidth;
1412 // width of group in pixels
1413 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1416 sy = verticalOffset + (i - startSeq) * charHeight;
1418 if (sx + xwidth < 0 || sx > visWidth)
1423 if ((sx <= (endRes - startRes) * charWidth)
1424 && group.getSequences(null)
1425 .contains(av.getAlignment().getSequenceAt(i)))
1427 if ((bottom == -1) && !group.getSequences(null)
1428 .contains(av.getAlignment().getSequenceAt(i + 1)))
1430 bottom = sy + charHeight;
1435 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1436 .contains(av.getAlignment().getSequenceAt(i - 1)))
1449 // if start position is visible, draw vertical line to left of
1451 if (sx >= 0 && sx < visWidth)
1453 g.drawLine(sx, oldY, sx, sy);
1456 // if end position is visible, draw vertical line to right of
1458 if (sx + xwidth < visWidth)
1460 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1469 // don't let width extend beyond current block, or group extent
1471 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1473 xwidth = (endRes - startRes + 1) * charWidth - sx;
1476 // draw horizontal line at top of group
1479 g.drawLine(sx, top, sx + xwidth, top);
1483 // draw horizontal line at bottom of group
1486 g.drawLine(sx, bottom, sx + xwidth, bottom);
1497 sy = verticalOffset + ((i - startSeq) * charHeight);
1498 if (sx >= 0 && sx < visWidth)
1500 g.drawLine(sx, oldY, sx, sy);
1503 if (sx + xwidth < visWidth)
1505 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1514 if (sx + xwidth > visWidth)
1518 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1520 xwidth = (endRes - startRes + 1) * charWidth;
1525 g.drawLine(sx, top, sx + xwidth, top);
1531 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1540 * Highlights search results in the visible region by rendering as white text
1541 * on a black background. Any previous highlighting is removed. Answers true
1542 * if any highlight was left on the visible alignment (so status bar should be
1543 * set to match), else false.
1545 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1546 * alignment had to be scrolled to show the highlighted region, then it should
1547 * be fully redrawn, otherwise a fast paint can be performed. This argument
1548 * could be removed if fast paint of scrolled wrapped alignment is coded in
1549 * future (JAL-2609).
1552 * @param noFastPaint
1555 public boolean highlightSearchResults(SearchResultsI results,
1556 boolean noFastPaint)
1562 boolean wrapped = av.getWrapAlignment();
1565 fastPaint = !noFastPaint;
1566 fastpainting = fastPaint;
1569 * to avoid redrawing the whole visible region, we instead
1570 * redraw just the minimal regions to remove previous highlights
1573 SearchResultsI previous = av.getSearchResults();
1574 av.setSearchResults(results);
1575 boolean redrawn = false;
1576 boolean drawn = false;
1579 redrawn = drawMappedPositionsWrapped(previous);
1580 drawn = drawMappedPositionsWrapped(results);
1585 redrawn = drawMappedPositions(previous);
1586 drawn = drawMappedPositions(results);
1591 * if highlights were either removed or added, repaint
1599 * return true only if highlights were added
1605 fastpainting = false;
1610 * Redraws the minimal rectangle in the visible region (if any) that includes
1611 * mapped positions of the given search results. Whether or not positions are
1612 * highlighted depends on the SearchResults set on the Viewport. This allows
1613 * this method to be called to either clear or set highlighting. Answers true
1614 * if any positions were drawn (in which case a repaint is still required),
1620 protected boolean drawMappedPositions(SearchResultsI results)
1622 if (results == null)
1628 * calculate the minimal rectangle to redraw that
1629 * includes both new and existing search results
1631 int firstSeq = Integer.MAX_VALUE;
1633 int firstCol = Integer.MAX_VALUE;
1635 boolean matchFound = false;
1637 ViewportRanges ranges = av.getRanges();
1638 int firstVisibleColumn = ranges.getStartRes();
1639 int lastVisibleColumn = ranges.getEndRes();
1640 AlignmentI alignment = av.getAlignment();
1641 if (av.hasHiddenColumns())
1643 firstVisibleColumn = alignment.getHiddenColumns()
1644 .visibleToAbsoluteColumn(firstVisibleColumn);
1645 lastVisibleColumn = alignment.getHiddenColumns()
1646 .visibleToAbsoluteColumn(lastVisibleColumn);
1649 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1650 .getEndSeq(); seqNo++)
1652 SequenceI seq = alignment.getSequenceAt(seqNo);
1654 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1656 if (visibleResults != null)
1658 for (int i = 0; i < visibleResults.length - 1; i += 2)
1660 int firstMatchedColumn = visibleResults[i];
1661 int lastMatchedColumn = visibleResults[i + 1];
1662 if (firstMatchedColumn <= lastVisibleColumn
1663 && lastMatchedColumn >= firstVisibleColumn)
1666 * found a search results match in the visible region -
1667 * remember the first and last sequence matched, and the first
1668 * and last visible columns in the matched positions
1671 firstSeq = Math.min(firstSeq, seqNo);
1672 lastSeq = Math.max(lastSeq, seqNo);
1673 firstMatchedColumn = Math.max(firstMatchedColumn,
1674 firstVisibleColumn);
1675 lastMatchedColumn = Math.min(lastMatchedColumn,
1677 firstCol = Math.min(firstCol, firstMatchedColumn);
1678 lastCol = Math.max(lastCol, lastMatchedColumn);
1686 if (av.hasHiddenColumns())
1688 firstCol = alignment.getHiddenColumns()
1689 .absoluteToVisibleColumn(firstCol);
1690 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1692 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1693 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1694 gg.translate(transX, transY);
1695 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1696 gg.translate(-transX, -transY);
1703 public void propertyChange(PropertyChangeEvent evt)
1705 String eventName = evt.getPropertyName();
1707 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1713 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1721 if (eventName.equals(ViewportRanges.STARTRES)
1722 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1724 // Make sure we're not trying to draw a panel
1725 // larger than the visible window
1726 if (eventName.equals(ViewportRanges.STARTRES))
1728 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1732 scrollX = ((int[]) evt.getNewValue())[0]
1733 - ((int[]) evt.getOldValue())[0];
1735 ViewportRanges vpRanges = av.getRanges();
1737 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1738 if (scrollX > range)
1742 else if (scrollX < -range)
1747 // Both scrolling and resizing change viewport ranges: scrolling changes
1748 // both start and end points, but resize only changes end values.
1749 // Here we only want to fastpaint on a scroll, with resize using a normal
1750 // paint, so scroll events are identified as changes to the horizontal or
1751 // vertical start value.
1752 if (eventName.equals(ViewportRanges.STARTRES))
1754 if (av.getWrapAlignment())
1756 fastPaintWrapped(scrollX);
1760 fastPaint(scrollX, 0);
1763 else if (eventName.equals(ViewportRanges.STARTSEQ))
1766 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1768 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1770 if (av.getWrapAlignment())
1772 fastPaintWrapped(scrollX);
1776 fastPaint(scrollX, 0);
1778 // bizarrely, we only need to scroll on the x value here as fastpaint
1779 // copies the full height of the image anyway. Passing in the y value
1780 // causes nasty repaint artefacts, which only disappear on a full
1787 * Does a minimal update of the image for a scroll movement. This method
1788 * handles scroll movements of up to one width of the wrapped alignment (one
1789 * click in the vertical scrollbar). Larger movements (for example after a
1790 * scroll to highlight a mapped position) trigger a full redraw instead.
1793 * number of positions scrolled (right if positive, left if negative)
1795 protected void fastPaintWrapped(int scrollX)
1797 ViewportRanges ranges = av.getRanges();
1799 // if (Math.abs(scrollX) > ranges.getViewportWidth())
1800 // JAL-2836, 2836 temporarily removed wrapped fastpaint for release 2.10.3
1804 * shift of more than one view width is
1805 * overcomplicated to handle in this method
1812 if (fastpainting || gg == null)
1818 fastpainting = true;
1822 calculateWrappedGeometry(getWidth(), getHeight());
1825 * relocate the regions of the alignment that are still visible
1827 shiftWrappedAlignment(-scrollX);
1830 * add new columns (sequence, annotation)
1831 * - at top left if scrollX < 0
1832 * - at right of last two widths if scrollX > 0
1836 int startRes = ranges.getStartRes();
1837 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1838 - scrollX - 1, getHeight());
1842 fastPaintWrappedAddRight(scrollX);
1846 * draw all scales (if shown) and hidden column markers
1848 drawWrappedDecorators(gg, ranges.getStartRes());
1853 fastpainting = false;
1858 * Draws the specified number of columns at the 'end' (bottom right) of a
1859 * wrapped alignment view, including sequences and annotations if shown, but
1860 * not scales. Also draws the same number of columns at the right hand end of
1861 * the second last width shown, if the last width is not full height (so
1862 * cannot simply be copied from the graphics image).
1866 protected void fastPaintWrappedAddRight(int columns)
1873 ViewportRanges ranges = av.getRanges();
1874 int viewportWidth = ranges.getViewportWidth();
1875 int charWidth = av.getCharWidth();
1878 * draw full height alignment in the second last row, last columns, if the
1879 * last row was not full height
1881 int visibleWidths = wrappedVisibleWidths;
1882 int canvasHeight = getHeight();
1883 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1885 if (lastWidthPartHeight)
1887 int widthsAbove = Math.max(0, visibleWidths - 2);
1888 int ypos = wrappedRepeatHeightPx * widthsAbove
1889 + wrappedSpaceAboveAlignment;
1890 int endRes = ranges.getEndRes();
1891 endRes += widthsAbove * viewportWidth;
1892 int startRes = endRes - columns;
1893 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1897 * white fill first to erase annotations
1899 gg.translate(xOffset, 0);
1900 gg.setColor(Color.white);
1901 gg.fillRect(labelWidthWest, ypos,
1902 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1903 gg.translate(-xOffset, 0);
1905 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1909 * draw newly visible columns in last wrapped width (none if we
1910 * have reached the end of the alignment)
1911 * y-offset for drawing last width is height of widths above,
1914 int widthsAbove = visibleWidths - 1;
1915 int ypos = wrappedRepeatHeightPx * widthsAbove
1916 + wrappedSpaceAboveAlignment;
1917 int endRes = ranges.getEndRes();
1918 endRes += widthsAbove * viewportWidth;
1919 int startRes = endRes - columns + 1;
1922 * white fill first to erase annotations
1924 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1926 gg.translate(xOffset, 0);
1927 gg.setColor(Color.white);
1928 int width = viewportWidth * charWidth - xOffset;
1929 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1930 gg.translate(-xOffset, 0);
1932 gg.setFont(av.getFont());
1933 gg.setColor(Color.black);
1935 if (startRes < ranges.getVisibleAlignmentWidth())
1937 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1941 * and finally, white fill any space below the visible alignment
1943 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1944 if (heightBelow > 0)
1946 gg.setColor(Color.white);
1947 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1952 * Shifts the visible alignment by the specified number of columns - left if
1953 * negative, right if positive. Copies and moves sequences and annotations (if
1954 * shown). Scales, hidden column markers and any newly visible columns must be
1959 protected void shiftWrappedAlignment(int positions)
1965 int charWidth = av.getCharWidth();
1967 int canvasHeight = getHeight();
1968 ViewportRanges ranges = av.getRanges();
1969 int viewportWidth = ranges.getViewportWidth();
1970 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1972 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1973 int xMax = ranges.getVisibleAlignmentWidth();
1978 * shift right (after scroll left)
1979 * for each wrapped width (starting with the last), copy (width-positions)
1980 * columns from the left margin to the right margin, and copy positions
1981 * columns from the right margin of the row above (if any) to the
1982 * left margin of the current row
1986 * get y-offset of last wrapped width, first row of sequences
1988 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1989 y += wrappedSpaceAboveAlignment;
1990 int copyFromLeftStart = labelWidthWest;
1991 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1995 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1996 positions * charWidth, 0);
1999 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2000 positions * charWidth, heightToCopy, -widthToCopy,
2001 wrappedRepeatHeightPx);
2004 y -= wrappedRepeatHeightPx;
2010 * shift left (after scroll right)
2011 * for each wrapped width (starting with the first), copy (width-positions)
2012 * columns from the right margin to the left margin, and copy positions
2013 * columns from the left margin of the row below (if any) to the
2014 * right margin of the current row
2016 int xpos = av.getRanges().getStartRes();
2017 int y = wrappedSpaceAboveAlignment;
2018 int copyFromRightStart = labelWidthWest - positions * charWidth;
2020 while (y < canvasHeight)
2022 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2023 positions * charWidth, 0);
2024 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2025 && (xpos + viewportWidth <= xMax))
2027 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2028 * charWidth, heightToCopy, widthToCopy,
2029 -wrappedRepeatHeightPx);
2032 y += wrappedRepeatHeightPx;
2033 xpos += viewportWidth;
2040 * Redraws any positions in the search results in the visible region of a
2041 * wrapped alignment. Any highlights are drawn depending on the search results
2042 * set on the Viewport, not the <code>results</code> argument. This allows
2043 * this method to be called either to clear highlights (passing the previous
2044 * search results), or to draw new highlights.
2049 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2051 if (results == null)
2055 int charHeight = av.getCharHeight();
2057 boolean matchFound = false;
2059 calculateWrappedGeometry(getWidth(), getHeight());
2060 int wrappedWidth = av.getWrappedWidth();
2061 int wrappedHeight = wrappedRepeatHeightPx;
2063 ViewportRanges ranges = av.getRanges();
2064 int canvasHeight = getHeight();
2065 int repeats = canvasHeight / wrappedHeight;
2066 if (canvasHeight / wrappedHeight > 0)
2071 int firstVisibleColumn = ranges.getStartRes();
2072 int lastVisibleColumn = ranges.getStartRes() + repeats
2073 * ranges.getViewportWidth() - 1;
2075 AlignmentI alignment = av.getAlignment();
2076 if (av.hasHiddenColumns())
2078 firstVisibleColumn = alignment.getHiddenColumns()
2079 .visibleToAbsoluteColumn(firstVisibleColumn);
2080 lastVisibleColumn = alignment.getHiddenColumns()
2081 .visibleToAbsoluteColumn(lastVisibleColumn);
2084 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2086 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2087 .getEndSeq(); seqNo++)
2089 SequenceI seq = alignment.getSequenceAt(seqNo);
2091 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2093 if (visibleResults != null)
2095 for (int i = 0; i < visibleResults.length - 1; i += 2)
2097 int firstMatchedColumn = visibleResults[i];
2098 int lastMatchedColumn = visibleResults[i + 1];
2099 if (firstMatchedColumn <= lastVisibleColumn
2100 && lastMatchedColumn >= firstVisibleColumn)
2103 * found a search results match in the visible region
2105 firstMatchedColumn = Math.max(firstMatchedColumn,
2106 firstVisibleColumn);
2107 lastMatchedColumn = Math.min(lastMatchedColumn,
2111 * draw each mapped position separately (as contiguous positions may
2112 * wrap across lines)
2114 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2116 int displayColumn = mappedPos;
2117 if (av.hasHiddenColumns())
2119 displayColumn = alignment.getHiddenColumns()
2120 .absoluteToVisibleColumn(displayColumn);
2124 * transX: offset from left edge of canvas to residue position
2126 int transX = labelWidthWest
2127 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2128 * av.getCharWidth();
2131 * transY: offset from top edge of canvas to residue position
2133 int transY = gapHeight;
2134 transY += (displayColumn - ranges.getStartRes())
2135 / wrappedWidth * wrappedHeight;
2136 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2139 * yOffset is from graphics origin to start of visible region
2141 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2142 if (transY < getHeight())
2145 gg.translate(transX, transY);
2146 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2148 gg.translate(-transX, -transY);
2160 * Answers the width in pixels of the left scale labels (0 if not shown)
2164 int getLabelWidthWest()
2166 return labelWidthWest;