2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.AlphaComposite;
36 import java.awt.BasicStroke;
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Graphics2D;
42 import java.awt.RenderingHints;
43 import java.awt.Shape;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.Iterator;
47 import java.util.List;
49 import javax.swing.JPanel;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 public class SeqCanvas extends JPanel implements ViewportListenerI
59 private static final String ZEROS = "0000000000";
61 final FeatureRenderer fr;
71 private final SequenceRenderer seqRdr;
73 private boolean fastPaint = false;
75 private boolean fastpainting = false;
77 private AnnotationPanel annotations;
80 * measurements for drawing a wrapped alignment
82 private int labelWidthEast; // label right width in pixels if shown
84 private int labelWidthWest; // label left width in pixels if shown
86 private int wrappedSpaceAboveAlignment; // gap between widths
88 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
90 private int wrappedVisibleWidths; // number of wrapped widths displayed
92 // Don't do this! Graphics handles are supposed to be transient
93 //private Graphics2D gg;
96 * Creates a new SeqCanvas object.
100 public SeqCanvas(AlignmentPanel ap)
103 fr = new FeatureRenderer(ap);
104 seqRdr = new SequenceRenderer(av);
105 setLayout(new BorderLayout());
106 PaintRefresher.Register(this, av.getSequenceSetId());
107 setBackground(Color.white);
109 av.getRanges().addPropertyChangeListener(this);
112 public SequenceRenderer getSequenceRenderer()
117 public FeatureRenderer getFeatureRenderer()
123 * Draws the scale above a region of a wrapped alignment, consisting of a
124 * column number every major interval (10 columns).
127 * the graphics context to draw on, positioned at the start (bottom
128 * left) of the line on which to draw any scale marks
130 * start alignment column (0..)
132 * end alignment column (0..)
134 * y offset to draw at
136 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
138 int charHeight = av.getCharHeight();
139 int charWidth = av.getCharWidth();
142 * white fill the scale space (for the fastPaint case)
144 g.setColor(Color.white);
145 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
146 charHeight * 3 / 2 + 2);
147 g.setColor(Color.black);
149 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
151 for (ScaleMark mark : marks)
153 int mpos = mark.column; // (i - startx - 1)
158 String mstring = mark.text;
164 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
168 * draw a tick mark below the column number, centred on the column;
169 * height of tick mark is 4 pixels less than half a character
171 int xpos = (mpos * charWidth) + (charWidth / 2);
172 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
178 * Draw the scale to the left or right of a wrapped alignment
181 * graphics context, positioned at the start of the scale to be drawn
183 * first column of wrapped width (0.. excluding any hidden columns)
185 * last column of wrapped width (0.. excluding any hidden columns)
187 * vertical offset at which to begin the scale
189 * if true, scale is left of residues, if false, scale is right
191 void drawVerticalScale(Graphics g, final int startx, final int endx,
192 final int ypos, final boolean left)
194 int charHeight = av.getCharHeight();
195 int charWidth = av.getCharWidth();
197 int yPos = ypos + charHeight;
201 if (av.hasHiddenColumns())
203 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
204 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
205 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
207 FontMetrics fm = getFontMetrics(av.getFont());
209 for (int i = 0; i < av.getAlignment().getHeight(); i++)
211 SequenceI seq = av.getAlignment().getSequenceAt(i);
214 * find sequence position of first non-gapped position -
215 * to the right if scale left, to the left if scale right
217 int index = left ? startX : endX;
219 while (index >= startX && index <= endX)
221 if (!Comparison.isGap(seq.getCharAt(index)))
223 value = seq.findPosition(index);
238 * white fill the space for the scale
240 g.setColor(Color.white);
241 int y = (yPos + (i * charHeight)) - (charHeight / 5);
242 // fillRect origin is top left of rectangle
243 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
249 * draw scale value, right justified within its width less half a
250 * character width padding on the right
252 int labelSpace = left ? labelWidthWest : labelWidthEast;
253 labelSpace -= charWidth / 2; // leave space to the right
254 String valueAsString = String.valueOf(value);
255 int labelLength = fm.stringWidth(valueAsString);
256 int xOffset = labelSpace - labelLength;
257 g.setColor(Color.black);
258 g.drawString(valueAsString, xOffset, y);
265 * Does a fast paint of an alignment in response to a scroll. Most of the
266 * visible region is simply copied and shifted, and then any newly visible
267 * columns or rows are drawn. The scroll may be horizontal or vertical, but
268 * not both at once. Scrolling may be the result of
270 * <li>dragging a scroll bar</li>
271 * <li>clicking in the scroll bar</li>
272 * <li>scrolling by trackpad, middle mouse button, or other device</li>
273 * <li>by moving the box in the Overview window</li>
274 * <li>programmatically to make a highlighted position visible</li>
278 * columns to shift right (positive) or left (negative)
280 * rows to shift down (positive) or up (negative)
282 public void fastPaint(int horizontal, int vertical)
284 if (fastpainting || img == null)
293 int charHeight = av.getCharHeight();
294 int charWidth = av.getCharWidth();
296 ViewportRanges ranges = av.getRanges();
297 int startRes = ranges.getStartRes();
298 int endRes = ranges.getEndRes();
299 int startSeq = ranges.getStartSeq();
300 int endSeq = ranges.getEndSeq();
304 Graphics gg = img.getGraphics();
305 gg.copyArea(horizontal * charWidth, vertical * charHeight,
306 img.getWidth(), img.getHeight(), -horizontal * charWidth,
307 -vertical * charHeight);
309 if (horizontal > 0) // scrollbar pulled right, image to the left
311 transX = (endRes - startRes - horizontal) * charWidth;
312 startRes = endRes - horizontal;
314 else if (horizontal < 0)
316 endRes = startRes - horizontal;
319 if (vertical > 0) // scroll down
321 startSeq = endSeq - vertical;
323 if (startSeq < ranges.getStartSeq())
324 { // ie scrolling too fast, more than a page at a time
325 startSeq = ranges.getStartSeq();
329 transY = img.getHeight() - ((vertical + 1) * charHeight);
332 else if (vertical < 0)
334 endSeq = startSeq - vertical;
336 if (endSeq > ranges.getEndSeq())
338 endSeq = ranges.getEndSeq();
342 gg.translate(transX, transY);
343 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
344 gg.translate(-transX, -transY);
347 // Call repaint on alignment panel so that repaints from other alignment
348 // panel components can be aggregated. Otherwise performance of the
349 // overview window and others may be adversely affected.
350 av.getAlignPanel().repaint();
353 fastpainting = false;
358 public void paintComponent(Graphics g)
360 super.paintComponent(g);
362 int charHeight = av.getCharHeight();
363 int charWidth = av.getCharWidth();
365 ViewportRanges ranges = av.getRanges();
367 int width = getWidth();
368 int height = getHeight();
370 width -= (width % charWidth);
371 height -= (height % charHeight);
373 // selectImage is the selection group outline image
374 BufferedImage selectImage = drawSelectionGroup(ranges.getStartRes(),
375 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
377 if ((img != null) && (fastPaint
378 || (getVisibleRect().width != g.getClipBounds().width)
379 || (getVisibleRect().height != g.getClipBounds().height)))
381 BufferedImage lcimg = buildLocalImage(selectImage);
382 g.drawImage(lcimg, 0, 0, this);
385 else if ((width > 0) && (height > 0))
387 // img is a cached version of the last view we drew, if any
388 // if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
400 Graphics2D gg = (Graphics2D) img.getGraphics();
401 gg.setFont(av.getFont());
405 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
406 RenderingHints.VALUE_ANTIALIAS_ON);
409 gg.setColor(Color.white);
410 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
412 if (av.getWrapAlignment())
414 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
418 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
419 ranges.getStartSeq(), ranges.getEndSeq(), 0);
425 // lcimg is a local *copy* of img which we'll draw selectImage on top of
426 BufferedImage lcimg = buildLocalImage(selectImage);
427 g.drawImage(lcimg, 0, 0, this);
433 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
434 ranges.getStartSeq(), ranges.getEndSeq());
439 * Draw an alignment panel for printing
442 * Graphics object to draw with
444 * start residue of print area
446 * end residue of print area
448 * start sequence of print area
450 * end sequence of print area
452 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
453 int startSeq, int endSeq)
455 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
457 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
459 if (selectImage != null)
461 ((Graphics2D) g1).setComposite(AlphaComposite
462 .getInstance(AlphaComposite.SRC_OVER));
463 g1.drawImage(selectImage, 0, 0, this);
468 * Draw a wrapped alignment panel for printing
471 * Graphics object to draw with
473 * width of drawing area
474 * @param canvasHeight
475 * height of drawing area
477 * start residue of print area
479 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
480 int canvasHeight, int startRes)
482 SequenceGroup group = av.getSelectionGroup();
484 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
488 BufferedImage selectImage = null;
491 selectImage = new BufferedImage(canvasWidth, canvasHeight,
492 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
493 } catch (OutOfMemoryError er)
496 System.err.println("Print image OutOfMemory Error.\n" + er);
497 new OOMWarning("Creating wrapped alignment image for printing", er);
499 if (selectImage != null)
501 Graphics2D g2 = selectImage.createGraphics();
502 setupSelectionGroup(g2, selectImage);
503 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
507 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
508 g.drawImage(selectImage, 0, 0, this);
515 * Make a local image by combining the cached image img
518 private BufferedImage buildLocalImage(BufferedImage selectImage)
520 // clone the cached image
521 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
524 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
526 Graphics2D g2d = lcimg.createGraphics();
527 g2d.drawImage(img, 0, 0, null);
529 // overlay selection group on lcimg
530 if (selectImage != null)
533 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
534 g2d.drawImage(selectImage, 0, 0, this);
543 * Set up a buffered image of the correct height and size for the sequence canvas
545 private BufferedImage setupImage()
547 BufferedImage lcimg = null;
549 int charWidth = av.getCharWidth();
550 int charHeight = av.getCharHeight();
552 int width = getWidth();
553 int height = getHeight();
555 width -= (width % charWidth);
556 height -= (height % charHeight);
558 if ((width < 1) || (height < 1))
565 lcimg = new BufferedImage(width, height,
566 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
567 } catch (OutOfMemoryError er)
571 "Group image OutOfMemory Redraw Error.\n" + er);
572 new OOMWarning("Creating alignment image for display", er);
581 * Returns the visible width of the canvas in residues, after allowing for
582 * East or West scales (if shown)
585 * the width in pixels (possibly including scales)
589 public int getWrappedCanvasWidth(int canvasWidth)
591 int charWidth = av.getCharWidth();
593 FontMetrics fm = getFontMetrics(av.getFont());
597 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
599 labelWidth = getLabelWidth(fm);
602 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
604 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
606 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
610 * Returns a pixel width sufficient to show the largest sequence coordinate
611 * (end position) in the alignment, calculated as the FontMetrics width of
612 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
613 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
614 * half a character width space on either side.
619 protected int getLabelWidth(FontMetrics fm)
622 * find the biggest sequence end position we need to show
623 * (note this is not necessarily the sequence length)
626 AlignmentI alignment = av.getAlignment();
627 for (int i = 0; i < alignment.getHeight(); i++)
629 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
633 for (int i = maxWidth; i > 0; i /= 10)
638 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
642 * Draws as many widths of a wrapped alignment as can fit in the visible
647 * available width in pixels
648 * @param canvasHeight
649 * available height in pixels
651 * the first column (0...) of the alignment to draw
653 public void drawWrappedPanel(Graphics g, int canvasWidth,
654 int canvasHeight, final int startColumn)
656 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
659 av.setWrappedWidth(wrappedWidthInResidues);
661 ViewportRanges ranges = av.getRanges();
662 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
664 // we need to call this again to make sure the startColumn +
665 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
667 calculateWrappedGeometry(canvasWidth, canvasHeight);
670 * draw one width at a time (including any scales or annotation shown),
671 * until we have run out of either alignment or vertical space available
673 int ypos = wrappedSpaceAboveAlignment;
674 int maxWidth = ranges.getVisibleAlignmentWidth();
676 int start = startColumn;
677 int currentWidth = 0;
678 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
681 .min(maxWidth, start + wrappedWidthInResidues - 1);
682 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
683 ypos += wrappedRepeatHeightPx;
684 start += wrappedWidthInResidues;
688 drawWrappedDecorators(g, startColumn);
692 * Calculates and saves values needed when rendering a wrapped alignment.
693 * These depend on many factors, including
695 * <li>canvas width and height</li>
696 * <li>number of visible sequences, and height of annotations if shown</li>
697 * <li>font and character width</li>
698 * <li>whether scales are shown left, right or above the alignment</li>
702 * @param canvasHeight
703 * @return the number of residue columns in each width
705 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
707 int charHeight = av.getCharHeight();
710 * vertical space in pixels between wrapped widths of alignment
711 * - one character height, or two if scale above is drawn
713 wrappedSpaceAboveAlignment = charHeight
714 * (av.getScaleAboveWrapped() ? 2 : 1);
717 * height in pixels of the wrapped widths
719 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
721 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
723 // add annotations panel height if shown
724 wrappedRepeatHeightPx += getAnnotationHeight();
727 * number of visible widths (the last one may be part height),
728 * ensuring a part height includes at least one sequence
730 ViewportRanges ranges = av.getRanges();
731 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
732 int remainder = canvasHeight % wrappedRepeatHeightPx;
733 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
735 wrappedVisibleWidths++;
739 * compute width in residues; this also sets East and West label widths
741 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
744 * limit visibleWidths to not exceed width of alignment
746 int xMax = ranges.getVisibleAlignmentWidth();
747 int startToEnd = xMax - ranges.getStartRes();
748 int maxWidths = startToEnd / wrappedWidthInResidues;
749 if (startToEnd % wrappedWidthInResidues > 0)
753 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
755 return wrappedWidthInResidues;
759 * Draws one width of a wrapped alignment, including sequences and
760 * annnotations, if shown, but not scales or hidden column markers
766 * @param canvasHeight
768 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
769 int endColumn, int canvasHeight)
771 ViewportRanges ranges = av.getRanges();
772 int viewportWidth = ranges.getViewportWidth();
774 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
777 * move right before drawing by the width of the scale left (if any)
778 * plus column offset from left margin (usually zero, but may be non-zero
779 * when fast painting is drawing just a few columns)
781 int charWidth = av.getCharWidth();
782 int xOffset = labelWidthWest
783 + ((startColumn - ranges.getStartRes()) % viewportWidth)
786 // BH 2018 note: I have switched to using Graphics.create() here because it is
787 // more reliable (and simpler) to reset. The difference seems to be that SwingJS
788 // automatically sets a clipping region on an image to be the image dimensions, whereas
789 // Java sets no clip for an image. (A bug? Go figure!)
790 // Since we are using an off-screen BufferedImage here, the result is that g.getClip()
791 // returns non-null in JavaScript but not Java.
793 // Anyway, this works and, I suggest, is better design anyway.
795 // Graphics g = gg.create();
796 // mc 30/08/18 undone because gnu.jpdf.PDFGraphics doesn't handle
799 g.translate(xOffset, 0);
801 // When printing we have an extra clipped region,
802 // the Printable page which we need to account for here
803 Shape clip = g.getClip();
806 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
810 g.setClip(0, (int) clip.getBounds().getY(),
811 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
816 * white fill the region to be drawn (so incremental fast paint doesn't
817 * scribble over an existing image)
819 g.setColor(Color.white);
820 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
821 wrappedRepeatHeightPx);
823 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
826 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
828 if (av.isShowAnnotation())
830 g.translate(0, cHeight + ypos + 3);
831 if (annotations == null)
833 annotations = new AnnotationPanel(av);
836 annotations.renderer.drawComponent(annotations, av, g, -1,
837 startColumn, endx + 1);
838 g.translate(0, -cHeight - ypos - 3);
841 g.translate(-xOffset, 0);
846 * Draws scales left, right and above (if shown), and any hidden column
847 * markers, on all widths of the wrapped alignment
852 protected void drawWrappedDecorators(Graphics g, final int startColumn)
854 int charWidth = av.getCharWidth();
856 g.setFont(av.getFont());
858 g.setColor(Color.black);
860 int ypos = wrappedSpaceAboveAlignment;
861 ViewportRanges ranges = av.getRanges();
862 int viewportWidth = ranges.getViewportWidth();
863 int maxWidth = ranges.getVisibleAlignmentWidth();
865 int startCol = startColumn;
867 while (widthsDrawn < wrappedVisibleWidths)
869 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
871 if (av.getScaleLeftWrapped())
873 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
876 if (av.getScaleRightWrapped())
878 int x = labelWidthWest + viewportWidth * charWidth;
881 drawVerticalScale(g, startCol, endColumn, ypos, false);
886 * white fill region of scale above and hidden column markers
887 * (to support incremental fast paint of image)
889 g.translate(labelWidthWest, 0);
890 g.setColor(Color.white);
891 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
892 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
893 g.setColor(Color.black);
894 g.translate(-labelWidthWest, 0);
896 g.translate(labelWidthWest, 0);
898 if (av.getScaleAboveWrapped())
900 drawNorthScale(g, startCol, endColumn, ypos);
903 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
905 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
908 g.translate(-labelWidthWest, 0);
910 ypos += wrappedRepeatHeightPx;
911 startCol += viewportWidth;
917 * Draws markers (triangles) above hidden column positions between startColumn
925 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
926 int startColumn, int endColumn)
928 int charHeight = av.getCharHeight();
929 int charWidth = av.getCharWidth();
931 g.setColor(Color.blue);
933 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
935 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
939 res = it.next() - startColumn;
941 if (res < 0 || res > endColumn - startColumn + 1)
947 * draw a downward-pointing triangle at the hidden columns location
948 * (before the following visible column)
950 int xMiddle = res * charWidth;
951 int[] xPoints = new int[] { xMiddle - charHeight / 4,
952 xMiddle + charHeight / 4, xMiddle };
953 int yTop = ypos - (charHeight / 2);
954 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
955 g.fillPolygon(xPoints, yPoints, 3);
960 * Draw a selection group over a wrapped alignment
962 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
964 int canvasHeight, int startRes)
966 int charHeight = av.getCharHeight();
967 int charWidth = av.getCharWidth();
969 // height gap above each panel
970 int hgap = charHeight;
971 if (av.getScaleAboveWrapped())
976 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
978 int cHeight = av.getAlignment().getHeight() * charHeight;
980 int startx = startRes;
982 int ypos = hgap; // vertical offset
983 int maxwidth = av.getAlignment().getWidth();
985 if (av.hasHiddenColumns())
987 maxwidth = av.getAlignment().getHiddenColumns()
988 .absoluteToVisibleColumn(maxwidth);
991 // chop the wrapped alignment extent up into panel-sized blocks and treat
992 // each block as if it were a block from an unwrapped alignment
993 while ((ypos <= canvasHeight) && (startx < maxwidth))
995 // set end value to be start + width, or maxwidth, whichever is smaller
996 endx = startx + cWidth - 1;
1003 g.translate(labelWidthWest, 0);
1005 drawUnwrappedSelection(g, group, startx, endx, 0,
1006 av.getAlignment().getHeight() - 1,
1009 g.translate(-labelWidthWest, 0);
1011 // update vertical offset
1012 ypos += cHeight + getAnnotationHeight() + hgap;
1014 // update horizontal offset
1019 int getAnnotationHeight()
1021 if (!av.isShowAnnotation())
1026 if (annotations == null)
1028 annotations = new AnnotationPanel(av);
1031 return annotations.adjustPanelHeight();
1035 * Draws the visible region of the alignment on the graphics context. If there
1036 * are hidden column markers in the visible region, then each sub-region
1037 * between the markers is drawn separately, followed by the hidden column
1041 * the graphics context, positioned at the first residue to be drawn
1043 * offset of the first column to draw (0..)
1045 * offset of the last column to draw (0..)
1047 * offset of the first sequence to draw (0..)
1049 * offset of the last sequence to draw (0..)
1051 * vertical offset at which to draw (for wrapped alignments)
1053 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1054 final int startSeq, final int endSeq, final int yOffset)
1056 int charHeight = av.getCharHeight();
1057 int charWidth = av.getCharWidth();
1059 if (!av.hasHiddenColumns())
1061 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1069 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1070 VisibleContigsIterator regions = hidden
1071 .getVisContigsIterator(startRes, endRes + 1, true);
1073 while (regions.hasNext())
1075 int[] region = regions.next();
1076 blockEnd = region[1];
1077 blockStart = region[0];
1080 * draw up to just before the next hidden region, or the end of
1081 * the visible region, whichever comes first
1083 g1.translate(screenY * charWidth, 0);
1085 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1088 * draw the downline of the hidden column marker (ScalePanel draws the
1089 * triangle on top) if we reached it
1091 if (av.getShowHiddenMarkers()
1092 && (regions.hasNext() || regions.endsAtHidden()))
1094 g1.setColor(Color.blue);
1096 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1097 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1098 (endSeq - startSeq + 1) * charHeight + yOffset);
1101 g1.translate(-screenY * charWidth, 0);
1102 screenY += blockEnd - blockStart + 1;
1109 * Draws a region of the visible alignment
1113 * offset of the first column in the visible region (0..)
1115 * offset of the last column in the visible region (0..)
1117 * offset of the first sequence in the visible region (0..)
1119 * offset of the last sequence in the visible region (0..)
1121 * vertical offset at which to draw (for wrapped alignments)
1123 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1124 int endSeq, int offset)
1126 int charHeight = av.getCharHeight();
1127 int charWidth = av.getCharWidth();
1129 g.setFont(av.getFont());
1130 seqRdr.prepare(g, av.isRenderGaps());
1134 // / First draw the sequences
1135 // ///////////////////////////
1136 for (int i = startSeq; i <= endSeq; i++)
1138 nextSeq = av.getAlignment().getSequenceAt(i);
1139 if (nextSeq == null)
1141 // occasionally, a race condition occurs such that the alignment row is
1145 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1146 startRes, endRes, offset + ((i - startSeq) * charHeight));
1148 if (av.isShowSequenceFeatures())
1150 fr.drawSequence(g, nextSeq, startRes, endRes,
1151 offset + ((i - startSeq) * charHeight), false);
1155 * highlight search Results once sequence has been drawn
1157 if (av.hasSearchResults())
1159 SearchResultsI searchResults = av.getSearchResults();
1160 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1162 if (visibleResults != null)
1164 for (int r = 0; r < visibleResults.length; r += 2)
1166 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1167 visibleResults[r + 1],
1168 (visibleResults[r] - startRes) * charWidth,
1169 offset + ((i - startSeq) * charHeight));
1175 if (av.getSelectionGroup() != null
1176 || av.getAlignment().getGroups().size() > 0)
1178 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1183 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1184 int startSeq, int endSeq, int offset)
1186 Graphics2D g = (Graphics2D) g1;
1188 // ///////////////////////////////////
1189 // Now outline any areas if necessary
1190 // ///////////////////////////////////
1192 SequenceGroup group = null;
1193 int groupIndex = -1;
1195 if (av.getAlignment().getGroups().size() > 0)
1197 group = av.getAlignment().getGroups().get(0);
1203 g.setStroke(new BasicStroke());
1207 g.setColor(group.getOutlineColour());
1208 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1213 g.setStroke(new BasicStroke());
1215 if (groupIndex >= av.getAlignment().getGroups().size())
1220 group = av.getAlignment().getGroups().get(groupIndex);
1222 } while (groupIndex < av.getAlignment().getGroups().size());
1230 * Draw the selection group as a separate image and overlay
1232 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1233 int startSeq, int endSeq)
1235 // get a new image of the correct size
1236 BufferedImage selectionImage = setupImage();
1238 if (selectionImage == null)
1243 SequenceGroup group = av.getSelectionGroup();
1250 // set up drawing colour
1251 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1253 setupSelectionGroup(g, selectionImage);
1255 if (!av.getWrapAlignment())
1257 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1262 drawWrappedSelection(g, group, getWidth(), getHeight(),
1263 av.getRanges().getStartRes());
1267 return selectionImage;
1271 * Draw the cursor as a separate image and overlay
1274 * start residue of area to draw cursor in
1276 * end residue of area to draw cursor in
1278 * start sequence of area to draw cursor in
1280 * end sequence of are to draw cursor in
1281 * @return a transparent image of the same size as the sequence canvas, with
1282 * the cursor drawn on it, if any
1284 private void drawCursor(Graphics g, int startRes, int endRes,
1288 // convert the cursorY into a position on the visible alignment
1289 int cursor_ypos = cursorY;
1291 // don't do work unless we have to
1292 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1296 int startx = startRes;
1299 // convert the cursorX into a position on the visible alignment
1300 int cursor_xpos = av.getAlignment().getHiddenColumns()
1301 .absoluteToVisibleColumn(cursorX);
1303 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1306 if (av.getWrapAlignment())
1308 // work out the correct offsets for the cursor
1309 int charHeight = av.getCharHeight();
1310 int charWidth = av.getCharWidth();
1311 int canvasWidth = getWidth();
1312 int canvasHeight = getHeight();
1314 // height gap above each panel
1315 int hgap = charHeight;
1316 if (av.getScaleAboveWrapped())
1321 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1323 int cHeight = av.getAlignment().getHeight() * charHeight;
1325 endx = startx + cWidth - 1;
1326 int ypos = hgap; // vertical offset
1328 // iterate down the wrapped panels
1329 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1331 // update vertical offset
1332 ypos += cHeight + getAnnotationHeight() + hgap;
1334 // update horizontal offset
1336 endx = startx + cWidth - 1;
1339 xoffset = labelWidthWest;
1342 // now check if cursor is within range for x values
1343 if (cursor_xpos >= startx && cursor_xpos <= endx)
1345 // get the character the cursor is drawn at
1346 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1347 char s = seq.getCharAt(cursorX);
1349 seqRdr.drawCursor(g, s,
1350 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1351 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1359 * Set up graphics for selection group
1361 private void setupSelectionGroup(Graphics2D g,
1362 BufferedImage selectionImage)
1364 // set background to transparent
1365 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1366 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1368 // set up foreground to draw red dashed line
1369 g.setComposite(AlphaComposite.Src);
1370 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1371 BasicStroke.JOIN_ROUND, 3f, new float[]
1373 g.setColor(Color.RED);
1377 * Draw a selection group over an unwrapped alignment
1378 * @param g graphics object to draw with
1379 * @param group selection group
1380 * @param startRes start residue of area to draw
1381 * @param endRes end residue of area to draw
1382 * @param startSeq start sequence of area to draw
1383 * @param endSeq end sequence of area to draw
1384 * @param offset vertical offset (used when called from wrapped alignment code)
1386 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1387 int startRes, int endRes, int startSeq, int endSeq, int offset)
1389 int charWidth = av.getCharWidth();
1391 if (!av.hasHiddenColumns())
1393 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1398 // package into blocks of visible columns
1403 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1404 VisibleContigsIterator regions = hidden
1405 .getVisContigsIterator(startRes, endRes + 1, true);
1406 while (regions.hasNext())
1408 int[] region = regions.next();
1409 blockEnd = region[1];
1410 blockStart = region[0];
1412 g.translate(screenY * charWidth, 0);
1413 drawPartialGroupOutline(g, group,
1414 blockStart, blockEnd, startSeq, endSeq, offset);
1416 g.translate(-screenY * charWidth, 0);
1417 screenY += blockEnd - blockStart + 1;
1423 * Draw the selection group as a separate image and overlay
1425 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1426 int startRes, int endRes, int startSeq, int endSeq,
1429 int charHeight = av.getCharHeight();
1430 int charWidth = av.getCharWidth();
1431 int visWidth = (endRes - startRes + 1) * charWidth;
1435 boolean inGroup = false;
1440 List<SequenceI> seqs = group.getSequences(null);
1442 // position of start residue of group relative to startRes, in pixels
1443 int sx = (group.getStartRes() - startRes) * charWidth;
1445 // width of group in pixels
1446 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1449 if (!(sx + xwidth < 0 || sx > visWidth))
1451 for (i = startSeq; i <= endSeq; i++)
1453 sy = verticalOffset + (i - startSeq) * charHeight;
1455 if ((sx <= (endRes - startRes) * charWidth)
1456 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1459 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1461 bottom = sy + charHeight;
1466 if (((top == -1) && (i == 0)) || !seqs
1467 .contains(av.getAlignment().getSequenceAt(i - 1)))
1478 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1479 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1481 // reset top and bottom
1489 sy = verticalOffset + ((i - startSeq) * charHeight);
1490 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1491 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1497 * Draw horizontal selection group boundaries at top and bottom positions
1500 * graphics object to draw on
1506 * visWidth maximum available width
1508 * position to draw top of group at
1510 * position to draw bottom of group at
1512 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1513 int visWidth, int top, int bottom)
1523 // don't let width extend beyond current block, or group extent
1525 if (startx + width >= visWidth)
1527 width = visWidth - startx;
1532 g.drawLine(startx, top, startx + width, top);
1537 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1542 * Draw vertical lines at sx and sx+xwidth providing they lie within
1546 * graphics object to draw on
1552 * visWidth maximum available width
1558 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1561 // if start position is visible, draw vertical line to left of
1563 if (sx >= 0 && sx < visWidth)
1565 g.drawLine(sx, oldY, sx, sy);
1568 // if end position is visible, draw vertical line to right of
1570 if (sx + xwidth < visWidth)
1572 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1577 * Highlights search results in the visible region by rendering as white text
1578 * on a black background. Any previous highlighting is removed. Answers true
1579 * if any highlight was left on the visible alignment (so status bar should be
1580 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1581 * so allows the next repaint to update the whole display.
1586 public boolean highlightSearchResults(SearchResultsI results)
1588 return highlightSearchResults(results, false);
1593 * Highlights search results in the visible region by rendering as white text
1594 * on a black background. Any previous highlighting is removed. Answers true
1595 * if any highlight was left on the visible alignment (so status bar should be
1596 * set to match), else false.
1598 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1599 * highlighted regions are modified. This speeds up highlighting across linked
1602 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1603 * a wrapped alignment had to be scrolled to show the highlighted region, then
1604 * it should be fully redrawn, otherwise a fast paint can be performed. This
1605 * argument could be removed if fast paint of scrolled wrapped alignment is
1606 * coded in future (JAL-2609).
1609 * @param doFastPaint
1610 * if true, sets a flag so the next repaint only redraws the modified
1614 public boolean highlightSearchResults(SearchResultsI results,
1615 boolean doFastPaint)
1621 boolean wrapped = av.getWrapAlignment();
1624 fastPaint = doFastPaint;
1625 fastpainting = fastPaint;
1628 * to avoid redrawing the whole visible region, we instead
1629 * redraw just the minimal regions to remove previous highlights
1632 SearchResultsI previous = av.getSearchResults();
1633 av.setSearchResults(results);
1634 boolean redrawn = false;
1635 boolean drawn = false;
1638 redrawn = drawMappedPositionsWrapped(previous);
1639 drawn = drawMappedPositionsWrapped(results);
1644 redrawn = drawMappedPositions(previous);
1645 drawn = drawMappedPositions(results);
1650 * if highlights were either removed or added, repaint
1658 * return true only if highlights were added
1664 fastpainting = false;
1669 * Redraws the minimal rectangle in the visible region (if any) that includes
1670 * mapped positions of the given search results. Whether or not positions are
1671 * highlighted depends on the SearchResults set on the Viewport. This allows
1672 * this method to be called to either clear or set highlighting. Answers true
1673 * if any positions were drawn (in which case a repaint is still required),
1679 protected boolean drawMappedPositions(SearchResultsI results)
1681 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1687 * calculate the minimal rectangle to redraw that
1688 * includes both new and existing search results
1690 int firstSeq = Integer.MAX_VALUE;
1692 int firstCol = Integer.MAX_VALUE;
1694 boolean matchFound = false;
1696 ViewportRanges ranges = av.getRanges();
1697 int firstVisibleColumn = ranges.getStartRes();
1698 int lastVisibleColumn = ranges.getEndRes();
1699 AlignmentI alignment = av.getAlignment();
1700 if (av.hasHiddenColumns())
1702 firstVisibleColumn = alignment.getHiddenColumns()
1703 .visibleToAbsoluteColumn(firstVisibleColumn);
1704 lastVisibleColumn = alignment.getHiddenColumns()
1705 .visibleToAbsoluteColumn(lastVisibleColumn);
1708 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1709 .getEndSeq(); seqNo++)
1711 SequenceI seq = alignment.getSequenceAt(seqNo);
1713 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1715 if (visibleResults != null)
1717 for (int i = 0; i < visibleResults.length - 1; i += 2)
1719 int firstMatchedColumn = visibleResults[i];
1720 int lastMatchedColumn = visibleResults[i + 1];
1721 if (firstMatchedColumn <= lastVisibleColumn
1722 && lastMatchedColumn >= firstVisibleColumn)
1725 * found a search results match in the visible region -
1726 * remember the first and last sequence matched, and the first
1727 * and last visible columns in the matched positions
1730 firstSeq = Math.min(firstSeq, seqNo);
1731 lastSeq = Math.max(lastSeq, seqNo);
1732 firstMatchedColumn = Math.max(firstMatchedColumn,
1733 firstVisibleColumn);
1734 lastMatchedColumn = Math.min(lastMatchedColumn,
1736 firstCol = Math.min(firstCol, firstMatchedColumn);
1737 lastCol = Math.max(lastCol, lastMatchedColumn);
1745 if (av.hasHiddenColumns())
1747 firstCol = alignment.getHiddenColumns()
1748 .absoluteToVisibleColumn(firstCol);
1749 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1751 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1752 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1753 Graphics gg = img.getGraphics();
1754 gg.translate(transX, transY);
1755 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1756 gg.translate(-transX, -transY);
1764 public void propertyChange(PropertyChangeEvent evt)
1766 String eventName = evt.getPropertyName();
1768 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1774 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1782 if (eventName.equals(ViewportRanges.STARTRES)
1783 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1785 // Make sure we're not trying to draw a panel
1786 // larger than the visible window
1787 if (eventName.equals(ViewportRanges.STARTRES))
1789 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1793 scrollX = ((int[]) evt.getNewValue())[0]
1794 - ((int[]) evt.getOldValue())[0];
1796 ViewportRanges vpRanges = av.getRanges();
1798 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1799 if (scrollX > range)
1803 else if (scrollX < -range)
1808 // Both scrolling and resizing change viewport ranges: scrolling changes
1809 // both start and end points, but resize only changes end values.
1810 // Here we only want to fastpaint on a scroll, with resize using a normal
1811 // paint, so scroll events are identified as changes to the horizontal or
1812 // vertical start value.
1813 if (eventName.equals(ViewportRanges.STARTRES))
1815 if (av.getWrapAlignment())
1817 fastPaintWrapped(scrollX);
1821 fastPaint(scrollX, 0);
1824 else if (eventName.equals(ViewportRanges.STARTSEQ))
1827 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1829 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1831 if (av.getWrapAlignment())
1833 fastPaintWrapped(scrollX);
1837 fastPaint(scrollX, 0);
1840 else if (eventName.equals(ViewportRanges.STARTSEQ))
1843 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1845 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1847 if (av.getWrapAlignment())
1849 fastPaintWrapped(scrollX);
1855 * Does a minimal update of the image for a scroll movement. This method
1856 * handles scroll movements of up to one width of the wrapped alignment (one
1857 * click in the vertical scrollbar). Larger movements (for example after a
1858 * scroll to highlight a mapped position) trigger a full redraw instead.
1861 * number of positions scrolled (right if positive, left if negative)
1863 protected void fastPaintWrapped(int scrollX)
1865 ViewportRanges ranges = av.getRanges();
1867 if (Math.abs(scrollX) > ranges.getViewportWidth())
1870 * shift of more than one view width is
1871 * overcomplicated to handle in this method
1878 if (fastpainting || img == null)
1884 fastpainting = true;
1889 Graphics gg = img.getGraphics();
1891 calculateWrappedGeometry(getWidth(), getHeight());
1894 * relocate the regions of the alignment that are still visible
1896 shiftWrappedAlignment(-scrollX);
1899 * add new columns (sequence, annotation)
1900 * - at top left if scrollX < 0
1901 * - at right of last two widths if scrollX > 0
1905 int startRes = ranges.getStartRes();
1906 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1907 - scrollX - 1, getHeight());
1911 fastPaintWrappedAddRight(scrollX);
1915 * draw all scales (if shown) and hidden column markers
1917 drawWrappedDecorators(gg, ranges.getStartRes());
1924 fastpainting = false;
1929 * Draws the specified number of columns at the 'end' (bottom right) of a
1930 * wrapped alignment view, including sequences and annotations if shown, but
1931 * not scales. Also draws the same number of columns at the right hand end of
1932 * the second last width shown, if the last width is not full height (so
1933 * cannot simply be copied from the graphics image).
1937 protected void fastPaintWrappedAddRight(int columns)
1944 Graphics gg = img.getGraphics();
1946 ViewportRanges ranges = av.getRanges();
1947 int viewportWidth = ranges.getViewportWidth();
1948 int charWidth = av.getCharWidth();
1951 * draw full height alignment in the second last row, last columns, if the
1952 * last row was not full height
1954 int visibleWidths = wrappedVisibleWidths;
1955 int canvasHeight = getHeight();
1956 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1958 if (lastWidthPartHeight)
1960 int widthsAbove = Math.max(0, visibleWidths - 2);
1961 int ypos = wrappedRepeatHeightPx * widthsAbove
1962 + wrappedSpaceAboveAlignment;
1963 int endRes = ranges.getEndRes();
1964 endRes += widthsAbove * viewportWidth;
1965 int startRes = endRes - columns;
1966 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1970 * white fill first to erase annotations
1974 gg.translate(xOffset, 0);
1975 gg.setColor(Color.white);
1976 gg.fillRect(labelWidthWest, ypos,
1977 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1978 gg.translate(-xOffset, 0);
1980 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1985 * draw newly visible columns in last wrapped width (none if we
1986 * have reached the end of the alignment)
1987 * y-offset for drawing last width is height of widths above,
1990 int widthsAbove = visibleWidths - 1;
1991 int ypos = wrappedRepeatHeightPx * widthsAbove
1992 + wrappedSpaceAboveAlignment;
1993 int endRes = ranges.getEndRes();
1994 endRes += widthsAbove * viewportWidth;
1995 int startRes = endRes - columns + 1;
1998 * white fill first to erase annotations
2000 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
2002 gg.translate(xOffset, 0);
2003 gg.setColor(Color.white);
2004 int width = viewportWidth * charWidth - xOffset;
2005 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2006 gg.translate(-xOffset, 0);
2008 gg.setFont(av.getFont());
2009 gg.setColor(Color.black);
2011 if (startRes < ranges.getVisibleAlignmentWidth())
2013 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2017 * and finally, white fill any space below the visible alignment
2019 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2020 if (heightBelow > 0)
2022 gg.setColor(Color.white);
2023 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2029 * Shifts the visible alignment by the specified number of columns - left if
2030 * negative, right if positive. Copies and moves sequences and annotations (if
2031 * shown). Scales, hidden column markers and any newly visible columns must be
2036 protected void shiftWrappedAlignment(int positions)
2043 Graphics gg = img.getGraphics();
2045 int charWidth = av.getCharWidth();
2047 int canvasHeight = getHeight();
2048 ViewportRanges ranges = av.getRanges();
2049 int viewportWidth = ranges.getViewportWidth();
2050 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2052 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2053 int xMax = ranges.getVisibleAlignmentWidth();
2058 * shift right (after scroll left)
2059 * for each wrapped width (starting with the last), copy (width-positions)
2060 * columns from the left margin to the right margin, and copy positions
2061 * columns from the right margin of the row above (if any) to the
2062 * left margin of the current row
2066 * get y-offset of last wrapped width, first row of sequences
2068 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2069 y += wrappedSpaceAboveAlignment;
2070 int copyFromLeftStart = labelWidthWest;
2071 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2075 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2076 positions * charWidth, 0);
2079 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2080 positions * charWidth, heightToCopy, -widthToCopy,
2081 wrappedRepeatHeightPx);
2084 y -= wrappedRepeatHeightPx;
2090 * shift left (after scroll right)
2091 * for each wrapped width (starting with the first), copy (width-positions)
2092 * columns from the right margin to the left margin, and copy positions
2093 * columns from the left margin of the row below (if any) to the
2094 * right margin of the current row
2096 int xpos = av.getRanges().getStartRes();
2097 int y = wrappedSpaceAboveAlignment;
2098 int copyFromRightStart = labelWidthWest - positions * charWidth;
2100 while (y < canvasHeight)
2102 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2103 positions * charWidth, 0);
2104 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2105 && (xpos + viewportWidth <= xMax))
2107 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2108 * charWidth, heightToCopy, widthToCopy,
2109 -wrappedRepeatHeightPx);
2111 y += wrappedRepeatHeightPx;
2112 xpos += viewportWidth;
2120 * Redraws any positions in the search results in the visible region of a
2121 * wrapped alignment. Any highlights are drawn depending on the search results
2122 * set on the Viewport, not the <code>results</code> argument. This allows
2123 * this method to be called either to clear highlights (passing the previous
2124 * search results), or to draw new highlights.
2129 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2131 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2135 int charHeight = av.getCharHeight();
2137 boolean matchFound = false;
2139 calculateWrappedGeometry(getWidth(), getHeight());
2140 int wrappedWidth = av.getWrappedWidth();
2141 int wrappedHeight = wrappedRepeatHeightPx;
2143 ViewportRanges ranges = av.getRanges();
2144 int canvasHeight = getHeight();
2145 int repeats = canvasHeight / wrappedHeight;
2146 if (canvasHeight / wrappedHeight > 0)
2151 int firstVisibleColumn = ranges.getStartRes();
2152 int lastVisibleColumn = ranges.getStartRes() + repeats
2153 * ranges.getViewportWidth() - 1;
2155 AlignmentI alignment = av.getAlignment();
2156 if (av.hasHiddenColumns())
2158 firstVisibleColumn = alignment.getHiddenColumns()
2159 .visibleToAbsoluteColumn(firstVisibleColumn);
2160 lastVisibleColumn = alignment.getHiddenColumns()
2161 .visibleToAbsoluteColumn(lastVisibleColumn);
2164 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2167 Graphics gg = img.getGraphics();
2169 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2170 .getEndSeq(); seqNo++)
2172 SequenceI seq = alignment.getSequenceAt(seqNo);
2174 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2176 if (visibleResults != null)
2178 for (int i = 0; i < visibleResults.length - 1; i += 2)
2180 int firstMatchedColumn = visibleResults[i];
2181 int lastMatchedColumn = visibleResults[i + 1];
2182 if (firstMatchedColumn <= lastVisibleColumn
2183 && lastMatchedColumn >= firstVisibleColumn)
2186 * found a search results match in the visible region
2188 firstMatchedColumn = Math.max(firstMatchedColumn,
2189 firstVisibleColumn);
2190 lastMatchedColumn = Math.min(lastMatchedColumn,
2194 * draw each mapped position separately (as contiguous positions may
2195 * wrap across lines)
2197 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2199 int displayColumn = mappedPos;
2200 if (av.hasHiddenColumns())
2202 displayColumn = alignment.getHiddenColumns()
2203 .absoluteToVisibleColumn(displayColumn);
2207 * transX: offset from left edge of canvas to residue position
2209 int transX = labelWidthWest
2210 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2211 * av.getCharWidth();
2214 * transY: offset from top edge of canvas to residue position
2216 int transY = gapHeight;
2217 transY += (displayColumn - ranges.getStartRes())
2218 / wrappedWidth * wrappedHeight;
2219 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2222 * yOffset is from graphics origin to start of visible region
2224 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2225 if (transY < getHeight())
2228 gg.translate(transX, transY);
2229 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2231 gg.translate(-transX, -transY);
2245 * Answers the width in pixels of the left scale labels (0 if not shown)
2249 int getLabelWidthWest()
2251 return labelWidthWest;