2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.util.Comparison;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
35 import java.awt.BasicStroke;
36 import java.awt.BorderLayout;
37 import java.awt.Color;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Graphics2D;
41 import java.awt.RenderingHints;
42 import java.awt.Shape;
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.JComponent;
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 public class SeqCanvas extends JComponent implements ViewportListenerI
58 private static final String ZEROS = "0000000000";
60 final FeatureRenderer fr;
70 private final SequenceRenderer seqRdr;
72 private boolean fastPaint = false;
74 private boolean fastpainting = false;
76 private AnnotationPanel annotations;
79 * measurements for drawing a wrapped alignment
81 private int labelWidthEast; // label right width in pixels if shown
83 private int labelWidthWest; // label left width in pixels if shown
85 private int wrappedSpaceAboveAlignment; // gap between widths
87 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
89 private int wrappedVisibleWidths; // number of wrapped widths displayed
91 private Graphics2D gg;
94 * Creates a new SeqCanvas object.
98 public SeqCanvas(AlignmentPanel ap)
101 fr = new FeatureRenderer(ap);
102 seqRdr = new SequenceRenderer(av);
103 setLayout(new BorderLayout());
104 PaintRefresher.Register(this, av.getSequenceSetId());
105 setBackground(Color.white);
107 av.getRanges().addPropertyChangeListener(this);
110 public SequenceRenderer getSequenceRenderer()
115 public FeatureRenderer getFeatureRenderer()
121 * Draws the scale above a region of a wrapped alignment, consisting of a
122 * column number every major interval (10 columns).
125 * the graphics context to draw on, positioned at the start (bottom
126 * left) of the line on which to draw any scale marks
128 * start alignment column (0..)
130 * end alignment column (0..)
132 * y offset to draw at
134 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 int charHeight = av.getCharHeight();
137 int charWidth = av.getCharWidth();
140 * white fill the scale space (for the fastPaint case)
142 g.setColor(Color.white);
143 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
144 charHeight * 3 / 2 + 2);
145 g.setColor(Color.black);
147 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
149 for (ScaleMark mark : marks)
151 int mpos = mark.column; // (i - startx - 1)
156 String mstring = mark.text;
162 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
166 * draw a tick mark below the column number, centred on the column;
167 * height of tick mark is 4 pixels less than half a character
169 int xpos = (mpos * charWidth) + (charWidth / 2);
170 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
176 * Draw the scale to the left or right of a wrapped alignment
179 * graphics context, positioned at the start of the scale to be drawn
181 * first column of wrapped width (0.. excluding any hidden columns)
183 * last column of wrapped width (0.. excluding any hidden columns)
185 * vertical offset at which to begin the scale
187 * if true, scale is left of residues, if false, scale is right
189 void drawVerticalScale(Graphics g, final int startx, final int endx,
190 final int ypos, final boolean left)
192 int charHeight = av.getCharHeight();
193 int charWidth = av.getCharWidth();
195 int yPos = ypos + charHeight;
199 if (av.hasHiddenColumns())
201 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
202 startX = hiddenColumns.visibleToAbsoluteColumn(startx);
203 endX = hiddenColumns.visibleToAbsoluteColumn(endx);
205 FontMetrics fm = getFontMetrics(av.getFont());
207 for (int i = 0; i < av.getAlignment().getHeight(); i++)
209 SequenceI seq = av.getAlignment().getSequenceAt(i);
212 * find sequence position of first non-gapped position -
213 * to the right if scale left, to the left if scale right
215 int index = left ? startX : endX;
217 while (index >= startX && index <= endX)
219 if (!Comparison.isGap(seq.getCharAt(index)))
221 value = seq.findPosition(index);
235 * white fill the space for the scale
237 g.setColor(Color.white);
238 int y = (yPos + (i * charHeight)) - (charHeight / 5);
239 // fillRect origin is top left of rectangle
240 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
246 * draw scale value, right justified within its width less half a
247 * character width padding on the right
249 int labelSpace = left ? labelWidthWest : labelWidthEast;
250 labelSpace -= charWidth / 2; // leave space to the right
251 String valueAsString = String.valueOf(value);
252 int labelLength = fm.stringWidth(valueAsString);
253 int xOffset = labelSpace - labelLength;
254 g.setColor(Color.black);
255 g.drawString(valueAsString, xOffset, y);
261 * Does a fast paint of an alignment in response to a scroll. Most of the
262 * visible region is simply copied and shifted, and then any newly visible
263 * columns or rows are drawn. The scroll may be horizontal or vertical, but
264 * not both at once. Scrolling may be the result of
266 * <li>dragging a scroll bar</li>
267 * <li>clicking in the scroll bar</li>
268 * <li>scrolling by trackpad, middle mouse button, or other device</li>
269 * <li>by moving the box in the Overview window</li>
270 * <li>programmatically to make a highlighted position visible</li>
274 * columns to shift right (positive) or left (negative)
276 * rows to shift down (positive) or up (negative)
278 public void fastPaint(int horizontal, int vertical)
280 if (fastpainting || gg == null || img == null)
289 int charHeight = av.getCharHeight();
290 int charWidth = av.getCharWidth();
292 ViewportRanges ranges = av.getRanges();
293 int startRes = ranges.getStartRes();
294 int endRes = ranges.getEndRes();
295 int startSeq = ranges.getStartSeq();
296 int endSeq = ranges.getEndSeq();
300 gg.copyArea(horizontal * charWidth, vertical * charHeight,
301 img.getWidth(), img.getHeight(), -horizontal * charWidth,
302 -vertical * charHeight);
304 if (horizontal > 0) // scrollbar pulled right, image to the left
306 transX = (endRes - startRes - horizontal) * charWidth;
307 startRes = endRes - horizontal;
309 else if (horizontal < 0)
311 endRes = startRes - horizontal;
314 if (vertical > 0) // scroll down
316 startSeq = endSeq - vertical;
318 if (startSeq < ranges.getStartSeq())
319 { // ie scrolling too fast, more than a page at a time
320 startSeq = ranges.getStartSeq();
324 transY = img.getHeight() - ((vertical + 1) * charHeight);
327 else if (vertical < 0)
329 endSeq = startSeq - vertical;
331 if (endSeq > ranges.getEndSeq())
333 endSeq = ranges.getEndSeq();
337 gg.translate(transX, transY);
338 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
339 gg.translate(-transX, -transY);
341 // Call repaint on alignment panel so that repaints from other alignment
342 // panel components can be aggregated. Otherwise performance of the
343 // overview window and others may be adversely affected.
344 av.getAlignPanel().repaint();
347 fastpainting = false;
352 public void paintComponent(Graphics g)
354 super.paintComponent(g);
356 int charHeight = av.getCharHeight();
357 int charWidth = av.getCharWidth();
359 ViewportRanges ranges = av.getRanges();
361 int width = getWidth();
362 int height = getHeight();
364 width -= (width % charWidth);
365 height -= (height % charHeight);
367 drawSelectionGroup((Graphics2D) g,
368 ranges.getStartRes(), ranges.getEndRes(),
369 ranges.getStartSeq(), ranges.getEndSeq());
371 if ((img != null) && (fastPaint
372 || (getVisibleRect().width != g.getClipBounds().width)
373 || (getVisibleRect().height != g.getClipBounds().height)))
375 g.drawImage(img, 0, 0, this);
377 drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
378 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
382 else if (width > 0 && height > 0)
385 * img is a cached version of the last view we drew, if any
386 * if we have no img or the size has changed, make a new one
388 if (img == null || width != img.getWidth()
389 || height != img.getHeight())
391 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
392 gg = (Graphics2D) img.getGraphics();
393 gg.setFont(av.getFont());
398 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
399 RenderingHints.VALUE_ANTIALIAS_ON);
402 gg.setColor(Color.white);
403 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
405 if (av.getWrapAlignment())
407 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
411 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
412 ranges.getStartSeq(), ranges.getEndSeq(), 0);
415 drawSelectionGroup(gg, ranges.getStartRes(),
416 ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
418 g.drawImage(img, 0, 0, this);
423 drawCursor(g, ranges.getStartRes(), ranges.getEndRes(),
424 ranges.getStartSeq(), ranges.getEndSeq());
429 * Draw an alignment panel for printing
432 * Graphics object to draw with
434 * start residue of print area
436 * end residue of print area
438 * start sequence of print area
440 * end sequence of print area
442 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
443 int startSeq, int endSeq)
445 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
447 drawSelectionGroup((Graphics2D) g1, startRes, endRes,
452 * Draw a wrapped alignment panel for printing
455 * Graphics object to draw with
457 * width of drawing area
458 * @param canvasHeight
459 * height of drawing area
461 * start residue of print area
463 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
464 int canvasHeight, int startRes)
466 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
468 SequenceGroup group = av.getSelectionGroup();
471 drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
477 * Returns the visible width of the canvas in residues, after allowing for
478 * East or West scales (if shown)
481 * the width in pixels (possibly including scales)
485 public int getWrappedCanvasWidth(int canvasWidth)
487 int charWidth = av.getCharWidth();
489 FontMetrics fm = getFontMetrics(av.getFont());
493 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
495 labelWidth = getLabelWidth(fm);
498 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
500 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
502 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
506 * Returns a pixel width sufficient to show the largest sequence coordinate
507 * (end position) in the alignment, calculated as the FontMetrics width of
508 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
509 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
510 * half a character width space on either side.
515 protected int getLabelWidth(FontMetrics fm)
518 * find the biggest sequence end position we need to show
519 * (note this is not necessarily the sequence length)
522 AlignmentI alignment = av.getAlignment();
523 for (int i = 0; i < alignment.getHeight(); i++)
525 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
529 for (int i = maxWidth; i > 0; i /= 10)
534 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
538 * Draws as many widths of a wrapped alignment as can fit in the visible
543 * available width in pixels
544 * @param canvasHeight
545 * available height in pixels
547 * the first column (0...) of the alignment to draw
549 public void drawWrappedPanel(Graphics g, int canvasWidth,
550 int canvasHeight, final int startColumn)
552 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
555 av.setWrappedWidth(wrappedWidthInResidues);
557 ViewportRanges ranges = av.getRanges();
558 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
560 // we need to call this again to make sure the startColumn +
561 // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
563 calculateWrappedGeometry(canvasWidth, canvasHeight);
566 * draw one width at a time (excluding any scales or annotation shown),
567 * until we have run out of either alignment or vertical space available
569 int ypos = wrappedSpaceAboveAlignment;
570 int maxWidth = ranges.getVisibleAlignmentWidth();
572 int start = startColumn;
573 int currentWidth = 0;
574 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
577 .min(maxWidth, start + wrappedWidthInResidues - 1);
578 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
579 ypos += wrappedRepeatHeightPx;
580 start += wrappedWidthInResidues;
584 drawWrappedDecorators(g, startColumn);
588 * Calculates and saves values needed when rendering a wrapped alignment.
589 * These depend on many factors, including
591 * <li>canvas width and height</li>
592 * <li>number of visible sequences, and height of annotations if shown</li>
593 * <li>font and character width</li>
594 * <li>whether scales are shown left, right or above the alignment</li>
598 * @param canvasHeight
599 * @return the number of residue columns in each width
601 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
603 int charHeight = av.getCharHeight();
606 * vertical space in pixels between wrapped widths of alignment
607 * - one character height, or two if scale above is drawn
609 wrappedSpaceAboveAlignment = charHeight
610 * (av.getScaleAboveWrapped() ? 2 : 1);
613 * height in pixels of the wrapped widths
615 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
617 wrappedRepeatHeightPx += av.getAlignment().getHeight()
619 // add annotations panel height if shown
620 wrappedRepeatHeightPx += getAnnotationHeight();
623 * number of visible widths (the last one may be part height),
624 * ensuring a part height includes at least one sequence
626 ViewportRanges ranges = av.getRanges();
627 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
628 int remainder = canvasHeight % wrappedRepeatHeightPx;
629 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
631 wrappedVisibleWidths++;
635 * compute width in residues; this also sets East and West label widths
637 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
640 * limit visibleWidths to not exceed width of alignment
642 int xMax = ranges.getVisibleAlignmentWidth();
643 int startToEnd = xMax - ranges.getStartRes();
644 int maxWidths = startToEnd / wrappedWidthInResidues;
645 if (startToEnd % wrappedWidthInResidues > 0)
649 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
651 return wrappedWidthInResidues;
655 * Draws one width of a wrapped alignment, including sequences and
656 * annnotations, if shown, but not scales or hidden column markers
662 * @param canvasHeight
664 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
665 int endColumn, int canvasHeight)
667 ViewportRanges ranges = av.getRanges();
668 int viewportWidth = ranges.getViewportWidth();
670 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
673 * move right before drawing by the width of the scale left (if any)
674 * plus column offset from left margin (usually zero, but may be non-zero
675 * when fast painting is drawing just a few columns)
677 int charWidth = av.getCharWidth();
678 int xOffset = labelWidthWest
679 + ((startColumn - ranges.getStartRes()) % viewportWidth)
681 g.translate(xOffset, 0);
683 // When printing we have an extra clipped region,
684 // the Printable page which we need to account for here
685 Shape clip = g.getClip();
689 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
693 g.setClip(0, (int) clip.getBounds().getY(),
694 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
698 * white fill the region to be drawn (so incremental fast paint doesn't
699 * scribble over an existing image)
701 g.setColor(Color.white);
702 g.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
703 wrappedRepeatHeightPx);
705 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
708 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
710 if (av.isShowAnnotation())
712 g.translate(0, cHeight + ypos + 3);
713 if (annotations == null)
715 annotations = new AnnotationPanel(av);
718 annotations.renderer.drawComponent(annotations, av, g, -1,
719 startColumn, endx + 1);
720 g.translate(0, -cHeight - ypos - 3);
723 g.translate(-xOffset, 0);
727 * Draws scales left, right and above (if shown), and any hidden column
728 * markers, on all widths of the wrapped alignment
733 protected void drawWrappedDecorators(Graphics g, final int startColumn)
735 int charWidth = av.getCharWidth();
737 g.setFont(av.getFont());
738 g.setColor(Color.black);
740 int ypos = wrappedSpaceAboveAlignment;
741 ViewportRanges ranges = av.getRanges();
742 int viewportWidth = ranges.getViewportWidth();
743 int maxWidth = ranges.getVisibleAlignmentWidth();
745 int startCol = startColumn;
747 while (widthsDrawn < wrappedVisibleWidths)
749 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
751 if (av.getScaleLeftWrapped())
753 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
756 if (av.getScaleRightWrapped())
758 int x = labelWidthWest + viewportWidth * charWidth;
760 drawVerticalScale(g, startCol, endColumn, ypos, false);
765 * white fill region of scale above and hidden column markers
766 * (to support incremental fast paint of image)
768 g.translate(labelWidthWest, 0);
769 g.setColor(Color.white);
770 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
771 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
772 g.setColor(Color.black);
773 g.translate(-labelWidthWest, 0);
775 g.translate(labelWidthWest, 0);
777 if (av.getScaleAboveWrapped())
779 drawNorthScale(g, startCol, endColumn, ypos);
782 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
784 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
787 g.translate(-labelWidthWest, 0);
789 ypos += wrappedRepeatHeightPx;
790 startCol += viewportWidth;
796 * Draws markers (triangles) above hidden column positions between startColumn
804 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
805 int startColumn, int endColumn)
807 int charHeight = av.getCharHeight();
808 int charWidth = av.getCharWidth();
810 g.setColor(Color.blue);
812 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
814 Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
818 res = it.next() - startColumn;
820 if (res < 0 || res > endColumn - startColumn + 1)
826 * draw a downward-pointing triangle at the hidden columns location
827 * (before the following visible column)
829 int xMiddle = res * charWidth;
830 int[] xPoints = new int[] { xMiddle - charHeight / 4,
831 xMiddle + charHeight / 4, xMiddle };
832 int yTop = ypos - (charHeight / 2);
833 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
834 g.fillPolygon(xPoints, yPoints, 3);
839 * Draw a selection group over a wrapped alignment
841 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
843 int canvasHeight, int startRes)
845 int charHeight = av.getCharHeight();
846 int charWidth = av.getCharWidth();
848 // height gap above each panel
849 int hgap = charHeight;
850 if (av.getScaleAboveWrapped())
855 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
857 int cHeight = av.getAlignment().getHeight() * charHeight;
859 int startx = startRes;
861 int ypos = hgap; // vertical offset
862 int maxwidth = av.getAlignment().getWidth();
864 if (av.hasHiddenColumns())
866 maxwidth = av.getAlignment().getHiddenColumns()
867 .absoluteToVisibleColumn(maxwidth);
870 // chop the wrapped alignment extent up into panel-sized blocks and treat
871 // each block as if it were a block from an unwrapped alignment
872 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
873 BasicStroke.JOIN_ROUND, 3f, new float[]
875 g.setColor(Color.RED);
876 while ((ypos <= canvasHeight) && (startx < maxwidth))
878 // set end value to be start + width, or maxwidth, whichever is smaller
879 endx = startx + cWidth - 1;
886 g.translate(labelWidthWest, 0);
888 drawUnwrappedSelection(g, group, startx, endx, 0,
889 av.getAlignment().getHeight() - 1,
892 g.translate(-labelWidthWest, 0);
894 // update vertical offset
895 ypos += cHeight + getAnnotationHeight() + hgap;
897 // update horizontal offset
900 g.setStroke(new BasicStroke());
903 int getAnnotationHeight()
905 if (!av.isShowAnnotation())
910 if (annotations == null)
912 annotations = new AnnotationPanel(av);
915 return annotations.adjustPanelHeight();
919 * Draws the visible region of the alignment on the graphics context. If there
920 * are hidden column markers in the visible region, then each sub-region
921 * between the markers is drawn separately, followed by the hidden column
925 * the graphics context, positioned at the first residue to be drawn
927 * offset of the first column to draw (0..)
929 * offset of the last column to draw (0..)
931 * offset of the first sequence to draw (0..)
933 * offset of the last sequence to draw (0..)
935 * vertical offset at which to draw (for wrapped alignments)
937 public void drawPanel(Graphics g1, final int startRes, final int endRes,
938 final int startSeq, final int endSeq, final int yOffset)
940 int charHeight = av.getCharHeight();
941 int charWidth = av.getCharWidth();
943 if (!av.hasHiddenColumns())
945 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
953 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
954 VisibleContigsIterator regions = hidden
955 .getVisContigsIterator(startRes, endRes + 1, true);
957 while (regions.hasNext())
959 int[] region = regions.next();
960 blockEnd = region[1];
961 blockStart = region[0];
964 * draw up to just before the next hidden region, or the end of
965 * the visible region, whichever comes first
967 g1.translate(screenY * charWidth, 0);
969 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
972 * draw the downline of the hidden column marker (ScalePanel draws the
973 * triangle on top) if we reached it
975 if (av.getShowHiddenMarkers()
976 && (regions.hasNext() || regions.endsAtHidden()))
978 g1.setColor(Color.blue);
980 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
981 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
982 (endSeq - startSeq + 1) * charHeight + yOffset);
985 g1.translate(-screenY * charWidth, 0);
986 screenY += blockEnd - blockStart + 1;
993 * Draws a region of the visible alignment
997 * offset of the first column in the visible region (0..)
999 * offset of the last column in the visible region (0..)
1001 * offset of the first sequence in the visible region (0..)
1003 * offset of the last sequence in the visible region (0..)
1005 * vertical offset at which to draw (for wrapped alignments)
1007 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1008 int endSeq, int offset)
1010 int charHeight = av.getCharHeight();
1011 int charWidth = av.getCharWidth();
1013 g.setFont(av.getFont());
1014 seqRdr.prepare(g, av.isRenderGaps());
1018 // / First draw the sequences
1019 // ///////////////////////////
1020 for (int i = startSeq; i <= endSeq; i++)
1022 nextSeq = av.getAlignment().getSequenceAt(i);
1023 if (nextSeq == null)
1025 // occasionally, a race condition occurs such that the alignment row is
1029 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1030 startRes, endRes, offset + ((i - startSeq) * charHeight));
1032 if (av.isShowSequenceFeatures())
1034 fr.drawSequence(g, nextSeq, startRes, endRes,
1035 offset + ((i - startSeq) * charHeight), false);
1039 * highlight search Results once sequence has been drawn
1041 if (av.hasSearchResults())
1043 SearchResultsI searchResults = av.getSearchResults();
1044 int[] visibleResults = searchResults.getResults(nextSeq, startRes,
1046 if (visibleResults != null)
1048 for (int r = 0; r < visibleResults.length; r += 2)
1050 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1051 visibleResults[r + 1],
1052 (visibleResults[r] - startRes) * charWidth,
1053 offset + ((i - startSeq) * charHeight));
1059 if (av.getSelectionGroup() != null
1060 || av.getAlignment().getGroups().size() > 0)
1062 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1068 * Draws the outlines of any groups defined on the alignment (excluding the
1069 * current selection group, if any)
1078 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1079 int startSeq, int endSeq, int offset)
1081 Graphics2D g = (Graphics2D) g1;
1083 SequenceGroup group = null;
1084 int groupIndex = -1;
1086 if (av.getAlignment().getGroups().size() > 0)
1088 group = av.getAlignment().getGroups().get(0);
1094 g.setColor(group.getOutlineColour());
1098 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1103 if (groupIndex >= av.getAlignment().getGroups().size())
1108 group = av.getAlignment().getGroups().get(groupIndex);
1110 } while (groupIndex < av.getAlignment().getGroups().size());
1117 * Draws the outline of the current selection group (if any)
1125 private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
1126 int startSeq, int endSeq)
1128 SequenceGroup group = av.getSelectionGroup();
1134 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1135 BasicStroke.JOIN_ROUND, 3f, new float[]
1137 g.setColor(Color.RED);
1138 if (!av.getWrapAlignment())
1140 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1145 drawWrappedSelection(g, group, getWidth(), getHeight(),
1146 av.getRanges().getStartRes());
1148 g.setStroke(new BasicStroke());
1152 * Draw the cursor as a separate image and overlay
1155 * start residue of area to draw cursor in
1157 * end residue of area to draw cursor in
1159 * start sequence of area to draw cursor in
1161 * end sequence of are to draw cursor in
1162 * @return a transparent image of the same size as the sequence canvas, with
1163 * the cursor drawn on it, if any
1165 private void drawCursor(Graphics g, int startRes, int endRes,
1169 // convert the cursorY into a position on the visible alignment
1170 int cursor_ypos = cursorY;
1172 // don't do work unless we have to
1173 if (cursor_ypos >= startSeq && cursor_ypos <= endSeq)
1177 int startx = startRes;
1180 // convert the cursorX into a position on the visible alignment
1181 int cursor_xpos = av.getAlignment().getHiddenColumns()
1182 .absoluteToVisibleColumn(cursorX);
1184 if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
1187 if (av.getWrapAlignment())
1189 // work out the correct offsets for the cursor
1190 int charHeight = av.getCharHeight();
1191 int charWidth = av.getCharWidth();
1192 int canvasWidth = getWidth();
1193 int canvasHeight = getHeight();
1195 // height gap above each panel
1196 int hgap = charHeight;
1197 if (av.getScaleAboveWrapped())
1202 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
1204 int cHeight = av.getAlignment().getHeight() * charHeight;
1206 endx = startx + cWidth - 1;
1207 int ypos = hgap; // vertical offset
1209 // iterate down the wrapped panels
1210 while ((ypos <= canvasHeight) && (endx < cursor_xpos))
1212 // update vertical offset
1213 ypos += cHeight + getAnnotationHeight() + hgap;
1215 // update horizontal offset
1217 endx = startx + cWidth - 1;
1220 xoffset = labelWidthWest;
1223 // now check if cursor is within range for x values
1224 if (cursor_xpos >= startx && cursor_xpos <= endx)
1226 // get the character the cursor is drawn at
1227 SequenceI seq = av.getAlignment().getSequenceAt(cursorY);
1228 char s = seq.getCharAt(cursorX);
1230 seqRdr.drawCursor(g, s,
1231 xoffset + (cursor_xpos - startx) * av.getCharWidth(),
1232 yoffset + (cursor_ypos - startSeq) * av.getCharHeight());
1240 * Draw a selection group over an unwrapped alignment
1243 * graphics object to draw with
1247 * start residue of area to draw
1249 * end residue of area to draw
1251 * start sequence of area to draw
1253 * end sequence of area to draw
1255 * vertical offset (used when called from wrapped alignment code)
1257 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1258 int startRes, int endRes, int startSeq, int endSeq, int offset)
1260 int charWidth = av.getCharWidth();
1262 if (!av.hasHiddenColumns())
1264 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1269 // package into blocks of visible columns
1274 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1275 VisibleContigsIterator regions = hidden
1276 .getVisContigsIterator(startRes, endRes + 1, true);
1277 while (regions.hasNext())
1279 int[] region = regions.next();
1280 blockEnd = region[1];
1281 blockStart = region[0];
1283 g.translate(screenY * charWidth, 0);
1284 drawPartialGroupOutline(g, group,
1285 blockStart, blockEnd, startSeq, endSeq, offset);
1287 g.translate(-screenY * charWidth, 0);
1288 screenY += blockEnd - blockStart + 1;
1294 * Draws part of a selection group outline
1302 * @param verticalOffset
1304 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1305 int startRes, int endRes, int startSeq, int endSeq,
1308 int charHeight = av.getCharHeight();
1309 int charWidth = av.getCharWidth();
1310 int visWidth = (endRes - startRes + 1) * charWidth;
1314 boolean inGroup = false;
1319 List<SequenceI> seqs = group.getSequences(null);
1321 // position of start residue of group relative to startRes, in pixels
1322 int sx = (group.getStartRes() - startRes) * charWidth;
1324 // width of group in pixels
1325 int xwidth = (((group.getEndRes() + 1) - group.getStartRes())
1328 if (!(sx + xwidth < 0 || sx > visWidth))
1330 for (i = startSeq; i <= endSeq; i++)
1332 sy = verticalOffset + (i - startSeq) * charHeight;
1334 if ((sx <= (endRes - startRes) * charWidth)
1335 && seqs.contains(av.getAlignment().getSequenceAt(i)))
1338 && !seqs.contains(av.getAlignment().getSequenceAt(i + 1)))
1340 bottom = sy + charHeight;
1345 if (((top == -1) && (i == 0)) || !seqs
1346 .contains(av.getAlignment().getSequenceAt(i - 1)))
1357 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1358 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1360 // reset top and bottom
1368 sy = verticalOffset + ((i - startSeq) * charHeight);
1369 drawVerticals(g, sx, xwidth, visWidth, oldY, sy);
1370 drawHorizontals(g, sx, xwidth, visWidth, top, bottom);
1376 * Draw horizontal selection group boundaries at top and bottom positions
1379 * graphics object to draw on
1385 * visWidth maximum available width
1387 * position to draw top of group at
1389 * position to draw bottom of group at
1391 private void drawHorizontals(Graphics2D g, int sx, int xwidth,
1392 int visWidth, int top, int bottom)
1402 // don't let width extend beyond current block, or group extent
1404 if (startx + width >= visWidth)
1406 width = visWidth - startx;
1411 g.drawLine(startx, top, startx + width, top);
1416 g.drawLine(startx, bottom - 1, startx + width, bottom - 1);
1421 * Draw vertical lines at sx and sx+xwidth providing they lie within
1425 * graphics object to draw on
1431 * visWidth maximum available width
1437 private void drawVerticals(Graphics2D g, int sx, int xwidth, int visWidth,
1440 // if start position is visible, draw vertical line to left of
1442 if (sx >= 0 && sx < visWidth)
1444 g.drawLine(sx, oldY, sx, sy);
1447 // if end position is visible, draw vertical line to right of
1449 if (sx + xwidth < visWidth)
1451 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1456 * Highlights search results in the visible region by rendering as white text
1457 * on a black background. Any previous highlighting is removed. Answers true
1458 * if any highlight was left on the visible alignment (so status bar should be
1459 * set to match), else false.
1461 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1462 * alignment had to be scrolled to show the highlighted region, then it should
1463 * be fully redrawn, otherwise a fast paint can be performed. This argument
1464 * could be removed if fast paint of scrolled wrapped alignment is coded in
1465 * future (JAL-2609).
1468 * @param noFastPaint
1471 public boolean highlightSearchResults(SearchResultsI results,
1472 boolean noFastPaint)
1478 boolean wrapped = av.getWrapAlignment();
1481 fastPaint = !noFastPaint;
1482 fastpainting = fastPaint;
1485 * to avoid redrawing the whole visible region, we instead
1486 * redraw just the minimal regions to remove previous highlights
1489 SearchResultsI previous = av.getSearchResults();
1490 av.setSearchResults(results);
1491 boolean redrawn = false;
1492 boolean drawn = false;
1495 redrawn = drawMappedPositionsWrapped(previous);
1496 drawn = drawMappedPositionsWrapped(results);
1501 redrawn = drawMappedPositions(previous);
1502 drawn = drawMappedPositions(results);
1507 * if highlights were either removed or added, repaint
1515 * return true only if highlights were added
1521 fastpainting = false;
1526 * Redraws the minimal rectangle in the visible region (if any) that includes
1527 * mapped positions of the given search results. Whether or not positions are
1528 * highlighted depends on the SearchResults set on the Viewport. This allows
1529 * this method to be called to either clear or set highlighting. Answers true
1530 * if any positions were drawn (in which case a repaint is still required),
1536 protected boolean drawMappedPositions(SearchResultsI results)
1538 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1544 * calculate the minimal rectangle to redraw that
1545 * includes both new and existing search results
1547 int firstSeq = Integer.MAX_VALUE;
1549 int firstCol = Integer.MAX_VALUE;
1551 boolean matchFound = false;
1553 ViewportRanges ranges = av.getRanges();
1554 int firstVisibleColumn = ranges.getStartRes();
1555 int lastVisibleColumn = ranges.getEndRes();
1556 AlignmentI alignment = av.getAlignment();
1557 if (av.hasHiddenColumns())
1559 firstVisibleColumn = alignment.getHiddenColumns()
1560 .visibleToAbsoluteColumn(firstVisibleColumn);
1561 lastVisibleColumn = alignment.getHiddenColumns()
1562 .visibleToAbsoluteColumn(lastVisibleColumn);
1565 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1566 .getEndSeq(); seqNo++)
1568 SequenceI seq = alignment.getSequenceAt(seqNo);
1570 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1572 if (visibleResults != null)
1574 for (int i = 0; i < visibleResults.length - 1; i += 2)
1576 int firstMatchedColumn = visibleResults[i];
1577 int lastMatchedColumn = visibleResults[i + 1];
1578 if (firstMatchedColumn <= lastVisibleColumn
1579 && lastMatchedColumn >= firstVisibleColumn)
1582 * found a search results match in the visible region -
1583 * remember the first and last sequence matched, and the first
1584 * and last visible columns in the matched positions
1587 firstSeq = Math.min(firstSeq, seqNo);
1588 lastSeq = Math.max(lastSeq, seqNo);
1589 firstMatchedColumn = Math.max(firstMatchedColumn,
1590 firstVisibleColumn);
1591 lastMatchedColumn = Math.min(lastMatchedColumn,
1593 firstCol = Math.min(firstCol, firstMatchedColumn);
1594 lastCol = Math.max(lastCol, lastMatchedColumn);
1602 if (av.hasHiddenColumns())
1604 firstCol = alignment.getHiddenColumns()
1605 .absoluteToVisibleColumn(firstCol);
1606 lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
1608 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1609 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1610 gg.translate(transX, transY);
1611 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1612 gg.translate(-transX, -transY);
1619 public void propertyChange(PropertyChangeEvent evt)
1621 String eventName = evt.getPropertyName();
1623 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1629 else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
1637 if (eventName.equals(ViewportRanges.STARTRES)
1638 || eventName.equals(ViewportRanges.STARTRESANDSEQ))
1640 // Make sure we're not trying to draw a panel
1641 // larger than the visible window
1642 if (eventName.equals(ViewportRanges.STARTRES))
1644 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1648 scrollX = ((int[]) evt.getNewValue())[0]
1649 - ((int[]) evt.getOldValue())[0];
1651 ViewportRanges vpRanges = av.getRanges();
1653 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1654 if (scrollX > range)
1658 else if (scrollX < -range)
1663 // Both scrolling and resizing change viewport ranges: scrolling changes
1664 // both start and end points, but resize only changes end values.
1665 // Here we only want to fastpaint on a scroll, with resize using a normal
1666 // paint, so scroll events are identified as changes to the horizontal or
1667 // vertical start value.
1668 if (eventName.equals(ViewportRanges.STARTRES))
1670 if (av.getWrapAlignment())
1672 fastPaintWrapped(scrollX);
1676 fastPaint(scrollX, 0);
1679 else if (eventName.equals(ViewportRanges.STARTSEQ))
1682 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1684 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1686 if (av.getWrapAlignment())
1688 fastPaintWrapped(scrollX);
1692 fastPaint(scrollX, 0);
1695 else if (eventName.equals(ViewportRanges.STARTSEQ))
1698 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1700 else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
1702 if (av.getWrapAlignment())
1704 fastPaintWrapped(scrollX);
1710 * Does a minimal update of the image for a scroll movement. This method
1711 * handles scroll movements of up to one width of the wrapped alignment (one
1712 * click in the vertical scrollbar). Larger movements (for example after a
1713 * scroll to highlight a mapped position) trigger a full redraw instead.
1716 * number of positions scrolled (right if positive, left if negative)
1718 protected void fastPaintWrapped(int scrollX)
1720 ViewportRanges ranges = av.getRanges();
1722 if (Math.abs(scrollX) > ranges.getViewportWidth())
1725 * shift of more than one view width is
1726 * overcomplicated to handle in this method
1733 if (fastpainting || gg == null)
1739 fastpainting = true;
1743 calculateWrappedGeometry(getWidth(), getHeight());
1746 * relocate the regions of the alignment that are still visible
1748 shiftWrappedAlignment(-scrollX);
1751 * add new columns (sequence, annotation)
1752 * - at top left if scrollX < 0
1753 * - at right of last two widths if scrollX > 0
1757 int startRes = ranges.getStartRes();
1758 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1759 - scrollX - 1, getHeight());
1763 fastPaintWrappedAddRight(scrollX);
1767 * draw all scales (if shown) and hidden column markers
1769 drawWrappedDecorators(gg, ranges.getStartRes());
1774 fastpainting = false;
1779 * Draws the specified number of columns at the 'end' (bottom right) of a
1780 * wrapped alignment view, including sequences and annotations if shown, but
1781 * not scales. Also draws the same number of columns at the right hand end of
1782 * the second last width shown, if the last width is not full height (so
1783 * cannot simply be copied from the graphics image).
1787 protected void fastPaintWrappedAddRight(int columns)
1794 ViewportRanges ranges = av.getRanges();
1795 int viewportWidth = ranges.getViewportWidth();
1796 int charWidth = av.getCharWidth();
1799 * draw full height alignment in the second last row, last columns, if the
1800 * last row was not full height
1802 int visibleWidths = wrappedVisibleWidths;
1803 int canvasHeight = getHeight();
1804 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1806 if (lastWidthPartHeight)
1808 int widthsAbove = Math.max(0, visibleWidths - 2);
1809 int ypos = wrappedRepeatHeightPx * widthsAbove
1810 + wrappedSpaceAboveAlignment;
1811 int endRes = ranges.getEndRes();
1812 endRes += widthsAbove * viewportWidth;
1813 int startRes = endRes - columns;
1814 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1818 * white fill first to erase annotations
1820 gg.translate(xOffset, 0);
1821 gg.setColor(Color.white);
1822 gg.fillRect(labelWidthWest, ypos,
1823 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1824 gg.translate(-xOffset, 0);
1826 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1830 * draw newly visible columns in last wrapped width (none if we
1831 * have reached the end of the alignment)
1832 * y-offset for drawing last width is height of widths above,
1835 int widthsAbove = visibleWidths - 1;
1836 int ypos = wrappedRepeatHeightPx * widthsAbove
1837 + wrappedSpaceAboveAlignment;
1838 int endRes = ranges.getEndRes();
1839 endRes += widthsAbove * viewportWidth;
1840 int startRes = endRes - columns + 1;
1843 * white fill first to erase annotations
1845 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1847 gg.translate(xOffset, 0);
1848 gg.setColor(Color.white);
1849 int width = viewportWidth * charWidth - xOffset;
1850 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1851 gg.translate(-xOffset, 0);
1853 gg.setFont(av.getFont());
1854 gg.setColor(Color.black);
1856 if (startRes < ranges.getVisibleAlignmentWidth())
1858 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1862 * and finally, white fill any space below the visible alignment
1864 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1865 if (heightBelow > 0)
1867 gg.setColor(Color.white);
1868 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1873 * Shifts the visible alignment by the specified number of columns - left if
1874 * negative, right if positive. Copies and moves sequences and annotations (if
1875 * shown). Scales, hidden column markers and any newly visible columns must be
1880 protected void shiftWrappedAlignment(int positions)
1886 int charWidth = av.getCharWidth();
1888 int canvasHeight = getHeight();
1889 ViewportRanges ranges = av.getRanges();
1890 int viewportWidth = ranges.getViewportWidth();
1891 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1893 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1894 int xMax = ranges.getVisibleAlignmentWidth();
1899 * shift right (after scroll left)
1900 * for each wrapped width (starting with the last), copy (width-positions)
1901 * columns from the left margin to the right margin, and copy positions
1902 * columns from the right margin of the row above (if any) to the
1903 * left margin of the current row
1907 * get y-offset of last wrapped width, first row of sequences
1909 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1910 y += wrappedSpaceAboveAlignment;
1911 int copyFromLeftStart = labelWidthWest;
1912 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1916 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1917 positions * charWidth, 0);
1920 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1921 positions * charWidth, heightToCopy, -widthToCopy,
1922 wrappedRepeatHeightPx);
1925 y -= wrappedRepeatHeightPx;
1931 * shift left (after scroll right)
1932 * for each wrapped width (starting with the first), copy (width-positions)
1933 * columns from the right margin to the left margin, and copy positions
1934 * columns from the left margin of the row below (if any) to the
1935 * right margin of the current row
1937 int xpos = av.getRanges().getStartRes();
1938 int y = wrappedSpaceAboveAlignment;
1939 int copyFromRightStart = labelWidthWest - positions * charWidth;
1941 while (y < canvasHeight)
1943 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1944 positions * charWidth, 0);
1945 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1946 && (xpos + viewportWidth <= xMax))
1948 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1949 * charWidth, heightToCopy, widthToCopy,
1950 -wrappedRepeatHeightPx);
1953 y += wrappedRepeatHeightPx;
1954 xpos += viewportWidth;
1961 * Redraws any positions in the search results in the visible region of a
1962 * wrapped alignment. Any highlights are drawn depending on the search results
1963 * set on the Viewport, not the <code>results</code> argument. This allows
1964 * this method to be called either to clear highlights (passing the previous
1965 * search results), or to draw new highlights.
1970 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1972 if ((results == null) || (gg == null)) // JAL-2784 check gg is not null
1976 int charHeight = av.getCharHeight();
1978 boolean matchFound = false;
1980 calculateWrappedGeometry(getWidth(), getHeight());
1981 int wrappedWidth = av.getWrappedWidth();
1982 int wrappedHeight = wrappedRepeatHeightPx;
1984 ViewportRanges ranges = av.getRanges();
1985 int canvasHeight = getHeight();
1986 int repeats = canvasHeight / wrappedHeight;
1987 if (canvasHeight / wrappedHeight > 0)
1992 int firstVisibleColumn = ranges.getStartRes();
1993 int lastVisibleColumn = ranges.getStartRes() + repeats
1994 * ranges.getViewportWidth() - 1;
1996 AlignmentI alignment = av.getAlignment();
1997 if (av.hasHiddenColumns())
1999 firstVisibleColumn = alignment.getHiddenColumns()
2000 .visibleToAbsoluteColumn(firstVisibleColumn);
2001 lastVisibleColumn = alignment.getHiddenColumns()
2002 .visibleToAbsoluteColumn(lastVisibleColumn);
2005 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2007 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2008 .getEndSeq(); seqNo++)
2010 SequenceI seq = alignment.getSequenceAt(seqNo);
2012 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2014 if (visibleResults != null)
2016 for (int i = 0; i < visibleResults.length - 1; i += 2)
2018 int firstMatchedColumn = visibleResults[i];
2019 int lastMatchedColumn = visibleResults[i + 1];
2020 if (firstMatchedColumn <= lastVisibleColumn
2021 && lastMatchedColumn >= firstVisibleColumn)
2024 * found a search results match in the visible region
2026 firstMatchedColumn = Math.max(firstMatchedColumn,
2027 firstVisibleColumn);
2028 lastMatchedColumn = Math.min(lastMatchedColumn,
2032 * draw each mapped position separately (as contiguous positions may
2033 * wrap across lines)
2035 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2037 int displayColumn = mappedPos;
2038 if (av.hasHiddenColumns())
2040 displayColumn = alignment.getHiddenColumns()
2041 .absoluteToVisibleColumn(displayColumn);
2045 * transX: offset from left edge of canvas to residue position
2047 int transX = labelWidthWest
2048 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2049 * av.getCharWidth();
2052 * transY: offset from top edge of canvas to residue position
2054 int transY = gapHeight;
2055 transY += (displayColumn - ranges.getStartRes())
2056 / wrappedWidth * wrappedHeight;
2057 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2060 * yOffset is from graphics origin to start of visible region
2062 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2063 if (transY < getHeight())
2066 gg.translate(transX, transY);
2067 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2069 gg.translate(-transX, -transY);
2081 * Answers the width in pixels of the left scale labels (0 if not shown)
2085 int getLabelWidthWest()
2087 return labelWidthWest;