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.JPanel;
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 JPanel 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 // Don't do this! Graphics handles are supposed to be transient
93 //private Graphics2D gg;
96 * Creates a new SeqCanvas object.
100 public SeqCanvas(AlignmentPanel ap)
103 fr = new FeatureRenderer(ap);
104 seqRdr = new SequenceRenderer(av);
105 setLayout(new BorderLayout());
106 PaintRefresher.Register(this, av.getSequenceSetId());
107 setBackground(Color.white);
109 av.getRanges().addPropertyChangeListener(this);
112 public SequenceRenderer getSequenceRenderer()
117 public FeatureRenderer getFeatureRenderer()
123 * Draws the scale above a region of a wrapped alignment, consisting of a
124 * column number every major interval (10 columns).
127 * the graphics context to draw on, positioned at the start (bottom
128 * left) of the line on which to draw any scale marks
130 * start alignment column (0..)
132 * end alignment column (0..)
134 * y offset to draw at
136 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
138 int charHeight = av.getCharHeight();
139 int charWidth = av.getCharWidth();
142 * white fill the scale space (for the fastPaint case)
144 g.setColor(Color.white);
145 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
146 charHeight * 3 / 2 + 2);
147 g.setColor(Color.black);
149 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
151 for (ScaleMark mark : marks)
153 int mpos = mark.column; // (i - startx - 1)
158 String mstring = mark.text;
164 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
168 * draw a tick mark below the column number, centred on the column;
169 * height of tick mark is 4 pixels less than half a character
171 int xpos = (mpos * charWidth) + (charWidth / 2);
172 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
178 * Draw the scale to the left or right of a wrapped alignment
181 * graphics context, positioned at the start of the scale to be drawn
183 * first column of wrapped width (0.. excluding any hidden columns)
185 * last column of wrapped width (0.. excluding any hidden columns)
187 * vertical offset at which to begin the scale
189 * if true, scale is left of residues, if false, scale is right
191 void drawVerticalScale(Graphics g, final int startx, final int endx,
192 final int ypos, final boolean left)
194 int charHeight = av.getCharHeight();
195 int charWidth = av.getCharWidth();
197 int yPos = ypos + charHeight;
201 if (av.hasHiddenColumns())
203 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
204 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
205 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
207 FontMetrics fm = getFontMetrics(av.getFont());
209 for (int i = 0; i < av.getAlignment().getHeight(); i++)
211 SequenceI seq = av.getAlignment().getSequenceAt(i);
214 * find sequence position of first non-gapped position -
215 * to the right if scale left, to the left if scale right
217 int index = left ? startX : endX;
219 while (index >= startX && index <= endX)
221 if (!Comparison.isGap(seq.getCharAt(index)))
223 value = seq.findPosition(index);
238 * white fill the space for the scale
240 g.setColor(Color.white);
241 int y = (yPos + (i * charHeight)) - (charHeight / 5);
242 // fillRect origin is top left of rectangle
243 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
249 * draw scale value, right justified within its width less half a
250 * character width padding on the right
252 int labelSpace = left ? labelWidthWest : labelWidthEast;
253 labelSpace -= charWidth / 2; // leave space to the right
254 String valueAsString = String.valueOf(value);
255 int labelLength = fm.stringWidth(valueAsString);
256 int xOffset = labelSpace - labelLength;
257 g.setColor(Color.black);
258 g.drawString(valueAsString, xOffset, y);
265 * Does a fast paint of an alignment in response to a scroll. Most of the
266 * visible region is simply copied and shifted, and then any newly visible
267 * columns or rows are drawn. The scroll may be horizontal or vertical, but
268 * not both at once. Scrolling may be the result of
270 * <li>dragging a scroll bar</li>
271 * <li>clicking in the scroll bar</li>
272 * <li>scrolling by trackpad, middle mouse button, or other device</li>
273 * <li>by moving the box in the Overview window</li>
274 * <li>programmatically to make a highlighted position visible</li>
278 * columns to shift right (positive) or left (negative)
280 * rows to shift down (positive) or up (negative)
282 public void fastPaint(int horizontal, int vertical)
284 if (fastpainting || img == null)
293 int charHeight = av.getCharHeight();
294 int charWidth = av.getCharWidth();
296 ViewportRanges ranges = av.getRanges();
297 int startRes = ranges.getStartRes();
298 int endRes = ranges.getEndRes();
299 int startSeq = ranges.getStartSeq();
300 int endSeq = ranges.getEndSeq();
304 Graphics gg = img.getGraphics();
305 gg.copyArea(horizontal * charWidth, vertical * charHeight,
306 img.getWidth(), img.getHeight(), -horizontal * charWidth,
307 -vertical * charHeight);
309 if (horizontal > 0) // scrollbar pulled right, image to the left
311 transX = (endRes - startRes - horizontal) * charWidth;
312 startRes = endRes - horizontal;
314 else if (horizontal < 0)
316 endRes = startRes - horizontal;
319 if (vertical > 0) // scroll down
321 startSeq = endSeq - vertical;
323 if (startSeq < ranges.getStartSeq())
324 { // ie scrolling too fast, more than a page at a time
325 startSeq = ranges.getStartSeq();
329 transY = img.getHeight() - ((vertical + 1) * charHeight);
332 else if (vertical < 0)
334 endSeq = startSeq - vertical;
336 if (endSeq > ranges.getEndSeq())
338 endSeq = ranges.getEndSeq();
342 gg.translate(transX, transY);
343 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
344 gg.translate(-transX, -transY);
347 // Call repaint on alignment panel so that repaints from other alignment
348 // panel components can be aggregated. Otherwise performance of the
349 // overview window and others may be adversely affected.
350 av.getAlignPanel().repaint();
353 fastpainting = false;
358 public void paintComponent(Graphics g)
360 super.paintComponent(g);
362 int charHeight = av.getCharHeight();
363 int charWidth = av.getCharWidth();
365 ViewportRanges ranges = av.getRanges();
367 int width = getWidth();
368 int height = getHeight();
370 width -= (width % charWidth);
371 height -= (height % charHeight);
373 // selectImage is the selection group outline image
374 BufferedImage selectImage = drawSelectionGroup(ranges.getStartRes(),
375 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
377 if ((img != null) && (fastPaint
378 || (getVisibleRect().width != g.getClipBounds().width)
379 || (getVisibleRect().height != g.getClipBounds().height)))
381 BufferedImage lcimg = buildLocalImage(selectImage);
382 g.drawImage(lcimg, 0, 0, this);
385 else if ((width > 0) && (height > 0))
387 // img is a cached version of the last view we drew, if any
388 // if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
400 Graphics2D gg = (Graphics2D) img.getGraphics();
401 gg.setFont(av.getFont());
405 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
406 RenderingHints.VALUE_ANTIALIAS_ON);
409 gg.setColor(Color.white);
410 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
412 if (av.getWrapAlignment())
414 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
418 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
419 ranges.getStartSeq(), ranges.getEndSeq(), 0);
425 // lcimg is a local *copy* of img which we'll draw selectImage on top of
426 BufferedImage lcimg = buildLocalImage(selectImage);
427 g.drawImage(lcimg, 0, 0, this);
433 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
434 ranges.getStartSeq(), ranges.getEndSeq());
439 * Draw an alignment panel for printing
442 * Graphics object to draw with
444 * start residue of print area
446 * end residue of print area
448 * start sequence of print area
450 * end sequence of print area
452 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
453 int startSeq, int endSeq)
455 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
457 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
459 if (selectImage != null)
461 ((Graphics2D) g1).setComposite(AlphaComposite
462 .getInstance(AlphaComposite.SRC_OVER));
463 g1.drawImage(selectImage, 0, 0, this);
468 * Draw a wrapped alignment panel for printing
471 * Graphics object to draw with
473 * width of drawing area
474 * @param canvasHeight
475 * height of drawing area
477 * start residue of print area
479 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
480 int canvasHeight, int startRes)
482 SequenceGroup group = av.getSelectionGroup();
484 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
488 BufferedImage selectImage = null;
491 selectImage = new BufferedImage(canvasWidth, canvasHeight,
492 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
493 } catch (OutOfMemoryError er)
496 System.err.println("Print image OutOfMemory Error.\n" + er);
497 new OOMWarning("Creating wrapped alignment image for printing", er);
499 if (selectImage != null)
501 Graphics2D g2 = selectImage.createGraphics();
502 setupSelectionGroup(g2, selectImage);
503 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
507 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
508 g.drawImage(selectImage, 0, 0, this);
515 * Make a local image by combining the cached image img
518 private BufferedImage buildLocalImage(BufferedImage selectImage)
520 // clone the cached image
521 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
524 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
526 Graphics2D g2d = lcimg.createGraphics();
527 g2d.drawImage(img, 0, 0, null);
529 // overlay selection group on lcimg
530 if (selectImage != null)
533 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
534 g2d.drawImage(selectImage, 0, 0, this);
543 * Set up a buffered image of the correct height and size for the sequence canvas
545 private BufferedImage setupImage()
547 BufferedImage lcimg = null;
549 int charWidth = av.getCharWidth();
550 int charHeight = av.getCharHeight();
552 int width = getWidth();
553 int height = getHeight();
555 width -= (width % charWidth);
556 height -= (height % charHeight);
558 if ((width < 1) || (height < 1))
565 lcimg = new BufferedImage(width, height,
566 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
567 } catch (OutOfMemoryError er)
571 "Group image OutOfMemory Redraw Error.\n" + er);
572 new OOMWarning("Creating alignment image for display", er);
581 * Returns the visible width of the canvas in residues, after allowing for
582 * East or West scales (if shown)
585 * the width in pixels (possibly including scales)
589 public int getWrappedCanvasWidth(int canvasWidth)
591 int charWidth = av.getCharWidth();
593 FontMetrics fm = getFontMetrics(av.getFont());
597 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
599 labelWidth = getLabelWidth(fm);
602 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
604 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
606 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
610 * Returns a pixel width sufficient to show the largest sequence coordinate
611 * (end position) in the alignment, calculated as the FontMetrics width of
612 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
613 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
614 * half a character width space on either side.
619 protected int getLabelWidth(FontMetrics fm)
622 * find the biggest sequence end position we need to show
623 * (note this is not necessarily the sequence length)
626 AlignmentI alignment = av.getAlignment();
627 for (int i = 0; i < alignment.getHeight(); i++)
629 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
633 for (int i = maxWidth; i > 0; i /= 10)
638 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
642 * Draws as many widths of a wrapped alignment as can fit in the visible
647 * available width in pixels
648 * @param canvasHeight
649 * available height in pixels
651 * the first column (0...) of the alignment to draw
653 public void drawWrappedPanel(Graphics g, int canvasWidth,
654 int canvasHeight, final int startColumn)
656 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
659 av.setWrappedWidth(wrappedWidthInResidues);
661 ViewportRanges ranges = av.getRanges();
662 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
664 // we need to call this again to make sure the startColumn +
665 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
667 calculateWrappedGeometry(canvasWidth, canvasHeight);
670 * draw one width at a time (including any scales or annotation shown),
671 * until we have run out of either alignment or vertical space available
673 int ypos = wrappedSpaceAboveAlignment;
674 int maxWidth = ranges.getVisibleAlignmentWidth();
676 int start = startColumn;
677 int currentWidth = 0;
678 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
681 .min(maxWidth, start + wrappedWidthInResidues - 1);
682 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
683 ypos += wrappedRepeatHeightPx;
684 start += wrappedWidthInResidues;
688 drawWrappedDecorators(g, startColumn);
692 * Calculates and saves values needed when rendering a wrapped alignment.
693 * These depend on many factors, including
695 * <li>canvas width and height</li>
696 * <li>number of visible sequences, and height of annotations if shown</li>
697 * <li>font and character width</li>
698 * <li>whether scales are shown left, right or above the alignment</li>
702 * @param canvasHeight
703 * @return the number of residue columns in each width
705 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
707 int charHeight = av.getCharHeight();
710 * vertical space in pixels between wrapped widths of alignment
711 * - one character height, or two if scale above is drawn
713 wrappedSpaceAboveAlignment = charHeight
714 * (av.getScaleAboveWrapped() ? 2 : 1);
717 * height in pixels of the wrapped widths
719 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
721 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
723 // add annotations panel height if shown
724 wrappedRepeatHeightPx += getAnnotationHeight();
727 * number of visible widths (the last one may be part height),
728 * ensuring a part height includes at least one sequence
730 ViewportRanges ranges = av.getRanges();
731 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
732 int remainder = canvasHeight % wrappedRepeatHeightPx;
733 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
735 wrappedVisibleWidths++;
739 * compute width in residues; this also sets East and West label widths
741 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
744 * limit visibleWidths to not exceed width of alignment
746 int xMax = ranges.getVisibleAlignmentWidth();
747 int startToEnd = xMax - ranges.getStartRes();
748 int maxWidths = startToEnd / wrappedWidthInResidues;
749 if (startToEnd % wrappedWidthInResidues > 0)
753 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
755 return wrappedWidthInResidues;
759 * Draws one width of a wrapped alignment, including sequences and
760 * annnotations, if shown, but not scales or hidden column markers
766 * @param canvasHeight
768 protected void drawWrappedWidth(Graphics gg, int ypos, int startColumn,
769 int endColumn, int canvasHeight)
771 ViewportRanges ranges = av.getRanges();
772 int viewportWidth = ranges.getViewportWidth();
774 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
777 * move right before drawing by the width of the scale left (if any)
778 * plus column offset from left margin (usually zero, but may be non-zero
779 * when fast painting is drawing just a few columns)
781 int charWidth = av.getCharWidth();
782 int xOffset = labelWidthWest
783 + ((startColumn - ranges.getStartRes()) % viewportWidth)
786 // BH 2018 note: I have switched to using Graphics.create() here because it is
787 // more reliable (and simpler) to reset. The difference seems to be that SwingJS
788 // automatically sets a clipping region on an image to be the image dimensions, whereas
789 // Java sets no clip for an image. (A bug? Go figure!)
790 // Since we are using an off-screen BufferedImage here, the result is that g.getClip()
791 // returns non-null in JavaScript but not Java.
793 // Anyway, this works and, I suggest, is better design anyway.
795 Graphics g = gg.create();
797 g.translate(xOffset, 0);
799 // When printing we have an extra clipped region,
800 // the Printable page which we need to account for here
801 Shape clip = g.getClip();
804 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
808 g.setClip(0, (int) clip.getBounds().getY(),
809 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
814 * white fill the region to be drawn (so incremental fast paint doesn't
815 * scribble over an existing image)
817 g.setColor(Color.white);
818 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
819 wrappedRepeatHeightPx);
821 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
824 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
826 if (av.isShowAnnotation())
828 g.translate(0, cHeight + ypos + 3);
829 if (annotations == null)
831 annotations = new AnnotationPanel(av);
834 annotations.renderer.drawComponent(annotations, av, g, -1,
835 startColumn, endx + 1);
836 g.translate(0, -cHeight - ypos - 3);
839 // g.translate(-xOffset, 0);
844 * Draws scales left, right and above (if shown), and any hidden column
845 * markers, on all widths of the wrapped alignment
850 protected void drawWrappedDecorators(Graphics g, final int startColumn)
852 int charWidth = av.getCharWidth();
854 g.setFont(av.getFont());
856 g.setColor(Color.black);
858 int ypos = wrappedSpaceAboveAlignment;
859 ViewportRanges ranges = av.getRanges();
860 int viewportWidth = ranges.getViewportWidth();
861 int maxWidth = ranges.getVisibleAlignmentWidth();
863 int startCol = startColumn;
865 while (widthsDrawn < wrappedVisibleWidths)
867 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
869 if (av.getScaleLeftWrapped())
871 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
874 if (av.getScaleRightWrapped())
876 int x = labelWidthWest + viewportWidth * charWidth;
879 drawVerticalScale(g, startCol, endColumn, ypos, false);
884 * white fill region of scale above and hidden column markers
885 * (to support incremental fast paint of image)
887 g.translate(labelWidthWest, 0);
888 g.setColor(Color.white);
889 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
890 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
891 g.setColor(Color.black);
892 g.translate(-labelWidthWest, 0);
894 g.translate(labelWidthWest, 0);
896 if (av.getScaleAboveWrapped())
898 drawNorthScale(g, startCol, endColumn, ypos);
901 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
903 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
906 g.translate(-labelWidthWest, 0);
908 ypos += wrappedRepeatHeightPx;
909 startCol += viewportWidth;
915 * Draws markers (triangles) above hidden column positions between startColumn
923 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
924 int startColumn, int endColumn)
926 int charHeight = av.getCharHeight();
927 int charWidth = av.getCharWidth();
929 g.setColor(Color.blue);
931 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
933 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
937 res = it.next() - startColumn;
939 if (res < 0 || res > endColumn - startColumn + 1)
945 * draw a downward-pointing triangle at the hidden columns location
946 * (before the following visible column)
948 int xMiddle = res * charWidth;
949 int[] xPoints = new int[] { xMiddle - charHeight / 4,
950 xMiddle + charHeight / 4, xMiddle };
951 int yTop = ypos - (charHeight / 2);
952 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
953 g.fillPolygon(xPoints, yPoints, 3);
958 * Draw a selection group over a wrapped alignment
960 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
962 int canvasHeight, int startRes)
964 int charHeight = av.getCharHeight();
965 int charWidth = av.getCharWidth();
967 // height gap above each panel
968 int hgap = charHeight;
969 if (av.getScaleAboveWrapped())
974 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
976 int cHeight = av.getAlignment().getHeight() * charHeight;
978 int startx = startRes;
980 int ypos = hgap; // vertical offset
981 int maxwidth = av.getAlignment().getWidth();
983 if (av.hasHiddenColumns())
985 maxwidth = av.getAlignment().getHiddenColumns()
986 .absoluteToVisibleColumn(maxwidth);
989 // chop the wrapped alignment extent up into panel-sized blocks and treat
990 // each block as if it were a block from an unwrapped alignment
991 while ((ypos <= canvasHeight) && (startx < maxwidth))
993 // set end value to be start + width, or maxwidth, whichever is smaller
994 endx = startx + cWidth - 1;
1001 g.translate(labelWidthWest, 0);
1003 drawUnwrappedSelection(g, group, startx, endx, 0,
1004 av.getAlignment().getHeight() - 1,
1007 g.translate(-labelWidthWest, 0);
1009 // update vertical offset
1010 ypos += cHeight + getAnnotationHeight() + hgap;
1012 // update horizontal offset
1017 int getAnnotationHeight()
1019 if (!av.isShowAnnotation())
1024 if (annotations == null)
1026 annotations = new AnnotationPanel(av);
1029 return annotations.adjustPanelHeight();
1033 * Draws the visible region of the alignment on the graphics context. If there
1034 * are hidden column markers in the visible region, then each sub-region
1035 * between the markers is drawn separately, followed by the hidden column
1039 * the graphics context, positioned at the first residue to be drawn
1041 * offset of the first column to draw (0..)
1043 * offset of the last column to draw (0..)
1045 * offset of the first sequence to draw (0..)
1047 * offset of the last sequence to draw (0..)
1049 * vertical offset at which to draw (for wrapped alignments)
1051 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1052 final int startSeq, final int endSeq, final int yOffset)
1054 int charHeight = av.getCharHeight();
1055 int charWidth = av.getCharWidth();
1057 if (!av.hasHiddenColumns())
1059 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1067 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1068 VisibleContigsIterator regions = hidden
1069 .getVisContigsIterator(startRes, endRes + 1, true);
1071 while (regions.hasNext())
1073 int[] region = regions.next();
1074 blockEnd = region[1];
1075 blockStart = region[0];
1078 * draw up to just before the next hidden region, or the end of
1079 * the visible region, whichever comes first
1081 g1.translate(screenY * charWidth, 0);
1083 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1086 * draw the downline of the hidden column marker (ScalePanel draws the
1087 * triangle on top) if we reached it
1089 if (av.getShowHiddenMarkers()
1090 && (regions.hasNext() || regions.endsAtHidden()))
1092 g1.setColor(Color.blue);
1094 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1095 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1096 (endSeq - startSeq + 1) * charHeight + yOffset);
1099 g1.translate(-screenY * charWidth, 0);
1100 screenY += blockEnd - blockStart + 1;
1107 * Draws a region of the visible alignment
1111 * offset of the first column in the visible region (0..)
1113 * offset of the last column in the visible region (0..)
1115 * offset of the first sequence in the visible region (0..)
1117 * offset of the last sequence in the visible region (0..)
1119 * vertical offset at which to draw (for wrapped alignments)
1121 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1122 int endSeq, int offset)
1124 int charHeight = av.getCharHeight();
1125 int charWidth = av.getCharWidth();
1127 g.setFont(av.getFont());
1128 seqRdr.prepare(g, av.isRenderGaps());
1132 // / First draw the sequences
1133 // ///////////////////////////
1134 for (int i = startSeq; i <= endSeq; i++)
1136 nextSeq = av.getAlignment().getSequenceAt(i);
1137 if (nextSeq == null)
1139 // occasionally, a race condition occurs such that the alignment row is
1143 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1144 startRes, endRes, offset + ((i - startSeq) * charHeight));
1146 if (av.isShowSequenceFeatures())
1148 fr.drawSequence(g, nextSeq, startRes, endRes,
1149 offset + ((i - startSeq) * charHeight), false);
1153 * highlight search Results once sequence has been drawn
1155 if (av.hasSearchResults())
1157 SearchResultsI searchResults = av.getSearchResults();
1158 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1160 if (visibleResults != null)
1162 for (int r = 0; r < visibleResults.length; r += 2)
1164 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1165 visibleResults[r + 1],
1166 (visibleResults[r] - startRes) * charWidth,
1167 offset + ((i - startSeq) * charHeight));
1173 if (av.getSelectionGroup() != null
1174 || av.getAlignment().getGroups().size() > 0)
1176 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1181 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1182 int startSeq, int endSeq, int offset)
1184 Graphics2D g = (Graphics2D) g1;
1186 // ///////////////////////////////////
1187 // Now outline any areas if necessary
1188 // ///////////////////////////////////
1190 SequenceGroup group = null;
1191 int groupIndex = -1;
1193 if (av.getAlignment().getGroups().size() > 0)
1195 group = av.getAlignment().getGroups().get(0);
1201 g.setStroke(new BasicStroke());
1205 g.setColor(group.getOutlineColour());
1206 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1211 g.setStroke(new BasicStroke());
1213 if (groupIndex >= av.getAlignment().getGroups().size())
1218 group = av.getAlignment().getGroups().get(groupIndex);
1220 } while (groupIndex < av.getAlignment().getGroups().size());
1228 * Draw the selection group as a separate image and overlay
1230 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1231 int startSeq, int endSeq)
1233 // get a new image of the correct size
1234 BufferedImage selectionImage = setupImage();
1236 if (selectionImage == null)
1241 SequenceGroup group = av.getSelectionGroup();
1248 // set up drawing colour
1249 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1251 setupSelectionGroup(g, selectionImage);
1253 if (!av.getWrapAlignment())
1255 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1260 drawWrappedSelection(g, group, getWidth(), getHeight(),
1261 av.getRanges().getStartRes());
1265 return selectionImage;
1269 * Draw the cursor as a separate image and overlay
1272 * start residue of area to draw cursor in
1274 * end residue of area to draw cursor in
1276 * start sequence of area to draw cursor in
1278 * end sequence of are to draw cursor in
1279 * @return a transparent image of the same size as the sequence canvas, with
1280 * the cursor drawn on it, if any
1282 private void drawCursor(Graphics g, int startRes, int endRes,
1286 // convert the cursorY into a position on the visible alignment
1287 int cursor_ypos = cursorY;
1289 // don't do work unless we have to
1290 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1294 int startx = startRes;
1297 // convert the cursorX into a position on the visible alignment
1298 int cursor_xpos = av.getAlignment().getHiddenColumns()
1299 .absoluteToVisibleColumn(cursorX);
1301 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1304 if (av.getWrapAlignment())
1306 // work out the correct offsets for the cursor
1307 int charHeight = av.getCharHeight();
1308 int charWidth = av.getCharWidth();
1309 int canvasWidth = getWidth();
1310 int canvasHeight = getHeight();
1312 // height gap above each panel
1313 int hgap = charHeight;
1314 if (av.getScaleAboveWrapped())
1319 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1321 int cHeight = av.getAlignment().getHeight() * charHeight;
1323 endx = startx + cWidth - 1;
1324 int ypos = hgap; // vertical offset
1326 // iterate down the wrapped panels
1327 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1329 // update vertical offset
1330 ypos += cHeight + getAnnotationHeight() + hgap;
1332 // update horizontal offset
1334 endx = startx + cWidth - 1;
1337 xoffset = labelWidthWest;
1340 // now check if cursor is within range for x values
1341 if (cursor_xpos >= startx && cursor_xpos <= endx)
1343 // get the character the cursor is drawn at
1344 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1345 char s = seq.getCharAt(cursorX);
1347 seqRdr.drawCursor(g, s,
1348 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1349 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1357 * Set up graphics for selection group
1359 private void setupSelectionGroup(Graphics2D g,
1360 BufferedImage selectionImage)
1362 // set background to transparent
1363 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1364 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1366 // set up foreground to draw red dashed line
1367 g.setComposite(AlphaComposite.Src);
1368 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1369 BasicStroke.JOIN_ROUND, 3f, new float[]
1371 g.setColor(Color.RED);
1375 * Draw a selection group over an unwrapped alignment
1376 * @param g graphics object to draw with
1377 * @param group selection group
1378 * @param startRes start residue of area to draw
1379 * @param endRes end residue of area to draw
1380 * @param startSeq start sequence of area to draw
1381 * @param endSeq end sequence of area to draw
1382 * @param offset vertical offset (used when called from wrapped alignment code)
1384 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1385 int startRes, int endRes, int startSeq, int endSeq, int offset)
1387 int charWidth = av.getCharWidth();
1389 if (!av.hasHiddenColumns())
1391 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1396 // package into blocks of visible columns
1401 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1402 VisibleContigsIterator regions = hidden
1403 .getVisContigsIterator(startRes, endRes + 1, true);
1404 while (regions.hasNext())
1406 int[] region = regions.next();
1407 blockEnd = region[1];
1408 blockStart = region[0];
1410 g.translate(screenY * charWidth, 0);
1411 drawPartialGroupOutline(g, group,
1412 blockStart, blockEnd, startSeq, endSeq, offset);
1414 g.translate(-screenY * charWidth, 0);
1415 screenY += blockEnd - blockStart + 1;
1421 * Draw the selection group as a separate image and overlay
1423 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1424 int startRes, int endRes, int startSeq, int endSeq,
1427 int charHeight = av.getCharHeight();
1428 int charWidth = av.getCharWidth();
1429 int visWidth = (endRes - startRes + 1) * charWidth;
1433 boolean inGroup = false;
1438 List<SequenceI> seqs = group.getSequences(null);
1440 // position of start residue of group relative to startRes, in pixels
1441 int sx = (group.getStartRes() - startRes) * charWidth;
1443 // width of group in pixels
1444 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1447 if (!(sx + xwidth < 0 || sx > visWidth))
1449 for (i = startSeq; i <= endSeq; i++)
1451 sy = verticalOffset + (i - startSeq) * charHeight;
1453 if ((sx <= (endRes - startRes) * charWidth)
1454 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1457 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1459 bottom = sy + charHeight;
1464 if (((top == -1) && (i == 0)) || !seqs
1465 .contains(av.getAlignment().getSequenceAt(i - 1)))
1476 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1477 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1479 // reset top and bottom
1487 sy = verticalOffset + ((i - startSeq) * charHeight);
1488 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1489 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1495 * Draw horizontal selection group boundaries at top and bottom positions
1498 * graphics object to draw on
1504 * visWidth maximum available width
1506 * position to draw top of group at
1508 * position to draw bottom of group at
1510 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1511 int visWidth, int top, int bottom)
1521 // don't let width extend beyond current block, or group extent
1523 if (startx + width >= visWidth)
1525 width = visWidth - startx;
1530 g.drawLine(startx, top, startx + width, top);
1535 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1540 * Draw vertical lines at sx and sx+xwidth providing they lie within
1544 * graphics object to draw on
1550 * visWidth maximum available width
1556 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1559 // if start position is visible, draw vertical line to left of
1561 if (sx >= 0 && sx < visWidth)
1563 g.drawLine(sx, oldY, sx, sy);
1566 // if end position is visible, draw vertical line to right of
1568 if (sx + xwidth < visWidth)
1570 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1575 * Highlights search results in the visible region by rendering as white text
1576 * on a black background. Any previous highlighting is removed. Answers true
1577 * if any highlight was left on the visible alignment (so status bar should be
1578 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1579 * so allows the next repaint to update the whole display.
1584 public boolean highlightSearchResults(SearchResultsI results)
1586 return highlightSearchResults(results, false);
1591 * Highlights search results in the visible region by rendering as white text
1592 * on a black background. Any previous highlighting is removed. Answers true
1593 * if any highlight was left on the visible alignment (so status bar should be
1594 * set to match), else false.
1596 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1597 * highlighted regions are modified. This speeds up highlighting across linked
1600 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1601 * a wrapped alignment had to be scrolled to show the highlighted region, then
1602 * it should be fully redrawn, otherwise a fast paint can be performed. This
1603 * argument could be removed if fast paint of scrolled wrapped alignment is
1604 * coded in future (JAL-2609).
1607 * @param doFastPaint
1608 * if true, sets a flag so the next repaint only redraws the modified
1612 public boolean highlightSearchResults(SearchResultsI results,
1613 boolean doFastPaint)
1619 boolean wrapped = av.getWrapAlignment();
1622 fastPaint = doFastPaint;
1623 fastpainting = fastPaint;
1626 * to avoid redrawing the whole visible region, we instead
1627 * redraw just the minimal regions to remove previous highlights
1630 SearchResultsI previous = av.getSearchResults();
1631 av.setSearchResults(results);
1632 boolean redrawn = false;
1633 boolean drawn = false;
1636 redrawn = drawMappedPositionsWrapped(previous);
1637 drawn = drawMappedPositionsWrapped(results);
1642 redrawn = drawMappedPositions(previous);
1643 drawn = drawMappedPositions(results);
1648 * if highlights were either removed or added, repaint
1656 * return true only if highlights were added
1662 fastpainting = false;
1667 * Redraws the minimal rectangle in the visible region (if any) that includes
1668 * mapped positions of the given search results. Whether or not positions are
1669 * highlighted depends on the SearchResults set on the Viewport. This allows
1670 * this method to be called to either clear or set highlighting. Answers true
1671 * if any positions were drawn (in which case a repaint is still required),
1677 protected boolean drawMappedPositions(SearchResultsI results)
1679 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1685 * calculate the minimal rectangle to redraw that
1686 * includes both new and existing search results
1688 int firstSeq = Integer.MAX_VALUE;
1690 int firstCol = Integer.MAX_VALUE;
1692 boolean matchFound = false;
1694 ViewportRanges ranges = av.getRanges();
1695 int firstVisibleColumn = ranges.getStartRes();
1696 int lastVisibleColumn = ranges.getEndRes();
1697 AlignmentI alignment = av.getAlignment();
1698 if (av.hasHiddenColumns())
1700 firstVisibleColumn = alignment.getHiddenColumns()
1701 .visibleToAbsoluteColumn(firstVisibleColumn);
1702 lastVisibleColumn = alignment.getHiddenColumns()
1703 .visibleToAbsoluteColumn(lastVisibleColumn);
1706 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1707 .getEndSeq(); seqNo++)
1709 SequenceI seq = alignment.getSequenceAt(seqNo);
1711 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1713 if (visibleResults != null)
1715 for (int i = 0; i < visibleResults.length - 1; i += 2)
1717 int firstMatchedColumn = visibleResults[i];
1718 int lastMatchedColumn = visibleResults[i + 1];
1719 if (firstMatchedColumn <= lastVisibleColumn
1720 && lastMatchedColumn >= firstVisibleColumn)
1723 * found a search results match in the visible region -
1724 * remember the first and last sequence matched, and the first
1725 * and last visible columns in the matched positions
1728 firstSeq = Math.min(firstSeq, seqNo);
1729 lastSeq = Math.max(lastSeq, seqNo);
1730 firstMatchedColumn = Math.max(firstMatchedColumn,
1731 firstVisibleColumn);
1732 lastMatchedColumn = Math.min(lastMatchedColumn,
1734 firstCol = Math.min(firstCol, firstMatchedColumn);
1735 lastCol = Math.max(lastCol, lastMatchedColumn);
1743 if (av.hasHiddenColumns())
1745 firstCol = alignment.getHiddenColumns()
1746 .absoluteToVisibleColumn(firstCol);
1747 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1749 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1750 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1751 Graphics gg = img.getGraphics();
1752 gg.translate(transX, transY);
1753 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1754 gg.translate(-transX, -transY);
1762 public void propertyChange(PropertyChangeEvent evt)
1764 String eventName = evt.getPropertyName();
1766 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1772 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1780 if (eventName.equals(ViewportRanges.STARTRES)
1781 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1783 // Make sure we're not trying to draw a panel
1784 // larger than the visible window
1785 if (eventName.equals(ViewportRanges.STARTRES))
1787 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1791 scrollX = ((int[]) evt.getNewValue())[0]
1792 - ((int[]) evt.getOldValue())[0];
1794 ViewportRanges vpRanges = av.getRanges();
1796 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1797 if (scrollX > range)
1801 else if (scrollX < -range)
1806 // Both scrolling and resizing change viewport ranges: scrolling changes
1807 // both start and end points, but resize only changes end values.
1808 // Here we only want to fastpaint on a scroll, with resize using a normal
1809 // paint, so scroll events are identified as changes to the horizontal or
1810 // vertical start value.
1811 if (eventName.equals(ViewportRanges.STARTRES))
1813 if (av.getWrapAlignment())
1815 fastPaintWrapped(scrollX);
1819 fastPaint(scrollX, 0);
1822 else if (eventName.equals(ViewportRanges.STARTSEQ))
1825 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1827 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1829 if (av.getWrapAlignment())
1831 fastPaintWrapped(scrollX);
1835 fastPaint(scrollX, 0);
1838 else if (eventName.equals(ViewportRanges.STARTSEQ))
1841 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1843 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1845 if (av.getWrapAlignment())
1847 fastPaintWrapped(scrollX);
1853 * Does a minimal update of the image for a scroll movement. This method
1854 * handles scroll movements of up to one width of the wrapped alignment (one
1855 * click in the vertical scrollbar). Larger movements (for example after a
1856 * scroll to highlight a mapped position) trigger a full redraw instead.
1859 * number of positions scrolled (right if positive, left if negative)
1861 protected void fastPaintWrapped(int scrollX)
1863 ViewportRanges ranges = av.getRanges();
1865 if (Math.abs(scrollX) > ranges.getViewportWidth())
1868 * shift of more than one view width is
1869 * overcomplicated to handle in this method
1876 if (fastpainting || img == null)
1882 fastpainting = true;
1887 Graphics gg = img.getGraphics();
1889 calculateWrappedGeometry(getWidth(), getHeight());
1892 * relocate the regions of the alignment that are still visible
1894 shiftWrappedAlignment(-scrollX);
1897 * add new columns (sequence, annotation)
1898 * - at top left if scrollX < 0
1899 * - at right of last two widths if scrollX > 0
1903 int startRes = ranges.getStartRes();
1904 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1905 - scrollX - 1, getHeight());
1909 fastPaintWrappedAddRight(scrollX);
1913 * draw all scales (if shown) and hidden column markers
1915 drawWrappedDecorators(gg, ranges.getStartRes());
1922 fastpainting = false;
1927 * Draws the specified number of columns at the 'end' (bottom right) of a
1928 * wrapped alignment view, including sequences and annotations if shown, but
1929 * not scales. Also draws the same number of columns at the right hand end of
1930 * the second last width shown, if the last width is not full height (so
1931 * cannot simply be copied from the graphics image).
1935 protected void fastPaintWrappedAddRight(int columns)
1942 Graphics gg = img.getGraphics();
1944 ViewportRanges ranges = av.getRanges();
1945 int viewportWidth = ranges.getViewportWidth();
1946 int charWidth = av.getCharWidth();
1949 * draw full height alignment in the second last row, last columns, if the
1950 * last row was not full height
1952 int visibleWidths = wrappedVisibleWidths;
1953 int canvasHeight = getHeight();
1954 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1956 if (lastWidthPartHeight)
1958 int widthsAbove = Math.max(0, visibleWidths - 2);
1959 int ypos = wrappedRepeatHeightPx * widthsAbove
1960 + wrappedSpaceAboveAlignment;
1961 int endRes = ranges.getEndRes();
1962 endRes += widthsAbove * viewportWidth;
1963 int startRes = endRes - columns;
1964 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1968 * white fill first to erase annotations
1972 gg.translate(xOffset, 0);
1973 gg.setColor(Color.white);
1974 gg.fillRect(labelWidthWest, ypos,
1975 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1976 gg.translate(-xOffset, 0);
1978 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1983 * draw newly visible columns in last wrapped width (none if we
1984 * have reached the end of the alignment)
1985 * y-offset for drawing last width is height of widths above,
1988 int widthsAbove = visibleWidths - 1;
1989 int ypos = wrappedRepeatHeightPx * widthsAbove
1990 + wrappedSpaceAboveAlignment;
1991 int endRes = ranges.getEndRes();
1992 endRes += widthsAbove * viewportWidth;
1993 int startRes = endRes - columns + 1;
1996 * white fill first to erase annotations
1998 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
2000 gg.translate(xOffset, 0);
2001 gg.setColor(Color.white);
2002 int width = viewportWidth * charWidth - xOffset;
2003 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2004 gg.translate(-xOffset, 0);
2006 gg.setFont(av.getFont());
2007 gg.setColor(Color.black);
2009 if (startRes < ranges.getVisibleAlignmentWidth())
2011 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2015 * and finally, white fill any space below the visible alignment
2017 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2018 if (heightBelow > 0)
2020 gg.setColor(Color.white);
2021 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2027 * Shifts the visible alignment by the specified number of columns - left if
2028 * negative, right if positive. Copies and moves sequences and annotations (if
2029 * shown). Scales, hidden column markers and any newly visible columns must be
2034 protected void shiftWrappedAlignment(int positions)
2041 Graphics gg = img.getGraphics();
2043 int charWidth = av.getCharWidth();
2045 int canvasHeight = getHeight();
2046 ViewportRanges ranges = av.getRanges();
2047 int viewportWidth = ranges.getViewportWidth();
2048 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2050 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2051 int xMax = ranges.getVisibleAlignmentWidth();
2056 * shift right (after scroll left)
2057 * for each wrapped width (starting with the last), copy (width-positions)
2058 * columns from the left margin to the right margin, and copy positions
2059 * columns from the right margin of the row above (if any) to the
2060 * left margin of the current row
2064 * get y-offset of last wrapped width, first row of sequences
2066 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2067 y += wrappedSpaceAboveAlignment;
2068 int copyFromLeftStart = labelWidthWest;
2069 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2073 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2074 positions * charWidth, 0);
2077 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2078 positions * charWidth, heightToCopy, -widthToCopy,
2079 wrappedRepeatHeightPx);
2082 y -= wrappedRepeatHeightPx;
2088 * shift left (after scroll right)
2089 * for each wrapped width (starting with the first), copy (width-positions)
2090 * columns from the right margin to the left margin, and copy positions
2091 * columns from the left margin of the row below (if any) to the
2092 * right margin of the current row
2094 int xpos = av.getRanges().getStartRes();
2095 int y = wrappedSpaceAboveAlignment;
2096 int copyFromRightStart = labelWidthWest - positions * charWidth;
2098 while (y < canvasHeight)
2100 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2101 positions * charWidth, 0);
2102 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2103 && (xpos + viewportWidth <= xMax))
2105 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2106 * charWidth, heightToCopy, widthToCopy,
2107 -wrappedRepeatHeightPx);
2109 y += wrappedRepeatHeightPx;
2110 xpos += viewportWidth;
2118 * Redraws any positions in the search results in the visible region of a
2119 * wrapped alignment. Any highlights are drawn depending on the search results
2120 * set on the Viewport, not the <code>results</code> argument. This allows
2121 * this method to be called either to clear highlights (passing the previous
2122 * search results), or to draw new highlights.
2127 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2129 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2133 int charHeight = av.getCharHeight();
2135 boolean matchFound = false;
2137 calculateWrappedGeometry(getWidth(), getHeight());
2138 int wrappedWidth = av.getWrappedWidth();
2139 int wrappedHeight = wrappedRepeatHeightPx;
2141 ViewportRanges ranges = av.getRanges();
2142 int canvasHeight = getHeight();
2143 int repeats = canvasHeight / wrappedHeight;
2144 if (canvasHeight / wrappedHeight > 0)
2149 int firstVisibleColumn = ranges.getStartRes();
2150 int lastVisibleColumn = ranges.getStartRes() + repeats
2151 * ranges.getViewportWidth() - 1;
2153 AlignmentI alignment = av.getAlignment();
2154 if (av.hasHiddenColumns())
2156 firstVisibleColumn = alignment.getHiddenColumns()
2157 .visibleToAbsoluteColumn(firstVisibleColumn);
2158 lastVisibleColumn = alignment.getHiddenColumns()
2159 .visibleToAbsoluteColumn(lastVisibleColumn);
2162 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2165 Graphics gg = img.getGraphics();
2167 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2168 .getEndSeq(); seqNo++)
2170 SequenceI seq = alignment.getSequenceAt(seqNo);
2172 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2174 if (visibleResults != null)
2176 for (int i = 0; i < visibleResults.length - 1; i += 2)
2178 int firstMatchedColumn = visibleResults[i];
2179 int lastMatchedColumn = visibleResults[i + 1];
2180 if (firstMatchedColumn <= lastVisibleColumn
2181 && lastMatchedColumn >= firstVisibleColumn)
2184 * found a search results match in the visible region
2186 firstMatchedColumn = Math.max(firstMatchedColumn,
2187 firstVisibleColumn);
2188 lastMatchedColumn = Math.min(lastMatchedColumn,
2192 * draw each mapped position separately (as contiguous positions may
2193 * wrap across lines)
2195 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2197 int displayColumn = mappedPos;
2198 if (av.hasHiddenColumns())
2200 displayColumn = alignment.getHiddenColumns()
2201 .absoluteToVisibleColumn(displayColumn);
2205 * transX: offset from left edge of canvas to residue position
2207 int transX = labelWidthWest
2208 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2209 * av.getCharWidth();
2212 * transY: offset from top edge of canvas to residue position
2214 int transY = gapHeight;
2215 transY += (displayColumn - ranges.getStartRes())
2216 / wrappedWidth * wrappedHeight;
2217 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2220 * yOffset is from graphics origin to start of visible region
2222 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2223 if (transY < getHeight())
2226 gg.translate(transX, transY);
2227 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2229 gg.translate(-transX, -transY);
2243 * Answers the width in pixels of the left scale labels (0 if not shown)
2247 int getLabelWidthWest()
2249 return labelWidthWest;