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.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.util.Comparison;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.AlphaComposite;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.List;
47 import javax.swing.JComponent;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static final String ZEROS = "0000000000";
59 final FeatureRenderer fr;
69 private final SequenceRenderer seqRdr;
71 private boolean fastPaint = false;
73 private boolean fastpainting = false;
75 private AnnotationPanel annotations;
78 * measurements for drawing a wrapped alignment
80 private int labelWidthEast; // label right width in pixels if shown
82 private int labelWidthWest; // label left width in pixels if shown
84 private int wrappedSpaceAboveAlignment; // gap between widths
86 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
88 private int wrappedVisibleWidths; // number of wrapped widths displayed
90 private Graphics2D gg;
93 * Creates a new SeqCanvas object.
97 public SeqCanvas(AlignmentPanel ap)
100 fr = new FeatureRenderer(ap);
101 seqRdr = new SequenceRenderer(av);
102 setLayout(new BorderLayout());
103 PaintRefresher.Register(this, av.getSequenceSetId());
104 setBackground(Color.white);
106 av.getRanges().addPropertyChangeListener(this);
109 public SequenceRenderer getSequenceRenderer()
114 public FeatureRenderer getFeatureRenderer()
120 * Draws the scale above a region of a wrapped alignment, consisting of a
121 * column number every major interval (10 columns).
124 * the graphics context to draw on, positioned at the start (bottom
125 * left) of the line on which to draw any scale marks
127 * start alignment column (0..)
129 * end alignment column (0..)
131 * y offset to draw at
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
135 int charHeight = av.getCharHeight();
136 int charWidth = av.getCharWidth();
139 * white fill the scale space (for the fastPaint case)
141 g.setColor(Color.white);
142 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
143 charHeight * 3 / 2 + 2);
144 g.setColor(Color.black);
146 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
148 for (ScaleMark mark : marks)
150 int mpos = mark.column; // (i - startx - 1)
155 String mstring = mark.text;
161 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
165 * draw a tick mark below the column number, centred on the column;
166 * height of tick mark is 4 pixels less than half a character
168 int xpos = (mpos * charWidth) + (charWidth / 2);
169 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
175 * Draw the scale to the left or right of a wrapped alignment
178 * graphics context, positioned at the start of the scale to be drawn
180 * first column of wrapped width (0.. excluding any hidden columns)
182 * last column of wrapped width (0.. excluding any hidden columns)
184 * vertical offset at which to begin the scale
186 * if true, scale is left of residues, if false, scale is right
188 void drawVerticalScale(Graphics g, final int startx, final int endx,
189 final int ypos, final boolean left)
191 int charHeight = av.getCharHeight();
192 int charWidth = av.getCharWidth();
194 int yPos = ypos + charHeight;
198 if (av.hasHiddenColumns())
200 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
201 startX = hiddenColumns.adjustForHiddenColumns(startx);
202 endX = hiddenColumns.adjustForHiddenColumns(endx);
204 FontMetrics fm = getFontMetrics(av.getFont());
206 for (int i = 0; i < av.getAlignment().getHeight(); i++)
208 SequenceI seq = av.getAlignment().getSequenceAt(i);
211 * find sequence position of first non-gapped position -
212 * to the right if scale left, to the left if scale right
214 int index = left ? startX : endX;
216 while (index >= startX && index <= endX)
218 if (!Comparison.isGap(seq.getCharAt(index)))
220 value = seq.findPosition(index);
234 * white fill the space for the scale
236 g.setColor(Color.white);
237 int y = (yPos + (i * charHeight)) - (charHeight / 5);
238 // fillRect origin is top left of rectangle
239 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
245 * draw scale value, right justified within its width less half a
246 * character width padding on the right
248 int labelSpace = left ? labelWidthWest : labelWidthEast;
249 labelSpace -= charWidth / 2; // leave space to the right
250 String valueAsString = String.valueOf(value);
251 int labelLength = fm.stringWidth(valueAsString);
252 int xOffset = labelSpace - labelLength;
253 g.setColor(Color.black);
254 g.drawString(valueAsString, xOffset, y);
260 * Does a fast paint of an alignment in response to a scroll. Most of the
261 * visible region is simply copied and shifted, and then any newly visible
262 * columns or rows are drawn. The scroll may be horizontal or vertical, but
263 * not both at once. Scrolling may be the result of
265 * <li>dragging a scroll bar</li>
266 * <li>clicking in the scroll bar</li>
267 * <li>scrolling by trackpad, middle mouse button, or other device</li>
268 * <li>by moving the box in the Overview window</li>
269 * <li>programmatically to make a highlighted position visible</li>
273 * columns to shift right (positive) or left (negative)
275 * rows to shift down (positive) or up (negative)
277 public void fastPaint(int horizontal, int vertical)
279 if (fastpainting || gg == null || img == null)
288 int charHeight = av.getCharHeight();
289 int charWidth = av.getCharWidth();
291 ViewportRanges ranges = av.getRanges();
292 int startRes = ranges.getStartRes();
293 int endRes = ranges.getEndRes();
294 int startSeq = ranges.getStartSeq();
295 int endSeq = ranges.getEndSeq();
299 gg.copyArea(horizontal * charWidth, vertical * charHeight,
300 img.getWidth(), img.getHeight(), -horizontal * charWidth,
301 -vertical * charHeight);
303 if (horizontal > 0) // scrollbar pulled right, image to the left
305 transX = (endRes - startRes - horizontal) * charWidth;
306 startRes = endRes - horizontal;
308 else if (horizontal < 0)
310 endRes = startRes - horizontal;
313 if (vertical > 0) // scroll down
315 startSeq = endSeq - vertical;
317 if (startSeq < ranges.getStartSeq())
318 { // ie scrolling too fast, more than a page at a time
319 startSeq = ranges.getStartSeq();
323 transY = img.getHeight() - ((vertical + 1) * charHeight);
326 else if (vertical < 0)
328 endSeq = startSeq - vertical;
330 if (endSeq > ranges.getEndSeq())
332 endSeq = ranges.getEndSeq();
336 gg.translate(transX, transY);
337 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
338 gg.translate(-transX, -transY);
340 // Call repaint on alignment panel so that repaints from other alignment
341 // panel components can be aggregated. Otherwise performance of the
342 // overview window and others may be adversely affected.
343 av.getAlignPanel().repaint();
346 fastpainting = false;
351 public void paintComponent(Graphics g)
353 super.paintComponent(g);
355 int charHeight = av.getCharHeight();
356 int charWidth = av.getCharWidth();
358 ViewportRanges ranges = av.getRanges();
360 int width = getWidth();
361 int height = getHeight();
363 width -= (width % charWidth);
364 height -= (height % charHeight);
366 // selectImage is the selection group outline image
367 BufferedImage selectImage = drawSelectionGroup(
368 ranges.getStartRes(), ranges.getEndRes(),
369 ranges.getStartSeq(), ranges.getEndSeq());
371 if ((img != null) && (fastPaint
372 || (getVisibleRect().width != g.getClipBounds().width)
373 || (getVisibleRect().height != g.getClipBounds().height)))
375 BufferedImage lcimg = buildLocalImage(selectImage);
376 g.drawImage(lcimg, 0, 0, this);
379 else if ((width > 0) && (height > 0))
381 // img is a cached version of the last view we drew, if any
382 // if we have no img or the size has changed, make a new one
383 if (img == null || width != img.getWidth()
384 || height != img.getHeight())
391 gg = (Graphics2D) img.getGraphics();
392 gg.setFont(av.getFont());
397 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
398 RenderingHints.VALUE_ANTIALIAS_ON);
401 gg.setColor(Color.white);
402 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
404 if (av.getWrapAlignment())
406 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
410 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
411 ranges.getStartSeq(), ranges.getEndSeq(), 0);
414 // lcimg is a local *copy* of img which we'll draw selectImage on top of
415 BufferedImage lcimg = buildLocalImage(selectImage);
416 g.drawImage(lcimg, 0, 0, this);
421 * Draw an alignment panel for printing
424 * Graphics object to draw with
426 * start residue of print area
428 * end residue of print area
430 * start sequence of print area
432 * end sequence of print area
434 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
435 int startSeq, int endSeq)
437 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
439 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
441 if (selectImage != null)
443 ((Graphics2D) g1).setComposite(AlphaComposite
444 .getInstance(AlphaComposite.SRC_OVER));
445 g1.drawImage(selectImage, 0, 0, this);
450 * Draw a wrapped alignment panel for printing
453 * Graphics object to draw with
455 * width of drawing area
456 * @param canvasHeight
457 * height of drawing area
459 * start residue of print area
461 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
462 int canvasHeight, int startRes)
464 SequenceGroup group = av.getSelectionGroup();
466 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
470 BufferedImage selectImage = null;
473 selectImage = new BufferedImage(canvasWidth, canvasHeight,
474 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
475 } catch (OutOfMemoryError er)
478 System.err.println("Print image OutOfMemory Error.\n" + er);
479 new OOMWarning("Creating wrapped alignment image for printing", er);
481 if (selectImage != null)
483 Graphics2D g2 = selectImage.createGraphics();
484 setupSelectionGroup(g2, selectImage);
485 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
489 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
490 g.drawImage(selectImage, 0, 0, this);
497 * Make a local image by combining the cached image img
500 private BufferedImage buildLocalImage(BufferedImage selectImage)
502 // clone the cached image
503 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
506 // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
508 Graphics2D g2d = lcimg.createGraphics();
509 g2d.drawImage(img, 0, 0, null);
511 // overlay selection group on lcimg
512 if (selectImage != null)
515 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
516 g2d.drawImage(selectImage, 0, 0, this);
524 * Set up a buffered image of the correct height and size for the sequence canvas
526 private BufferedImage setupImage()
528 BufferedImage lcimg = null;
530 int charWidth = av.getCharWidth();
531 int charHeight = av.getCharHeight();
533 int width = getWidth();
534 int height = getHeight();
536 width -= (width % charWidth);
537 height -= (height % charHeight);
539 if ((width < 1) || (height < 1))
546 lcimg = new BufferedImage(width, height,
547 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
548 } catch (OutOfMemoryError er)
552 "Group image OutOfMemory Redraw Error.\n" + er);
553 new OOMWarning("Creating alignment image for display", er);
562 * Returns the visible width of the canvas in residues, after allowing for
563 * East or West scales (if shown)
566 * the width in pixels (possibly including scales)
570 public int getWrappedCanvasWidth(int canvasWidth)
572 int charWidth = av.getCharWidth();
574 FontMetrics fm = getFontMetrics(av.getFont());
578 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
580 labelWidth = getLabelWidth(fm);
583 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
585 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
587 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
591 * Returns a pixel width sufficient to show the largest sequence coordinate
592 * (end position) in the alignment, calculated as the FontMetrics width of
593 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
594 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
595 * half a character width space on either side.
600 protected int getLabelWidth(FontMetrics fm)
603 * find the biggest sequence end position we need to show
604 * (note this is not necessarily the sequence length)
607 AlignmentI alignment = av.getAlignment();
608 for (int i = 0; i < alignment.getHeight(); i++)
610 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
614 for (int i = maxWidth; i > 0; i /= 10)
619 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
623 * Draws as many widths of a wrapped alignment as can fit in the visible
628 * available width in pixels
629 * @param canvasHeight
630 * available height in pixels
632 * the first column (0...) of the alignment to draw
634 public void drawWrappedPanel(Graphics g, int canvasWidth,
635 int canvasHeight, final int startColumn)
637 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
640 av.setWrappedWidth(wrappedWidthInResidues);
642 ViewportRanges ranges = av.getRanges();
643 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
646 * draw one width at a time (including any scales or annotation shown),
647 * until we have run out of either alignment or vertical space available
649 int ypos = wrappedSpaceAboveAlignment;
650 int maxWidth = ranges.getVisibleAlignmentWidth();
652 int start = startColumn;
653 int currentWidth = 0;
654 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
657 .min(maxWidth, start + wrappedWidthInResidues - 1);
658 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
659 ypos += wrappedRepeatHeightPx;
660 start += wrappedWidthInResidues;
664 drawWrappedDecorators(g, startColumn);
668 * Calculates and saves values needed when rendering a wrapped alignment.
669 * These depend on many factors, including
671 * <li>canvas width and height</li>
672 * <li>number of visible sequences, and height of annotations if shown</li>
673 * <li>font and character width</li>
674 * <li>whether scales are shown left, right or above the alignment</li>
678 * @param canvasHeight
679 * @return the number of residue columns in each width
681 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
683 int charHeight = av.getCharHeight();
686 * vertical space in pixels between wrapped widths of alignment
687 * - one character height, or two if scale above is drawn
689 wrappedSpaceAboveAlignment = charHeight
690 * (av.getScaleAboveWrapped() ? 2 : 1);
693 * height in pixels of the wrapped widths
695 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
697 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
699 // add annotations panel height if shown
700 wrappedRepeatHeightPx += getAnnotationHeight();
703 * number of visible widths (the last one may be part height),
704 * ensuring a part height includes at least one sequence
706 ViewportRanges ranges = av.getRanges();
707 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
708 int remainder = canvasHeight % wrappedRepeatHeightPx;
709 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
711 wrappedVisibleWidths++;
715 * compute width in residues; this also sets East and West label widths
717 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
720 * limit visibleWidths to not exceed width of alignment
722 int xMax = ranges.getVisibleAlignmentWidth();
723 int startToEnd = xMax - ranges.getStartRes();
724 int maxWidths = startToEnd / wrappedWidthInResidues;
725 if (startToEnd % wrappedWidthInResidues > 0)
729 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
731 return wrappedWidthInResidues;
735 * Draws one width of a wrapped alignment, including sequences and
736 * annnotations, if shown, but not scales or hidden column markers
742 * @param canvasHeight
744 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
745 int endColumn, int canvasHeight)
747 ViewportRanges ranges = av.getRanges();
748 int viewportWidth = ranges.getViewportWidth();
750 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
753 * move right before drawing by the width of the scale left (if any)
754 * plus column offset from left margin (usually zero, but may be non-zero
755 * when fast painting is drawing just a few columns)
757 int charWidth = av.getCharWidth();
758 int xOffset = labelWidthWest
759 + ((startColumn - ranges.getStartRes()) % viewportWidth)
761 g.translate(xOffset, 0);
763 // When printing we have an extra clipped region,
764 // the Printable page which we need to account for here
765 Shape clip = g.getClip();
769 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
773 g.setClip(0, (int) clip.getBounds().getY(),
774 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
778 * white fill the region to be drawn (so incremental fast paint doesn't
779 * scribble over an existing image)
781 gg.setColor(Color.white);
782 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
783 wrappedRepeatHeightPx);
785 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
788 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
790 if (av.isShowAnnotation())
792 g.translate(0, cHeight + ypos + 3);
793 if (annotations == null)
795 annotations = new AnnotationPanel(av);
798 annotations.renderer.drawComponent(annotations, av, g, -1,
799 startColumn, endx + 1);
800 g.translate(0, -cHeight - ypos - 3);
803 g.translate(-xOffset, 0);
807 * Draws scales left, right and above (if shown), and any hidden column
808 * markers, on all widths of the wrapped alignment
813 protected void drawWrappedDecorators(Graphics g, final int startColumn)
815 int charWidth = av.getCharWidth();
817 g.setFont(av.getFont());
818 g.setColor(Color.black);
820 int ypos = wrappedSpaceAboveAlignment;
821 ViewportRanges ranges = av.getRanges();
822 int viewportWidth = ranges.getViewportWidth();
823 int maxWidth = ranges.getVisibleAlignmentWidth();
825 int startCol = startColumn;
827 while (widthsDrawn < wrappedVisibleWidths)
829 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
831 if (av.getScaleLeftWrapped())
833 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
836 if (av.getScaleRightWrapped())
838 int x = labelWidthWest + viewportWidth * charWidth;
840 drawVerticalScale(g, startCol, endColumn, ypos, false);
845 * white fill region of scale above and hidden column markers
846 * (to support incremental fast paint of image)
848 g.translate(labelWidthWest, 0);
849 g.setColor(Color.white);
850 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
851 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
852 g.setColor(Color.black);
853 g.translate(-labelWidthWest, 0);
855 g.translate(labelWidthWest, 0);
857 if (av.getScaleAboveWrapped())
859 drawNorthScale(g, startCol, endColumn, ypos);
862 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
864 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
867 g.translate(-labelWidthWest, 0);
869 ypos += wrappedRepeatHeightPx;
870 startCol += viewportWidth;
876 * Draws markers (triangles) above hidden column positions between startColumn
884 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
885 int startColumn, int endColumn)
887 int charHeight = av.getCharHeight();
888 int charWidth = av.getCharWidth();
890 g.setColor(Color.blue);
891 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
892 List<Integer> positions = hidden.findHiddenRegionPositions();
893 for (int pos : positions)
895 int res = pos - startColumn;
897 if (res < 0 || res > endColumn - startColumn + 1)
903 * draw a downward-pointing triangle at the hidden columns location
904 * (before the following visible column)
906 int xMiddle = res * charWidth;
907 int[] xPoints = new int[] { xMiddle - charHeight / 4,
908 xMiddle + charHeight / 4, xMiddle };
909 int yTop = ypos - (charHeight / 2);
910 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
911 g.fillPolygon(xPoints, yPoints, 3);
916 * Draw a selection group over a wrapped alignment
918 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
920 int canvasHeight, int startRes)
922 int charHeight = av.getCharHeight();
923 int charWidth = av.getCharWidth();
925 // height gap above each panel
926 int hgap = charHeight;
927 if (av.getScaleAboveWrapped())
932 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
934 int cHeight = av.getAlignment().getHeight() * charHeight;
936 int startx = startRes;
938 int ypos = hgap; // vertical offset
939 int maxwidth = av.getAlignment().getWidth();
941 if (av.hasHiddenColumns())
943 maxwidth = av.getAlignment().getHiddenColumns()
944 .findColumnPosition(maxwidth);
947 // chop the wrapped alignment extent up into panel-sized blocks and treat
948 // each block as if it were a block from an unwrapped alignment
949 while ((ypos <= canvasHeight) && (startx < maxwidth))
951 // set end value to be start + width, or maxwidth, whichever is smaller
952 endx = startx + cWidth - 1;
959 g.translate(labelWidthWest, 0);
961 drawUnwrappedSelection(g, group, startx, endx, 0,
962 av.getAlignment().getHeight() - 1,
965 g.translate(-labelWidthWest, 0);
967 // update vertical offset
968 ypos += cHeight + getAnnotationHeight() + hgap;
970 // update horizontal offset
975 int getAnnotationHeight()
977 if (!av.isShowAnnotation())
982 if (annotations == null)
984 annotations = new AnnotationPanel(av);
987 return annotations.adjustPanelHeight();
991 * Draws the visible region of the alignment on the graphics context. If there
992 * are hidden column markers in the visible region, then each sub-region
993 * between the markers is drawn separately, followed by the hidden column
997 * the graphics context, positioned at the first residue to be drawn
999 * offset of the first column to draw (0..)
1001 * offset of the last column to draw (0..)
1003 * offset of the first sequence to draw (0..)
1005 * offset of the last sequence to draw (0..)
1007 * vertical offset at which to draw (for wrapped alignments)
1009 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1010 final int startSeq, final int endSeq, final int yOffset)
1012 int charHeight = av.getCharHeight();
1013 int charWidth = av.getCharWidth();
1015 if (!av.hasHiddenColumns())
1017 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1022 final int screenYMax = endRes - startRes;
1023 int blockStart = startRes;
1024 int blockEnd = endRes;
1026 for (int[] region : av.getAlignment().getHiddenColumns()
1027 .getHiddenColumnsCopy())
1029 int hideStart = region[0];
1030 int hideEnd = region[1];
1032 if (hideStart <= blockStart)
1034 blockStart += (hideEnd - hideStart) + 1;
1039 * draw up to just before the next hidden region, or the end of
1040 * the visible region, whichever comes first
1042 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1045 g1.translate(screenY * charWidth, 0);
1047 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1050 * draw the downline of the hidden column marker (ScalePanel draws the
1051 * triangle on top) if we reached it
1053 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1055 g1.setColor(Color.blue);
1057 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1058 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1059 (endSeq - startSeq + 1) * charHeight + yOffset);
1062 g1.translate(-screenY * charWidth, 0);
1063 screenY += blockEnd - blockStart + 1;
1064 blockStart = hideEnd + 1;
1066 if (screenY > screenYMax)
1068 // already rendered last block
1073 if (screenY <= screenYMax)
1075 // remaining visible region to render
1076 blockEnd = blockStart + screenYMax - screenY;
1077 g1.translate(screenY * charWidth, 0);
1078 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1080 g1.translate(-screenY * charWidth, 0);
1087 * Draws a region of the visible alignment
1091 * offset of the first column in the visible region (0..)
1093 * offset of the last column in the visible region (0..)
1095 * offset of the first sequence in the visible region (0..)
1097 * offset of the last sequence in the visible region (0..)
1099 * vertical offset at which to draw (for wrapped alignments)
1101 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1102 int endSeq, int offset)
1104 int charHeight = av.getCharHeight();
1105 int charWidth = av.getCharWidth();
1107 g.setFont(av.getFont());
1108 seqRdr.prepare(g, av.isRenderGaps());
1112 // / First draw the sequences
1113 // ///////////////////////////
1114 for (int i = startSeq; i <= endSeq; i++)
1116 nextSeq = av.getAlignment().getSequenceAt(i);
1117 if (nextSeq == null)
1119 // occasionally, a race condition occurs such that the alignment row is
1123 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1124 startRes, endRes, offset + ((i - startSeq) * charHeight));
1126 if (av.isShowSequenceFeatures())
1128 fr.drawSequence(g, nextSeq, startRes, endRes,
1129 offset + ((i - startSeq) * charHeight), false);
1133 * highlight search Results once sequence has been drawn
1135 if (av.hasSearchResults())
1137 SearchResultsI searchResults = av.getSearchResults();
1138 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1140 if (visibleResults != null)
1142 for (int r = 0; r < visibleResults.length; r += 2)
1144 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1145 visibleResults[r + 1],
1146 (visibleResults[r] - startRes) * charWidth,
1147 offset + ((i - startSeq) * charHeight));
1152 if (av.cursorMode && cursorY == i && cursorX >= startRes
1153 && cursorX <= endRes)
1155 seqRdr.drawCursor(nextSeq, cursorX,
1156 (cursorX - startRes) * charWidth,
1157 offset + ((i - startSeq) * charHeight));
1161 if (av.getSelectionGroup() != null
1162 || av.getAlignment().getGroups().size() > 0)
1164 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1169 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1170 int startSeq, int endSeq, int offset)
1172 Graphics2D g = (Graphics2D) g1;
1174 // ///////////////////////////////////
1175 // Now outline any areas if necessary
1176 // ///////////////////////////////////
1178 SequenceGroup group = null;
1179 int groupIndex = -1;
1181 if (av.getAlignment().getGroups().size() > 0)
1183 group = av.getAlignment().getGroups().get(0);
1189 g.setStroke(new BasicStroke());
1190 g.setColor(group.getOutlineColour());
1194 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1199 g.setStroke(new BasicStroke());
1201 if (groupIndex >= av.getAlignment().getGroups().size())
1206 group = av.getAlignment().getGroups().get(groupIndex);
1208 } while (groupIndex < av.getAlignment().getGroups().size());
1216 * Draw the selection group as a separate image and overlay
1218 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1219 int startSeq, int endSeq)
1221 // get a new image of the correct size
1222 BufferedImage selectionImage = setupImage();
1224 if (selectionImage == null)
1229 SequenceGroup group = av.getSelectionGroup();
1236 // set up drawing colour
1237 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1239 setupSelectionGroup(g, selectionImage);
1241 if (!av.getWrapAlignment())
1243 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1248 drawWrappedSelection(g, group, getWidth(), getHeight(),
1249 av.getRanges().getStartRes());
1253 return selectionImage;
1257 * Set up graphics for selection group
1259 private void setupSelectionGroup(Graphics2D g,
1260 BufferedImage selectionImage)
1262 // set background to transparent
1263 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1264 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1266 // set up foreground to draw red dashed line
1267 g.setComposite(AlphaComposite.Src);
1268 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1269 BasicStroke.JOIN_ROUND, 3f, new float[]
1271 g.setColor(Color.RED);
1275 * Draw a selection group over an unwrapped alignment
1276 * @param g graphics object to draw with
1277 * @param group selection group
1278 * @param startRes start residue of area to draw
1279 * @param endRes end residue of area to draw
1280 * @param startSeq start sequence of area to draw
1281 * @param endSeq end sequence of area to draw
1282 * @param offset vertical offset (used when called from wrapped alignment code)
1284 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1285 int startRes, int endRes, int startSeq, int endSeq, int offset)
1287 int charWidth = av.getCharWidth();
1289 if (!av.hasHiddenColumns())
1291 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1296 // package into blocks of visible columns
1298 int blockStart = startRes;
1299 int blockEnd = endRes;
1301 for (int[] region : av.getAlignment().getHiddenColumns()
1302 .getHiddenColumnsCopy())
1304 int hideStart = region[0];
1305 int hideEnd = region[1];
1307 if (hideStart <= blockStart)
1309 blockStart += (hideEnd - hideStart) + 1;
1313 blockEnd = hideStart - 1;
1315 g.translate(screenY * charWidth, 0);
1316 drawPartialGroupOutline(g, group,
1317 blockStart, blockEnd, startSeq, endSeq, offset);
1319 g.translate(-screenY * charWidth, 0);
1320 screenY += blockEnd - blockStart + 1;
1321 blockStart = hideEnd + 1;
1323 if (screenY > (endRes - startRes))
1325 // already rendered last block
1330 if (screenY <= (endRes - startRes))
1332 // remaining visible region to render
1333 blockEnd = blockStart + (endRes - startRes) - screenY;
1334 g.translate(screenY * charWidth, 0);
1335 drawPartialGroupOutline(g, group,
1336 blockStart, blockEnd, startSeq, endSeq, offset);
1338 g.translate(-screenY * charWidth, 0);
1344 * Draw the selection group as a separate image and overlay
1346 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1347 int startRes, int endRes, int startSeq, int endSeq,
1350 int charHeight = av.getCharHeight();
1351 int charWidth = av.getCharWidth();
1353 int visWidth = (endRes - startRes + 1) * charWidth;
1357 boolean inGroup = false;
1365 for (i = startSeq; i <= endSeq; i++)
1367 // position of start residue of group relative to startRes, in pixels
1368 sx = (group.getStartRes() - startRes) * charWidth;
1370 // width of group in pixels
1371 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1374 sy = verticalOffset + (i - startSeq) * charHeight;
1376 if (sx + xwidth < 0 || sx > visWidth)
1381 if ((sx <= (endRes - startRes) * charWidth)
1382 && group.getSequences(null)
1383 .contains(av.getAlignment().getSequenceAt(i)))
1385 if ((bottom == -1) && !group.getSequences(null)
1386 .contains(av.getAlignment().getSequenceAt(i + 1)))
1388 bottom = sy + charHeight;
1393 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1394 .contains(av.getAlignment().getSequenceAt(i - 1)))
1407 // if start position is visible, draw vertical line to left of
1409 if (sx >= 0 && sx < visWidth)
1411 g.drawLine(sx, oldY, sx, sy);
1414 // if end position is visible, draw vertical line to right of
1416 if (sx + xwidth < visWidth)
1418 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1427 // don't let width extend beyond current block, or group extent
1429 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1431 xwidth = (endRes - startRes + 1) * charWidth - sx;
1434 // draw horizontal line at top of group
1437 g.drawLine(sx, top, sx + xwidth, top);
1441 // draw horizontal line at bottom of group
1444 g.drawLine(sx, bottom, sx + xwidth, bottom);
1455 sy = verticalOffset + ((i - startSeq) * charHeight);
1456 if (sx >= 0 && sx < visWidth)
1458 g.drawLine(sx, oldY, sx, sy);
1461 if (sx + xwidth < visWidth)
1463 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1472 if (sx + xwidth > visWidth)
1476 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1478 xwidth = (endRes - startRes + 1) * charWidth;
1483 g.drawLine(sx, top, sx + xwidth, top);
1489 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1498 * Highlights search results in the visible region by rendering as white text
1499 * on a black background. Any previous highlighting is removed. Answers true
1500 * if any highlight was left on the visible alignment (so status bar should be
1501 * set to match), else false.
1503 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1504 * alignment had to be scrolled to show the highlighted region, then it should
1505 * be fully redrawn, otherwise a fast paint can be performed. This argument
1506 * could be removed if fast paint of scrolled wrapped alignment is coded in
1507 * future (JAL-2609).
1510 * @param noFastPaint
1513 public boolean highlightSearchResults(SearchResultsI results,
1514 boolean noFastPaint)
1520 boolean wrapped = av.getWrapAlignment();
1523 fastPaint = !noFastPaint;
1524 fastpainting = fastPaint;
1527 * to avoid redrawing the whole visible region, we instead
1528 * redraw just the minimal regions to remove previous highlights
1531 SearchResultsI previous = av.getSearchResults();
1532 av.setSearchResults(results);
1533 boolean redrawn = false;
1534 boolean drawn = false;
1537 redrawn = drawMappedPositionsWrapped(previous);
1538 drawn = drawMappedPositionsWrapped(results);
1543 redrawn = drawMappedPositions(previous);
1544 drawn = drawMappedPositions(results);
1549 * if highlights were either removed or added, repaint
1557 * return true only if highlights were added
1563 fastpainting = false;
1568 * Redraws the minimal rectangle in the visible region (if any) that includes
1569 * mapped positions of the given search results. Whether or not positions are
1570 * highlighted depends on the SearchResults set on the Viewport. This allows
1571 * this method to be called to either clear or set highlighting. Answers true
1572 * if any positions were drawn (in which case a repaint is still required),
1578 protected boolean drawMappedPositions(SearchResultsI results)
1580 if (results == null)
1586 * calculate the minimal rectangle to redraw that
1587 * includes both new and existing search results
1589 int firstSeq = Integer.MAX_VALUE;
1591 int firstCol = Integer.MAX_VALUE;
1593 boolean matchFound = false;
1595 ViewportRanges ranges = av.getRanges();
1596 int firstVisibleColumn = ranges.getStartRes();
1597 int lastVisibleColumn = ranges.getEndRes();
1598 AlignmentI alignment = av.getAlignment();
1599 if (av.hasHiddenColumns())
1601 firstVisibleColumn = alignment.getHiddenColumns()
1602 .adjustForHiddenColumns(firstVisibleColumn);
1603 lastVisibleColumn = alignment.getHiddenColumns()
1604 .adjustForHiddenColumns(lastVisibleColumn);
1607 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1608 .getEndSeq(); seqNo++)
1610 SequenceI seq = alignment.getSequenceAt(seqNo);
1612 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1614 if (visibleResults != null)
1616 for (int i = 0; i < visibleResults.length - 1; i += 2)
1618 int firstMatchedColumn = visibleResults[i];
1619 int lastMatchedColumn = visibleResults[i + 1];
1620 if (firstMatchedColumn <= lastVisibleColumn
1621 && lastMatchedColumn >= firstVisibleColumn)
1624 * found a search results match in the visible region -
1625 * remember the first and last sequence matched, and the first
1626 * and last visible columns in the matched positions
1629 firstSeq = Math.min(firstSeq, seqNo);
1630 lastSeq = Math.max(lastSeq, seqNo);
1631 firstMatchedColumn = Math.max(firstMatchedColumn,
1632 firstVisibleColumn);
1633 lastMatchedColumn = Math.min(lastMatchedColumn,
1635 firstCol = Math.min(firstCol, firstMatchedColumn);
1636 lastCol = Math.max(lastCol, lastMatchedColumn);
1644 if (av.hasHiddenColumns())
1646 firstCol = alignment.getHiddenColumns()
1647 .findColumnPosition(firstCol);
1648 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1650 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1651 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1652 gg.translate(transX, transY);
1653 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1654 gg.translate(-transX, -transY);
1661 public void propertyChange(PropertyChangeEvent evt)
1663 String eventName = evt.getPropertyName();
1665 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1673 if (eventName.equals(ViewportRanges.STARTRES)
1674 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1676 // Make sure we're not trying to draw a panel
1677 // larger than the visible window
1678 if (eventName.equals(ViewportRanges.STARTRES))
1680 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1684 scrollX = ((int[]) evt.getNewValue())[0]
1685 - ((int[]) evt.getOldValue())[0];
1687 ViewportRanges vpRanges = av.getRanges();
1689 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1690 if (scrollX > range)
1694 else if (scrollX < -range)
1700 // Both scrolling and resizing change viewport ranges: scrolling changes
1701 // both start and end points, but resize only changes end values.
1702 // Here we only want to fastpaint on a scroll, with resize using a normal
1703 // paint, so scroll events are identified as changes to the horizontal or
1704 // vertical start value.
1705 if (eventName.equals(ViewportRanges.STARTRES))
1707 if (av.getWrapAlignment())
1709 fastPaintWrapped(scrollX);
1713 fastPaint(scrollX, 0);
1716 else if (eventName.equals(ViewportRanges.STARTSEQ))
1719 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1721 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1723 fastPaint(scrollX, 0);
1724 // bizarrely, we only need to scroll on the x value here as fastpaint
1725 // copies the full height of the image anyway. Passing in the y value
1726 // causes nasty repaint artefacts, which only disappear on a full
1732 * Does a minimal update of the image for a scroll movement. This method
1733 * handles scroll movements of up to one width of the wrapped alignment (one
1734 * click in the vertical scrollbar). Larger movements (for example after a
1735 * scroll to highlight a mapped position) trigger a full redraw instead.
1738 * number of positions scrolled (right if positive, left if negative)
1740 protected void fastPaintWrapped(int scrollX)
1742 ViewportRanges ranges = av.getRanges();
1744 if (Math.abs(scrollX) > ranges.getViewportWidth())
1747 * shift of more than one view width is
1748 * overcomplicated to handle in this method
1755 if (fastpainting || gg == null)
1761 fastpainting = true;
1765 calculateWrappedGeometry(getWidth(), getHeight());
1768 * relocate the regions of the alignment that are still visible
1770 shiftWrappedAlignment(-scrollX);
1773 * add new columns (sequence, annotation)
1774 * - at top left if scrollX < 0
1775 * - at right of last two widths if scrollX > 0
1779 int startRes = ranges.getStartRes();
1780 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1781 - scrollX - 1, getHeight());
1785 fastPaintWrappedAddRight(scrollX);
1789 * draw all scales (if shown) and hidden column markers
1791 drawWrappedDecorators(gg, ranges.getStartRes());
1796 fastpainting = false;
1801 * Draws the specified number of columns at the 'end' (bottom right) of a
1802 * wrapped alignment view, including sequences and annotations if shown, but
1803 * not scales. Also draws the same number of columns at the right hand end of
1804 * the second last width shown, if the last width is not full height (so
1805 * cannot simply be copied from the graphics image).
1809 protected void fastPaintWrappedAddRight(int columns)
1816 ViewportRanges ranges = av.getRanges();
1817 int viewportWidth = ranges.getViewportWidth();
1818 int charWidth = av.getCharWidth();
1821 * draw full height alignment in the second last row, last columns, if the
1822 * last row was not full height
1824 int visibleWidths = wrappedVisibleWidths;
1825 int canvasHeight = getHeight();
1826 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1828 if (lastWidthPartHeight)
1830 int widthsAbove = Math.max(0, visibleWidths - 2);
1831 int ypos = wrappedRepeatHeightPx * widthsAbove
1832 + wrappedSpaceAboveAlignment;
1833 int endRes = ranges.getEndRes();
1834 endRes += widthsAbove * viewportWidth;
1835 int startRes = endRes - columns;
1836 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1840 * white fill first to erase annotations
1842 gg.translate(xOffset, 0);
1843 gg.setColor(Color.white);
1844 gg.fillRect(labelWidthWest, ypos,
1845 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1846 gg.translate(-xOffset, 0);
1848 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1852 * draw newly visible columns in last wrapped width (none if we
1853 * have reached the end of the alignment)
1854 * y-offset for drawing last width is height of widths above,
1857 int widthsAbove = visibleWidths - 1;
1858 int ypos = wrappedRepeatHeightPx * widthsAbove
1859 + wrappedSpaceAboveAlignment;
1860 int endRes = ranges.getEndRes();
1861 endRes += widthsAbove * viewportWidth;
1862 int startRes = endRes - columns + 1;
1865 * white fill first to erase annotations
1867 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1869 gg.translate(xOffset, 0);
1870 gg.setColor(Color.white);
1871 int width = viewportWidth * charWidth - xOffset;
1872 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1873 gg.translate(-xOffset, 0);
1875 gg.setFont(av.getFont());
1876 gg.setColor(Color.black);
1878 if (startRes < ranges.getVisibleAlignmentWidth())
1880 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1884 * and finally, white fill any space below the visible alignment
1886 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1887 if (heightBelow > 0)
1889 gg.setColor(Color.white);
1890 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1895 * Shifts the visible alignment by the specified number of columns - left if
1896 * negative, right if positive. Copies and moves sequences and annotations (if
1897 * shown). Scales, hidden column markers and any newly visible columns must be
1902 protected void shiftWrappedAlignment(int positions)
1908 int charWidth = av.getCharWidth();
1910 int canvasHeight = getHeight();
1911 ViewportRanges ranges = av.getRanges();
1912 int viewportWidth = ranges.getViewportWidth();
1913 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1915 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1916 int xMax = ranges.getVisibleAlignmentWidth();
1921 * shift right (after scroll left)
1922 * for each wrapped width (starting with the last), copy (width-positions)
1923 * columns from the left margin to the right margin, and copy positions
1924 * columns from the right margin of the row above (if any) to the
1925 * left margin of the current row
1929 * get y-offset of last wrapped width, first row of sequences
1931 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1932 y += wrappedSpaceAboveAlignment;
1933 int copyFromLeftStart = labelWidthWest;
1934 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1938 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1939 positions * charWidth, 0);
1942 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1943 positions * charWidth, heightToCopy, -widthToCopy,
1944 wrappedRepeatHeightPx);
1947 y -= wrappedRepeatHeightPx;
1953 * shift left (after scroll right)
1954 * for each wrapped width (starting with the first), copy (width-positions)
1955 * columns from the right margin to the left margin, and copy positions
1956 * columns from the left margin of the row below (if any) to the
1957 * right margin of the current row
1959 int xpos = av.getRanges().getStartRes();
1960 int y = wrappedSpaceAboveAlignment;
1961 int copyFromRightStart = labelWidthWest - positions * charWidth;
1963 while (y < canvasHeight)
1965 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1966 positions * charWidth, 0);
1967 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1968 && (xpos + viewportWidth <= xMax))
1970 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1971 * charWidth, heightToCopy, widthToCopy,
1972 -wrappedRepeatHeightPx);
1975 y += wrappedRepeatHeightPx;
1976 xpos += viewportWidth;
1983 * Redraws any positions in the search results in the visible region of a
1984 * wrapped alignment. Any highlights are drawn depending on the search results
1985 * set on the Viewport, not the <code>results</code> argument. This allows
1986 * this method to be called either to clear highlights (passing the previous
1987 * search results), or to draw new highlights.
1992 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1994 if (results == null)
1998 int charHeight = av.getCharHeight();
2000 boolean matchFound = false;
2002 calculateWrappedGeometry(getWidth(), getHeight());
2003 int wrappedWidth = av.getWrappedWidth();
2004 int wrappedHeight = wrappedRepeatHeightPx;
2006 ViewportRanges ranges = av.getRanges();
2007 int canvasHeight = getHeight();
2008 int repeats = canvasHeight / wrappedHeight;
2009 if (canvasHeight / wrappedHeight > 0)
2014 int firstVisibleColumn = ranges.getStartRes();
2015 int lastVisibleColumn = ranges.getStartRes() + repeats
2016 * ranges.getViewportWidth() - 1;
2018 AlignmentI alignment = av.getAlignment();
2019 if (av.hasHiddenColumns())
2021 firstVisibleColumn = alignment.getHiddenColumns()
2022 .adjustForHiddenColumns(firstVisibleColumn);
2023 lastVisibleColumn = alignment.getHiddenColumns()
2024 .adjustForHiddenColumns(lastVisibleColumn);
2027 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2029 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2030 .getEndSeq(); seqNo++)
2032 SequenceI seq = alignment.getSequenceAt(seqNo);
2034 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2036 if (visibleResults != null)
2038 for (int i = 0; i < visibleResults.length - 1; i += 2)
2040 int firstMatchedColumn = visibleResults[i];
2041 int lastMatchedColumn = visibleResults[i + 1];
2042 if (firstMatchedColumn <= lastVisibleColumn
2043 && lastMatchedColumn >= firstVisibleColumn)
2046 * found a search results match in the visible region
2048 firstMatchedColumn = Math.max(firstMatchedColumn,
2049 firstVisibleColumn);
2050 lastMatchedColumn = Math.min(lastMatchedColumn,
2054 * draw each mapped position separately (as contiguous positions may
2055 * wrap across lines)
2057 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2059 int displayColumn = mappedPos;
2060 if (av.hasHiddenColumns())
2062 displayColumn = alignment.getHiddenColumns()
2063 .findColumnPosition(displayColumn);
2067 * transX: offset from left edge of canvas to residue position
2069 int transX = labelWidthWest
2070 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2071 * av.getCharWidth();
2074 * transY: offset from top edge of canvas to residue position
2076 int transY = gapHeight;
2077 transY += (displayColumn - ranges.getStartRes())
2078 / wrappedWidth * wrappedHeight;
2079 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2082 * yOffset is from graphics origin to start of visible region
2084 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2085 if (transY < getHeight())
2088 gg.translate(transX, transY);
2089 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2091 gg.translate(-transX, -transY);
2103 * Answers the width in pixels of the left scale labels (0 if not shown)
2107 int getLabelWidthWest()
2109 return labelWidthWest;