2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.AlphaComposite;
36 import java.awt.BasicStroke;
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Graphics2D;
42 import java.awt.RenderingHints;
43 import java.awt.Shape;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.Iterator;
47 import java.util.List;
49 import javax.swing.JComponent;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 public class SeqCanvas extends JComponent implements ViewportListenerI
59 private static final String ZEROS = "0000000000";
61 final FeatureRenderer fr;
71 private final SequenceRenderer seqRdr;
73 private boolean fastPaint = false;
75 private boolean fastpainting = false;
77 private AnnotationPanel annotations;
80 * measurements for drawing a wrapped alignment
82 private int labelWidthEast; // label right width in pixels if shown
84 private int labelWidthWest; // label left width in pixels if shown
86 private int wrappedSpaceAboveAlignment; // gap between widths
88 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
90 private int wrappedVisibleWidths; // number of wrapped widths displayed
92 private Graphics2D gg;
95 * Creates a new SeqCanvas object.
99 public SeqCanvas(AlignmentPanel ap)
102 fr = new FeatureRenderer(ap);
103 seqRdr = new SequenceRenderer(av);
104 setLayout(new BorderLayout());
105 PaintRefresher.Register(this, av.getSequenceSetId());
106 setBackground(Color.white);
108 av.getRanges().addPropertyChangeListener(this);
111 public SequenceRenderer getSequenceRenderer()
116 public FeatureRenderer getFeatureRenderer()
122 * Draws the scale above a region of a wrapped alignment, consisting of a
123 * column number every major interval (10 columns).
126 * the graphics context to draw on, positioned at the start (bottom
127 * left) of the line on which to draw any scale marks
129 * start alignment column (0..)
131 * end alignment column (0..)
133 * y offset to draw at
135 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
137 int charHeight = av.getCharHeight();
138 int charWidth = av.getCharWidth();
141 * white fill the scale space (for the fastPaint case)
143 g.setColor(Color.white);
144 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
145 charHeight * 3 / 2 + 2);
146 g.setColor(Color.black);
148 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
150 for (ScaleMark mark : marks)
152 int mpos = mark.column; // (i - startx - 1)
157 String mstring = mark.text;
163 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
167 * draw a tick mark below the column number, centred on the column;
168 * height of tick mark is 4 pixels less than half a character
170 int xpos = (mpos * charWidth) + (charWidth / 2);
171 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
177 * Draw the scale to the left or right of a wrapped alignment
180 * graphics context, positioned at the start of the scale to be drawn
182 * first column of wrapped width (0.. excluding any hidden columns)
184 * last column of wrapped width (0.. excluding any hidden columns)
186 * vertical offset at which to begin the scale
188 * if true, scale is left of residues, if false, scale is right
190 void drawVerticalScale(Graphics g, final int startx, final int endx,
191 final int ypos, final boolean left)
193 int charHeight = av.getCharHeight();
194 int charWidth = av.getCharWidth();
196 int yPos = ypos + charHeight;
200 if (av.hasHiddenColumns())
202 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
203 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
204 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
206 FontMetrics fm = getFontMetrics(av.getFont());
208 for (int i = 0; i < av.getAlignment().getHeight(); i++)
210 SequenceI seq = av.getAlignment().getSequenceAt(i);
213 * find sequence position of first non-gapped position -
214 * to the right if scale left, to the left if scale right
216 int index = left ? startX : endX;
218 while (index >= startX && index <= endX)
220 if (!Comparison.isGap(seq.getCharAt(index)))
222 value = seq.findPosition(index);
236 * white fill the space for the scale
238 g.setColor(Color.white);
239 int y = (yPos + (i * charHeight)) - (charHeight / 5);
240 // fillRect origin is top left of rectangle
241 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
247 * draw scale value, right justified within its width less half a
248 * character width padding on the right
250 int labelSpace = left ? labelWidthWest : labelWidthEast;
251 labelSpace -= charWidth / 2; // leave space to the right
252 String valueAsString = String.valueOf(value);
253 int labelLength = fm.stringWidth(valueAsString);
254 int xOffset = labelSpace - labelLength;
255 g.setColor(Color.black);
256 g.drawString(valueAsString, xOffset, y);
262 * Does a fast paint of an alignment in response to a scroll. Most of the
263 * visible region is simply copied and shifted, and then any newly visible
264 * columns or rows are drawn. The scroll may be horizontal or vertical, but
265 * not both at once. Scrolling may be the result of
267 * <li>dragging a scroll bar</li>
268 * <li>clicking in the scroll bar</li>
269 * <li>scrolling by trackpad, middle mouse button, or other device</li>
270 * <li>by moving the box in the Overview window</li>
271 * <li>programmatically to make a highlighted position visible</li>
275 * columns to shift right (positive) or left (negative)
277 * rows to shift down (positive) or up (negative)
279 public void fastPaint(int horizontal, int vertical)
281 if (fastpainting || gg == null || img == null)
290 int charHeight = av.getCharHeight();
291 int charWidth = av.getCharWidth();
293 ViewportRanges ranges = av.getRanges();
294 int startRes = ranges.getStartRes();
295 int endRes = ranges.getEndRes();
296 int startSeq = ranges.getStartSeq();
297 int endSeq = ranges.getEndSeq();
301 gg.copyArea(horizontal * charWidth, vertical * charHeight,
302 img.getWidth(), img.getHeight(), -horizontal * charWidth,
303 -vertical * charHeight);
305 if (horizontal > 0) // scrollbar pulled right, image to the left
307 transX = (endRes - startRes - horizontal) * charWidth;
308 startRes = endRes - horizontal;
310 else if (horizontal < 0)
312 endRes = startRes - horizontal;
315 if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
342 // Call repaint on alignment panel so that repaints from other alignment
343 // panel components can be aggregated. Otherwise performance of the
344 // overview window and others may be adversely affected.
345 av.getAlignPanel().repaint();
348 fastpainting = false;
353 public void paintComponent(Graphics g)
355 super.paintComponent(g);
357 int charHeight = av.getCharHeight();
358 int charWidth = av.getCharWidth();
360 ViewportRanges ranges = av.getRanges();
362 int width = getWidth();
363 int height = getHeight();
365 width -= (width % charWidth);
366 height -= (height % charHeight);
368 // selectImage is the selection group outline image
369 BufferedImage selectImage = drawSelectionGroup(
370 ranges.getStartRes(), ranges.getEndRes(),
371 ranges.getStartSeq(), ranges.getEndSeq());
373 if ((img != null) && (fastPaint
374 || (getVisibleRect().width != g.getClipBounds().width)
375 || (getVisibleRect().height != g.getClipBounds().height)))
377 BufferedImage lcimg = buildLocalImage(selectImage);
378 g.drawImage(lcimg, 0, 0, this);
381 else if ((width > 0) && (height > 0))
383 // img is a cached version of the last view we drew, if any
384 // if we have no img or the size has changed, make a new one
385 if (img == null || width != img.getWidth()
386 || height != img.getHeight())
393 gg = (Graphics2D) img.getGraphics();
394 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 // lcimg is a local *copy* of img which we'll draw selectImage on top of
417 BufferedImage lcimg = buildLocalImage(selectImage);
418 g.drawImage(lcimg, 0, 0, this);
424 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
425 ranges.getStartSeq(), ranges.getEndSeq());
430 * Draw an alignment panel for printing
433 * Graphics object to draw with
435 * start residue of print area
437 * end residue of print area
439 * start sequence of print area
441 * end sequence of print area
443 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
444 int startSeq, int endSeq)
446 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
448 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
450 if (selectImage != null)
452 ((Graphics2D) g1).setComposite(AlphaComposite
453 .getInstance(AlphaComposite.SRC_OVER));
454 g1.drawImage(selectImage, 0, 0, this);
459 * Draw a wrapped alignment panel for printing
462 * Graphics object to draw with
464 * width of drawing area
465 * @param canvasHeight
466 * height of drawing area
468 * start residue of print area
470 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
471 int canvasHeight, int startRes)
473 SequenceGroup group = av.getSelectionGroup();
475 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
479 BufferedImage selectImage = null;
482 selectImage = new BufferedImage(canvasWidth, canvasHeight,
483 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
484 } catch (OutOfMemoryError er)
487 System.err.println("Print image OutOfMemory Error.\n" + er);
488 new OOMWarning("Creating wrapped alignment image for printing", er);
490 if (selectImage != null)
492 Graphics2D g2 = selectImage.createGraphics();
493 setupSelectionGroup(g2, selectImage);
494 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
498 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
499 g.drawImage(selectImage, 0, 0, this);
506 * Make a local image by combining the cached image img
509 private BufferedImage buildLocalImage(BufferedImage selectImage)
511 // clone the cached image
512 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
515 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
517 Graphics2D g2d = lcimg.createGraphics();
518 g2d.drawImage(img, 0, 0, null);
520 // overlay selection group on lcimg
521 if (selectImage != null)
524 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
525 g2d.drawImage(selectImage, 0, 0, this);
534 * Set up a buffered image of the correct height and size for the sequence canvas
536 private BufferedImage setupImage()
538 BufferedImage lcimg = null;
540 int charWidth = av.getCharWidth();
541 int charHeight = av.getCharHeight();
543 int width = getWidth();
544 int height = getHeight();
546 width -= (width % charWidth);
547 height -= (height % charHeight);
549 if ((width < 1) || (height < 1))
556 lcimg = new BufferedImage(width, height,
557 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
558 } catch (OutOfMemoryError er)
562 "Group image OutOfMemory Redraw Error.\n" + er);
563 new OOMWarning("Creating alignment image for display", er);
572 * Returns the visible width of the canvas in residues, after allowing for
573 * East or West scales (if shown)
576 * the width in pixels (possibly including scales)
580 public int getWrappedCanvasWidth(int canvasWidth)
582 int charWidth = av.getCharWidth();
584 FontMetrics fm = getFontMetrics(av.getFont());
588 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
590 labelWidth = getLabelWidth(fm);
593 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
595 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
597 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
601 * Returns a pixel width sufficient to show the largest sequence coordinate
602 * (end position) in the alignment, calculated as the FontMetrics width of
603 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
604 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
605 * half a character width space on either side.
610 protected int getLabelWidth(FontMetrics fm)
613 * find the biggest sequence end position we need to show
614 * (note this is not necessarily the sequence length)
617 AlignmentI alignment = av.getAlignment();
618 for (int i = 0; i < alignment.getHeight(); i++)
620 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
624 for (int i = maxWidth; i > 0; i /= 10)
629 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
633 * Draws as many widths of a wrapped alignment as can fit in the visible
638 * available width in pixels
639 * @param canvasHeight
640 * available height in pixels
642 * the first column (0...) of the alignment to draw
644 public void drawWrappedPanel(Graphics g, int canvasWidth,
645 int canvasHeight, final int startColumn)
647 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
650 av.setWrappedWidth(wrappedWidthInResidues);
652 ViewportRanges ranges = av.getRanges();
653 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
655 // we need to call this again to make sure the startColumn +
656 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
658 calculateWrappedGeometry(canvasWidth, canvasHeight);
661 * draw one width at a time (including any scales or annotation shown),
662 * until we have run out of either alignment or vertical space available
664 int ypos = wrappedSpaceAboveAlignment;
665 int maxWidth = ranges.getVisibleAlignmentWidth();
667 int start = startColumn;
668 int currentWidth = 0;
669 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
672 .min(maxWidth, start + wrappedWidthInResidues - 1);
673 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
674 ypos += wrappedRepeatHeightPx;
675 start += wrappedWidthInResidues;
679 drawWrappedDecorators(g, startColumn);
683 * Calculates and saves values needed when rendering a wrapped alignment.
684 * These depend on many factors, including
686 * <li>canvas width and height</li>
687 * <li>number of visible sequences, and height of annotations if shown</li>
688 * <li>font and character width</li>
689 * <li>whether scales are shown left, right or above the alignment</li>
693 * @param canvasHeight
694 * @return the number of residue columns in each width
696 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
698 int charHeight = av.getCharHeight();
701 * vertical space in pixels between wrapped widths of alignment
702 * - one character height, or two if scale above is drawn
704 wrappedSpaceAboveAlignment = charHeight
705 * (av.getScaleAboveWrapped() ? 2 : 1);
708 * height in pixels of the wrapped widths
710 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
712 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
714 // add annotations panel height if shown
715 wrappedRepeatHeightPx += getAnnotationHeight();
718 * number of visible widths (the last one may be part height),
719 * ensuring a part height includes at least one sequence
721 ViewportRanges ranges = av.getRanges();
722 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
723 int remainder = canvasHeight % wrappedRepeatHeightPx;
724 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
726 wrappedVisibleWidths++;
730 * compute width in residues; this also sets East and West label widths
732 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
735 * limit visibleWidths to not exceed width of alignment
737 int xMax = ranges.getVisibleAlignmentWidth();
738 int startToEnd = xMax - ranges.getStartRes();
739 int maxWidths = startToEnd / wrappedWidthInResidues;
740 if (startToEnd % wrappedWidthInResidues > 0)
744 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
746 return wrappedWidthInResidues;
750 * Draws one width of a wrapped alignment, including sequences and
751 * annnotations, if shown, but not scales or hidden column markers
757 * @param canvasHeight
759 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
760 int endColumn, int canvasHeight)
762 ViewportRanges ranges = av.getRanges();
763 int viewportWidth = ranges.getViewportWidth();
765 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
768 * move right before drawing by the width of the scale left (if any)
769 * plus column offset from left margin (usually zero, but may be non-zero
770 * when fast painting is drawing just a few columns)
772 int charWidth = av.getCharWidth();
773 int xOffset = labelWidthWest
774 + ((startColumn - ranges.getStartRes()) % viewportWidth)
776 g.translate(xOffset, 0);
778 // When printing we have an extra clipped region,
779 // the Printable page which we need to account for here
780 Shape clip = g.getClip();
784 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
788 g.setClip(0, (int) clip.getBounds().getY(),
789 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
793 * white fill the region to be drawn (so incremental fast paint doesn't
794 * scribble over an existing image)
796 g.setColor(Color.white);
797 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
798 wrappedRepeatHeightPx);
800 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
803 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
805 if (av.isShowAnnotation())
807 g.translate(0, cHeight + ypos + 3);
808 if (annotations == null)
810 annotations = new AnnotationPanel(av);
813 annotations.renderer.drawComponent(annotations, av, g, -1,
814 startColumn, endx + 1);
815 g.translate(0, -cHeight - ypos - 3);
818 g.translate(-xOffset, 0);
822 * Draws scales left, right and above (if shown), and any hidden column
823 * markers, on all widths of the wrapped alignment
828 protected void drawWrappedDecorators(Graphics g, final int startColumn)
830 int charWidth = av.getCharWidth();
832 g.setFont(av.getFont());
833 g.setColor(Color.black);
835 int ypos = wrappedSpaceAboveAlignment;
836 ViewportRanges ranges = av.getRanges();
837 int viewportWidth = ranges.getViewportWidth();
838 int maxWidth = ranges.getVisibleAlignmentWidth();
840 int startCol = startColumn;
842 while (widthsDrawn < wrappedVisibleWidths)
844 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
846 if (av.getScaleLeftWrapped())
848 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
851 if (av.getScaleRightWrapped())
853 int x = labelWidthWest + viewportWidth * charWidth;
855 drawVerticalScale(g, startCol, endColumn, ypos, false);
860 * white fill region of scale above and hidden column markers
861 * (to support incremental fast paint of image)
863 g.translate(labelWidthWest, 0);
864 g.setColor(Color.white);
865 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
866 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
867 g.setColor(Color.black);
868 g.translate(-labelWidthWest, 0);
870 g.translate(labelWidthWest, 0);
872 if (av.getScaleAboveWrapped())
874 drawNorthScale(g, startCol, endColumn, ypos);
877 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
879 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
882 g.translate(-labelWidthWest, 0);
884 ypos += wrappedRepeatHeightPx;
885 startCol += viewportWidth;
891 * Draws markers (triangles) above hidden column positions between startColumn
899 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
900 int startColumn, int endColumn)
902 int charHeight = av.getCharHeight();
903 int charWidth = av.getCharWidth();
905 g.setColor(Color.blue);
907 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
909 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
913 res = it.next() - startColumn;
915 if (res < 0 || res > endColumn - startColumn + 1)
921 * draw a downward-pointing triangle at the hidden columns location
922 * (before the following visible column)
924 int xMiddle = res * charWidth;
925 int[] xPoints = new int[] { xMiddle - charHeight / 4,
926 xMiddle + charHeight / 4, xMiddle };
927 int yTop = ypos - (charHeight / 2);
928 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
929 g.fillPolygon(xPoints, yPoints, 3);
934 * Draw a selection group over a wrapped alignment
936 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
938 int canvasHeight, int startRes)
940 int charHeight = av.getCharHeight();
941 int charWidth = av.getCharWidth();
943 // height gap above each panel
944 int hgap = charHeight;
945 if (av.getScaleAboveWrapped())
950 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
952 int cHeight = av.getAlignment().getHeight() * charHeight;
954 int startx = startRes;
956 int ypos = hgap; // vertical offset
957 int maxwidth = av.getAlignment().getWidth();
959 if (av.hasHiddenColumns())
961 maxwidth = av.getAlignment().getHiddenColumns()
962 .absoluteToVisibleColumn(maxwidth);
965 // chop the wrapped alignment extent up into panel-sized blocks and treat
966 // each block as if it were a block from an unwrapped alignment
967 while ((ypos <= canvasHeight) && (startx < maxwidth))
969 // set end value to be start + width, or maxwidth, whichever is smaller
970 endx = startx + cWidth - 1;
977 g.translate(labelWidthWest, 0);
979 drawUnwrappedSelection(g, group, startx, endx, 0,
980 av.getAlignment().getHeight() - 1,
983 g.translate(-labelWidthWest, 0);
985 // update vertical offset
986 ypos += cHeight + getAnnotationHeight() + hgap;
988 // update horizontal offset
993 int getAnnotationHeight()
995 if (!av.isShowAnnotation())
1000 if (annotations == null)
1002 annotations = new AnnotationPanel(av);
1005 return annotations.adjustPanelHeight();
1009 * Draws the visible region of the alignment on the graphics context. If there
1010 * are hidden column markers in the visible region, then each sub-region
1011 * between the markers is drawn separately, followed by the hidden column
1015 * the graphics context, positioned at the first residue to be drawn
1017 * offset of the first column to draw (0..)
1019 * offset of the last column to draw (0..)
1021 * offset of the first sequence to draw (0..)
1023 * offset of the last sequence to draw (0..)
1025 * vertical offset at which to draw (for wrapped alignments)
1027 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1028 final int startSeq, final int endSeq, final int yOffset)
1030 int charHeight = av.getCharHeight();
1031 int charWidth = av.getCharWidth();
1033 if (!av.hasHiddenColumns())
1035 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1043 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1044 VisibleContigsIterator regions = hidden
1045 .getVisContigsIterator(startRes, endRes + 1, true);
1047 while (regions.hasNext())
1049 int[] region = regions.next();
1050 blockEnd = region[1];
1051 blockStart = region[0];
1054 * draw up to just before the next hidden region, or the end of
1055 * the visible region, whichever comes first
1057 g1.translate(screenY * charWidth, 0);
1059 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1062 * draw the downline of the hidden column marker (ScalePanel draws the
1063 * triangle on top) if we reached it
1065 if (av.getShowHiddenMarkers()
1066 && (regions.hasNext() || regions.endsAtHidden()))
1068 g1.setColor(Color.blue);
1070 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1071 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1072 (endSeq - startSeq + 1) * charHeight + yOffset);
1075 g1.translate(-screenY * charWidth, 0);
1076 screenY += blockEnd - blockStart + 1;
1083 * Draws a region of the visible alignment
1087 * offset of the first column in the visible region (0..)
1089 * offset of the last column in the visible region (0..)
1091 * offset of the first sequence in the visible region (0..)
1093 * offset of the last sequence in the visible region (0..)
1095 * vertical offset at which to draw (for wrapped alignments)
1097 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1098 int endSeq, int offset)
1100 int charHeight = av.getCharHeight();
1101 int charWidth = av.getCharWidth();
1103 g.setFont(av.getFont());
1104 seqRdr.prepare(g, av.isRenderGaps());
1108 // / First draw the sequences
1109 // ///////////////////////////
1110 for (int i = startSeq; i <= endSeq; i++)
1112 nextSeq = av.getAlignment().getSequenceAt(i);
1113 if (nextSeq == null)
1115 // occasionally, a race condition occurs such that the alignment row is
1119 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1120 startRes, endRes, offset + ((i - startSeq) * charHeight));
1122 if (av.isShowSequenceFeatures())
1124 fr.drawSequence(g, nextSeq, startRes, endRes,
1125 offset + ((i - startSeq) * charHeight), false);
1129 * highlight search Results once sequence has been drawn
1131 if (av.hasSearchResults())
1133 SearchResultsI searchResults = av.getSearchResults();
1134 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1136 if (visibleResults != null)
1138 for (int r = 0; r < visibleResults.length; r += 2)
1140 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1141 visibleResults[r + 1],
1142 (visibleResults[r] - startRes) * charWidth,
1143 offset + ((i - startSeq) * charHeight));
1149 if (av.getSelectionGroup() != null
1150 || av.getAlignment().getGroups().size() > 0)
1152 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1157 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1158 int startSeq, int endSeq, int offset)
1160 Graphics2D g = (Graphics2D) g1;
1162 // ///////////////////////////////////
1163 // Now outline any areas if necessary
1164 // ///////////////////////////////////
1166 SequenceGroup group = null;
1167 int groupIndex = -1;
1169 if (av.getAlignment().getGroups().size() > 0)
1171 group = av.getAlignment().getGroups().get(0);
1177 g.setStroke(new BasicStroke());
1178 g.setColor(group.getOutlineColour());
1182 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1187 g.setStroke(new BasicStroke());
1189 if (groupIndex >= av.getAlignment().getGroups().size())
1194 group = av.getAlignment().getGroups().get(groupIndex);
1196 } while (groupIndex < av.getAlignment().getGroups().size());
1204 * Draw the selection group as a separate image and overlay
1206 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1207 int startSeq, int endSeq)
1209 // get a new image of the correct size
1210 BufferedImage selectionImage = setupImage();
1212 if (selectionImage == null)
1217 SequenceGroup group = av.getSelectionGroup();
1224 // set up drawing colour
1225 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1227 setupSelectionGroup(g, selectionImage);
1229 if (!av.getWrapAlignment())
1231 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1236 drawWrappedSelection(g, group, getWidth(), getHeight(),
1237 av.getRanges().getStartRes());
1241 return selectionImage;
1245 * Draw the cursor as a separate image and overlay
1248 * start residue of area to draw cursor in
1250 * end residue of area to draw cursor in
1252 * start sequence of area to draw cursor in
1254 * end sequence of are to draw cursor in
1255 * @return a transparent image of the same size as the sequence canvas, with
1256 * the cursor drawn on it, if any
1258 private void drawCursor(Graphics g, int startRes, int endRes,
1262 // convert the cursorY into a position on the visible alignment
1263 int cursor_ypos = cursorY;
1265 // don't do work unless we have to
1266 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1270 int startx = startRes;
1273 // convert the cursorX into a position on the visible alignment
1274 int cursor_xpos = av.getAlignment().getHiddenColumns()
1275 .absoluteToVisibleColumn(cursorX);
1277 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1280 if (av.getWrapAlignment())
1282 // work out the correct offsets for the cursor
1283 int charHeight = av.getCharHeight();
1284 int charWidth = av.getCharWidth();
1285 int canvasWidth = getWidth();
1286 int canvasHeight = getHeight();
1288 // height gap above each panel
1289 int hgap = charHeight;
1290 if (av.getScaleAboveWrapped())
1295 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1297 int cHeight = av.getAlignment().getHeight() * charHeight;
1299 endx = startx + cWidth - 1;
1300 int ypos = hgap; // vertical offset
1302 // iterate down the wrapped panels
1303 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1305 // update vertical offset
1306 ypos += cHeight + getAnnotationHeight() + hgap;
1308 // update horizontal offset
1310 endx = startx + cWidth - 1;
1313 xoffset = labelWidthWest;
1316 // now check if cursor is within range for x values
1317 if (cursor_xpos >= startx && cursor_xpos <= endx)
1319 // get the character the cursor is drawn at
1320 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1321 char s = seq.getCharAt(cursorX);
1323 seqRdr.drawCursor(g, s,
1324 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1325 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1333 * Set up graphics for selection group
1335 private void setupSelectionGroup(Graphics2D g,
1336 BufferedImage selectionImage)
1338 // set background to transparent
1339 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1340 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1342 // set up foreground to draw red dashed line
1343 g.setComposite(AlphaComposite.Src);
1344 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1345 BasicStroke.JOIN_ROUND, 3f, new float[]
1347 g.setColor(Color.RED);
1351 * Draw a selection group over an unwrapped alignment
1352 * @param g graphics object to draw with
1353 * @param group selection group
1354 * @param startRes start residue of area to draw
1355 * @param endRes end residue of area to draw
1356 * @param startSeq start sequence of area to draw
1357 * @param endSeq end sequence of area to draw
1358 * @param offset vertical offset (used when called from wrapped alignment code)
1360 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1361 int startRes, int endRes, int startSeq, int endSeq, int offset)
1363 int charWidth = av.getCharWidth();
1365 if (!av.hasHiddenColumns())
1367 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1372 // package into blocks of visible columns
1377 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1378 VisibleContigsIterator regions = hidden
1379 .getVisContigsIterator(startRes, endRes + 1, true);
1380 while (regions.hasNext())
1382 int[] region = regions.next();
1383 blockEnd = region[1];
1384 blockStart = region[0];
1386 g.translate(screenY * charWidth, 0);
1387 drawPartialGroupOutline(g, group,
1388 blockStart, blockEnd, startSeq, endSeq, offset);
1390 g.translate(-screenY * charWidth, 0);
1391 screenY += blockEnd - blockStart + 1;
1397 * Draw the selection group as a separate image and overlay
1399 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1400 int startRes, int endRes, int startSeq, int endSeq,
1403 int charHeight = av.getCharHeight();
1404 int charWidth = av.getCharWidth();
1405 int visWidth = (endRes - startRes + 1) * charWidth;
1409 boolean inGroup = false;
1414 List<SequenceI> seqs = group.getSequences(null);
1416 // position of start residue of group relative to startRes, in pixels
1417 int sx = (group.getStartRes() - startRes) * charWidth;
1419 // width of group in pixels
1420 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1423 if (!(sx + xwidth < 0 || sx > visWidth))
1425 for (i = startSeq; i <= endSeq; i++)
1427 sy = verticalOffset + (i - startSeq) * charHeight;
1429 if ((sx <= (endRes - startRes) * charWidth)
1430 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1433 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1435 bottom = sy + charHeight;
1440 if (((top == -1) && (i == 0)) || !seqs
1441 .contains(av.getAlignment().getSequenceAt(i - 1)))
1452 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1453 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1455 // reset top and bottom
1463 sy = verticalOffset + ((i - startSeq) * charHeight);
1464 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1465 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1471 * Draw horizontal selection group boundaries at top and bottom positions
1474 * graphics object to draw on
1480 * visWidth maximum available width
1482 * position to draw top of group at
1484 * position to draw bottom of group at
1486 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1487 int visWidth, int top, int bottom)
1497 // don't let width extend beyond current block, or group extent
1499 if (startx + width >= visWidth)
1501 width = visWidth - startx;
1506 g.drawLine(startx, top, startx + width, top);
1511 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1516 * Draw vertical lines at sx and sx+xwidth providing they lie within
1520 * graphics object to draw on
1526 * visWidth maximum available width
1532 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1535 // if start position is visible, draw vertical line to left of
1537 if (sx >= 0 && sx < visWidth)
1539 g.drawLine(sx, oldY, sx, sy);
1542 // if end position is visible, draw vertical line to right of
1544 if (sx + xwidth < visWidth)
1546 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1551 * Highlights search results in the visible region by rendering as white text
1552 * on a black background. Any previous highlighting is removed. Answers true
1553 * if any highlight was left on the visible alignment (so status bar should be
1554 * set to match), else false.
1556 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1557 * alignment had to be scrolled to show the highlighted region, then it should
1558 * be fully redrawn, otherwise a fast paint can be performed. This argument
1559 * could be removed if fast paint of scrolled wrapped alignment is coded in
1560 * future (JAL-2609).
1563 * @param noFastPaint
1566 public boolean highlightSearchResults(SearchResultsI results,
1567 boolean noFastPaint)
1573 boolean wrapped = av.getWrapAlignment();
1576 fastPaint = !noFastPaint;
1577 fastpainting = fastPaint;
1580 * to avoid redrawing the whole visible region, we instead
1581 * redraw just the minimal regions to remove previous highlights
1584 SearchResultsI previous = av.getSearchResults();
1585 av.setSearchResults(results);
1586 boolean redrawn = false;
1587 boolean drawn = false;
1590 redrawn = drawMappedPositionsWrapped(previous);
1591 drawn = drawMappedPositionsWrapped(results);
1596 redrawn = drawMappedPositions(previous);
1597 drawn = drawMappedPositions(results);
1602 * if highlights were either removed or added, repaint
1610 * return true only if highlights were added
1616 fastpainting = false;
1621 * Redraws the minimal rectangle in the visible region (if any) that includes
1622 * mapped positions of the given search results. Whether or not positions are
1623 * highlighted depends on the SearchResults set on the Viewport. This allows
1624 * this method to be called to either clear or set highlighting. Answers true
1625 * if any positions were drawn (in which case a repaint is still required),
1631 protected boolean drawMappedPositions(SearchResultsI results)
1633 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1639 * calculate the minimal rectangle to redraw that
1640 * includes both new and existing search results
1642 int firstSeq = Integer.MAX_VALUE;
1644 int firstCol = Integer.MAX_VALUE;
1646 boolean matchFound = false;
1648 ViewportRanges ranges = av.getRanges();
1649 int firstVisibleColumn = ranges.getStartRes();
1650 int lastVisibleColumn = ranges.getEndRes();
1651 AlignmentI alignment = av.getAlignment();
1652 if (av.hasHiddenColumns())
1654 firstVisibleColumn = alignment.getHiddenColumns()
1655 .visibleToAbsoluteColumn(firstVisibleColumn);
1656 lastVisibleColumn = alignment.getHiddenColumns()
1657 .visibleToAbsoluteColumn(lastVisibleColumn);
1660 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1661 .getEndSeq(); seqNo++)
1663 SequenceI seq = alignment.getSequenceAt(seqNo);
1665 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1667 if (visibleResults != null)
1669 for (int i = 0; i < visibleResults.length - 1; i += 2)
1671 int firstMatchedColumn = visibleResults[i];
1672 int lastMatchedColumn = visibleResults[i + 1];
1673 if (firstMatchedColumn <= lastVisibleColumn
1674 && lastMatchedColumn >= firstVisibleColumn)
1677 * found a search results match in the visible region -
1678 * remember the first and last sequence matched, and the first
1679 * and last visible columns in the matched positions
1682 firstSeq = Math.min(firstSeq, seqNo);
1683 lastSeq = Math.max(lastSeq, seqNo);
1684 firstMatchedColumn = Math.max(firstMatchedColumn,
1685 firstVisibleColumn);
1686 lastMatchedColumn = Math.min(lastMatchedColumn,
1688 firstCol = Math.min(firstCol, firstMatchedColumn);
1689 lastCol = Math.max(lastCol, lastMatchedColumn);
1697 if (av.hasHiddenColumns())
1699 firstCol = alignment.getHiddenColumns()
1700 .absoluteToVisibleColumn(firstCol);
1701 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1703 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1704 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1705 gg.translate(transX, transY);
1706 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1707 gg.translate(-transX, -transY);
1714 public void propertyChange(PropertyChangeEvent evt)
1716 String eventName = evt.getPropertyName();
1718 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1724 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1732 if (eventName.equals(ViewportRanges.STARTRES)
1733 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1735 // Make sure we're not trying to draw a panel
1736 // larger than the visible window
1737 if (eventName.equals(ViewportRanges.STARTRES))
1739 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1743 scrollX = ((int[]) evt.getNewValue())[0]
1744 - ((int[]) evt.getOldValue())[0];
1746 ViewportRanges vpRanges = av.getRanges();
1748 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1749 if (scrollX > range)
1753 else if (scrollX < -range)
1758 // Both scrolling and resizing change viewport ranges: scrolling changes
1759 // both start and end points, but resize only changes end values.
1760 // Here we only want to fastpaint on a scroll, with resize using a normal
1761 // paint, so scroll events are identified as changes to the horizontal or
1762 // vertical start value.
1763 if (eventName.equals(ViewportRanges.STARTRES))
1765 if (av.getWrapAlignment())
1767 fastPaintWrapped(scrollX);
1771 fastPaint(scrollX, 0);
1774 else if (eventName.equals(ViewportRanges.STARTSEQ))
1777 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1779 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1781 if (av.getWrapAlignment())
1783 fastPaintWrapped(scrollX);
1787 fastPaint(scrollX, 0);
1790 else if (eventName.equals(ViewportRanges.STARTSEQ))
1793 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1795 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1797 if (av.getWrapAlignment())
1799 fastPaintWrapped(scrollX);
1805 * Does a minimal update of the image for a scroll movement. This method
1806 * handles scroll movements of up to one width of the wrapped alignment (one
1807 * click in the vertical scrollbar). Larger movements (for example after a
1808 * scroll to highlight a mapped position) trigger a full redraw instead.
1811 * number of positions scrolled (right if positive, left if negative)
1813 protected void fastPaintWrapped(int scrollX)
1815 ViewportRanges ranges = av.getRanges();
1817 if (Math.abs(scrollX) > ranges.getViewportWidth())
1820 * shift of more than one view width is
1821 * overcomplicated to handle in this method
1828 if (fastpainting || gg == null)
1834 fastpainting = true;
1838 calculateWrappedGeometry(getWidth(), getHeight());
1841 * relocate the regions of the alignment that are still visible
1843 shiftWrappedAlignment(-scrollX);
1846 * add new columns (sequence, annotation)
1847 * - at top left if scrollX < 0
1848 * - at right of last two widths if scrollX > 0
1852 int startRes = ranges.getStartRes();
1853 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1854 - scrollX - 1, getHeight());
1858 fastPaintWrappedAddRight(scrollX);
1862 * draw all scales (if shown) and hidden column markers
1864 drawWrappedDecorators(gg, ranges.getStartRes());
1869 fastpainting = false;
1874 * Draws the specified number of columns at the 'end' (bottom right) of a
1875 * wrapped alignment view, including sequences and annotations if shown, but
1876 * not scales. Also draws the same number of columns at the right hand end of
1877 * the second last width shown, if the last width is not full height (so
1878 * cannot simply be copied from the graphics image).
1882 protected void fastPaintWrappedAddRight(int columns)
1889 ViewportRanges ranges = av.getRanges();
1890 int viewportWidth = ranges.getViewportWidth();
1891 int charWidth = av.getCharWidth();
1894 * draw full height alignment in the second last row, last columns, if the
1895 * last row was not full height
1897 int visibleWidths = wrappedVisibleWidths;
1898 int canvasHeight = getHeight();
1899 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1901 if (lastWidthPartHeight)
1903 int widthsAbove = Math.max(0, visibleWidths - 2);
1904 int ypos = wrappedRepeatHeightPx * widthsAbove
1905 + wrappedSpaceAboveAlignment;
1906 int endRes = ranges.getEndRes();
1907 endRes += widthsAbove * viewportWidth;
1908 int startRes = endRes - columns;
1909 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1913 * white fill first to erase annotations
1915 gg.translate(xOffset, 0);
1916 gg.setColor(Color.white);
1917 gg.fillRect(labelWidthWest, ypos,
1918 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1919 gg.translate(-xOffset, 0);
1921 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1925 * draw newly visible columns in last wrapped width (none if we
1926 * have reached the end of the alignment)
1927 * y-offset for drawing last width is height of widths above,
1930 int widthsAbove = visibleWidths - 1;
1931 int ypos = wrappedRepeatHeightPx * widthsAbove
1932 + wrappedSpaceAboveAlignment;
1933 int endRes = ranges.getEndRes();
1934 endRes += widthsAbove * viewportWidth;
1935 int startRes = endRes - columns + 1;
1938 * white fill first to erase annotations
1940 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1942 gg.translate(xOffset, 0);
1943 gg.setColor(Color.white);
1944 int width = viewportWidth * charWidth - xOffset;
1945 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1946 gg.translate(-xOffset, 0);
1948 gg.setFont(av.getFont());
1949 gg.setColor(Color.black);
1951 if (startRes < ranges.getVisibleAlignmentWidth())
1953 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1957 * and finally, white fill any space below the visible alignment
1959 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1960 if (heightBelow > 0)
1962 gg.setColor(Color.white);
1963 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1968 * Shifts the visible alignment by the specified number of columns - left if
1969 * negative, right if positive. Copies and moves sequences and annotations (if
1970 * shown). Scales, hidden column markers and any newly visible columns must be
1975 protected void shiftWrappedAlignment(int positions)
1981 int charWidth = av.getCharWidth();
1983 int canvasHeight = getHeight();
1984 ViewportRanges ranges = av.getRanges();
1985 int viewportWidth = ranges.getViewportWidth();
1986 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1988 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1989 int xMax = ranges.getVisibleAlignmentWidth();
1994 * shift right (after scroll left)
1995 * for each wrapped width (starting with the last), copy (width-positions)
1996 * columns from the left margin to the right margin, and copy positions
1997 * columns from the right margin of the row above (if any) to the
1998 * left margin of the current row
2002 * get y-offset of last wrapped width, first row of sequences
2004 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2005 y += wrappedSpaceAboveAlignment;
2006 int copyFromLeftStart = labelWidthWest;
2007 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2011 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2012 positions * charWidth, 0);
2015 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2016 positions * charWidth, heightToCopy, -widthToCopy,
2017 wrappedRepeatHeightPx);
2020 y -= wrappedRepeatHeightPx;
2026 * shift left (after scroll right)
2027 * for each wrapped width (starting with the first), copy (width-positions)
2028 * columns from the right margin to the left margin, and copy positions
2029 * columns from the left margin of the row below (if any) to the
2030 * right margin of the current row
2032 int xpos = av.getRanges().getStartRes();
2033 int y = wrappedSpaceAboveAlignment;
2034 int copyFromRightStart = labelWidthWest - positions * charWidth;
2036 while (y < canvasHeight)
2038 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2039 positions * charWidth, 0);
2040 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2041 && (xpos + viewportWidth <= xMax))
2043 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2044 * charWidth, heightToCopy, widthToCopy,
2045 -wrappedRepeatHeightPx);
2048 y += wrappedRepeatHeightPx;
2049 xpos += viewportWidth;
2056 * Redraws any positions in the search results in the visible region of a
2057 * wrapped alignment. Any highlights are drawn depending on the search results
2058 * set on the Viewport, not the <code>results</code> argument. This allows
2059 * this method to be called either to clear highlights (passing the previous
2060 * search results), or to draw new highlights.
2065 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2067 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
2071 int charHeight = av.getCharHeight();
2073 boolean matchFound = false;
2075 calculateWrappedGeometry(getWidth(), getHeight());
2076 int wrappedWidth = av.getWrappedWidth();
2077 int wrappedHeight = wrappedRepeatHeightPx;
2079 ViewportRanges ranges = av.getRanges();
2080 int canvasHeight = getHeight();
2081 int repeats = canvasHeight / wrappedHeight;
2082 if (canvasHeight / wrappedHeight > 0)
2087 int firstVisibleColumn = ranges.getStartRes();
2088 int lastVisibleColumn = ranges.getStartRes() + repeats
2089 * ranges.getViewportWidth() - 1;
2091 AlignmentI alignment = av.getAlignment();
2092 if (av.hasHiddenColumns())
2094 firstVisibleColumn = alignment.getHiddenColumns()
2095 .visibleToAbsoluteColumn(firstVisibleColumn);
2096 lastVisibleColumn = alignment.getHiddenColumns()
2097 .visibleToAbsoluteColumn(lastVisibleColumn);
2100 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2102 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2103 .getEndSeq(); seqNo++)
2105 SequenceI seq = alignment.getSequenceAt(seqNo);
2107 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2109 if (visibleResults != null)
2111 for (int i = 0; i < visibleResults.length - 1; i += 2)
2113 int firstMatchedColumn = visibleResults[i];
2114 int lastMatchedColumn = visibleResults[i + 1];
2115 if (firstMatchedColumn <= lastVisibleColumn
2116 && lastMatchedColumn >= firstVisibleColumn)
2119 * found a search results match in the visible region
2121 firstMatchedColumn = Math.max(firstMatchedColumn,
2122 firstVisibleColumn);
2123 lastMatchedColumn = Math.min(lastMatchedColumn,
2127 * draw each mapped position separately (as contiguous positions may
2128 * wrap across lines)
2130 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2132 int displayColumn = mappedPos;
2133 if (av.hasHiddenColumns())
2135 displayColumn = alignment.getHiddenColumns()
2136 .absoluteToVisibleColumn(displayColumn);
2140 * transX: offset from left edge of canvas to residue position
2142 int transX = labelWidthWest
2143 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2144 * av.getCharWidth();
2147 * transY: offset from top edge of canvas to residue position
2149 int transY = gapHeight;
2150 transY += (displayColumn - ranges.getStartRes())
2151 / wrappedWidth * wrappedHeight;
2152 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2155 * yOffset is from graphics origin to start of visible region
2157 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2158 if (transY < getHeight())
2161 gg.translate(transX, transY);
2162 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2164 gg.translate(-transX, -transY);
2176 * Answers the width in pixels of the left scale labels (0 if not shown)
2180 int getLabelWidthWest()
2182 return labelWidthWest;