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
58 * pixels gap between sequences and annotations when in wrapped mode
60 static final int SEQS_ANNOTATION_GAP = 3;
62 private static final String ZEROS = "0000000000";
64 final FeatureRenderer fr;
74 private final SequenceRenderer seqRdr;
76 private boolean fastPaint = false;
78 private boolean fastpainting = false;
80 private AnnotationPanel annotations;
83 * measurements for drawing a wrapped alignment
85 private int labelWidthEast; // label right width in pixels if shown
87 private int labelWidthWest; // label left width in pixels if shown
89 int wrappedSpaceAboveAlignment; // gap between widths
91 int wrappedRepeatHeightPx; // height in pixels of wrapped width
93 private int wrappedVisibleWidths; // number of wrapped widths displayed
95 // Don't do this! Graphics handles are supposed to be transient
96 //private Graphics2D gg;
99 * Creates a new SeqCanvas object.
103 public SeqCanvas(AlignmentPanel ap)
106 fr = new FeatureRenderer(ap);
107 seqRdr = new SequenceRenderer(av);
108 setLayout(new BorderLayout());
109 PaintRefresher.Register(this, av.getSequenceSetId());
110 setBackground(Color.white);
112 av.getRanges().addPropertyChangeListener(this);
115 public SequenceRenderer getSequenceRenderer()
120 public FeatureRenderer getFeatureRenderer()
126 * Draws the scale above a region of a wrapped alignment, consisting of a
127 * column number every major interval (10 columns).
130 * the graphics context to draw on, positioned at the start (bottom
131 * left) of the line on which to draw any scale marks
133 * start alignment column (0..)
135 * end alignment column (0..)
137 * y offset to draw at
139 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
141 int charHeight = av.getCharHeight();
142 int charWidth = av.getCharWidth();
145 * white fill the scale space (for the fastPaint case)
147 g.setColor(Color.white);
148 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
149 charHeight * 3 / 2 + 2);
150 g.setColor(Color.black);
152 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
154 for (ScaleMark mark : marks)
156 int mpos = mark.column; // (i - startx - 1)
161 String mstring = mark.text;
167 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
171 * draw a tick mark below the column number, centred on the column;
172 * height of tick mark is 4 pixels less than half a character
174 int xpos = (mpos * charWidth) + (charWidth / 2);
175 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
181 * Draw the scale to the left or right of a wrapped alignment
184 * graphics context, positioned at the start of the scale to be drawn
186 * first column of wrapped width (0.. excluding any hidden columns)
188 * last column of wrapped width (0.. excluding any hidden columns)
190 * vertical offset at which to begin the scale
192 * if true, scale is left of residues, if false, scale is right
194 void drawVerticalScale(Graphics g, final int startx, final int endx,
195 final int ypos, final boolean left)
197 int charHeight = av.getCharHeight();
198 int charWidth = av.getCharWidth();
200 int yPos = ypos + charHeight;
204 if (av.hasHiddenColumns())
206 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
207 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
208 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
210 FontMetrics fm = getFontMetrics(av.getFont());
212 for (int i = 0; i < av.getAlignment().getHeight(); i++)
214 SequenceI seq = av.getAlignment().getSequenceAt(i);
217 * find sequence position of first non-gapped position -
218 * to the right if scale left, to the left if scale right
220 int index = left ? startX : endX;
222 while (index >= startX && index <= endX)
224 if (!Comparison.isGap(seq.getCharAt(index)))
226 value = seq.findPosition(index);
241 * white fill the space for the scale
243 g.setColor(Color.white);
244 int y = (yPos + (i * charHeight)) - (charHeight / 5);
245 // fillRect origin is top left of rectangle
246 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
252 * draw scale value, right justified within its width less half a
253 * character width padding on the right
255 int labelSpace = left ? labelWidthWest : labelWidthEast;
256 labelSpace -= charWidth / 2; // leave space to the right
257 String valueAsString = String.valueOf(value);
258 int labelLength = fm.stringWidth(valueAsString);
259 int xOffset = labelSpace - labelLength;
260 g.setColor(Color.black);
261 g.drawString(valueAsString, xOffset, y);
268 * Does a fast paint of an alignment in response to a scroll. Most of the
269 * visible region is simply copied and shifted, and then any newly visible
270 * columns or rows are drawn. The scroll may be horizontal or vertical, but
271 * not both at once. Scrolling may be the result of
273 * <li>dragging a scroll bar</li>
274 * <li>clicking in the scroll bar</li>
275 * <li>scrolling by trackpad, middle mouse button, or other device</li>
276 * <li>by moving the box in the Overview window</li>
277 * <li>programmatically to make a highlighted position visible</li>
281 * columns to shift right (positive) or left (negative)
283 * rows to shift down (positive) or up (negative)
285 public void fastPaint(int horizontal, int vertical)
287 if (fastpainting || img == null)
296 int charHeight = av.getCharHeight();
297 int charWidth = av.getCharWidth();
299 ViewportRanges ranges = av.getRanges();
300 int startRes = ranges.getStartRes();
301 int endRes = ranges.getEndRes();
302 int startSeq = ranges.getStartSeq();
303 int endSeq = ranges.getEndSeq();
307 Graphics gg = img.getGraphics();
308 gg.copyArea(horizontal * charWidth, vertical * charHeight,
309 img.getWidth(), img.getHeight(), -horizontal * charWidth,
310 -vertical * charHeight);
312 if (horizontal > 0) // scrollbar pulled right, image to the left
314 transX = (endRes - startRes - horizontal) * charWidth;
315 startRes = endRes - horizontal;
317 else if (horizontal < 0)
319 endRes = startRes - horizontal;
322 if (vertical > 0) // scroll down
324 startSeq = endSeq - vertical;
326 if (startSeq < ranges.getStartSeq())
327 { // ie scrolling too fast, more than a page at a time
328 startSeq = ranges.getStartSeq();
332 transY = img.getHeight() - ((vertical + 1) * charHeight);
335 else if (vertical < 0)
337 endSeq = startSeq - vertical;
339 if (endSeq > ranges.getEndSeq())
341 endSeq = ranges.getEndSeq();
345 gg.translate(transX, transY);
346 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
347 gg.translate(-transX, -transY);
350 // Call repaint on alignment panel so that repaints from other alignment
351 // panel components can be aggregated. Otherwise performance of the
352 // overview window and others may be adversely affected.
353 av.getAlignPanel().repaint();
356 fastpainting = false;
361 public void paintComponent(Graphics g)
363 //super.paintComponent(g); // BH 2019
365 int charHeight = av.getCharHeight();
366 int charWidth = av.getCharWidth();
368 ViewportRanges ranges = av.getRanges();
370 int width = getWidth();
371 int height = getHeight();
373 width -= (width % charWidth);
374 height -= (height % charHeight);
377 if ((img != null) && (fastPaint
378 || (getVisibleRect().width != g.getClipBounds().width)
379 || (getVisibleRect().height != g.getClipBounds().height)))
381 g.drawImage(img, 0, 0, this);
383 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
384 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
388 else if (width > 0 && height > 0)
391 * img is a cached version of the last view we drew, if any
392 * if we have no img or the size has changed, make a new one
394 if (img == null || width != img.getWidth()
395 || height != img.getHeight())
397 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
400 Graphics2D gg = (Graphics2D) img.getGraphics();
401 gg.setFont(av.getFont());
405 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
406 RenderingHints.VALUE_ANTIALIAS_ON);
409 gg.setColor(Color.white);
410 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
412 if (av.getWrapAlignment())
414 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
418 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
419 ranges.getStartSeq(), ranges.getEndSeq(), 0);
422 drawSelectionGroup(gg, ranges.getStartRes(),
423 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
425 g.drawImage(img, 0, 0, this);
431 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
432 ranges.getStartSeq(), ranges.getEndSeq());
437 * Draw an alignment panel for printing
440 * Graphics object to draw with
442 * start residue of print area
444 * end residue of print area
446 * start sequence of print area
448 * end sequence of print area
450 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
451 int startSeq, int endSeq)
453 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
455 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
460 * Draw a wrapped alignment panel for printing
463 * Graphics object to draw with
465 * width of drawing area
466 * @param canvasHeight
467 * height of drawing area
469 * start residue of print area
471 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
472 int canvasHeight, int startRes)
474 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
476 SequenceGroup group = av.getSelectionGroup();
479 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
485 * Returns the visible width of the canvas in residues, after allowing for
486 * East or West scales (if shown)
489 * the width in pixels (possibly including scales)
493 public int getWrappedCanvasWidth(int canvasWidth)
495 int charWidth = av.getCharWidth();
497 FontMetrics fm = getFontMetrics(av.getFont());
501 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
503 labelWidth = getLabelWidth(fm);
506 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
508 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
510 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
514 * Returns a pixel width sufficient to show the largest sequence coordinate
515 * (end position) in the alignment, calculated as the FontMetrics width of
516 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
517 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
518 * half a character width space on either side.
523 protected int getLabelWidth(FontMetrics fm)
526 * find the biggest sequence end position we need to show
527 * (note this is not necessarily the sequence length)
530 AlignmentI alignment = av.getAlignment();
531 for (int i = 0; i < alignment.getHeight(); i++)
533 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
537 for (int i = maxWidth; i > 0; i /= 10)
542 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
546 * Draws as many widths of a wrapped alignment as can fit in the visible
551 * available width in pixels
552 * @param canvasHeight
553 * available height in pixels
555 * the first column (0...) of the alignment to draw
557 public void drawWrappedPanel(Graphics g, int canvasWidth,
558 int canvasHeight, final int startColumn)
560 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
563 av.setWrappedWidth(wrappedWidthInResidues);
565 ViewportRanges ranges = av.getRanges();
566 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
568 // we need to call this again to make sure the startColumn +
569 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
571 calculateWrappedGeometry(canvasWidth, canvasHeight);
574 * draw one width at a time (excluding any scales shown),
575 * until we have run out of either alignment or vertical space available
577 int ypos = wrappedSpaceAboveAlignment;
578 int maxWidth = ranges.getVisibleAlignmentWidth();
580 int start = startColumn;
581 int currentWidth = 0;
582 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
585 .min(maxWidth, start + wrappedWidthInResidues - 1);
586 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
587 ypos += wrappedRepeatHeightPx;
588 start += wrappedWidthInResidues;
592 drawWrappedDecorators(g, startColumn);
596 * Calculates and saves values needed when rendering a wrapped alignment.
597 * These depend on many factors, including
599 * <li>canvas width and height</li>
600 * <li>number of visible sequences, and height of annotations if shown</li>
601 * <li>font and character width</li>
602 * <li>whether scales are shown left, right or above the alignment</li>
606 * @param canvasHeight
607 * @return the number of residue columns in each width
609 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
611 int charHeight = av.getCharHeight();
614 * vertical space in pixels between wrapped widths of alignment
615 * - one character height, or two if scale above is drawn
617 wrappedSpaceAboveAlignment = charHeight
618 * (av.getScaleAboveWrapped() ? 2 : 1);
621 * compute height in pixels of the wrapped widths
622 * - start with space above plus sequences
624 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
625 wrappedRepeatHeightPx += av.getAlignment().getHeight()
629 * add annotations panel height if shown
630 * also gap between sequences and annotations
632 if (av.isShowAnnotation())
634 wrappedRepeatHeightPx += getAnnotationHeight();
635 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
639 * number of visible widths (the last one may be part height),
640 * ensuring a part height includes at least one sequence
642 ViewportRanges ranges = av.getRanges();
643 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
644 int remainder = canvasHeight % wrappedRepeatHeightPx;
645 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
647 wrappedVisibleWidths++;
651 * compute width in residues; this also sets East and West label widths
653 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
656 * limit visibleWidths to not exceed width of alignment
658 int xMax = ranges.getVisibleAlignmentWidth();
659 int startToEnd = xMax - ranges.getStartRes();
660 int maxWidths = startToEnd / wrappedWidthInResidues;
661 if (startToEnd % wrappedWidthInResidues > 0)
665 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
667 return wrappedWidthInResidues;
671 * Draws one width of a wrapped alignment, including sequences and
672 * annnotations, if shown, but not scales or hidden column markers
678 * @param canvasHeight
680 protected void drawWrappedWidth(Graphics g, final int ypos,
681 final int startColumn, final int endColumn,
682 final int canvasHeight)
684 ViewportRanges ranges = av.getRanges();
685 int viewportWidth = ranges.getViewportWidth();
687 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
690 * move right before drawing by the width of the scale left (if any)
691 * plus column offset from left margin (usually zero, but may be non-zero
692 * when fast painting is drawing just a few columns)
694 int charWidth = av.getCharWidth();
695 int xOffset = labelWidthWest
696 + ((startColumn - ranges.getStartRes()) % viewportWidth)
699 g.translate(xOffset, 0);
702 * white fill the region to be drawn (so incremental fast paint doesn't
703 * scribble over an existing image)
705 g.setColor(Color.white);
706 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
707 wrappedRepeatHeightPx);
709 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
712 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
714 if (av.isShowAnnotation())
716 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
717 g.translate(0, yShift);
718 if (annotations == null)
720 annotations = new AnnotationPanel(av);
723 annotations.renderer.drawComponent(annotations, av, g, -1,
724 startColumn, endx + 1);
725 g.translate(0, -yShift);
727 g.translate(-xOffset, 0);
731 * Draws scales left, right and above (if shown), and any hidden column
732 * markers, on all widths of the wrapped alignment
737 protected void drawWrappedDecorators(Graphics g, final int startColumn)
739 int charWidth = av.getCharWidth();
741 g.setFont(av.getFont());
743 g.setColor(Color.black);
745 int ypos = wrappedSpaceAboveAlignment;
746 ViewportRanges ranges = av.getRanges();
747 int viewportWidth = ranges.getViewportWidth();
748 int maxWidth = ranges.getVisibleAlignmentWidth();
750 int startCol = startColumn;
752 while (widthsDrawn < wrappedVisibleWidths)
754 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
756 if (av.getScaleLeftWrapped())
758 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
761 if (av.getScaleRightWrapped())
763 int x = labelWidthWest + viewportWidth * charWidth;
766 drawVerticalScale(g, startCol, endColumn, ypos, false);
771 * white fill region of scale above and hidden column markers
772 * (to support incremental fast paint of image)
774 g.translate(labelWidthWest, 0);
775 g.setColor(Color.white);
776 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
777 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
778 g.setColor(Color.black);
779 g.translate(-labelWidthWest, 0);
781 g.translate(labelWidthWest, 0);
783 if (av.getScaleAboveWrapped())
785 drawNorthScale(g, startCol, endColumn, ypos);
788 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
790 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
793 g.translate(-labelWidthWest, 0);
795 ypos += wrappedRepeatHeightPx;
796 startCol += viewportWidth;
802 * Draws markers (triangles) above hidden column positions between startColumn
810 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
811 int startColumn, int endColumn)
813 int charHeight = av.getCharHeight();
814 int charWidth = av.getCharWidth();
816 g.setColor(Color.blue);
818 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
820 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
824 res = it.next() - startColumn;
826 if (res < 0 || res > endColumn - startColumn + 1)
832 * draw a downward-pointing triangle at the hidden columns location
833 * (before the following visible column)
835 int xMiddle = res * charWidth;
836 int[] xPoints = new int[] { xMiddle - charHeight / 4,
837 xMiddle + charHeight / 4, xMiddle };
838 int yTop = ypos - (charHeight / 2);
839 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
840 g.fillPolygon(xPoints, yPoints, 3);
845 * Draw a selection group over a wrapped alignment
847 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
849 int canvasHeight, int startRes)
851 int charHeight = av.getCharHeight();
852 int charWidth = av.getCharWidth();
854 // height gap above each panel
855 int hgap = charHeight;
856 if (av.getScaleAboveWrapped())
861 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
863 int cHeight = av.getAlignment().getHeight() * charHeight;
865 int startx = startRes;
867 int ypos = hgap; // vertical offset
868 int maxwidth = av.getAlignment().getVisibleWidth();
870 // chop the wrapped alignment extent up into panel-sized blocks and treat
871 // each block as if it were a block from an unwrapped alignment
872 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
873 BasicStroke.JOIN_ROUND, 3f, new float[]
875 g.setColor(Color.RED);
876 while ((ypos <= canvasHeight) && (startx < maxwidth))
878 // set end value to be start + width, or maxwidth, whichever is smaller
879 endx = startx + cWidth - 1;
886 g.translate(labelWidthWest, 0);
888 drawUnwrappedSelection(g, group, startx, endx, 0,
889 av.getAlignment().getHeight() - 1,
892 g.translate(-labelWidthWest, 0);
894 // update vertical offset
895 ypos += cHeight + getAnnotationHeight() + hgap;
897 // update horizontal offset
900 g.setStroke(new BasicStroke());
903 int getAnnotationHeight()
905 if (!av.isShowAnnotation())
910 if (annotations == null)
912 annotations = new AnnotationPanel(av);
915 return annotations.adjustPanelHeight();
919 * Draws the visible region of the alignment on the graphics context. If there
920 * are hidden column markers in the visible region, then each sub-region
921 * between the markers is drawn separately, followed by the hidden column
925 * the graphics context, positioned at the first residue to be drawn
927 * offset of the first column to draw (0..)
929 * offset of the last column to draw (0..)
931 * offset of the first sequence to draw (0..)
933 * offset of the last sequence to draw (0..)
935 * vertical offset at which to draw (for wrapped alignments)
937 public void drawPanel(Graphics g1, final int startRes, final int endRes,
938 final int startSeq, final int endSeq, final int yOffset)
940 int charHeight = av.getCharHeight();
941 int charWidth = av.getCharWidth();
943 if (!av.hasHiddenColumns())
945 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
953 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
954 VisibleContigsIterator regions = hidden
955 .getVisContigsIterator(startRes, endRes + 1, true);
957 while (regions.hasNext())
959 int[] region = regions.next();
960 blockEnd = region[1];
961 blockStart = region[0];
964 * draw up to just before the next hidden region, or the end of
965 * the visible region, whichever comes first
967 g1.translate(screenY * charWidth, 0);
969 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
972 * draw the downline of the hidden column marker (ScalePanel draws the
973 * triangle on top) if we reached it
975 if (av.getShowHiddenMarkers()
976 && (regions.hasNext() || regions.endsAtHidden()))
978 g1.setColor(Color.blue);
980 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
981 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
982 (endSeq - startSeq + 1) * charHeight + yOffset);
985 g1.translate(-screenY * charWidth, 0);
986 screenY += blockEnd - blockStart + 1;
993 * Draws a region of the visible alignment
997 * offset of the first column in the visible region (0..)
999 * offset of the last column in the visible region (0..)
1001 * offset of the first sequence in the visible region (0..)
1003 * offset of the last sequence in the visible region (0..)
1005 * vertical offset at which to draw (for wrapped alignments)
1007 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1008 int endSeq, int offset)
1010 int charHeight = av.getCharHeight();
1011 int charWidth = av.getCharWidth();
1013 g.setFont(av.getFont());
1014 seqRdr.prepare(g, av.isRenderGaps());
1018 // / First draw the sequences
1019 // ///////////////////////////
1020 for (int i = startSeq; i <= endSeq; i++)
1022 nextSeq = av.getAlignment().getSequenceAt(i);
1023 if (nextSeq == null)
1025 // occasionally, a race condition occurs such that the alignment row is
1029 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1030 startRes, endRes, offset + ((i - startSeq) * charHeight));
1032 if (av.isShowSequenceFeatures())
1034 fr.drawSequence(g, nextSeq, startRes, endRes,
1035 offset + ((i - startSeq) * charHeight), false);
1039 * highlight search Results once sequence has been drawn
1041 if (av.hasSearchResults())
1043 SearchResultsI searchResults = av.getSearchResults();
1044 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1046 if (visibleResults != null)
1048 for (int r = 0; r < visibleResults.length; r += 2)
1050 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1051 visibleResults[r + 1],
1052 (visibleResults[r] - startRes) * charWidth,
1053 offset + ((i - startSeq) * charHeight));
1059 if (av.getSelectionGroup() != null
1060 || av.getAlignment().getGroups().size() > 0)
1062 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1068 * Draws the outlines of any groups defined on the alignment (excluding the
1069 * current selection group, if any)
1078 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1079 int startSeq, int endSeq, int offset)
1081 Graphics2D g = (Graphics2D) g1;
1083 SequenceGroup group = null;
1084 int groupIndex = -1;
1086 if (av.getAlignment().getGroups().size() > 0)
1088 group = av.getAlignment().getGroups().get(0);
1096 g.setColor(group.getOutlineColour());
1097 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1101 if (groupIndex >= av.getAlignment().getGroups().size())
1105 group = av.getAlignment().getGroups().get(groupIndex);
1106 } while (groupIndex < av.getAlignment().getGroups().size());
1111 * Draws the outline of the current selection group (if any)
1119 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1120 int startSeq, int endSeq)
1122 SequenceGroup group = av.getSelectionGroup();
1128 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1129 BasicStroke.JOIN_ROUND, 3f, new float[]
1131 g.setColor(Color.RED);
1132 if (!av.getWrapAlignment())
1134 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1139 drawWrappedSelection(g, group, getWidth(), getHeight(),
1140 av.getRanges().getStartRes());
1142 g.setStroke(new BasicStroke());
1146 * Draw the cursor as a separate image and overlay
1149 * start residue of area to draw cursor in
1151 * end residue of area to draw cursor in
1153 * start sequence of area to draw cursor in
1155 * end sequence of are to draw cursor in
1156 * @return a transparent image of the same size as the sequence canvas, with
1157 * the cursor drawn on it, if any
1159 private void drawCursor(Graphics g, int startRes, int endRes,
1163 // convert the cursorY into a position on the visible alignment
1164 int cursor_ypos = cursorY;
1166 // don't do work unless we have to
1167 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1171 int startx = startRes;
1174 // convert the cursorX into a position on the visible alignment
1175 int cursor_xpos = av.getAlignment().getHiddenColumns()
1176 .absoluteToVisibleColumn(cursorX);
1178 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1181 if (av.getWrapAlignment())
1183 // work out the correct offsets for the cursor
1184 int charHeight = av.getCharHeight();
1185 int charWidth = av.getCharWidth();
1186 int canvasWidth = getWidth();
1187 int canvasHeight = getHeight();
1189 // height gap above each panel
1190 int hgap = charHeight;
1191 if (av.getScaleAboveWrapped())
1196 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1198 int cHeight = av.getAlignment().getHeight() * charHeight;
1200 endx = startx + cWidth - 1;
1201 int ypos = hgap; // vertical offset
1203 // iterate down the wrapped panels
1204 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1206 // update vertical offset
1207 ypos += cHeight + getAnnotationHeight() + hgap;
1209 // update horizontal offset
1211 endx = startx + cWidth - 1;
1214 xoffset = labelWidthWest;
1217 // now check if cursor is within range for x values
1218 if (cursor_xpos >= startx && cursor_xpos <= endx)
1220 // get the character the cursor is drawn at
1221 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1222 char s = seq.getCharAt(cursorX);
1224 seqRdr.drawCursor(g, s,
1225 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1226 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1234 * Draw a selection group over an unwrapped alignment
1237 * graphics object to draw with
1241 * start residue of area to draw
1243 * end residue of area to draw
1245 * start sequence of area to draw
1247 * end sequence of area to draw
1249 * vertical offset (used when called from wrapped alignment code)
1251 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1252 int startRes, int endRes, int startSeq, int endSeq, int offset)
1254 int charWidth = av.getCharWidth();
1256 if (!av.hasHiddenColumns())
1258 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1263 // package into blocks of visible columns
1268 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1269 VisibleContigsIterator regions = hidden
1270 .getVisContigsIterator(startRes, endRes + 1, true);
1271 while (regions.hasNext())
1273 int[] region = regions.next();
1274 blockEnd = region[1];
1275 blockStart = region[0];
1277 g.translate(screenY * charWidth, 0);
1278 drawPartialGroupOutline(g, group,
1279 blockStart, blockEnd, startSeq, endSeq, offset);
1281 g.translate(-screenY * charWidth, 0);
1282 screenY += blockEnd - blockStart + 1;
1288 * Draws part of a selection group outline
1296 * @param verticalOffset
1298 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1299 int startRes, int endRes, int startSeq, int endSeq,
1302 int charHeight = av.getCharHeight();
1303 int charWidth = av.getCharWidth();
1304 int visWidth = (endRes - startRes + 1) * charWidth;
1308 boolean inGroup = false;
1313 List<SequenceI> seqs = group.getSequences(null);
1315 // position of start residue of group relative to startRes, in pixels
1316 int sx = (group.getStartRes() - startRes) * charWidth;
1318 // width of group in pixels
1319 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1322 if (!(sx + xwidth < 0 || sx > visWidth))
1324 for (i = startSeq; i <= endSeq; i++)
1326 sy = verticalOffset + (i - startSeq) * charHeight;
1328 if ((sx <= (endRes - startRes) * charWidth)
1329 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1332 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1334 bottom = sy + charHeight;
1339 if (((top == -1) && (i == 0)) || !seqs
1340 .contains(av.getAlignment().getSequenceAt(i - 1)))
1351 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1352 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1354 // reset top and bottom
1362 sy = verticalOffset + ((i - startSeq) * charHeight);
1363 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1364 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1370 * Draw horizontal selection group boundaries at top and bottom positions
1373 * graphics object to draw on
1379 * visWidth maximum available width
1381 * position to draw top of group at
1383 * position to draw bottom of group at
1385 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1386 int visWidth, int top, int bottom)
1396 // don't let width extend beyond current block, or group extent
1398 if (startx + width >= visWidth)
1400 width = visWidth - startx;
1405 g.drawLine(startx, top, startx + width, top);
1410 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1415 * Draw vertical lines at sx and sx+xwidth providing they lie within
1419 * graphics object to draw on
1425 * visWidth maximum available width
1431 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1434 // if start position is visible, draw vertical line to left of
1436 if (sx >= 0 && sx < visWidth)
1438 g.drawLine(sx, oldY, sx, sy);
1441 // if end position is visible, draw vertical line to right of
1443 if (sx + xwidth < visWidth)
1445 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1450 * Highlights search results in the visible region by rendering as white text
1451 * on a black background. Any previous highlighting is removed. Answers true
1452 * if any highlight was left on the visible alignment (so status bar should be
1453 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1454 * so allows the next repaint to update the whole display.
1459 public boolean highlightSearchResults(SearchResultsI results)
1461 return highlightSearchResults(results, false);
1466 * Highlights search results in the visible region by rendering as white text
1467 * on a black background. Any previous highlighting is removed. Answers true
1468 * if any highlight was left on the visible alignment (so status bar should be
1469 * set to match), else false.
1471 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1472 * highlighted regions are modified. This speeds up highlighting across linked
1475 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1476 * a wrapped alignment had to be scrolled to show the highlighted region, then
1477 * it should be fully redrawn, otherwise a fast paint can be performed. This
1478 * argument could be removed if fast paint of scrolled wrapped alignment is
1479 * coded in future (JAL-2609).
1482 * @param doFastPaint
1483 * if true, sets a flag so the next repaint only redraws the modified
1487 public boolean highlightSearchResults(SearchResultsI results,
1488 boolean doFastPaint)
1494 boolean wrapped = av.getWrapAlignment();
1497 fastPaint = doFastPaint;
1498 fastpainting = fastPaint;
1501 * to avoid redrawing the whole visible region, we instead
1502 * redraw just the minimal regions to remove previous highlights
1505 SearchResultsI previous = av.getSearchResults();
1506 av.setSearchResults(results);
1507 boolean redrawn = false;
1508 boolean drawn = false;
1511 redrawn = drawMappedPositionsWrapped(previous);
1512 drawn = drawMappedPositionsWrapped(results);
1517 redrawn = drawMappedPositions(previous);
1518 drawn = drawMappedPositions(results);
1523 * if highlights were either removed or added, repaint
1531 * return true only if highlights were added
1537 fastpainting = false;
1542 * Redraws the minimal rectangle in the visible region (if any) that includes
1543 * mapped positions of the given search results. Whether or not positions are
1544 * highlighted depends on the SearchResults set on the Viewport. This allows
1545 * this method to be called to either clear or set highlighting. Answers true
1546 * if any positions were drawn (in which case a repaint is still required),
1552 protected boolean drawMappedPositions(SearchResultsI results)
1554 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1560 * calculate the minimal rectangle to redraw that
1561 * includes both new and existing search results
1563 int firstSeq = Integer.MAX_VALUE;
1565 int firstCol = Integer.MAX_VALUE;
1567 boolean matchFound = false;
1569 ViewportRanges ranges = av.getRanges();
1570 int firstVisibleColumn = ranges.getStartRes();
1571 int lastVisibleColumn = ranges.getEndRes();
1572 AlignmentI alignment = av.getAlignment();
1573 if (av.hasHiddenColumns())
1575 firstVisibleColumn = alignment.getHiddenColumns()
1576 .visibleToAbsoluteColumn(firstVisibleColumn);
1577 lastVisibleColumn = alignment.getHiddenColumns()
1578 .visibleToAbsoluteColumn(lastVisibleColumn);
1581 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1582 .getEndSeq(); seqNo++)
1584 SequenceI seq = alignment.getSequenceAt(seqNo);
1586 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1588 if (visibleResults != null)
1590 for (int i = 0; i < visibleResults.length - 1; i += 2)
1592 int firstMatchedColumn = visibleResults[i];
1593 int lastMatchedColumn = visibleResults[i + 1];
1594 if (firstMatchedColumn <= lastVisibleColumn
1595 && lastMatchedColumn >= firstVisibleColumn)
1598 * found a search results match in the visible region -
1599 * remember the first and last sequence matched, and the first
1600 * and last visible columns in the matched positions
1603 firstSeq = Math.min(firstSeq, seqNo);
1604 lastSeq = Math.max(lastSeq, seqNo);
1605 firstMatchedColumn = Math.max(firstMatchedColumn,
1606 firstVisibleColumn);
1607 lastMatchedColumn = Math.min(lastMatchedColumn,
1609 firstCol = Math.min(firstCol, firstMatchedColumn);
1610 lastCol = Math.max(lastCol, lastMatchedColumn);
1618 if (av.hasHiddenColumns())
1620 firstCol = alignment.getHiddenColumns()
1621 .absoluteToVisibleColumn(firstCol);
1622 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1624 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1625 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1626 Graphics gg = img.getGraphics();
1627 gg.translate(transX, transY);
1628 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1629 gg.translate(-transX, -transY);
1637 public void propertyChange(PropertyChangeEvent evt)
1639 String eventName = evt.getPropertyName();
1641 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1647 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1655 if (eventName.equals(ViewportRanges.STARTRES)
1656 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1658 // Make sure we're not trying to draw a panel
1659 // larger than the visible window
1660 if (eventName.equals(ViewportRanges.STARTRES))
1662 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1666 scrollX = ((int[]) evt.getNewValue())[0]
1667 - ((int[]) evt.getOldValue())[0];
1669 ViewportRanges vpRanges = av.getRanges();
1671 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1672 if (scrollX > range)
1676 else if (scrollX < -range)
1681 // Both scrolling and resizing change viewport ranges: scrolling changes
1682 // both start and end points, but resize only changes end values.
1683 // Here we only want to fastpaint on a scroll, with resize using a normal
1684 // paint, so scroll events are identified as changes to the horizontal or
1685 // vertical start value.
1686 if (eventName.equals(ViewportRanges.STARTRES))
1688 if (av.getWrapAlignment())
1690 fastPaintWrapped(scrollX);
1694 fastPaint(scrollX, 0);
1697 else if (eventName.equals(ViewportRanges.STARTSEQ))
1700 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1702 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1704 if (av.getWrapAlignment())
1706 fastPaintWrapped(scrollX);
1710 fastPaint(scrollX, 0);
1713 else if (eventName.equals(ViewportRanges.STARTSEQ))
1716 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1718 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1720 if (av.getWrapAlignment())
1722 fastPaintWrapped(scrollX);
1728 * Does a minimal update of the image for a scroll movement. This method
1729 * handles scroll movements of up to one width of the wrapped alignment (one
1730 * click in the vertical scrollbar). Larger movements (for example after a
1731 * scroll to highlight a mapped position) trigger a full redraw instead.
1734 * number of positions scrolled (right if positive, left if negative)
1736 protected void fastPaintWrapped(int scrollX)
1738 ViewportRanges ranges = av.getRanges();
1740 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1743 * shift of one view width or more is
1744 * overcomplicated to handle in this method
1751 if (fastpainting || img == null)
1757 fastpainting = true;
1762 Graphics gg = img.getGraphics();
1764 calculateWrappedGeometry(getWidth(), getHeight());
1767 * relocate the regions of the alignment that are still visible
1769 shiftWrappedAlignment(-scrollX);
1772 * add new columns (sequence, annotation)
1773 * - at top left if scrollX < 0
1774 * - at right of last two widths if scrollX > 0
1778 int startRes = ranges.getStartRes();
1779 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1780 - scrollX - 1, getHeight());
1784 fastPaintWrappedAddRight(scrollX);
1788 * draw all scales (if shown) and hidden column markers
1790 drawWrappedDecorators(gg, ranges.getStartRes());
1797 fastpainting = false;
1802 * Draws the specified number of columns at the 'end' (bottom right) of a
1803 * wrapped alignment view, including sequences and annotations if shown, but
1804 * not scales. Also draws the same number of columns at the right hand end of
1805 * the second last width shown, if the last width is not full height (so
1806 * cannot simply be copied from the graphics image).
1810 protected void fastPaintWrappedAddRight(int columns)
1817 Graphics gg = img.getGraphics();
1819 ViewportRanges ranges = av.getRanges();
1820 int viewportWidth = ranges.getViewportWidth();
1821 int charWidth = av.getCharWidth();
1824 * draw full height alignment in the second last row, last columns, if the
1825 * last row was not full height
1827 int visibleWidths = wrappedVisibleWidths;
1828 int canvasHeight = getHeight();
1829 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1831 if (lastWidthPartHeight)
1833 int widthsAbove = Math.max(0, visibleWidths - 2);
1834 int ypos = wrappedRepeatHeightPx * widthsAbove
1835 + wrappedSpaceAboveAlignment;
1836 int endRes = ranges.getEndRes();
1837 endRes += widthsAbove * viewportWidth;
1838 int startRes = endRes - columns;
1839 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1843 * white fill first to erase annotations
1847 gg.translate(xOffset, 0);
1848 gg.setColor(Color.white);
1849 gg.fillRect(labelWidthWest, ypos,
1850 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1851 gg.translate(-xOffset, 0);
1853 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1858 * draw newly visible columns in last wrapped width (none if we
1859 * have reached the end of the alignment)
1860 * y-offset for drawing last width is height of widths above,
1863 int widthsAbove = visibleWidths - 1;
1864 int ypos = wrappedRepeatHeightPx * widthsAbove
1865 + wrappedSpaceAboveAlignment;
1866 int endRes = ranges.getEndRes();
1867 endRes += widthsAbove * viewportWidth;
1868 int startRes = endRes - columns + 1;
1871 * white fill first to erase annotations
1873 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1875 gg.translate(xOffset, 0);
1876 gg.setColor(Color.white);
1877 int width = viewportWidth * charWidth - xOffset;
1878 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1879 gg.translate(-xOffset, 0);
1881 gg.setFont(av.getFont());
1882 gg.setColor(Color.black);
1884 if (startRes < ranges.getVisibleAlignmentWidth())
1886 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1890 * and finally, white fill any space below the visible alignment
1892 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1893 if (heightBelow > 0)
1895 gg.setColor(Color.white);
1896 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1902 * Shifts the visible alignment by the specified number of columns - left if
1903 * negative, right if positive. Copies and moves sequences and annotations (if
1904 * shown). Scales, hidden column markers and any newly visible columns must be
1909 protected void shiftWrappedAlignment(int positions)
1916 Graphics gg = img.getGraphics();
1918 int charWidth = av.getCharWidth();
1920 int canvasHeight = getHeight();
1921 ViewportRanges ranges = av.getRanges();
1922 int viewportWidth = ranges.getViewportWidth();
1923 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1925 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1926 int xMax = ranges.getVisibleAlignmentWidth();
1931 * shift right (after scroll left)
1932 * for each wrapped width (starting with the last), copy (width-positions)
1933 * columns from the left margin to the right margin, and copy positions
1934 * columns from the right margin of the row above (if any) to the
1935 * left margin of the current row
1939 * get y-offset of last wrapped width, first row of sequences
1941 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1942 y += wrappedSpaceAboveAlignment;
1943 int copyFromLeftStart = labelWidthWest;
1944 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1949 * shift 'widthToCopy' residues by 'positions' places to the right
1951 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1952 positions * charWidth, 0);
1956 * copy 'positions' residue from the row above (right hand end)
1957 * to this row's left hand end
1959 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1960 positions * charWidth, heightToCopy, -widthToCopy,
1961 wrappedRepeatHeightPx);
1964 y -= wrappedRepeatHeightPx;
1970 * shift left (after scroll right)
1971 * for each wrapped width (starting with the first), copy (width-positions)
1972 * columns from the right margin to the left margin, and copy positions
1973 * columns from the left margin of the row below (if any) to the
1974 * right margin of the current row
1976 int xpos = av.getRanges().getStartRes();
1977 int y = wrappedSpaceAboveAlignment;
1978 int copyFromRightStart = labelWidthWest - positions * charWidth;
1980 while (y < canvasHeight)
1982 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1983 positions * charWidth, 0);
1984 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1985 && (xpos + viewportWidth <= xMax))
1987 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1988 * charWidth, heightToCopy, widthToCopy,
1989 -wrappedRepeatHeightPx);
1991 y += wrappedRepeatHeightPx;
1992 xpos += viewportWidth;
2000 * Redraws any positions in the search results in the visible region of a
2001 * wrapped alignment. Any highlights are drawn depending on the search results
2002 * set on the Viewport, not the <code>results</code> argument. This allows
2003 * this method to be called either to clear highlights (passing the previous
2004 * search results), or to draw new highlights.
2009 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2011 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2015 int charHeight = av.getCharHeight();
2017 boolean matchFound = false;
2019 calculateWrappedGeometry(getWidth(), getHeight());
2020 int wrappedWidth = av.getWrappedWidth();
2021 int wrappedHeight = wrappedRepeatHeightPx;
2023 ViewportRanges ranges = av.getRanges();
2024 int canvasHeight = getHeight();
2025 int repeats = canvasHeight / wrappedHeight;
2026 if (canvasHeight / wrappedHeight > 0)
2031 int firstVisibleColumn = ranges.getStartRes();
2032 int lastVisibleColumn = ranges.getStartRes() + repeats
2033 * ranges.getViewportWidth() - 1;
2035 AlignmentI alignment = av.getAlignment();
2036 if (av.hasHiddenColumns())
2038 firstVisibleColumn = alignment.getHiddenColumns()
2039 .visibleToAbsoluteColumn(firstVisibleColumn);
2040 lastVisibleColumn = alignment.getHiddenColumns()
2041 .visibleToAbsoluteColumn(lastVisibleColumn);
2044 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2047 Graphics gg = img.getGraphics();
2049 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2050 .getEndSeq(); seqNo++)
2052 SequenceI seq = alignment.getSequenceAt(seqNo);
2054 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2056 if (visibleResults != null)
2058 for (int i = 0; i < visibleResults.length - 1; i += 2)
2060 int firstMatchedColumn = visibleResults[i];
2061 int lastMatchedColumn = visibleResults[i + 1];
2062 if (firstMatchedColumn <= lastVisibleColumn
2063 && lastMatchedColumn >= firstVisibleColumn)
2066 * found a search results match in the visible region
2068 firstMatchedColumn = Math.max(firstMatchedColumn,
2069 firstVisibleColumn);
2070 lastMatchedColumn = Math.min(lastMatchedColumn,
2074 * draw each mapped position separately (as contiguous positions may
2075 * wrap across lines)
2077 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2079 int displayColumn = mappedPos;
2080 if (av.hasHiddenColumns())
2082 displayColumn = alignment.getHiddenColumns()
2083 .absoluteToVisibleColumn(displayColumn);
2087 * transX: offset from left edge of canvas to residue position
2089 int transX = labelWidthWest
2090 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2091 * av.getCharWidth();
2094 * transY: offset from top edge of canvas to residue position
2096 int transY = gapHeight;
2097 transY += (displayColumn - ranges.getStartRes())
2098 / wrappedWidth * wrappedHeight;
2099 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2102 * yOffset is from graphics origin to start of visible region
2104 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2105 if (transY < getHeight())
2108 gg.translate(transX, transY);
2109 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2111 gg.translate(-transX, -transY);
2125 * Answers the width in pixels of the left scale labels (0 if not shown)
2129 int getLabelWidthWest()
2131 return labelWidthWest;