2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.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.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.Iterator;
45 import java.util.List;
47 import javax.swing.JPanel;
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 JPanel 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 // Don't do this! Graphics handles are supposed to be transient
91 //private Graphics2D gg;
94 * Creates a new SeqCanvas object.
98 public SeqCanvas(AlignmentPanel ap)
101 fr = new FeatureRenderer(ap);
102 seqRdr = new SequenceRenderer(av);
103 setLayout(new BorderLayout());
104 PaintRefresher.Register(this, av.getSequenceSetId());
105 setBackground(Color.white);
107 av.getRanges().addPropertyChangeListener(this);
110 public SequenceRenderer getSequenceRenderer()
115 public FeatureRenderer getFeatureRenderer()
121 * Draws the scale above a region of a wrapped alignment, consisting of a
122 * column number every major interval (10 columns).
125 * the graphics context to draw on, positioned at the start (bottom
126 * left) of the line on which to draw any scale marks
128 * start alignment column (0..)
130 * end alignment column (0..)
132 * y offset to draw at
134 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 int charHeight = av.getCharHeight();
137 int charWidth = av.getCharWidth();
140 * white fill the scale space (for the fastPaint case)
142 g.setColor(Color.white);
143 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
144 charHeight * 3 / 2 + 2);
145 g.setColor(Color.black);
147 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
149 for (ScaleMark mark : marks)
151 int mpos = mark.column; // (i - startx - 1)
156 String mstring = mark.text;
162 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
166 * draw a tick mark below the column number, centred on the column;
167 * height of tick mark is 4 pixels less than half a character
169 int xpos = (mpos * charWidth) + (charWidth / 2);
170 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
176 * Draw the scale to the left or right of a wrapped alignment
179 * graphics context, positioned at the start of the scale to be drawn
181 * first column of wrapped width (0.. excluding any hidden columns)
183 * last column of wrapped width (0.. excluding any hidden columns)
185 * vertical offset at which to begin the scale
187 * if true, scale is left of residues, if false, scale is right
189 void drawVerticalScale(Graphics g, final int startx, final int endx,
190 final int ypos, final boolean left)
192 int charHeight = av.getCharHeight();
193 int charWidth = av.getCharWidth();
195 int yPos = ypos + charHeight;
199 if (av.hasHiddenColumns())
201 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
202 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
203 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
205 FontMetrics fm = getFontMetrics(av.getFont());
207 for (int i = 0; i < av.getAlignment().getHeight(); i++)
209 SequenceI seq = av.getAlignment().getSequenceAt(i);
212 * find sequence position of first non-gapped position -
213 * to the right if scale left, to the left if scale right
215 int index = left ? startX : endX;
217 while (index >= startX && index <= endX)
219 if (!Comparison.isGap(seq.getCharAt(index)))
221 value = seq.findPosition(index);
236 * white fill the space for the scale
238 g.setColor(Color.white);
239 int y = (yPos + (i * charHeight)) - (charHeight / 5);
240 // fillRect origin is top left of rectangle
241 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
247 * draw scale value, right justified within its width less half a
248 * character width padding on the right
250 int labelSpace = left ? labelWidthWest : labelWidthEast;
251 labelSpace -= charWidth / 2; // leave space to the right
252 String valueAsString = String.valueOf(value);
253 int labelLength = fm.stringWidth(valueAsString);
254 int xOffset = labelSpace - labelLength;
255 g.setColor(Color.black);
256 g.drawString(valueAsString, xOffset, y);
263 * Does a fast paint of an alignment in response to a scroll. Most of the
264 * visible region is simply copied and shifted, and then any newly visible
265 * columns or rows are drawn. The scroll may be horizontal or vertical, but
266 * not both at once. Scrolling may be the result of
268 * <li>dragging a scroll bar</li>
269 * <li>clicking in the scroll bar</li>
270 * <li>scrolling by trackpad, middle mouse button, or other device</li>
271 * <li>by moving the box in the Overview window</li>
272 * <li>programmatically to make a highlighted position visible</li>
276 * columns to shift right (positive) or left (negative)
278 * rows to shift down (positive) or up (negative)
280 public void fastPaint(int horizontal, int vertical)
282 if (fastpainting || img == null)
291 int charHeight = av.getCharHeight();
292 int charWidth = av.getCharWidth();
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 Graphics gg = img.getGraphics();
303 gg.copyArea(horizontal * charWidth, vertical * charHeight,
304 img.getWidth(), img.getHeight(), -horizontal * charWidth,
305 -vertical * charHeight);
307 if (horizontal > 0) // scrollbar pulled right, image to the left
309 transX = (endRes - startRes - horizontal) * charWidth;
310 startRes = endRes - horizontal;
312 else if (horizontal < 0)
314 endRes = startRes - horizontal;
317 if (vertical > 0) // scroll down
319 startSeq = endSeq - vertical;
321 if (startSeq < ranges.getStartSeq())
322 { // ie scrolling too fast, more than a page at a time
323 startSeq = ranges.getStartSeq();
327 transY = img.getHeight() - ((vertical + 1) * charHeight);
330 else if (vertical < 0)
332 endSeq = startSeq - vertical;
334 if (endSeq > ranges.getEndSeq())
336 endSeq = ranges.getEndSeq();
340 gg.translate(transX, transY);
341 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
342 gg.translate(-transX, -transY);
345 // Call repaint on alignment panel so that repaints from other alignment
346 // panel components can be aggregated. Otherwise performance of the
347 // overview window and others may be adversely affected.
348 av.getAlignPanel().repaint();
351 fastpainting = false;
356 public void paintComponent(Graphics g)
358 super.paintComponent(g);
360 int charHeight = av.getCharHeight();
361 int charWidth = av.getCharWidth();
363 ViewportRanges ranges = av.getRanges();
365 int width = getWidth();
366 int height = getHeight();
368 width -= (width % charWidth);
369 height -= (height % charHeight);
372 if ((img != null) && (fastPaint
373 || (getVisibleRect().width != g.getClipBounds().width)
374 || (getVisibleRect().height != g.getClipBounds().height)))
376 g.drawImage(img, 0, 0, this);
378 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
379 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
383 else if (width > 0 && height > 0)
386 * img is a cached version of the last view we drew, if any
387 * if we have no img or the size has changed, make a new one
389 if (img == null || width != img.getWidth()
390 || height != img.getHeight())
392 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
395 Graphics2D gg = (Graphics2D) img.getGraphics();
396 gg.setFont(av.getFont());
400 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
401 RenderingHints.VALUE_ANTIALIAS_ON);
404 gg.setColor(Color.white);
405 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
407 if (av.getWrapAlignment())
409 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
413 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
414 ranges.getStartSeq(), ranges.getEndSeq(), 0);
417 drawSelectionGroup(gg, ranges.getStartRes(),
418 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
420 g.drawImage(img, 0, 0, this);
426 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
427 ranges.getStartSeq(), ranges.getEndSeq());
432 * Draw an alignment panel for printing
435 * Graphics object to draw with
437 * start residue of print area
439 * end residue of print area
441 * start sequence of print area
443 * end sequence of print area
445 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
446 int startSeq, int endSeq)
448 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
450 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
455 * Draw a wrapped alignment panel for printing
458 * Graphics object to draw with
460 * width of drawing area
461 * @param canvasHeight
462 * height of drawing area
464 * start residue of print area
466 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
467 int canvasHeight, int startRes)
469 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
471 SequenceGroup group = av.getSelectionGroup();
474 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
480 * Returns the visible width of the canvas in residues, after allowing for
481 * East or West scales (if shown)
484 * the width in pixels (possibly including scales)
488 public int getWrappedCanvasWidth(int canvasWidth)
490 int charWidth = av.getCharWidth();
492 FontMetrics fm = getFontMetrics(av.getFont());
496 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
498 labelWidth = getLabelWidth(fm);
501 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
503 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
505 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
509 * Returns a pixel width sufficient to show the largest sequence coordinate
510 * (end position) in the alignment, calculated as the FontMetrics width of
511 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
512 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
513 * half a character width space on either side.
518 protected int getLabelWidth(FontMetrics fm)
521 * find the biggest sequence end position we need to show
522 * (note this is not necessarily the sequence length)
525 AlignmentI alignment = av.getAlignment();
526 for (int i = 0; i < alignment.getHeight(); i++)
528 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
532 for (int i = maxWidth; i > 0; i /= 10)
537 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
541 * Draws as many widths of a wrapped alignment as can fit in the visible
546 * available width in pixels
547 * @param canvasHeight
548 * available height in pixels
550 * the first column (0...) of the alignment to draw
552 public void drawWrappedPanel(Graphics g, int canvasWidth,
553 int canvasHeight, final int startColumn)
555 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
558 av.setWrappedWidth(wrappedWidthInResidues);
560 ViewportRanges ranges = av.getRanges();
561 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
563 // we need to call this again to make sure the startColumn +
564 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
566 calculateWrappedGeometry(canvasWidth, canvasHeight);
569 * draw one width at a time (excluding any scales or annotation shown),
570 * until we have run out of either alignment or vertical space available
572 int ypos = wrappedSpaceAboveAlignment;
573 int maxWidth = ranges.getVisibleAlignmentWidth();
575 int start = startColumn;
576 int currentWidth = 0;
577 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
580 .min(maxWidth, start + wrappedWidthInResidues - 1);
581 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
582 ypos += wrappedRepeatHeightPx;
583 start += wrappedWidthInResidues;
587 drawWrappedDecorators(g, startColumn);
591 * Calculates and saves values needed when rendering a wrapped alignment.
592 * These depend on many factors, including
594 * <li>canvas width and height</li>
595 * <li>number of visible sequences, and height of annotations if shown</li>
596 * <li>font and character width</li>
597 * <li>whether scales are shown left, right or above the alignment</li>
601 * @param canvasHeight
602 * @return the number of residue columns in each width
604 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
606 int charHeight = av.getCharHeight();
609 * vertical space in pixels between wrapped widths of alignment
610 * - one character height, or two if scale above is drawn
612 wrappedSpaceAboveAlignment = charHeight
613 * (av.getScaleAboveWrapped() ? 2 : 1);
616 * height in pixels of the wrapped widths
618 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
620 wrappedRepeatHeightPx += av.getAlignment().getHeight()
622 // add annotations panel height if shown
623 wrappedRepeatHeightPx += getAnnotationHeight();
626 * number of visible widths (the last one may be part height),
627 * ensuring a part height includes at least one sequence
629 ViewportRanges ranges = av.getRanges();
630 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
631 int remainder = canvasHeight % wrappedRepeatHeightPx;
632 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
634 wrappedVisibleWidths++;
638 * compute width in residues; this also sets East and West label widths
640 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
643 * limit visibleWidths to not exceed width of alignment
645 int xMax = ranges.getVisibleAlignmentWidth();
646 int startToEnd = xMax - ranges.getStartRes();
647 int maxWidths = startToEnd / wrappedWidthInResidues;
648 if (startToEnd % wrappedWidthInResidues > 0)
652 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
654 return wrappedWidthInResidues;
658 * Draws one width of a wrapped alignment, including sequences and
659 * annnotations, if shown, but not scales or hidden column markers
665 * @param canvasHeight
667 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
668 int endColumn, int canvasHeight)
670 ViewportRanges ranges = av.getRanges();
671 int viewportWidth = ranges.getViewportWidth();
673 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
676 * move right before drawing by the width of the scale left (if any)
677 * plus column offset from left margin (usually zero, but may be non-zero
678 * when fast painting is drawing just a few columns)
680 int charWidth = av.getCharWidth();
681 int xOffset = labelWidthWest
682 + ((startColumn - ranges.getStartRes()) % viewportWidth)
685 g.translate(xOffset, 0);
688 * white fill the region to be drawn (so incremental fast paint doesn't
689 * scribble over an existing image)
691 g.setColor(Color.white);
692 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
693 wrappedRepeatHeightPx);
695 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
698 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
700 if (av.isShowAnnotation())
702 g.translate(0, cHeight + ypos + 3);
703 if (annotations == null)
705 annotations = new AnnotationPanel(av);
708 annotations.renderer.drawComponent(annotations, av, g, -1,
709 startColumn, endx + 1);
710 g.translate(0, -cHeight - ypos - 3);
712 g.translate(-xOffset, 0);
716 * Draws scales left, right and above (if shown), and any hidden column
717 * markers, on all widths of the wrapped alignment
722 protected void drawWrappedDecorators(Graphics g, final int startColumn)
724 int charWidth = av.getCharWidth();
726 g.setFont(av.getFont());
728 g.setColor(Color.black);
730 int ypos = wrappedSpaceAboveAlignment;
731 ViewportRanges ranges = av.getRanges();
732 int viewportWidth = ranges.getViewportWidth();
733 int maxWidth = ranges.getVisibleAlignmentWidth();
735 int startCol = startColumn;
737 while (widthsDrawn < wrappedVisibleWidths)
739 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
741 if (av.getScaleLeftWrapped())
743 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
746 if (av.getScaleRightWrapped())
748 int x = labelWidthWest + viewportWidth * charWidth;
751 drawVerticalScale(g, startCol, endColumn, ypos, false);
756 * white fill region of scale above and hidden column markers
757 * (to support incremental fast paint of image)
759 g.translate(labelWidthWest, 0);
760 g.setColor(Color.white);
761 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
762 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
763 g.setColor(Color.black);
764 g.translate(-labelWidthWest, 0);
766 g.translate(labelWidthWest, 0);
768 if (av.getScaleAboveWrapped())
770 drawNorthScale(g, startCol, endColumn, ypos);
773 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
775 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
778 g.translate(-labelWidthWest, 0);
780 ypos += wrappedRepeatHeightPx;
781 startCol += viewportWidth;
787 * Draws markers (triangles) above hidden column positions between startColumn
795 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
796 int startColumn, int endColumn)
798 int charHeight = av.getCharHeight();
799 int charWidth = av.getCharWidth();
801 g.setColor(Color.blue);
803 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
805 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
809 res = it.next() - startColumn;
811 if (res < 0 || res > endColumn - startColumn + 1)
817 * draw a downward-pointing triangle at the hidden columns location
818 * (before the following visible column)
820 int xMiddle = res * charWidth;
821 int[] xPoints = new int[] { xMiddle - charHeight / 4,
822 xMiddle + charHeight / 4, xMiddle };
823 int yTop = ypos - (charHeight / 2);
824 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
825 g.fillPolygon(xPoints, yPoints, 3);
830 * Draw a selection group over a wrapped alignment
832 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
834 int canvasHeight, int startRes)
836 int charHeight = av.getCharHeight();
837 int charWidth = av.getCharWidth();
839 // height gap above each panel
840 int hgap = charHeight;
841 if (av.getScaleAboveWrapped())
846 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
848 int cHeight = av.getAlignment().getHeight() * charHeight;
850 int startx = startRes;
852 int ypos = hgap; // vertical offset
853 int maxwidth = av.getAlignment().getWidth();
855 if (av.hasHiddenColumns())
857 maxwidth = av.getAlignment().getHiddenColumns()
858 .absoluteToVisibleColumn(maxwidth);
861 // chop the wrapped alignment extent up into panel-sized blocks and treat
862 // each block as if it were a block from an unwrapped alignment
863 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
864 BasicStroke.JOIN_ROUND, 3f, new float[]
866 g.setColor(Color.RED);
867 while ((ypos <= canvasHeight) && (startx < maxwidth))
869 // set end value to be start + width, or maxwidth, whichever is smaller
870 endx = startx + cWidth - 1;
877 g.translate(labelWidthWest, 0);
879 drawUnwrappedSelection(g, group, startx, endx, 0,
880 av.getAlignment().getHeight() - 1,
883 g.translate(-labelWidthWest, 0);
885 // update vertical offset
886 ypos += cHeight + getAnnotationHeight() + hgap;
888 // update horizontal offset
891 g.setStroke(new BasicStroke());
894 int getAnnotationHeight()
896 if (!av.isShowAnnotation())
901 if (annotations == null)
903 annotations = new AnnotationPanel(av);
906 return annotations.adjustPanelHeight();
910 * Draws the visible region of the alignment on the graphics context. If there
911 * are hidden column markers in the visible region, then each sub-region
912 * between the markers is drawn separately, followed by the hidden column
916 * the graphics context, positioned at the first residue to be drawn
918 * offset of the first column to draw (0..)
920 * offset of the last column to draw (0..)
922 * offset of the first sequence to draw (0..)
924 * offset of the last sequence to draw (0..)
926 * vertical offset at which to draw (for wrapped alignments)
928 public void drawPanel(Graphics g1, final int startRes, final int endRes,
929 final int startSeq, final int endSeq, final int yOffset)
931 int charHeight = av.getCharHeight();
932 int charWidth = av.getCharWidth();
934 if (!av.hasHiddenColumns())
936 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
944 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
945 VisibleContigsIterator regions = hidden
946 .getVisContigsIterator(startRes, endRes + 1, true);
948 while (regions.hasNext())
950 int[] region = regions.next();
951 blockEnd = region[1];
952 blockStart = region[0];
955 * draw up to just before the next hidden region, or the end of
956 * the visible region, whichever comes first
958 g1.translate(screenY * charWidth, 0);
960 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
963 * draw the downline of the hidden column marker (ScalePanel draws the
964 * triangle on top) if we reached it
966 if (av.getShowHiddenMarkers()
967 && (regions.hasNext() || regions.endsAtHidden()))
969 g1.setColor(Color.blue);
971 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
972 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
973 (endSeq - startSeq + 1) * charHeight + yOffset);
976 g1.translate(-screenY * charWidth, 0);
977 screenY += blockEnd - blockStart + 1;
984 * Draws a region of the visible alignment
988 * offset of the first column in the visible region (0..)
990 * offset of the last column in the visible region (0..)
992 * offset of the first sequence in the visible region (0..)
994 * offset of the last sequence in the visible region (0..)
996 * vertical offset at which to draw (for wrapped alignments)
998 private void draw(Graphics g, int startRes, int endRes, int startSeq,
999 int endSeq, int offset)
1001 int charHeight = av.getCharHeight();
1002 int charWidth = av.getCharWidth();
1004 g.setFont(av.getFont());
1005 seqRdr.prepare(g, av.isRenderGaps());
1009 // / First draw the sequences
1010 // ///////////////////////////
1011 for (int i = startSeq; i <= endSeq; i++)
1013 nextSeq = av.getAlignment().getSequenceAt(i);
1014 if (nextSeq == null)
1016 // occasionally, a race condition occurs such that the alignment row is
1020 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1021 startRes, endRes, offset + ((i - startSeq) * charHeight));
1023 if (av.isShowSequenceFeatures())
1025 fr.drawSequence(g, nextSeq, startRes, endRes,
1026 offset + ((i - startSeq) * charHeight), false);
1030 * highlight search Results once sequence has been drawn
1032 if (av.hasSearchResults())
1034 SearchResultsI searchResults = av.getSearchResults();
1035 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1037 if (visibleResults != null)
1039 for (int r = 0; r < visibleResults.length; r += 2)
1041 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1042 visibleResults[r + 1],
1043 (visibleResults[r] - startRes) * charWidth,
1044 offset + ((i - startSeq) * charHeight));
1050 if (av.getSelectionGroup() != null
1051 || av.getAlignment().getGroups().size() > 0)
1053 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1059 * Draws the outlines of any groups defined on the alignment (excluding the
1060 * current selection group, if any)
1069 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1070 int startSeq, int endSeq, int offset)
1072 Graphics2D g = (Graphics2D) g1;
1074 SequenceGroup group = null;
1075 int groupIndex = -1;
1077 if (av.getAlignment().getGroups().size() > 0)
1079 group = av.getAlignment().getGroups().get(0);
1087 g.setColor(group.getOutlineColour());
1088 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1092 if (groupIndex >= av.getAlignment().getGroups().size())
1096 group = av.getAlignment().getGroups().get(groupIndex);
1097 } while (groupIndex < av.getAlignment().getGroups().size());
1102 * Draws the outline of the current selection group (if any)
1110 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1111 int startSeq, int endSeq)
1113 SequenceGroup group = av.getSelectionGroup();
1119 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1120 BasicStroke.JOIN_ROUND, 3f, new float[]
1122 g.setColor(Color.RED);
1123 if (!av.getWrapAlignment())
1125 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1130 drawWrappedSelection(g, group, getWidth(), getHeight(),
1131 av.getRanges().getStartRes());
1133 g.setStroke(new BasicStroke());
1137 * Draw the cursor as a separate image and overlay
1140 * start residue of area to draw cursor in
1142 * end residue of area to draw cursor in
1144 * start sequence of area to draw cursor in
1146 * end sequence of are to draw cursor in
1147 * @return a transparent image of the same size as the sequence canvas, with
1148 * the cursor drawn on it, if any
1150 private void drawCursor(Graphics g, int startRes, int endRes,
1154 // convert the cursorY into a position on the visible alignment
1155 int cursor_ypos = cursorY;
1157 // don't do work unless we have to
1158 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1162 int startx = startRes;
1165 // convert the cursorX into a position on the visible alignment
1166 int cursor_xpos = av.getAlignment().getHiddenColumns()
1167 .absoluteToVisibleColumn(cursorX);
1169 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1172 if (av.getWrapAlignment())
1174 // work out the correct offsets for the cursor
1175 int charHeight = av.getCharHeight();
1176 int charWidth = av.getCharWidth();
1177 int canvasWidth = getWidth();
1178 int canvasHeight = getHeight();
1180 // height gap above each panel
1181 int hgap = charHeight;
1182 if (av.getScaleAboveWrapped())
1187 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1189 int cHeight = av.getAlignment().getHeight() * charHeight;
1191 endx = startx + cWidth - 1;
1192 int ypos = hgap; // vertical offset
1194 // iterate down the wrapped panels
1195 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1197 // update vertical offset
1198 ypos += cHeight + getAnnotationHeight() + hgap;
1200 // update horizontal offset
1202 endx = startx + cWidth - 1;
1205 xoffset = labelWidthWest;
1208 // now check if cursor is within range for x values
1209 if (cursor_xpos >= startx && cursor_xpos <= endx)
1211 // get the character the cursor is drawn at
1212 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1213 char s = seq.getCharAt(cursorX);
1215 seqRdr.drawCursor(g, s,
1216 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1217 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1225 * Draw a selection group over an unwrapped alignment
1228 * graphics object to draw with
1232 * start residue of area to draw
1234 * end residue of area to draw
1236 * start sequence of area to draw
1238 * end sequence of area to draw
1240 * vertical offset (used when called from wrapped alignment code)
1242 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1243 int startRes, int endRes, int startSeq, int endSeq, int offset)
1245 int charWidth = av.getCharWidth();
1247 if (!av.hasHiddenColumns())
1249 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1254 // package into blocks of visible columns
1259 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1260 VisibleContigsIterator regions = hidden
1261 .getVisContigsIterator(startRes, endRes + 1, true);
1262 while (regions.hasNext())
1264 int[] region = regions.next();
1265 blockEnd = region[1];
1266 blockStart = region[0];
1268 g.translate(screenY * charWidth, 0);
1269 drawPartialGroupOutline(g, group,
1270 blockStart, blockEnd, startSeq, endSeq, offset);
1272 g.translate(-screenY * charWidth, 0);
1273 screenY += blockEnd - blockStart + 1;
1279 * Draws part of a selection group outline
1287 * @param verticalOffset
1289 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1290 int startRes, int endRes, int startSeq, int endSeq,
1293 int charHeight = av.getCharHeight();
1294 int charWidth = av.getCharWidth();
1295 int visWidth = (endRes - startRes + 1) * charWidth;
1299 boolean inGroup = false;
1304 List<SequenceI> seqs = group.getSequences(null);
1306 // position of start residue of group relative to startRes, in pixels
1307 int sx = (group.getStartRes() - startRes) * charWidth;
1309 // width of group in pixels
1310 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1313 if (!(sx + xwidth < 0 || sx > visWidth))
1315 for (i = startSeq; i <= endSeq; i++)
1317 sy = verticalOffset + (i - startSeq) * charHeight;
1319 if ((sx <= (endRes - startRes) * charWidth)
1320 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1323 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1325 bottom = sy + charHeight;
1330 if (((top == -1) && (i == 0)) || !seqs
1331 .contains(av.getAlignment().getSequenceAt(i - 1)))
1342 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1343 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1345 // reset top and bottom
1353 sy = verticalOffset + ((i - startSeq) * charHeight);
1354 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1355 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1361 * Draw horizontal selection group boundaries at top and bottom positions
1364 * graphics object to draw on
1370 * visWidth maximum available width
1372 * position to draw top of group at
1374 * position to draw bottom of group at
1376 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1377 int visWidth, int top, int bottom)
1387 // don't let width extend beyond current block, or group extent
1389 if (startx + width >= visWidth)
1391 width = visWidth - startx;
1396 g.drawLine(startx, top, startx + width, top);
1401 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1406 * Draw vertical lines at sx and sx+xwidth providing they lie within
1410 * graphics object to draw on
1416 * visWidth maximum available width
1422 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1425 // if start position is visible, draw vertical line to left of
1427 if (sx >= 0 && sx < visWidth)
1429 g.drawLine(sx, oldY, sx, sy);
1432 // if end position is visible, draw vertical line to right of
1434 if (sx + xwidth < visWidth)
1436 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1441 * Highlights search results in the visible region by rendering as white text
1442 * on a black background. Any previous highlighting is removed. Answers true
1443 * if any highlight was left on the visible alignment (so status bar should be
1444 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1445 * so allows the next repaint to update the whole display.
1450 public boolean highlightSearchResults(SearchResultsI results)
1452 return highlightSearchResults(results, false);
1457 * Highlights search results in the visible region by rendering as white text
1458 * on a black background. Any previous highlighting is removed. Answers true
1459 * if any highlight was left on the visible alignment (so status bar should be
1460 * set to match), else false.
1462 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1463 * highlighted regions are modified. This speeds up highlighting across linked
1466 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1467 * a wrapped alignment had to be scrolled to show the highlighted region, then
1468 * it should be fully redrawn, otherwise a fast paint can be performed. This
1469 * argument could be removed if fast paint of scrolled wrapped alignment is
1470 * coded in future (JAL-2609).
1473 * @param doFastPaint
1474 * if true, sets a flag so the next repaint only redraws the modified
1478 public boolean highlightSearchResults(SearchResultsI results,
1479 boolean doFastPaint)
1485 boolean wrapped = av.getWrapAlignment();
1488 fastPaint = doFastPaint;
1489 fastpainting = fastPaint;
1492 * to avoid redrawing the whole visible region, we instead
1493 * redraw just the minimal regions to remove previous highlights
1496 SearchResultsI previous = av.getSearchResults();
1497 av.setSearchResults(results);
1498 boolean redrawn = false;
1499 boolean drawn = false;
1502 redrawn = drawMappedPositionsWrapped(previous);
1503 drawn = drawMappedPositionsWrapped(results);
1508 redrawn = drawMappedPositions(previous);
1509 drawn = drawMappedPositions(results);
1514 * if highlights were either removed or added, repaint
1522 * return true only if highlights were added
1528 fastpainting = false;
1533 * Redraws the minimal rectangle in the visible region (if any) that includes
1534 * mapped positions of the given search results. Whether or not positions are
1535 * highlighted depends on the SearchResults set on the Viewport. This allows
1536 * this method to be called to either clear or set highlighting. Answers true
1537 * if any positions were drawn (in which case a repaint is still required),
1543 protected boolean drawMappedPositions(SearchResultsI results)
1545 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1551 * calculate the minimal rectangle to redraw that
1552 * includes both new and existing search results
1554 int firstSeq = Integer.MAX_VALUE;
1556 int firstCol = Integer.MAX_VALUE;
1558 boolean matchFound = false;
1560 ViewportRanges ranges = av.getRanges();
1561 int firstVisibleColumn = ranges.getStartRes();
1562 int lastVisibleColumn = ranges.getEndRes();
1563 AlignmentI alignment = av.getAlignment();
1564 if (av.hasHiddenColumns())
1566 firstVisibleColumn = alignment.getHiddenColumns()
1567 .visibleToAbsoluteColumn(firstVisibleColumn);
1568 lastVisibleColumn = alignment.getHiddenColumns()
1569 .visibleToAbsoluteColumn(lastVisibleColumn);
1572 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1573 .getEndSeq(); seqNo++)
1575 SequenceI seq = alignment.getSequenceAt(seqNo);
1577 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1579 if (visibleResults != null)
1581 for (int i = 0; i < visibleResults.length - 1; i += 2)
1583 int firstMatchedColumn = visibleResults[i];
1584 int lastMatchedColumn = visibleResults[i + 1];
1585 if (firstMatchedColumn <= lastVisibleColumn
1586 && lastMatchedColumn >= firstVisibleColumn)
1589 * found a search results match in the visible region -
1590 * remember the first and last sequence matched, and the first
1591 * and last visible columns in the matched positions
1594 firstSeq = Math.min(firstSeq, seqNo);
1595 lastSeq = Math.max(lastSeq, seqNo);
1596 firstMatchedColumn = Math.max(firstMatchedColumn,
1597 firstVisibleColumn);
1598 lastMatchedColumn = Math.min(lastMatchedColumn,
1600 firstCol = Math.min(firstCol, firstMatchedColumn);
1601 lastCol = Math.max(lastCol, lastMatchedColumn);
1609 if (av.hasHiddenColumns())
1611 firstCol = alignment.getHiddenColumns()
1612 .absoluteToVisibleColumn(firstCol);
1613 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1615 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1616 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1617 Graphics gg = img.getGraphics();
1618 gg.translate(transX, transY);
1619 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1620 gg.translate(-transX, -transY);
1628 public void propertyChange(PropertyChangeEvent evt)
1630 String eventName = evt.getPropertyName();
1632 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1638 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1646 if (eventName.equals(ViewportRanges.STARTRES)
1647 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1649 // Make sure we're not trying to draw a panel
1650 // larger than the visible window
1651 if (eventName.equals(ViewportRanges.STARTRES))
1653 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1657 scrollX = ((int[]) evt.getNewValue())[0]
1658 - ((int[]) evt.getOldValue())[0];
1660 ViewportRanges vpRanges = av.getRanges();
1662 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1663 if (scrollX > range)
1667 else if (scrollX < -range)
1672 // Both scrolling and resizing change viewport ranges: scrolling changes
1673 // both start and end points, but resize only changes end values.
1674 // Here we only want to fastpaint on a scroll, with resize using a normal
1675 // paint, so scroll events are identified as changes to the horizontal or
1676 // vertical start value.
1677 if (eventName.equals(ViewportRanges.STARTRES))
1679 if (av.getWrapAlignment())
1681 fastPaintWrapped(scrollX);
1685 fastPaint(scrollX, 0);
1688 else if (eventName.equals(ViewportRanges.STARTSEQ))
1691 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1693 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1695 if (av.getWrapAlignment())
1697 fastPaintWrapped(scrollX);
1701 fastPaint(scrollX, 0);
1704 else if (eventName.equals(ViewportRanges.STARTSEQ))
1707 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1709 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1711 if (av.getWrapAlignment())
1713 fastPaintWrapped(scrollX);
1719 * Does a minimal update of the image for a scroll movement. This method
1720 * handles scroll movements of up to one width of the wrapped alignment (one
1721 * click in the vertical scrollbar). Larger movements (for example after a
1722 * scroll to highlight a mapped position) trigger a full redraw instead.
1725 * number of positions scrolled (right if positive, left if negative)
1727 protected void fastPaintWrapped(int scrollX)
1729 ViewportRanges ranges = av.getRanges();
1731 if (Math.abs(scrollX) > ranges.getViewportWidth())
1734 * shift of more than one view width is
1735 * overcomplicated to handle in this method
1742 if (fastpainting || img == null)
1748 fastpainting = true;
1753 Graphics gg = img.getGraphics();
1755 calculateWrappedGeometry(getWidth(), getHeight());
1758 * relocate the regions of the alignment that are still visible
1760 shiftWrappedAlignment(-scrollX);
1763 * add new columns (sequence, annotation)
1764 * - at top left if scrollX < 0
1765 * - at right of last two widths if scrollX > 0
1769 int startRes = ranges.getStartRes();
1770 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1771 - scrollX - 1, getHeight());
1775 fastPaintWrappedAddRight(scrollX);
1779 * draw all scales (if shown) and hidden column markers
1781 drawWrappedDecorators(gg, ranges.getStartRes());
1788 fastpainting = false;
1793 * Draws the specified number of columns at the 'end' (bottom right) of a
1794 * wrapped alignment view, including sequences and annotations if shown, but
1795 * not scales. Also draws the same number of columns at the right hand end of
1796 * the second last width shown, if the last width is not full height (so
1797 * cannot simply be copied from the graphics image).
1801 protected void fastPaintWrappedAddRight(int columns)
1808 Graphics gg = img.getGraphics();
1810 ViewportRanges ranges = av.getRanges();
1811 int viewportWidth = ranges.getViewportWidth();
1812 int charWidth = av.getCharWidth();
1815 * draw full height alignment in the second last row, last columns, if the
1816 * last row was not full height
1818 int visibleWidths = wrappedVisibleWidths;
1819 int canvasHeight = getHeight();
1820 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1822 if (lastWidthPartHeight)
1824 int widthsAbove = Math.max(0, visibleWidths - 2);
1825 int ypos = wrappedRepeatHeightPx * widthsAbove
1826 + wrappedSpaceAboveAlignment;
1827 int endRes = ranges.getEndRes();
1828 endRes += widthsAbove * viewportWidth;
1829 int startRes = endRes - columns;
1830 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1834 * white fill first to erase annotations
1838 gg.translate(xOffset, 0);
1839 gg.setColor(Color.white);
1840 gg.fillRect(labelWidthWest, ypos,
1841 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1842 gg.translate(-xOffset, 0);
1844 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1849 * draw newly visible columns in last wrapped width (none if we
1850 * have reached the end of the alignment)
1851 * y-offset for drawing last width is height of widths above,
1854 int widthsAbove = visibleWidths - 1;
1855 int ypos = wrappedRepeatHeightPx * widthsAbove
1856 + wrappedSpaceAboveAlignment;
1857 int endRes = ranges.getEndRes();
1858 endRes += widthsAbove * viewportWidth;
1859 int startRes = endRes - columns + 1;
1862 * white fill first to erase annotations
1864 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1866 gg.translate(xOffset, 0);
1867 gg.setColor(Color.white);
1868 int width = viewportWidth * charWidth - xOffset;
1869 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1870 gg.translate(-xOffset, 0);
1872 gg.setFont(av.getFont());
1873 gg.setColor(Color.black);
1875 if (startRes < ranges.getVisibleAlignmentWidth())
1877 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1881 * and finally, white fill any space below the visible alignment
1883 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1884 if (heightBelow > 0)
1886 gg.setColor(Color.white);
1887 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1893 * Shifts the visible alignment by the specified number of columns - left if
1894 * negative, right if positive. Copies and moves sequences and annotations (if
1895 * shown). Scales, hidden column markers and any newly visible columns must be
1900 protected void shiftWrappedAlignment(int positions)
1907 Graphics gg = img.getGraphics();
1909 int charWidth = av.getCharWidth();
1911 int canvasHeight = getHeight();
1912 ViewportRanges ranges = av.getRanges();
1913 int viewportWidth = ranges.getViewportWidth();
1914 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1916 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1917 int xMax = ranges.getVisibleAlignmentWidth();
1922 * shift right (after scroll left)
1923 * for each wrapped width (starting with the last), copy (width-positions)
1924 * columns from the left margin to the right margin, and copy positions
1925 * columns from the right margin of the row above (if any) to the
1926 * left margin of the current row
1930 * get y-offset of last wrapped width, first row of sequences
1932 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1933 y += wrappedSpaceAboveAlignment;
1934 int copyFromLeftStart = labelWidthWest;
1935 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1939 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1940 positions * charWidth, 0);
1943 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1944 positions * charWidth, heightToCopy, -widthToCopy,
1945 wrappedRepeatHeightPx);
1948 y -= wrappedRepeatHeightPx;
1954 * shift left (after scroll right)
1955 * for each wrapped width (starting with the first), copy (width-positions)
1956 * columns from the right margin to the left margin, and copy positions
1957 * columns from the left margin of the row below (if any) to the
1958 * right margin of the current row
1960 int xpos = av.getRanges().getStartRes();
1961 int y = wrappedSpaceAboveAlignment;
1962 int copyFromRightStart = labelWidthWest - positions * charWidth;
1964 while (y < canvasHeight)
1966 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1967 positions * charWidth, 0);
1968 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1969 && (xpos + viewportWidth <= xMax))
1971 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1972 * charWidth, heightToCopy, widthToCopy,
1973 -wrappedRepeatHeightPx);
1975 y += wrappedRepeatHeightPx;
1976 xpos += viewportWidth;
1984 * Redraws any positions in the search results in the visible region of a
1985 * wrapped alignment. Any highlights are drawn depending on the search results
1986 * set on the Viewport, not the <code>results</code> argument. This allows
1987 * this method to be called either to clear highlights (passing the previous
1988 * search results), or to draw new highlights.
1993 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1995 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1999 int charHeight = av.getCharHeight();
2001 boolean matchFound = false;
2003 calculateWrappedGeometry(getWidth(), getHeight());
2004 int wrappedWidth = av.getWrappedWidth();
2005 int wrappedHeight = wrappedRepeatHeightPx;
2007 ViewportRanges ranges = av.getRanges();
2008 int canvasHeight = getHeight();
2009 int repeats = canvasHeight / wrappedHeight;
2010 if (canvasHeight / wrappedHeight > 0)
2015 int firstVisibleColumn = ranges.getStartRes();
2016 int lastVisibleColumn = ranges.getStartRes() + repeats
2017 * ranges.getViewportWidth() - 1;
2019 AlignmentI alignment = av.getAlignment();
2020 if (av.hasHiddenColumns())
2022 firstVisibleColumn = alignment.getHiddenColumns()
2023 .visibleToAbsoluteColumn(firstVisibleColumn);
2024 lastVisibleColumn = alignment.getHiddenColumns()
2025 .visibleToAbsoluteColumn(lastVisibleColumn);
2028 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2031 Graphics gg = img.getGraphics();
2033 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2034 .getEndSeq(); seqNo++)
2036 SequenceI seq = alignment.getSequenceAt(seqNo);
2038 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2040 if (visibleResults != null)
2042 for (int i = 0; i < visibleResults.length - 1; i += 2)
2044 int firstMatchedColumn = visibleResults[i];
2045 int lastMatchedColumn = visibleResults[i + 1];
2046 if (firstMatchedColumn <= lastVisibleColumn
2047 && lastMatchedColumn >= firstVisibleColumn)
2050 * found a search results match in the visible region
2052 firstMatchedColumn = Math.max(firstMatchedColumn,
2053 firstVisibleColumn);
2054 lastMatchedColumn = Math.min(lastMatchedColumn,
2058 * draw each mapped position separately (as contiguous positions may
2059 * wrap across lines)
2061 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2063 int displayColumn = mappedPos;
2064 if (av.hasHiddenColumns())
2066 displayColumn = alignment.getHiddenColumns()
2067 .absoluteToVisibleColumn(displayColumn);
2071 * transX: offset from left edge of canvas to residue position
2073 int transX = labelWidthWest
2074 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2075 * av.getCharWidth();
2078 * transY: offset from top edge of canvas to residue position
2080 int transY = gapHeight;
2081 transY += (displayColumn - ranges.getStartRes())
2082 / wrappedWidth * wrappedHeight;
2083 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2086 * yOffset is from graphics origin to start of visible region
2088 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2089 if (transY < getHeight())
2092 gg.translate(transX, transY);
2093 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2095 gg.translate(-transX, -transY);
2109 * Answers the width in pixels of the left scale labels (0 if not shown)
2113 int getLabelWidthWest()
2115 return labelWidthWest;