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);
371 if ((img != null) && (fastPaint
372 || (getVisibleRect().width != g.getClipBounds().width)
373 || (getVisibleRect().height != g.getClipBounds().height)))
375 g.drawImage(img, 0, 0, this);
377 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
378 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
382 else if (width > 0 && height > 0)
385 * img is a cached version of the last view we drew, if any
386 * if we have no img or the size has changed, make a new one
388 if (img == null || width != img.getWidth()
389 || height != img.getHeight())
391 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
394 Graphics2D gg = (Graphics2D) img.getGraphics();
395 gg.setFont(av.getFont());
399 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 gg.setColor(Color.white);
404 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
406 if (av.getWrapAlignment())
408 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
412 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
413 ranges.getStartSeq(), ranges.getEndSeq(), 0);
416 drawSelectionGroup(gg, ranges.getStartRes(),
417 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
419 g.drawImage(img, 0, 0, this);
425 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
426 ranges.getStartSeq(), ranges.getEndSeq());
431 * Draw an alignment panel for printing
434 * Graphics object to draw with
436 * start residue of print area
438 * end residue of print area
440 * start sequence of print area
442 * end sequence of print area
444 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
445 int startSeq, int endSeq)
447 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
449 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
454 * Draw a wrapped alignment panel for printing
457 * Graphics object to draw with
459 * width of drawing area
460 * @param canvasHeight
461 * height of drawing area
463 * start residue of print area
465 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
466 int canvasHeight, int startRes)
468 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
470 SequenceGroup group = av.getSelectionGroup();
473 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
479 * Returns the visible width of the canvas in residues, after allowing for
480 * East or West scales (if shown)
483 * the width in pixels (possibly including scales)
487 public int getWrappedCanvasWidth(int canvasWidth)
489 int charWidth = av.getCharWidth();
491 FontMetrics fm = getFontMetrics(av.getFont());
495 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
497 labelWidth = getLabelWidth(fm);
500 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
502 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
504 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
508 * Returns a pixel width sufficient to show the largest sequence coordinate
509 * (end position) in the alignment, calculated as the FontMetrics width of
510 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
511 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
512 * half a character width space on either side.
517 protected int getLabelWidth(FontMetrics fm)
520 * find the biggest sequence end position we need to show
521 * (note this is not necessarily the sequence length)
524 AlignmentI alignment = av.getAlignment();
525 for (int i = 0; i < alignment.getHeight(); i++)
527 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
531 for (int i = maxWidth; i > 0; i /= 10)
536 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
540 * Draws as many widths of a wrapped alignment as can fit in the visible
545 * available width in pixels
546 * @param canvasHeight
547 * available height in pixels
549 * the first column (0...) of the alignment to draw
551 public void drawWrappedPanel(Graphics g, int canvasWidth,
552 int canvasHeight, final int startColumn)
554 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
557 av.setWrappedWidth(wrappedWidthInResidues);
559 ViewportRanges ranges = av.getRanges();
560 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
562 // we need to call this again to make sure the startColumn +
563 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
565 calculateWrappedGeometry(canvasWidth, canvasHeight);
568 * draw one width at a time (excluding any scales or annotation shown),
569 * until we have run out of either alignment or vertical space available
571 int ypos = wrappedSpaceAboveAlignment;
572 int maxWidth = ranges.getVisibleAlignmentWidth();
574 int start = startColumn;
575 int currentWidth = 0;
576 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
579 .min(maxWidth, start + wrappedWidthInResidues - 1);
580 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
581 ypos += wrappedRepeatHeightPx;
582 start += wrappedWidthInResidues;
586 drawWrappedDecorators(g, startColumn);
590 * Calculates and saves values needed when rendering a wrapped alignment.
591 * These depend on many factors, including
593 * <li>canvas width and height</li>
594 * <li>number of visible sequences, and height of annotations if shown</li>
595 * <li>font and character width</li>
596 * <li>whether scales are shown left, right or above the alignment</li>
600 * @param canvasHeight
601 * @return the number of residue columns in each width
603 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
605 int charHeight = av.getCharHeight();
608 * vertical space in pixels between wrapped widths of alignment
609 * - one character height, or two if scale above is drawn
611 wrappedSpaceAboveAlignment = charHeight
612 * (av.getScaleAboveWrapped() ? 2 : 1);
615 * height in pixels of the wrapped widths
617 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
619 wrappedRepeatHeightPx += av.getAlignment().getHeight()
621 // add annotations panel height if shown
622 wrappedRepeatHeightPx += getAnnotationHeight();
625 * number of visible widths (the last one may be part height),
626 * ensuring a part height includes at least one sequence
628 ViewportRanges ranges = av.getRanges();
629 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
630 int remainder = canvasHeight % wrappedRepeatHeightPx;
631 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
633 wrappedVisibleWidths++;
637 * compute width in residues; this also sets East and West label widths
639 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
642 * limit visibleWidths to not exceed width of alignment
644 int xMax = ranges.getVisibleAlignmentWidth();
645 int startToEnd = xMax - ranges.getStartRes();
646 int maxWidths = startToEnd / wrappedWidthInResidues;
647 if (startToEnd % wrappedWidthInResidues > 0)
651 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
653 return wrappedWidthInResidues;
657 * Draws one width of a wrapped alignment, including sequences and
658 * annnotations, if shown, but not scales or hidden column markers
664 * @param canvasHeight
666 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
667 int endColumn, int canvasHeight)
669 ViewportRanges ranges = av.getRanges();
670 int viewportWidth = ranges.getViewportWidth();
672 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
675 * move right before drawing by the width of the scale left (if any)
676 * plus column offset from left margin (usually zero, but may be non-zero
677 * when fast painting is drawing just a few columns)
679 int charWidth = av.getCharWidth();
680 int xOffset = labelWidthWest
681 + ((startColumn - ranges.getStartRes()) % viewportWidth)
684 g.translate(xOffset, 0);
687 * white fill the region to be drawn (so incremental fast paint doesn't
688 * scribble over an existing image)
690 g.setColor(Color.white);
691 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
692 wrappedRepeatHeightPx);
694 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
697 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
699 if (av.isShowAnnotation())
701 g.translate(0, cHeight + ypos + 3);
702 if (annotations == null)
704 annotations = new AnnotationPanel(av);
707 annotations.renderer.drawComponent(annotations, av, g, -1,
708 startColumn, endx + 1);
709 g.translate(0, -cHeight - ypos - 3);
711 g.translate(-xOffset, 0);
715 * Draws scales left, right and above (if shown), and any hidden column
716 * markers, on all widths of the wrapped alignment
721 protected void drawWrappedDecorators(Graphics g, final int startColumn)
723 int charWidth = av.getCharWidth();
725 g.setFont(av.getFont());
727 g.setColor(Color.black);
729 int ypos = wrappedSpaceAboveAlignment;
730 ViewportRanges ranges = av.getRanges();
731 int viewportWidth = ranges.getViewportWidth();
732 int maxWidth = ranges.getVisibleAlignmentWidth();
734 int startCol = startColumn;
736 while (widthsDrawn < wrappedVisibleWidths)
738 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
740 if (av.getScaleLeftWrapped())
742 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
745 if (av.getScaleRightWrapped())
747 int x = labelWidthWest + viewportWidth * charWidth;
750 drawVerticalScale(g, startCol, endColumn, ypos, false);
755 * white fill region of scale above and hidden column markers
756 * (to support incremental fast paint of image)
758 g.translate(labelWidthWest, 0);
759 g.setColor(Color.white);
760 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
761 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
762 g.setColor(Color.black);
763 g.translate(-labelWidthWest, 0);
765 g.translate(labelWidthWest, 0);
767 if (av.getScaleAboveWrapped())
769 drawNorthScale(g, startCol, endColumn, ypos);
772 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
774 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
777 g.translate(-labelWidthWest, 0);
779 ypos += wrappedRepeatHeightPx;
780 startCol += viewportWidth;
786 * Draws markers (triangles) above hidden column positions between startColumn
794 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
795 int startColumn, int endColumn)
797 int charHeight = av.getCharHeight();
798 int charWidth = av.getCharWidth();
800 g.setColor(Color.blue);
802 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
804 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
808 res = it.next() - startColumn;
810 if (res < 0 || res > endColumn - startColumn + 1)
816 * draw a downward-pointing triangle at the hidden columns location
817 * (before the following visible column)
819 int xMiddle = res * charWidth;
820 int[] xPoints = new int[] { xMiddle - charHeight / 4,
821 xMiddle + charHeight / 4, xMiddle };
822 int yTop = ypos - (charHeight / 2);
823 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
824 g.fillPolygon(xPoints, yPoints, 3);
829 * Draw a selection group over a wrapped alignment
831 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
833 int canvasHeight, int startRes)
835 int charHeight = av.getCharHeight();
836 int charWidth = av.getCharWidth();
838 // height gap above each panel
839 int hgap = charHeight;
840 if (av.getScaleAboveWrapped())
845 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
847 int cHeight = av.getAlignment().getHeight() * charHeight;
849 int startx = startRes;
851 int ypos = hgap; // vertical offset
852 int maxwidth = av.getAlignment().getWidth();
854 if (av.hasHiddenColumns())
856 maxwidth = av.getAlignment().getHiddenColumns()
857 .absoluteToVisibleColumn(maxwidth);
860 // chop the wrapped alignment extent up into panel-sized blocks and treat
861 // each block as if it were a block from an unwrapped alignment
862 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
863 BasicStroke.JOIN_ROUND, 3f, new float[]
865 g.setColor(Color.RED);
866 while ((ypos <= canvasHeight) && (startx < maxwidth))
868 // set end value to be start + width, or maxwidth, whichever is smaller
869 endx = startx + cWidth - 1;
876 g.translate(labelWidthWest, 0);
878 drawUnwrappedSelection(g, group, startx, endx, 0,
879 av.getAlignment().getHeight() - 1,
882 g.translate(-labelWidthWest, 0);
884 // update vertical offset
885 ypos += cHeight + getAnnotationHeight() + hgap;
887 // update horizontal offset
890 g.setStroke(new BasicStroke());
893 int getAnnotationHeight()
895 if (!av.isShowAnnotation())
900 if (annotations == null)
902 annotations = new AnnotationPanel(av);
905 return annotations.adjustPanelHeight();
909 * Draws the visible region of the alignment on the graphics context. If there
910 * are hidden column markers in the visible region, then each sub-region
911 * between the markers is drawn separately, followed by the hidden column
915 * the graphics context, positioned at the first residue to be drawn
917 * offset of the first column to draw (0..)
919 * offset of the last column to draw (0..)
921 * offset of the first sequence to draw (0..)
923 * offset of the last sequence to draw (0..)
925 * vertical offset at which to draw (for wrapped alignments)
927 public void drawPanel(Graphics g1, final int startRes, final int endRes,
928 final int startSeq, final int endSeq, final int yOffset)
930 int charHeight = av.getCharHeight();
931 int charWidth = av.getCharWidth();
933 if (!av.hasHiddenColumns())
935 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
943 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
944 VisibleContigsIterator regions = hidden
945 .getVisContigsIterator(startRes, endRes + 1, true);
947 while (regions.hasNext())
949 int[] region = regions.next();
950 blockEnd = region[1];
951 blockStart = region[0];
954 * draw up to just before the next hidden region, or the end of
955 * the visible region, whichever comes first
957 g1.translate(screenY * charWidth, 0);
959 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
962 * draw the downline of the hidden column marker (ScalePanel draws the
963 * triangle on top) if we reached it
965 if (av.getShowHiddenMarkers()
966 && (regions.hasNext() || regions.endsAtHidden()))
968 g1.setColor(Color.blue);
970 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
971 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
972 (endSeq - startSeq + 1) * charHeight + yOffset);
975 g1.translate(-screenY * charWidth, 0);
976 screenY += blockEnd - blockStart + 1;
983 * Draws a region of the visible alignment
987 * offset of the first column in the visible region (0..)
989 * offset of the last column in the visible region (0..)
991 * offset of the first sequence in the visible region (0..)
993 * offset of the last sequence in the visible region (0..)
995 * vertical offset at which to draw (for wrapped alignments)
997 private void draw(Graphics g, int startRes, int endRes, int startSeq,
998 int endSeq, int offset)
1000 int charHeight = av.getCharHeight();
1001 int charWidth = av.getCharWidth();
1003 g.setFont(av.getFont());
1004 seqRdr.prepare(g, av.isRenderGaps());
1008 // / First draw the sequences
1009 // ///////////////////////////
1010 for (int i = startSeq; i <= endSeq; i++)
1012 nextSeq = av.getAlignment().getSequenceAt(i);
1013 if (nextSeq == null)
1015 // occasionally, a race condition occurs such that the alignment row is
1019 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1020 startRes, endRes, offset + ((i - startSeq) * charHeight));
1022 if (av.isShowSequenceFeatures())
1024 fr.drawSequence(g, nextSeq, startRes, endRes,
1025 offset + ((i - startSeq) * charHeight), false);
1029 * highlight search Results once sequence has been drawn
1031 if (av.hasSearchResults())
1033 SearchResultsI searchResults = av.getSearchResults();
1034 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1036 if (visibleResults != null)
1038 for (int r = 0; r < visibleResults.length; r += 2)
1040 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1041 visibleResults[r + 1],
1042 (visibleResults[r] - startRes) * charWidth,
1043 offset + ((i - startSeq) * charHeight));
1049 if (av.getSelectionGroup() != null
1050 || av.getAlignment().getGroups().size() > 0)
1052 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1058 * Draws the outlines of any groups defined on the alignment (excluding the
1059 * current selection group, if any)
1068 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1069 int startSeq, int endSeq, int offset)
1071 Graphics2D g = (Graphics2D) g1;
1073 SequenceGroup group = null;
1074 int groupIndex = -1;
1076 if (av.getAlignment().getGroups().size() > 0)
1078 group = av.getAlignment().getGroups().get(0);
1086 g.setColor(group.getOutlineColour());
1087 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1091 if (groupIndex >= av.getAlignment().getGroups().size())
1095 group = av.getAlignment().getGroups().get(groupIndex);
1096 } while (groupIndex < av.getAlignment().getGroups().size());
1101 * Draws the outline of the current selection group (if any)
1109 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1110 int startSeq, int endSeq)
1112 SequenceGroup group = av.getSelectionGroup();
1118 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1119 BasicStroke.JOIN_ROUND, 3f, new float[]
1121 g.setColor(Color.RED);
1122 if (!av.getWrapAlignment())
1124 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1129 drawWrappedSelection(g, group, getWidth(), getHeight(),
1130 av.getRanges().getStartRes());
1132 g.setStroke(new BasicStroke());
1136 * Draw the cursor as a separate image and overlay
1139 * start residue of area to draw cursor in
1141 * end residue of area to draw cursor in
1143 * start sequence of area to draw cursor in
1145 * end sequence of are to draw cursor in
1146 * @return a transparent image of the same size as the sequence canvas, with
1147 * the cursor drawn on it, if any
1149 private void drawCursor(Graphics g, int startRes, int endRes,
1153 // convert the cursorY into a position on the visible alignment
1154 int cursor_ypos = cursorY;
1156 // don't do work unless we have to
1157 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1161 int startx = startRes;
1164 // convert the cursorX into a position on the visible alignment
1165 int cursor_xpos = av.getAlignment().getHiddenColumns()
1166 .absoluteToVisibleColumn(cursorX);
1168 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1171 if (av.getWrapAlignment())
1173 // work out the correct offsets for the cursor
1174 int charHeight = av.getCharHeight();
1175 int charWidth = av.getCharWidth();
1176 int canvasWidth = getWidth();
1177 int canvasHeight = getHeight();
1179 // height gap above each panel
1180 int hgap = charHeight;
1181 if (av.getScaleAboveWrapped())
1186 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1188 int cHeight = av.getAlignment().getHeight() * charHeight;
1190 endx = startx + cWidth - 1;
1191 int ypos = hgap; // vertical offset
1193 // iterate down the wrapped panels
1194 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1196 // update vertical offset
1197 ypos += cHeight + getAnnotationHeight() + hgap;
1199 // update horizontal offset
1201 endx = startx + cWidth - 1;
1204 xoffset = labelWidthWest;
1207 // now check if cursor is within range for x values
1208 if (cursor_xpos >= startx && cursor_xpos <= endx)
1210 // get the character the cursor is drawn at
1211 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1212 char s = seq.getCharAt(cursorX);
1214 seqRdr.drawCursor(g, s,
1215 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1216 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1224 * Draw a selection group over an unwrapped alignment
1227 * graphics object to draw with
1231 * start residue of area to draw
1233 * end residue of area to draw
1235 * start sequence of area to draw
1237 * end sequence of area to draw
1239 * vertical offset (used when called from wrapped alignment code)
1241 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1242 int startRes, int endRes, int startSeq, int endSeq, int offset)
1244 int charWidth = av.getCharWidth();
1246 if (!av.hasHiddenColumns())
1248 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1253 // package into blocks of visible columns
1258 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1259 VisibleContigsIterator regions = hidden
1260 .getVisContigsIterator(startRes, endRes + 1, true);
1261 while (regions.hasNext())
1263 int[] region = regions.next();
1264 blockEnd = region[1];
1265 blockStart = region[0];
1267 g.translate(screenY * charWidth, 0);
1268 drawPartialGroupOutline(g, group,
1269 blockStart, blockEnd, startSeq, endSeq, offset);
1271 g.translate(-screenY * charWidth, 0);
1272 screenY += blockEnd - blockStart + 1;
1278 * Draws part of a selection group outline
1286 * @param verticalOffset
1288 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1289 int startRes, int endRes, int startSeq, int endSeq,
1292 int charHeight = av.getCharHeight();
1293 int charWidth = av.getCharWidth();
1294 int visWidth = (endRes - startRes + 1) * charWidth;
1298 boolean inGroup = false;
1303 List<SequenceI> seqs = group.getSequences(null);
1305 // position of start residue of group relative to startRes, in pixels
1306 int sx = (group.getStartRes() - startRes) * charWidth;
1308 // width of group in pixels
1309 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1312 if (!(sx + xwidth < 0 || sx > visWidth))
1314 for (i = startSeq; i <= endSeq; i++)
1316 sy = verticalOffset + (i - startSeq) * charHeight;
1318 if ((sx <= (endRes - startRes) * charWidth)
1319 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1322 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1324 bottom = sy + charHeight;
1329 if (((top == -1) && (i == 0)) || !seqs
1330 .contains(av.getAlignment().getSequenceAt(i - 1)))
1341 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1342 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1344 // reset top and bottom
1352 sy = verticalOffset + ((i - startSeq) * charHeight);
1353 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1354 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1360 * Draw horizontal selection group boundaries at top and bottom positions
1363 * graphics object to draw on
1369 * visWidth maximum available width
1371 * position to draw top of group at
1373 * position to draw bottom of group at
1375 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1376 int visWidth, int top, int bottom)
1386 // don't let width extend beyond current block, or group extent
1388 if (startx + width >= visWidth)
1390 width = visWidth - startx;
1395 g.drawLine(startx, top, startx + width, top);
1400 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1405 * Draw vertical lines at sx and sx+xwidth providing they lie within
1409 * graphics object to draw on
1415 * visWidth maximum available width
1421 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1424 // if start position is visible, draw vertical line to left of
1426 if (sx >= 0 && sx < visWidth)
1428 g.drawLine(sx, oldY, sx, sy);
1431 // if end position is visible, draw vertical line to right of
1433 if (sx + xwidth < visWidth)
1435 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1440 * Highlights search results in the visible region by rendering as white text
1441 * on a black background. Any previous highlighting is removed. Answers true
1442 * if any highlight was left on the visible alignment (so status bar should be
1443 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1444 * so allows the next repaint to update the whole display.
1449 public boolean highlightSearchResults(SearchResultsI results)
1451 return highlightSearchResults(results, false);
1456 * Highlights search results in the visible region by rendering as white text
1457 * on a black background. Any previous highlighting is removed. Answers true
1458 * if any highlight was left on the visible alignment (so status bar should be
1459 * set to match), else false.
1461 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1462 * highlighted regions are modified. This speeds up highlighting across linked
1465 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1466 * a wrapped alignment had to be scrolled to show the highlighted region, then
1467 * it should be fully redrawn, otherwise a fast paint can be performed. This
1468 * argument could be removed if fast paint of scrolled wrapped alignment is
1469 * coded in future (JAL-2609).
1472 * @param doFastPaint
1473 * if true, sets a flag so the next repaint only redraws the modified
1477 public boolean highlightSearchResults(SearchResultsI results,
1478 boolean doFastPaint)
1484 boolean wrapped = av.getWrapAlignment();
1487 fastPaint = doFastPaint;
1488 fastpainting = fastPaint;
1491 * to avoid redrawing the whole visible region, we instead
1492 * redraw just the minimal regions to remove previous highlights
1495 SearchResultsI previous = av.getSearchResults();
1496 av.setSearchResults(results);
1497 boolean redrawn = false;
1498 boolean drawn = false;
1501 redrawn = drawMappedPositionsWrapped(previous);
1502 drawn = drawMappedPositionsWrapped(results);
1507 redrawn = drawMappedPositions(previous);
1508 drawn = drawMappedPositions(results);
1513 * if highlights were either removed or added, repaint
1521 * return true only if highlights were added
1527 fastpainting = false;
1532 * Redraws the minimal rectangle in the visible region (if any) that includes
1533 * mapped positions of the given search results. Whether or not positions are
1534 * highlighted depends on the SearchResults set on the Viewport. This allows
1535 * this method to be called to either clear or set highlighting. Answers true
1536 * if any positions were drawn (in which case a repaint is still required),
1542 protected boolean drawMappedPositions(SearchResultsI results)
1544 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1550 * calculate the minimal rectangle to redraw that
1551 * includes both new and existing search results
1553 int firstSeq = Integer.MAX_VALUE;
1555 int firstCol = Integer.MAX_VALUE;
1557 boolean matchFound = false;
1559 ViewportRanges ranges = av.getRanges();
1560 int firstVisibleColumn = ranges.getStartRes();
1561 int lastVisibleColumn = ranges.getEndRes();
1562 AlignmentI alignment = av.getAlignment();
1563 if (av.hasHiddenColumns())
1565 firstVisibleColumn = alignment.getHiddenColumns()
1566 .visibleToAbsoluteColumn(firstVisibleColumn);
1567 lastVisibleColumn = alignment.getHiddenColumns()
1568 .visibleToAbsoluteColumn(lastVisibleColumn);
1571 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1572 .getEndSeq(); seqNo++)
1574 SequenceI seq = alignment.getSequenceAt(seqNo);
1576 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1578 if (visibleResults != null)
1580 for (int i = 0; i < visibleResults.length - 1; i += 2)
1582 int firstMatchedColumn = visibleResults[i];
1583 int lastMatchedColumn = visibleResults[i + 1];
1584 if (firstMatchedColumn <= lastVisibleColumn
1585 && lastMatchedColumn >= firstVisibleColumn)
1588 * found a search results match in the visible region -
1589 * remember the first and last sequence matched, and the first
1590 * and last visible columns in the matched positions
1593 firstSeq = Math.min(firstSeq, seqNo);
1594 lastSeq = Math.max(lastSeq, seqNo);
1595 firstMatchedColumn = Math.max(firstMatchedColumn,
1596 firstVisibleColumn);
1597 lastMatchedColumn = Math.min(lastMatchedColumn,
1599 firstCol = Math.min(firstCol, firstMatchedColumn);
1600 lastCol = Math.max(lastCol, lastMatchedColumn);
1608 if (av.hasHiddenColumns())
1610 firstCol = alignment.getHiddenColumns()
1611 .absoluteToVisibleColumn(firstCol);
1612 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1614 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1615 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1616 Graphics gg = img.getGraphics();
1617 gg.translate(transX, transY);
1618 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1619 gg.translate(-transX, -transY);
1627 public void propertyChange(PropertyChangeEvent evt)
1629 String eventName = evt.getPropertyName();
1631 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1637 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1645 if (eventName.equals(ViewportRanges.STARTRES)
1646 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1648 // Make sure we're not trying to draw a panel
1649 // larger than the visible window
1650 if (eventName.equals(ViewportRanges.STARTRES))
1652 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1656 scrollX = ((int[]) evt.getNewValue())[0]
1657 - ((int[]) evt.getOldValue())[0];
1659 ViewportRanges vpRanges = av.getRanges();
1661 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1662 if (scrollX > range)
1666 else if (scrollX < -range)
1671 // Both scrolling and resizing change viewport ranges: scrolling changes
1672 // both start and end points, but resize only changes end values.
1673 // Here we only want to fastpaint on a scroll, with resize using a normal
1674 // paint, so scroll events are identified as changes to the horizontal or
1675 // vertical start value.
1676 if (eventName.equals(ViewportRanges.STARTRES))
1678 if (av.getWrapAlignment())
1680 fastPaintWrapped(scrollX);
1684 fastPaint(scrollX, 0);
1687 else if (eventName.equals(ViewportRanges.STARTSEQ))
1690 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1692 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1694 if (av.getWrapAlignment())
1696 fastPaintWrapped(scrollX);
1700 fastPaint(scrollX, 0);
1703 else if (eventName.equals(ViewportRanges.STARTSEQ))
1706 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1708 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1710 if (av.getWrapAlignment())
1712 fastPaintWrapped(scrollX);
1718 * Does a minimal update of the image for a scroll movement. This method
1719 * handles scroll movements of up to one width of the wrapped alignment (one
1720 * click in the vertical scrollbar). Larger movements (for example after a
1721 * scroll to highlight a mapped position) trigger a full redraw instead.
1724 * number of positions scrolled (right if positive, left if negative)
1726 protected void fastPaintWrapped(int scrollX)
1728 ViewportRanges ranges = av.getRanges();
1730 if (Math.abs(scrollX) > ranges.getViewportWidth())
1733 * shift of more than one view width is
1734 * overcomplicated to handle in this method
1741 if (fastpainting || img == null)
1747 fastpainting = true;
1752 Graphics gg = img.getGraphics();
1754 calculateWrappedGeometry(getWidth(), getHeight());
1757 * relocate the regions of the alignment that are still visible
1759 shiftWrappedAlignment(-scrollX);
1762 * add new columns (sequence, annotation)
1763 * - at top left if scrollX < 0
1764 * - at right of last two widths if scrollX > 0
1768 int startRes = ranges.getStartRes();
1769 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1770 - scrollX - 1, getHeight());
1774 fastPaintWrappedAddRight(scrollX);
1778 * draw all scales (if shown) and hidden column markers
1780 drawWrappedDecorators(gg, ranges.getStartRes());
1787 fastpainting = false;
1792 * Draws the specified number of columns at the 'end' (bottom right) of a
1793 * wrapped alignment view, including sequences and annotations if shown, but
1794 * not scales. Also draws the same number of columns at the right hand end of
1795 * the second last width shown, if the last width is not full height (so
1796 * cannot simply be copied from the graphics image).
1800 protected void fastPaintWrappedAddRight(int columns)
1807 Graphics gg = img.getGraphics();
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
1837 gg.translate(xOffset, 0);
1838 gg.setColor(Color.white);
1839 gg.fillRect(labelWidthWest, ypos,
1840 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1841 gg.translate(-xOffset, 0);
1843 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1848 * draw newly visible columns in last wrapped width (none if we
1849 * have reached the end of the alignment)
1850 * y-offset for drawing last width is height of widths above,
1853 int widthsAbove = visibleWidths - 1;
1854 int ypos = wrappedRepeatHeightPx * widthsAbove
1855 + wrappedSpaceAboveAlignment;
1856 int endRes = ranges.getEndRes();
1857 endRes += widthsAbove * viewportWidth;
1858 int startRes = endRes - columns + 1;
1861 * white fill first to erase annotations
1863 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1865 gg.translate(xOffset, 0);
1866 gg.setColor(Color.white);
1867 int width = viewportWidth * charWidth - xOffset;
1868 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1869 gg.translate(-xOffset, 0);
1871 gg.setFont(av.getFont());
1872 gg.setColor(Color.black);
1874 if (startRes < ranges.getVisibleAlignmentWidth())
1876 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1880 * and finally, white fill any space below the visible alignment
1882 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1883 if (heightBelow > 0)
1885 gg.setColor(Color.white);
1886 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1892 * Shifts the visible alignment by the specified number of columns - left if
1893 * negative, right if positive. Copies and moves sequences and annotations (if
1894 * shown). Scales, hidden column markers and any newly visible columns must be
1899 protected void shiftWrappedAlignment(int positions)
1906 Graphics gg = img.getGraphics();
1908 int charWidth = av.getCharWidth();
1910 int canvasHeight = getHeight();
1911 ViewportRanges ranges = av.getRanges();
1912 int viewportWidth = ranges.getViewportWidth();
1913 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1915 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1916 int xMax = ranges.getVisibleAlignmentWidth();
1921 * shift right (after scroll left)
1922 * for each wrapped width (starting with the last), copy (width-positions)
1923 * columns from the left margin to the right margin, and copy positions
1924 * columns from the right margin of the row above (if any) to the
1925 * left margin of the current row
1929 * get y-offset of last wrapped width, first row of sequences
1931 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1932 y += wrappedSpaceAboveAlignment;
1933 int copyFromLeftStart = labelWidthWest;
1934 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1938 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1939 positions * charWidth, 0);
1942 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1943 positions * charWidth, heightToCopy, -widthToCopy,
1944 wrappedRepeatHeightPx);
1947 y -= wrappedRepeatHeightPx;
1953 * shift left (after scroll right)
1954 * for each wrapped width (starting with the first), copy (width-positions)
1955 * columns from the right margin to the left margin, and copy positions
1956 * columns from the left margin of the row below (if any) to the
1957 * right margin of the current row
1959 int xpos = av.getRanges().getStartRes();
1960 int y = wrappedSpaceAboveAlignment;
1961 int copyFromRightStart = labelWidthWest - positions * charWidth;
1963 while (y < canvasHeight)
1965 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1966 positions * charWidth, 0);
1967 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1968 && (xpos + viewportWidth <= xMax))
1970 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1971 * charWidth, heightToCopy, widthToCopy,
1972 -wrappedRepeatHeightPx);
1974 y += wrappedRepeatHeightPx;
1975 xpos += viewportWidth;
1983 * Redraws any positions in the search results in the visible region of a
1984 * wrapped alignment. Any highlights are drawn depending on the search results
1985 * set on the Viewport, not the <code>results</code> argument. This allows
1986 * this method to be called either to clear highlights (passing the previous
1987 * search results), or to draw new highlights.
1992 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1994 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1998 int charHeight = av.getCharHeight();
2000 boolean matchFound = false;
2002 calculateWrappedGeometry(getWidth(), getHeight());
2003 int wrappedWidth = av.getWrappedWidth();
2004 int wrappedHeight = wrappedRepeatHeightPx;
2006 ViewportRanges ranges = av.getRanges();
2007 int canvasHeight = getHeight();
2008 int repeats = canvasHeight / wrappedHeight;
2009 if (canvasHeight / wrappedHeight > 0)
2014 int firstVisibleColumn = ranges.getStartRes();
2015 int lastVisibleColumn = ranges.getStartRes() + repeats
2016 * ranges.getViewportWidth() - 1;
2018 AlignmentI alignment = av.getAlignment();
2019 if (av.hasHiddenColumns())
2021 firstVisibleColumn = alignment.getHiddenColumns()
2022 .visibleToAbsoluteColumn(firstVisibleColumn);
2023 lastVisibleColumn = alignment.getHiddenColumns()
2024 .visibleToAbsoluteColumn(lastVisibleColumn);
2027 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2030 Graphics gg = img.getGraphics();
2032 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2033 .getEndSeq(); seqNo++)
2035 SequenceI seq = alignment.getSequenceAt(seqNo);
2037 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2039 if (visibleResults != null)
2041 for (int i = 0; i < visibleResults.length - 1; i += 2)
2043 int firstMatchedColumn = visibleResults[i];
2044 int lastMatchedColumn = visibleResults[i + 1];
2045 if (firstMatchedColumn <= lastVisibleColumn
2046 && lastMatchedColumn >= firstVisibleColumn)
2049 * found a search results match in the visible region
2051 firstMatchedColumn = Math.max(firstMatchedColumn,
2052 firstVisibleColumn);
2053 lastMatchedColumn = Math.min(lastMatchedColumn,
2057 * draw each mapped position separately (as contiguous positions may
2058 * wrap across lines)
2060 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2062 int displayColumn = mappedPos;
2063 if (av.hasHiddenColumns())
2065 displayColumn = alignment.getHiddenColumns()
2066 .absoluteToVisibleColumn(displayColumn);
2070 * transX: offset from left edge of canvas to residue position
2072 int transX = labelWidthWest
2073 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2074 * av.getCharWidth();
2077 * transY: offset from top edge of canvas to residue position
2079 int transY = gapHeight;
2080 transY += (displayColumn - ranges.getStartRes())
2081 / wrappedWidth * wrappedHeight;
2082 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2085 * yOffset is from graphics origin to start of visible region
2087 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2088 if (transY < getHeight())
2091 gg.translate(transX, transY);
2092 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2094 gg.translate(-transX, -transY);
2108 * Answers the width in pixels of the left scale labels (0 if not shown)
2112 int getLabelWidthWest()
2114 return labelWidthWest;