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 * pixels gap between sequences and annotations when in wrapped mode
62 static final int SEQS_ANNOTATION_GAP = 3;
64 private static final String ZEROS = "0000000000";
66 final FeatureRenderer fr;
76 private final SequenceRenderer seqRdr;
78 boolean fastPaint = false;
80 private boolean fastpainting = false;
82 private AnnotationPanel annotations;
85 * measurements for drawing a wrapped alignment
87 private int labelWidthEast; // label right width in pixels if shown
89 private int labelWidthWest; // label left width in pixels if shown
91 int wrappedSpaceAboveAlignment; // gap between widths
93 int wrappedRepeatHeightPx; // height in pixels of wrapped width
95 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 // Don't do this! Graphics handles are supposed to be transient
98 //private Graphics2D gg;
101 * Creates a new SeqCanvas object.
105 public SeqCanvas(AlignmentPanel ap)
108 fr = new FeatureRenderer(ap);
109 seqRdr = new SequenceRenderer(av);
110 setLayout(new BorderLayout());
111 PaintRefresher.Register(this, av.getSequenceSetId());
112 setBackground(Color.white);
114 av.getRanges().addPropertyChangeListener(this);
117 public SequenceRenderer getSequenceRenderer()
122 public FeatureRenderer getFeatureRenderer()
128 * Draws the scale above a region of a wrapped alignment, consisting of a
129 * column number every major interval (10 columns).
132 * the graphics context to draw on, positioned at the start (bottom
133 * left) of the line on which to draw any scale marks
135 * start alignment column (0..)
137 * end alignment column (0..)
139 * y offset to draw at
141 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
143 int charHeight = av.getCharHeight();
144 int charWidth = av.getCharWidth();
147 * white fill the scale space (for the fastPaint case)
149 g.setColor(Color.white);
150 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
151 charHeight * 3 / 2 + 2);
152 g.setColor(Color.black);
154 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
156 for (ScaleMark mark : marks)
158 int mpos = mark.column; // (i - startx - 1)
163 String mstring = mark.text;
169 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
173 * draw a tick mark below the column number, centred on the column;
174 * height of tick mark is 4 pixels less than half a character
176 int xpos = (mpos * charWidth) + (charWidth / 2);
177 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
183 * Draw the scale to the left or right of a wrapped alignment
186 * graphics context, positioned at the start of the scale to be drawn
188 * first column of wrapped width (0.. excluding any hidden columns)
190 * last column of wrapped width (0.. excluding any hidden columns)
192 * vertical offset at which to begin the scale
194 * if true, scale is left of residues, if false, scale is right
196 void drawVerticalScale(Graphics g, final int startx, final int endx,
197 final int ypos, final boolean left)
199 int charHeight = av.getCharHeight();
200 int charWidth = av.getCharWidth();
202 int yPos = ypos + charHeight;
206 if (av.hasHiddenColumns())
208 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
209 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
210 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
212 FontMetrics fm = getFontMetrics(av.getFont());
214 for (int i = 0; i < av.getAlignment().getHeight(); i++)
216 SequenceI seq = av.getAlignment().getSequenceAt(i);
219 * find sequence position of first non-gapped position -
220 * to the right if scale left, to the left if scale right
222 int index = left ? startX : endX;
224 while (index >= startX && index <= endX)
226 if (!Comparison.isGap(seq.getCharAt(index)))
228 value = seq.findPosition(index);
243 * white fill the space for the scale
245 g.setColor(Color.white);
246 int y = (yPos + (i * charHeight)) - (charHeight / 5);
247 // fillRect origin is top left of rectangle
248 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
254 * draw scale value, right justified within its width less half a
255 * character width padding on the right
257 int labelSpace = left ? labelWidthWest : labelWidthEast;
258 labelSpace -= charWidth / 2; // leave space to the right
259 String valueAsString = String.valueOf(value);
260 int labelLength = fm.stringWidth(valueAsString);
261 int xOffset = labelSpace - labelLength;
262 g.setColor(Color.black);
263 g.drawString(valueAsString, xOffset, y);
270 * Does a fast paint of an alignment in response to a scroll. Most of the
271 * visible region is simply copied and shifted, and then any newly visible
272 * columns or rows are drawn. The scroll may be horizontal or vertical, but
273 * not both at once. Scrolling may be the result of
275 * <li>dragging a scroll bar</li>
276 * <li>clicking in the scroll bar</li>
277 * <li>scrolling by trackpad, middle mouse button, or other device</li>
278 * <li>by moving the box in the Overview window</li>
279 * <li>programmatically to make a highlighted position visible</li>
280 * <li>pasting a block of sequences</li>
284 * columns to shift right (positive) or left (negative)
286 * rows to shift down (positive) or up (negative)
288 public void fastPaint(int horizontal, int vertical)
292 // if (horizontal != 0 && vertical != 0)
293 // throw new InvalidArgumentException();
294 if (fastpainting || img == null)
302 int charHeight = av.getCharHeight();
303 int charWidth = av.getCharWidth();
305 ViewportRanges ranges = av.getRanges();
306 int startRes = ranges.getStartRes();
307 int endRes = ranges.getEndRes();
308 int startSeq = ranges.getStartSeq();
309 int endSeq = ranges.getEndSeq();
313 if (horizontal > 0) // scrollbar pulled right, image to the left
315 transX = (endRes - startRes - horizontal) * charWidth;
316 startRes = endRes - horizontal;
318 else if (horizontal < 0)
320 endRes = startRes - horizontal;
323 if (vertical > 0) // scroll down
325 startSeq = endSeq - vertical;
327 if (startSeq < ranges.getStartSeq())
328 { // ie scrolling too fast, more than a page at a time
329 startSeq = ranges.getStartSeq();
333 transY = img.getHeight() - ((vertical + 1) * charHeight);
336 else if (vertical < 0)
338 endSeq = startSeq - vertical;
340 if (endSeq > ranges.getEndSeq())
342 endSeq = ranges.getEndSeq();
347 // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
348 // + horizontal + " " + vertical + " " + startRes + " " + endRes
349 // + " " + startSeq + " " + endSeq);
351 Graphics gg = img.getGraphics();
352 gg.copyArea(horizontal * charWidth, vertical * charHeight,
353 img.getWidth(), img.getHeight(), -horizontal * charWidth,
354 -vertical * charHeight);
355 gg.translate(transX, transY);
356 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
357 gg.translate(-transX, -transY);
360 // Call repaint on alignment panel so that repaints from other alignment
361 // panel components can be aggregated. Otherwise performance of the
362 // overview window and others may be adversely affected.
363 // System.out.println("SeqCanvas fastPaint() repaint() request...");
364 av.getAlignPanel().repaint();
367 fastpainting = false;
372 public void paintComponent(Graphics g)
375 int charHeight = av.getCharHeight();
376 int charWidth = av.getCharWidth();
378 int width = getWidth();
379 int height = getHeight();
381 width -= (width % charWidth);
382 height -= (height % charHeight);
384 // BH 2019 can't possibly fastPaint if either width or height is 0
386 if (width == 0 || height == 0)
391 ViewportRanges ranges = av.getRanges();
392 int startRes = ranges.getStartRes();
393 int startSeq = ranges.getStartSeq();
394 int endRes = ranges.getEndRes();
395 int endSeq = ranges.getEndSeq();
397 // [JAL-3226] problem that JavaScript (or Java) may consolidate multiple
398 // repaint() requests in unpredictable ways. In this case, the issue was
399 // that in response to a CTRL-C/CTRL-V paste request, in Java a fast
400 // repaint request preceded two full requests, thus resulting
401 // in a full request for paint. In constrast, in JavaScript, the three
402 // requests were bundled together into one, so the fastPaint flag was
403 // still present for the second and third request.
405 // This resulted in incomplete painting.
407 // The solution was to set seqCanvas.fastPaint and idCanvas.fastPaint false
408 // in PaintRefresher when the target to be painted is one of those two
413 // An initial idea; can be removed once we determine this issue is closed:
414 // if (av.isFastPaintDisabled())
416 // fastPaint = false;
422 || (vis = getVisibleRect()).width != (clip = g
423 .getClipBounds()).width
424 || vis.height != clip.height))
426 g.drawImage(img, 0, 0, this);
427 drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
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 || width != img.getWidth()
437 || height != img.getHeight())
439 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
442 Graphics2D gg = (Graphics2D) img.getGraphics();
443 gg.setFont(av.getFont());
447 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
448 RenderingHints.VALUE_ANTIALIAS_ON);
451 gg.setColor(Color.white);
452 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
454 if (av.getWrapAlignment())
456 drawWrappedPanel(gg, width, height, ranges.getStartRes());
460 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
463 drawSelectionGroup(gg, startRes, endRes, startSeq, endSeq);
465 g.drawImage(img, 0, 0, this);
471 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,
499 * Draw a wrapped alignment panel for printing
502 * Graphics object to draw with
504 * width of drawing area
505 * @param canvasHeight
506 * height of drawing area
508 * start residue of print area
510 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
511 int canvasHeight, int startRes)
513 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
515 SequenceGroup group = av.getSelectionGroup();
518 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
524 * Returns the visible width of the canvas in residues, after allowing for
525 * East or West scales (if shown)
528 * the width in pixels (possibly including scales)
532 public int getWrappedCanvasWidth(int canvasWidth)
534 int charWidth = av.getCharWidth();
536 FontMetrics fm = getFontMetrics(av.getFont());
540 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
542 labelWidth = getLabelWidth(fm);
545 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
547 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
549 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
553 * Returns a pixel width sufficient to show the largest sequence coordinate
554 * (end position) in the alignment, calculated as the FontMetrics width of
555 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
556 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
557 * half a character width space on either side.
562 protected int getLabelWidth(FontMetrics fm)
565 * find the biggest sequence end position we need to show
566 * (note this is not necessarily the sequence length)
569 AlignmentI alignment = av.getAlignment();
570 for (int i = 0; i < alignment.getHeight(); i++)
572 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
591 * @param canvasHeight
592 * available height in pixels
594 * the first column (0...) of the alignment to draw
596 public void drawWrappedPanel(Graphics g, int canvasWidth,
597 int canvasHeight, final int startColumn)
599 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
602 av.setWrappedWidth(wrappedWidthInResidues);
604 ViewportRanges ranges = av.getRanges();
605 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
607 // we need to call this again to make sure the startColumn +
608 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
610 calculateWrappedGeometry(canvasWidth, canvasHeight);
613 * draw one width at a time (excluding any scales shown),
614 * until we have run out of either alignment or vertical space available
616 int ypos = wrappedSpaceAboveAlignment;
617 int maxWidth = ranges.getVisibleAlignmentWidth();
619 int start = startColumn;
620 int currentWidth = 0;
621 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
624 .min(maxWidth, start + wrappedWidthInResidues - 1);
625 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
626 ypos += wrappedRepeatHeightPx;
627 start += wrappedWidthInResidues;
631 drawWrappedDecorators(g, startColumn);
635 * Calculates and saves values needed when rendering a wrapped alignment.
636 * These depend on many factors, including
638 * <li>canvas width and height</li>
639 * <li>number of visible sequences, and height of annotations if shown</li>
640 * <li>font and character width</li>
641 * <li>whether scales are shown left, right or above the alignment</li>
645 * @param canvasHeight
646 * @return the number of residue columns in each width
648 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
650 int charHeight = av.getCharHeight();
653 * vertical space in pixels between wrapped widths of alignment
654 * - one character height, or two if scale above is drawn
656 wrappedSpaceAboveAlignment = charHeight
657 * (av.getScaleAboveWrapped() ? 2 : 1);
660 * compute height in pixels of the wrapped widths
661 * - start with space above plus sequences
663 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
664 wrappedRepeatHeightPx += av.getAlignment().getHeight()
668 * add annotations panel height if shown
669 * also gap between sequences and annotations
671 if (av.isShowAnnotation())
673 wrappedRepeatHeightPx += getAnnotationHeight();
674 wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
678 * number of visible widths (the last one may be part height),
679 * ensuring a part height includes at least one sequence
681 ViewportRanges ranges = av.getRanges();
682 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
683 int remainder = canvasHeight % wrappedRepeatHeightPx;
684 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
686 wrappedVisibleWidths++;
690 * compute width in residues; this also sets East and West label widths
692 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
695 * limit visibleWidths to not exceed width of alignment
697 int xMax = ranges.getVisibleAlignmentWidth();
698 int startToEnd = xMax - ranges.getStartRes();
699 int maxWidths = startToEnd / wrappedWidthInResidues;
700 if (startToEnd % wrappedWidthInResidues > 0)
704 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
706 return wrappedWidthInResidues;
710 * Draws one width of a wrapped alignment, including sequences and
711 * annnotations, if shown, but not scales or hidden column markers
717 * @param canvasHeight
719 protected void drawWrappedWidth(Graphics g, final int ypos,
720 final int startColumn, final int endColumn,
721 final int canvasHeight)
723 ViewportRanges ranges = av.getRanges();
724 int viewportWidth = ranges.getViewportWidth();
726 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
729 * move right before drawing by the width of the scale left (if any)
730 * plus column offset from left margin (usually zero, but may be non-zero
731 * when fast painting is drawing just a few columns)
733 int charWidth = av.getCharWidth();
734 int xOffset = labelWidthWest
735 + ((startColumn - ranges.getStartRes()) % viewportWidth)
738 g.translate(xOffset, 0);
741 * white fill the region to be drawn (so incremental fast paint doesn't
742 * scribble over an existing image)
744 g.setColor(Color.white);
745 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
746 wrappedRepeatHeightPx);
748 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
751 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
753 if (av.isShowAnnotation())
755 final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
756 g.translate(0, yShift);
757 if (annotations == null)
759 annotations = new AnnotationPanel(av);
762 annotations.renderer.drawComponent(annotations, av, g, -1,
763 startColumn, endx + 1);
764 g.translate(0, -yShift);
766 g.translate(-xOffset, 0);
770 * Draws scales left, right and above (if shown), and any hidden column
771 * markers, on all widths of the wrapped alignment
776 protected void drawWrappedDecorators(Graphics g, final int startColumn)
778 int charWidth = av.getCharWidth();
780 g.setFont(av.getFont());
782 g.setColor(Color.black);
784 int ypos = wrappedSpaceAboveAlignment;
785 ViewportRanges ranges = av.getRanges();
786 int viewportWidth = ranges.getViewportWidth();
787 int maxWidth = ranges.getVisibleAlignmentWidth();
789 int startCol = startColumn;
791 while (widthsDrawn < wrappedVisibleWidths)
793 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
795 if (av.getScaleLeftWrapped())
797 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
800 if (av.getScaleRightWrapped())
802 int x = labelWidthWest + viewportWidth * charWidth;
805 drawVerticalScale(g, startCol, endColumn, ypos, false);
810 * white fill region of scale above and hidden column markers
811 * (to support incremental fast paint of image)
813 g.translate(labelWidthWest, 0);
814 g.setColor(Color.white);
815 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
816 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
817 g.setColor(Color.black);
818 g.translate(-labelWidthWest, 0);
820 g.translate(labelWidthWest, 0);
822 if (av.getScaleAboveWrapped())
824 drawNorthScale(g, startCol, endColumn, ypos);
827 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
829 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
832 g.translate(-labelWidthWest, 0);
834 ypos += wrappedRepeatHeightPx;
835 startCol += viewportWidth;
841 * Draws markers (triangles) above hidden column positions between startColumn
849 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
850 int startColumn, int endColumn)
852 int charHeight = av.getCharHeight();
853 int charWidth = av.getCharWidth();
855 g.setColor(Color.blue);
857 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
859 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
863 res = it.next() - startColumn;
865 if (res < 0 || res > endColumn - startColumn + 1)
871 * draw a downward-pointing triangle at the hidden columns location
872 * (before the following visible column)
874 int xMiddle = res * charWidth;
875 int[] xPoints = new int[] { xMiddle - charHeight / 4,
876 xMiddle + charHeight / 4, xMiddle };
877 int yTop = ypos - (charHeight / 2);
878 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
879 g.fillPolygon(xPoints, yPoints, 3);
884 * Draw a selection group over a wrapped alignment
886 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
888 int canvasHeight, int startRes)
890 int charHeight = av.getCharHeight();
891 int charWidth = av.getCharWidth();
893 // height gap above each panel
894 int hgap = charHeight;
895 if (av.getScaleAboveWrapped())
900 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
902 int cHeight = av.getAlignment().getHeight() * charHeight;
904 int startx = startRes;
906 int ypos = hgap; // vertical offset
907 int maxwidth = av.getAlignment().getVisibleWidth();
909 // chop the wrapped alignment extent up into panel-sized blocks and treat
910 // each block as if it were a block from an unwrapped alignment
911 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
912 BasicStroke.JOIN_ROUND, 3f, new float[]
914 g.setColor(Color.RED);
915 while ((ypos <= canvasHeight) && (startx < maxwidth))
917 // set end value to be start + width, or maxwidth, whichever is smaller
918 endx = startx + cWidth - 1;
925 g.translate(labelWidthWest, 0);
927 drawUnwrappedSelection(g, group, startx, endx, 0,
928 av.getAlignment().getHeight() - 1,
931 g.translate(-labelWidthWest, 0);
933 // update vertical offset
934 ypos += cHeight + getAnnotationHeight() + hgap;
936 // update horizontal offset
939 g.setStroke(new BasicStroke());
942 int getAnnotationHeight()
944 if (!av.isShowAnnotation())
949 if (annotations == null)
951 annotations = new AnnotationPanel(av);
954 return annotations.adjustPanelHeight();
958 * Draws the visible region of the alignment on the graphics context. If there
959 * are hidden column markers in the visible region, then each sub-region
960 * between the markers is drawn separately, followed by the hidden column
964 * the graphics context, positioned at the first residue to be drawn
966 * offset of the first column to draw (0..)
968 * offset of the last column to draw (0..)
970 * offset of the first sequence to draw (0..)
972 * offset of the last sequence to draw (0..)
974 * vertical offset at which to draw (for wrapped alignments)
976 public void drawPanel(Graphics g1, final int startRes, final int endRes,
977 final int startSeq, final int endSeq, final int yOffset)
979 int charHeight = av.getCharHeight();
980 int charWidth = av.getCharWidth();
982 if (!av.hasHiddenColumns())
984 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
992 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
993 VisibleContigsIterator regions = hidden
994 .getVisContigsIterator(startRes, endRes + 1, true);
996 while (regions.hasNext())
998 int[] region = regions.next();
999 blockEnd = region[1];
1000 blockStart = region[0];
1003 * draw up to just before the next hidden region, or the end of
1004 * the visible region, whichever comes first
1006 g1.translate(screenY * charWidth, 0);
1008 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1011 * draw the downline of the hidden column marker (ScalePanel draws the
1012 * triangle on top) if we reached it
1014 if (av.getShowHiddenMarkers()
1015 && (regions.hasNext() || regions.endsAtHidden()))
1017 g1.setColor(Color.blue);
1019 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1020 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1021 (endSeq - startSeq + 1) * charHeight + yOffset);
1024 g1.translate(-screenY * charWidth, 0);
1025 screenY += blockEnd - blockStart + 1;
1032 * Draws a region of the visible alignment
1036 * offset of the first column in the visible region (0..)
1038 * offset of the last column in the visible region (0..)
1040 * offset of the first sequence in the visible region (0..)
1042 * offset of the last sequence in the visible region (0..)
1044 * vertical offset at which to draw (for wrapped alignments)
1046 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1047 int endSeq, int offset)
1049 int charHeight = av.getCharHeight();
1050 int charWidth = av.getCharWidth();
1052 g.setFont(av.getFont());
1053 seqRdr.prepare(g, av.isRenderGaps());
1057 // / First draw the sequences
1058 // ///////////////////////////
1059 for (int i = startSeq; i <= endSeq; i++)
1061 nextSeq = av.getAlignment().getSequenceAt(i);
1062 if (nextSeq == null)
1064 // occasionally, a race condition occurs such that the alignment row is
1068 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1069 startRes, endRes, offset + ((i - startSeq) * charHeight));
1071 if (av.isShowSequenceFeatures())
1073 fr.drawSequence(g, nextSeq, startRes, endRes,
1074 offset + ((i - startSeq) * charHeight), false);
1078 * highlight search Results once sequence has been drawn
1080 if (av.hasSearchResults())
1082 SearchResultsI searchResults = av.getSearchResults();
1083 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1085 if (visibleResults != null)
1087 for (int r = 0; r < visibleResults.length; r += 2)
1089 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1090 visibleResults[r + 1],
1091 (visibleResults[r] - startRes) * charWidth,
1092 offset + ((i - startSeq) * charHeight));
1098 if (av.getSelectionGroup() != null
1099 || av.getAlignment().getGroups().size() > 0)
1101 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1107 * Draws the outlines of any groups defined on the alignment (excluding the
1108 * current selection group, if any)
1117 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1118 int startSeq, int endSeq, int offset)
1120 Graphics2D g = (Graphics2D) g1;
1122 SequenceGroup group = null;
1123 int groupIndex = -1;
1125 if (av.getAlignment().getGroups().size() > 0)
1127 group = av.getAlignment().getGroups().get(0);
1135 g.setColor(group.getOutlineColour());
1136 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1140 if (groupIndex >= av.getAlignment().getGroups().size())
1144 group = av.getAlignment().getGroups().get(groupIndex);
1145 } while (groupIndex < av.getAlignment().getGroups().size());
1150 * Draws the outline of the current selection group (if any)
1158 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1159 int startSeq, int endSeq)
1161 SequenceGroup group = av.getSelectionGroup();
1167 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1168 BasicStroke.JOIN_ROUND, 3f, new float[]
1170 g.setColor(Color.RED);
1171 if (!av.getWrapAlignment())
1173 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1178 drawWrappedSelection(g, group, getWidth(), getHeight(),
1179 av.getRanges().getStartRes());
1181 g.setStroke(new BasicStroke());
1185 * Draw the cursor as a separate image and overlay
1188 * start residue of area to draw cursor in
1190 * end residue of area to draw cursor in
1192 * start sequence of area to draw cursor in
1194 * end sequence of are to draw cursor in
1195 * @return a transparent image of the same size as the sequence canvas, with
1196 * the cursor drawn on it, if any
1198 private void drawCursor(Graphics g, int startRes, int endRes,
1202 // convert the cursorY into a position on the visible alignment
1203 int cursor_ypos = cursorY;
1205 // don't do work unless we have to
1206 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1210 int startx = startRes;
1213 // convert the cursorX into a position on the visible alignment
1214 int cursor_xpos = av.getAlignment().getHiddenColumns()
1215 .absoluteToVisibleColumn(cursorX);
1217 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1220 if (av.getWrapAlignment())
1222 // work out the correct offsets for the cursor
1223 int charHeight = av.getCharHeight();
1224 int charWidth = av.getCharWidth();
1225 int canvasWidth = getWidth();
1226 int canvasHeight = getHeight();
1228 // height gap above each panel
1229 int hgap = charHeight;
1230 if (av.getScaleAboveWrapped())
1235 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1237 int cHeight = av.getAlignment().getHeight() * charHeight;
1239 endx = startx + cWidth - 1;
1240 int ypos = hgap; // vertical offset
1242 // iterate down the wrapped panels
1243 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1245 // update vertical offset
1246 ypos += cHeight + getAnnotationHeight() + hgap;
1248 // update horizontal offset
1250 endx = startx + cWidth - 1;
1253 xoffset = labelWidthWest;
1256 // now check if cursor is within range for x values
1257 if (cursor_xpos >= startx && cursor_xpos <= endx)
1259 // get the character the cursor is drawn at
1260 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1261 char s = seq.getCharAt(cursorX);
1263 seqRdr.drawCursor(g, s,
1264 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1265 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1273 * Draw a selection group over an unwrapped alignment
1276 * graphics object to draw with
1280 * start residue of area to draw
1282 * end residue of area to draw
1284 * start sequence of area to draw
1286 * end sequence of area to draw
1288 * vertical offset (used when called from wrapped alignment code)
1290 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1291 int startRes, int endRes, int startSeq, int endSeq, int offset)
1293 int charWidth = av.getCharWidth();
1295 if (!av.hasHiddenColumns())
1297 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1302 // package into blocks of visible columns
1307 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1308 VisibleContigsIterator regions = hidden
1309 .getVisContigsIterator(startRes, endRes + 1, true);
1310 while (regions.hasNext())
1312 int[] region = regions.next();
1313 blockEnd = region[1];
1314 blockStart = region[0];
1316 g.translate(screenY * charWidth, 0);
1317 drawPartialGroupOutline(g, group,
1318 blockStart, blockEnd, startSeq, endSeq, offset);
1320 g.translate(-screenY * charWidth, 0);
1321 screenY += blockEnd - blockStart + 1;
1327 * Draws part of a selection group outline
1335 * @param verticalOffset
1337 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1338 int startRes, int endRes, int startSeq, int endSeq,
1341 int charHeight = av.getCharHeight();
1342 int charWidth = av.getCharWidth();
1343 int visWidth = (endRes - startRes + 1) * charWidth;
1347 boolean inGroup = false;
1352 List<SequenceI> seqs = group.getSequences(null);
1354 // position of start residue of group relative to startRes, in pixels
1355 int sx = (group.getStartRes() - startRes) * charWidth;
1357 // width of group in pixels
1358 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1361 if (!(sx + xwidth < 0 || sx > visWidth))
1363 for (i = startSeq; i <= endSeq; i++)
1365 sy = verticalOffset + (i - startSeq) * charHeight;
1367 if ((sx <= (endRes - startRes) * charWidth)
1368 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1371 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1373 bottom = sy + charHeight;
1378 if (((top == -1) && (i == 0)) || !seqs
1379 .contains(av.getAlignment().getSequenceAt(i - 1)))
1390 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1391 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1393 // reset top and bottom
1401 sy = verticalOffset + ((i - startSeq) * charHeight);
1402 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1403 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1409 * Draw horizontal selection group boundaries at top and bottom positions
1412 * graphics object to draw on
1418 * visWidth maximum available width
1420 * position to draw top of group at
1422 * position to draw bottom of group at
1424 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1425 int visWidth, int top, int bottom)
1435 // don't let width extend beyond current block, or group extent
1437 if (startx + width >= visWidth)
1439 width = visWidth - startx;
1444 g.drawLine(startx, top, startx + width, top);
1449 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1454 * Draw vertical lines at sx and sx+xwidth providing they lie within
1458 * graphics object to draw on
1464 * visWidth maximum available width
1470 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1473 // if start position is visible, draw vertical line to left of
1475 if (sx >= 0 && sx < visWidth)
1477 g.drawLine(sx, oldY, sx, sy);
1480 // if end position is visible, draw vertical line to right of
1482 if (sx + xwidth < visWidth)
1484 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1489 * Highlights search results in the visible region by rendering as white text
1490 * on a black background. Any previous highlighting is removed. Answers true
1491 * if any highlight was left on the visible alignment (so status bar should be
1492 * set to match), else false. This method does _not_ set the 'fastPaint' flag,
1493 * so allows the next repaint to update the whole display.
1498 public boolean highlightSearchResults(SearchResultsI results)
1500 return highlightSearchResults(results, false);
1505 * Highlights search results in the visible region by rendering as white text
1506 * on a black background. Any previous highlighting is removed. Answers true
1507 * if any highlight was left on the visible alignment (so status bar should be
1508 * set to match), else false.
1510 * Optionally, set the 'fastPaint' flag for a faster redraw if only the
1511 * highlighted regions are modified. This speeds up highlighting across linked
1514 * Currently fastPaint is not implemented for scrolled wrapped alignments. If
1515 * a wrapped alignment had to be scrolled to show the highlighted region, then
1516 * it should be fully redrawn, otherwise a fast paint can be performed. This
1517 * argument could be removed if fast paint of scrolled wrapped alignment is
1518 * coded in future (JAL-2609).
1521 * @param doFastPaint
1522 * if true, sets a flag so the next repaint only redraws the modified
1526 public boolean highlightSearchResults(SearchResultsI results,
1527 boolean doFastPaint)
1533 boolean wrapped = av.getWrapAlignment();
1536 fastPaint = doFastPaint;
1537 fastpainting = fastPaint;
1540 * to avoid redrawing the whole visible region, we instead
1541 * redraw just the minimal regions to remove previous highlights
1544 SearchResultsI previous = av.getSearchResults();
1545 av.setSearchResults(results);
1546 boolean redrawn = false;
1547 boolean drawn = false;
1550 redrawn = drawMappedPositionsWrapped(previous);
1551 drawn = drawMappedPositionsWrapped(results);
1556 redrawn = drawMappedPositions(previous);
1557 drawn = drawMappedPositions(results);
1562 * if highlights were either removed or added, repaint
1570 * return true only if highlights were added
1576 fastpainting = false;
1581 * Redraws the minimal rectangle in the visible region (if any) that includes
1582 * mapped positions of the given search results. Whether or not positions are
1583 * highlighted depends on the SearchResults set on the Viewport. This allows
1584 * this method to be called to either clear or set highlighting. Answers true
1585 * if any positions were drawn (in which case a repaint is still required),
1591 protected boolean drawMappedPositions(SearchResultsI results)
1593 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
1599 * calculate the minimal rectangle to redraw that
1600 * includes both new and existing search results
1602 int firstSeq = Integer.MAX_VALUE;
1604 int firstCol = Integer.MAX_VALUE;
1606 boolean matchFound = false;
1608 ViewportRanges ranges = av.getRanges();
1609 int firstVisibleColumn = ranges.getStartRes();
1610 int lastVisibleColumn = ranges.getEndRes();
1611 AlignmentI alignment = av.getAlignment();
1612 if (av.hasHiddenColumns())
1614 firstVisibleColumn = alignment.getHiddenColumns()
1615 .visibleToAbsoluteColumn(firstVisibleColumn);
1616 lastVisibleColumn = alignment.getHiddenColumns()
1617 .visibleToAbsoluteColumn(lastVisibleColumn);
1620 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1621 .getEndSeq(); seqNo++)
1623 SequenceI seq = alignment.getSequenceAt(seqNo);
1625 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1627 if (visibleResults != null)
1629 for (int i = 0; i < visibleResults.length - 1; i += 2)
1631 int firstMatchedColumn = visibleResults[i];
1632 int lastMatchedColumn = visibleResults[i + 1];
1633 if (firstMatchedColumn <= lastVisibleColumn
1634 && lastMatchedColumn >= firstVisibleColumn)
1637 * found a search results match in the visible region -
1638 * remember the first and last sequence matched, and the first
1639 * and last visible columns in the matched positions
1642 firstSeq = Math.min(firstSeq, seqNo);
1643 lastSeq = Math.max(lastSeq, seqNo);
1644 firstMatchedColumn = Math.max(firstMatchedColumn,
1645 firstVisibleColumn);
1646 lastMatchedColumn = Math.min(lastMatchedColumn,
1648 firstCol = Math.min(firstCol, firstMatchedColumn);
1649 lastCol = Math.max(lastCol, lastMatchedColumn);
1657 if (av.hasHiddenColumns())
1659 firstCol = alignment.getHiddenColumns()
1660 .absoluteToVisibleColumn(firstCol);
1661 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1663 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1664 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1665 Graphics gg = img.getGraphics();
1666 gg.translate(transX, transY);
1667 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1668 gg.translate(-transX, -transY);
1676 public void propertyChange(PropertyChangeEvent evt)
1678 String eventName = evt.getPropertyName();
1680 // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
1681 // logic, emphasizing no check for ENDRES or ENDSEQ
1683 // Both scrolling and resizing change viewport ranges: scrolling changes
1684 // both start and end points, but resize only changes end values.
1685 // Here we only want to fastpaint on a scroll, with resize using a normal
1686 // paint, so scroll events are identified as changes to the horizontal or
1687 // vertical start value.
1689 // Make sure we're not trying to draw a panel
1690 // larger than the visible window
1694 case SequenceGroup.SEQ_GROUP_CHANGED:
1698 case ViewportRanges.MOVE_VIEWPORT:
1702 case ViewportRanges.STARTSEQ:
1703 // meaning STARTOREND
1704 // typically scroll, but possibly just the end changed
1705 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1707 case ViewportRanges.ENDRES:
1708 case ViewportRanges.ENDSEQ:
1709 // meaning second event along with "START" -- ENDONLY,NOTSTART
1712 case ViewportRanges.STARTRES:
1713 // meaning STARTOREND
1714 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1716 case ViewportRanges.STARTRESANDSEQ:
1717 scrollX = ((int[]) evt.getNewValue())[0]
1718 - ((int[]) evt.getOldValue())[0];
1721 ViewportRanges vpRanges = av.getRanges();
1722 int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1723 scrollX = Math.max(Math.min(scrollX, range), -range);
1724 // only STARTRES or STARTRESANDSEQ:
1725 if (av.getWrapAlignment())
1727 fastPaintWrapped(scrollX);
1731 fastPaint(scrollX, 0);
1734 // BH 2019.07.27 was:
1735 // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1737 // fastPaint = true;
1741 // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1743 // fastPaint = false;
1744 // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
1749 // if (eventName.equals(ViewportRanges.STARTRES)
1750 // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1752 // // Make sure we're not trying to draw a panel
1753 // // larger than the visible window
1754 // if (eventName.equals(ViewportRanges.STARTRES))
1756 // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1760 // scrollX = ((int[]) evt.getNewValue())[0]
1761 // - ((int[]) evt.getOldValue())[0];
1763 // ViewportRanges vpRanges = av.getRanges();
1765 // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
1766 // if (scrollX > range)
1770 // else if (scrollX < -range)
1772 // scrollX = -range;
1775 // Both scrolling and resizing change viewport ranges: scrolling changes
1776 // both start and end points, but resize only changes end values.
1777 // Here we only want to fastpaint on a scroll, with resize using a normal
1778 // paint, so scroll events are identified as changes to the horizontal or
1779 // vertical start value.
1780 // BH 2019.07.27 was:
1781 // if (eventName.equals(ViewportRanges.STARTRES))
1783 // if (av.getWrapAlignment())
1785 // fastPaintWrapped(scrollX);
1789 // fastPaint(scrollX, 0);
1792 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1795 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1797 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1799 // if (av.getWrapAlignment())
1801 // fastPaintWrapped(scrollX);
1805 // fastPaint(scrollX, 0);
1811 // else if (eventName.equals(ViewportRanges.STARTSEQ))
1814 // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1816 // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1818 // if (av.getWrapAlignment())
1820 // fastPaintWrapped(scrollX);
1826 * Does a minimal update of the image for a scroll movement. This method
1827 * handles scroll movements of up to one width of the wrapped alignment (one
1828 * click in the vertical scrollbar). Larger movements (for example after a
1829 * scroll to highlight a mapped position) trigger a full redraw instead.
1832 * number of positions scrolled (right if positive, left if negative)
1834 protected void fastPaintWrapped(int scrollX)
1836 ViewportRanges ranges = av.getRanges();
1838 if (Math.abs(scrollX) >= ranges.getViewportWidth())
1841 * shift of one view width or more is
1842 * overcomplicated to handle in this method
1849 if (fastpainting || img == null)
1855 fastpainting = true;
1860 Graphics gg = img.getGraphics();
1862 calculateWrappedGeometry(getWidth(), getHeight());
1865 * relocate the regions of the alignment that are still visible
1867 shiftWrappedAlignment(-scrollX);
1870 * add new columns (sequence, annotation)
1871 * - at top left if scrollX < 0
1872 * - at right of last two widths if scrollX > 0
1876 int startRes = ranges.getStartRes();
1877 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1878 - scrollX - 1, getHeight());
1882 fastPaintWrappedAddRight(scrollX);
1886 * draw all scales (if shown) and hidden column markers
1888 drawWrappedDecorators(gg, ranges.getStartRes());
1895 fastpainting = false;
1900 * Draws the specified number of columns at the 'end' (bottom right) of a
1901 * wrapped alignment view, including sequences and annotations if shown, but
1902 * not scales. Also draws the same number of columns at the right hand end of
1903 * the second last width shown, if the last width is not full height (so
1904 * cannot simply be copied from the graphics image).
1908 protected void fastPaintWrappedAddRight(int columns)
1915 Graphics gg = img.getGraphics();
1917 ViewportRanges ranges = av.getRanges();
1918 int viewportWidth = ranges.getViewportWidth();
1919 int charWidth = av.getCharWidth();
1922 * draw full height alignment in the second last row, last columns, if the
1923 * last row was not full height
1925 int visibleWidths = wrappedVisibleWidths;
1926 int canvasHeight = getHeight();
1927 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1929 if (lastWidthPartHeight)
1931 int widthsAbove = Math.max(0, visibleWidths - 2);
1932 int ypos = wrappedRepeatHeightPx * widthsAbove
1933 + wrappedSpaceAboveAlignment;
1934 int endRes = ranges.getEndRes();
1935 endRes += widthsAbove * viewportWidth;
1936 int startRes = endRes - columns;
1937 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1941 * white fill first to erase annotations
1945 gg.translate(xOffset, 0);
1946 gg.setColor(Color.white);
1947 gg.fillRect(labelWidthWest, ypos,
1948 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1949 gg.translate(-xOffset, 0);
1951 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1956 * draw newly visible columns in last wrapped width (none if we
1957 * have reached the end of the alignment)
1958 * y-offset for drawing last width is height of widths above,
1961 int widthsAbove = visibleWidths - 1;
1962 int ypos = wrappedRepeatHeightPx * widthsAbove
1963 + wrappedSpaceAboveAlignment;
1964 int endRes = ranges.getEndRes();
1965 endRes += widthsAbove * viewportWidth;
1966 int startRes = endRes - columns + 1;
1969 * white fill first to erase annotations
1971 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1973 gg.translate(xOffset, 0);
1974 gg.setColor(Color.white);
1975 int width = viewportWidth * charWidth - xOffset;
1976 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1977 gg.translate(-xOffset, 0);
1979 gg.setFont(av.getFont());
1980 gg.setColor(Color.black);
1982 if (startRes < ranges.getVisibleAlignmentWidth())
1984 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1988 * and finally, white fill any space below the visible alignment
1990 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1991 if (heightBelow > 0)
1993 gg.setColor(Color.white);
1994 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
2000 * Shifts the visible alignment by the specified number of columns - left if
2001 * negative, right if positive. Copies and moves sequences and annotations (if
2002 * shown). Scales, hidden column markers and any newly visible columns must be
2007 protected void shiftWrappedAlignment(int positions)
2014 Graphics gg = img.getGraphics();
2016 int charWidth = av.getCharWidth();
2018 int canvasHeight = getHeight();
2019 ViewportRanges ranges = av.getRanges();
2020 int viewportWidth = ranges.getViewportWidth();
2021 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
2023 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
2024 int xMax = ranges.getVisibleAlignmentWidth();
2029 * shift right (after scroll left)
2030 * for each wrapped width (starting with the last), copy (width-positions)
2031 * columns from the left margin to the right margin, and copy positions
2032 * columns from the right margin of the row above (if any) to the
2033 * left margin of the current row
2037 * get y-offset of last wrapped width, first row of sequences
2039 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
2040 y += wrappedSpaceAboveAlignment;
2041 int copyFromLeftStart = labelWidthWest;
2042 int copyFromRightStart = copyFromLeftStart + widthToCopy;
2047 * shift 'widthToCopy' residues by 'positions' places to the right
2049 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
2050 positions * charWidth, 0);
2054 * copy 'positions' residue from the row above (right hand end)
2055 * to this row's left hand end
2057 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
2058 positions * charWidth, heightToCopy, -widthToCopy,
2059 wrappedRepeatHeightPx);
2062 y -= wrappedRepeatHeightPx;
2068 * shift left (after scroll right)
2069 * for each wrapped width (starting with the first), copy (width-positions)
2070 * columns from the right margin to the left margin, and copy positions
2071 * columns from the left margin of the row below (if any) to the
2072 * right margin of the current row
2074 int xpos = av.getRanges().getStartRes();
2075 int y = wrappedSpaceAboveAlignment;
2076 int copyFromRightStart = labelWidthWest - positions * charWidth;
2078 while (y < canvasHeight)
2080 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
2081 positions * charWidth, 0);
2082 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
2083 && (xpos + viewportWidth <= xMax))
2085 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
2086 * charWidth, heightToCopy, widthToCopy,
2087 -wrappedRepeatHeightPx);
2089 y += wrappedRepeatHeightPx;
2090 xpos += viewportWidth;
2098 * Redraws any positions in the search results in the visible region of a
2099 * wrapped alignment. Any highlights are drawn depending on the search results
2100 * set on the Viewport, not the <code>results</code> argument. This allows
2101 * this method to be called either to clear highlights (passing the previous
2102 * search results), or to draw new highlights.
2107 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
2109 if ((results == null) || (img == null)) // JAL-2784 check gg is not null
2113 int charHeight = av.getCharHeight();
2115 boolean matchFound = false;
2117 calculateWrappedGeometry(getWidth(), getHeight());
2118 int wrappedWidth = av.getWrappedWidth();
2119 int wrappedHeight = wrappedRepeatHeightPx;
2121 ViewportRanges ranges = av.getRanges();
2122 int canvasHeight = getHeight();
2123 int repeats = canvasHeight / wrappedHeight;
2124 if (canvasHeight / wrappedHeight > 0)
2129 int firstVisibleColumn = ranges.getStartRes();
2130 int lastVisibleColumn = ranges.getStartRes() + repeats
2131 * ranges.getViewportWidth() - 1;
2133 AlignmentI alignment = av.getAlignment();
2134 if (av.hasHiddenColumns())
2136 firstVisibleColumn = alignment.getHiddenColumns()
2137 .visibleToAbsoluteColumn(firstVisibleColumn);
2138 lastVisibleColumn = alignment.getHiddenColumns()
2139 .visibleToAbsoluteColumn(lastVisibleColumn);
2142 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2145 Graphics gg = img.getGraphics();
2147 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2148 .getEndSeq(); seqNo++)
2150 SequenceI seq = alignment.getSequenceAt(seqNo);
2152 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2154 if (visibleResults != null)
2156 for (int i = 0; i < visibleResults.length - 1; i += 2)
2158 int firstMatchedColumn = visibleResults[i];
2159 int lastMatchedColumn = visibleResults[i + 1];
2160 if (firstMatchedColumn <= lastVisibleColumn
2161 && lastMatchedColumn >= firstVisibleColumn)
2164 * found a search results match in the visible region
2166 firstMatchedColumn = Math.max(firstMatchedColumn,
2167 firstVisibleColumn);
2168 lastMatchedColumn = Math.min(lastMatchedColumn,
2172 * draw each mapped position separately (as contiguous positions may
2173 * wrap across lines)
2175 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2177 int displayColumn = mappedPos;
2178 if (av.hasHiddenColumns())
2180 displayColumn = alignment.getHiddenColumns()
2181 .absoluteToVisibleColumn(displayColumn);
2185 * transX: offset from left edge of canvas to residue position
2187 int transX = labelWidthWest
2188 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2189 * av.getCharWidth();
2192 * transY: offset from top edge of canvas to residue position
2194 int transY = gapHeight;
2195 transY += (displayColumn - ranges.getStartRes())
2196 / wrappedWidth * wrappedHeight;
2197 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2200 * yOffset is from graphics origin to start of visible region
2202 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2203 if (transY < getHeight())
2206 gg.translate(transX, transY);
2207 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2209 gg.translate(-transX, -transY);
2223 * Answers the width in pixels of the left scale labels (0 if not shown)
2227 int getLabelWidthWest()
2229 return labelWidthWest;