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.renderer.ScaleRenderer;
29 import jalview.renderer.ScaleRenderer.ScaleMark;
30 import jalview.util.Comparison;
31 import jalview.viewmodel.ViewportListenerI;
32 import jalview.viewmodel.ViewportRanges;
34 import java.awt.AlphaComposite;
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.List;
47 import javax.swing.JComponent;
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static String ZEROS = "0000000000";
59 final FeatureRenderer fr;
61 final SequenceRenderer seqRdr;
73 boolean fastPaint = false;
75 boolean fastpainting = false;
81 private AnnotationPanel annotations;
84 * measurements for drawing a wrapped alignment
86 int labelWidthWest; // label left width in pixels if shown
88 private int labelWidthEast; // label right width in pixels if shown
90 private int wrappedSpaceAboveAlignment; // gap between widths
92 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
94 private int wrappedVisibleWidths; // number of wrapped widths displayed
97 * Creates a new SeqCanvas object.
102 public SeqCanvas(AlignmentPanel ap)
105 fr = new FeatureRenderer(ap);
106 seqRdr = new SequenceRenderer(av);
107 setLayout(new BorderLayout());
108 PaintRefresher.Register(this, av.getSequenceSetId());
109 setBackground(Color.white);
111 av.getRanges().addPropertyChangeListener(this);
114 public SequenceRenderer getSequenceRenderer()
119 public FeatureRenderer getFeatureRenderer()
125 * Draws the scale above a region of a wrapped alignment, consisting of a
126 * column number every major interval (10 columns).
129 * the graphics context to draw on, positioned at the start (bottom
130 * left) of the line on which to draw any scale marks
132 * start alignment column (0..)
134 * end alignment column (0..)
136 * y offset to draw at
138 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
140 int charHeight = av.getCharHeight();
141 int charWidth = av.getCharWidth();
144 * white fill the scale space (for the fastPaint case)
146 g.setColor(Color.white);
147 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
148 charHeight * 3 / 2 + 2);
149 g.setColor(Color.black);
151 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
153 for (ScaleMark mark : marks)
155 int mpos = mark.column; // (i - startx - 1)
160 String mstring = mark.text;
166 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
170 * draw a tick mark below the column number, centred on the column;
171 * height of tick mark is 4 pixels less than half a character
173 int xpos = (mpos * charWidth) + (charWidth / 2);
174 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
180 * Draw the scale to the left or right of a wrapped alignment
183 * graphics context, positioned at the start of the scale to be drawn
185 * first column of wrapped width (0.. excluding any hidden columns)
187 * last column of wrapped width (0.. excluding any hidden columns)
189 * vertical offset at which to begin the scale
191 * if true, scale is left of residues, if false, scale is right
193 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
196 int charHeight = av.getCharHeight();
197 int charWidth = av.getCharWidth();
201 if (av.hasHiddenColumns())
203 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
204 startx = hiddenColumns.adjustForHiddenColumns(startx);
205 endx = hiddenColumns.adjustForHiddenColumns(endx);
207 FontMetrics fm = getFontMetrics(av.getFont());
209 for (int i = 0; i < av.getAlignment().getHeight(); i++)
211 SequenceI seq = av.getAlignment().getSequenceAt(i);
214 * find sequence position of first non-gapped position -
215 * to the right if scale left, to the left if scale right
217 int index = left ? startx : endx;
219 while (index >= startx && index <= endx)
221 if (!Comparison.isGap(seq.getCharAt(index)))
223 value = seq.findPosition(index);
237 * white fill the space for the scale
239 g.setColor(Color.white);
240 int y = (ypos + (i * charHeight)) - (charHeight / 5);
241 // fillRect origin is top left of rectangle
242 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
249 * draw scale value, right justified, with half a character width
250 * separation from the sequence data
252 String valueAsString = String.valueOf(value);
253 int justify = fm.stringWidth(valueAsString) + charWidth;
254 int xpos = left ? labelWidthWest - justify + charWidth / 2
255 : labelWidthEast - justify + charWidth / 2;
256 g.setColor(Color.black);
257 g.drawString(valueAsString, xpos, y);
263 * Does a fast paint of an alignment in response to a scroll. Most of the
264 * visible region is simply copied and shifted, and then any newly visible
265 * columns or rows are drawn. The scroll may be horizontal or vertical, but
266 * not both at once. Scrolling may be the result of
268 * <li>dragging a scroll bar</li>
269 * <li>clicking in the scroll bar</li>
270 * <li>scrolling by trackpad, middle mouse button, or other device</li>
271 * <li>by moving the box in the Overview window</li>
272 * <li>programmatically to make a highlighted position visible</li>
276 * columns to shift right (positive) or left (negative)
278 * rows to shift down (positive) or up (negative)
280 public void fastPaint(int horizontal, int vertical)
282 if (fastpainting || gg == null || img == null)
291 int charHeight = av.getCharHeight();
292 int charWidth = av.getCharWidth();
294 ViewportRanges ranges = av.getRanges();
295 int startRes = ranges.getStartRes();
296 int endRes = ranges.getEndRes();
297 int startSeq = ranges.getStartSeq();
298 int endSeq = ranges.getEndSeq();
302 gg.copyArea(horizontal * charWidth, vertical * charHeight,
303 img.getWidth(), img.getHeight(), -horizontal * charWidth,
304 -vertical * charHeight);
306 if (horizontal > 0) // scrollbar pulled right, image to the left
308 transX = (endRes - startRes - horizontal) * charWidth;
309 startRes = endRes - horizontal;
311 else if (horizontal < 0)
313 endRes = startRes - horizontal;
315 else if (vertical > 0) // scroll down
317 startSeq = endSeq - vertical;
319 if (startSeq < ranges.getStartSeq())
320 { // ie scrolling too fast, more than a page at a time
321 startSeq = ranges.getStartSeq();
325 transY = img.getHeight() - ((vertical + 1) * charHeight);
328 else if (vertical < 0)
330 endSeq = startSeq - vertical;
332 if (endSeq > ranges.getEndSeq())
334 endSeq = ranges.getEndSeq();
338 gg.translate(transX, transY);
339 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
340 gg.translate(-transX, -transY);
345 fastpainting = false;
350 public void paintComponent(Graphics g)
352 super.paintComponent(g);
354 int charHeight = av.getCharHeight();
355 int charWidth = av.getCharWidth();
357 ViewportRanges ranges = av.getRanges();
359 int width = getWidth();
360 int height = getHeight();
362 width -= (width % charWidth);
363 height -= (height % charHeight);
365 // selectImage is the selection group outline image
366 BufferedImage selectImage = drawSelectionGroup(
367 ranges.getStartRes(), ranges.getEndRes(),
368 ranges.getStartSeq(), ranges.getEndSeq());
370 if ((img != null) && (fastPaint
371 || (getVisibleRect().width != g.getClipBounds().width)
372 || (getVisibleRect().height != g.getClipBounds().height)))
374 BufferedImage lcimg = buildLocalImage(selectImage);
375 g.drawImage(lcimg, 0, 0, this);
378 else if ((width > 0) && (height > 0))
380 // img is a cached version of the last view we drew, if any
381 // if we have no img or the size has changed, make a new one
382 if (img == null || width != img.getWidth()
383 || height != img.getHeight())
390 gg = (Graphics2D) img.getGraphics();
391 gg.setFont(av.getFont());
396 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
397 RenderingHints.VALUE_ANTIALIAS_ON);
400 gg.setColor(Color.white);
401 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
403 if (av.getWrapAlignment())
405 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
409 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
410 ranges.getStartSeq(), ranges.getEndSeq(), 0);
413 // lcimg is a local *copy* of img which we'll draw selectImage on top of
414 BufferedImage lcimg = buildLocalImage(selectImage);
415 g.drawImage(lcimg, 0, 0, this);
420 * Draw an alignment panel for printing
423 * Graphics object to draw with
425 * start residue of print area
427 * end residue of print area
429 * start sequence of print area
431 * end sequence of print area
433 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
434 int startSeq, int endSeq)
436 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
438 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
440 if (selectImage != null)
442 ((Graphics2D) g1).setComposite(AlphaComposite
443 .getInstance(AlphaComposite.SRC_OVER));
444 g1.drawImage(selectImage, 0, 0, this);
449 * Draw a wrapped alignment panel for printing
452 * Graphics object to draw with
454 * width of drawing area
455 * @param canvasHeight
456 * height of drawing area
458 * start residue of print area
460 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
461 int canvasHeight, int startRes)
463 SequenceGroup group = av.getSelectionGroup();
465 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
469 BufferedImage selectImage = null;
472 selectImage = new BufferedImage(canvasWidth, canvasHeight,
473 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
474 } catch (OutOfMemoryError er)
477 System.err.println("Print image OutOfMemory Error.\n" + er);
478 new OOMWarning("Creating wrapped alignment image for printing", er);
480 if (selectImage != null)
482 Graphics2D g2 = selectImage.createGraphics();
483 setupSelectionGroup(g2, selectImage);
484 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
488 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
489 g.drawImage(selectImage, 0, 0, this);
496 * Make a local image by combining the cached image img
499 private BufferedImage buildLocalImage(BufferedImage selectImage)
501 // clone the cached image
502 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
504 Graphics2D g2d = lcimg.createGraphics();
505 g2d.drawImage(img, 0, 0, null);
507 // overlay selection group on lcimg
508 if (selectImage != null)
511 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
512 g2d.drawImage(selectImage, 0, 0, this);
520 * Set up a buffered image of the correct height and size for the sequence canvas
522 private BufferedImage setupImage()
524 BufferedImage lcimg = null;
526 int charWidth = av.getCharWidth();
527 int charHeight = av.getCharHeight();
529 int width = getWidth();
530 int height = getHeight();
532 width -= (width % charWidth);
533 height -= (height % charHeight);
535 if ((width < 1) || (height < 1))
542 lcimg = new BufferedImage(width, height,
543 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
544 } catch (OutOfMemoryError er)
548 "Group image OutOfMemory Redraw Error.\n" + er);
549 new OOMWarning("Creating alignment image for display", er);
558 * Returns the visible width of the canvas in residues, after allowing for
559 * East or West scales (if shown)
562 * the width in pixels (possibly including scales)
566 public int getWrappedCanvasWidth(int canvasWidth)
568 int charWidth = av.getCharWidth();
570 FontMetrics fm = getFontMetrics(av.getFont());
575 if (av.getScaleRightWrapped())
577 labelWidthEast = getLabelWidth(fm);
580 if (av.getScaleLeftWrapped())
582 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
586 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
590 * Returns a pixel width suitable for showing the largest sequence coordinate
591 * (end position) in the alignment. Returns 2 plus the number of decimal
592 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
597 protected int getLabelWidth(FontMetrics fm)
600 * find the biggest sequence end position we need to show
601 * (note this is not necessarily the sequence length)
604 AlignmentI alignment = av.getAlignment();
605 for (int i = 0; i < alignment.getHeight(); i++)
607 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
611 for (int i = maxWidth; i > 0; i /= 10)
616 return fm.stringWidth(ZEROS.substring(0, length));
620 * Draws as many widths of a wrapped alignment as can fit in the visible
625 * available width in pixels
626 * @param canvasHeight
627 * available height in pixels
629 * the first column (0...) of the alignment to draw
631 public void drawWrappedPanel(Graphics g, int canvasWidth,
632 int canvasHeight, final int startColumn)
634 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
637 av.setWrappedWidth(wrappedWidthInResidues);
639 ViewportRanges ranges = av.getRanges();
640 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
643 * draw one width at a time (including any scales or annotation shown),
644 * until we have run out of either alignment or vertical space available
646 int ypos = wrappedSpaceAboveAlignment;
647 int maxWidth = ranges.getVisibleAlignmentWidth();
649 int start = startColumn;
650 int currentWidth = 0;
651 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
654 .min(maxWidth, start + wrappedWidthInResidues - 1);
655 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
656 ypos += wrappedRepeatHeightPx;
657 start += wrappedWidthInResidues;
661 drawWrappedDecorators(g, startColumn);
665 * Calculates and saves values needed when rendering a wrapped alignment.
666 * These depend on many factors, including
668 * <li>canvas width and height</li>
669 * <li>number of visible sequences, and height of annotations if shown</li>
670 * <li>font and character width</li>
671 * <li>whether scales are shown left, right or above the alignment</li>
675 * @param canvasHeight
676 * @return the number of residue columns in each width
678 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
680 int charHeight = av.getCharHeight();
681 int charWidth = av.getCharWidth();
684 * width of labels in pixels left and right (if shown)
687 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
689 FontMetrics fm = getFontMetrics(av.getFont());
690 labelWidth = getLabelWidth(fm);
692 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
693 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
696 * vertical space in pixels between wrapped widths of alignment
697 * - one character height, or two if scale above is drawn
699 wrappedSpaceAboveAlignment = charHeight
700 * (av.getScaleAboveWrapped() ? 2 : 1);
703 * height in pixels of the wrapped widths
705 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
707 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
709 // add annotations panel height if shown
710 wrappedRepeatHeightPx += getAnnotationHeight();
713 * number of residue columns we can show in each row;
714 * this is just canvas width less scale left and right (if shown),
715 * as a whole multiple of character widths
717 int wrappedWidthInResidues = (canvasWidth - labelWidthEast
718 - labelWidthWest) / charWidth;
721 * number of visible widths (the last one may be part height),
722 * ensuring a part height includes at least one sequence
724 ViewportRanges ranges = av.getRanges();
725 int xMax = ranges.getVisibleAlignmentWidth();
726 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
727 int remainder = canvasHeight % wrappedRepeatHeightPx;
728 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
730 wrappedVisibleWidths++;
734 * limit visibleWidths to not exceed width of alignment
736 int maxWidths = (xMax - ranges.getStartRes()) / wrappedWidthInResidues;
737 if (xMax % wrappedWidthInResidues > 0)
741 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
743 return wrappedWidthInResidues;
747 * Draws one width of a wrapped alignment, including sequences and
748 * annnotations, if shown, but not scales or hidden column markers
754 * @param canvasHeight
756 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
757 int endColumn, int canvasHeight)
759 int charHeight = av.getCharHeight();
760 int charWidth = av.getCharWidth();
762 ViewportRanges ranges = av.getRanges();
763 int viewportWidth = ranges.getViewportWidth();
765 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
768 * move right before drawing by the width of the scale left (if any)
769 * plus column offset from left margin (usually zero, but may be non-zero
770 * when fast painting is drawing just a few columns)
772 int xOffset = labelWidthWest
773 + ((startColumn - ranges.getStartRes()) % viewportWidth)
775 g.translate(xOffset, 0);
777 // When printing we have an extra clipped region,
778 // the Printable page which we need to account for here
779 Shape clip = g.getClip();
783 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
787 g.setClip(0, (int) clip.getBounds().getY(),
788 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
792 * white fill the region to be drawn (so incremental fast paint doesn't
793 * scribble over an existing image)
795 gg.setColor(Color.white);
796 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
797 wrappedRepeatHeightPx);
799 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
802 int cHeight = av.getAlignment().getHeight() * charHeight;
804 if (av.isShowAnnotation())
806 g.translate(0, cHeight + ypos + 3);
807 if (annotations == null)
809 annotations = new AnnotationPanel(av);
812 annotations.renderer.drawComponent(annotations, av, g, -1,
813 startColumn, endx + 1);
814 g.translate(0, -cHeight - ypos - 3);
817 g.translate(-xOffset, 0);
821 * Draws scales left, right and above (if shown), and any hidden column
822 * markers, on all widths of the wrapped alignment
827 protected void drawWrappedDecorators(Graphics g, int startColumn)
829 int charWidth = av.getCharWidth();
831 g.setFont(av.getFont());
832 g.setColor(Color.black);
834 int ypos = wrappedSpaceAboveAlignment;
835 ViewportRanges ranges = av.getRanges();
836 int viewportWidth = ranges.getViewportWidth();
837 int maxWidth = ranges.getVisibleAlignmentWidth();
839 while (widthsDrawn < wrappedVisibleWidths)
841 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
843 if (av.getScaleLeftWrapped())
845 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
848 if (av.getScaleRightWrapped())
850 int x = labelWidthWest + viewportWidth * charWidth;
852 drawVerticalScale(g, startColumn, endColumn, ypos, false);
857 * white fill region of scale above and hidden column markers
858 * (to support incremental fast paint of image)
860 g.setColor(Color.white);
861 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
862 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
863 g.setColor(Color.black);
865 g.translate(labelWidthWest, 0);
867 if (av.getScaleAboveWrapped())
869 drawNorthScale(g, startColumn, endColumn, ypos);
872 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
874 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
877 g.translate(-labelWidthWest, 0);
879 ypos += wrappedRepeatHeightPx;
880 startColumn += viewportWidth;
891 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
892 int startColumn, int endColumn)
894 int charHeight = av.getCharHeight();
895 int charWidth = av.getCharWidth();
897 g.setColor(Color.blue);
898 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
899 List<Integer> positions = hidden.findHiddenRegionPositions();
900 for (int pos : positions)
902 int res = pos - startColumn;
904 if (res < 0 || res > endColumn - startColumn)
910 * draw a downward-pointing triangle at the hidden columns location
911 * (before the following visible column)
913 int xMiddle = res * charWidth;
914 int[] xPoints = new int[] { xMiddle - charHeight / 4,
915 xMiddle + charHeight / 4, xMiddle };
916 int yTop = ypos - (charHeight / 2);
917 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
918 g.fillPolygon(xPoints, yPoints, 3);
923 * Draw a selection group over a wrapped alignment
925 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
927 int canvasHeight, int startRes)
929 int charHeight = av.getCharHeight();
930 int charWidth = av.getCharWidth();
932 // height gap above each panel
933 int hgap = charHeight;
934 if (av.getScaleAboveWrapped())
939 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
941 int cHeight = av.getAlignment().getHeight() * charHeight;
943 int startx = startRes;
945 int ypos = hgap; // vertical offset
946 int maxwidth = av.getAlignment().getWidth();
948 if (av.hasHiddenColumns())
950 maxwidth = av.getAlignment().getHiddenColumns()
951 .findColumnPosition(maxwidth);
954 // chop the wrapped alignment extent up into panel-sized blocks and treat
955 // each block as if it were a block from an unwrapped alignment
956 while ((ypos <= canvasHeight) && (startx < maxwidth))
958 // set end value to be start + width, or maxwidth, whichever is smaller
959 endx = startx + cWidth - 1;
966 g.translate(labelWidthWest, 0);
968 drawUnwrappedSelection(g, group, startx, endx, 0,
969 av.getAlignment().getHeight() - 1,
972 g.translate(-labelWidthWest, 0);
974 // update vertical offset
975 ypos += cHeight + getAnnotationHeight() + hgap;
977 // update horizontal offset
982 int getAnnotationHeight()
984 if (!av.isShowAnnotation())
989 if (annotations == null)
991 annotations = new AnnotationPanel(av);
994 return annotations.adjustPanelHeight();
998 * Draws the visible region of the alignment on the graphics context. If there
999 * are hidden column markers in the visible region, then each sub-region
1000 * between the markers is drawn separately, followed by the hidden column
1004 * the graphics context, positioned at the first residue to be drawn
1006 * offset of the first column to draw (0..)
1008 * offset of the last column to draw (0..)
1010 * offset of the first sequence to draw (0..)
1012 * offset of the last sequence to draw (0..)
1014 * vertical offset at which to draw (for wrapped alignments)
1016 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1017 final int startSeq, final int endSeq, final int yOffset)
1019 int charHeight = av.getCharHeight();
1020 int charWidth = av.getCharWidth();
1022 if (!av.hasHiddenColumns())
1024 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1029 final int screenYMax = endRes - startRes;
1030 int blockStart = startRes;
1031 int blockEnd = endRes;
1033 for (int[] region : av.getAlignment().getHiddenColumns()
1034 .getHiddenColumnsCopy())
1036 int hideStart = region[0];
1037 int hideEnd = region[1];
1039 if (hideStart <= blockStart)
1041 blockStart += (hideEnd - hideStart) + 1;
1046 * draw up to just before the next hidden region, or the end of
1047 * the visible region, whichever comes first
1049 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1052 g1.translate(screenY * charWidth, 0);
1054 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1057 * draw the downline of the hidden column marker (ScalePanel draws the
1058 * triangle on top) if we reached it
1060 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
1062 g1.setColor(Color.blue);
1064 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
1065 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
1066 (endSeq - startSeq + 1) * charHeight + yOffset);
1069 g1.translate(-screenY * charWidth, 0);
1070 screenY += blockEnd - blockStart + 1;
1071 blockStart = hideEnd + 1;
1073 if (screenY > screenYMax)
1075 // already rendered last block
1080 if (screenY <= screenYMax)
1082 // remaining visible region to render
1083 blockEnd = blockStart + screenYMax - screenY;
1084 g1.translate(screenY * charWidth, 0);
1085 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1087 g1.translate(-screenY * charWidth, 0);
1094 * Draws a region of the visible alignment
1098 * offset of the first column in the visible region (0..)
1100 * offset of the last column in the visible region (0..)
1102 * offset of the first sequence in the visible region (0..)
1104 * offset of the last sequence in the visible region (0..)
1106 * vertical offset at which to draw (for wrapped alignments)
1108 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1109 int endSeq, int offset)
1111 int charHeight = av.getCharHeight();
1112 int charWidth = av.getCharWidth();
1114 g.setFont(av.getFont());
1115 seqRdr.prepare(g, av.isRenderGaps());
1119 // / First draw the sequences
1120 // ///////////////////////////
1121 for (int i = startSeq; i <= endSeq; i++)
1123 nextSeq = av.getAlignment().getSequenceAt(i);
1124 if (nextSeq == null)
1126 // occasionally, a race condition occurs such that the alignment row is
1130 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1131 startRes, endRes, offset + ((i - startSeq) * charHeight));
1133 if (av.isShowSequenceFeatures())
1135 fr.drawSequence(g, nextSeq, startRes, endRes,
1136 offset + ((i - startSeq) * charHeight), false);
1140 * highlight search Results once sequence has been drawn
1142 if (av.hasSearchResults())
1144 SearchResultsI searchResults = av.getSearchResults();
1145 int[] visibleResults = searchResults.getResults(nextSeq,
1147 if (visibleResults != null)
1149 for (int r = 0; r < visibleResults.length; r += 2)
1151 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1152 visibleResults[r + 1], (visibleResults[r] - startRes)
1154 + ((i - startSeq) * charHeight));
1159 if (av.cursorMode && cursorY == i && cursorX >= startRes
1160 && cursorX <= endRes)
1162 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1163 offset + ((i - startSeq) * charHeight));
1167 if (av.getSelectionGroup() != null
1168 || av.getAlignment().getGroups().size() > 0)
1170 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1175 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1176 int startSeq, int endSeq, int offset)
1178 Graphics2D g = (Graphics2D) g1;
1180 // ///////////////////////////////////
1181 // Now outline any areas if necessary
1182 // ///////////////////////////////////
1184 SequenceGroup group = null;
1185 int groupIndex = -1;
1187 if (av.getAlignment().getGroups().size() > 0)
1189 group = av.getAlignment().getGroups().get(0);
1195 g.setStroke(new BasicStroke());
1196 g.setColor(group.getOutlineColour());
1200 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1205 g.setStroke(new BasicStroke());
1207 if (groupIndex >= av.getAlignment().getGroups().size())
1212 group = av.getAlignment().getGroups().get(groupIndex);
1214 } while (groupIndex < av.getAlignment().getGroups().size());
1222 * Draw the selection group as a separate image and overlay
1224 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1225 int startSeq, int endSeq)
1227 // get a new image of the correct size
1228 BufferedImage selectionImage = setupImage();
1230 if (selectionImage == null)
1235 SequenceGroup group = av.getSelectionGroup();
1242 // set up drawing colour
1243 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1245 setupSelectionGroup(g, selectionImage);
1247 if (!av.getWrapAlignment())
1249 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1254 drawWrappedSelection(g, group, getWidth(), getHeight(),
1255 av.getRanges().getStartRes());
1259 return selectionImage;
1263 * Set up graphics for selection group
1265 private void setupSelectionGroup(Graphics2D g,
1266 BufferedImage selectionImage)
1268 // set background to transparent
1269 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1270 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1272 // set up foreground to draw red dashed line
1273 g.setComposite(AlphaComposite.Src);
1274 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1275 BasicStroke.JOIN_ROUND, 3f, new float[]
1277 g.setColor(Color.RED);
1281 * Draw a selection group over an unwrapped alignment
1282 * @param g graphics object to draw with
1283 * @param group selection group
1284 * @param startRes start residue of area to draw
1285 * @param endRes end residue of area to draw
1286 * @param startSeq start sequence of area to draw
1287 * @param endSeq end sequence of area to draw
1288 * @param offset vertical offset (used when called from wrapped alignment code)
1290 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1291 int startRes, int endRes, int startSeq, int endSeq, int offset)
1293 int charWidth = av.getCharWidth();
1295 if (!av.hasHiddenColumns())
1297 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1302 // package into blocks of visible columns
1304 int blockStart = startRes;
1305 int blockEnd = endRes;
1307 for (int[] region : av.getAlignment().getHiddenColumns()
1308 .getHiddenColumnsCopy())
1310 int hideStart = region[0];
1311 int hideEnd = region[1];
1313 if (hideStart <= blockStart)
1315 blockStart += (hideEnd - hideStart) + 1;
1319 blockEnd = hideStart - 1;
1321 g.translate(screenY * charWidth, 0);
1322 drawPartialGroupOutline(g, group,
1323 blockStart, blockEnd, startSeq, endSeq, offset);
1325 g.translate(-screenY * charWidth, 0);
1326 screenY += blockEnd - blockStart + 1;
1327 blockStart = hideEnd + 1;
1329 if (screenY > (endRes - startRes))
1331 // already rendered last block
1336 if (screenY <= (endRes - startRes))
1338 // remaining visible region to render
1339 blockEnd = blockStart + (endRes - startRes) - screenY;
1340 g.translate(screenY * charWidth, 0);
1341 drawPartialGroupOutline(g, group,
1342 blockStart, blockEnd, startSeq, endSeq, offset);
1344 g.translate(-screenY * charWidth, 0);
1350 * Draw the selection group as a separate image and overlay
1352 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1353 int startRes, int endRes, int startSeq, int endSeq,
1356 int charHeight = av.getCharHeight();
1357 int charWidth = av.getCharWidth();
1359 int visWidth = (endRes - startRes + 1) * charWidth;
1363 boolean inGroup = false;
1371 for (i = startSeq; i <= endSeq; i++)
1373 // position of start residue of group relative to startRes, in pixels
1374 sx = (group.getStartRes() - startRes) * charWidth;
1376 // width of group in pixels
1377 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1380 sy = verticalOffset + (i - startSeq) * charHeight;
1382 if (sx + xwidth < 0 || sx > visWidth)
1387 if ((sx <= (endRes - startRes) * charWidth)
1388 && group.getSequences(null)
1389 .contains(av.getAlignment().getSequenceAt(i)))
1391 if ((bottom == -1) && !group.getSequences(null)
1392 .contains(av.getAlignment().getSequenceAt(i + 1)))
1394 bottom = sy + charHeight;
1399 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1400 .contains(av.getAlignment().getSequenceAt(i - 1)))
1413 // if start position is visible, draw vertical line to left of
1415 if (sx >= 0 && sx < visWidth)
1417 g.drawLine(sx, oldY, sx, sy);
1420 // if end position is visible, draw vertical line to right of
1422 if (sx + xwidth < visWidth)
1424 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1433 // don't let width extend beyond current block, or group extent
1435 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1437 xwidth = (endRes - startRes + 1) * charWidth - sx;
1440 // draw horizontal line at top of group
1443 g.drawLine(sx, top, sx + xwidth, top);
1447 // draw horizontal line at bottom of group
1450 g.drawLine(sx, bottom, sx + xwidth, bottom);
1461 sy = verticalOffset + ((i - startSeq) * charHeight);
1462 if (sx >= 0 && sx < visWidth)
1464 g.drawLine(sx, oldY, sx, sy);
1467 if (sx + xwidth < visWidth)
1469 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1478 if (sx + xwidth > visWidth)
1482 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1484 xwidth = (endRes - startRes + 1) * charWidth;
1489 g.drawLine(sx, top, sx + xwidth, top);
1495 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1504 * Highlights search results in the visible region by rendering as white text
1505 * on a black background. Any previous highlighting is removed. Answers true
1506 * if any highlight was left on the visible alignment (so status bar should be
1507 * set to match), else false.
1509 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1510 * alignment had to be scrolled to show the highlighted region, then it should
1511 * be fully redrawn, otherwise a fast paint can be performed. This argument
1512 * could be removed if fast paint of scrolled wrapped alignment is coded in
1513 * future (JAL-2609).
1516 * @param noFastPaint
1519 public boolean highlightSearchResults(SearchResultsI results,
1520 boolean noFastPaint)
1526 boolean wrapped = av.getWrapAlignment();
1529 fastPaint = !noFastPaint;
1530 fastpainting = fastPaint;
1533 * to avoid redrawing the whole visible region, we instead
1534 * redraw just the minimal regions to remove previous highlights
1537 SearchResultsI previous = av.getSearchResults();
1538 av.setSearchResults(results);
1539 boolean redrawn = false;
1540 boolean drawn = false;
1543 redrawn = drawMappedPositionsWrapped(previous);
1544 drawn = drawMappedPositionsWrapped(results);
1549 redrawn = drawMappedPositions(previous);
1550 drawn = drawMappedPositions(results);
1555 * if highlights were either removed or added, repaint
1563 * return true only if highlights were added
1569 fastpainting = false;
1574 * Redraws the minimal rectangle in the visible region (if any) that includes
1575 * mapped positions of the given search results. Whether or not positions are
1576 * highlighted depends on the SearchResults set on the Viewport. This allows
1577 * this method to be called to either clear or set highlighting. Answers true
1578 * if any positions were drawn (in which case a repaint is still required),
1584 protected boolean drawMappedPositions(SearchResultsI results)
1586 if (results == null)
1592 * calculate the minimal rectangle to redraw that
1593 * includes both new and existing search results
1595 int firstSeq = Integer.MAX_VALUE;
1597 int firstCol = Integer.MAX_VALUE;
1599 boolean matchFound = false;
1601 ViewportRanges ranges = av.getRanges();
1602 int firstVisibleColumn = ranges.getStartRes();
1603 int lastVisibleColumn = ranges.getEndRes();
1604 AlignmentI alignment = av.getAlignment();
1605 if (av.hasHiddenColumns())
1607 firstVisibleColumn = alignment.getHiddenColumns()
1608 .adjustForHiddenColumns(firstVisibleColumn);
1609 lastVisibleColumn = alignment.getHiddenColumns()
1610 .adjustForHiddenColumns(lastVisibleColumn);
1613 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1614 .getEndSeq(); seqNo++)
1616 SequenceI seq = alignment.getSequenceAt(seqNo);
1618 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1620 if (visibleResults != null)
1622 for (int i = 0; i < visibleResults.length - 1; i += 2)
1624 int firstMatchedColumn = visibleResults[i];
1625 int lastMatchedColumn = visibleResults[i + 1];
1626 if (firstMatchedColumn <= lastVisibleColumn
1627 && lastMatchedColumn >= firstVisibleColumn)
1630 * found a search results match in the visible region -
1631 * remember the first and last sequence matched, and the first
1632 * and last visible columns in the matched positions
1635 firstSeq = Math.min(firstSeq, seqNo);
1636 lastSeq = Math.max(lastSeq, seqNo);
1637 firstMatchedColumn = Math.max(firstMatchedColumn,
1638 firstVisibleColumn);
1639 lastMatchedColumn = Math.min(lastMatchedColumn,
1641 firstCol = Math.min(firstCol, firstMatchedColumn);
1642 lastCol = Math.max(lastCol, lastMatchedColumn);
1650 if (av.hasHiddenColumns())
1652 firstCol = alignment.getHiddenColumns()
1653 .findColumnPosition(firstCol);
1654 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1656 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1657 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1658 gg.translate(transX, transY);
1659 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1660 gg.translate(-transX, -transY);
1667 public void propertyChange(PropertyChangeEvent evt)
1669 String eventName = evt.getPropertyName();
1671 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1676 else if (eventName.equals(ViewportRanges.STARTRES))
1679 if (eventName.equals(ViewportRanges.STARTRES))
1681 // Make sure we're not trying to draw a panel
1682 // larger than the visible window
1683 ViewportRanges vpRanges = av.getRanges();
1684 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1685 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1686 if (scrollX > range)
1690 else if (scrollX < -range)
1695 // Both scrolling and resizing change viewport ranges: scrolling changes
1696 // both start and end points, but resize only changes end values.
1697 // Here we only want to fastpaint on a scroll, with resize using a normal
1698 // paint, so scroll events are identified as changes to the horizontal or
1699 // vertical start value.
1701 // scroll - startres and endres both change
1702 if (av.getWrapAlignment())
1704 fastPaintWrapped(scrollX);
1708 fastPaint(scrollX, 0);
1711 else if (eventName.equals(ViewportRanges.STARTSEQ))
1714 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1720 * Does a minimal update of the image for a scroll movement. This method
1721 * handles scroll movements of up to one width of the wrapped alignment (one
1722 * click in the vertical scrollbar). Larger movements (for example after a
1723 * scroll to highlight a mapped position) trigger a full redraw instead.
1726 * number of positions scrolled (right if positive, left if negative)
1728 protected void fastPaintWrapped(int scrollX)
1730 ViewportRanges ranges = av.getRanges();
1732 if (Math.abs(scrollX) > ranges.getViewportWidth())
1735 * shift of more than one view width is
1736 * overcomplicated to handle in this method
1743 if (fastpainting || gg == null)
1749 fastpainting = true;
1753 calculateWrappedGeometry(getWidth(), getHeight());
1756 * relocate the regions of the alignment that are still visible
1758 shiftWrappedAlignment(-scrollX);
1761 * add new columns (sequence, annotation)
1762 * - at top left if scrollX < 0
1763 * - at right of last two widths if scrollX > 0
1767 int startRes = ranges.getStartRes();
1768 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1769 - scrollX - 1, getHeight());
1773 fastPaintWrappedAddRight(scrollX);
1777 * draw all scales (if shown) and hidden column markers
1779 drawWrappedDecorators(gg, ranges.getStartRes());
1784 fastpainting = false;
1789 * Draws the specified number of columns at the 'end' (bottom right) of a
1790 * wrapped alignment view, including sequences and annotations if shown, but
1791 * not scales. Also draws the same number of columns at the right hand end of
1792 * the second last width shown, if the last width is not full height (so
1793 * cannot simply be copied from the graphics image).
1797 protected void fastPaintWrappedAddRight(int columns)
1804 ViewportRanges ranges = av.getRanges();
1805 int viewportWidth = ranges.getViewportWidth();
1806 int charWidth = av.getCharWidth();
1809 * draw full height alignment in the second last row, last columns, if the
1810 * last row was not full height
1812 int visibleWidths = wrappedVisibleWidths;
1813 int canvasHeight = getHeight();
1814 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1816 if (lastWidthPartHeight)
1818 int widthsAbove = Math.max(0, visibleWidths - 2);
1819 int ypos = wrappedRepeatHeightPx * widthsAbove
1820 + wrappedSpaceAboveAlignment;
1821 int endRes = ranges.getEndRes();
1822 endRes += widthsAbove * viewportWidth;
1823 int startRes = endRes - columns;
1824 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1828 * white fill first to erase annotations
1830 gg.translate(xOffset, 0);
1831 gg.setColor(Color.white);
1832 gg.fillRect(labelWidthWest, ypos,
1833 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1834 gg.translate(-xOffset, 0);
1836 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1840 * draw newly visible columns in last wrapped width (none if we
1841 * have reached the end of the alignment)
1842 * y-offset for drawing last width is height of widths above,
1845 int widthsAbove = visibleWidths - 1;
1846 int ypos = wrappedRepeatHeightPx * widthsAbove
1847 + wrappedSpaceAboveAlignment;
1848 int endRes = ranges.getEndRes();
1849 endRes += widthsAbove * viewportWidth;
1850 int startRes = endRes - columns + 1;
1853 * white fill first to erase annotations
1855 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1857 gg.translate(xOffset, 0);
1858 gg.setColor(Color.white);
1859 int width = viewportWidth * charWidth - xOffset;
1860 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1861 gg.translate(-xOffset, 0);
1863 gg.setFont(av.getFont());
1864 gg.setColor(Color.black);
1866 if (startRes < ranges.getVisibleAlignmentWidth())
1868 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1872 * and finally, white fill any space below the visible alignment
1874 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1875 if (heightBelow > 0)
1877 gg.setColor(Color.white);
1878 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1883 * Shifts the visible alignment by the specified number of columns - left if
1884 * negative, right if positive. Copies and moves sequences and annotations (if
1885 * shown). Scales, hidden column markers and any newly visible columns must be
1890 protected void shiftWrappedAlignment(int positions)
1896 int charWidth = av.getCharWidth();
1898 int canvasHeight = getHeight();
1899 ViewportRanges ranges = av.getRanges();
1900 int viewportWidth = ranges.getViewportWidth();
1901 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1903 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1904 int xMax = ranges.getVisibleAlignmentWidth();
1909 * shift right (after scroll left)
1910 * for each wrapped width (starting with the last), copy (width-positions)
1911 * columns from the left margin to the right margin, and copy positions
1912 * columns from the right margin of the row above (if any) to the
1913 * left margin of the current row
1917 * get y-offset of last wrapped width, first row of sequences
1919 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1920 y += wrappedSpaceAboveAlignment;
1921 int copyFromLeftStart = labelWidthWest;
1922 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1926 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1927 positions * charWidth, 0);
1930 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1931 positions * charWidth, heightToCopy, -widthToCopy,
1932 wrappedRepeatHeightPx);
1935 y -= wrappedRepeatHeightPx;
1941 * shift left (after scroll right)
1942 * for each wrapped width (starting with the first), copy (width-positions)
1943 * columns from the right margin to the left margin, and copy positions
1944 * columns from the left margin of the row below (if any) to the
1945 * right margin of the current row
1947 int xpos = av.getRanges().getStartRes();
1948 int y = wrappedSpaceAboveAlignment;
1949 int copyFromRightStart = labelWidthWest - positions * charWidth;
1951 while (y < canvasHeight)
1953 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1954 positions * charWidth, 0);
1955 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1956 && (xpos + viewportWidth <= xMax))
1958 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1959 * charWidth, heightToCopy, widthToCopy,
1960 -wrappedRepeatHeightPx);
1963 y += wrappedRepeatHeightPx;
1964 xpos += viewportWidth;
1971 * Redraws any positions in the search results in the visible region of a
1972 * wrapped alignment. Any highlights are drawn depending on the search results
1973 * set on the Viewport, not the <code>results</code> argument. This allows
1974 * this method to be called either to clear highlights (passing the previous
1975 * search results), or to draw new highlights.
1980 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1982 if (results == null)
1986 int charHeight = av.getCharHeight();
1988 boolean matchFound = false;
1990 calculateWrappedGeometry(getWidth(), getHeight());
1991 int wrappedWidth = av.getWrappedWidth();
1992 int wrappedHeight = wrappedRepeatHeightPx;
1994 ViewportRanges ranges = av.getRanges();
1995 int canvasHeight = getHeight();
1996 int repeats = canvasHeight / wrappedHeight;
1997 if (canvasHeight / wrappedHeight > 0)
2002 int firstVisibleColumn = ranges.getStartRes();
2003 int lastVisibleColumn = ranges.getStartRes() + repeats
2004 * ranges.getViewportWidth() - 1;
2006 AlignmentI alignment = av.getAlignment();
2007 if (av.hasHiddenColumns())
2009 firstVisibleColumn = alignment.getHiddenColumns()
2010 .adjustForHiddenColumns(firstVisibleColumn);
2011 lastVisibleColumn = alignment.getHiddenColumns()
2012 .adjustForHiddenColumns(lastVisibleColumn);
2015 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2017 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2018 .getEndSeq(); seqNo++)
2020 SequenceI seq = alignment.getSequenceAt(seqNo);
2022 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2024 if (visibleResults != null)
2026 for (int i = 0; i < visibleResults.length - 1; i += 2)
2028 int firstMatchedColumn = visibleResults[i];
2029 int lastMatchedColumn = visibleResults[i + 1];
2030 if (firstMatchedColumn <= lastVisibleColumn
2031 && lastMatchedColumn >= firstVisibleColumn)
2034 * found a search results match in the visible region
2036 firstMatchedColumn = Math.max(firstMatchedColumn,
2037 firstVisibleColumn);
2038 lastMatchedColumn = Math.min(lastMatchedColumn,
2042 * draw each mapped position separately (as contiguous positions may
2043 * wrap across lines)
2045 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2047 int displayColumn = mappedPos;
2048 if (av.hasHiddenColumns())
2050 displayColumn = alignment.getHiddenColumns()
2051 .findColumnPosition(displayColumn);
2055 * transX: offset from left edge of canvas to residue position
2057 int transX = labelWidthWest
2058 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2059 * av.getCharWidth();
2062 * transY: offset from top edge of canvas to residue position
2064 int transY = gapHeight;
2065 transY += (displayColumn - ranges.getStartRes())
2066 / wrappedWidth * wrappedHeight;
2067 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2070 * yOffset is from graphics origin to start of visible region
2072 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2073 if (transY < getHeight())
2076 gg.translate(transX, transY);
2077 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2079 gg.translate(-transX, -transY);