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)
154 // System.out.println("n " + mpos + " " + ypos);
159 String mstring = mark.text;
165 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
169 * draw a tick mark below the column number, centred on the column;
170 * height of tick mark is 4 pixels less than half a character
172 int xpos = (mpos * charWidth) + (charWidth / 2);
173 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
179 * Draw the scale to the left or right of a wrapped alignment
182 * graphics context, positioned at the start of the scale to be drawn
184 * first column of wrapped width (0.. excluding any hidden columns)
186 * last column of wrapped width (0.. excluding any hidden columns)
188 * vertical offset at which to begin the scale
190 * if true, scale is left of residues, if false, scale is right
192 void drawVerticalScale(Graphics g, final int startx, final int endx,
193 final int ypos, final boolean left)
195 int charHeight = av.getCharHeight();
196 int charWidth = av.getCharWidth();
198 int yPos = ypos + charHeight;
202 //System.out.println("v " + startx + " " + endx + " " + ypos);
205 if (av.hasHiddenColumns())
207 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
208 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
209 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
211 FontMetrics fm = getFontMetrics(av.getFont());
213 for (int i = 0; i < av.getAlignment().getHeight(); i++)
215 SequenceI seq = av.getAlignment().getSequenceAt(i);
218 * find sequence position of first non-gapped position -
219 * to the right if scale left, to the left if scale right
221 int index = left ? startX : endX;
223 while (index >= startX && index <= endX)
225 if (!Comparison.isGap(seq.getCharAt(index)))
227 value = seq.findPosition(index);
241 * white fill the space for the scale
243 g.setColor(Color.white);
244 int y = (yPos + (i * charHeight)) - (charHeight / 5);
245 // fillRect origin is top left of rectangle
246 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
252 * draw scale value, right justified within its width less half a
253 * character width padding on the right
255 int labelSpace = left ? labelWidthWest : labelWidthEast;
256 labelSpace -= charWidth / 2; // leave space to the right
257 String valueAsString = String.valueOf(value);
258 int labelLength = fm.stringWidth(valueAsString);
259 int xOffset = labelSpace - labelLength;
260 g.setColor(Color.black);
261 g.drawString(valueAsString, xOffset, y);
263 //System.out.println("v " + valueAsString + " " + xOffset + " " + y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
283 * columns to shift right (positive) or left (negative)
285 * rows to shift down (positive) or up (negative)
287 public void fastPaint(int horizontal, int vertical)
289 if (fastpainting || img == null)
298 int charHeight = av.getCharHeight();
299 int charWidth = av.getCharWidth();
301 ViewportRanges ranges = av.getRanges();
302 int startRes = ranges.getStartRes();
303 int endRes = ranges.getEndRes();
304 int startSeq = ranges.getStartSeq();
305 int endSeq = ranges.getEndSeq();
309 Graphics gg = img.getGraphics();
310 gg.copyArea(horizontal * charWidth, vertical * charHeight,
311 img.getWidth(), img.getHeight(), -horizontal * charWidth,
312 -vertical * charHeight);
314 if (horizontal > 0) // scrollbar pulled right, image to the left
316 transX = (endRes - startRes - horizontal) * charWidth;
317 startRes = endRes - horizontal;
319 else if (horizontal < 0)
321 endRes = startRes - horizontal;
324 if (vertical > 0) // scroll down
326 startSeq = endSeq - vertical;
328 if (startSeq < ranges.getStartSeq())
329 { // ie scrolling too fast, more than a page at a time
330 startSeq = ranges.getStartSeq();
334 transY = img.getHeight() - ((vertical + 1) * charHeight);
337 else if (vertical < 0)
339 endSeq = startSeq - vertical;
341 if (endSeq > ranges.getEndSeq())
343 endSeq = ranges.getEndSeq();
347 gg.translate(transX, transY);
348 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
349 gg.translate(-transX, -transY);
352 // Call repaint on alignment panel so that repaints from other alignment
353 // panel components can be aggregated. Otherwise performance of the
354 // overview window and others may be adversely affected.
355 av.getAlignPanel().repaint();
358 fastpainting = false;
363 public void paintComponent(Graphics g)
365 super.paintComponent(g);
367 int charHeight = av.getCharHeight();
368 int charWidth = av.getCharWidth();
370 ViewportRanges ranges = av.getRanges();
372 int width = getWidth();
373 int height = getHeight();
375 width -= (width % charWidth);
376 height -= (height % charHeight);
378 // selectImage is the selection group outline image
379 BufferedImage selectImage = drawSelectionGroup(ranges.getStartRes(),
380 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
382 if ((img != null) && (fastPaint
383 || (getVisibleRect().width != g.getClipBounds().width)
384 || (getVisibleRect().height != g.getClipBounds().height)))
386 BufferedImage lcimg = buildLocalImage(selectImage);
387 g.drawImage(lcimg, 0, 0, this);
390 else if ((width > 0) && (height > 0))
392 // img is a cached version of the last view we drew, if any
393 // if we have no img or the size has changed, make a new one
394 if (img == null || width != img.getWidth()
395 || height != img.getHeight())
405 Graphics2D gg = (Graphics2D) img.getGraphics();
406 gg.setFont(av.getFont());
410 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
411 RenderingHints.VALUE_ANTIALIAS_ON);
414 gg.setColor(Color.white);
415 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
417 if (av.getWrapAlignment())
419 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
423 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
424 ranges.getStartSeq(), ranges.getEndSeq(), 0);
430 // lcimg is a local *copy* of img which we'll draw selectImage on top of
431 BufferedImage lcimg = buildLocalImage(selectImage);
432 g.drawImage(lcimg, 0, 0, this);
438 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
439 ranges.getStartSeq(), ranges.getEndSeq());
444 * Draw an alignment panel for printing
447 * Graphics object to draw with
449 * start residue of print area
451 * end residue of print area
453 * start sequence of print area
455 * end sequence of print area
457 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
458 int startSeq, int endSeq)
460 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
462 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
464 if (selectImage != null)
466 ((Graphics2D) g1).setComposite(AlphaComposite
467 .getInstance(AlphaComposite.SRC_OVER));
468 g1.drawImage(selectImage, 0, 0, this);
473 * Draw a wrapped alignment panel for printing
476 * Graphics object to draw with
478 * width of drawing area
479 * @param canvasHeight
480 * height of drawing area
482 * start residue of print area
484 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
485 int canvasHeight, int startRes)
487 SequenceGroup group = av.getSelectionGroup();
489 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
493 BufferedImage selectImage = null;
496 selectImage = new BufferedImage(canvasWidth, canvasHeight,
497 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
498 } catch (OutOfMemoryError er)
501 System.err.println("Print image OutOfMemory Error.\n" + er);
502 new OOMWarning("Creating wrapped alignment image for printing", er);
504 if (selectImage != null)
506 Graphics2D g2 = selectImage.createGraphics();
507 setupSelectionGroup(g2, selectImage);
508 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
512 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
513 g.drawImage(selectImage, 0, 0, this);
520 * Make a local image by combining the cached image img
523 private BufferedImage buildLocalImage(BufferedImage selectImage)
525 // clone the cached image
526 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
529 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
531 Graphics2D g2d = lcimg.createGraphics();
532 g2d.drawImage(img, 0, 0, null);
534 // overlay selection group on lcimg
535 if (selectImage != null)
538 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
539 g2d.drawImage(selectImage, 0, 0, this);
548 * Set up a buffered image of the correct height and size for the sequence canvas
550 private BufferedImage setupImage()
552 BufferedImage lcimg = null;
554 int charWidth = av.getCharWidth();
555 int charHeight = av.getCharHeight();
557 int width = getWidth();
558 int height = getHeight();
560 width -= (width % charWidth);
561 height -= (height % charHeight);
563 if ((width < 1) || (height < 1))
570 lcimg = new BufferedImage(width, height,
571 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
572 } catch (OutOfMemoryError er)
576 "Group image OutOfMemory Redraw Error.\n" + er);
577 new OOMWarning("Creating alignment image for display", er);
586 * Returns the visible width of the canvas in residues, after allowing for
587 * East or West scales (if shown)
590 * the width in pixels (possibly including scales)
594 public int getWrappedCanvasWidth(int canvasWidth)
596 int charWidth = av.getCharWidth();
598 FontMetrics fm = getFontMetrics(av.getFont());
602 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
604 labelWidth = getLabelWidth(fm);
607 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
609 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
611 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
615 * Returns a pixel width sufficient to show the largest sequence coordinate
616 * (end position) in the alignment, calculated as the FontMetrics width of
617 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
618 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
619 * half a character width space on either side.
624 protected int getLabelWidth(FontMetrics fm)
627 * find the biggest sequence end position we need to show
628 * (note this is not necessarily the sequence length)
631 AlignmentI alignment = av.getAlignment();
632 for (int i = 0; i < alignment.getHeight(); i++)
634 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
638 for (int i = maxWidth; i > 0; i /= 10)
643 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
647 * Draws as many widths of a wrapped alignment as can fit in the visible
652 * available width in pixels
653 * @param canvasHeight
654 * available height in pixels
656 * the first column (0...) of the alignment to draw
658 public void drawWrappedPanel(Graphics g, int canvasWidth,
659 int canvasHeight, final int startColumn)
661 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
664 av.setWrappedWidth(wrappedWidthInResidues);
666 ViewportRanges ranges = av.getRanges();
667 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
669 // we need to call this again to make sure the startColumn +
670 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
672 calculateWrappedGeometry(canvasWidth, canvasHeight);
675 * draw one width at a time (including any scales or annotation shown),
676 * until we have run out of either alignment or vertical space available
678 int ypos = wrappedSpaceAboveAlignment;
679 int maxWidth = ranges.getVisibleAlignmentWidth();
681 int start = startColumn;
682 int currentWidth = 0;
683 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
686 .min(maxWidth, start + wrappedWidthInResidues - 1);
687 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
688 ypos += wrappedRepeatHeightPx;
689 start += wrappedWidthInResidues;
693 drawWrappedDecorators(g, startColumn);
697 * Calculates and saves values needed when rendering a wrapped alignment.
698 * These depend on many factors, including
700 * <li>canvas width and height</li>
701 * <li>number of visible sequences, and height of annotations if shown</li>
702 * <li>font and character width</li>
703 * <li>whether scales are shown left, right or above the alignment</li>
707 * @param canvasHeight
708 * @return the number of residue columns in each width
710 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
712 int charHeight = av.getCharHeight();
715 * vertical space in pixels between wrapped widths of alignment
716 * - one character height, or two if scale above is drawn
718 wrappedSpaceAboveAlignment = charHeight
719 * (av.getScaleAboveWrapped() ? 2 : 1);
722 * height in pixels of the wrapped widths
724 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
726 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
728 // add annotations panel height if shown
729 wrappedRepeatHeightPx += getAnnotationHeight();
732 * number of visible widths (the last one may be part height),
733 * ensuring a part height includes at least one sequence
735 ViewportRanges ranges = av.getRanges();
736 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
737 int remainder = canvasHeight % wrappedRepeatHeightPx;
738 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
740 wrappedVisibleWidths++;
744 * compute width in residues; this also sets East and West label widths
746 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
749 * limit visibleWidths to not exceed width of alignment
751 int xMax = ranges.getVisibleAlignmentWidth();
752 int startToEnd = xMax - ranges.getStartRes();
753 int maxWidths = startToEnd / wrappedWidthInResidues;
754 if (startToEnd % wrappedWidthInResidues > 0)
758 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
760 return wrappedWidthInResidues;
764 * Draws one width of a wrapped alignment, including sequences and
765 * annnotations, if shown, but not scales or hidden column markers
771 * @param canvasHeight
773 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
774 int endColumn, int canvasHeight)
776 ViewportRanges ranges = av.getRanges();
777 int viewportWidth = ranges.getViewportWidth();
779 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
782 * move right before drawing by the width of the scale left (if any)
783 * plus column offset from left margin (usually zero, but may be non-zero
784 * when fast painting is drawing just a few columns)
786 int charWidth = av.getCharWidth();
787 int xOffset = labelWidthWest
788 + ((startColumn - ranges.getStartRes()) % viewportWidth)
790 g.translate(xOffset, 0);
792 // When printing we have an extra clipped region,
793 // the Printable page which we need to account for here
794 Shape clip = g.getClip();
798 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
802 g.setClip(0, (int) clip.getBounds().getY(),
803 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
807 * white fill the region to be drawn (so incremental fast paint doesn't
808 * scribble over an existing image)
810 g.setColor(Color.white);
811 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
812 wrappedRepeatHeightPx);
814 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
817 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
819 if (av.isShowAnnotation())
821 g.translate(0, cHeight + ypos + 3);
822 if (annotations == null)
824 annotations = new AnnotationPanel(av);
827 annotations.renderer.drawComponent(annotations, av, g, -1,
828 startColumn, endx + 1);
829 g.translate(0, -cHeight - ypos - 3);
832 g.translate(-xOffset, 0);
836 * Draws scales left, right and above (if shown), and any hidden column
837 * markers, on all widths of the wrapped alignment
842 protected void drawWrappedDecorators(Graphics g, final int startColumn)
844 int charWidth = av.getCharWidth();
846 g.setFont(av.getFont());
847 g.setColor(Color.black);
849 int ypos = wrappedSpaceAboveAlignment;
850 ViewportRanges ranges = av.getRanges();
851 int viewportWidth = ranges.getViewportWidth();
852 int maxWidth = ranges.getVisibleAlignmentWidth();
854 int startCol = startColumn;
856 while (widthsDrawn < wrappedVisibleWidths)
858 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
860 if (av.getScaleLeftWrapped())
862 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
865 if (av.getScaleRightWrapped())
867 int x = labelWidthWest + viewportWidth * charWidth;
869 //System.out.println("shifting " + x);
871 drawVerticalScale(g, startCol, endColumn, ypos, false);
876 * white fill region of scale above and hidden column markers
877 * (to support incremental fast paint of image)
879 g.translate(labelWidthWest, 0);
880 g.setColor(Color.white);
881 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
882 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
883 g.setColor(Color.black);
884 g.translate(-labelWidthWest, 0);
886 g.translate(labelWidthWest, 0);
888 if (av.getScaleAboveWrapped())
890 drawNorthScale(g, startCol, endColumn, ypos);
893 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
895 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
898 g.translate(-labelWidthWest, 0);
900 ypos += wrappedRepeatHeightPx;
901 startCol += viewportWidth;
907 * Draws markers (triangles) above hidden column positions between startColumn
915 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
916 int startColumn, int endColumn)
918 int charHeight = av.getCharHeight();
919 int charWidth = av.getCharWidth();
921 g.setColor(Color.blue);
923 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
925 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
929 res = it.next() - startColumn;
931 if (res < 0 || res > endColumn - startColumn + 1)
937 * draw a downward-pointing triangle at the hidden columns location
938 * (before the following visible column)
940 int xMiddle = res * charWidth;
941 int[] xPoints = new int[] { xMiddle - charHeight / 4,
942 xMiddle + charHeight / 4, xMiddle };
943 int yTop = ypos - (charHeight / 2);
944 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
945 g.fillPolygon(xPoints, yPoints, 3);
950 * Draw a selection group over a wrapped alignment
952 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
954 int canvasHeight, int startRes)
956 int charHeight = av.getCharHeight();
957 int charWidth = av.getCharWidth();
959 // height gap above each panel
960 int hgap = charHeight;
961 if (av.getScaleAboveWrapped())
966 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
968 int cHeight = av.getAlignment().getHeight() * charHeight;
970 int startx = startRes;
972 int ypos = hgap; // vertical offset
973 int maxwidth = av.getAlignment().getWidth();
975 if (av.hasHiddenColumns())
977 maxwidth = av.getAlignment().getHiddenColumns()
978 .absoluteToVisibleColumn(maxwidth);
981 // chop the wrapped alignment extent up into panel-sized blocks and treat
982 // each block as if it were a block from an unwrapped alignment
983 while ((ypos <= canvasHeight) && (startx < maxwidth))
985 // set end value to be start + width, or maxwidth, whichever is smaller
986 endx = startx + cWidth - 1;
993 g.translate(labelWidthWest, 0);
995 drawUnwrappedSelection(g, group, startx, endx, 0,
996 av.getAlignment().getHeight() - 1,
999 g.translate(-labelWidthWest, 0);
1001 // update vertical offset
1002 ypos += cHeight + getAnnotationHeight() + hgap;
1004 // update horizontal offset
1009 int getAnnotationHeight()
1011 if (!av.isShowAnnotation())
1016 if (annotations == null)
1018 annotations = new AnnotationPanel(av);
1021 return annotations.adjustPanelHeight();
1025 * Draws the visible region of the alignment on the graphics context. If there
1026 * are hidden column markers in the visible region, then each sub-region
1027 * between the markers is drawn separately, followed by the hidden column
1031 * the graphics context, positioned at the first residue to be drawn
1033 * offset of the first column to draw (0..)
1035 * offset of the last column to draw (0..)
1037 * offset of the first sequence to draw (0..)
1039 * offset of the last sequence to draw (0..)
1041 * vertical offset at which to draw (for wrapped alignments)
1043 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1044 final int startSeq, final int endSeq, final int yOffset)
1046 int charHeight = av.getCharHeight();
1047 int charWidth = av.getCharWidth();
1049 if (!av.hasHiddenColumns())
1051 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1059 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1060 VisibleContigsIterator regions = hidden
1061 .getVisContigsIterator(startRes, endRes + 1, true);
1063 while (regions.hasNext())
1065 int[] region = regions.next();
1066 blockEnd = region[1];
1067 blockStart = region[0];
1070 * draw up to just before the next hidden region, or the end of
1071 * the visible region, whichever comes first
1073 g1.translate(screenY * charWidth, 0);
1075 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1078 * draw the downline of the hidden column marker (ScalePanel draws the
1079 * triangle on top) if we reached it
1081 if (av.getShowHiddenMarkers()
1082 && (regions.hasNext() || regions.endsAtHidden()))
1084 g1.setColor(Color.blue);
1086 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1087 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1088 (endSeq - startSeq + 1) * charHeight + yOffset);
1091 g1.translate(-screenY * charWidth, 0);
1092 screenY += blockEnd - blockStart + 1;
1099 * Draws a region of the visible alignment
1103 * offset of the first column in the visible region (0..)
1105 * offset of the last column in the visible region (0..)
1107 * offset of the first sequence in the visible region (0..)
1109 * offset of the last sequence in the visible region (0..)
1111 * vertical offset at which to draw (for wrapped alignments)
1113 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1114 int endSeq, int offset)
1116 int charHeight = av.getCharHeight();
1117 int charWidth = av.getCharWidth();
1119 g.setFont(av.getFont());
1120 seqRdr.prepare(g, av.isRenderGaps());
1124 // / First draw the sequences
1125 // ///////////////////////////
1126 for (int i = startSeq; i <= endSeq; i++)
1128 nextSeq = av.getAlignment().getSequenceAt(i);
1129 if (nextSeq == null)
1131 // occasionally, a race condition occurs such that the alignment row is
1135 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1136 startRes, endRes, offset + ((i - startSeq) * charHeight));
1138 if (av.isShowSequenceFeatures())
1140 fr.drawSequence(g, nextSeq, startRes, endRes,
1141 offset + ((i - startSeq) * charHeight), false);
1145 * highlight search Results once sequence has been drawn
1147 if (av.hasSearchResults())
1149 SearchResultsI searchResults = av.getSearchResults();
1150 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1152 if (visibleResults != null)
1154 for (int r = 0; r < visibleResults.length; r += 2)
1156 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1157 visibleResults[r + 1],
1158 (visibleResults[r] - startRes) * charWidth,
1159 offset + ((i - startSeq) * charHeight));
1165 if (av.getSelectionGroup() != null
1166 || av.getAlignment().getGroups().size() > 0)
1168 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1173 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1174 int startSeq, int endSeq, int offset)
1176 Graphics2D g = (Graphics2D) g1;
1178 // ///////////////////////////////////
1179 // Now outline any areas if necessary
1180 // ///////////////////////////////////
1182 SequenceGroup group = null;
1183 int groupIndex = -1;
1185 if (av.getAlignment().getGroups().size() > 0)
1187 group = av.getAlignment().getGroups().get(0);
1193 g.setStroke(new BasicStroke());
1197 g.setColor(group.getOutlineColour());
1198 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1203 g.setStroke(new BasicStroke());
1205 if (groupIndex >= av.getAlignment().getGroups().size())
1210 group = av.getAlignment().getGroups().get(groupIndex);
1212 } while (groupIndex < av.getAlignment().getGroups().size());
1220 * Draw the selection group as a separate image and overlay
1222 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1223 int startSeq, int endSeq)
1225 // get a new image of the correct size
1226 BufferedImage selectionImage = setupImage();
1228 if (selectionImage == null)
1233 SequenceGroup group = av.getSelectionGroup();
1240 // set up drawing colour
1241 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1243 setupSelectionGroup(g, selectionImage);
1245 if (!av.getWrapAlignment())
1247 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1252 drawWrappedSelection(g, group, getWidth(), getHeight(),
1253 av.getRanges().getStartRes());
1257 return selectionImage;
1261 * Draw the cursor as a separate image and overlay
1264 * start residue of area to draw cursor in
1266 * end residue of area to draw cursor in
1268 * start sequence of area to draw cursor in
1270 * end sequence of are to draw cursor in
1271 * @return a transparent image of the same size as the sequence canvas, with
1272 * the cursor drawn on it, if any
1274 private void drawCursor(Graphics g, int startRes, int endRes,
1278 // convert the cursorY into a position on the visible alignment
1279 int cursor_ypos = cursorY;
1281 // don't do work unless we have to
1282 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1286 int startx = startRes;
1289 // convert the cursorX into a position on the visible alignment
1290 int cursor_xpos = av.getAlignment().getHiddenColumns()
1291 .absoluteToVisibleColumn(cursorX);
1293 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1296 if (av.getWrapAlignment())
1298 // work out the correct offsets for the cursor
1299 int charHeight = av.getCharHeight();
1300 int charWidth = av.getCharWidth();
1301 int canvasWidth = getWidth();
1302 int canvasHeight = getHeight();
1304 // height gap above each panel
1305 int hgap = charHeight;
1306 if (av.getScaleAboveWrapped())
1311 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1313 int cHeight = av.getAlignment().getHeight() * charHeight;
1315 endx = startx + cWidth - 1;
1316 int ypos = hgap; // vertical offset
1318 // iterate down the wrapped panels
1319 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1321 // update vertical offset
1322 ypos += cHeight + getAnnotationHeight() + hgap;
1324 // update horizontal offset
1326 endx = startx + cWidth - 1;
1329 xoffset = labelWidthWest;
1332 // now check if cursor is within range for x values
1333 if (cursor_xpos >= startx && cursor_xpos <= endx)
1335 // get the character the cursor is drawn at
1336 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1337 char s = seq.getCharAt(cursorX);
1339 seqRdr.drawCursor(g, s,
1340 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1341 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1349 * Set up graphics for selection group
1351 private void setupSelectionGroup(Graphics2D g,
1352 BufferedImage selectionImage)
1354 // set background to transparent
1355 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1356 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1358 // set up foreground to draw red dashed line
1359 g.setComposite(AlphaComposite.Src);
1360 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1361 BasicStroke.JOIN_ROUND, 3f, new float[]
1363 g.setColor(Color.RED);
1367 * Draw a selection group over an unwrapped alignment
1368 * @param g graphics object to draw with
1369 * @param group selection group
1370 * @param startRes start residue of area to draw
1371 * @param endRes end residue of area to draw
1372 * @param startSeq start sequence of area to draw
1373 * @param endSeq end sequence of area to draw
1374 * @param offset vertical offset (used when called from wrapped alignment code)
1376 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1377 int startRes, int endRes, int startSeq, int endSeq, int offset)
1379 int charWidth = av.getCharWidth();
1381 if (!av.hasHiddenColumns())
1383 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1388 // package into blocks of visible columns
1393 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1394 VisibleContigsIterator regions = hidden
1395 .getVisContigsIterator(startRes, endRes + 1, true);
1396 while (regions.hasNext())
1398 int[] region = regions.next();
1399 blockEnd = region[1];
1400 blockStart = region[0];
1402 g.translate(screenY * charWidth, 0);
1403 drawPartialGroupOutline(g, group,
1404 blockStart, blockEnd, startSeq, endSeq, offset);
1406 g.translate(-screenY * charWidth, 0);
1407 screenY += blockEnd - blockStart + 1;
1413 * Draw the selection group as a separate image and overlay
1415 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1416 int startRes, int endRes, int startSeq, int endSeq,
1419 int charHeight = av.getCharHeight();
1420 int charWidth = av.getCharWidth();
1421 int visWidth = (endRes - startRes + 1) * charWidth;
1425 boolean inGroup = false;
1430 List<SequenceI> seqs = group.getSequences(null);
1432 // position of start residue of group relative to startRes, in pixels
1433 int sx = (group.getStartRes() - startRes) * charWidth;
1435 // width of group in pixels
1436 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1439 if (!(sx + xwidth < 0 || sx > visWidth))
1441 for (i = startSeq; i <= endSeq; i++)
1443 sy = verticalOffset + (i - startSeq) * charHeight;
1445 if ((sx <= (endRes - startRes) * charWidth)
1446 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1449 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1451 bottom = sy + charHeight;
1456 if (((top == -1) && (i == 0)) || !seqs
1457 .contains(av.getAlignment().getSequenceAt(i - 1)))
1468 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1469 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1471 // reset top and bottom
1479 sy = verticalOffset + ((i - startSeq) * charHeight);
1480 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1481 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1487 * Draw horizontal selection group boundaries at top and bottom positions
1490 * graphics object to draw on
1496 * visWidth maximum available width
1498 * position to draw top of group at
1500 * position to draw bottom of group at
1502 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1503 int visWidth, int top, int bottom)
1513 // don't let width extend beyond current block, or group extent
1515 if (startx + width >= visWidth)
1517 width = visWidth - startx;
1522 g.drawLine(startx, top, startx + width, top);
1527 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1532 * Draw vertical lines at sx and sx+xwidth providing they lie within
1536 * graphics object to draw on
1542 * visWidth maximum available width
1548 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1551 // if start position is visible, draw vertical line to left of
1553 if (sx >= 0 && sx < visWidth)
1555 g.drawLine(sx, oldY, sx, sy);
1558 // if end position is visible, draw vertical line to right of
1560 if (sx + xwidth < visWidth)
1562 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1567 * Highlights search results in the visible region by rendering as white text
1568 * on a black background. Any previous highlighting is removed. Answers true
1569 * if any highlight was left on the visible alignment (so status bar should be
1570 * set to match), else false.
1572 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1573 * alignment had to be scrolled to show the highlighted region, then it should
1574 * be fully redrawn, otherwise a fast paint can be performed. This argument
1575 * could be removed if fast paint of scrolled wrapped alignment is coded in
1576 * future (JAL-2609).
1579 * @param noFastPaint
1582 public boolean highlightSearchResults(SearchResultsI results,
1583 boolean noFastPaint)
1589 boolean wrapped = av.getWrapAlignment();
1592 fastPaint = !noFastPaint;
1593 fastpainting = fastPaint;
1596 * to avoid redrawing the whole visible region, we instead
1597 * redraw just the minimal regions to remove previous highlights
1600 SearchResultsI previous = av.getSearchResults();
1601 av.setSearchResults(results);
1602 boolean redrawn = false;
1603 boolean drawn = false;
1606 redrawn = drawMappedPositionsWrapped(previous);
1607 drawn = drawMappedPositionsWrapped(results);
1612 redrawn = drawMappedPositions(previous);
1613 drawn = drawMappedPositions(results);
1618 * if highlights were either removed or added, repaint
1626 * return true only if highlights were added
1632 fastpainting = false;
1637 * Redraws the minimal rectangle in the visible region (if any) that includes
1638 * mapped positions of the given search results. Whether or not positions are
1639 * highlighted depends on the SearchResults set on the Viewport. This allows
1640 * this method to be called to either clear or set highlighting. Answers true
1641 * if any positions were drawn (in which case a repaint is still required),
1647 protected boolean drawMappedPositions(SearchResultsI results)
1649 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1655 * calculate the minimal rectangle to redraw that
1656 * includes both new and existing search results
1658 int firstSeq = Integer.MAX_VALUE;
1660 int firstCol = Integer.MAX_VALUE;
1662 boolean matchFound = false;
1664 ViewportRanges ranges = av.getRanges();
1665 int firstVisibleColumn = ranges.getStartRes();
1666 int lastVisibleColumn = ranges.getEndRes();
1667 AlignmentI alignment = av.getAlignment();
1668 if (av.hasHiddenColumns())
1670 firstVisibleColumn = alignment.getHiddenColumns()
1671 .visibleToAbsoluteColumn(firstVisibleColumn);
1672 lastVisibleColumn = alignment.getHiddenColumns()
1673 .visibleToAbsoluteColumn(lastVisibleColumn);
1676 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1677 .getEndSeq(); seqNo++)
1679 SequenceI seq = alignment.getSequenceAt(seqNo);
1681 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1683 if (visibleResults != null)
1685 for (int i = 0; i < visibleResults.length - 1; i += 2)
1687 int firstMatchedColumn = visibleResults[i];
1688 int lastMatchedColumn = visibleResults[i + 1];
1689 if (firstMatchedColumn <= lastVisibleColumn
1690 && lastMatchedColumn >= firstVisibleColumn)
1693 * found a search results match in the visible region -
1694 * remember the first and last sequence matched, and the first
1695 * and last visible columns in the matched positions
1698 firstSeq = Math.min(firstSeq, seqNo);
1699 lastSeq = Math.max(lastSeq, seqNo);
1700 firstMatchedColumn = Math.max(firstMatchedColumn,
1701 firstVisibleColumn);
1702 lastMatchedColumn = Math.min(lastMatchedColumn,
1704 firstCol = Math.min(firstCol, firstMatchedColumn);
1705 lastCol = Math.max(lastCol, lastMatchedColumn);
1713 if (av.hasHiddenColumns())
1715 firstCol = alignment.getHiddenColumns()
1716 .absoluteToVisibleColumn(firstCol);
1717 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1719 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1720 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1721 Graphics gg = img.getGraphics();
1722 gg.translate(transX, transY);
1723 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1724 gg.translate(-transX, -transY);
1732 public void propertyChange(PropertyChangeEvent evt)
1734 String eventName = evt.getPropertyName();
1736 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1742 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1750 if (eventName.equals(ViewportRanges.STARTRES)
1751 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1753 // Make sure we're not trying to draw a panel
1754 // larger than the visible window
1755 if (eventName.equals(ViewportRanges.STARTRES))
1757 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1761 scrollX = ((int[]) evt.getNewValue())[0]
1762 - ((int[]) evt.getOldValue())[0];
1764 ViewportRanges vpRanges = av.getRanges();
1766 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1767 if (scrollX > range)
1771 else if (scrollX < -range)
1776 // Both scrolling and resizing change viewport ranges: scrolling changes
1777 // both start and end points, but resize only changes end values.
1778 // Here we only want to fastpaint on a scroll, with resize using a normal
1779 // paint, so scroll events are identified as changes to the horizontal or
1780 // vertical start value.
1781 if (eventName.equals(ViewportRanges.STARTRES))
1783 if (av.getWrapAlignment())
1785 fastPaintWrapped(scrollX);
1789 fastPaint(scrollX, 0);
1792 else if (eventName.equals(ViewportRanges.STARTSEQ))
1795 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1797 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1799 if (av.getWrapAlignment())
1801 fastPaintWrapped(scrollX);
1805 fastPaint(scrollX, 0);
1808 else if (eventName.equals(ViewportRanges.STARTSEQ))
1811 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1813 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1815 if (av.getWrapAlignment())
1817 fastPaintWrapped(scrollX);
1823 * Does a minimal update of the image for a scroll movement. This method
1824 * handles scroll movements of up to one width of the wrapped alignment (one
1825 * click in the vertical scrollbar). Larger movements (for example after a
1826 * scroll to highlight a mapped position) trigger a full redraw instead.
1829 * number of positions scrolled (right if positive, left if negative)
1831 protected void fastPaintWrapped(int scrollX)
1833 ViewportRanges ranges = av.getRanges();
1835 if (Math.abs(scrollX) > ranges.getViewportWidth())
1838 * shift of more than one view width is
1839 * overcomplicated to handle in this method
1846 if (fastpainting || img == null)
1852 fastpainting = true;
1857 Graphics gg = img.getGraphics();
1859 calculateWrappedGeometry(getWidth(), getHeight());
1862 * relocate the regions of the alignment that are still visible
1864 shiftWrappedAlignment(-scrollX);
1867 * add new columns (sequence, annotation)
1868 * - at top left if scrollX < 0
1869 * - at right of last two widths if scrollX > 0
1873 int startRes = ranges.getStartRes();
1874 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1875 - scrollX - 1, getHeight());
1879 fastPaintWrappedAddRight(scrollX);
1883 * draw all scales (if shown) and hidden column markers
1885 drawWrappedDecorators(gg, ranges.getStartRes());
1892 fastpainting = false;
1897 * Draws the specified number of columns at the 'end' (bottom right) of a
1898 * wrapped alignment view, including sequences and annotations if shown, but
1899 * not scales. Also draws the same number of columns at the right hand end of
1900 * the second last width shown, if the last width is not full height (so
1901 * cannot simply be copied from the graphics image).
1905 protected void fastPaintWrappedAddRight(int columns)
1912 Graphics gg = img.getGraphics();
1914 ViewportRanges ranges = av.getRanges();
1915 int viewportWidth = ranges.getViewportWidth();
1916 int charWidth = av.getCharWidth();
1919 * draw full height alignment in the second last row, last columns, if the
1920 * last row was not full height
1922 int visibleWidths = wrappedVisibleWidths;
1923 int canvasHeight = getHeight();
1924 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1926 if (lastWidthPartHeight)
1928 int widthsAbove = Math.max(0, visibleWidths - 2);
1929 int ypos = wrappedRepeatHeightPx * widthsAbove
1930 + wrappedSpaceAboveAlignment;
1931 int endRes = ranges.getEndRes();
1932 endRes += widthsAbove * viewportWidth;
1933 int startRes = endRes - columns;
1934 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1938 * white fill first to erase annotations
1942 gg.translate(xOffset, 0);
1943 gg.setColor(Color.white);
1944 gg.fillRect(labelWidthWest, ypos,
1945 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1946 gg.translate(-xOffset, 0);
1948 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1953 * draw newly visible columns in last wrapped width (none if we
1954 * have reached the end of the alignment)
1955 * y-offset for drawing last width is height of widths above,
1958 int widthsAbove = visibleWidths - 1;
1959 int ypos = wrappedRepeatHeightPx * widthsAbove
1960 + wrappedSpaceAboveAlignment;
1961 int endRes = ranges.getEndRes();
1962 endRes += widthsAbove * viewportWidth;
1963 int startRes = endRes - columns + 1;
1966 * white fill first to erase annotations
1968 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1970 gg.translate(xOffset, 0);
1971 gg.setColor(Color.white);
1972 int width = viewportWidth * charWidth - xOffset;
1973 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1974 gg.translate(-xOffset, 0);
1976 gg.setFont(av.getFont());
1977 gg.setColor(Color.black);
1979 if (startRes < ranges.getVisibleAlignmentWidth())
1981 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1985 * and finally, white fill any space below the visible alignment
1987 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1988 if (heightBelow > 0)
1990 gg.setColor(Color.white);
1991 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1997 * Shifts the visible alignment by the specified number of columns - left if
1998 * negative, right if positive. Copies and moves sequences and annotations (if
1999 * shown). Scales, hidden column markers and any newly visible columns must be
2004 protected void shiftWrappedAlignment(int positions)
2011 Graphics gg = img.getGraphics();
2013 int charWidth = av.getCharWidth();
2015 int canvasHeight = getHeight();
2016 ViewportRanges ranges = av.getRanges();
2017 int viewportWidth = ranges.getViewportWidth();
2018 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2020 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2021 int xMax = ranges.getVisibleAlignmentWidth();
2026 * shift right (after scroll left)
2027 * for each wrapped width (starting with the last), copy (width-positions)
2028 * columns from the left margin to the right margin, and copy positions
2029 * columns from the right margin of the row above (if any) to the
2030 * left margin of the current row
2034 * get y-offset of last wrapped width, first row of sequences
2036 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2037 y += wrappedSpaceAboveAlignment;
2038 int copyFromLeftStart = labelWidthWest;
2039 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2043 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2044 positions * charWidth, 0);
2047 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2048 positions * charWidth, heightToCopy, -widthToCopy,
2049 wrappedRepeatHeightPx);
2052 y -= wrappedRepeatHeightPx;
2058 * shift left (after scroll right)
2059 * for each wrapped width (starting with the first), copy (width-positions)
2060 * columns from the right margin to the left margin, and copy positions
2061 * columns from the left margin of the row below (if any) to the
2062 * right margin of the current row
2064 int xpos = av.getRanges().getStartRes();
2065 int y = wrappedSpaceAboveAlignment;
2066 int copyFromRightStart = labelWidthWest - positions * charWidth;
2068 while (y < canvasHeight)
2070 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2071 positions * charWidth, 0);
2072 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2073 && (xpos + viewportWidth <= xMax))
2075 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2076 * charWidth, heightToCopy, widthToCopy,
2077 -wrappedRepeatHeightPx);
2079 y += wrappedRepeatHeightPx;
2080 xpos += viewportWidth;
2088 * Redraws any positions in the search results in the visible region of a
2089 * wrapped alignment. Any highlights are drawn depending on the search results
2090 * set on the Viewport, not the <code>results</code> argument. This allows
2091 * this method to be called either to clear highlights (passing the previous
2092 * search results), or to draw new highlights.
2097 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2099 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2103 int charHeight = av.getCharHeight();
2105 boolean matchFound = false;
2107 calculateWrappedGeometry(getWidth(), getHeight());
2108 int wrappedWidth = av.getWrappedWidth();
2109 int wrappedHeight = wrappedRepeatHeightPx;
2111 ViewportRanges ranges = av.getRanges();
2112 int canvasHeight = getHeight();
2113 int repeats = canvasHeight / wrappedHeight;
2114 if (canvasHeight / wrappedHeight > 0)
2119 int firstVisibleColumn = ranges.getStartRes();
2120 int lastVisibleColumn = ranges.getStartRes() + repeats
2121 * ranges.getViewportWidth() - 1;
2123 AlignmentI alignment = av.getAlignment();
2124 if (av.hasHiddenColumns())
2126 firstVisibleColumn = alignment.getHiddenColumns()
2127 .visibleToAbsoluteColumn(firstVisibleColumn);
2128 lastVisibleColumn = alignment.getHiddenColumns()
2129 .visibleToAbsoluteColumn(lastVisibleColumn);
2132 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2135 Graphics gg = img.getGraphics();
2137 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2138 .getEndSeq(); seqNo++)
2140 SequenceI seq = alignment.getSequenceAt(seqNo);
2142 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2144 if (visibleResults != null)
2146 for (int i = 0; i < visibleResults.length - 1; i += 2)
2148 int firstMatchedColumn = visibleResults[i];
2149 int lastMatchedColumn = visibleResults[i + 1];
2150 if (firstMatchedColumn <= lastVisibleColumn
2151 && lastMatchedColumn >= firstVisibleColumn)
2154 * found a search results match in the visible region
2156 firstMatchedColumn = Math.max(firstMatchedColumn,
2157 firstVisibleColumn);
2158 lastMatchedColumn = Math.min(lastMatchedColumn,
2162 * draw each mapped position separately (as contiguous positions may
2163 * wrap across lines)
2165 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2167 int displayColumn = mappedPos;
2168 if (av.hasHiddenColumns())
2170 displayColumn = alignment.getHiddenColumns()
2171 .absoluteToVisibleColumn(displayColumn);
2175 * transX: offset from left edge of canvas to residue position
2177 int transX = labelWidthWest
2178 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2179 * av.getCharWidth();
2182 * transY: offset from top edge of canvas to residue position
2184 int transY = gapHeight;
2185 transY += (displayColumn - ranges.getStartRes())
2186 / wrappedWidth * wrappedHeight;
2187 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2190 * yOffset is from graphics origin to start of visible region
2192 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2193 if (transY < getHeight())
2196 gg.translate(transX, transY);
2197 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2199 gg.translate(-transX, -transY);
2213 * Answers the width in pixels of the left scale labels (0 if not shown)
2217 int getLabelWidthWest()
2219 return labelWidthWest;