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;
50 * The Swing component on which the alignment sequences, and annotations (if
51 * shown), are drawn. This includes scales above, left and right (if shown) in
52 * Wrapped mode, but not the scale above in Unwrapped mode.
55 public class SeqCanvas extends JComponent implements ViewportListenerI
57 private static final String ZEROS = "0000000000";
59 final FeatureRenderer fr;
69 private final SequenceRenderer seqRdr;
71 private boolean fastPaint = false;
73 private boolean fastpainting = false;
75 private AnnotationPanel annotations;
78 * measurements for drawing a wrapped alignment
80 private int labelWidthEast; // label right width in pixels if shown
82 private int labelWidthWest; // label left width in pixels if shown
84 private int wrappedSpaceAboveAlignment; // gap between widths
86 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
88 private int wrappedVisibleWidths; // number of wrapped widths displayed
90 private Graphics2D gg;
93 * Creates a new SeqCanvas object.
97 public SeqCanvas(AlignmentPanel ap)
100 fr = new FeatureRenderer(ap);
101 seqRdr = new SequenceRenderer(av);
102 setLayout(new BorderLayout());
103 PaintRefresher.Register(this, av.getSequenceSetId());
104 setBackground(Color.white);
106 av.getRanges().addPropertyChangeListener(this);
109 public SequenceRenderer getSequenceRenderer()
114 public FeatureRenderer getFeatureRenderer()
120 * Draws the scale above a region of a wrapped alignment, consisting of a
121 * column number every major interval (10 columns).
124 * the graphics context to draw on, positioned at the start (bottom
125 * left) of the line on which to draw any scale marks
127 * start alignment column (0..)
129 * end alignment column (0..)
131 * y offset to draw at
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
135 int charHeight = av.getCharHeight();
136 int charWidth = av.getCharWidth();
139 * white fill the scale space (for the fastPaint case)
141 g.setColor(Color.white);
142 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
143 charHeight * 3 / 2 + 2);
144 g.setColor(Color.black);
146 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
148 for (ScaleMark mark : marks)
150 int mpos = mark.column; // (i - startx - 1)
155 String mstring = mark.text;
161 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
165 * draw a tick mark below the column number, centred on the column;
166 * height of tick mark is 4 pixels less than half a character
168 int xpos = (mpos * charWidth) + (charWidth / 2);
169 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
175 * Draw the scale to the left or right of a wrapped alignment
178 * graphics context, positioned at the start of the scale to be drawn
180 * first column of wrapped width (0.. excluding any hidden columns)
182 * last column of wrapped width (0.. excluding any hidden columns)
184 * vertical offset at which to begin the scale
186 * if true, scale is left of residues, if false, scale is right
188 void drawVerticalScale(Graphics g, final int startx, final int endx,
189 final int ypos, final boolean left)
191 int charHeight = av.getCharHeight();
192 int charWidth = av.getCharWidth();
194 int yPos = ypos + charHeight;
198 if (av.hasHiddenColumns())
200 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
201 startX = hiddenColumns.adjustForHiddenColumns(startx);
202 endX = hiddenColumns.adjustForHiddenColumns(endx);
204 FontMetrics fm = getFontMetrics(av.getFont());
206 for (int i = 0; i < av.getAlignment().getHeight(); i++)
208 SequenceI seq = av.getAlignment().getSequenceAt(i);
211 * find sequence position of first non-gapped position -
212 * to the right if scale left, to the left if scale right
214 int index = left ? startX : endX;
216 while (index >= startX && index <= endX)
218 if (!Comparison.isGap(seq.getCharAt(index)))
220 value = seq.findPosition(index);
234 * white fill the space for the scale
236 g.setColor(Color.white);
237 int y = (yPos + (i * charHeight)) - (charHeight / 5);
238 // fillRect origin is top left of rectangle
239 g.fillRect(0, y - charHeight, left ? labelWidthWest : labelWidthEast,
245 * draw scale value, right justified within its width less half a
246 * character width padding on the right
248 int labelSpace = left ? labelWidthWest : labelWidthEast;
249 labelSpace -= charWidth / 2; // leave space to the right
250 String valueAsString = String.valueOf(value);
251 int labelLength = fm.stringWidth(valueAsString);
252 int xOffset = labelSpace - labelLength;
253 g.setColor(Color.black);
254 g.drawString(valueAsString, xOffset, y);
260 * Does a fast paint of an alignment in response to a scroll. Most of the
261 * visible region is simply copied and shifted, and then any newly visible
262 * columns or rows are drawn. The scroll may be horizontal or vertical, but
263 * not both at once. Scrolling may be the result of
265 * <li>dragging a scroll bar</li>
266 * <li>clicking in the scroll bar</li>
267 * <li>scrolling by trackpad, middle mouse button, or other device</li>
268 * <li>by moving the box in the Overview window</li>
269 * <li>programmatically to make a highlighted position visible</li>
273 * columns to shift right (positive) or left (negative)
275 * rows to shift down (positive) or up (negative)
277 public void fastPaint(int horizontal, int vertical)
279 if (fastpainting || gg == null || img == null)
288 int charHeight = av.getCharHeight();
289 int charWidth = av.getCharWidth();
291 ViewportRanges ranges = av.getRanges();
292 int startRes = ranges.getStartRes();
293 int endRes = ranges.getEndRes();
294 int startSeq = ranges.getStartSeq();
295 int endSeq = ranges.getEndSeq();
299 gg.copyArea(horizontal * charWidth, vertical * charHeight,
300 img.getWidth(), img.getHeight(), -horizontal * charWidth,
301 -vertical * charHeight);
303 if (horizontal > 0) // scrollbar pulled right, image to the left
305 transX = (endRes - startRes - horizontal) * charWidth;
306 startRes = endRes - horizontal;
308 else if (horizontal < 0)
310 endRes = startRes - horizontal;
312 else if (vertical > 0) // scroll down
314 startSeq = endSeq - vertical;
316 if (startSeq < ranges.getStartSeq())
317 { // ie scrolling too fast, more than a page at a time
318 startSeq = ranges.getStartSeq();
322 transY = img.getHeight() - ((vertical + 1) * charHeight);
325 else if (vertical < 0)
327 endSeq = startSeq - vertical;
329 if (endSeq > ranges.getEndSeq())
331 endSeq = ranges.getEndSeq();
335 gg.translate(transX, transY);
336 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
337 gg.translate(-transX, -transY);
342 fastpainting = false;
347 public void paintComponent(Graphics g)
349 super.paintComponent(g);
351 int charHeight = av.getCharHeight();
352 int charWidth = av.getCharWidth();
354 ViewportRanges ranges = av.getRanges();
356 int width = getWidth();
357 int height = getHeight();
359 width -= (width % charWidth);
360 height -= (height % charHeight);
362 // selectImage is the selection group outline image
363 BufferedImage selectImage = drawSelectionGroup(
364 ranges.getStartRes(), ranges.getEndRes(),
365 ranges.getStartSeq(), ranges.getEndSeq());
367 if ((img != null) && (fastPaint
368 || (getVisibleRect().width != g.getClipBounds().width)
369 || (getVisibleRect().height != g.getClipBounds().height)))
371 BufferedImage lcimg = buildLocalImage(selectImage);
372 g.drawImage(lcimg, 0, 0, this);
375 else if ((width > 0) && (height > 0))
377 // img is a cached version of the last view we drew, if any
378 // if we have no img or the size has changed, make a new one
379 if (img == null || width != img.getWidth()
380 || height != img.getHeight())
387 gg = (Graphics2D) img.getGraphics();
388 gg.setFont(av.getFont());
393 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
394 RenderingHints.VALUE_ANTIALIAS_ON);
397 gg.setColor(Color.white);
398 gg.fillRect(0, 0, img.getWidth(), img.getHeight());
400 if (av.getWrapAlignment())
402 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
406 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
407 ranges.getStartSeq(), ranges.getEndSeq(), 0);
410 // lcimg is a local *copy* of img which we'll draw selectImage on top of
411 BufferedImage lcimg = buildLocalImage(selectImage);
412 g.drawImage(lcimg, 0, 0, this);
417 * Draw an alignment panel for printing
420 * Graphics object to draw with
422 * start residue of print area
424 * end residue of print area
426 * start sequence of print area
428 * end sequence of print area
430 public void drawPanelForPrinting(Graphics g1, int startRes, int endRes,
431 int startSeq, int endSeq)
433 drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
435 BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
437 if (selectImage != null)
439 ((Graphics2D) g1).setComposite(AlphaComposite
440 .getInstance(AlphaComposite.SRC_OVER));
441 g1.drawImage(selectImage, 0, 0, this);
446 * Draw a wrapped alignment panel for printing
449 * Graphics object to draw with
451 * width of drawing area
452 * @param canvasHeight
453 * height of drawing area
455 * start residue of print area
457 public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
458 int canvasHeight, int startRes)
460 SequenceGroup group = av.getSelectionGroup();
462 drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
466 BufferedImage selectImage = null;
469 selectImage = new BufferedImage(canvasWidth, canvasHeight,
470 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
471 } catch (OutOfMemoryError er)
474 System.err.println("Print image OutOfMemory Error.\n" + er);
475 new OOMWarning("Creating wrapped alignment image for printing", er);
477 if (selectImage != null)
479 Graphics2D g2 = selectImage.createGraphics();
480 setupSelectionGroup(g2, selectImage);
481 drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
485 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
486 g.drawImage(selectImage, 0, 0, this);
493 * Make a local image by combining the cached image img
496 private BufferedImage buildLocalImage(BufferedImage selectImage)
498 // clone the cached image
499 BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
501 Graphics2D g2d = lcimg.createGraphics();
502 g2d.drawImage(img, 0, 0, null);
504 // overlay selection group on lcimg
505 if (selectImage != null)
508 AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
509 g2d.drawImage(selectImage, 0, 0, this);
517 * Set up a buffered image of the correct height and size for the sequence canvas
519 private BufferedImage setupImage()
521 BufferedImage lcimg = null;
523 int charWidth = av.getCharWidth();
524 int charHeight = av.getCharHeight();
526 int width = getWidth();
527 int height = getHeight();
529 width -= (width % charWidth);
530 height -= (height % charHeight);
532 if ((width < 1) || (height < 1))
539 lcimg = new BufferedImage(width, height,
540 BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
541 } catch (OutOfMemoryError er)
545 "Group image OutOfMemory Redraw Error.\n" + er);
546 new OOMWarning("Creating alignment image for display", er);
555 * Returns the visible width of the canvas in residues, after allowing for
556 * East or West scales (if shown)
559 * the width in pixels (possibly including scales)
563 public int getWrappedCanvasWidth(int canvasWidth)
565 int charWidth = av.getCharWidth();
567 FontMetrics fm = getFontMetrics(av.getFont());
571 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
573 labelWidth = getLabelWidth(fm);
576 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
578 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
580 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
584 * Returns a pixel width sufficient to show the largest sequence coordinate
585 * (end position) in the alignment, calculated as the FontMetrics width of
586 * zeroes "0000000" limited to the number of decimal digits to be shown (3 for
587 * 1-10, 4 for 11-99 etc). One character width is added to this, to allow for
588 * half a character width space on either side.
593 protected int getLabelWidth(FontMetrics fm)
596 * find the biggest sequence end position we need to show
597 * (note this is not necessarily the sequence length)
600 AlignmentI alignment = av.getAlignment();
601 for (int i = 0; i < alignment.getHeight(); i++)
603 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
607 for (int i = maxWidth; i > 0; i /= 10)
612 return fm.stringWidth(ZEROS.substring(0, length)) + av.getCharWidth();
616 * Draws as many widths of a wrapped alignment as can fit in the visible
621 * available width in pixels
622 * @param canvasHeight
623 * available height in pixels
625 * the first column (0...) of the alignment to draw
627 public void drawWrappedPanel(Graphics g, int canvasWidth,
628 int canvasHeight, final int startColumn)
630 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
633 av.setWrappedWidth(wrappedWidthInResidues);
635 ViewportRanges ranges = av.getRanges();
636 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
639 * draw one width at a time (including any scales or annotation shown),
640 * until we have run out of either alignment or vertical space available
642 int ypos = wrappedSpaceAboveAlignment;
643 int maxWidth = ranges.getVisibleAlignmentWidth();
645 int start = startColumn;
646 int currentWidth = 0;
647 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
650 .min(maxWidth, start + wrappedWidthInResidues - 1);
651 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
652 ypos += wrappedRepeatHeightPx;
653 start += wrappedWidthInResidues;
657 drawWrappedDecorators(g, startColumn);
661 * Calculates and saves values needed when rendering a wrapped alignment.
662 * These depend on many factors, including
664 * <li>canvas width and height</li>
665 * <li>number of visible sequences, and height of annotations if shown</li>
666 * <li>font and character width</li>
667 * <li>whether scales are shown left, right or above the alignment</li>
671 * @param canvasHeight
672 * @return the number of residue columns in each width
674 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
676 int charHeight = av.getCharHeight();
679 * vertical space in pixels between wrapped widths of alignment
680 * - one character height, or two if scale above is drawn
682 wrappedSpaceAboveAlignment = charHeight
683 * (av.getScaleAboveWrapped() ? 2 : 1);
686 * height in pixels of the wrapped widths
688 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
690 wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
692 // add annotations panel height if shown
693 wrappedRepeatHeightPx += getAnnotationHeight();
696 * number of visible widths (the last one may be part height),
697 * ensuring a part height includes at least one sequence
699 ViewportRanges ranges = av.getRanges();
700 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
701 int remainder = canvasHeight % wrappedRepeatHeightPx;
702 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
704 wrappedVisibleWidths++;
708 * compute width in residues; this also sets East and West label widths
710 int wrappedWidthInResidues = getWrappedCanvasWidth(canvasWidth);
713 * limit visibleWidths to not exceed width of alignment
715 int xMax = ranges.getVisibleAlignmentWidth();
716 int startToEnd = xMax - ranges.getStartRes();
717 int maxWidths = startToEnd / wrappedWidthInResidues;
718 if (startToEnd % wrappedWidthInResidues > 0)
722 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
724 return wrappedWidthInResidues;
728 * Draws one width of a wrapped alignment, including sequences and
729 * annnotations, if shown, but not scales or hidden column markers
735 * @param canvasHeight
737 protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
738 int endColumn, int canvasHeight)
740 ViewportRanges ranges = av.getRanges();
741 int viewportWidth = ranges.getViewportWidth();
743 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
746 * move right before drawing by the width of the scale left (if any)
747 * plus column offset from left margin (usually zero, but may be non-zero
748 * when fast painting is drawing just a few columns)
750 int charWidth = av.getCharWidth();
751 int xOffset = labelWidthWest
752 + ((startColumn - ranges.getStartRes()) % viewportWidth)
754 g.translate(xOffset, 0);
756 // When printing we have an extra clipped region,
757 // the Printable page which we need to account for here
758 Shape clip = g.getClip();
762 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
766 g.setClip(0, (int) clip.getBounds().getY(),
767 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
771 * white fill the region to be drawn (so incremental fast paint doesn't
772 * scribble over an existing image)
774 gg.setColor(Color.white);
775 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
776 wrappedRepeatHeightPx);
778 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
781 int cHeight = av.getAlignment().getHeight() * av.getCharHeight();
783 if (av.isShowAnnotation())
785 g.translate(0, cHeight + ypos + 3);
786 if (annotations == null)
788 annotations = new AnnotationPanel(av);
791 annotations.renderer.drawComponent(annotations, av, g, -1,
792 startColumn, endx + 1);
793 g.translate(0, -cHeight - ypos - 3);
796 g.translate(-xOffset, 0);
800 * Draws scales left, right and above (if shown), and any hidden column
801 * markers, on all widths of the wrapped alignment
806 protected void drawWrappedDecorators(Graphics g, final int startColumn)
808 int charWidth = av.getCharWidth();
810 g.setFont(av.getFont());
811 g.setColor(Color.black);
813 int ypos = wrappedSpaceAboveAlignment;
814 ViewportRanges ranges = av.getRanges();
815 int viewportWidth = ranges.getViewportWidth();
816 int maxWidth = ranges.getVisibleAlignmentWidth();
818 int startCol = startColumn;
820 while (widthsDrawn < wrappedVisibleWidths)
822 int endColumn = Math.min(maxWidth, startCol + viewportWidth - 1);
824 if (av.getScaleLeftWrapped())
826 drawVerticalScale(g, startCol, endColumn - 1, ypos, true);
829 if (av.getScaleRightWrapped())
831 int x = labelWidthWest + viewportWidth * charWidth;
833 drawVerticalScale(g, startCol, endColumn, ypos, false);
838 * white fill region of scale above and hidden column markers
839 * (to support incremental fast paint of image)
841 g.translate(labelWidthWest, 0);
842 g.setColor(Color.white);
843 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
844 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
845 g.setColor(Color.black);
846 g.translate(-labelWidthWest, 0);
848 g.translate(labelWidthWest, 0);
850 if (av.getScaleAboveWrapped())
852 drawNorthScale(g, startCol, endColumn, ypos);
855 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
857 drawHiddenColumnMarkers(g, ypos, startCol, endColumn);
860 g.translate(-labelWidthWest, 0);
862 ypos += wrappedRepeatHeightPx;
863 startCol += viewportWidth;
869 * Draws markers (triangles) above hidden column positions between startColumn
877 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
878 int startColumn, int endColumn)
880 int charHeight = av.getCharHeight();
881 int charWidth = av.getCharWidth();
883 g.setColor(Color.blue);
884 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
885 List<Integer> positions = hidden.findHiddenRegionPositions();
886 for (int pos : positions)
888 int res = pos - startColumn;
890 if (res < 0 || res > endColumn - startColumn + 1)
896 * draw a downward-pointing triangle at the hidden columns location
897 * (before the following visible column)
899 int xMiddle = res * charWidth;
900 int[] xPoints = new int[] { xMiddle - charHeight / 4,
901 xMiddle + charHeight / 4, xMiddle };
902 int yTop = ypos - (charHeight / 2);
903 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
904 g.fillPolygon(xPoints, yPoints, 3);
909 * Draw a selection group over a wrapped alignment
911 private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
913 int canvasHeight, int startRes)
915 int charHeight = av.getCharHeight();
916 int charWidth = av.getCharWidth();
918 // height gap above each panel
919 int hgap = charHeight;
920 if (av.getScaleAboveWrapped())
925 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
927 int cHeight = av.getAlignment().getHeight() * charHeight;
929 int startx = startRes;
931 int ypos = hgap; // vertical offset
932 int maxwidth = av.getAlignment().getWidth();
934 if (av.hasHiddenColumns())
936 maxwidth = av.getAlignment().getHiddenColumns()
937 .findColumnPosition(maxwidth);
940 // chop the wrapped alignment extent up into panel-sized blocks and treat
941 // each block as if it were a block from an unwrapped alignment
942 while ((ypos <= canvasHeight) && (startx < maxwidth))
944 // set end value to be start + width, or maxwidth, whichever is smaller
945 endx = startx + cWidth - 1;
952 g.translate(labelWidthWest, 0);
954 drawUnwrappedSelection(g, group, startx, endx, 0,
955 av.getAlignment().getHeight() - 1,
958 g.translate(-labelWidthWest, 0);
960 // update vertical offset
961 ypos += cHeight + getAnnotationHeight() + hgap;
963 // update horizontal offset
968 int getAnnotationHeight()
970 if (!av.isShowAnnotation())
975 if (annotations == null)
977 annotations = new AnnotationPanel(av);
980 return annotations.adjustPanelHeight();
984 * Draws the visible region of the alignment on the graphics context. If there
985 * are hidden column markers in the visible region, then each sub-region
986 * between the markers is drawn separately, followed by the hidden column
990 * the graphics context, positioned at the first residue to be drawn
992 * offset of the first column to draw (0..)
994 * offset of the last column to draw (0..)
996 * offset of the first sequence to draw (0..)
998 * offset of the last sequence to draw (0..)
1000 * vertical offset at which to draw (for wrapped alignments)
1002 public void drawPanel(Graphics g1, final int startRes, final int endRes,
1003 final int startSeq, final int endSeq, final int yOffset)
1005 int charHeight = av.getCharHeight();
1006 int charWidth = av.getCharWidth();
1008 if (!av.hasHiddenColumns())
1010 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
1015 final int screenYMax = endRes - startRes;
1016 int blockStart = startRes;
1017 int blockEnd = endRes;
1019 for (int[] region : av.getAlignment().getHiddenColumns()
1020 .getHiddenColumnsCopy())
1022 int hideStart = region[0];
1023 int hideEnd = region[1];
1025 if (hideStart <= blockStart)
1027 blockStart += (hideEnd - hideStart) + 1;
1032 * draw up to just before the next hidden region, or the end of
1033 * the visible region, whichever comes first
1035 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
1038 g1.translate(screenY * charWidth, 0);
1040 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1043 * draw the downline of the hidden column marker (ScalePanel draws the
1044 * triangle on top) if we reached it
1046 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
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;
1057 blockStart = hideEnd + 1;
1059 if (screenY > screenYMax)
1061 // already rendered last block
1066 if (screenY <= screenYMax)
1068 // remaining visible region to render
1069 blockEnd = blockStart + screenYMax - screenY;
1070 g1.translate(screenY * charWidth, 0);
1071 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
1073 g1.translate(-screenY * charWidth, 0);
1080 * Draws a region of the visible alignment
1084 * offset of the first column in the visible region (0..)
1086 * offset of the last column in the visible region (0..)
1088 * offset of the first sequence in the visible region (0..)
1090 * offset of the last sequence in the visible region (0..)
1092 * vertical offset at which to draw (for wrapped alignments)
1094 private void draw(Graphics g, int startRes, int endRes, int startSeq,
1095 int endSeq, int offset)
1097 int charHeight = av.getCharHeight();
1098 int charWidth = av.getCharWidth();
1100 g.setFont(av.getFont());
1101 seqRdr.prepare(g, av.isRenderGaps());
1105 // / First draw the sequences
1106 // ///////////////////////////
1107 for (int i = startSeq; i <= endSeq; i++)
1109 nextSeq = av.getAlignment().getSequenceAt(i);
1110 if (nextSeq == null)
1112 // occasionally, a race condition occurs such that the alignment row is
1116 seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
1117 startRes, endRes, offset + ((i - startSeq) * charHeight));
1119 if (av.isShowSequenceFeatures())
1121 fr.drawSequence(g, nextSeq, startRes, endRes,
1122 offset + ((i - startSeq) * charHeight), false);
1126 * highlight search Results once sequence has been drawn
1128 if (av.hasSearchResults())
1130 SearchResultsI searchResults = av.getSearchResults();
1131 int[] visibleResults = searchResults.getResults(nextSeq,
1133 if (visibleResults != null)
1135 for (int r = 0; r < visibleResults.length; r += 2)
1137 seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
1138 visibleResults[r + 1], (visibleResults[r] - startRes)
1140 + ((i - startSeq) * charHeight));
1145 if (av.cursorMode && cursorY == i && cursorX >= startRes
1146 && cursorX <= endRes)
1148 seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
1149 offset + ((i - startSeq) * charHeight));
1153 if (av.getSelectionGroup() != null
1154 || av.getAlignment().getGroups().size() > 0)
1156 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
1161 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
1162 int startSeq, int endSeq, int offset)
1164 Graphics2D g = (Graphics2D) g1;
1166 // ///////////////////////////////////
1167 // Now outline any areas if necessary
1168 // ///////////////////////////////////
1170 SequenceGroup group = null;
1171 int groupIndex = -1;
1173 if (av.getAlignment().getGroups().size() > 0)
1175 group = av.getAlignment().getGroups().get(0);
1181 g.setStroke(new BasicStroke());
1182 g.setColor(group.getOutlineColour());
1186 drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
1191 g.setStroke(new BasicStroke());
1193 if (groupIndex >= av.getAlignment().getGroups().size())
1198 group = av.getAlignment().getGroups().get(groupIndex);
1200 } while (groupIndex < av.getAlignment().getGroups().size());
1208 * Draw the selection group as a separate image and overlay
1210 private BufferedImage drawSelectionGroup(int startRes, int endRes,
1211 int startSeq, int endSeq)
1213 // get a new image of the correct size
1214 BufferedImage selectionImage = setupImage();
1216 if (selectionImage == null)
1221 SequenceGroup group = av.getSelectionGroup();
1228 // set up drawing colour
1229 Graphics2D g = (Graphics2D) selectionImage.getGraphics();
1231 setupSelectionGroup(g, selectionImage);
1233 if (!av.getWrapAlignment())
1235 drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
1240 drawWrappedSelection(g, group, getWidth(), getHeight(),
1241 av.getRanges().getStartRes());
1245 return selectionImage;
1249 * Set up graphics for selection group
1251 private void setupSelectionGroup(Graphics2D g,
1252 BufferedImage selectionImage)
1254 // set background to transparent
1255 g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
1256 g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
1258 // set up foreground to draw red dashed line
1259 g.setComposite(AlphaComposite.Src);
1260 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1261 BasicStroke.JOIN_ROUND, 3f, new float[]
1263 g.setColor(Color.RED);
1267 * Draw a selection group over an unwrapped alignment
1268 * @param g graphics object to draw with
1269 * @param group selection group
1270 * @param startRes start residue of area to draw
1271 * @param endRes end residue of area to draw
1272 * @param startSeq start sequence of area to draw
1273 * @param endSeq end sequence of area to draw
1274 * @param offset vertical offset (used when called from wrapped alignment code)
1276 private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
1277 int startRes, int endRes, int startSeq, int endSeq, int offset)
1279 int charWidth = av.getCharWidth();
1281 if (!av.hasHiddenColumns())
1283 drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
1288 // package into blocks of visible columns
1290 int blockStart = startRes;
1291 int blockEnd = endRes;
1293 for (int[] region : av.getAlignment().getHiddenColumns()
1294 .getHiddenColumnsCopy())
1296 int hideStart = region[0];
1297 int hideEnd = region[1];
1299 if (hideStart <= blockStart)
1301 blockStart += (hideEnd - hideStart) + 1;
1305 blockEnd = hideStart - 1;
1307 g.translate(screenY * charWidth, 0);
1308 drawPartialGroupOutline(g, group,
1309 blockStart, blockEnd, startSeq, endSeq, offset);
1311 g.translate(-screenY * charWidth, 0);
1312 screenY += blockEnd - blockStart + 1;
1313 blockStart = hideEnd + 1;
1315 if (screenY > (endRes - startRes))
1317 // already rendered last block
1322 if (screenY <= (endRes - startRes))
1324 // remaining visible region to render
1325 blockEnd = blockStart + (endRes - startRes) - screenY;
1326 g.translate(screenY * charWidth, 0);
1327 drawPartialGroupOutline(g, group,
1328 blockStart, blockEnd, startSeq, endSeq, offset);
1330 g.translate(-screenY * charWidth, 0);
1336 * Draw the selection group as a separate image and overlay
1338 private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
1339 int startRes, int endRes, int startSeq, int endSeq,
1342 int charHeight = av.getCharHeight();
1343 int charWidth = av.getCharWidth();
1345 int visWidth = (endRes - startRes + 1) * charWidth;
1349 boolean inGroup = false;
1357 for (i = startSeq; i <= endSeq; i++)
1359 // position of start residue of group relative to startRes, in pixels
1360 sx = (group.getStartRes() - startRes) * charWidth;
1362 // width of group in pixels
1363 xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1366 sy = verticalOffset + (i - startSeq) * charHeight;
1368 if (sx + xwidth < 0 || sx > visWidth)
1373 if ((sx <= (endRes - startRes) * charWidth)
1374 && group.getSequences(null)
1375 .contains(av.getAlignment().getSequenceAt(i)))
1377 if ((bottom == -1) && !group.getSequences(null)
1378 .contains(av.getAlignment().getSequenceAt(i + 1)))
1380 bottom = sy + charHeight;
1385 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1386 .contains(av.getAlignment().getSequenceAt(i - 1)))
1399 // if start position is visible, draw vertical line to left of
1401 if (sx >= 0 && sx < visWidth)
1403 g.drawLine(sx, oldY, sx, sy);
1406 // if end position is visible, draw vertical line to right of
1408 if (sx + xwidth < visWidth)
1410 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1419 // don't let width extend beyond current block, or group extent
1421 if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1423 xwidth = (endRes - startRes + 1) * charWidth - sx;
1426 // draw horizontal line at top of group
1429 g.drawLine(sx, top, sx + xwidth, top);
1433 // draw horizontal line at bottom of group
1436 g.drawLine(sx, bottom, sx + xwidth, bottom);
1447 sy = verticalOffset + ((i - startSeq) * charHeight);
1448 if (sx >= 0 && sx < visWidth)
1450 g.drawLine(sx, oldY, sx, sy);
1453 if (sx + xwidth < visWidth)
1455 g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
1464 if (sx + xwidth > visWidth)
1468 else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
1470 xwidth = (endRes - startRes + 1) * charWidth;
1475 g.drawLine(sx, top, sx + xwidth, top);
1481 g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
1490 * Highlights search results in the visible region by rendering as white text
1491 * on a black background. Any previous highlighting is removed. Answers true
1492 * if any highlight was left on the visible alignment (so status bar should be
1493 * set to match), else false.
1495 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1496 * alignment had to be scrolled to show the highlighted region, then it should
1497 * be fully redrawn, otherwise a fast paint can be performed. This argument
1498 * could be removed if fast paint of scrolled wrapped alignment is coded in
1499 * future (JAL-2609).
1502 * @param noFastPaint
1505 public boolean highlightSearchResults(SearchResultsI results,
1506 boolean noFastPaint)
1512 boolean wrapped = av.getWrapAlignment();
1515 fastPaint = !noFastPaint;
1516 fastpainting = fastPaint;
1519 * to avoid redrawing the whole visible region, we instead
1520 * redraw just the minimal regions to remove previous highlights
1523 SearchResultsI previous = av.getSearchResults();
1524 av.setSearchResults(results);
1525 boolean redrawn = false;
1526 boolean drawn = false;
1529 redrawn = drawMappedPositionsWrapped(previous);
1530 drawn = drawMappedPositionsWrapped(results);
1535 redrawn = drawMappedPositions(previous);
1536 drawn = drawMappedPositions(results);
1541 * if highlights were either removed or added, repaint
1549 * return true only if highlights were added
1555 fastpainting = false;
1560 * Redraws the minimal rectangle in the visible region (if any) that includes
1561 * mapped positions of the given search results. Whether or not positions are
1562 * highlighted depends on the SearchResults set on the Viewport. This allows
1563 * this method to be called to either clear or set highlighting. Answers true
1564 * if any positions were drawn (in which case a repaint is still required),
1570 protected boolean drawMappedPositions(SearchResultsI results)
1572 if (results == null)
1578 * calculate the minimal rectangle to redraw that
1579 * includes both new and existing search results
1581 int firstSeq = Integer.MAX_VALUE;
1583 int firstCol = Integer.MAX_VALUE;
1585 boolean matchFound = false;
1587 ViewportRanges ranges = av.getRanges();
1588 int firstVisibleColumn = ranges.getStartRes();
1589 int lastVisibleColumn = ranges.getEndRes();
1590 AlignmentI alignment = av.getAlignment();
1591 if (av.hasHiddenColumns())
1593 firstVisibleColumn = alignment.getHiddenColumns()
1594 .adjustForHiddenColumns(firstVisibleColumn);
1595 lastVisibleColumn = alignment.getHiddenColumns()
1596 .adjustForHiddenColumns(lastVisibleColumn);
1599 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1600 .getEndSeq(); seqNo++)
1602 SequenceI seq = alignment.getSequenceAt(seqNo);
1604 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1606 if (visibleResults != null)
1608 for (int i = 0; i < visibleResults.length - 1; i += 2)
1610 int firstMatchedColumn = visibleResults[i];
1611 int lastMatchedColumn = visibleResults[i + 1];
1612 if (firstMatchedColumn <= lastVisibleColumn
1613 && lastMatchedColumn >= firstVisibleColumn)
1616 * found a search results match in the visible region -
1617 * remember the first and last sequence matched, and the first
1618 * and last visible columns in the matched positions
1621 firstSeq = Math.min(firstSeq, seqNo);
1622 lastSeq = Math.max(lastSeq, seqNo);
1623 firstMatchedColumn = Math.max(firstMatchedColumn,
1624 firstVisibleColumn);
1625 lastMatchedColumn = Math.min(lastMatchedColumn,
1627 firstCol = Math.min(firstCol, firstMatchedColumn);
1628 lastCol = Math.max(lastCol, lastMatchedColumn);
1636 if (av.hasHiddenColumns())
1638 firstCol = alignment.getHiddenColumns()
1639 .findColumnPosition(firstCol);
1640 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1642 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1643 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1644 gg.translate(transX, transY);
1645 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1646 gg.translate(-transX, -transY);
1653 public void propertyChange(PropertyChangeEvent evt)
1655 String eventName = evt.getPropertyName();
1657 if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
1665 if (eventName.equals(ViewportRanges.STARTRES))
1667 // Make sure we're not trying to draw a panel
1668 // larger than the visible window
1669 ViewportRanges vpRanges = av.getRanges();
1670 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1671 int range = vpRanges.getViewportWidth();
1672 if (scrollX > range)
1676 else if (scrollX < -range)
1682 // Both scrolling and resizing change viewport ranges: scrolling changes
1683 // both start and end points, but resize only changes end values.
1684 // Here we only want to fastpaint on a scroll, with resize using a normal
1685 // paint, so scroll events are identified as changes to the horizontal or
1686 // vertical start value.
1688 // scroll - startres and endres both change
1689 if (eventName.equals(ViewportRanges.STARTRES))
1691 if (av.getWrapAlignment())
1693 fastPaintWrapped(scrollX);
1697 fastPaint(scrollX, 0);
1700 else if (eventName.equals(ViewportRanges.STARTSEQ))
1703 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1708 * Does a minimal update of the image for a scroll movement. This method
1709 * handles scroll movements of up to one width of the wrapped alignment (one
1710 * click in the vertical scrollbar). Larger movements (for example after a
1711 * scroll to highlight a mapped position) trigger a full redraw instead.
1714 * number of positions scrolled (right if positive, left if negative)
1716 protected void fastPaintWrapped(int scrollX)
1718 ViewportRanges ranges = av.getRanges();
1720 if (Math.abs(scrollX) > ranges.getViewportWidth())
1723 * shift of more than one view width is
1724 * overcomplicated to handle in this method
1731 if (fastpainting || gg == null)
1737 fastpainting = true;
1741 calculateWrappedGeometry(getWidth(), getHeight());
1744 * relocate the regions of the alignment that are still visible
1746 shiftWrappedAlignment(-scrollX);
1749 * add new columns (sequence, annotation)
1750 * - at top left if scrollX < 0
1751 * - at right of last two widths if scrollX > 0
1755 int startRes = ranges.getStartRes();
1756 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1757 - scrollX - 1, getHeight());
1761 fastPaintWrappedAddRight(scrollX);
1765 * draw all scales (if shown) and hidden column markers
1767 drawWrappedDecorators(gg, ranges.getStartRes());
1772 fastpainting = false;
1777 * Draws the specified number of columns at the 'end' (bottom right) of a
1778 * wrapped alignment view, including sequences and annotations if shown, but
1779 * not scales. Also draws the same number of columns at the right hand end of
1780 * the second last width shown, if the last width is not full height (so
1781 * cannot simply be copied from the graphics image).
1785 protected void fastPaintWrappedAddRight(int columns)
1792 ViewportRanges ranges = av.getRanges();
1793 int viewportWidth = ranges.getViewportWidth();
1794 int charWidth = av.getCharWidth();
1797 * draw full height alignment in the second last row, last columns, if the
1798 * last row was not full height
1800 int visibleWidths = wrappedVisibleWidths;
1801 int canvasHeight = getHeight();
1802 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1804 if (lastWidthPartHeight)
1806 int widthsAbove = Math.max(0, visibleWidths - 2);
1807 int ypos = wrappedRepeatHeightPx * widthsAbove
1808 + wrappedSpaceAboveAlignment;
1809 int endRes = ranges.getEndRes();
1810 endRes += widthsAbove * viewportWidth;
1811 int startRes = endRes - columns;
1812 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1816 * white fill first to erase annotations
1818 gg.translate(xOffset, 0);
1819 gg.setColor(Color.white);
1820 gg.fillRect(labelWidthWest, ypos,
1821 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1822 gg.translate(-xOffset, 0);
1824 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1828 * draw newly visible columns in last wrapped width (none if we
1829 * have reached the end of the alignment)
1830 * y-offset for drawing last width is height of widths above,
1833 int widthsAbove = visibleWidths - 1;
1834 int ypos = wrappedRepeatHeightPx * widthsAbove
1835 + wrappedSpaceAboveAlignment;
1836 int endRes = ranges.getEndRes();
1837 endRes += widthsAbove * viewportWidth;
1838 int startRes = endRes - columns + 1;
1841 * white fill first to erase annotations
1843 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1845 gg.translate(xOffset, 0);
1846 gg.setColor(Color.white);
1847 int width = viewportWidth * charWidth - xOffset;
1848 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1849 gg.translate(-xOffset, 0);
1851 gg.setFont(av.getFont());
1852 gg.setColor(Color.black);
1854 if (startRes < ranges.getVisibleAlignmentWidth())
1856 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1860 * and finally, white fill any space below the visible alignment
1862 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1863 if (heightBelow > 0)
1865 gg.setColor(Color.white);
1866 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1871 * Shifts the visible alignment by the specified number of columns - left if
1872 * negative, right if positive. Copies and moves sequences and annotations (if
1873 * shown). Scales, hidden column markers and any newly visible columns must be
1878 protected void shiftWrappedAlignment(int positions)
1884 int charWidth = av.getCharWidth();
1886 int canvasHeight = getHeight();
1887 ViewportRanges ranges = av.getRanges();
1888 int viewportWidth = ranges.getViewportWidth();
1889 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1891 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1892 int xMax = ranges.getVisibleAlignmentWidth();
1897 * shift right (after scroll left)
1898 * for each wrapped width (starting with the last), copy (width-positions)
1899 * columns from the left margin to the right margin, and copy positions
1900 * columns from the right margin of the row above (if any) to the
1901 * left margin of the current row
1905 * get y-offset of last wrapped width, first row of sequences
1907 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1908 y += wrappedSpaceAboveAlignment;
1909 int copyFromLeftStart = labelWidthWest;
1910 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1914 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1915 positions * charWidth, 0);
1918 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1919 positions * charWidth, heightToCopy, -widthToCopy,
1920 wrappedRepeatHeightPx);
1923 y -= wrappedRepeatHeightPx;
1929 * shift left (after scroll right)
1930 * for each wrapped width (starting with the first), copy (width-positions)
1931 * columns from the right margin to the left margin, and copy positions
1932 * columns from the left margin of the row below (if any) to the
1933 * right margin of the current row
1935 int xpos = av.getRanges().getStartRes();
1936 int y = wrappedSpaceAboveAlignment;
1937 int copyFromRightStart = labelWidthWest - positions * charWidth;
1939 while (y < canvasHeight)
1941 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1942 positions * charWidth, 0);
1943 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1944 && (xpos + viewportWidth <= xMax))
1946 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1947 * charWidth, heightToCopy, widthToCopy,
1948 -wrappedRepeatHeightPx);
1951 y += wrappedRepeatHeightPx;
1952 xpos += viewportWidth;
1959 * Redraws any positions in the search results in the visible region of a
1960 * wrapped alignment. Any highlights are drawn depending on the search results
1961 * set on the Viewport, not the <code>results</code> argument. This allows
1962 * this method to be called either to clear highlights (passing the previous
1963 * search results), or to draw new highlights.
1968 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1970 if (results == null)
1974 int charHeight = av.getCharHeight();
1976 boolean matchFound = false;
1978 calculateWrappedGeometry(getWidth(), getHeight());
1979 int wrappedWidth = av.getWrappedWidth();
1980 int wrappedHeight = wrappedRepeatHeightPx;
1982 ViewportRanges ranges = av.getRanges();
1983 int canvasHeight = getHeight();
1984 int repeats = canvasHeight / wrappedHeight;
1985 if (canvasHeight / wrappedHeight > 0)
1990 int firstVisibleColumn = ranges.getStartRes();
1991 int lastVisibleColumn = ranges.getStartRes() + repeats
1992 * ranges.getViewportWidth() - 1;
1994 AlignmentI alignment = av.getAlignment();
1995 if (av.hasHiddenColumns())
1997 firstVisibleColumn = alignment.getHiddenColumns()
1998 .adjustForHiddenColumns(firstVisibleColumn);
1999 lastVisibleColumn = alignment.getHiddenColumns()
2000 .adjustForHiddenColumns(lastVisibleColumn);
2003 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
2005 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
2006 .getEndSeq(); seqNo++)
2008 SequenceI seq = alignment.getSequenceAt(seqNo);
2010 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
2012 if (visibleResults != null)
2014 for (int i = 0; i < visibleResults.length - 1; i += 2)
2016 int firstMatchedColumn = visibleResults[i];
2017 int lastMatchedColumn = visibleResults[i + 1];
2018 if (firstMatchedColumn <= lastVisibleColumn
2019 && lastMatchedColumn >= firstVisibleColumn)
2022 * found a search results match in the visible region
2024 firstMatchedColumn = Math.max(firstMatchedColumn,
2025 firstVisibleColumn);
2026 lastMatchedColumn = Math.min(lastMatchedColumn,
2030 * draw each mapped position separately (as contiguous positions may
2031 * wrap across lines)
2033 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
2035 int displayColumn = mappedPos;
2036 if (av.hasHiddenColumns())
2038 displayColumn = alignment.getHiddenColumns()
2039 .findColumnPosition(displayColumn);
2043 * transX: offset from left edge of canvas to residue position
2045 int transX = labelWidthWest
2046 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
2047 * av.getCharWidth();
2050 * transY: offset from top edge of canvas to residue position
2052 int transY = gapHeight;
2053 transY += (displayColumn - ranges.getStartRes())
2054 / wrappedWidth * wrappedHeight;
2055 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
2058 * yOffset is from graphics origin to start of visible region
2060 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
2061 if (transY < getHeight())
2064 gg.translate(transX, transY);
2065 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
2067 gg.translate(-transX, -transY);
2079 * Answers the width in pixels of the left scale labels (0 if not shown)
2083 int getLabelWidthWest()
2085 return labelWidthWest;