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;
85 * Creates a new SeqCanvas object.
90 public SeqCanvas(AlignmentPanel ap)
94 fr = new FeatureRenderer(ap);
95 sr = new SequenceRenderer(av);
96 setLayout(new BorderLayout());
97 PaintRefresher.Register(this, av.getSequenceSetId());
98 setBackground(Color.white);
100 av.getRanges().addPropertyChangeListener(this);
103 public SequenceRenderer getSequenceRenderer()
108 public FeatureRenderer getFeatureRenderer()
113 int charHeight = 0, charWidth = 0;
115 private void updateViewport()
117 charHeight = av.getCharHeight();
118 charWidth = av.getCharWidth();
122 * Draws the scale above a region of a wrapped alignment, consisting of a
123 * column number every major interval (10 columns).
126 * the graphics context to draw on, positioned at the start (bottom
127 * left) of the line on which to draw any scale marks
129 * start alignment column (0..)
131 * end alignment column (0..)
133 * y offset to draw at
135 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
138 List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
140 for (ScaleMark mark : marks)
142 int mpos = mark.column; // (i - startx - 1)
147 String mstring = mark.text;
153 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
157 * draw a tick mark below the column number, centred on the column;
158 * height of tick mark is 4 pixels less than half a character
160 int xpos = (mpos * charWidth) + (charWidth / 2);
161 g.drawLine(xpos, (ypos + 2) - (charHeight / 2), xpos, ypos - 2);
167 * Draw the scale to the left or right of a wrapped alignment
171 * first column of wrapped width (0.. excluding any hidden columns)
173 * last column of wrapped width (0.. excluding any hidden columns)
175 * vertical offset at which to begin the scale
177 * if true, scale is left of residues, if false, scale is right
179 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
184 if (av.hasHiddenColumns())
186 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
187 startx = hiddenColumns.adjustForHiddenColumns(startx);
188 endx = hiddenColumns.adjustForHiddenColumns(endx);
190 FontMetrics fm = getFontMetrics(av.getFont());
192 for (int i = 0; i < av.getAlignment().getHeight(); i++)
194 SequenceI seq = av.getAlignment().getSequenceAt(i);
197 * find sequence position of first non-gapped position -
198 * to the right if scale left, to the left if scale right
200 int index = left ? startx : endx;
202 while (index >= startx && index <= endx)
204 if (!Comparison.isGap(seq.getCharAt(index)))
206 value = seq.findPosition(index);
222 * white fill the space for the scale
224 g.setColor(Color.white);
225 int y = (ypos + (i * charHeight)) - (charHeight / 5);
226 y -= charHeight; // fillRect: origin is top left of rectangle
227 int xpos = left ? 0 : getWidth() - labelWidthEast;
228 g.fillRect(xpos, y, left ? labelWidthWest : labelWidthEast,
230 y += charHeight; // drawString: origin is bottom left of text
233 * draw scale value, right justified, with half a character width
234 * separation from the sequence data
236 String valueAsString = String.valueOf(value);
237 int justify = fm.stringWidth(valueAsString) + charWidth;
238 xpos = left ? labelWidthWest - justify + charWidth / 2
239 : getWidth() - justify - charWidth / 2;
241 g.setColor(Color.black);
242 g.drawString(valueAsString, xpos, y);
248 * Does a fast paint of an alignment in response to a scroll. Most of the
249 * visible region is simply copied and shifted, and then any newly visible
250 * columns or rows are drawn. The scroll may be horizontal or vertical, but
251 * not both at once. Scrolling may be the result of
253 * <li>dragging a scroll bar</li>
254 * <li>clicking in the scroll bar</li>
255 * <li>scrolling by trackpad, middle mouse button, or other device</li>
256 * <li>by moving the box in the Overview window</li>
257 * <li>programmatically to make a highlighted position visible</li>
261 * columns to shift right (positive) or left (negative)
263 * rows to shift down (positive) or up (negative)
265 public void fastPaint(int horizontal, int vertical)
267 if (fastpainting || gg == null)
278 ViewportRanges ranges = av.getRanges();
279 int startRes = ranges.getStartRes();
280 int endRes = ranges.getEndRes();
281 int startSeq = ranges.getStartSeq();
282 int endSeq = ranges.getEndSeq();
286 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
287 imgHeight, -horizontal * charWidth, -vertical * charHeight);
289 if (horizontal > 0) // scrollbar pulled right, image to the left
291 transX = (endRes - startRes - horizontal) * charWidth;
292 startRes = endRes - horizontal;
294 else if (horizontal < 0)
296 endRes = startRes - horizontal;
298 else if (vertical > 0) // scroll down
300 startSeq = endSeq - vertical;
302 if (startSeq < ranges.getStartSeq())
303 { // ie scrolling too fast, more than a page at a time
304 startSeq = ranges.getStartSeq();
308 transY = imgHeight - ((vertical + 1) * charHeight);
311 else if (vertical < 0)
313 endSeq = startSeq - vertical;
315 if (endSeq > ranges.getEndSeq())
317 endSeq = ranges.getEndSeq();
321 gg.translate(transX, transY);
322 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
323 gg.translate(-transX, -transY);
328 fastpainting = false;
333 public void paintComponent(Graphics g)
336 BufferedImage lcimg = img; // take reference since other threads may null
337 // img and call later.
338 super.paintComponent(g);
342 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
343 .getClipBounds().height)))
345 g.drawImage(lcimg, 0, 0, this);
350 // this draws the whole of the alignment
351 imgWidth = getWidth();
352 imgHeight = getHeight();
354 imgWidth -= (imgWidth % charWidth);
355 imgHeight -= (imgHeight % charHeight);
357 if ((imgWidth < 1) || (imgHeight < 1))
362 if (lcimg == null || imgWidth != lcimg.getWidth()
363 || imgHeight != lcimg.getHeight())
367 lcimg = img = new BufferedImage(imgWidth, imgHeight,
368 BufferedImage.TYPE_INT_RGB);
369 gg = (Graphics2D) img.getGraphics();
370 gg.setFont(av.getFont());
371 } catch (OutOfMemoryError er)
374 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
375 new OOMWarning("Creating alignment image for display", er);
383 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
384 RenderingHints.VALUE_ANTIALIAS_ON);
387 gg.setColor(Color.white);
388 gg.fillRect(0, 0, imgWidth, imgHeight);
390 ViewportRanges ranges = av.getRanges();
391 if (av.getWrapAlignment())
393 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
397 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
398 ranges.getStartSeq(), ranges.getEndSeq(), 0);
401 g.drawImage(lcimg, 0, 0, this);
406 * Returns the visible width of the canvas in residues, after allowing for
407 * East or West scales (if shown)
410 * the width in pixels (possibly including scales)
414 public int getWrappedCanvasWidth(int canvasWidth)
416 FontMetrics fm = getFontMetrics(av.getFont());
421 if (av.getScaleRightWrapped())
423 labelWidthEast = getLabelWidth(fm);
426 if (av.getScaleLeftWrapped())
428 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
432 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
436 * Returns a pixel width suitable for showing the largest sequence coordinate
437 * (end position) in the alignment. Returns 2 plus the number of decimal
438 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
443 protected int getLabelWidth(FontMetrics fm)
446 * find the biggest sequence end position we need to show
447 * (note this is not necessarily the sequence length)
450 AlignmentI alignment = av.getAlignment();
451 for (int i = 0; i < alignment.getHeight(); i++)
453 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
457 for (int i = maxWidth; i > 0; i /= 10)
462 return fm.stringWidth(ZEROS.substring(0, length));
466 * Draws as many widths of a wrapped alignment as can fit in the visible
471 * available width in pixels
472 * @param canvasHeight
473 * available height in pixels
475 * the first visible column (0...) of the alignment to draw
477 public void drawWrappedPanel(Graphics g, int canvasWidth,
478 int canvasHeight, int startRes)
482 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
484 FontMetrics fm = getFontMetrics(av.getFont());
485 labelWidth = getLabelWidth(fm);
488 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
489 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
491 int hgap = charHeight;
492 if (av.getScaleAboveWrapped())
497 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
499 av.setWrappedWidth(cWidth);
501 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
504 int maxwidth = av.getAlignment().getWidth();
506 if (av.hasHiddenColumns())
508 maxwidth = av.getAlignment().getHiddenColumns()
509 .findColumnPosition(maxwidth);
512 int annotationHeight = getAnnotationHeight();
513 int sequencesHeight = av.getAlignment().getHeight() * charHeight;
516 * draw one width at a time (including any scales or annotation shown),
517 * until we have run out of alignment or vertical space available
518 * (stop if not enough room left for at least one sequence)
520 int yposMax = canvasHeight;// - hgap - charHeight + 1;
521 while ((ypos <= yposMax) && (startRes < maxwidth))
523 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
525 ypos += sequencesHeight + annotationHeight + hgap;
532 * Draws one width of a wrapped alignment, including scales left, right or
533 * above, and annnotations, if shown
537 * @param canvasHeight
542 protected void drawWrappedWidth(Graphics g, int startRes,
543 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
546 endx = startRes + canvasWidth - 1;
553 g.setFont(av.getFont());
554 g.setColor(Color.black);
556 if (av.getScaleLeftWrapped())
558 drawVerticalScale(g, startRes, endx, ypos, true);
561 if (av.getScaleRightWrapped())
563 drawVerticalScale(g, startRes, endx, ypos, false);
566 drawWrappedRegion(g, startRes, endx, canvasHeight, canvasWidth, ypos);
570 * Draws columns of a wrapped alignment from startRes to endRes, including
571 * scale above and annotations if shown, but not scale left or right.
576 * @param canvasHeight
580 protected void drawWrappedRegion(Graphics g, int startRes, int endRes,
581 int canvasHeight, int canvasWidth, int ypos)
583 g.translate(labelWidthWest, 0);
585 if (av.getScaleAboveWrapped())
587 drawNorthScale(g, startRes, endRes, ypos);
590 // todo can we let drawPanel() handle this?
591 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
593 g.setColor(Color.blue);
594 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
595 List<Integer> positions = hidden.findHiddenRegionPositions();
596 for (int pos : positions)
598 int res = pos - startRes;
600 if (res < 0 || res > endRes - startRes)
605 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
606 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
607 ypos - (charHeight / 2), ypos - (charHeight / 2),
608 ypos - (charHeight / 2) + 8 }, 3);
612 // When printing we have an extra clipped region,
613 // the Printable page which we need to account for here
614 Shape clip = g.getClip();
618 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
622 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
623 (int) clip.getBounds().getHeight());
626 drawPanel(g, startRes, endRes, 0, av.getAlignment().getHeight() - 1, ypos);
628 int cHeight = av.getAlignment().getHeight() * charHeight;
630 if (av.isShowAnnotation())
632 g.translate(0, cHeight + ypos + 3);
633 if (annotations == null)
635 annotations = new AnnotationPanel(av);
638 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
640 g.translate(0, -cHeight - ypos - 3);
643 g.translate(-labelWidthWest, 0);
646 AnnotationPanel annotations;
648 int getAnnotationHeight()
650 if (!av.isShowAnnotation())
655 if (annotations == null)
657 annotations = new AnnotationPanel(av);
660 return annotations.adjustPanelHeight();
664 * Draws the visible region of the alignment on the graphics context. If there
665 * are hidden column markers in the visible region, then each sub-region
666 * between the markers is drawn separately, followed by the hidden column
670 * the graphics context, positioned at the first residue to be drawn
672 * offset of the first column to draw (0..)
674 * offset of the last column to draw (0..)
676 * offset of the first sequence to draw (0..)
678 * offset of the last sequence to draw (0..)
680 * vertical offset at which to draw (for wrapped alignments)
682 public void drawPanel(Graphics g1, final int startRes, final int endRes,
683 final int startSeq, final int endSeq, final int yOffset)
686 if (!av.hasHiddenColumns())
688 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
693 final int screenYMax = endRes - startRes;
694 int blockStart = startRes;
695 int blockEnd = endRes;
697 for (int[] region : av.getAlignment().getHiddenColumns()
698 .getHiddenColumnsCopy())
700 int hideStart = region[0];
701 int hideEnd = region[1];
703 if (hideStart <= blockStart)
705 blockStart += (hideEnd - hideStart) + 1;
710 * draw up to just before the next hidden region, or the end of
711 * the visible region, whichever comes first
713 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
716 g1.translate(screenY * charWidth, 0);
718 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
721 * draw the downline of the hidden column marker (ScalePanel draws the
722 * triangle on top) if we reached it
724 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
726 g1.setColor(Color.blue);
728 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
729 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
730 (endSeq - startSeq + 1) * charHeight + yOffset);
733 g1.translate(-screenY * charWidth, 0);
734 screenY += blockEnd - blockStart + 1;
735 blockStart = hideEnd + 1;
737 if (screenY > screenYMax)
739 // already rendered last block
744 if (screenY <= screenYMax)
746 // remaining visible region to render
747 blockEnd = blockStart + screenYMax - screenY;
748 g1.translate(screenY * charWidth, 0);
749 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
751 g1.translate(-screenY * charWidth, 0);
758 * Draws a region of the visible alignment
762 * offset of the first column in the visible region (0..)
764 * offset of the last column in the visible region (0..)
766 * offset of the first sequence in the visible region (0..)
768 * offset of the last sequence in the visible region (0..)
770 * vertical offset at which to draw (for wrapped alignments)
772 private void draw(Graphics g, int startRes, int endRes, int startSeq,
773 int endSeq, int offset)
775 g.setFont(av.getFont());
776 sr.prepare(g, av.isRenderGaps());
780 // / First draw the sequences
781 // ///////////////////////////
782 for (int i = startSeq; i <= endSeq; i++)
784 nextSeq = av.getAlignment().getSequenceAt(i);
787 // occasionally, a race condition occurs such that the alignment row is
791 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
792 startRes, endRes, offset + ((i - startSeq) * charHeight));
794 if (av.isShowSequenceFeatures())
796 fr.drawSequence(g, nextSeq, startRes, endRes, offset
797 + ((i - startSeq) * charHeight), false);
801 * highlight search Results once sequence has been drawn
803 if (av.hasSearchResults())
805 SearchResultsI searchResults = av.getSearchResults();
806 int[] visibleResults = searchResults.getResults(nextSeq,
808 if (visibleResults != null)
810 for (int r = 0; r < visibleResults.length; r += 2)
812 sr.drawHighlightedText(nextSeq, visibleResults[r],
813 visibleResults[r + 1], (visibleResults[r] - startRes)
815 + ((i - startSeq) * charHeight));
820 if (av.cursorMode && cursorY == i && cursorX >= startRes
821 && cursorX <= endRes)
823 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
824 offset + ((i - startSeq) * charHeight));
828 if (av.getSelectionGroup() != null
829 || av.getAlignment().getGroups().size() > 0)
831 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
836 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
837 int startSeq, int endSeq, int offset)
839 Graphics2D g = (Graphics2D) g1;
841 // ///////////////////////////////////
842 // Now outline any areas if necessary
843 // ///////////////////////////////////
844 SequenceGroup group = av.getSelectionGroup();
850 int visWidth = (endRes - startRes + 1) * charWidth;
852 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
854 group = av.getAlignment().getGroups().get(0);
864 boolean inGroup = false;
868 for (i = startSeq; i <= endSeq; i++)
870 sx = (group.getStartRes() - startRes) * charWidth;
871 sy = offset + ((i - startSeq) * charHeight);
872 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
874 if (sx + ex < 0 || sx > visWidth)
879 if ((sx <= (endRes - startRes) * charWidth)
880 && group.getSequences(null).contains(
881 av.getAlignment().getSequenceAt(i)))
884 && !group.getSequences(null).contains(
885 av.getAlignment().getSequenceAt(i + 1)))
887 bottom = sy + charHeight;
892 if (((top == -1) && (i == 0))
893 || !group.getSequences(null).contains(
894 av.getAlignment().getSequenceAt(i - 1)))
902 if (group == av.getSelectionGroup())
904 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
905 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
907 g.setColor(Color.RED);
911 g.setStroke(new BasicStroke());
912 g.setColor(group.getOutlineColour());
920 if (sx >= 0 && sx < visWidth)
922 g.drawLine(sx, oldY, sx, sy);
925 if (sx + ex < visWidth)
927 g.drawLine(sx + ex, oldY, sx + ex, sy);
936 if (sx + ex > visWidth)
941 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
943 ex = (endRes - startRes + 1) * charWidth;
948 g.drawLine(sx, top, sx + ex, top);
954 g.drawLine(sx, bottom, sx + ex, bottom);
965 sy = offset + ((i - startSeq) * charHeight);
966 if (sx >= 0 && sx < visWidth)
968 g.drawLine(sx, oldY, sx, sy);
971 if (sx + ex < visWidth)
973 g.drawLine(sx + ex, oldY, sx + ex, sy);
982 if (sx + ex > visWidth)
986 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
988 ex = (endRes - startRes + 1) * charWidth;
993 g.drawLine(sx, top, sx + ex, top);
999 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1008 g.setStroke(new BasicStroke());
1010 if (groupIndex >= av.getAlignment().getGroups().size())
1015 group = av.getAlignment().getGroups().get(groupIndex);
1017 } while (groupIndex < av.getAlignment().getGroups().size());
1024 * Highlights search results in the visible region by rendering as white text
1025 * on a black background. Any previous highlighting is removed. Answers true
1026 * if any highlight was left on the visible alignment (so status bar should be
1027 * set to match), else false.
1029 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1030 * alignment had to be scrolled to show the highlighted region, then it should
1031 * be fully redrawn, otherwise a fast paint can be performed. This argument
1032 * could be removed if fast paint of scrolled wrapped alignment is coded in
1033 * future (JAL-2609).
1036 * @param noFastPaint
1039 public boolean highlightSearchResults(SearchResultsI results,
1040 boolean noFastPaint)
1046 boolean wrapped = av.getWrapAlignment();
1050 fastPaint = !noFastPaint;
1051 fastpainting = fastPaint;
1056 * to avoid redrawing the whole visible region, we instead
1057 * redraw just the minimal regions to remove previous highlights
1060 SearchResultsI previous = av.getSearchResults();
1061 av.setSearchResults(results);
1062 boolean redrawn = false;
1063 boolean drawn = false;
1066 redrawn = drawMappedPositionsWrapped(previous);
1067 drawn = drawMappedPositionsWrapped(results);
1072 redrawn = drawMappedPositions(previous);
1073 drawn = drawMappedPositions(results);
1078 * if highlights were either removed or added, repaint
1086 * return true only if highlights were added
1092 fastpainting = false;
1097 * Redraws the minimal rectangle in the visible region (if any) that includes
1098 * mapped positions of the given search results. Whether or not positions are
1099 * highlighted depends on the SearchResults set on the Viewport. This allows
1100 * this method to be called to either clear or set highlighting. Answers true
1101 * if any positions were drawn (in which case a repaint is still required),
1107 protected boolean drawMappedPositions(SearchResultsI results)
1109 if (results == null)
1115 * calculate the minimal rectangle to redraw that
1116 * includes both new and existing search results
1118 int firstSeq = Integer.MAX_VALUE;
1120 int firstCol = Integer.MAX_VALUE;
1122 boolean matchFound = false;
1124 ViewportRanges ranges = av.getRanges();
1125 int firstVisibleColumn = ranges.getStartRes();
1126 int lastVisibleColumn = ranges.getEndRes();
1127 AlignmentI alignment = av.getAlignment();
1128 if (av.hasHiddenColumns())
1130 firstVisibleColumn = alignment.getHiddenColumns()
1131 .adjustForHiddenColumns(firstVisibleColumn);
1132 lastVisibleColumn = alignment.getHiddenColumns()
1133 .adjustForHiddenColumns(lastVisibleColumn);
1136 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1137 .getEndSeq(); seqNo++)
1139 SequenceI seq = alignment.getSequenceAt(seqNo);
1141 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1143 if (visibleResults != null)
1145 for (int i = 0; i < visibleResults.length - 1; i += 2)
1147 int firstMatchedColumn = visibleResults[i];
1148 int lastMatchedColumn = visibleResults[i + 1];
1149 if (firstMatchedColumn <= lastVisibleColumn
1150 && lastMatchedColumn >= firstVisibleColumn)
1153 * found a search results match in the visible region -
1154 * remember the first and last sequence matched, and the first
1155 * and last visible columns in the matched positions
1158 firstSeq = Math.min(firstSeq, seqNo);
1159 lastSeq = Math.max(lastSeq, seqNo);
1160 firstMatchedColumn = Math.max(firstMatchedColumn,
1161 firstVisibleColumn);
1162 lastMatchedColumn = Math.min(lastMatchedColumn,
1164 firstCol = Math.min(firstCol, firstMatchedColumn);
1165 lastCol = Math.max(lastCol, lastMatchedColumn);
1173 if (av.hasHiddenColumns())
1175 firstCol = alignment.getHiddenColumns()
1176 .findColumnPosition(firstCol);
1177 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1179 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1180 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1181 gg.translate(transX, transY);
1182 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1183 gg.translate(-transX, -transY);
1190 public void propertyChange(PropertyChangeEvent evt)
1192 String eventName = evt.getPropertyName();
1195 if (eventName.equals(ViewportRanges.STARTRES))
1197 // Make sure we're not trying to draw a panel
1198 // larger than the visible window
1199 ViewportRanges vpRanges = av.getRanges();
1200 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1201 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1202 if (scrollX > range)
1206 else if (scrollX < -range)
1212 // Both scrolling and resizing change viewport ranges: scrolling changes
1213 // both start and end points, but resize only changes end values.
1214 // Here we only want to fastpaint on a scroll, with resize using a normal
1215 // paint, so scroll events are identified as changes to the horizontal or
1216 // vertical start value.
1217 if (eventName.equals(ViewportRanges.STARTRES))
1219 // scroll - startres and endres both change
1220 if (av.getWrapAlignment())
1222 fastPaintWrapped(scrollX);
1226 fastPaint(scrollX, 0);
1229 else if (eventName.equals(ViewportRanges.STARTSEQ))
1231 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1236 * Does a minimal update of the image for a scroll movement. This method
1237 * handles scroll movements of up to one width of the wrapped alignment (one
1238 * click in the vertical scrollbar). Larger movements (for example after a
1239 * scroll to highlight a mapped position) trigger a full redraw instead.
1242 * number of positions scrolled (right if positive, left if negative)
1244 protected void fastPaintWrapped(int scrollX)
1246 if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
1249 * shift of more than one view width is
1250 * overcomplicated to handle in this method
1257 if (fastpainting || gg == null)
1263 fastpainting = true;
1268 * relocate the regions of the alignment that are still visible
1270 shiftWrappedAlignment(-scrollX);
1273 * add new columns (scale above, sequence, annotation)
1274 * - at top left if scrollX < 0
1275 * - at right of last two widths if scrollX > 0
1276 * also West scale top left or East scale bottom right if shown
1280 fastPaintWrappedAddLeft(-scrollX);
1284 fastPaintWrappedAddRight(scrollX);
1290 fastpainting = false;
1295 * Draws the specified number of columns at the 'end' (bottom right) of a
1296 * wrapped alignment view, including scale above and right and annotations if
1297 * shown. Also draws the same number of columns at the right hand end of the
1298 * second last width shown, if the last width is not full height (so cannot
1299 * simply be copied from the graphics image).
1303 protected void fastPaintWrappedAddRight(int columns)
1311 * how many widths are visible? we will be adding
1312 * columns to the last visible width, right hand end
1314 int repeatHeight = getRepeatHeightWrapped();
1315 int canvasHeight = getHeight();
1316 int visibleWidths = canvasHeight / repeatHeight;
1317 int remainder = canvasHeight % repeatHeight;
1318 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1319 boolean lastWidthPartHeight = false;
1320 if (remainder >= (hgap + charHeight))
1323 lastWidthPartHeight = true;
1327 * limit visible widths to max widths of alignment, from the
1328 * current start residue (we may be scrolled down)
1330 ViewportRanges ranges = av.getRanges();
1331 int availableAlignmentWidth = ranges.getVisibleAlignmentWidth()
1332 - ranges.getStartRes();
1333 int viewportWidth = ranges.getViewportWidth();
1334 int maxWidths = availableAlignmentWidth / viewportWidth;
1335 if (availableAlignmentWidth % viewportWidth > 0)
1339 visibleWidths = Math.min(visibleWidths, maxWidths);
1340 int canvasWidth = getWidth();
1341 int widthInColumns = (canvasWidth - labelWidthEast - labelWidthWest)
1345 * draw full height alignment in the second last row, last columns, if the
1346 * last row was not full height
1348 if (lastWidthPartHeight)
1350 int widthsAbove = visibleWidths - 2;
1351 int ypos = repeatHeight * widthsAbove + hgap;
1352 int endRes = ranges.getEndRes();
1353 endRes += widthsAbove * viewportWidth;
1354 int startRes = endRes - columns;
1355 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1357 gg.translate(xOffset, 0);
1360 * white fill first to erase annotations
1362 gg.setColor(Color.white);
1363 gg.fillRect(labelWidthWest, ypos,
1364 (endRes - startRes + 1) * charWidth, repeatHeight);
1366 drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
1368 gg.translate(-xOffset, 0);
1372 * y-offset for drawing is height of widths above,
1375 int widthsAbove = visibleWidths - 1;
1376 int ypos = repeatHeight * widthsAbove + hgap;
1377 int endRes = ranges.getEndRes();
1378 endRes += widthsAbove * viewportWidth;
1379 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1382 * draw one extra column than strictly needed - this is a (harmless)
1383 * fudge to ensure scale marks get drawn (JAL-2636)
1385 int startRes = endRes - columns;
1388 * x-offset is x-start modulo viewport start residue;
1389 * doesn't include label West (offset is applied in drawWrappedRegion)
1392 int leftEndColumn = ranges.getStartRes() + widthsAbove
1393 * ranges.getViewportWidth();
1394 // startRes = Math.max(startRes - 0, leftEndColumn);
1395 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1397 gg.translate(xOffset, 0);
1400 * white fill the region to be drawn including scale left or above;
1401 * extend to right hand margin so as to erase scale above when
1402 * scrolling right beyond end of alignment
1404 gg.setColor(Color.white);
1405 int width = canvasWidth - labelWidthWest - xOffset;
1406 gg.fillRect(labelWidthWest, ypos - hgap, width, repeatHeight);
1408 gg.setFont(av.getFont());
1409 gg.setColor(Color.black);
1411 drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
1413 gg.translate(-xOffset, 0);
1416 * draw scale right if shown, passing in the start/end columns
1417 * for the whole line, not just the last few columns
1419 if (av.getScaleRightWrapped())
1421 drawVerticalScale(gg, leftEndColumn, endRes, ypos, false);
1425 * and finally, white fill any space below the visible alignment
1426 * (in case it has wrapped to just the top part of the panel)
1428 int heightBelow = canvasHeight - visibleWidths * repeatHeight;
1429 if (heightBelow > 0)
1431 gg.setColor(Color.white);
1432 gg.fillRect(0, canvasHeight - heightBelow, canvasWidth, heightBelow);
1437 * Draws the specified number of columns at the 'start' (top left) of a
1438 * wrapped alignment view, including scale above and left and annotations if
1443 protected void fastPaintWrappedAddLeft(int columns)
1445 int startRes = av.getRanges().getStartRes();
1448 * draw one extra column than strictly needed - this is a (harmless)
1449 * fudge to ensure scale marks get drawn (JAL-2636)
1451 int endx = startRes + columns;
1455 * white fill the region to be drawn including scale left or above
1457 gg.setColor(Color.white);
1458 int height = getRepeatHeightWrapped();
1459 gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
1460 ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1462 gg.setFont(av.getFont());
1463 gg.setColor(Color.black);
1465 if (av.getScaleLeftWrapped())
1467 drawVerticalScale(gg, startRes, endx, ypos, true);
1470 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1472 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1476 * Shifts the visible alignment by the specified number of columns - left if
1477 * negative, right if positive. Includes scale above, left or right and
1478 * annotations (if shown). Does not draw newly visible columns.
1482 protected void shiftWrappedAlignment(int positions)
1489 int repeatHeight = getRepeatHeightWrapped();
1490 ViewportRanges ranges = av.getRanges();
1491 int xMax = ranges.getVisibleAlignmentWidth();
1492 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1494 int canvasHeight = getHeight();
1495 int visibleWidths = canvasHeight / repeatHeight;
1496 if (canvasHeight % repeatHeight > 0)
1500 int viewportWidth = ranges.getViewportWidth();
1501 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1503 int remainder = canvasHeight % repeatHeight;
1504 if (remainder >= (hgap + charHeight))
1508 // todo limit visibleWidths to not exceed width of alignment
1509 // (don't process white space below)
1514 * shift right (after scroll left)
1515 * for each wrapped width (starting with the last), copy (width-positions)
1516 * columns from the left margin to the right margin, and copy positions
1517 * columns from the right margin of the row above (if any) to the
1518 * left margin of the current row
1520 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1523 * get y-offset of last wrapped width
1525 int y = canvasHeight / repeatHeight * repeatHeight;
1526 int copyFromLeftStart = labelWidthWest;
1527 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1531 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1532 positions * charWidth, 0);
1535 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1536 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1539 if (av.getScaleLeftWrapped())
1541 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1544 if (av.getScaleRightWrapped())
1546 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1551 xpos -= viewportWidth;
1557 * shift left (after scroll right)
1558 * for each wrapped width (starting with the first), copy (width-positions)
1559 * columns from the right margin to the left margin, and copy positions
1560 * columns from the left margin of the row below (if any) to the
1561 * right margin of the current row
1563 int xpos = ranges.getStartRes();
1565 int copyFromRightStart = labelWidthWest - positions * charWidth;
1567 while (y < canvasHeight)
1569 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1570 positions * charWidth, 0);
1571 if (y + repeatHeight < canvasHeight - repeatHeight
1572 && (xpos + viewportWidth <= xMax))
1574 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1575 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1578 if (av.getScaleLeftWrapped())
1580 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1582 if (av.getScaleRightWrapped())
1584 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1588 xpos += viewportWidth;
1594 * Redraws any positions in the search results in the visible region of a
1595 * wrapped alignment. Any highlights are drawn depending on the search results
1596 * set on the Viewport, not the <code>results</code> argument. This allows
1597 * this method to be called either to clear highlights (passing the previous
1598 * search results), or to draw new highlights.
1603 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1605 if (results == null)
1610 boolean matchFound = false;
1612 int wrappedWidth = av.getWrappedWidth();
1613 int wrappedHeight = getRepeatHeightWrapped();
1615 ViewportRanges ranges = av.getRanges();
1616 int canvasHeight = getHeight();
1617 int repeats = canvasHeight / wrappedHeight;
1618 if (canvasHeight / wrappedHeight > 0)
1623 int firstVisibleColumn = ranges.getStartRes();
1624 int lastVisibleColumn = ranges.getStartRes() + repeats
1625 * ranges.getViewportWidth() - 1;
1627 AlignmentI alignment = av.getAlignment();
1628 if (av.hasHiddenColumns())
1630 firstVisibleColumn = alignment.getHiddenColumns()
1631 .adjustForHiddenColumns(firstVisibleColumn);
1632 lastVisibleColumn = alignment.getHiddenColumns()
1633 .adjustForHiddenColumns(lastVisibleColumn);
1636 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1638 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1639 .getEndSeq(); seqNo++)
1641 SequenceI seq = alignment.getSequenceAt(seqNo);
1643 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1645 if (visibleResults != null)
1647 for (int i = 0; i < visibleResults.length - 1; i += 2)
1649 int firstMatchedColumn = visibleResults[i];
1650 int lastMatchedColumn = visibleResults[i + 1];
1651 if (firstMatchedColumn <= lastVisibleColumn
1652 && lastMatchedColumn >= firstVisibleColumn)
1655 * found a search results match in the visible region
1657 firstMatchedColumn = Math.max(firstMatchedColumn,
1658 firstVisibleColumn);
1659 lastMatchedColumn = Math.min(lastMatchedColumn,
1663 * draw each mapped position separately (as contiguous positions may
1664 * wrap across lines)
1666 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1668 int displayColumn = mappedPos;
1669 if (av.hasHiddenColumns())
1671 displayColumn = alignment.getHiddenColumns()
1672 .findColumnPosition(displayColumn);
1676 * transX: offset from left edge of canvas to residue position
1678 int transX = labelWidthWest
1679 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1680 * av.getCharWidth();
1683 * transY: offset from top edge of canvas to residue position
1685 int transY = gapHeight;
1686 transY += (displayColumn - ranges.getStartRes())
1687 / wrappedWidth * wrappedHeight;
1688 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1691 * yOffset is from graphics origin to start of visible region
1693 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1694 if (transY < getHeight())
1697 gg.translate(transX, transY);
1698 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1700 gg.translate(-transX, -transY);
1712 * Answers the height in pixels of a repeating section of the wrapped
1713 * alignment, including space above, scale above if shown, sequences, and
1714 * annotation panel if shown
1718 protected int getRepeatHeightWrapped()
1720 // gap (and maybe scale) above
1721 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1724 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1726 // add annotations panel height if shown
1727 repeatHeight += getAnnotationHeight();
1729 return repeatHeight;