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.Rectangle;
42 import java.awt.RenderingHints;
43 import java.awt.image.BufferedImage;
44 import java.beans.PropertyChangeEvent;
45 import java.util.Iterator;
46 import java.util.List;
48 import javax.swing.JPanel;
51 * The Swing component on which the alignment sequences, and annotations (if
52 * shown), are drawn. This includes scales above, left and right (if shown) in
53 * Wrapped mode, but not the scale above in Unwrapped mode.
56 @SuppressWarnings("serial")
57 public class SeqCanvas extends JPanel implements ViewportListenerI
60 * vertical gap in pixels between sequences and annotations when in wrapped
63 static final int SEQS_ANNOTATION_GAP = 3;
65 private static final String ZEROS = "0000000000";
67 final FeatureRenderer fr;
77 private final SequenceRenderer seqRdr;
79 private boolean fastPaint = false;
81 private boolean fastpainting = false;
83 private AnnotationPanel annotations;
86 * measurements for drawing a wrapped alignment
88 private int labelWidthEast; // label right width in pixels if shown
90 private int labelWidthWest; // label left width in pixels if shown
92 int wrappedSpaceAboveAlignment; // gap between widths
94 int wrappedRepeatHeightPx; // height in pixels of wrapped width
96 private int wrappedVisibleWidths; // number of wrapped widths displayed
98 private int availWidth;
100 private int availHeight;
102 private boolean allowFastPaint;
103 // Don't do this! Graphics handles are supposed to be transient
104 // private Graphics2D gg;
107 * Creates a new SeqCanvas object.
111 public SeqCanvas(AlignmentPanel ap)
114 fr = new FeatureRenderer(ap);
115 seqRdr = new SequenceRenderer(av);
116 setLayout(new BorderLayout());
117 PaintRefresher.Register(this, av.getSequenceSetId());
118 setBackground(Color.white);
120 av.getRanges().addPropertyChangeListener(this);
123 public SequenceRenderer getSequenceRenderer()
128 public FeatureRenderer getFeatureRenderer()
134 * Draws the scale above a region of a wrapped alignment, consisting of a
135 * column number every major interval (10 columns).
138 * the graphics context to draw on, positioned at the start (bottom
139 * left) of the line on which to draw any scale marks
141 * start alignment column (0..)
143 * end alignment column (0..)
145 * y offset to draw at
147 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
149 int charHeight = av.getCharHeight();
150 int charWidth = av.getCharWidth();
153 * white fill the scale space (for the fastPaint case)
155 g.setColor(Color.white);
156 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
157 charHeight * 3 / 2 + 2);
158 g.setColor(Color.black);
160 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
162 for (ScaleMark mark : marks)
164 int mpos = mark.column; // (i - startx - 1)
169 String mstring = mark.text;
175 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
179 * draw a tick mark below the column number, centred on the column;
180 * height of tick mark is 4 pixels less than half a character
182 int xpos = (mpos * charWidth) + (charWidth / 2);
183 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
189 * Draw the scale to the left or right of a wrapped alignment
192 * graphics context, positioned at the start of the scale to be drawn
194 * first column of wrapped width (0.. excluding any hidden columns)
196 * last column of wrapped width (0.. excluding any hidden columns)
198 * vertical offset at which to begin the scale
200 * if true, scale is left of residues, if false, scale is right
202 void drawVerticalScale(Graphics g, final int startx, final int endx,
203 final int ypos, final boolean left)
205 int charHeight = av.getCharHeight();
206 int charWidth = av.getCharWidth();
208 int yPos = ypos + charHeight;
212 if (av.hasHiddenColumns())
214 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
215 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
216 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
218 FontMetrics fm = getFontMetrics(av.getFont());
220 for (int i = 0; i < av.getAlignment().getHeight(); i++)
222 SequenceI seq = av.getAlignment().getSequenceAt(i);
225 * find sequence position of first non-gapped position -
226 * to the right if scale left, to the left if scale right
228 int index = left ? startX : endX;
230 while (index >= startX && index <= endX)
232 if (!Comparison.isGap(seq.getCharAt(index)))
234 value = seq.findPosition(index);
248 * white fill the space for the scale
250 g.setColor(Color.white);
251 int y = (yPos + (i * charHeight)) - (charHeight / 5);
252 // fillRect origin is top left of rectangle
253 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
259 * draw scale value, right justified within its width less half a
260 * character width padding on the right
262 int labelSpace = left ? labelWidthWest : labelWidthEast;
263 labelSpace -= charWidth / 2; // leave space to the right
264 String valueAsString = String.valueOf(value);
265 int labelLength = fm.stringWidth(valueAsString);
266 int xOffset = labelSpace - labelLength;
267 g.setColor(Color.black);
268 g.drawString(valueAsString, xOffset, y);
275 * Does a fast paint of an alignment in response to a scroll. Most of the
276 * visible region is simply copied and shifted, and then any newly visible
277 * columns or rows are drawn. The scroll may be horizontal or vertical, but
278 * not both at once. Scrolling may be the result of
280 * <li>dragging a scroll bar</li>
281 * <li>clicking in the scroll bar</li>
282 * <li>scrolling by trackpad, middle mouse button, or other device</li>
283 * <li>by moving the box in the Overview window</li>
284 * <li>programmatically to make a highlighted position visible</li>
285 * <li>pasting a block of sequences</li>
289 * columns to shift right (positive) or left (negative)
291 * rows to shift down (positive) or up (negative)
293 public void fastPaint(int horizontal, int vertical)
297 // if (horizontal != 0 && vertical != 0)
298 // throw new InvalidArgumentException();
299 if (fastpainting || img == null)
307 int charHeight = av.getCharHeight();
308 int charWidth = av.getCharWidth();
310 ViewportRanges ranges = av.getRanges();
311 int startRes = ranges.getStartRes();
312 int endRes = ranges.getEndRes();
313 int startSeq = ranges.getStartSeq();
314 int endSeq = ranges.getEndSeq();
318 if (horizontal > 0) // scrollbar pulled right, image to the left
320 transX = (endRes - startRes - horizontal) * charWidth;
321 startRes = endRes - horizontal;
323 else if (horizontal < 0)
325 endRes = startRes - horizontal;
328 if (vertical > 0) // scroll down
330 startSeq = endSeq - vertical;
332 if (startSeq < ranges.getStartSeq())
333 { // ie scrolling too fast, more than a page at a time
334 startSeq = ranges.getStartSeq();
338 transY = img.getHeight() - ((vertical + 1) * charHeight);
341 else if (vertical < 0)
343 endSeq = startSeq - vertical;
345 if (endSeq > ranges.getEndSeq())
347 endSeq = ranges.getEndSeq();
352 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
353 // + horizontal + " " + vertical + " " + startRes + " " + endRes
354 // + " " + startSeq + " " + endSeq);
356 Graphics gg = img.getGraphics();
357 gg.copyArea(horizontal * charWidth, vertical * charHeight,
358 img.getWidth(), img.getHeight(), -horizontal * charWidth,
359 -vertical * charHeight);
362 gg.translate(transX, transY);
363 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
364 gg.translate(-transX, -transY);
367 // Call repaint on alignment panel so that repaints from other alignment
368 // panel components can be aggregated. Otherwise performance of the
369 // overview window and others may be adversely affected.
370 av.getAlignPanel().repaint();
373 fastpainting = false;
378 public void paintComponent(Graphics g)
380 if (av==null || av.getAlignPanel()==null || av.getAlignPanel().getHoldRepaint())
387 if (availWidth == 0 || availHeight == 0)
392 ViewportRanges ranges = av.getRanges();
393 int startRes = ranges.getStartRes();
394 int startSeq = ranges.getStartSeq();
395 int endRes = ranges.getEndRes();
396 int endSeq = ranges.getEndSeq();
398 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
399 // repaint() requests in unpredictable ways. In this case, the issue was
400 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
401 // repaint request preceded two full requests, thus resulting
402 // in a full request for paint. In constrast, in JavaScript, the three
403 // requests were bundled together into one, so the fastPaint flag was
404 // still present for the second and third request.
406 // This resulted in incomplete painting.
408 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
409 // in PaintRefresher when the target to be painted is one of those two
414 // An initial idea; can be removed once we determine this issue is closed:
415 // if (av.isFastPaintDisabled())
417 // fastPaint = false;
421 if (allowFastPaint && img != null
422 && (fastPaint || (vis = getVisibleRect()).width != (clip = g.getClipBounds()).width
423 || vis.height != clip.height))
425 g.drawImage(img, 0, 0, this);
426 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
432 allowFastPaint = true;
433 // img is a cached version of the last view we drew.
434 // If we have no img or the size has changed, make a new one.
436 if (img == null || availWidth != img.getWidth()
437 || availHeight != img.getHeight())
439 img = new BufferedImage(availWidth, availHeight,
440 BufferedImage.TYPE_INT_RGB);
443 Graphics2D gg = (Graphics2D) img.getGraphics();
444 gg.setFont(av.getFont());
448 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
449 RenderingHints.VALUE_ANTIALIAS_ON);
452 gg.setColor(Color.white);
453 gg.fillRect(0, 0, availWidth, availHeight);
455 if (av.getWrapAlignment())
457 drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
461 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
464 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
466 g.drawImage(img, 0, 0, this);
472 drawCursor(g, startRes, endRes, startSeq, endSeq);
476 * Draw an alignment panel for printing
479 * Graphics object to draw with
481 * start residue of print area
483 * end residue of print area
485 * start sequence of print area
487 * end sequence of print area
489 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
490 int startSeq, int endSeq)
492 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
494 drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
498 * Draw a wrapped alignment panel for printing
501 * Graphics object to draw with
503 * width of drawing area
504 * @param canvasHeight
505 * height of drawing area
507 * start residue of print area
509 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
510 int canvasHeight, int startRes)
512 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
514 SequenceGroup group = av.getSelectionGroup();
517 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
523 * Returns the visible width of the canvas in residues, after allowing for
524 * East or West scales (if shown)
527 * the width in pixels (possibly including scales)
531 public int getWrappedCanvasWidth(int canvasWidth)
533 int charWidth = av.getCharWidth();
535 FontMetrics fm = getFontMetrics(av.getFont());
539 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
541 labelWidth = getLabelWidth(fm);
544 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
546 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
548 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
552 * Returns a pixel width sufficient to show the largest sequence coordinate
553 * (end position) in the alignment, calculated as the FontMetrics width of
554 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
555 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
556 * half a character width space on either side.
561 protected int getLabelWidth(FontMetrics fm)
564 * find the biggest sequence end position we need to show
565 * (note this is not necessarily the sequence length)
568 AlignmentI alignment = av.getAlignment();
569 for (int i = 0; i < alignment.getHeight(); i++)
571 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
576 for (int i = maxWidth; i > 0; i /= 10)
581 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
585 * Draws as many widths of a wrapped alignment as can fit in the visible
590 * available width in pixels
592 * available height in pixels
594 * the first column (0...) of the alignment to draw
596 public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
597 final int startColumn)
599 int wrappedWidthInResidues = calculateWrappedGeometry();
601 av.setWrappedWidth(wrappedWidthInResidues);
603 ViewportRanges ranges = av.getRanges();
604 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
606 // we need to call this again to make sure the startColumn +
607 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
609 calculateWrappedGeometry();
612 * draw one width at a time (excluding any scales shown),
613 * until we have run out of either alignment or vertical space available
615 int ypos = wrappedSpaceAboveAlignment;
616 int maxWidth = ranges.getVisibleAlignmentWidth();
618 int start = startColumn;
619 int currentWidth = 0;
620 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
622 int endColumn = Math.min(maxWidth,
623 start + wrappedWidthInResidues - 1);
624 drawWrappedWidth(g, ypos, start, endColumn, availHeight);
625 ypos += wrappedRepeatHeightPx;
626 start += wrappedWidthInResidues;
630 drawWrappedDecorators(g, startColumn);
633 private void getAvailSizes()
635 int charHeight = av.getCharHeight();
636 int charWidth = av.getCharWidth();
637 availWidth = getWidth();
638 availHeight = getHeight();
639 availWidth -= (availWidth % charWidth);
640 availHeight -= (availHeight % charHeight);
643 * Calculates and saves values needed when rendering a wrapped alignment.
644 * These depend on many factors, including
646 * <li>canvas width and height</li>
647 * <li>number of visible sequences, and height of annotations if shown</li>
648 * <li>font and character width</li>
649 * <li>whether scales are shown left, right or above the alignment</li>
654 * @return the number of residue columns in each width
656 protected int calculateWrappedGeometry()
659 return calculateWrappedGeometry(availWidth, availHeight);
666 * @param canvasHeight
669 public int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
671 int charHeight = av.getCharHeight();
674 * vertical space in pixels between wrapped widths of alignment
675 * - one character height, or two if scale above is drawn
677 wrappedSpaceAboveAlignment = charHeight
678 * (av.getScaleAboveWrapped() ? 2 : 1);
681 * compute height in pixels of the wrapped widths
682 * - start with space above plus sequences
684 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment
685 + av.getAlignment().getHeight() * charHeight;
688 * add annotations panel height if shown
689 * also gap between sequences and annotations
691 if (av.isShowAnnotation())
693 wrappedRepeatHeightPx += getAnnotationHeight();
694 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
698 * number of visible widths (the last one may be part height),
699 * ensuring a part height includes at least one sequence
701 ViewportRanges ranges = av.getRanges();
702 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
703 int remainder = canvasHeight % wrappedRepeatHeightPx;
704 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
706 wrappedVisibleWidths++;
710 * compute width in residues; this also sets East and West label widths
712 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
715 * limit visibleWidths to not exceed width of alignment
717 int xMax = ranges.getVisibleAlignmentWidth();
718 int startToEnd = xMax - ranges.getStartRes();
719 int maxWidths = startToEnd / wrappedWidthInResidues;
720 if (startToEnd % wrappedWidthInResidues > 0)
724 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
726 return wrappedWidthInResidues;
730 * Draws one width of a wrapped alignment, including sequences and
731 * annnotations, if shown, but not scales or hidden column markers
737 * @param canvasHeight
739 protected void drawWrappedWidth(Graphics g, final int ypos,
740 final int startColumn, final int endColumn,
741 final int canvasHeight)
743 ViewportRanges ranges = av.getRanges();
744 int viewportWidth = ranges.getViewportWidth();
746 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
749 * move right before drawing by the width of the scale left (if any)
750 * plus column offset from left margin (usually zero, but may be non-zero
751 * when fast painting is drawing just a few columns)
753 int charWidth = av.getCharWidth();
754 int xOffset = labelWidthWest
755 + ((startColumn - ranges.getStartRes()) % viewportWidth)
758 g.translate(xOffset, 0);
761 * white fill the region to be drawn (so incremental fast paint doesn't
762 * scribble over an existing image)
764 g.setColor(Color.white);
765 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
766 wrappedRepeatHeightPx);
768 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
771 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
773 if (av.isShowAnnotation())
775 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
776 g.translate(0, yShift);
777 if (annotations == null)
779 annotations = new AnnotationPanel(av);
782 annotations.renderer.drawComponent(annotations, av, g, -1,
783 startColumn, endx + 1);
784 g.translate(0, -yShift);
786 g.translate(-xOffset, 0);
790 * Draws scales left, right and above (if shown), and any hidden column
791 * markers, on all widths of the wrapped alignment
796 protected void drawWrappedDecorators(Graphics g, final int startColumn)
798 int charWidth = av.getCharWidth();
800 g.setFont(av.getFont());
802 g.setColor(Color.black);
804 int ypos = wrappedSpaceAboveAlignment;
805 ViewportRanges ranges = av.getRanges();
806 int viewportWidth = ranges.getViewportWidth();
807 int maxWidth = ranges.getVisibleAlignmentWidth();
809 int startCol = startColumn;
811 while (widthsDrawn < wrappedVisibleWidths)
813 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
815 if (av.getScaleLeftWrapped())
817 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
820 if (av.getScaleRightWrapped())
822 int x = labelWidthWest + viewportWidth * charWidth;
824 drawVerticalScale(g, startCol, endColumn, ypos, false);
829 * white fill region of scale above and hidden column markers
830 * (to support incremental fast paint of image)
832 g.translate(labelWidthWest, 0);
833 g.setColor(Color.white);
834 g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
835 viewportWidth * charWidth + labelWidthWest,
836 wrappedSpaceAboveAlignment);
837 g.setColor(Color.black);
838 g.translate(-labelWidthWest, 0);
840 g.translate(labelWidthWest, 0);
842 if (av.getScaleAboveWrapped())
844 drawNorthScale(g, startCol, endColumn, ypos);
847 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
849 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
852 g.translate(-labelWidthWest, 0);
854 ypos += wrappedRepeatHeightPx;
855 startCol += viewportWidth;
861 * Draws markers (triangles) above hidden column positions between startColumn
869 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
870 int startColumn, int endColumn)
872 int charHeight = av.getCharHeight();
873 int charWidth = av.getCharWidth();
875 g.setColor(Color.blue);
877 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
879 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
883 res = it.next() - startColumn;
885 if (res < 0 || res > endColumn - startColumn + 1)
891 * draw a downward-pointing triangle at the hidden columns location
892 * (before the following visible column)
894 int xMiddle = res * charWidth;
895 int[] xPoints = new int[] { xMiddle - charHeight / 4,
896 xMiddle + charHeight / 4, xMiddle };
897 int yTop = ypos - (charHeight / 2);
898 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
899 g.fillPolygon(xPoints, yPoints, 3);
903 private final static BasicStroke dottedStroke = new BasicStroke(1,
904 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
907 private final static BasicStroke basicStroke = new BasicStroke();
909 * Draw a selection group over a wrapped alignment
911 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
912 int canvasWidth, int canvasHeight, int startRes)
914 // chop the wrapped alignment extent up into panel-sized blocks and treat
915 // each block as if it were a block from an unwrapped alignment
916 g.setStroke(dottedStroke);
917 g.setColor(Color.RED);
919 int charWidth = av.getCharWidth();
920 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
922 int startx = startRes;
923 int maxwidth = av.getAlignment().getVisibleWidth();
924 // JAL-3253-applet had this:
925 // // height gap above each panel
926 // int charHeight = av.getCharHeight();
927 // int hgap = charHeight;
928 // if (av.getScaleAboveWrapped())
930 // hgap += charHeight;
932 // int dy = getAnnotationHeight() + hgap
933 // + av.getAlignment().getHeight() * charHeight;
934 // int ypos = hgap; // vertical offset
936 // this is from 0b573ed (gmungoc)
937 int dy = wrappedRepeatHeightPx;
938 int ypos = wrappedSpaceAboveAlignment;
940 while ((ypos <= canvasHeight) && (startx < maxwidth))
942 // set end value to be start + width, or maxwidth, whichever is smaller
943 int endx = startx + cWidth - 1;
950 g.translate(labelWidthWest, 0);
951 drawUnwrappedSelection(g, group, startx, endx, 0,
952 av.getAlignment().getHeight() - 1, ypos);
953 g.translate(-labelWidthWest, 0);
955 // update vertical offset
958 // update horizontal offset
961 g.setStroke(basicStroke);
965 * Answers zero if annotations are not shown, otherwise recalculates and
966 * answers the total height of all annotation rows in pixels
970 int getAnnotationHeight()
972 if (!av.isShowAnnotation())
977 if (annotations == null)
979 annotations = new AnnotationPanel(av);
982 return annotations.adjustPanelHeight();
986 * Draws the visible region of the alignment on the graphics context. If there
987 * are hidden column markers in the visible region, then each sub-region
988 * between the markers is drawn separately, followed by the hidden column
992 * the graphics context, positioned at the first residue to be drawn
994 * offset of the first column to draw (0..)
996 * offset of the last column to draw (0..)
998 * offset of the first sequence to draw (0..)
1000 * offset of the last sequence to draw (0..)
1002 * vertical offset at which to draw (for wrapped alignments)
1004 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1005 final int startSeq, final int endSeq, final int yOffset)
1007 int charHeight = av.getCharHeight();
1008 int charWidth = av.getCharWidth();
1010 if (!av.hasHiddenColumns())
1012 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1020 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1021 VisibleContigsIterator regions = hidden
1022 .getVisContigsIterator(startRes, endRes + 1, true);
1024 while (regions.hasNext())
1026 int[] region = regions.next();
1027 blockEnd = region[1];
1028 blockStart = region[0];
1031 * draw up to just before the next hidden region, or the end of
1032 * the visible region, whichever comes first
1034 g1.translate(screenY * charWidth, 0);
1036 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1039 * draw the downline of the hidden column marker (ScalePanel draws the
1040 * triangle on top) if we reached it
1042 if (av.getShowHiddenMarkers()
1043 && (regions.hasNext() || regions.endsAtHidden()))
1045 g1.setColor(Color.blue);
1047 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1048 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1049 (endSeq - startSeq + 1) * charHeight + yOffset);
1052 g1.translate(-screenY * charWidth, 0);
1053 screenY += blockEnd - blockStart + 1;
1060 * Draws a region of the visible alignment
1064 * offset of the first column in the visible region (0..)
1066 * offset of the last column in the visible region (0..)
1068 * offset of the first sequence in the visible region (0..)
1070 * offset of the last sequence in the visible region (0..)
1072 * vertical offset at which to draw (for wrapped alignments)
1074 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1075 int endSeq, int offset)
1077 int charHeight = av.getCharHeight();
1078 int charWidth = av.getCharWidth();
1080 g.setFont(av.getFont());
1081 seqRdr.prepare(g, av.isRenderGaps());
1085 // / First draw the sequences
1086 // ///////////////////////////
1087 for (int i = startSeq; i <= endSeq; i++)
1089 nextSeq = av.getAlignment().getSequenceAt(i);
1090 if (nextSeq == null)
1092 // occasionally, a race condition occurs such that the alignment row is
1096 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1097 startRes, endRes, offset + ((i - startSeq) * charHeight));
1099 if (av.isShowSequenceFeatures())
1101 fr.drawSequence(g, nextSeq, startRes, endRes,
1102 offset + ((i - startSeq) * charHeight), false);
1106 * highlight search Results once sequence has been drawn
1108 if (av.hasSearchResults())
1110 SearchResultsI searchResults = av.getSearchResults();
1111 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1113 if (visibleResults != null)
1115 for (int r = 0; r < visibleResults.length; r += 2)
1117 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1118 visibleResults[r + 1],
1119 (visibleResults[r] - startRes) * charWidth,
1120 offset + ((i - startSeq) * charHeight));
1126 if (av.getSelectionGroup() != null
1127 || av.getAlignment().getGroups().size() > 0)
1129 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1135 * Draws the outlines of any groups defined on the alignment (excluding the
1136 * current selection group, if any)
1145 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1146 int startSeq, int endSeq, int offset)
1148 Graphics2D g = (Graphics2D) g1;
1150 SequenceGroup group = null;
1151 int groupIndex = -1;
1153 if (av.getAlignment().getGroups().size() > 0)
1155 group = av.getAlignment().getGroups().get(0);
1163 g.setColor(group.getOutlineColour());
1164 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1168 if (groupIndex >= av.getAlignment().getGroups().size())
1172 group = av.getAlignment().getGroups().get(groupIndex);
1173 } while (groupIndex < av.getAlignment().getGroups().size());
1178 * Draws the outline of the current selection group (if any)
1186 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1187 int startSeq, int endSeq)
1189 SequenceGroup group = av.getSelectionGroup();
1195 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1196 BasicStroke.JOIN_ROUND, 3f, new float[]
1198 g.setColor(Color.RED);
1199 if (!av.getWrapAlignment())
1201 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1206 drawWrappedSelection(g, group, getWidth(), getHeight(),
1207 av.getRanges().getStartRes());
1209 g.setStroke(new BasicStroke());
1213 * Draw the cursor as a separate image and overlay
1216 * start residue of area to draw cursor in
1218 * end residue of area to draw cursor in
1220 * start sequence of area to draw cursor in
1222 * end sequence of are to draw cursor in
1223 * @return a transparent image of the same size as the sequence canvas, with
1224 * the cursor drawn on it, if any
1226 private void drawCursor(Graphics g, int startRes, int endRes,
1227 int startSeq, int endSeq)
1229 // convert the cursorY into a position on the visible alignment
1230 int cursor_ypos = cursorY;
1232 // don't do work unless we have to
1233 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1237 int startx = startRes;
1240 // convert the cursorX into a position on the visible alignment
1241 int cursor_xpos = av.getAlignment().getHiddenColumns()
1242 .absoluteToVisibleColumn(cursorX);
1244 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1247 if (av.getWrapAlignment())
1249 // work out the correct offsets for the cursor
1250 int charHeight = av.getCharHeight();
1251 int charWidth = av.getCharWidth();
1252 int canvasWidth = getWidth();
1253 int canvasHeight = getHeight();
1255 // height gap above each panel
1256 int hgap = charHeight;
1257 if (av.getScaleAboveWrapped())
1262 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1264 int cHeight = av.getAlignment().getHeight() * charHeight;
1266 endx = startx + cWidth - 1;
1267 int ypos = hgap; // vertical offset
1269 // iterate down the wrapped panels
1270 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1272 // update vertical offset
1273 ypos += cHeight + getAnnotationHeight() + hgap;
1275 // update horizontal offset
1277 endx = startx + cWidth - 1;
1280 xoffset = labelWidthWest;
1283 // now check if cursor is within range for x values
1284 if (cursor_xpos >= startx && cursor_xpos <= endx)
1286 // get the character the cursor is drawn at
1287 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1288 char s = seq.getCharAt(cursorX);
1290 seqRdr.drawCursor(g, s,
1291 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1292 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1300 * Draw a selection group over an unwrapped alignment
1303 * graphics object to draw with
1307 * start residue of area to draw
1309 * end residue of area to draw
1311 * start sequence of area to draw
1313 * end sequence of area to draw
1315 * vertical offset (used when called from wrapped alignment code)
1317 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1318 int startRes, int endRes, int startSeq, int endSeq, int offset)
1320 int charWidth = av.getCharWidth();
1321 if (!av.hasHiddenColumns())
1323 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1328 // package into blocks of visible columns
1333 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1334 VisibleContigsIterator regions = hidden
1335 .getVisContigsIterator(startRes, endRes + 1, true);
1336 while (regions.hasNext())
1338 int[] region = regions.next();
1339 blockEnd = region[1];
1340 blockStart = region[0];
1342 g.translate(screenY * charWidth, 0);
1343 drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
1346 g.translate(-screenY * charWidth, 0);
1347 screenY += blockEnd - blockStart + 1;
1353 * Draws part of a selection group outline
1361 * @param verticalOffset
1363 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1364 int startRes, int endRes, int startSeq, int endSeq,
1367 int charHeight = av.getCharHeight();
1368 int charWidth = av.getCharWidth();
1369 int visWidth = (endRes - startRes + 1) * charWidth;
1373 boolean inGroup = false;
1378 List<SequenceI> seqs = group.getSequences(null);
1380 // position of start residue of group relative to startRes, in pixels
1381 int sx = (group.getStartRes() - startRes) * charWidth;
1383 // width of group in pixels
1384 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1387 if (!(sx + xwidth < 0 || sx > visWidth))
1389 for (i = startSeq; i <= endSeq; i++)
1391 sy = verticalOffset + (i - startSeq) * charHeight;
1393 if ((sx <= (endRes - startRes) * charWidth)
1394 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1397 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1399 bottom = sy + charHeight;
1404 if (((top == -1) && (i == 0)) || !seqs
1405 .contains(av.getAlignment().getSequenceAt(i - 1)))
1416 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1417 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1419 // reset top and bottom
1427 sy = verticalOffset + ((i - startSeq) * charHeight);
1428 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1429 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1435 * Draw horizontal selection group boundaries at top and bottom positions
1438 * graphics object to draw on
1444 * visWidth maximum available width
1446 * position to draw top of group at
1448 * position to draw bottom of group at
1450 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1451 int visWidth, int top, int bottom)
1461 // don't let width extend beyond current block, or group extent
1463 if (startx + width >= visWidth)
1465 width = visWidth - startx;
1470 g.drawLine(startx, top, startx + width, top);
1475 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1480 * Draw vertical lines at sx and sx+xwidth providing they lie within
1484 * graphics object to draw on
1490 * visWidth maximum available width
1496 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1499 // if start position is visible, draw vertical line to left of
1501 if (sx >= 0 && sx < visWidth)
1503 g.drawLine(sx, oldY, sx, sy);
1506 // if end position is visible, draw vertical line to right of
1508 if (sx + xwidth < visWidth)
1510 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1514 * Highlights search results in the visible region by rendering as white text
1515 * on a black background. Any previous highlighting is removed. Answers true
1516 * if any highlight was left on the visible alignment (so status bar should be
1517 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1518 * so allows the next repaint to update the whole display.
1523 public boolean highlightSearchResults(SearchResultsI results)
1525 return highlightSearchResults(results, false);
1529 * Highlights search results in the visible region by rendering as white text
1530 * on a black background. Any previous highlighting is removed. Answers true
1531 * if any highlight was left on the visible alignment (so status bar should be
1532 * set to match), else false.
1534 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1535 * highlighted regions are modified. This speeds up highlighting across linked
1538 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1539 * a wrapped alignment had to be scrolled to show the highlighted region, then
1540 * it should be fully redrawn, otherwise a fast paint can be performed. This
1541 * argument could be removed if fast paint of scrolled wrapped alignment is
1542 * coded in future (JAL-2609).
1545 * @param doFastPaint
1546 * if true, sets a flag so the next repaint only redraws the modified
1550 public boolean highlightSearchResults(SearchResultsI results,
1551 boolean doFastPaint)
1557 boolean wrapped = av.getWrapAlignment();
1560 fastPaint = doFastPaint;
1561 fastpainting = fastPaint;
1564 * to avoid redrawing the whole visible region, we instead
1565 * redraw just the minimal regions to remove previous highlights
1568 SearchResultsI previous = av.getSearchResults();
1569 av.setSearchResults(results);
1570 boolean redrawn = false;
1571 boolean drawn = false;
1574 redrawn = drawMappedPositionsWrapped(previous);
1575 drawn = drawMappedPositionsWrapped(results);
1580 redrawn = drawMappedPositions(previous);
1581 drawn = drawMappedPositions(results);
1586 * if highlights were either removed or added, repaint
1594 * return true only if highlights were added
1600 fastpainting = false;
1605 * Redraws the minimal rectangle in the visible region (if any) that includes
1606 * mapped positions of the given search results. Whether or not positions are
1607 * highlighted depends on the SearchResults set on the Viewport. This allows
1608 * this method to be called to either clear or set highlighting. Answers true
1609 * if any positions were drawn (in which case a repaint is still required),
1615 protected boolean drawMappedPositions(SearchResultsI results)
1617 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1623 * calculate the minimal rectangle to redraw that
1624 * includes both new and existing search results
1626 int firstSeq = Integer.MAX_VALUE;
1628 int firstCol = Integer.MAX_VALUE;
1630 boolean matchFound = false;
1632 ViewportRanges ranges = av.getRanges();
1633 int firstVisibleColumn = ranges.getStartRes();
1634 int lastVisibleColumn = ranges.getEndRes();
1635 AlignmentI alignment = av.getAlignment();
1636 if (av.hasHiddenColumns())
1638 firstVisibleColumn = alignment.getHiddenColumns()
1639 .visibleToAbsoluteColumn(firstVisibleColumn);
1640 lastVisibleColumn = alignment.getHiddenColumns()
1641 .visibleToAbsoluteColumn(lastVisibleColumn);
1644 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1645 .getEndSeq(); seqNo++)
1647 SequenceI seq = alignment.getSequenceAt(seqNo);
1649 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1651 if (visibleResults != null)
1653 for (int i = 0; i < visibleResults.length - 1; i += 2)
1655 int firstMatchedColumn = visibleResults[i];
1656 int lastMatchedColumn = visibleResults[i + 1];
1657 if (firstMatchedColumn <= lastVisibleColumn
1658 && lastMatchedColumn >= firstVisibleColumn)
1661 * found a search results match in the visible region -
1662 * remember the first and last sequence matched, and the first
1663 * and last visible columns in the matched positions
1666 firstSeq = Math.min(firstSeq, seqNo);
1667 lastSeq = Math.max(lastSeq, seqNo);
1668 firstMatchedColumn = Math.max(firstMatchedColumn,
1669 firstVisibleColumn);
1670 lastMatchedColumn = Math.min(lastMatchedColumn,
1672 firstCol = Math.min(firstCol, firstMatchedColumn);
1673 lastCol = Math.max(lastCol, lastMatchedColumn);
1681 if (av.hasHiddenColumns())
1683 firstCol = alignment.getHiddenColumns()
1684 .absoluteToVisibleColumn(firstCol);
1685 lastCol = alignment.getHiddenColumns()
1686 .absoluteToVisibleColumn(lastCol);
1688 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1689 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1690 Graphics gg = img.getGraphics();
1691 gg.translate(transX, transY);
1692 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1693 gg.translate(-transX, -transY);
1701 public void propertyChange(PropertyChangeEvent evt)
1703 String eventName = evt.getPropertyName();
1704 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1705 // logic, emphasizing no check for ENDRES or ENDSEQ
1707 // Both scrolling and resizing change viewport ranges: scrolling changes
1708 // both start and end points, but resize only changes end values.
1709 // Here we only want to fastpaint on a scroll, with resize using a normal
1710 // paint, so scroll events are identified as changes to the horizontal or
1711 // vertical start value.
1713 // Make sure we're not trying to draw a panel
1714 // larger than the visible window
1719 case SequenceGroup.SEQ_GROUP_CHANGED:
1723 case ViewportRanges.MOVE_VIEWPORT:
1727 case ViewportRanges.STARTSEQ:
1728 // meaning STARTOREND
1729 // typically scroll, but possibly just the end changed
1730 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1732 case ViewportRanges.STARTRES:
1733 // meaning STARTOREND
1734 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1736 case ViewportRanges.STARTRESANDSEQ:
1737 scrollX = ((int[]) evt.getNewValue())[0]
1738 - ((int[]) evt.getOldValue())[0];
1739 scrollY = ((int[]) evt.getNewValue())[1]
1740 - ((int[]) evt.getOldValue())[1];
1741 if (scrollX != 0 && scrollY != 0)
1743 // all sorts of problems in JavaScript if this is commented out.
1753 ViewportRanges vpRanges = av.getRanges();
1754 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1755 scrollX = Math.max(Math.min(scrollX, range), -range);
1756 // only STARTRES or STARTRESANDSEQ:
1757 if (av.getWrapAlignment())
1759 fastPaintWrapped(scrollX);
1763 fastPaint(scrollX, scrollY);
1766 // BH 2019.07.27 was:
1767 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1769 // fastPaint = true;
1773 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1775 // fastPaint = false;
1776 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1781 // if (eventName.equals(ViewportRanges.STARTRES)
1782 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1784 // // Make sure we're not trying to draw a panel
1785 // // larger than the visible window
1786 // if (eventName.equals(ViewportRanges.STARTRES))
1788 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1792 // scrollX = ((int[]) evt.getNewValue())[0]
1793 // - ((int[]) evt.getOldValue())[0];
1795 // ViewportRanges vpRanges = av.getRanges();
1797 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1798 // if (scrollX > range)
1802 // else if (scrollX < -range)
1804 // scrollX = -range;
1807 // Both scrolling and resizing change viewport ranges: scrolling changes
1808 // both start and end points, but resize only changes end values.
1809 // Here we only want to fastpaint on a scroll, with resize using a normal
1810 // paint, so scroll events are identified as changes to the horizontal or
1811 // vertical start value.
1812 // BH 2019.07.27 was:
1813 // if (eventName.equals(ViewportRanges.STARTRES))
1815 // if (av.getWrapAlignment())
1817 // fastPaintWrapped(scrollX);
1821 // fastPaint(scrollX, 0);
1824 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1827 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1829 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1831 // if (av.getWrapAlignment())
1833 // fastPaintWrapped(scrollX);
1837 // fastPaint(scrollX, 0);
1843 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1846 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1848 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1850 // if (av.getWrapAlignment())
1852 // fastPaintWrapped(scrollX);
1858 * Does a minimal update of the image for a scroll movement. This method
1859 * handles scroll movements of up to one width of the wrapped alignment (one
1860 * click in the vertical scrollbar). Larger movements (for example after a
1861 * scroll to highlight a mapped position) trigger a full redraw instead.
1864 * number of positions scrolled (right if positive, left if negative)
1866 protected void fastPaintWrapped(int scrollX)
1868 ViewportRanges ranges = av.getRanges();
1870 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1873 * shift of one view width or more is
1874 * overcomplicated to handle in this method
1881 if (fastpainting || img == null)
1887 fastpainting = true;
1891 Graphics gg = img.getGraphics();
1892 calculateWrappedGeometry();
1895 * relocate the regions of the alignment that are still visible
1897 shiftWrappedAlignment(-scrollX);
1900 * add new columns (sequence, annotation)
1901 * - at top left if scrollX < 0
1902 * - at right of last two widths if scrollX > 0
1906 int startRes = ranges.getStartRes();
1907 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
1908 startRes - scrollX - 1, getHeight());
1912 fastPaintWrappedAddRight(scrollX);
1916 * draw all scales (if shown) and hidden column markers
1918 drawWrappedDecorators(gg, ranges.getStartRes());
1924 fastpainting = false;
1929 * Draws the specified number of columns at the 'end' (bottom right) of a
1930 * wrapped alignment view, including sequences and annotations if shown, but
1931 * not scales. Also draws the same number of columns at the right hand end of
1932 * the second last width shown, if the last width is not full height (so
1933 * cannot simply be copied from the graphics image).
1937 protected void fastPaintWrappedAddRight(int columns)
1944 Graphics gg = img.getGraphics();
1945 ViewportRanges ranges = av.getRanges();
1946 int viewportWidth = ranges.getViewportWidth();
1947 int charWidth = av.getCharWidth();
1950 * draw full height alignment in the second last row, last columns, if the
1951 * last row was not full height
1953 int visibleWidths = wrappedVisibleWidths;
1954 int canvasHeight = getHeight();
1955 boolean lastWidthPartHeight = (wrappedVisibleWidths
1956 * wrappedRepeatHeightPx) > canvasHeight;
1958 if (lastWidthPartHeight)
1960 int widthsAbove = Math.max(0, visibleWidths - 2);
1961 int ypos = wrappedRepeatHeightPx * widthsAbove
1962 + wrappedSpaceAboveAlignment;
1963 int endRes = ranges.getEndRes();
1964 endRes += widthsAbove * viewportWidth;
1965 int startRes = endRes - columns;
1966 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1970 * white fill first to erase annotations
1972 gg.translate(xOffset, 0);
1973 gg.setColor(Color.white);
1974 gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
1975 wrappedRepeatHeightPx);
1976 gg.translate(-xOffset, 0);
1978 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1982 * draw newly visible columns in last wrapped width (none if we
1983 * have reached the end of the alignment)
1984 * y-offset for drawing last width is height of widths above,
1987 int widthsAbove = visibleWidths - 1;
1988 int ypos = wrappedRepeatHeightPx * widthsAbove
1989 + wrappedSpaceAboveAlignment;
1990 int endRes = ranges.getEndRes();
1991 endRes += widthsAbove * viewportWidth;
1992 int startRes = endRes - columns + 1;
1995 * white fill first to erase annotations
1997 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1999 gg.translate(xOffset, 0);
2000 gg.setColor(Color.white);
2001 int width = viewportWidth * charWidth - xOffset;
2002 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
2003 gg.translate(-xOffset, 0);
2005 gg.setFont(av.getFont());
2006 gg.setColor(Color.black);
2008 if (startRes < ranges.getVisibleAlignmentWidth())
2010 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
2014 * and finally, white fill any space below the visible alignment
2016 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
2017 if (heightBelow > 0)
2019 gg.setColor(Color.white);
2020 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2026 * Shifts the visible alignment by the specified number of columns - left if
2027 * negative, right if positive. Copies and moves sequences and annotations (if
2028 * shown). Scales, hidden column markers and any newly visible columns must be
2033 protected void shiftWrappedAlignment(int positions)
2040 Graphics gg = img.getGraphics();
2042 int charWidth = av.getCharWidth();
2044 int canvasHeight = getHeight();
2045 ViewportRanges ranges = av.getRanges();
2046 int viewportWidth = ranges.getViewportWidth();
2047 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2049 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2050 int xMax = ranges.getVisibleAlignmentWidth();
2055 * shift right (after scroll left)
2056 * for each wrapped width (starting with the last), copy (width-positions)
2057 * columns from the left margin to the right margin, and copy positions
2058 * columns from the right margin of the row above (if any) to the
2059 * left margin of the current row
2063 * get y-offset of last wrapped width, first row of sequences
2065 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2066 y += wrappedSpaceAboveAlignment;
2067 int copyFromLeftStart = labelWidthWest;
2068 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2073 * shift 'widthToCopy' residues by 'positions' places to the right
2075 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2076 positions * charWidth, 0);
2080 * copy 'positions' residue from the row above (right hand end)
2081 * to this row's left hand end
2083 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2084 positions * charWidth, heightToCopy, -widthToCopy,
2085 wrappedRepeatHeightPx);
2088 y -= wrappedRepeatHeightPx;
2094 * shift left (after scroll right)
2095 * for each wrapped width (starting with the first), copy (width-positions)
2096 * columns from the right margin to the left margin, and copy positions
2097 * columns from the left margin of the row below (if any) to the
2098 * right margin of the current row
2100 int xpos = av.getRanges().getStartRes();
2101 int y = wrappedSpaceAboveAlignment;
2102 int copyFromRightStart = labelWidthWest - positions * charWidth;
2104 while (y < canvasHeight)
2106 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2107 positions * charWidth, 0);
2108 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2109 && (xpos + viewportWidth <= xMax))
2111 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
2112 -positions * charWidth, heightToCopy, widthToCopy,
2113 -wrappedRepeatHeightPx);
2115 y += wrappedRepeatHeightPx;
2116 xpos += viewportWidth;
2123 * Redraws any positions in the search results in the visible region of a
2124 * wrapped alignment. Any highlights are drawn depending on the search results
2125 * set on the Viewport, not the <code>results</code> argument. This allows
2126 * this method to be called either to clear highlights (passing the previous
2127 * search results), or to draw new highlights.
2132 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2134 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2138 int charHeight = av.getCharHeight();
2140 boolean matchFound = false;
2142 calculateWrappedGeometry();
2143 int wrappedWidth = av.getWrappedWidth();
2144 int wrappedHeight = wrappedRepeatHeightPx;
2146 ViewportRanges ranges = av.getRanges();
2147 int canvasHeight = getHeight();
2148 int repeats = canvasHeight / wrappedHeight;
2149 if (canvasHeight / wrappedHeight > 0)
2154 int firstVisibleColumn = ranges.getStartRes();
2155 int lastVisibleColumn = ranges.getStartRes()
2156 + repeats * ranges.getViewportWidth() - 1;
2158 AlignmentI alignment = av.getAlignment();
2159 if (av.hasHiddenColumns())
2161 firstVisibleColumn = alignment.getHiddenColumns()
2162 .visibleToAbsoluteColumn(firstVisibleColumn);
2163 lastVisibleColumn = alignment.getHiddenColumns()
2164 .visibleToAbsoluteColumn(lastVisibleColumn);
2167 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2169 Graphics gg = img.getGraphics();
2171 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2172 .getEndSeq(); seqNo++)
2174 SequenceI seq = alignment.getSequenceAt(seqNo);
2176 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2178 if (visibleResults != null)
2180 for (int i = 0; i < visibleResults.length - 1; i += 2)
2182 int firstMatchedColumn = visibleResults[i];
2183 int lastMatchedColumn = visibleResults[i + 1];
2184 if (firstMatchedColumn <= lastVisibleColumn
2185 && lastMatchedColumn >= firstVisibleColumn)
2188 * found a search results match in the visible region
2190 firstMatchedColumn = Math.max(firstMatchedColumn,
2191 firstVisibleColumn);
2192 lastMatchedColumn = Math.min(lastMatchedColumn,
2196 * draw each mapped position separately (as contiguous positions may
2197 * wrap across lines)
2199 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2201 int displayColumn = mappedPos;
2202 if (av.hasHiddenColumns())
2204 displayColumn = alignment.getHiddenColumns()
2205 .absoluteToVisibleColumn(displayColumn);
2209 * transX: offset from left edge of canvas to residue position
2211 int transX = labelWidthWest
2212 + ((displayColumn - ranges.getStartRes())
2213 % wrappedWidth) * av.getCharWidth();
2216 * transY: offset from top edge of canvas to residue position
2218 int transY = gapHeight;
2219 transY += (displayColumn - ranges.getStartRes())
2220 / wrappedWidth * wrappedHeight;
2221 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2224 * yOffset is from graphics origin to start of visible region
2226 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2227 if (transY < getHeight())
2230 gg.translate(transX, transY);
2231 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2233 gg.translate(-transX, -transY);
2246 * Answers the width in pixels of the left scale labels (0 if not shown)
2250 int getLabelWidthWest()
2252 return labelWidthWest;
2256 * Clears the flag that allows a 'fast paint' on the next repaint, so
2257 * requiring a full repaint
2259 public void setNoFastPaint()
2261 allowFastPaint = false;