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.BasicStroke;
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.FontMetrics;
38 import java.awt.Graphics;
39 import java.awt.Graphics2D;
40 import java.awt.RenderingHints;
41 import java.awt.Shape;
42 import java.awt.image.BufferedImage;
43 import java.beans.PropertyChangeEvent;
44 import java.util.List;
46 import javax.swing.JComponent;
54 public class SeqCanvas extends JComponent implements ViewportListenerI
56 private static String ZEROS = "0000000000";
58 final FeatureRenderer fr;
60 final SequenceRenderer sr;
72 boolean fastPaint = false;
74 boolean fastpainting = false;
80 private AnnotationPanel annotations;
83 * measurements for drawing a wrapped alignment
85 int labelWidthWest; // label left width in pixels if shown
87 private int labelWidthEast; // label right width in pixels if shown
89 private int wrappedSpaceAboveAlignment; // gap between widths
91 private int wrappedRepeatHeightPx; // height in pixels of wrapped width
93 private int wrappedVisibleWidths; // number of wrapped widths displayed
96 * Creates a new SeqCanvas object.
101 public SeqCanvas(AlignmentPanel ap)
104 fr = new FeatureRenderer(ap);
105 sr = new SequenceRenderer(av);
106 setLayout(new BorderLayout());
107 PaintRefresher.Register(this, av.getSequenceSetId());
108 setBackground(Color.white);
110 av.getRanges().addPropertyChangeListener(this);
113 public SequenceRenderer getSequenceRenderer()
118 public FeatureRenderer getFeatureRenderer()
124 * Draws the scale above a region of a wrapped alignment, consisting of a
125 * column number every major interval (10 columns).
128 * the graphics context to draw on, positioned at the start (bottom
129 * left) of the line on which to draw any scale marks
131 * start alignment column (0..)
133 * end alignment column (0..)
135 * y offset to draw at
137 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
139 int charHeight = av.getCharHeight();
140 int charWidth = av.getCharWidth();
143 * white fill the scale space (for the fastPaint case)
145 g.setColor(Color.white);
146 g.fillRect(0, ypos - charHeight - charHeight / 2, getWidth(),
147 charHeight * 3 / 2 + 2);
148 g.setColor(Color.black);
150 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
152 for (ScaleMark mark : marks)
154 int mpos = mark.column; // (i - startx - 1)
159 String mstring = mark.text;
165 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
169 * draw a tick mark below the column number, centred on the column;
170 * height of tick mark is 4 pixels less than half a character
172 int xpos = (mpos * charWidth) + (charWidth / 2);
173 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
179 * Draw the scale to the left or right of a wrapped alignment
183 * first column of wrapped width (0.. excluding any hidden columns)
185 * last column of wrapped width (0.. excluding any hidden columns)
187 * vertical offset at which to begin the scale
189 * if true, scale is left of residues, if false, scale is right
191 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
194 int charHeight = av.getCharHeight();
195 int charWidth = av.getCharWidth();
199 if (av.hasHiddenColumns())
201 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
202 startx = hiddenColumns.adjustForHiddenColumns(startx);
203 endx = hiddenColumns.adjustForHiddenColumns(endx);
205 FontMetrics fm = getFontMetrics(av.getFont());
207 for (int i = 0; i < av.getAlignment().getHeight(); i++)
209 SequenceI seq = av.getAlignment().getSequenceAt(i);
212 * find sequence position of first non-gapped position -
213 * to the right if scale left, to the left if scale right
215 int index = left ? startx : endx;
217 while (index >= startx && index <= endx)
219 if (!Comparison.isGap(seq.getCharAt(index)))
221 value = seq.findPosition(index);
235 * white fill the space for the scale
237 g.setColor(Color.white);
238 int y = (ypos + (i * charHeight)) - (charHeight / 5);
239 y -= charHeight; // fillRect: origin is top left of rectangle
240 int xpos = left ? 0 : labelWidthWest + charWidth
241 * av.getRanges().getViewportWidth();
242 g.fillRect(xpos, y, left ? labelWidthWest : labelWidthEast,
244 y += charHeight; // drawString: origin is bottom left of text
250 * draw scale value, right justified, with half a character width
251 * separation from the sequence data
253 String valueAsString = String.valueOf(value);
254 int justify = fm.stringWidth(valueAsString) + charWidth;
255 xpos = left ? labelWidthWest - justify + charWidth / 2
256 : getWidth() - justify - charWidth / 2;
258 g.setColor(Color.black);
259 g.drawString(valueAsString, xpos, y);
265 * Does a fast paint of an alignment in response to a scroll. Most of the
266 * visible region is simply copied and shifted, and then any newly visible
267 * columns or rows are drawn. The scroll may be horizontal or vertical, but
268 * not both at once. Scrolling may be the result of
270 * <li>dragging a scroll bar</li>
271 * <li>clicking in the scroll bar</li>
272 * <li>scrolling by trackpad, middle mouse button, or other device</li>
273 * <li>by moving the box in the Overview window</li>
274 * <li>programmatically to make a highlighted position visible</li>
278 * columns to shift right (positive) or left (negative)
280 * rows to shift down (positive) or up (negative)
282 public void fastPaint(int horizontal, int vertical)
284 if (fastpainting || gg == null)
293 int charHeight = av.getCharHeight();
294 int charWidth = av.getCharWidth();
296 ViewportRanges ranges = av.getRanges();
297 int startRes = ranges.getStartRes();
298 int endRes = ranges.getEndRes();
299 int startSeq = ranges.getStartSeq();
300 int endSeq = ranges.getEndSeq();
304 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
305 imgHeight, -horizontal * charWidth, -vertical * charHeight);
307 if (horizontal > 0) // scrollbar pulled right, image to the left
309 transX = (endRes - startRes - horizontal) * charWidth;
310 startRes = endRes - horizontal;
312 else if (horizontal < 0)
314 endRes = startRes - horizontal;
316 else if (vertical > 0) // scroll down
318 startSeq = endSeq - vertical;
320 if (startSeq < ranges.getStartSeq())
321 { // ie scrolling too fast, more than a page at a time
322 startSeq = ranges.getStartSeq();
326 transY = imgHeight - ((vertical + 1) * charHeight);
329 else if (vertical < 0)
331 endSeq = startSeq - vertical;
333 if (endSeq > ranges.getEndSeq())
335 endSeq = ranges.getEndSeq();
339 gg.translate(transX, transY);
340 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
341 gg.translate(-transX, -transY);
346 fastpainting = false;
351 public void paintComponent(Graphics g)
353 int charHeight = av.getCharHeight();
354 int charWidth = av.getCharWidth();
355 BufferedImage lcimg = img; // take reference since other threads may null
356 // img and call later.
357 super.paintComponent(g);
359 if (lcimg != null && (fastPaint
360 || (getVisibleRect().width != g.getClipBounds().width)
361 || (getVisibleRect().height != g.getClipBounds().height)))
363 g.drawImage(lcimg, 0, 0, this);
368 // this draws the whole of the alignment
369 imgWidth = getWidth();
370 imgHeight = getHeight();
372 imgWidth -= (imgWidth % charWidth);
373 imgHeight -= (imgHeight % charHeight);
375 if ((imgWidth < 1) || (imgHeight < 1))
380 if (lcimg == null || imgWidth != lcimg.getWidth()
381 || imgHeight != lcimg.getHeight())
385 lcimg = img = new BufferedImage(imgWidth, imgHeight,
386 BufferedImage.TYPE_INT_RGB);
387 gg = (Graphics2D) img.getGraphics();
388 gg.setFont(av.getFont());
389 } catch (OutOfMemoryError er)
392 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
393 new OOMWarning("Creating alignment image for display", er);
401 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
402 RenderingHints.VALUE_ANTIALIAS_ON);
405 gg.setColor(Color.white);
406 gg.fillRect(0, 0, imgWidth, imgHeight);
408 ViewportRanges ranges = av.getRanges();
409 if (av.getWrapAlignment())
411 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
415 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
416 ranges.getStartSeq(), ranges.getEndSeq(), 0);
419 g.drawImage(lcimg, 0, 0, this);
424 * Returns the visible width of the canvas in residues, after allowing for
425 * East or West scales (if shown)
428 * the width in pixels (possibly including scales)
432 public int getWrappedCanvasWidth(int canvasWidth)
434 int charWidth = av.getCharWidth();
436 FontMetrics fm = getFontMetrics(av.getFont());
441 if (av.getScaleRightWrapped())
443 labelWidthEast = getLabelWidth(fm);
446 if (av.getScaleLeftWrapped())
448 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
452 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
456 * Returns a pixel width suitable for showing the largest sequence coordinate
457 * (end position) in the alignment. Returns 2 plus the number of decimal
458 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
463 protected int getLabelWidth(FontMetrics fm)
466 * find the biggest sequence end position we need to show
467 * (note this is not necessarily the sequence length)
470 AlignmentI alignment = av.getAlignment();
471 for (int i = 0; i < alignment.getHeight(); i++)
473 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
477 for (int i = maxWidth; i > 0; i /= 10)
482 return fm.stringWidth(ZEROS.substring(0, length));
486 * Draws as many widths of a wrapped alignment as can fit in the visible
491 * available width in pixels
492 * @param canvasHeight
493 * available height in pixels
495 * the first column (0...) of the alignment to draw
497 public void drawWrappedPanel(Graphics g, int canvasWidth,
498 int canvasHeight, final int startColumn)
500 int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
503 av.setWrappedWidth(wrappedWidthInResidues);
505 ViewportRanges ranges = av.getRanges();
506 ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
509 * draw one width at a time (including any scales or annotation shown),
510 * until we have run out of either alignment or vertical space available
512 int ypos = wrappedSpaceAboveAlignment;
513 int maxWidth = ranges.getVisibleAlignmentWidth();
515 int start = startColumn;
516 int currentWidth = 0;
517 while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
520 .min(maxWidth, start + wrappedWidthInResidues - 1);
521 drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
522 ypos += wrappedRepeatHeightPx;
523 start += wrappedWidthInResidues;
527 drawWrappedDecorators(g, startColumn);
531 * Calculates and saves values needed when rendering a wrapped alignment.
532 * These depend on many factors, including
534 * <li>canvas width and height</li>
535 * <li>number of visible sequences, and height of annotations if shown</li>
536 * <li>font and character width</li>
537 * <li>whether scales are shown left, right or above the alignment</li>
541 * @param canvasHeight
542 * @return the number of residue columns in each width
544 protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
546 int charHeight = av.getCharHeight();
547 int charWidth = av.getCharWidth();
550 * width of labels in pixels left and right (if shown)
553 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
555 FontMetrics fm = getFontMetrics(av.getFont());
556 labelWidth = getLabelWidth(fm);
558 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
559 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
562 * vertical space in pixels between wrapped widths of alignment
563 * - one character height, or two if scale above is drawn
565 wrappedSpaceAboveAlignment = charHeight
566 * (av.getScaleAboveWrapped() ? 2 : 1);
569 * height in pixels of the wrapped widths
571 wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
573 wrappedRepeatHeightPx += av.getRanges().getViewportHeight() * charHeight;
574 // add annotations panel height if shown
575 wrappedRepeatHeightPx += getAnnotationHeight();
578 * number of residue columns we can show in each row;
579 * this is just canvas width less scale left and right (if shown),
580 * as a whole multiple of character widths
582 int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
586 * number of visible widths (the last one may be part height),
587 * ensuring a part height includes at least one sequence
589 ViewportRanges ranges = av.getRanges();
590 int xMax = ranges.getVisibleAlignmentWidth();
591 wrappedVisibleWidths = canvasHeight / wrappedRepeatHeightPx;
592 int remainder = canvasHeight % wrappedRepeatHeightPx;
593 if (remainder >= (wrappedSpaceAboveAlignment + charHeight))
595 wrappedVisibleWidths++;
599 * limit visibleWidths to not exceed width of alignment
601 int maxWidths = (xMax - ranges.getStartRes()) / wrappedWidthInResidues;
602 if (xMax % wrappedWidthInResidues > 0)
606 wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
608 return wrappedWidthInResidues;
612 * Draws one width of a wrapped alignment, including sequences and
613 * annnotations, if shown, but not scales or hidden column markers
619 * @param canvasHeight
621 protected void drawWrappedWidth(Graphics g, int ypos,
622 int startColumn, int endColumn, int canvasHeight)
624 int charHeight = av.getCharHeight();
625 int charWidth = av.getCharWidth();
627 ViewportRanges ranges = av.getRanges();
628 int viewportWidth = ranges.getViewportWidth();
630 int endx = Math.min(startColumn + viewportWidth - 1, endColumn);
633 * move right before drawing by the width of the scale left (if any)
634 * plus column offset from left margin (usually zero, but may be non-zero
635 * when fast painting is drawing just a few columns)
637 int xOffset = labelWidthWest
638 + ((startColumn - ranges.getStartRes()) % viewportWidth)
640 g.translate(xOffset, 0);
642 // When printing we have an extra clipped region,
643 // the Printable page which we need to account for here
644 Shape clip = g.getClip();
648 g.setClip(0, 0, viewportWidth * charWidth, canvasHeight);
652 g.setClip(0, (int) clip.getBounds().getY(),
653 viewportWidth * charWidth, (int) clip.getBounds().getHeight());
657 * white fill the region to be drawn (so incremental fast paint doesn't
658 * scribble over an existing image)
660 gg.setColor(Color.white);
661 gg.fillRect(0, ypos, (endx - startColumn + 1) * charWidth,
662 wrappedRepeatHeightPx);
664 drawPanel(g, startColumn, endx, 0, av.getAlignment().getHeight() - 1,
667 int cHeight = av.getAlignment().getHeight() * charHeight;
669 if (av.isShowAnnotation())
671 g.translate(0, cHeight + ypos + 3);
672 if (annotations == null)
674 annotations = new AnnotationPanel(av);
677 annotations.renderer.drawComponent(annotations, av, g, -1,
678 startColumn, endx + 1);
679 g.translate(0, -cHeight - ypos - 3);
682 g.translate(-xOffset, 0);
686 * Draws scales left, right and above (if shown), and any hidden column
687 * markers, on all widths of the wrapped alignment
692 protected void drawWrappedDecorators(Graphics g, int startColumn)
694 int charWidth = av.getCharWidth();
696 g.setFont(av.getFont());
697 g.setColor(Color.black);
699 int ypos = wrappedSpaceAboveAlignment;
700 ViewportRanges ranges = av.getRanges();
701 int viewportWidth = ranges.getViewportWidth();
702 int maxWidth = ranges.getVisibleAlignmentWidth();
704 while (widthsDrawn < wrappedVisibleWidths)
706 int endColumn = Math.min(maxWidth, startColumn + viewportWidth - 1);
708 if (av.getScaleLeftWrapped())
710 drawVerticalScale(g, startColumn, endColumn - 1, ypos, true);
713 if (av.getScaleRightWrapped())
715 drawVerticalScale(g, startColumn, endColumn, ypos, false);
719 * white fill region of scale above and hidden column markers
720 * (to support incremental fast paint of image)
722 g.setColor(Color.white);
723 g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
724 * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
725 g.setColor(Color.black);
727 g.translate(labelWidthWest, 0);
729 if (av.getScaleAboveWrapped())
731 drawNorthScale(g, startColumn, endColumn, ypos);
734 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
736 drawHiddenColumnMarkers(g, ypos, startColumn, endColumn);
739 g.translate(-labelWidthWest, 0);
741 ypos += wrappedRepeatHeightPx;
742 startColumn += viewportWidth;
753 protected void drawHiddenColumnMarkers(Graphics g, int ypos,
754 int startColumn, int endColumn)
756 int charHeight = av.getCharHeight();
757 int charWidth = av.getCharWidth();
759 g.setColor(Color.blue);
760 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
761 List<Integer> positions = hidden.findHiddenRegionPositions();
762 for (int pos : positions)
764 int res = pos - startColumn;
766 if (res < 0 || res > endColumn - startColumn)
772 * draw a downward-pointing triangle at the hidden columns location
773 * (before the following visible column)
775 int xMiddle = res * charWidth;
776 int[] xPoints = new int[] { xMiddle - charHeight / 4,
777 xMiddle + charHeight / 4, xMiddle };
778 int yTop = ypos - (charHeight / 2);
779 int[] yPoints = new int[] { yTop, yTop, yTop + 8 };
780 gg.fillPolygon(xPoints, yPoints, 3);
784 int getAnnotationHeight()
786 if (!av.isShowAnnotation())
791 if (annotations == null)
793 annotations = new AnnotationPanel(av);
796 return annotations.adjustPanelHeight();
800 * Draws the visible region of the alignment on the graphics context. If there
801 * are hidden column markers in the visible region, then each sub-region
802 * between the markers is drawn separately, followed by the hidden column
806 * the graphics context, positioned at the first residue to be drawn
808 * offset of the first column to draw (0..)
810 * offset of the last column to draw (0..)
812 * offset of the first sequence to draw (0..)
814 * offset of the last sequence to draw (0..)
816 * vertical offset at which to draw (for wrapped alignments)
818 public void drawPanel(Graphics g1, final int startRes, final int endRes,
819 final int startSeq, final int endSeq, final int yOffset)
821 int charHeight = av.getCharHeight();
822 int charWidth = av.getCharWidth();
824 if (!av.hasHiddenColumns())
826 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
831 final int screenYMax = endRes - startRes;
832 int blockStart = startRes;
833 int blockEnd = endRes;
835 for (int[] region : av.getAlignment().getHiddenColumns()
836 .getHiddenColumnsCopy())
838 int hideStart = region[0];
839 int hideEnd = region[1];
841 if (hideStart <= blockStart)
843 blockStart += (hideEnd - hideStart) + 1;
848 * draw up to just before the next hidden region, or the end of
849 * the visible region, whichever comes first
851 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
854 g1.translate(screenY * charWidth, 0);
856 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
859 * draw the downline of the hidden column marker (ScalePanel draws the
860 * triangle on top) if we reached it
862 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
864 g1.setColor(Color.blue);
866 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
867 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
868 (endSeq - startSeq + 1) * charHeight + yOffset);
871 g1.translate(-screenY * charWidth, 0);
872 screenY += blockEnd - blockStart + 1;
873 blockStart = hideEnd + 1;
875 if (screenY > screenYMax)
877 // already rendered last block
882 if (screenY <= screenYMax)
884 // remaining visible region to render
885 blockEnd = blockStart + screenYMax - screenY;
886 g1.translate(screenY * charWidth, 0);
887 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
889 g1.translate(-screenY * charWidth, 0);
896 * Draws a region of the visible alignment
900 * offset of the first column in the visible region (0..)
902 * offset of the last column in the visible region (0..)
904 * offset of the first sequence in the visible region (0..)
906 * offset of the last sequence in the visible region (0..)
908 * vertical offset at which to draw (for wrapped alignments)
910 private void draw(Graphics g, int startRes, int endRes, int startSeq,
911 int endSeq, int offset)
913 int charHeight = av.getCharHeight();
914 int charWidth = av.getCharWidth();
916 g.setFont(av.getFont());
917 sr.prepare(g, av.isRenderGaps());
921 // / First draw the sequences
922 // ///////////////////////////
923 for (int i = startSeq; i <= endSeq; i++)
925 nextSeq = av.getAlignment().getSequenceAt(i);
928 // occasionally, a race condition occurs such that the alignment row is
932 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
933 startRes, endRes, offset + ((i - startSeq) * charHeight));
935 if (av.isShowSequenceFeatures())
937 fr.drawSequence(g, nextSeq, startRes, endRes,
938 offset + ((i - startSeq) * charHeight), false);
942 * highlight search Results once sequence has been drawn
944 if (av.hasSearchResults())
946 SearchResultsI searchResults = av.getSearchResults();
947 int[] visibleResults = searchResults.getResults(nextSeq,
949 if (visibleResults != null)
951 for (int r = 0; r < visibleResults.length; r += 2)
953 sr.drawHighlightedText(nextSeq, visibleResults[r],
954 visibleResults[r + 1],
955 (visibleResults[r] - startRes) * charWidth,
956 offset + ((i - startSeq) * charHeight));
961 if (av.cursorMode && cursorY == i && cursorX >= startRes
962 && cursorX <= endRes)
964 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
965 offset + ((i - startSeq) * charHeight));
969 if (av.getSelectionGroup() != null
970 || av.getAlignment().getGroups().size() > 0)
972 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
977 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
978 int startSeq, int endSeq, int offset)
980 int charHeight = av.getCharHeight();
981 int charWidth = av.getCharWidth();
983 Graphics2D g = (Graphics2D) g1;
985 // ///////////////////////////////////
986 // Now outline any areas if necessary
987 // ///////////////////////////////////
988 SequenceGroup group = av.getSelectionGroup();
994 int visWidth = (endRes - startRes + 1) * charWidth;
996 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
998 group = av.getAlignment().getGroups().get(0);
1008 boolean inGroup = false;
1012 for (i = startSeq; i <= endSeq; i++)
1014 sx = (group.getStartRes() - startRes) * charWidth;
1015 sy = offset + ((i - startSeq) * charHeight);
1016 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
1019 if (sx + ex < 0 || sx > visWidth)
1024 if ((sx <= (endRes - startRes) * charWidth)
1025 && group.getSequences(null)
1026 .contains(av.getAlignment().getSequenceAt(i)))
1028 if ((bottom == -1) && !group.getSequences(null)
1029 .contains(av.getAlignment().getSequenceAt(i + 1)))
1031 bottom = sy + charHeight;
1036 if (((top == -1) && (i == 0)) || !group.getSequences(null)
1037 .contains(av.getAlignment().getSequenceAt(i - 1)))
1045 if (group == av.getSelectionGroup())
1047 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
1048 BasicStroke.JOIN_ROUND, 3f, new float[]
1050 g.setColor(Color.RED);
1054 g.setStroke(new BasicStroke());
1055 g.setColor(group.getOutlineColour());
1063 if (sx >= 0 && sx < visWidth)
1065 g.drawLine(sx, oldY, sx, sy);
1068 if (sx + ex < visWidth)
1070 g.drawLine(sx + ex, oldY, sx + ex, sy);
1079 if (sx + ex > visWidth)
1084 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1086 ex = (endRes - startRes + 1) * charWidth;
1091 g.drawLine(sx, top, sx + ex, top);
1097 g.drawLine(sx, bottom, sx + ex, bottom);
1108 sy = offset + ((i - startSeq) * charHeight);
1109 if (sx >= 0 && sx < visWidth)
1111 g.drawLine(sx, oldY, sx, sy);
1114 if (sx + ex < visWidth)
1116 g.drawLine(sx + ex, oldY, sx + ex, sy);
1125 if (sx + ex > visWidth)
1129 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
1131 ex = (endRes - startRes + 1) * charWidth;
1136 g.drawLine(sx, top, sx + ex, top);
1142 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1151 g.setStroke(new BasicStroke());
1153 if (groupIndex >= av.getAlignment().getGroups().size())
1158 group = av.getAlignment().getGroups().get(groupIndex);
1160 } while (groupIndex < av.getAlignment().getGroups().size());
1167 * Highlights search results in the visible region by rendering as white text
1168 * on a black background. Any previous highlighting is removed. Answers true
1169 * if any highlight was left on the visible alignment (so status bar should be
1170 * set to match), else false.
1172 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1173 * alignment had to be scrolled to show the highlighted region, then it should
1174 * be fully redrawn, otherwise a fast paint can be performed. This argument
1175 * could be removed if fast paint of scrolled wrapped alignment is coded in
1176 * future (JAL-2609).
1179 * @param noFastPaint
1182 public boolean highlightSearchResults(SearchResultsI results,
1183 boolean noFastPaint)
1189 boolean wrapped = av.getWrapAlignment();
1192 fastPaint = !noFastPaint;
1193 fastpainting = fastPaint;
1196 * to avoid redrawing the whole visible region, we instead
1197 * redraw just the minimal regions to remove previous highlights
1200 SearchResultsI previous = av.getSearchResults();
1201 av.setSearchResults(results);
1202 boolean redrawn = false;
1203 boolean drawn = false;
1206 redrawn = drawMappedPositionsWrapped(previous);
1207 drawn = drawMappedPositionsWrapped(results);
1212 redrawn = drawMappedPositions(previous);
1213 drawn = drawMappedPositions(results);
1218 * if highlights were either removed or added, repaint
1226 * return true only if highlights were added
1232 fastpainting = false;
1237 * Redraws the minimal rectangle in the visible region (if any) that includes
1238 * mapped positions of the given search results. Whether or not positions are
1239 * highlighted depends on the SearchResults set on the Viewport. This allows
1240 * this method to be called to either clear or set highlighting. Answers true
1241 * if any positions were drawn (in which case a repaint is still required),
1247 protected boolean drawMappedPositions(SearchResultsI results)
1249 if (results == null)
1255 * calculate the minimal rectangle to redraw that
1256 * includes both new and existing search results
1258 int firstSeq = Integer.MAX_VALUE;
1260 int firstCol = Integer.MAX_VALUE;
1262 boolean matchFound = false;
1264 ViewportRanges ranges = av.getRanges();
1265 int firstVisibleColumn = ranges.getStartRes();
1266 int lastVisibleColumn = ranges.getEndRes();
1267 AlignmentI alignment = av.getAlignment();
1268 if (av.hasHiddenColumns())
1270 firstVisibleColumn = alignment.getHiddenColumns()
1271 .adjustForHiddenColumns(firstVisibleColumn);
1272 lastVisibleColumn = alignment.getHiddenColumns()
1273 .adjustForHiddenColumns(lastVisibleColumn);
1276 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1277 .getEndSeq(); seqNo++)
1279 SequenceI seq = alignment.getSequenceAt(seqNo);
1281 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1283 if (visibleResults != null)
1285 for (int i = 0; i < visibleResults.length - 1; i += 2)
1287 int firstMatchedColumn = visibleResults[i];
1288 int lastMatchedColumn = visibleResults[i + 1];
1289 if (firstMatchedColumn <= lastVisibleColumn
1290 && lastMatchedColumn >= firstVisibleColumn)
1293 * found a search results match in the visible region -
1294 * remember the first and last sequence matched, and the first
1295 * and last visible columns in the matched positions
1298 firstSeq = Math.min(firstSeq, seqNo);
1299 lastSeq = Math.max(lastSeq, seqNo);
1300 firstMatchedColumn = Math.max(firstMatchedColumn,
1301 firstVisibleColumn);
1302 lastMatchedColumn = Math.min(lastMatchedColumn,
1304 firstCol = Math.min(firstCol, firstMatchedColumn);
1305 lastCol = Math.max(lastCol, lastMatchedColumn);
1313 if (av.hasHiddenColumns())
1315 firstCol = alignment.getHiddenColumns()
1316 .findColumnPosition(firstCol);
1317 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1319 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1320 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1321 gg.translate(transX, transY);
1322 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1323 gg.translate(-transX, -transY);
1330 public void propertyChange(PropertyChangeEvent evt)
1332 String eventName = evt.getPropertyName();
1335 if (eventName.equals(ViewportRanges.STARTRES))
1337 // Make sure we're not trying to draw a panel
1338 // larger than the visible window
1339 ViewportRanges vpRanges = av.getRanges();
1340 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1341 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1342 if (scrollX > range)
1346 else if (scrollX < -range)
1352 // Both scrolling and resizing change viewport ranges: scrolling changes
1353 // both start and end points, but resize only changes end values.
1354 // Here we only want to fastpaint on a scroll, with resize using a normal
1355 // paint, so scroll events are identified as changes to the horizontal or
1356 // vertical start value.
1357 if (eventName.equals(ViewportRanges.STARTRES))
1359 // scroll - startres and endres both change
1360 if (av.getWrapAlignment())
1362 fastPaintWrapped(scrollX);
1366 fastPaint(scrollX, 0);
1369 else if (eventName.equals(ViewportRanges.STARTSEQ))
1371 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1376 * Does a minimal update of the image for a scroll movement. This method
1377 * handles scroll movements of up to one width of the wrapped alignment (one
1378 * click in the vertical scrollbar). Larger movements (for example after a
1379 * scroll to highlight a mapped position) trigger a full redraw instead.
1382 * number of positions scrolled (right if positive, left if negative)
1384 protected void fastPaintWrapped(int scrollX)
1386 ViewportRanges ranges = av.getRanges();
1388 if (Math.abs(scrollX) > ranges.getViewportWidth())
1391 * shift of more than one view width is
1392 * overcomplicated to handle in this method
1399 if (fastpainting || gg == null)
1405 fastpainting = true;
1409 calculateWrappedGeometry(getWidth(), getHeight());
1412 * relocate the regions of the alignment that are still visible
1414 shiftWrappedAlignment(-scrollX);
1417 * add new columns (sequence, annotation)
1418 * - at top left if scrollX < 0
1419 * - at right of last two widths if scrollX > 0
1423 int startRes = ranges.getStartRes();
1424 drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
1425 - scrollX - 1, getHeight());
1429 fastPaintWrappedAddRight(scrollX);
1433 * draw all scales (if shown) and hidden column markers
1435 drawWrappedDecorators(gg, ranges.getStartRes());
1440 fastpainting = false;
1445 * Draws the specified number of columns at the 'end' (bottom right) of a
1446 * wrapped alignment view, including sequences and annotations if shown, but
1447 * not scales. Also draws the same number of columns at the right hand end of
1448 * the second last width shown, if the last width is not full height (so
1449 * cannot simply be copied from the graphics image).
1453 protected void fastPaintWrappedAddRight(int columns)
1460 ViewportRanges ranges = av.getRanges();
1461 int viewportWidth = ranges.getViewportWidth();
1462 int charWidth = av.getCharWidth();
1465 * draw full height alignment in the second last row, last columns, if the
1466 * last row was not full height
1468 int visibleWidths = wrappedVisibleWidths;
1469 int canvasHeight = getHeight();
1470 boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
1472 if (lastWidthPartHeight)
1474 int widthsAbove = Math.max(0, visibleWidths - 2);
1475 int ypos = wrappedRepeatHeightPx * widthsAbove
1476 + wrappedSpaceAboveAlignment;
1477 int endRes = ranges.getEndRes();
1478 endRes += widthsAbove * viewportWidth;
1479 int startRes = endRes - columns;
1480 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1484 * white fill first to erase annotations
1486 gg.translate(xOffset, 0);
1487 gg.setColor(Color.white);
1488 gg.fillRect(labelWidthWest, ypos,
1489 (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
1490 gg.translate(-xOffset, 0);
1492 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1496 * draw newly visible columns in last wrapped width (none if we
1497 * have reached the end of the alignment)
1498 * y-offset for drawing last width is height of widths above,
1501 int widthsAbove = visibleWidths - 1;
1502 int ypos = wrappedRepeatHeightPx * widthsAbove
1503 + wrappedSpaceAboveAlignment;
1504 int endRes = ranges.getEndRes();
1505 endRes += widthsAbove * viewportWidth;
1506 int startRes = endRes - columns + 1;
1509 * white fill first to erase annotations
1511 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1513 gg.translate(xOffset, 0);
1514 gg.setColor(Color.white);
1515 int width = viewportWidth * charWidth - xOffset;
1516 gg.fillRect(labelWidthWest, ypos, width, wrappedRepeatHeightPx);
1517 gg.translate(-xOffset, 0);
1519 gg.setFont(av.getFont());
1520 gg.setColor(Color.black);
1522 if (startRes < ranges.getVisibleAlignmentWidth())
1524 drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
1528 * and finally, white fill any space below the visible alignment
1530 int heightBelow = canvasHeight - visibleWidths * wrappedRepeatHeightPx;
1531 if (heightBelow > 0)
1533 gg.setColor(Color.white);
1534 gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
1539 * Shifts the visible alignment by the specified number of columns - left if
1540 * negative, right if positive. Copies and moves sequences and annotations (if
1541 * shown). Scales, hidden column markers and any newly visible columns must be
1546 protected void shiftWrappedAlignment(int positions)
1552 int charWidth = av.getCharWidth();
1554 int canvasHeight = getHeight();
1555 ViewportRanges ranges = av.getRanges();
1556 int viewportWidth = ranges.getViewportWidth();
1557 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1559 int heightToCopy = wrappedRepeatHeightPx - wrappedSpaceAboveAlignment;
1560 int xMax = ranges.getVisibleAlignmentWidth();
1565 * shift right (after scroll left)
1566 * for each wrapped width (starting with the last), copy (width-positions)
1567 * columns from the left margin to the right margin, and copy positions
1568 * columns from the right margin of the row above (if any) to the
1569 * left margin of the current row
1573 * get y-offset of last wrapped width, first row of sequences
1575 int y = canvasHeight / wrappedRepeatHeightPx * wrappedRepeatHeightPx;
1576 y += wrappedSpaceAboveAlignment;
1577 int copyFromLeftStart = labelWidthWest;
1578 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1582 gg.copyArea(copyFromLeftStart, y, widthToCopy, heightToCopy,
1583 positions * charWidth, 0);
1586 gg.copyArea(copyFromRightStart, y - wrappedRepeatHeightPx,
1587 positions * charWidth, heightToCopy, -widthToCopy,
1588 wrappedRepeatHeightPx);
1591 y -= wrappedRepeatHeightPx;
1597 * shift left (after scroll right)
1598 * for each wrapped width (starting with the first), copy (width-positions)
1599 * columns from the right margin to the left margin, and copy positions
1600 * columns from the left margin of the row below (if any) to the
1601 * right margin of the current row
1603 int xpos = av.getRanges().getStartRes();
1604 int y = wrappedSpaceAboveAlignment;
1605 int copyFromRightStart = labelWidthWest - positions * charWidth;
1607 while (y < canvasHeight)
1609 gg.copyArea(copyFromRightStart, y, widthToCopy, heightToCopy,
1610 positions * charWidth, 0);
1611 if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
1612 && (xpos + viewportWidth <= xMax))
1614 gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
1615 * charWidth, heightToCopy, widthToCopy,
1616 -wrappedRepeatHeightPx);
1619 y += wrappedRepeatHeightPx;
1620 xpos += viewportWidth;
1626 * Redraws any positions in the search results in the visible region of a
1627 * wrapped alignment. Any highlights are drawn depending on the search results
1628 * set on the Viewport, not the <code>results</code> argument. This allows
1629 * this method to be called either to clear highlights (passing the previous
1630 * search results), or to draw new highlights.
1635 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1637 if (results == null)
1641 int charHeight = av.getCharHeight();
1643 boolean matchFound = false;
1645 calculateWrappedGeometry(getWidth(), getHeight());
1646 int wrappedWidth = av.getWrappedWidth();
1647 int wrappedHeight = wrappedRepeatHeightPx;
1649 ViewportRanges ranges = av.getRanges();
1650 int canvasHeight = getHeight();
1651 int repeats = canvasHeight / wrappedHeight;
1652 if (canvasHeight / wrappedHeight > 0)
1657 int firstVisibleColumn = ranges.getStartRes();
1658 int lastVisibleColumn = ranges.getStartRes() + repeats
1659 * ranges.getViewportWidth() - 1;
1661 AlignmentI alignment = av.getAlignment();
1662 if (av.hasHiddenColumns())
1664 firstVisibleColumn = alignment.getHiddenColumns()
1665 .adjustForHiddenColumns(firstVisibleColumn);
1666 lastVisibleColumn = alignment.getHiddenColumns()
1667 .adjustForHiddenColumns(lastVisibleColumn);
1670 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1672 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1673 .getEndSeq(); seqNo++)
1675 SequenceI seq = alignment.getSequenceAt(seqNo);
1677 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1679 if (visibleResults != null)
1681 for (int i = 0; i < visibleResults.length - 1; i += 2)
1683 int firstMatchedColumn = visibleResults[i];
1684 int lastMatchedColumn = visibleResults[i + 1];
1685 if (firstMatchedColumn <= lastVisibleColumn
1686 && lastMatchedColumn >= firstVisibleColumn)
1689 * found a search results match in the visible region
1691 firstMatchedColumn = Math.max(firstMatchedColumn,
1692 firstVisibleColumn);
1693 lastMatchedColumn = Math.min(lastMatchedColumn,
1697 * draw each mapped position separately (as contiguous positions may
1698 * wrap across lines)
1700 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1702 int displayColumn = mappedPos;
1703 if (av.hasHiddenColumns())
1705 displayColumn = alignment.getHiddenColumns()
1706 .findColumnPosition(displayColumn);
1710 * transX: offset from left edge of canvas to residue position
1712 int transX = labelWidthWest
1713 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1714 * av.getCharWidth();
1717 * transY: offset from top edge of canvas to residue position
1719 int transY = gapHeight;
1720 transY += (displayColumn - ranges.getStartRes())
1721 / wrappedWidth * wrappedHeight;
1722 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1725 * yOffset is from graphics origin to start of visible region
1727 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1728 if (transY < getHeight())
1731 gg.translate(transX, transY);
1732 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1734 gg.translate(-transX, -transY);