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);
343 fastpainting = false;
348 public void paintComponent(Graphics g)
350 super.paintComponent(g);
352 int charHeight = av.getCharHeight();
353 int charWidth = av.getCharWidth();
355 ViewportRanges ranges = av.getRanges();
357 int width = getWidth();
358 int height = getHeight();
360 width -= (width % charWidth);
361 height -= (height % charHeight);
363 // selectImage is the selection group outline image
364 BufferedImage selectImage = drawSelectionGroup(
365 ranges.getStartRes(), ranges.getEndRes(),
366 ranges.getStartSeq(), ranges.getEndSeq());
368 if ((img != null) && (fastPaint
369 || (getVisibleRect().width != g.getClipBounds().width)
370 || (getVisibleRect().height != g.getClipBounds().height)))
372 BufferedImage lcimg = buildLocalImage(selectImage);
373 g.drawImage(lcimg, 0, 0, this);
376 else if ((width > 0) && (height > 0))
378 // img is a cached version of the last view we drew, if any
379 // if we have no img or the size has changed, make a new one
380 if (img == null || width != img.getWidth()
381 || height != img.getHeight())
388 gg = (Graphics2D) img.getGraphics();
389 gg.setFont(av.getFont());
394 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
395 RenderingHints.VALUE_ANTIALIAS_ON);
398 gg.setColor(Color.white);
399 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
401 if (av.getWrapAlignment())
403 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
407 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
408 ranges.getStartSeq(), ranges.getEndSeq(), 0);
411 // lcimg is a local *copy* of img which we'll draw selectImage on top of
412 BufferedImage lcimg = buildLocalImage(selectImage);
413 g.drawImage(lcimg, 0, 0, this);
418 * Draw an alignment panel for printing
421 * Graphics object to draw with
423 * start residue of print area
425 * end residue of print area
427 * start sequence of print area
429 * end sequence of print area
431 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
432 int startSeq, int endSeq)
434 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
436 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
438 if (selectImage != null)
440 ((Graphics2D) g1).setComposite(AlphaComposite
441 .getInstance(AlphaComposite.SRC_OVER));
442 g1.drawImage(selectImage, 0, 0, this);
447 * Draw a wrapped alignment panel for printing
450 * Graphics object to draw with
452 * width of drawing area
453 * @param canvasHeight
454 * height of drawing area
456 * start residue of print area
458 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
459 int canvasHeight, int startRes)
461 SequenceGroup group = av.getSelectionGroup();
463 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
467 BufferedImage selectImage = null;
470 selectImage = new BufferedImage(canvasWidth, canvasHeight,
471 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
472 } catch (OutOfMemoryError er)
475 System.err.println("Print image OutOfMemory Error.\n" + er);
476 new OOMWarning("Creating wrapped alignment image for printing", er);
478 if (selectImage != null)
480 Graphics2D g2 = selectImage.createGraphics();
481 setupSelectionGroup(g2, selectImage);
482 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
486 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
487 g.drawImage(selectImage, 0, 0, this);
494 * Make a local image by combining the cached image img
497 private BufferedImage buildLocalImage(BufferedImage selectImage)
499 // clone the cached image
500 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
502 Graphics2D g2d = lcimg.createGraphics();
503 g2d.drawImage(img, 0, 0, null);
505 // overlay selection group on lcimg
506 if (selectImage != null)
509 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
510 g2d.drawImage(selectImage, 0, 0, this);
518 * Set up a buffered image of the correct height and size for the sequence canvas
520 private BufferedImage setupImage()
522 BufferedImage lcimg = null;
524 int charWidth = av.getCharWidth();
525 int charHeight = av.getCharHeight();
527 int width = getWidth();
528 int height = getHeight();
530 width -= (width % charWidth);
531 height -= (height % charHeight);
533 if ((width < 1) || (height < 1))
540 lcimg = new BufferedImage(width, height,
541 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
542 } catch (OutOfMemoryError er)
546 "Group image OutOfMemory Redraw Error.\n" + er);
547 new OOMWarning("Creating alignment image for display", er);
556 * Returns the visible width of the canvas in residues, after allowing for
557 * East or West scales (if shown)
560 * the width in pixels (possibly including scales)
564 public int getWrappedCanvasWidth(int canvasWidth)
566 int charWidth = av.getCharWidth();
568 FontMetrics fm = getFontMetrics(av.getFont());
572 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
574 labelWidth = getLabelWidth(fm);
577 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
579 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
581 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
585 * Returns a pixel width sufficient to show the largest sequence coordinate
586 * (end position) in the alignment, calculated as the FontMetrics width of
587 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
588 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
589 * half a character width space on either side.
594 protected int getLabelWidth(FontMetrics fm)
597 * find the biggest sequence end position we need to show
598 * (note this is not necessarily the sequence length)
601 AlignmentI alignment = av.getAlignment();
602 for (int i = 0; i < alignment.getHeight(); i++)
604 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
608 for (int i = maxWidth; i > 0; i /= 10)
613 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
617 * Draws as many widths of a wrapped alignment as can fit in the visible
622 * available width in pixels
623 * @param canvasHeight
624 * available height in pixels
626 * the first column (0...) of the alignment to draw
628 public void drawWrappedPanel(Graphics g, int canvasWidth,
629 int canvasHeight, final int startColumn)
631 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
634 av.setWrappedWidth(wrappedWidthInResidues);
636 ViewportRanges ranges = av.getRanges();
637 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
640 * draw one width at a time (including any scales or annotation shown),
641 * until we have run out of either alignment or vertical space available
643 int ypos = wrappedSpaceAboveAlignment;
644 int maxWidth = ranges.getVisibleAlignmentWidth();
646 int start = startColumn;
647 int currentWidth = 0;
648 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
651 .min(maxWidth, start + wrappedWidthInResidues - 1);
652 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
653 ypos += wrappedRepeatHeightPx;
654 start += wrappedWidthInResidues;
658 drawWrappedDecorators(g, startColumn);
662 * Calculates and saves values needed when rendering a wrapped alignment.
663 * These depend on many factors, including
665 * <li>canvas width and height</li>
666 * <li>number of visible sequences, and height of annotations if shown</li>
667 * <li>font and character width</li>
668 * <li>whether scales are shown left, right or above the alignment</li>
672 * @param canvasHeight
673 * @return the number of residue columns in each width
675 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
677 int charHeight = av.getCharHeight();
680 * vertical space in pixels between wrapped widths of alignment
681 * - one character height, or two if scale above is drawn
683 wrappedSpaceAboveAlignment = charHeight
684 * (av.getScaleAboveWrapped() ? 2 : 1);
687 * height in pixels of the wrapped widths
689 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
691 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
693 // add annotations panel height if shown
694 wrappedRepeatHeightPx += getAnnotationHeight();
697 * number of visible widths (the last one may be part height),
698 * ensuring a part height includes at least one sequence
700 ViewportRanges ranges = av.getRanges();
701 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
702 int remainder = canvasHeight % wrappedRepeatHeightPx;
703 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
705 wrappedVisibleWidths++;
709 * compute width in residues; this also sets East and West label widths
711 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
714 * limit visibleWidths to not exceed width of alignment
716 int xMax = ranges.getVisibleAlignmentWidth();
717 int startToEnd = xMax - ranges.getStartRes();
718 int maxWidths = startToEnd / wrappedWidthInResidues;
719 if (startToEnd % wrappedWidthInResidues > 0)
723 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
725 return wrappedWidthInResidues;
729 * Draws one width of a wrapped alignment, including sequences and
730 * annnotations, if shown, but not scales or hidden column markers
736 * @param canvasHeight
738 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
739 int endColumn, int canvasHeight)
741 ViewportRanges ranges = av.getRanges();
742 int viewportWidth = ranges.getViewportWidth();
744 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
747 * move right before drawing by the width of the scale left (if any)
748 * plus column offset from left margin (usually zero, but may be non-zero
749 * when fast painting is drawing just a few columns)
751 int charWidth = av.getCharWidth();
752 int xOffset = labelWidthWest
753 + ((startColumn - ranges.getStartRes()) % viewportWidth)
755 g.translate(xOffset, 0);
757 // When printing we have an extra clipped region,
758 // the Printable page which we need to account for here
759 Shape clip = g.getClip();
763 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
767 g.setClip(0, (int) clip.getBounds().getY(),
768 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
772 * white fill the region to be drawn (so incremental fast paint doesn't
773 * scribble over an existing image)
775 gg.setColor(Color.white);
776 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
777 wrappedRepeatHeightPx);
779 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
782 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
784 if (av.isShowAnnotation())
786 g.translate(0, cHeight + ypos + 3);
787 if (annotations == null)
789 annotations = new AnnotationPanel(av);
792 annotations.renderer.drawComponent(annotations, av, g, -1,
793 startColumn, endx + 1);
794 g.translate(0, -cHeight - ypos - 3);
797 g.translate(-xOffset, 0);
801 * Draws scales left, right and above (if shown), and any hidden column
802 * markers, on all widths of the wrapped alignment
807 protected void drawWrappedDecorators(Graphics g, final int startColumn)
809 int charWidth = av.getCharWidth();
811 g.setFont(av.getFont());
812 g.setColor(Color.black);
814 int ypos = wrappedSpaceAboveAlignment;
815 ViewportRanges ranges = av.getRanges();
816 int viewportWidth = ranges.getViewportWidth();
817 int maxWidth = ranges.getVisibleAlignmentWidth();
819 int startCol = startColumn;
821 while (widthsDrawn < wrappedVisibleWidths)
823 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
825 if (av.getScaleLeftWrapped())
827 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
830 if (av.getScaleRightWrapped())
832 int x = labelWidthWest + viewportWidth * charWidth;
834 drawVerticalScale(g, startCol, endColumn, ypos, false);
839 * white fill region of scale above and hidden column markers
840 * (to support incremental fast paint of image)
842 g.translate(labelWidthWest, 0);
843 g.setColor(Color.white);
844 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
845 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
846 g.setColor(Color.black);
847 g.translate(-labelWidthWest, 0);
849 g.translate(labelWidthWest, 0);
851 if (av.getScaleAboveWrapped())
853 drawNorthScale(g, startCol, endColumn, ypos);
856 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
858 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
861 g.translate(-labelWidthWest, 0);
863 ypos += wrappedRepeatHeightPx;
864 startCol += viewportWidth;
870 * Draws markers (triangles) above hidden column positions between startColumn
878 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
879 int startColumn, int endColumn)
881 int charHeight = av.getCharHeight();
882 int charWidth = av.getCharWidth();
884 g.setColor(Color.blue);
885 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
886 List<Integer> positions = hidden.findHiddenRegionPositions();
887 for (int pos : positions)
889 int res = pos - startColumn;
891 if (res < 0 || res > endColumn - startColumn + 1)
897 * draw a downward-pointing triangle at the hidden columns location
898 * (before the following visible column)
900 int xMiddle = res * charWidth;
901 int[] xPoints = new int[] { xMiddle - charHeight / 4,
902 xMiddle + charHeight / 4, xMiddle };
903 int yTop = ypos - (charHeight / 2);
904 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
905 g.fillPolygon(xPoints, yPoints, 3);
910 * Draw a selection group over a wrapped alignment
912 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
914 int canvasHeight, int startRes)
916 int charHeight = av.getCharHeight();
917 int charWidth = av.getCharWidth();
919 // height gap above each panel
920 int hgap = charHeight;
921 if (av.getScaleAboveWrapped())
926 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
928 int cHeight = av.getAlignment().getHeight() * charHeight;
930 int startx = startRes;
932 int ypos = hgap; // vertical offset
933 int maxwidth = av.getAlignment().getWidth();
935 if (av.hasHiddenColumns())
937 maxwidth = av.getAlignment().getHiddenColumns()
938 .findColumnPosition(maxwidth);
941 // chop the wrapped alignment extent up into panel-sized blocks and treat
942 // each block as if it were a block from an unwrapped alignment
943 while ((ypos <= canvasHeight) && (startx < maxwidth))
945 // set end value to be start + width, or maxwidth, whichever is smaller
946 endx = startx + cWidth - 1;
953 g.translate(labelWidthWest, 0);
955 drawUnwrappedSelection(g, group, startx, endx, 0,
956 av.getAlignment().getHeight() - 1,
959 g.translate(-labelWidthWest, 0);
961 // update vertical offset
962 ypos += cHeight + getAnnotationHeight() + hgap;
964 // update horizontal offset
969 int getAnnotationHeight()
971 if (!av.isShowAnnotation())
976 if (annotations == null)
978 annotations = new AnnotationPanel(av);
981 return annotations.adjustPanelHeight();
985 * Draws the visible region of the alignment on the graphics context. If there
986 * are hidden column markers in the visible region, then each sub-region
987 * between the markers is drawn separately, followed by the hidden column
991 * the graphics context, positioned at the first residue to be drawn
993 * offset of the first column to draw (0..)
995 * offset of the last column to draw (0..)
997 * offset of the first sequence to draw (0..)
999 * offset of the last sequence to draw (0..)
1001 * vertical offset at which to draw (for wrapped alignments)
1003 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1004 final int startSeq, final int endSeq, final int yOffset)
1006 int charHeight = av.getCharHeight();
1007 int charWidth = av.getCharWidth();
1009 if (!av.hasHiddenColumns())
1011 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1016 final int screenYMax = endRes - startRes;
1017 int blockStart = startRes;
1018 int blockEnd = endRes;
1020 for (int[] region : av.getAlignment().getHiddenColumns()
1021 .getHiddenColumnsCopy())
1023 int hideStart = region[0];
1024 int hideEnd = region[1];
1026 if (hideStart <= blockStart)
1028 blockStart += (hideEnd - hideStart) + 1;
1033 * draw up to just before the next hidden region, or the end of
1034 * the visible region, whichever comes first
1036 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1039 g1.translate(screenY * charWidth, 0);
1041 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1044 * draw the downline of the hidden column marker (ScalePanel draws the
1045 * triangle on top) if we reached it
1047 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1049 g1.setColor(Color.blue);
1051 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1052 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1053 (endSeq - startSeq + 1) * charHeight + yOffset);
1056 g1.translate(-screenY * charWidth, 0);
1057 screenY += blockEnd - blockStart + 1;
1058 blockStart = hideEnd + 1;
1060 if (screenY > screenYMax)
1062 // already rendered last block
1067 if (screenY <= screenYMax)
1069 // remaining visible region to render
1070 blockEnd = blockStart + screenYMax - screenY;
1071 g1.translate(screenY * charWidth, 0);
1072 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1074 g1.translate(-screenY * charWidth, 0);
1081 * Draws a region of the visible alignment
1085 * offset of the first column in the visible region (0..)
1087 * offset of the last column in the visible region (0..)
1089 * offset of the first sequence in the visible region (0..)
1091 * offset of the last sequence in the visible region (0..)
1093 * vertical offset at which to draw (for wrapped alignments)
1095 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1096 int endSeq, int offset)
1098 int charHeight = av.getCharHeight();
1099 int charWidth = av.getCharWidth();
1101 g.setFont(av.getFont());
1102 seqRdr.prepare(g, av.isRenderGaps());
1106 // / First draw the sequences
1107 // ///////////////////////////
1108 for (int i = startSeq; i <= endSeq; i++)
1110 nextSeq = av.getAlignment().getSequenceAt(i);
1111 if (nextSeq == null)
1113 // occasionally, a race condition occurs such that the alignment row is
1117 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1118 startRes, endRes, offset + ((i - startSeq) * charHeight));
1120 if (av.isShowSequenceFeatures())
1122 fr.drawSequence(g, nextSeq, startRes, endRes,
1123 offset + ((i - startSeq) * charHeight), false);
1127 * highlight search Results once sequence has been drawn
1129 if (av.hasSearchResults())
1131 SearchResultsI searchResults = av.getSearchResults();
1132 int[] visibleResults = searchResults.getResults(nextSeq,
1134 if (visibleResults != null)
1136 for (int r = 0; r < visibleResults.length; r += 2)
1138 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1139 visibleResults[r + 1], (visibleResults[r] - startRes)
1141 + ((i - startSeq) * charHeight));
1146 if (av.cursorMode && cursorY == i && cursorX >= startRes
1147 && cursorX <= endRes)
1149 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1150 offset + ((i - startSeq) * charHeight));
1154 if (av.getSelectionGroup() != null
1155 || av.getAlignment().getGroups().size() > 0)
1157 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1162 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1163 int startSeq, int endSeq, int offset)
1165 Graphics2D g = (Graphics2D) g1;
1167 // ///////////////////////////////////
1168 // Now outline any areas if necessary
1169 // ///////////////////////////////////
1171 SequenceGroup group = null;
1172 int groupIndex = -1;
1174 if (av.getAlignment().getGroups().size() > 0)
1176 group = av.getAlignment().getGroups().get(0);
1182 g.setStroke(new BasicStroke());
1183 g.setColor(group.getOutlineColour());
1187 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1192 g.setStroke(new BasicStroke());
1194 if (groupIndex >= av.getAlignment().getGroups().size())
1199 group = av.getAlignment().getGroups().get(groupIndex);
1201 } while (groupIndex < av.getAlignment().getGroups().size());
1209 * Draw the selection group as a separate image and overlay
1211 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1212 int startSeq, int endSeq)
1214 // get a new image of the correct size
1215 BufferedImage selectionImage = setupImage();
1217 if (selectionImage == null)
1222 SequenceGroup group = av.getSelectionGroup();
1229 // set up drawing colour
1230 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1232 setupSelectionGroup(g, selectionImage);
1234 if (!av.getWrapAlignment())
1236 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1241 drawWrappedSelection(g, group, getWidth(), getHeight(),
1242 av.getRanges().getStartRes());
1246 return selectionImage;
1250 * Set up graphics for selection group
1252 private void setupSelectionGroup(Graphics2D g,
1253 BufferedImage selectionImage)
1255 // set background to transparent
1256 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1257 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1259 // set up foreground to draw red dashed line
1260 g.setComposite(AlphaComposite.Src);
1261 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1262 BasicStroke.JOIN_ROUND, 3f, new float[]
1264 g.setColor(Color.RED);
1268 * Draw a selection group over an unwrapped alignment
1269 * @param g graphics object to draw with
1270 * @param group selection group
1271 * @param startRes start residue of area to draw
1272 * @param endRes end residue of area to draw
1273 * @param startSeq start sequence of area to draw
1274 * @param endSeq end sequence of area to draw
1275 * @param offset vertical offset (used when called from wrapped alignment code)
1277 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1278 int startRes, int endRes, int startSeq, int endSeq, int offset)
1280 int charWidth = av.getCharWidth();
1282 if (!av.hasHiddenColumns())
1284 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1289 // package into blocks of visible columns
1291 int blockStart = startRes;
1292 int blockEnd = endRes;
1294 for (int[] region : av.getAlignment().getHiddenColumns()
1295 .getHiddenColumnsCopy())
1297 int hideStart = region[0];
1298 int hideEnd = region[1];
1300 if (hideStart <= blockStart)
1302 blockStart += (hideEnd - hideStart) + 1;
1306 blockEnd = hideStart - 1;
1308 g.translate(screenY * charWidth, 0);
1309 drawPartialGroupOutline(g, group,
1310 blockStart, blockEnd, startSeq, endSeq, offset);
1312 g.translate(-screenY * charWidth, 0);
1313 screenY += blockEnd - blockStart + 1;
1314 blockStart = hideEnd + 1;
1316 if (screenY > (endRes - startRes))
1318 // already rendered last block
1323 if (screenY <= (endRes - startRes))
1325 // remaining visible region to render
1326 blockEnd = blockStart + (endRes - startRes) - screenY;
1327 g.translate(screenY * charWidth, 0);
1328 drawPartialGroupOutline(g, group,
1329 blockStart, blockEnd, startSeq, endSeq, offset);
1331 g.translate(-screenY * charWidth, 0);
1337 * Draw the selection group as a separate image and overlay
1339 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1340 int startRes, int endRes, int startSeq, int endSeq,
1343 int charHeight = av.getCharHeight();
1344 int charWidth = av.getCharWidth();
1346 int visWidth = (endRes - startRes + 1) * charWidth;
1350 boolean inGroup = false;
1358 for (i = startSeq; i <= endSeq; i++)
1360 // position of start residue of group relative to startRes, in pixels
1361 sx = (group.getStartRes() - startRes) * charWidth;
1363 // width of group in pixels
1364 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1367 sy = verticalOffset + (i - startSeq) * charHeight;
1369 if (sx + xwidth < 0 || sx > visWidth)
1374 if ((sx <= (endRes - startRes) * charWidth)
1375 && group.getSequences(null)
1376 .contains(av.getAlignment().getSequenceAt(i)))
1378 if ((bottom == -1) && !group.getSequences(null)
1379 .contains(av.getAlignment().getSequenceAt(i + 1)))
1381 bottom = sy + charHeight;
1386 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1387 .contains(av.getAlignment().getSequenceAt(i - 1)))
1400 // if start position is visible, draw vertical line to left of
1402 if (sx >= 0 && sx < visWidth)
1404 g.drawLine(sx, oldY, sx, sy);
1407 // if end position is visible, draw vertical line to right of
1409 if (sx + xwidth < visWidth)
1411 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1420 // don't let width extend beyond current block, or group extent
1422 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1424 xwidth = (endRes - startRes + 1) * charWidth - sx;
1427 // draw horizontal line at top of group
1430 g.drawLine(sx, top, sx + xwidth, top);
1434 // draw horizontal line at bottom of group
1437 g.drawLine(sx, bottom, sx + xwidth, bottom);
1448 sy = verticalOffset + ((i - startSeq) * charHeight);
1449 if (sx >= 0 && sx < visWidth)
1451 g.drawLine(sx, oldY, sx, sy);
1454 if (sx + xwidth < visWidth)
1456 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1465 if (sx + xwidth > visWidth)
1469 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1471 xwidth = (endRes - startRes + 1) * charWidth;
1476 g.drawLine(sx, top, sx + xwidth, top);
1482 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1491 * Highlights search results in the visible region by rendering as white text
1492 * on a black background. Any previous highlighting is removed. Answers true
1493 * if any highlight was left on the visible alignment (so status bar should be
1494 * set to match), else false.
1496 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1497 * alignment had to be scrolled to show the highlighted region, then it should
1498 * be fully redrawn, otherwise a fast paint can be performed. This argument
1499 * could be removed if fast paint of scrolled wrapped alignment is coded in
1500 * future (JAL-2609).
1503 * @param noFastPaint
1506 public boolean highlightSearchResults(SearchResultsI results,
1507 boolean noFastPaint)
1513 boolean wrapped = av.getWrapAlignment();
1516 fastPaint = !noFastPaint;
1517 fastpainting = fastPaint;
1520 * to avoid redrawing the whole visible region, we instead
1521 * redraw just the minimal regions to remove previous highlights
1524 SearchResultsI previous = av.getSearchResults();
1525 av.setSearchResults(results);
1526 boolean redrawn = false;
1527 boolean drawn = false;
1530 redrawn = drawMappedPositionsWrapped(previous);
1531 drawn = drawMappedPositionsWrapped(results);
1536 redrawn = drawMappedPositions(previous);
1537 drawn = drawMappedPositions(results);
1542 * if highlights were either removed or added, repaint
1550 * return true only if highlights were added
1556 fastpainting = false;
1561 * Redraws the minimal rectangle in the visible region (if any) that includes
1562 * mapped positions of the given search results. Whether or not positions are
1563 * highlighted depends on the SearchResults set on the Viewport. This allows
1564 * this method to be called to either clear or set highlighting. Answers true
1565 * if any positions were drawn (in which case a repaint is still required),
1571 protected boolean drawMappedPositions(SearchResultsI results)
1573 if (results == null)
1579 * calculate the minimal rectangle to redraw that
1580 * includes both new and existing search results
1582 int firstSeq = Integer.MAX_VALUE;
1584 int firstCol = Integer.MAX_VALUE;
1586 boolean matchFound = false;
1588 ViewportRanges ranges = av.getRanges();
1589 int firstVisibleColumn = ranges.getStartRes();
1590 int lastVisibleColumn = ranges.getEndRes();
1591 AlignmentI alignment = av.getAlignment();
1592 if (av.hasHiddenColumns())
1594 firstVisibleColumn = alignment.getHiddenColumns()
1595 .adjustForHiddenColumns(firstVisibleColumn);
1596 lastVisibleColumn = alignment.getHiddenColumns()
1597 .adjustForHiddenColumns(lastVisibleColumn);
1600 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1601 .getEndSeq(); seqNo++)
1603 SequenceI seq = alignment.getSequenceAt(seqNo);
1605 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1607 if (visibleResults != null)
1609 for (int i = 0; i < visibleResults.length - 1; i += 2)
1611 int firstMatchedColumn = visibleResults[i];
1612 int lastMatchedColumn = visibleResults[i + 1];
1613 if (firstMatchedColumn <= lastVisibleColumn
1614 && lastMatchedColumn >= firstVisibleColumn)
1617 * found a search results match in the visible region -
1618 * remember the first and last sequence matched, and the first
1619 * and last visible columns in the matched positions
1622 firstSeq = Math.min(firstSeq, seqNo);
1623 lastSeq = Math.max(lastSeq, seqNo);
1624 firstMatchedColumn = Math.max(firstMatchedColumn,
1625 firstVisibleColumn);
1626 lastMatchedColumn = Math.min(lastMatchedColumn,
1628 firstCol = Math.min(firstCol, firstMatchedColumn);
1629 lastCol = Math.max(lastCol, lastMatchedColumn);
1637 if (av.hasHiddenColumns())
1639 firstCol = alignment.getHiddenColumns()
1640 .findColumnPosition(firstCol);
1641 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1643 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1644 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1645 gg.translate(transX, transY);
1646 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1647 gg.translate(-transX, -transY);
1654 public void propertyChange(PropertyChangeEvent evt)
1656 String eventName = evt.getPropertyName();
1658 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1666 if (eventName.equals(ViewportRanges.STARTRES)
1667 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1669 // Make sure we're not trying to draw a panel
1670 // larger than the visible window
1671 if (eventName.equals(ViewportRanges.STARTRES))
1673 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1677 scrollX = ((int[]) evt.getNewValue())[0]
1678 - ((int[]) evt.getOldValue())[0];
1680 ViewportRanges vpRanges = av.getRanges();
1682 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1683 if (scrollX > range)
1687 else if (scrollX < -range)
1692 // Both scrolling and resizing change viewport ranges: scrolling changes
1693 // both start and end points, but resize only changes end values.
1694 // Here we only want to fastpaint on a scroll, with resize using a normal
1695 // paint, so scroll events are identified as changes to the horizontal or
1696 // vertical start value.
1697 if (eventName.equals(ViewportRanges.STARTRES))
1699 if (av.getWrapAlignment())
1701 fastPaintWrapped(scrollX);
1705 fastPaint(scrollX, 0);
1708 else if (eventName.equals(ViewportRanges.STARTSEQ))
1711 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1713 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1715 fastPaint(scrollX, 0);
1716 // bizarrely, we only need to scroll on the x value here as fastpaint
1717 // copies the full height of the image anyway. Passing in the y value
1718 // causes nasty repaint artefacts, which only disappear on a full
1725 * Does a minimal update of the image for a scroll movement. This method
1726 * handles scroll movements of up to one width of the wrapped alignment (one
1727 * click in the vertical scrollbar). Larger movements (for example after a
1728 * scroll to highlight a mapped position) trigger a full redraw instead.
1731 * number of positions scrolled (right if positive, left if negative)
1733 protected void fastPaintWrapped(int scrollX)
1735 ViewportRanges ranges = av.getRanges();
1737 if (Math.abs(scrollX) > ranges.getViewportWidth())
1740 * shift of more than one view width is
1741 * overcomplicated to handle in this method
1748 if (fastpainting || gg == null)
1754 fastpainting = true;
1758 calculateWrappedGeometry(getWidth(), getHeight());
1761 * relocate the regions of the alignment that are still visible
1763 shiftWrappedAlignment(-scrollX);
1766 * add new columns (sequence, annotation)
1767 * - at top left if scrollX < 0
1768 * - at right of last two widths if scrollX > 0
1772 int startRes = ranges.getStartRes();
1773 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1774 - scrollX - 1, getHeight());
1778 fastPaintWrappedAddRight(scrollX);
1782 * draw all scales (if shown) and hidden column markers
1784 drawWrappedDecorators(gg, ranges.getStartRes());
1789 fastpainting = false;
1794 * Draws the specified number of columns at the 'end' (bottom right) of a
1795 * wrapped alignment view, including sequences and annotations if shown, but
1796 * not scales. Also draws the same number of columns at the right hand end of
1797 * the second last width shown, if the last width is not full height (so
1798 * cannot simply be copied from the graphics image).
1802 protected void fastPaintWrappedAddRight(int columns)
1809 ViewportRanges ranges = av.getRanges();
1810 int viewportWidth = ranges.getViewportWidth();
1811 int charWidth = av.getCharWidth();
1814 * draw full height alignment in the second last row, last columns, if the
1815 * last row was not full height
1817 int visibleWidths = wrappedVisibleWidths;
1818 int canvasHeight = getHeight();
1819 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1821 if (lastWidthPartHeight)
1823 int widthsAbove = Math.max(0, visibleWidths - 2);
1824 int ypos = wrappedRepeatHeightPx * widthsAbove
1825 + wrappedSpaceAboveAlignment;
1826 int endRes = ranges.getEndRes();
1827 endRes += widthsAbove * viewportWidth;
1828 int startRes = endRes - columns;
1829 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1833 * white fill first to erase annotations
1835 gg.translate(xOffset, 0);
1836 gg.setColor(Color.white);
1837 gg.fillRect(labelWidthWest, ypos,
1838 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1839 gg.translate(-xOffset, 0);
1841 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1845 * draw newly visible columns in last wrapped width (none if we
1846 * have reached the end of the alignment)
1847 * y-offset for drawing last width is height of widths above,
1850 int widthsAbove = visibleWidths - 1;
1851 int ypos = wrappedRepeatHeightPx * widthsAbove
1852 + wrappedSpaceAboveAlignment;
1853 int endRes = ranges.getEndRes();
1854 endRes += widthsAbove * viewportWidth;
1855 int startRes = endRes - columns + 1;
1858 * white fill first to erase annotations
1860 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1862 gg.translate(xOffset, 0);
1863 gg.setColor(Color.white);
1864 int width = viewportWidth * charWidth - xOffset;
1865 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1866 gg.translate(-xOffset, 0);
1868 gg.setFont(av.getFont());
1869 gg.setColor(Color.black);
1871 if (startRes < ranges.getVisibleAlignmentWidth())
1873 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1877 * and finally, white fill any space below the visible alignment
1879 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1880 if (heightBelow > 0)
1882 gg.setColor(Color.white);
1883 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1888 * Shifts the visible alignment by the specified number of columns - left if
1889 * negative, right if positive. Copies and moves sequences and annotations (if
1890 * shown). Scales, hidden column markers and any newly visible columns must be
1895 protected void shiftWrappedAlignment(int positions)
1901 int charWidth = av.getCharWidth();
1903 int canvasHeight = getHeight();
1904 ViewportRanges ranges = av.getRanges();
1905 int viewportWidth = ranges.getViewportWidth();
1906 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1908 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1909 int xMax = ranges.getVisibleAlignmentWidth();
1914 * shift right (after scroll left)
1915 * for each wrapped width (starting with the last), copy (width-positions)
1916 * columns from the left margin to the right margin, and copy positions
1917 * columns from the right margin of the row above (if any) to the
1918 * left margin of the current row
1922 * get y-offset of last wrapped width, first row of sequences
1924 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1925 y += wrappedSpaceAboveAlignment;
1926 int copyFromLeftStart = labelWidthWest;
1927 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1931 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1932 positions * charWidth, 0);
1935 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1936 positions * charWidth, heightToCopy, -widthToCopy,
1937 wrappedRepeatHeightPx);
1940 y -= wrappedRepeatHeightPx;
1946 * shift left (after scroll right)
1947 * for each wrapped width (starting with the first), copy (width-positions)
1948 * columns from the right margin to the left margin, and copy positions
1949 * columns from the left margin of the row below (if any) to the
1950 * right margin of the current row
1952 int xpos = av.getRanges().getStartRes();
1953 int y = wrappedSpaceAboveAlignment;
1954 int copyFromRightStart = labelWidthWest - positions * charWidth;
1956 while (y < canvasHeight)
1958 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1959 positions * charWidth, 0);
1960 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1961 && (xpos + viewportWidth <= xMax))
1963 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1964 * charWidth, heightToCopy, widthToCopy,
1965 -wrappedRepeatHeightPx);
1968 y += wrappedRepeatHeightPx;
1969 xpos += viewportWidth;
1976 * Redraws any positions in the search results in the visible region of a
1977 * wrapped alignment. Any highlights are drawn depending on the search results
1978 * set on the Viewport, not the <code>results</code> argument. This allows
1979 * this method to be called either to clear highlights (passing the previous
1980 * search results), or to draw new highlights.
1985 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1987 if (results == null)
1991 int charHeight = av.getCharHeight();
1993 boolean matchFound = false;
1995 calculateWrappedGeometry(getWidth(), getHeight());
1996 int wrappedWidth = av.getWrappedWidth();
1997 int wrappedHeight = wrappedRepeatHeightPx;
1999 ViewportRanges ranges = av.getRanges();
2000 int canvasHeight = getHeight();
2001 int repeats = canvasHeight / wrappedHeight;
2002 if (canvasHeight / wrappedHeight > 0)
2007 int firstVisibleColumn = ranges.getStartRes();
2008 int lastVisibleColumn = ranges.getStartRes() + repeats
2009 * ranges.getViewportWidth() - 1;
2011 AlignmentI alignment = av.getAlignment();
2012 if (av.hasHiddenColumns())
2014 firstVisibleColumn = alignment.getHiddenColumns()
2015 .adjustForHiddenColumns(firstVisibleColumn);
2016 lastVisibleColumn = alignment.getHiddenColumns()
2017 .adjustForHiddenColumns(lastVisibleColumn);
2020 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2022 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2023 .getEndSeq(); seqNo++)
2025 SequenceI seq = alignment.getSequenceAt(seqNo);
2027 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2029 if (visibleResults != null)
2031 for (int i = 0; i < visibleResults.length - 1; i += 2)
2033 int firstMatchedColumn = visibleResults[i];
2034 int lastMatchedColumn = visibleResults[i + 1];
2035 if (firstMatchedColumn <= lastVisibleColumn
2036 && lastMatchedColumn >= firstVisibleColumn)
2039 * found a search results match in the visible region
2041 firstMatchedColumn = Math.max(firstMatchedColumn,
2042 firstVisibleColumn);
2043 lastMatchedColumn = Math.min(lastMatchedColumn,
2047 * draw each mapped position separately (as contiguous positions may
2048 * wrap across lines)
2050 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2052 int displayColumn = mappedPos;
2053 if (av.hasHiddenColumns())
2055 displayColumn = alignment.getHiddenColumns()
2056 .findColumnPosition(displayColumn);
2060 * transX: offset from left edge of canvas to residue position
2062 int transX = labelWidthWest
2063 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2064 * av.getCharWidth();
2067 * transY: offset from top edge of canvas to residue position
2069 int transY = gapHeight;
2070 transY += (displayColumn - ranges.getStartRes())
2071 / wrappedWidth * wrappedHeight;
2072 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2075 * yOffset is from graphics origin to start of visible region
2077 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2078 if (transY < getHeight())
2081 gg.translate(transX, transY);
2082 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2084 gg.translate(-transX, -transY);
2096 * Answers the width in pixels of the left scale labels (0 if not shown)
2100 int getLabelWidthWest()
2102 return labelWidthWest;