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.HiddenColumns.VisibleBlocksVisBoundsIterator;
26 import jalview.datamodel.SearchResultsI;
27 import jalview.datamodel.SequenceGroup;
28 import jalview.datamodel.SequenceI;
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.AlphaComposite;
36 import java.awt.BasicStroke;
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.FontMetrics;
40 import java.awt.Graphics;
41 import java.awt.Graphics2D;
42 import java.awt.RenderingHints;
43 import java.awt.Shape;
44 import java.awt.image.BufferedImage;
45 import java.beans.PropertyChangeEvent;
46 import java.util.Iterator;
47 import java.util.List;
49 import javax.swing.JComponent;
52 * The Swing component on which the alignment sequences, and annotations (if
53 * shown), are drawn. This includes scales above, left and right (if shown) in
54 * Wrapped mode, but not the scale above in Unwrapped mode.
57 public class SeqCanvas extends JComponent implements ViewportListenerI
59 private static final String ZEROS = "0000000000";
61 final FeatureRenderer fr;
71 private final SequenceRenderer seqRdr;
73 private boolean fastPaint = false;
75 private boolean fastpainting = false;
77 private AnnotationPanel annotations;
80 * measurements for drawing a wrapped alignment
82 private int labelWidthEast; // label right width in pixels if shown
84 private int labelWidthWest; // label left width in pixels if shown
86 private int wrappedSpaceAboveAlignment; // gap between widths
88 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
90 private int wrappedVisibleWidths; // number of wrapped widths displayed
92 private Graphics2D gg;
95 * Creates a new SeqCanvas object.
99 public SeqCanvas(AlignmentPanel ap)
102 fr = new FeatureRenderer(ap);
103 seqRdr = new SequenceRenderer(av);
104 setLayout(new BorderLayout());
105 PaintRefresher.Register(this, av.getSequenceSetId());
106 setBackground(Color.white);
108 av.getRanges().addPropertyChangeListener(this);
111 public SequenceRenderer getSequenceRenderer()
116 public FeatureRenderer getFeatureRenderer()
122 * Draws the scale above a region of a wrapped alignment, consisting of a
123 * column number every major interval (10 columns).
126 * the graphics context to draw on, positioned at the start (bottom
127 * left) of the line on which to draw any scale marks
129 * start alignment column (0..)
131 * end alignment column (0..)
133 * y offset to draw at
135 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
137 int charHeight = av.getCharHeight();
138 int charWidth = av.getCharWidth();
141 * white fill the scale space (for the fastPaint case)
143 g.setColor(Color.white);
144 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
145 charHeight * 3 / 2 + 2);
146 g.setColor(Color.black);
148 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
150 for (ScaleMark mark : marks)
152 int mpos = mark.column; // (i - startx - 1)
157 String mstring = mark.text;
163 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
167 * draw a tick mark below the column number, centred on the column;
168 * height of tick mark is 4 pixels less than half a character
170 int xpos = (mpos * charWidth) + (charWidth / 2);
171 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
177 * Draw the scale to the left or right of a wrapped alignment
180 * graphics context, positioned at the start of the scale to be drawn
182 * first column of wrapped width (0.. excluding any hidden columns)
184 * last column of wrapped width (0.. excluding any hidden columns)
186 * vertical offset at which to begin the scale
188 * if true, scale is left of residues, if false, scale is right
190 void drawVerticalScale(Graphics g, final int startx, final int endx,
191 final int ypos, final boolean left)
193 int charHeight = av.getCharHeight();
194 int charWidth = av.getCharWidth();
196 int yPos = ypos + charHeight;
200 if (av.hasHiddenColumns())
202 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
203 startX = hiddenColumns.adjustForHiddenColumns(startx);
204 endX = hiddenColumns.adjustForHiddenColumns(endx);
206 FontMetrics fm = getFontMetrics(av.getFont());
208 for (int i = 0; i < av.getAlignment().getHeight(); i++)
210 SequenceI seq = av.getAlignment().getSequenceAt(i);
213 * find sequence position of first non-gapped position -
214 * to the right if scale left, to the left if scale right
216 int index = left ? startX : endX;
218 while (index >= startX && index <= endX)
220 if (!Comparison.isGap(seq.getCharAt(index)))
222 value = seq.findPosition(index);
236 * white fill the space for the scale
238 g.setColor(Color.white);
239 int y = (yPos + (i * charHeight)) - (charHeight / 5);
240 // fillRect origin is top left of rectangle
241 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
247 * draw scale value, right justified within its width less half a
248 * character width padding on the right
250 int labelSpace = left ? labelWidthWest : labelWidthEast;
251 labelSpace -= charWidth / 2; // leave space to the right
252 String valueAsString = String.valueOf(value);
253 int labelLength = fm.stringWidth(valueAsString);
254 int xOffset = labelSpace - labelLength;
255 g.setColor(Color.black);
256 g.drawString(valueAsString, xOffset, y);
262 * Does a fast paint of an alignment in response to a scroll. Most of the
263 * visible region is simply copied and shifted, and then any newly visible
264 * columns or rows are drawn. The scroll may be horizontal or vertical, but
265 * not both at once. Scrolling may be the result of
267 * <li>dragging a scroll bar</li>
268 * <li>clicking in the scroll bar</li>
269 * <li>scrolling by trackpad, middle mouse button, or other device</li>
270 * <li>by moving the box in the Overview window</li>
271 * <li>programmatically to make a highlighted position visible</li>
275 * columns to shift right (positive) or left (negative)
277 * rows to shift down (positive) or up (negative)
279 public void fastPaint(int horizontal, int vertical)
281 if (fastpainting || gg == null || img == null)
290 int charHeight = av.getCharHeight();
291 int charWidth = av.getCharWidth();
293 ViewportRanges ranges = av.getRanges();
294 int startRes = ranges.getStartRes();
295 int endRes = ranges.getEndRes();
296 int startSeq = ranges.getStartSeq();
297 int endSeq = ranges.getEndSeq();
301 gg.copyArea(horizontal * charWidth, vertical * charHeight,
302 img.getWidth(), img.getHeight(), -horizontal * charWidth,
303 -vertical * charHeight);
305 if (horizontal > 0) // scrollbar pulled right, image to the left
307 transX = (endRes - startRes - horizontal) * charWidth;
308 startRes = endRes - horizontal;
310 else if (horizontal < 0)
312 endRes = startRes - horizontal;
314 else 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);
344 fastpainting = false;
349 public void paintComponent(Graphics g)
351 super.paintComponent(g);
353 int charHeight = av.getCharHeight();
354 int charWidth = av.getCharWidth();
356 ViewportRanges ranges = av.getRanges();
358 int width = getWidth();
359 int height = getHeight();
361 width -= (width % charWidth);
362 height -= (height % charHeight);
364 // selectImage is the selection group outline image
365 BufferedImage selectImage = drawSelectionGroup(
366 ranges.getStartRes(), ranges.getEndRes(),
367 ranges.getStartSeq(), ranges.getEndSeq());
369 if ((img != null) && (fastPaint
370 || (getVisibleRect().width != g.getClipBounds().width)
371 || (getVisibleRect().height != g.getClipBounds().height)))
373 BufferedImage lcimg = buildLocalImage(selectImage);
374 g.drawImage(lcimg, 0, 0, this);
377 else if ((width > 0) && (height > 0))
379 // img is a cached version of the last view we drew, if any
380 // if we have no img or the size has changed, make a new one
381 if (img == null || width != img.getWidth()
382 || height != img.getHeight())
389 gg = (Graphics2D) img.getGraphics();
390 gg.setFont(av.getFont());
395 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
396 RenderingHints.VALUE_ANTIALIAS_ON);
399 gg.setColor(Color.white);
400 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
402 if (av.getWrapAlignment())
404 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
408 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
409 ranges.getStartSeq(), ranges.getEndSeq(), 0);
412 // lcimg is a local *copy* of img which we'll draw selectImage on top of
413 BufferedImage lcimg = buildLocalImage(selectImage);
414 g.drawImage(lcimg, 0, 0, this);
419 * Draw an alignment panel for printing
422 * Graphics object to draw with
424 * start residue of print area
426 * end residue of print area
428 * start sequence of print area
430 * end sequence of print area
432 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
433 int startSeq, int endSeq)
435 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
437 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
439 if (selectImage != null)
441 ((Graphics2D) g1).setComposite(AlphaComposite
442 .getInstance(AlphaComposite.SRC_OVER));
443 g1.drawImage(selectImage, 0, 0, this);
448 * Draw a wrapped alignment panel for printing
451 * Graphics object to draw with
453 * width of drawing area
454 * @param canvasHeight
455 * height of drawing area
457 * start residue of print area
459 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
460 int canvasHeight, int startRes)
462 SequenceGroup group = av.getSelectionGroup();
464 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
468 BufferedImage selectImage = null;
471 selectImage = new BufferedImage(canvasWidth, canvasHeight,
472 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
473 } catch (OutOfMemoryError er)
476 System.err.println("Print image OutOfMemory Error.\n" + er);
477 new OOMWarning("Creating wrapped alignment image for printing", er);
479 if (selectImage != null)
481 Graphics2D g2 = selectImage.createGraphics();
482 setupSelectionGroup(g2, selectImage);
483 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
487 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
488 g.drawImage(selectImage, 0, 0, this);
495 * Make a local image by combining the cached image img
498 private BufferedImage buildLocalImage(BufferedImage selectImage)
500 // clone the cached image
501 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
503 Graphics2D g2d = lcimg.createGraphics();
504 g2d.drawImage(img, 0, 0, null);
506 // overlay selection group on lcimg
507 if (selectImage != null)
510 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
511 g2d.drawImage(selectImage, 0, 0, this);
519 * Set up a buffered image of the correct height and size for the sequence canvas
521 private BufferedImage setupImage()
523 BufferedImage lcimg = null;
525 int charWidth = av.getCharWidth();
526 int charHeight = av.getCharHeight();
528 int width = getWidth();
529 int height = getHeight();
531 width -= (width % charWidth);
532 height -= (height % charHeight);
534 if ((width < 1) || (height < 1))
541 lcimg = new BufferedImage(width, height,
542 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
543 } catch (OutOfMemoryError er)
547 "Group image OutOfMemory Redraw Error.\n" + er);
548 new OOMWarning("Creating alignment image for display", er);
557 * Returns the visible width of the canvas in residues, after allowing for
558 * East or West scales (if shown)
561 * the width in pixels (possibly including scales)
565 public int getWrappedCanvasWidth(int canvasWidth)
567 int charWidth = av.getCharWidth();
569 FontMetrics fm = getFontMetrics(av.getFont());
573 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
575 labelWidth = getLabelWidth(fm);
578 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
580 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
582 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
586 * Returns a pixel width sufficient to show the largest sequence coordinate
587 * (end position) in the alignment, calculated as the FontMetrics width of
588 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
589 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
590 * half a character width space on either side.
595 protected int getLabelWidth(FontMetrics fm)
598 * find the biggest sequence end position we need to show
599 * (note this is not necessarily the sequence length)
602 AlignmentI alignment = av.getAlignment();
603 for (int i = 0; i < alignment.getHeight(); i++)
605 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
609 for (int i = maxWidth; i > 0; i /= 10)
614 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
618 * Draws as many widths of a wrapped alignment as can fit in the visible
623 * available width in pixels
624 * @param canvasHeight
625 * available height in pixels
627 * the first column (0...) of the alignment to draw
629 public void drawWrappedPanel(Graphics g, int canvasWidth,
630 int canvasHeight, final int startColumn)
632 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
635 av.setWrappedWidth(wrappedWidthInResidues);
637 ViewportRanges ranges = av.getRanges();
638 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
641 * draw one width at a time (including any scales or annotation shown),
642 * until we have run out of either alignment or vertical space available
644 int ypos = wrappedSpaceAboveAlignment;
645 int maxWidth = ranges.getVisibleAlignmentWidth();
647 int start = startColumn;
648 int currentWidth = 0;
649 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
652 .min(maxWidth, start + wrappedWidthInResidues - 1);
653 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
654 ypos += wrappedRepeatHeightPx;
655 start += wrappedWidthInResidues;
659 drawWrappedDecorators(g, startColumn);
663 * Calculates and saves values needed when rendering a wrapped alignment.
664 * These depend on many factors, including
666 * <li>canvas width and height</li>
667 * <li>number of visible sequences, and height of annotations if shown</li>
668 * <li>font and character width</li>
669 * <li>whether scales are shown left, right or above the alignment</li>
673 * @param canvasHeight
674 * @return the number of residue columns in each width
676 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
678 int charHeight = av.getCharHeight();
681 * vertical space in pixels between wrapped widths of alignment
682 * - one character height, or two if scale above is drawn
684 wrappedSpaceAboveAlignment = charHeight
685 * (av.getScaleAboveWrapped() ? 2 : 1);
688 * height in pixels of the wrapped widths
690 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
692 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
694 // add annotations panel height if shown
695 wrappedRepeatHeightPx += getAnnotationHeight();
698 * number of visible widths (the last one may be part height),
699 * ensuring a part height includes at least one sequence
701 ViewportRanges ranges = av.getRanges();
702 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
703 int remainder = canvasHeight % wrappedRepeatHeightPx;
704 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
706 wrappedVisibleWidths++;
710 * compute width in residues; this also sets East and West label widths
712 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
715 * limit visibleWidths to not exceed width of alignment
717 int xMax = ranges.getVisibleAlignmentWidth();
718 int startToEnd = xMax - ranges.getStartRes();
719 int maxWidths = startToEnd / wrappedWidthInResidues;
720 if (startToEnd % wrappedWidthInResidues > 0)
724 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
726 return wrappedWidthInResidues;
730 * Draws one width of a wrapped alignment, including sequences and
731 * annnotations, if shown, but not scales or hidden column markers
737 * @param canvasHeight
739 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
740 int endColumn, int canvasHeight)
742 ViewportRanges ranges = av.getRanges();
743 int viewportWidth = ranges.getViewportWidth();
745 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
748 * move right before drawing by the width of the scale left (if any)
749 * plus column offset from left margin (usually zero, but may be non-zero
750 * when fast painting is drawing just a few columns)
752 int charWidth = av.getCharWidth();
753 int xOffset = labelWidthWest
754 + ((startColumn - ranges.getStartRes()) % viewportWidth)
756 g.translate(xOffset, 0);
758 // When printing we have an extra clipped region,
759 // the Printable page which we need to account for here
760 Shape clip = g.getClip();
764 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
768 g.setClip(0, (int) clip.getBounds().getY(),
769 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
773 * white fill the region to be drawn (so incremental fast paint doesn't
774 * scribble over an existing image)
776 gg.setColor(Color.white);
777 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
778 wrappedRepeatHeightPx);
780 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
783 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
785 if (av.isShowAnnotation())
787 g.translate(0, cHeight + ypos + 3);
788 if (annotations == null)
790 annotations = new AnnotationPanel(av);
793 annotations.renderer.drawComponent(annotations, av, g, -1,
794 startColumn, endx + 1);
795 g.translate(0, -cHeight - ypos - 3);
798 g.translate(-xOffset, 0);
802 * Draws scales left, right and above (if shown), and any hidden column
803 * markers, on all widths of the wrapped alignment
808 protected void drawWrappedDecorators(Graphics g, final int startColumn)
810 int charWidth = av.getCharWidth();
812 g.setFont(av.getFont());
813 g.setColor(Color.black);
815 int ypos = wrappedSpaceAboveAlignment;
816 ViewportRanges ranges = av.getRanges();
817 int viewportWidth = ranges.getViewportWidth();
818 int maxWidth = ranges.getVisibleAlignmentWidth();
820 int startCol = startColumn;
822 while (widthsDrawn < wrappedVisibleWidths)
824 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
826 if (av.getScaleLeftWrapped())
828 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
831 if (av.getScaleRightWrapped())
833 int x = labelWidthWest + viewportWidth * charWidth;
835 drawVerticalScale(g, startCol, endColumn, ypos, false);
840 * white fill region of scale above and hidden column markers
841 * (to support incremental fast paint of image)
843 g.translate(labelWidthWest, 0);
844 g.setColor(Color.white);
845 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
846 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
847 g.setColor(Color.black);
848 g.translate(-labelWidthWest, 0);
850 g.translate(labelWidthWest, 0);
852 if (av.getScaleAboveWrapped())
854 drawNorthScale(g, startCol, endColumn, ypos);
857 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
859 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
862 g.translate(-labelWidthWest, 0);
864 ypos += wrappedRepeatHeightPx;
865 startCol += viewportWidth;
871 * Draws markers (triangles) above hidden column positions between startColumn
879 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
880 int startColumn, int endColumn)
882 int charHeight = av.getCharHeight();
883 int charWidth = av.getCharWidth();
885 g.setColor(Color.blue);
887 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
889 Iterator<Integer> it = hidden.getBoundedStartIterator(startColumn,
893 res = it.next() - startColumn;
895 if (res < 0 || res > endColumn - startColumn + 1)
901 * draw a downward-pointing triangle at the hidden columns location
902 * (before the following visible column)
904 int xMiddle = res * charWidth;
905 int[] xPoints = new int[] { xMiddle - charHeight / 4,
906 xMiddle + charHeight / 4, xMiddle };
907 int yTop = ypos - (charHeight / 2);
908 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
909 g.fillPolygon(xPoints, yPoints, 3);
914 * Draw a selection group over a wrapped alignment
916 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
918 int canvasHeight, int startRes)
920 int charHeight = av.getCharHeight();
921 int charWidth = av.getCharWidth();
923 // height gap above each panel
924 int hgap = charHeight;
925 if (av.getScaleAboveWrapped())
930 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
932 int cHeight = av.getAlignment().getHeight() * charHeight;
934 int startx = startRes;
936 int ypos = hgap; // vertical offset
937 int maxwidth = av.getAlignment().getWidth();
939 if (av.hasHiddenColumns())
941 maxwidth = av.getAlignment().getHiddenColumns()
942 .findColumnPosition(maxwidth);
945 // chop the wrapped alignment extent up into panel-sized blocks and treat
946 // each block as if it were a block from an unwrapped alignment
947 while ((ypos <= canvasHeight) && (startx < maxwidth))
949 // set end value to be start + width, or maxwidth, whichever is smaller
950 endx = startx + cWidth - 1;
957 g.translate(labelWidthWest, 0);
959 drawUnwrappedSelection(g, group, startx, endx, 0,
960 av.getAlignment().getHeight() - 1,
963 g.translate(-labelWidthWest, 0);
965 // update vertical offset
966 ypos += cHeight + getAnnotationHeight() + hgap;
968 // update horizontal offset
973 int getAnnotationHeight()
975 if (!av.isShowAnnotation())
980 if (annotations == null)
982 annotations = new AnnotationPanel(av);
985 return annotations.adjustPanelHeight();
989 * Draws the visible region of the alignment on the graphics context. If there
990 * are hidden column markers in the visible region, then each sub-region
991 * between the markers is drawn separately, followed by the hidden column
995 * the graphics context, positioned at the first residue to be drawn
997 * offset of the first column to draw (0..)
999 * offset of the last column to draw (0..)
1001 * offset of the first sequence to draw (0..)
1003 * offset of the last sequence to draw (0..)
1005 * vertical offset at which to draw (for wrapped alignments)
1007 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1008 final int startSeq, final int endSeq, final int yOffset)
1010 int charHeight = av.getCharHeight();
1011 int charWidth = av.getCharWidth();
1013 if (!av.hasHiddenColumns())
1015 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1023 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1024 VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
1025 .getVisibleBlocksIterator(startRes, endRes, true);
1027 while (regions.hasNext())
1029 int[] region = regions.next();
1030 blockEnd = region[1];
1031 blockStart = region[0];
1034 * draw up to just before the next hidden region, or the end of
1035 * the visible region, whichever comes first
1037 g1.translate(screenY * charWidth, 0);
1039 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1042 * draw the downline of the hidden column marker (ScalePanel draws the
1043 * triangle on top) if we reached it
1045 if (av.getShowHiddenMarkers()
1046 && (regions.hasNext() || regions.endsAtHidden()))
1048 g1.setColor(Color.blue);
1050 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1051 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1052 (endSeq - startSeq + 1) * charHeight + yOffset);
1055 g1.translate(-screenY * charWidth, 0);
1056 screenY += blockEnd - blockStart + 1;
1063 * Draws a region of the visible alignment
1067 * offset of the first column in the visible region (0..)
1069 * offset of the last column in the visible region (0..)
1071 * offset of the first sequence in the visible region (0..)
1073 * offset of the last sequence in the visible region (0..)
1075 * vertical offset at which to draw (for wrapped alignments)
1077 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1078 int endSeq, int offset)
1080 int charHeight = av.getCharHeight();
1081 int charWidth = av.getCharWidth();
1083 g.setFont(av.getFont());
1084 seqRdr.prepare(g, av.isRenderGaps());
1088 // / First draw the sequences
1089 // ///////////////////////////
1090 for (int i = startSeq; i <= endSeq; i++)
1092 nextSeq = av.getAlignment().getSequenceAt(i);
1093 if (nextSeq == null)
1095 // occasionally, a race condition occurs such that the alignment row is
1099 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1100 startRes, endRes, offset + ((i - startSeq) * charHeight));
1102 if (av.isShowSequenceFeatures())
1104 fr.drawSequence(g, nextSeq, startRes, endRes,
1105 offset + ((i - startSeq) * charHeight), false);
1109 * highlight search Results once sequence has been drawn
1111 if (av.hasSearchResults())
1113 SearchResultsI searchResults = av.getSearchResults();
1114 int[] visibleResults = searchResults.getResults(nextSeq,
1116 if (visibleResults != null)
1118 for (int r = 0; r < visibleResults.length; r += 2)
1120 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1121 visibleResults[r + 1], (visibleResults[r] - startRes)
1123 + ((i - startSeq) * charHeight));
1128 if (av.cursorMode && cursorY == i && cursorX >= startRes
1129 && cursorX <= endRes)
1131 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1132 offset + ((i - startSeq) * charHeight));
1136 if (av.getSelectionGroup() != null
1137 || av.getAlignment().getGroups().size() > 0)
1139 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1144 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1145 int startSeq, int endSeq, int offset)
1147 Graphics2D g = (Graphics2D) g1;
1149 // ///////////////////////////////////
1150 // Now outline any areas if necessary
1151 // ///////////////////////////////////
1153 SequenceGroup group = null;
1154 int groupIndex = -1;
1156 if (av.getAlignment().getGroups().size() > 0)
1158 group = av.getAlignment().getGroups().get(0);
1164 g.setStroke(new BasicStroke());
1165 g.setColor(group.getOutlineColour());
1169 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1174 g.setStroke(new BasicStroke());
1176 if (groupIndex >= av.getAlignment().getGroups().size())
1181 group = av.getAlignment().getGroups().get(groupIndex);
1183 } while (groupIndex < av.getAlignment().getGroups().size());
1191 * Draw the selection group as a separate image and overlay
1193 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1194 int startSeq, int endSeq)
1196 // get a new image of the correct size
1197 BufferedImage selectionImage = setupImage();
1199 if (selectionImage == null)
1204 SequenceGroup group = av.getSelectionGroup();
1211 // set up drawing colour
1212 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1214 setupSelectionGroup(g, selectionImage);
1216 if (!av.getWrapAlignment())
1218 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1223 drawWrappedSelection(g, group, getWidth(), getHeight(),
1224 av.getRanges().getStartRes());
1228 return selectionImage;
1232 * Set up graphics for selection group
1234 private void setupSelectionGroup(Graphics2D g,
1235 BufferedImage selectionImage)
1237 // set background to transparent
1238 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1239 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1241 // set up foreground to draw red dashed line
1242 g.setComposite(AlphaComposite.Src);
1243 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1244 BasicStroke.JOIN_ROUND, 3f, new float[]
1246 g.setColor(Color.RED);
1250 * Draw a selection group over an unwrapped alignment
1251 * @param g graphics object to draw with
1252 * @param group selection group
1253 * @param startRes start residue of area to draw
1254 * @param endRes end residue of area to draw
1255 * @param startSeq start sequence of area to draw
1256 * @param endSeq end sequence of area to draw
1257 * @param offset vertical offset (used when called from wrapped alignment code)
1259 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1260 int startRes, int endRes, int startSeq, int endSeq, int offset)
1262 int charWidth = av.getCharWidth();
1264 if (!av.hasHiddenColumns())
1266 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1271 // package into blocks of visible columns
1276 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
1277 VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
1278 .getVisibleBlocksIterator(startRes, endRes, true);
1279 while (regions.hasNext())
1281 int[] region = regions.next();
1282 blockEnd = region[1];
1283 blockStart = region[0];
1285 g.translate(screenY * charWidth, 0);
1286 drawPartialGroupOutline(g, group,
1287 blockStart, blockEnd, startSeq, endSeq, offset);
1289 g.translate(-screenY * charWidth, 0);
1290 screenY += blockEnd - blockStart + 1;
1296 * Draw the selection group as a separate image and overlay
1298 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1299 int startRes, int endRes, int startSeq, int endSeq,
1302 int charHeight = av.getCharHeight();
1303 int charWidth = av.getCharWidth();
1305 int visWidth = (endRes - startRes + 1) * charWidth;
1309 boolean inGroup = false;
1317 for (i = startSeq; i <= endSeq; i++)
1319 // position of start residue of group relative to startRes, in pixels
1320 sx = (group.getStartRes() - startRes) * charWidth;
1322 // width of group in pixels
1323 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1326 sy = verticalOffset + (i - startSeq) * charHeight;
1328 if (sx + xwidth < 0 || sx > visWidth)
1333 if ((sx <= (endRes - startRes) * charWidth)
1334 && group.getSequences(null)
1335 .contains(av.getAlignment().getSequenceAt(i)))
1337 if ((bottom == -1) && !group.getSequences(null)
1338 .contains(av.getAlignment().getSequenceAt(i + 1)))
1340 bottom = sy + charHeight;
1345 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1346 .contains(av.getAlignment().getSequenceAt(i - 1)))
1359 // if start position is visible, draw vertical line to left of
1361 if (sx >= 0 && sx < visWidth)
1363 g.drawLine(sx, oldY, sx, sy);
1366 // if end position is visible, draw vertical line to right of
1368 if (sx + xwidth < visWidth)
1370 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1379 // don't let width extend beyond current block, or group extent
1381 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1383 xwidth = (endRes - startRes + 1) * charWidth - sx;
1386 // draw horizontal line at top of group
1389 g.drawLine(sx, top, sx + xwidth, top);
1393 // draw horizontal line at bottom of group
1396 g.drawLine(sx, bottom, sx + xwidth, bottom);
1407 sy = verticalOffset + ((i - startSeq) * charHeight);
1408 if (sx >= 0 && sx < visWidth)
1410 g.drawLine(sx, oldY, sx, sy);
1413 if (sx + xwidth < visWidth)
1415 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1424 if (sx + xwidth > visWidth)
1428 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1430 xwidth = (endRes - startRes + 1) * charWidth;
1435 g.drawLine(sx, top, sx + xwidth, top);
1441 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1450 * Highlights search results in the visible region by rendering as white text
1451 * on a black background. Any previous highlighting is removed. Answers true
1452 * if any highlight was left on the visible alignment (so status bar should be
1453 * set to match), else false.
1455 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1456 * alignment had to be scrolled to show the highlighted region, then it should
1457 * be fully redrawn, otherwise a fast paint can be performed. This argument
1458 * could be removed if fast paint of scrolled wrapped alignment is coded in
1459 * future (JAL-2609).
1462 * @param noFastPaint
1465 public boolean highlightSearchResults(SearchResultsI results,
1466 boolean noFastPaint)
1472 boolean wrapped = av.getWrapAlignment();
1475 fastPaint = !noFastPaint;
1476 fastpainting = fastPaint;
1479 * to avoid redrawing the whole visible region, we instead
1480 * redraw just the minimal regions to remove previous highlights
1483 SearchResultsI previous = av.getSearchResults();
1484 av.setSearchResults(results);
1485 boolean redrawn = false;
1486 boolean drawn = false;
1489 redrawn = drawMappedPositionsWrapped(previous);
1490 drawn = drawMappedPositionsWrapped(results);
1495 redrawn = drawMappedPositions(previous);
1496 drawn = drawMappedPositions(results);
1501 * if highlights were either removed or added, repaint
1509 * return true only if highlights were added
1515 fastpainting = false;
1520 * Redraws the minimal rectangle in the visible region (if any) that includes
1521 * mapped positions of the given search results. Whether or not positions are
1522 * highlighted depends on the SearchResults set on the Viewport. This allows
1523 * this method to be called to either clear or set highlighting. Answers true
1524 * if any positions were drawn (in which case a repaint is still required),
1530 protected boolean drawMappedPositions(SearchResultsI results)
1532 if (results == null)
1538 * calculate the minimal rectangle to redraw that
1539 * includes both new and existing search results
1541 int firstSeq = Integer.MAX_VALUE;
1543 int firstCol = Integer.MAX_VALUE;
1545 boolean matchFound = false;
1547 ViewportRanges ranges = av.getRanges();
1548 int firstVisibleColumn = ranges.getStartRes();
1549 int lastVisibleColumn = ranges.getEndRes();
1550 AlignmentI alignment = av.getAlignment();
1551 if (av.hasHiddenColumns())
1553 firstVisibleColumn = alignment.getHiddenColumns()
1554 .adjustForHiddenColumns(firstVisibleColumn);
1555 lastVisibleColumn = alignment.getHiddenColumns()
1556 .adjustForHiddenColumns(lastVisibleColumn);
1559 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1560 .getEndSeq(); seqNo++)
1562 SequenceI seq = alignment.getSequenceAt(seqNo);
1564 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1566 if (visibleResults != null)
1568 for (int i = 0; i < visibleResults.length - 1; i += 2)
1570 int firstMatchedColumn = visibleResults[i];
1571 int lastMatchedColumn = visibleResults[i + 1];
1572 if (firstMatchedColumn <= lastVisibleColumn
1573 && lastMatchedColumn >= firstVisibleColumn)
1576 * found a search results match in the visible region -
1577 * remember the first and last sequence matched, and the first
1578 * and last visible columns in the matched positions
1581 firstSeq = Math.min(firstSeq, seqNo);
1582 lastSeq = Math.max(lastSeq, seqNo);
1583 firstMatchedColumn = Math.max(firstMatchedColumn,
1584 firstVisibleColumn);
1585 lastMatchedColumn = Math.min(lastMatchedColumn,
1587 firstCol = Math.min(firstCol, firstMatchedColumn);
1588 lastCol = Math.max(lastCol, lastMatchedColumn);
1596 if (av.hasHiddenColumns())
1598 firstCol = alignment.getHiddenColumns()
1599 .findColumnPosition(firstCol);
1600 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1602 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1603 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1604 gg.translate(transX, transY);
1605 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1606 gg.translate(-transX, -transY);
1613 public void propertyChange(PropertyChangeEvent evt)
1615 String eventName = evt.getPropertyName();
1617 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1625 if (eventName.equals(ViewportRanges.STARTRES))
1627 // Make sure we're not trying to draw a panel
1628 // larger than the visible window
1629 ViewportRanges vpRanges = av.getRanges();
1630 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1631 int range = vpRanges.getViewportWidth();
1632 if (scrollX > range)
1636 else if (scrollX < -range)
1642 // Both scrolling and resizing change viewport ranges: scrolling changes
1643 // both start and end points, but resize only changes end values.
1644 // Here we only want to fastpaint on a scroll, with resize using a normal
1645 // paint, so scroll events are identified as changes to the horizontal or
1646 // vertical start value.
1648 // scroll - startres and endres both change
1649 if (eventName.equals(ViewportRanges.STARTRES))
1651 if (av.getWrapAlignment())
1653 fastPaintWrapped(scrollX);
1657 fastPaint(scrollX, 0);
1660 else if (eventName.equals(ViewportRanges.STARTSEQ))
1663 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1668 * Does a minimal update of the image for a scroll movement. This method
1669 * handles scroll movements of up to one width of the wrapped alignment (one
1670 * click in the vertical scrollbar). Larger movements (for example after a
1671 * scroll to highlight a mapped position) trigger a full redraw instead.
1674 * number of positions scrolled (right if positive, left if negative)
1676 protected void fastPaintWrapped(int scrollX)
1678 ViewportRanges ranges = av.getRanges();
1680 if (Math.abs(scrollX) > ranges.getViewportWidth())
1683 * shift of more than one view width is
1684 * overcomplicated to handle in this method
1691 if (fastpainting || gg == null)
1697 fastpainting = true;
1701 calculateWrappedGeometry(getWidth(), getHeight());
1704 * relocate the regions of the alignment that are still visible
1706 shiftWrappedAlignment(-scrollX);
1709 * add new columns (sequence, annotation)
1710 * - at top left if scrollX < 0
1711 * - at right of last two widths if scrollX > 0
1715 int startRes = ranges.getStartRes();
1716 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1717 - scrollX - 1, getHeight());
1721 fastPaintWrappedAddRight(scrollX);
1725 * draw all scales (if shown) and hidden column markers
1727 drawWrappedDecorators(gg, ranges.getStartRes());
1732 fastpainting = false;
1737 * Draws the specified number of columns at the 'end' (bottom right) of a
1738 * wrapped alignment view, including sequences and annotations if shown, but
1739 * not scales. Also draws the same number of columns at the right hand end of
1740 * the second last width shown, if the last width is not full height (so
1741 * cannot simply be copied from the graphics image).
1745 protected void fastPaintWrappedAddRight(int columns)
1752 ViewportRanges ranges = av.getRanges();
1753 int viewportWidth = ranges.getViewportWidth();
1754 int charWidth = av.getCharWidth();
1757 * draw full height alignment in the second last row, last columns, if the
1758 * last row was not full height
1760 int visibleWidths = wrappedVisibleWidths;
1761 int canvasHeight = getHeight();
1762 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1764 if (lastWidthPartHeight)
1766 int widthsAbove = Math.max(0, visibleWidths - 2);
1767 int ypos = wrappedRepeatHeightPx * widthsAbove
1768 + wrappedSpaceAboveAlignment;
1769 int endRes = ranges.getEndRes();
1770 endRes += widthsAbove * viewportWidth;
1771 int startRes = endRes - columns;
1772 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1776 * white fill first to erase annotations
1778 gg.translate(xOffset, 0);
1779 gg.setColor(Color.white);
1780 gg.fillRect(labelWidthWest, ypos,
1781 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1782 gg.translate(-xOffset, 0);
1784 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1788 * draw newly visible columns in last wrapped width (none if we
1789 * have reached the end of the alignment)
1790 * y-offset for drawing last width is height of widths above,
1793 int widthsAbove = visibleWidths - 1;
1794 int ypos = wrappedRepeatHeightPx * widthsAbove
1795 + wrappedSpaceAboveAlignment;
1796 int endRes = ranges.getEndRes();
1797 endRes += widthsAbove * viewportWidth;
1798 int startRes = endRes - columns + 1;
1801 * white fill first to erase annotations
1803 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1805 gg.translate(xOffset, 0);
1806 gg.setColor(Color.white);
1807 int width = viewportWidth * charWidth - xOffset;
1808 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1809 gg.translate(-xOffset, 0);
1811 gg.setFont(av.getFont());
1812 gg.setColor(Color.black);
1814 if (startRes < ranges.getVisibleAlignmentWidth())
1816 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1820 * and finally, white fill any space below the visible alignment
1822 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1823 if (heightBelow > 0)
1825 gg.setColor(Color.white);
1826 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1831 * Shifts the visible alignment by the specified number of columns - left if
1832 * negative, right if positive. Copies and moves sequences and annotations (if
1833 * shown). Scales, hidden column markers and any newly visible columns must be
1838 protected void shiftWrappedAlignment(int positions)
1844 int charWidth = av.getCharWidth();
1846 int canvasHeight = getHeight();
1847 ViewportRanges ranges = av.getRanges();
1848 int viewportWidth = ranges.getViewportWidth();
1849 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1851 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1852 int xMax = ranges.getVisibleAlignmentWidth();
1857 * shift right (after scroll left)
1858 * for each wrapped width (starting with the last), copy (width-positions)
1859 * columns from the left margin to the right margin, and copy positions
1860 * columns from the right margin of the row above (if any) to the
1861 * left margin of the current row
1865 * get y-offset of last wrapped width, first row of sequences
1867 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1868 y += wrappedSpaceAboveAlignment;
1869 int copyFromLeftStart = labelWidthWest;
1870 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1874 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1875 positions * charWidth, 0);
1878 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1879 positions * charWidth, heightToCopy, -widthToCopy,
1880 wrappedRepeatHeightPx);
1883 y -= wrappedRepeatHeightPx;
1889 * shift left (after scroll right)
1890 * for each wrapped width (starting with the first), copy (width-positions)
1891 * columns from the right margin to the left margin, and copy positions
1892 * columns from the left margin of the row below (if any) to the
1893 * right margin of the current row
1895 int xpos = av.getRanges().getStartRes();
1896 int y = wrappedSpaceAboveAlignment;
1897 int copyFromRightStart = labelWidthWest - positions * charWidth;
1899 while (y < canvasHeight)
1901 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1902 positions * charWidth, 0);
1903 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1904 && (xpos + viewportWidth <= xMax))
1906 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1907 * charWidth, heightToCopy, widthToCopy,
1908 -wrappedRepeatHeightPx);
1911 y += wrappedRepeatHeightPx;
1912 xpos += viewportWidth;
1919 * Redraws any positions in the search results in the visible region of a
1920 * wrapped alignment. Any highlights are drawn depending on the search results
1921 * set on the Viewport, not the <code>results</code> argument. This allows
1922 * this method to be called either to clear highlights (passing the previous
1923 * search results), or to draw new highlights.
1928 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1930 if (results == null)
1934 int charHeight = av.getCharHeight();
1936 boolean matchFound = false;
1938 calculateWrappedGeometry(getWidth(), getHeight());
1939 int wrappedWidth = av.getWrappedWidth();
1940 int wrappedHeight = wrappedRepeatHeightPx;
1942 ViewportRanges ranges = av.getRanges();
1943 int canvasHeight = getHeight();
1944 int repeats = canvasHeight / wrappedHeight;
1945 if (canvasHeight / wrappedHeight > 0)
1950 int firstVisibleColumn = ranges.getStartRes();
1951 int lastVisibleColumn = ranges.getStartRes() + repeats
1952 * ranges.getViewportWidth() - 1;
1954 AlignmentI alignment = av.getAlignment();
1955 if (av.hasHiddenColumns())
1957 firstVisibleColumn = alignment.getHiddenColumns()
1958 .adjustForHiddenColumns(firstVisibleColumn);
1959 lastVisibleColumn = alignment.getHiddenColumns()
1960 .adjustForHiddenColumns(lastVisibleColumn);
1963 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1965 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1966 .getEndSeq(); seqNo++)
1968 SequenceI seq = alignment.getSequenceAt(seqNo);
1970 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1972 if (visibleResults != null)
1974 for (int i = 0; i < visibleResults.length - 1; i += 2)
1976 int firstMatchedColumn = visibleResults[i];
1977 int lastMatchedColumn = visibleResults[i + 1];
1978 if (firstMatchedColumn <= lastVisibleColumn
1979 && lastMatchedColumn >= firstVisibleColumn)
1982 * found a search results match in the visible region
1984 firstMatchedColumn = Math.max(firstMatchedColumn,
1985 firstVisibleColumn);
1986 lastMatchedColumn = Math.min(lastMatchedColumn,
1990 * draw each mapped position separately (as contiguous positions may
1991 * wrap across lines)
1993 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1995 int displayColumn = mappedPos;
1996 if (av.hasHiddenColumns())
1998 displayColumn = alignment.getHiddenColumns()
1999 .findColumnPosition(displayColumn);
2003 * transX: offset from left edge of canvas to residue position
2005 int transX = labelWidthWest
2006 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2007 * av.getCharWidth();
2010 * transY: offset from top edge of canvas to residue position
2012 int transY = gapHeight;
2013 transY += (displayColumn - ranges.getStartRes())
2014 / wrappedWidth * wrappedHeight;
2015 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2018 * yOffset is from graphics origin to start of visible region
2020 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2021 if (transY < getHeight())
2024 gg.translate(transX, transY);
2025 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2027 gg.translate(-transX, -transY);
2039 * Answers the width in pixels of the left scale labels (0 if not shown)
2043 int getLabelWidthWest()
2045 return labelWidthWest;