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)
275 ViewportRanges ranges = av.getRanges();
276 int startRes = ranges.getStartRes();
277 int endRes = ranges.getEndRes();
278 int startSeq = ranges.getStartSeq();
279 int endSeq = ranges.getEndSeq();
283 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
284 imgHeight, -horizontal * charWidth, -vertical * charHeight);
286 if (horizontal > 0) // scrollbar pulled right, image to the left
288 transX = (endRes - startRes - horizontal) * charWidth;
289 startRes = endRes - horizontal;
291 else if (horizontal < 0)
293 endRes = startRes - horizontal;
295 else if (vertical > 0) // scroll down
297 startSeq = endSeq - vertical;
299 if (startSeq < ranges.getStartSeq())
300 { // ie scrolling too fast, more than a page at a time
301 startSeq = ranges.getStartSeq();
305 transY = imgHeight - ((vertical + 1) * charHeight);
308 else if (vertical < 0)
310 endSeq = startSeq - vertical;
312 if (endSeq > ranges.getEndSeq())
314 endSeq = ranges.getEndSeq();
318 gg.translate(transX, transY);
319 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
320 gg.translate(-transX, -transY);
323 fastpainting = false;
327 public void paintComponent(Graphics g)
330 BufferedImage lcimg = img; // take reference since other threads may null
331 // img and call later.
332 super.paintComponent(g);
336 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
337 .getClipBounds().height)))
339 g.drawImage(lcimg, 0, 0, this);
344 // this draws the whole of the alignment
345 imgWidth = getWidth();
346 imgHeight = getHeight();
348 imgWidth -= (imgWidth % charWidth);
349 imgHeight -= (imgHeight % charHeight);
351 if ((imgWidth < 1) || (imgHeight < 1))
356 if (lcimg == null || imgWidth != lcimg.getWidth()
357 || imgHeight != lcimg.getHeight())
361 lcimg = img = new BufferedImage(imgWidth, imgHeight,
362 BufferedImage.TYPE_INT_RGB);
363 gg = (Graphics2D) img.getGraphics();
364 gg.setFont(av.getFont());
365 } catch (OutOfMemoryError er)
368 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
369 new OOMWarning("Creating alignment image for display", er);
377 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
378 RenderingHints.VALUE_ANTIALIAS_ON);
381 gg.setColor(Color.white);
382 gg.fillRect(0, 0, imgWidth, imgHeight);
384 ViewportRanges ranges = av.getRanges();
385 if (av.getWrapAlignment())
387 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
391 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
392 ranges.getStartSeq(), ranges.getEndSeq(), 0);
395 g.drawImage(lcimg, 0, 0, this);
400 * Returns the visible width of the canvas in residues, after allowing for
401 * East or West scales (if shown)
404 * the width in pixels (possibly including scales)
408 public int getWrappedCanvasWidth(int canvasWidth)
410 FontMetrics fm = getFontMetrics(av.getFont());
415 if (av.getScaleRightWrapped())
417 labelWidthEast = getLabelWidth(fm);
420 if (av.getScaleLeftWrapped())
422 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
426 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
430 * Returns a pixel width suitable for showing the largest sequence coordinate
431 * (end position) in the alignment. Returns 2 plus the number of decimal
432 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
437 protected int getLabelWidth(FontMetrics fm)
440 * find the biggest sequence end position we need to show
441 * (note this is not necessarily the sequence length)
444 AlignmentI alignment = av.getAlignment();
445 for (int i = 0; i < alignment.getHeight(); i++)
447 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
451 for (int i = maxWidth; i > 0; i /= 10)
456 return fm.stringWidth(ZEROS.substring(0, length));
460 * Draws as many widths of a wrapped alignment as can fit in the visible
465 * available width in pixels
466 * @param canvasHeight
467 * available height in pixels
469 * the first visible column (0...) of the alignment to draw
471 public void drawWrappedPanel(Graphics g, int canvasWidth,
472 int canvasHeight, int startRes)
476 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
478 FontMetrics fm = getFontMetrics(av.getFont());
479 labelWidth = getLabelWidth(fm);
482 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
483 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
485 int hgap = charHeight;
486 if (av.getScaleAboveWrapped())
491 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
493 av.setWrappedWidth(cWidth);
495 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
498 int maxwidth = av.getAlignment().getWidth();
500 if (av.hasHiddenColumns())
502 maxwidth = av.getAlignment().getHiddenColumns()
503 .findColumnPosition(maxwidth);
506 int annotationHeight = getAnnotationHeight();
507 int sequencesHeight = av.getAlignment().getHeight() * charHeight;
510 * draw one width at a time (including any scales or annotation shown),
511 * until we have run out of alignment or vertical space available
512 * (stop if not enough room left for at least one sequence)
514 int yposMax = canvasHeight;// - hgap - charHeight + 1;
515 while ((ypos <= yposMax) && (startRes < maxwidth))
517 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
519 ypos += sequencesHeight + annotationHeight + hgap;
526 * Draws one width of a wrapped alignment, including scales left, right or
527 * above, and annnotations, if shown
531 * @param canvasHeight
536 protected void drawWrappedWidth(Graphics g, int startRes,
537 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
540 endx = startRes + canvasWidth - 1;
547 g.setFont(av.getFont());
548 g.setColor(Color.black);
550 if (av.getScaleLeftWrapped())
552 drawVerticalScale(g, startRes, endx, ypos, true);
555 if (av.getScaleRightWrapped())
557 drawVerticalScale(g, startRes, endx, ypos, false);
560 drawWrappedRegion(g, startRes, endx, canvasHeight, canvasWidth, ypos);
564 * Draws columns of a wrapped alignment from startRes to endRes, including
565 * scale above and annotations if shown, but not scale left or right.
570 * @param canvasHeight
574 protected void drawWrappedRegion(Graphics g, int startRes, int endRes,
575 int canvasHeight, int canvasWidth, int ypos)
577 g.translate(labelWidthWest, 0);
579 if (av.getScaleAboveWrapped())
581 drawNorthScale(g, startRes, endRes, ypos);
584 // todo can we let drawPanel() handle this?
585 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
587 g.setColor(Color.blue);
588 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
589 List<Integer> positions = hidden.findHiddenRegionPositions();
590 for (int pos : positions)
592 int res = pos - startRes;
594 if (res < 0 || res > endRes - startRes)
599 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
600 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
601 ypos - (charHeight / 2), ypos - (charHeight / 2),
602 ypos - (charHeight / 2) + 8 }, 3);
606 // When printing we have an extra clipped region,
607 // the Printable page which we need to account for here
608 Shape clip = g.getClip();
612 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
616 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
617 (int) clip.getBounds().getHeight());
620 drawPanel(g, startRes, endRes, 0, av.getAlignment().getHeight() - 1, ypos);
622 int cHeight = av.getAlignment().getHeight() * charHeight;
624 if (av.isShowAnnotation())
626 g.translate(0, cHeight + ypos + 3);
627 if (annotations == null)
629 annotations = new AnnotationPanel(av);
632 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
634 g.translate(0, -cHeight - ypos - 3);
637 g.translate(-labelWidthWest, 0);
640 AnnotationPanel annotations;
642 int getAnnotationHeight()
644 if (!av.isShowAnnotation())
649 if (annotations == null)
651 annotations = new AnnotationPanel(av);
654 return annotations.adjustPanelHeight();
658 * Draws the visible region of the alignment on the graphics context. If there
659 * are hidden column markers in the visible region, then each sub-region
660 * between the markers is drawn separately, followed by the hidden column
664 * the graphics context, positioned at the first residue to be drawn
666 * offset of the first column to draw (0..)
668 * offset of the last column to draw (0..)
670 * offset of the first sequence to draw (0..)
672 * offset of the last sequence to draw (0..)
674 * vertical offset at which to draw (for wrapped alignments)
676 public void drawPanel(Graphics g1, final int startRes, final int endRes,
677 final int startSeq, final int endSeq, final int yOffset)
680 if (!av.hasHiddenColumns())
682 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
687 final int screenYMax = endRes - startRes;
688 int blockStart = startRes;
689 int blockEnd = endRes;
691 for (int[] region : av.getAlignment().getHiddenColumns()
692 .getHiddenColumnsCopy())
694 int hideStart = region[0];
695 int hideEnd = region[1];
697 if (hideStart <= blockStart)
699 blockStart += (hideEnd - hideStart) + 1;
704 * draw up to just before the next hidden region, or the end of
705 * the visible region, whichever comes first
707 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
710 g1.translate(screenY * charWidth, 0);
712 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
715 * draw the downline of the hidden column marker (ScalePanel draws the
716 * triangle on top) if we reached it
718 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
720 g1.setColor(Color.blue);
722 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
723 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
724 (endSeq - startSeq + 1) * charHeight + yOffset);
727 g1.translate(-screenY * charWidth, 0);
728 screenY += blockEnd - blockStart + 1;
729 blockStart = hideEnd + 1;
731 if (screenY > screenYMax)
733 // already rendered last block
738 if (screenY <= screenYMax)
740 // remaining visible region to render
741 blockEnd = blockStart + screenYMax - screenY;
742 g1.translate(screenY * charWidth, 0);
743 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
745 g1.translate(-screenY * charWidth, 0);
752 * Draws a region of the visible alignment
756 * offset of the first column in the visible region (0..)
758 * offset of the last column in the visible region (0..)
760 * offset of the first sequence in the visible region (0..)
762 * offset of the last sequence in the visible region (0..)
764 * vertical offset at which to draw (for wrapped alignments)
766 private void draw(Graphics g, int startRes, int endRes, int startSeq,
767 int endSeq, int offset)
769 g.setFont(av.getFont());
770 sr.prepare(g, av.isRenderGaps());
774 // / First draw the sequences
775 // ///////////////////////////
776 for (int i = startSeq; i <= endSeq; i++)
778 nextSeq = av.getAlignment().getSequenceAt(i);
781 // occasionally, a race condition occurs such that the alignment row is
785 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
786 startRes, endRes, offset + ((i - startSeq) * charHeight));
788 if (av.isShowSequenceFeatures())
790 fr.drawSequence(g, nextSeq, startRes, endRes, offset
791 + ((i - startSeq) * charHeight), false);
795 * highlight search Results once sequence has been drawn
797 if (av.hasSearchResults())
799 SearchResultsI searchResults = av.getSearchResults();
800 int[] visibleResults = searchResults.getResults(nextSeq,
802 if (visibleResults != null)
804 for (int r = 0; r < visibleResults.length; r += 2)
806 sr.drawHighlightedText(nextSeq, visibleResults[r],
807 visibleResults[r + 1], (visibleResults[r] - startRes)
809 + ((i - startSeq) * charHeight));
814 if (av.cursorMode && cursorY == i && cursorX >= startRes
815 && cursorX <= endRes)
817 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
818 offset + ((i - startSeq) * charHeight));
822 if (av.getSelectionGroup() != null
823 || av.getAlignment().getGroups().size() > 0)
825 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
830 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
831 int startSeq, int endSeq, int offset)
833 Graphics2D g = (Graphics2D) g1;
835 // ///////////////////////////////////
836 // Now outline any areas if necessary
837 // ///////////////////////////////////
838 SequenceGroup group = av.getSelectionGroup();
844 int visWidth = (endRes - startRes + 1) * charWidth;
846 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
848 group = av.getAlignment().getGroups().get(0);
858 boolean inGroup = false;
862 for (i = startSeq; i <= endSeq; i++)
864 sx = (group.getStartRes() - startRes) * charWidth;
865 sy = offset + ((i - startSeq) * charHeight);
866 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
868 if (sx + ex < 0 || sx > visWidth)
873 if ((sx <= (endRes - startRes) * charWidth)
874 && group.getSequences(null).contains(
875 av.getAlignment().getSequenceAt(i)))
878 && !group.getSequences(null).contains(
879 av.getAlignment().getSequenceAt(i + 1)))
881 bottom = sy + charHeight;
886 if (((top == -1) && (i == 0))
887 || !group.getSequences(null).contains(
888 av.getAlignment().getSequenceAt(i - 1)))
896 if (group == av.getSelectionGroup())
898 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
899 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
901 g.setColor(Color.RED);
905 g.setStroke(new BasicStroke());
906 g.setColor(group.getOutlineColour());
914 if (sx >= 0 && sx < visWidth)
916 g.drawLine(sx, oldY, sx, sy);
919 if (sx + ex < visWidth)
921 g.drawLine(sx + ex, oldY, sx + ex, sy);
930 if (sx + ex > visWidth)
935 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
937 ex = (endRes - startRes + 1) * charWidth;
942 g.drawLine(sx, top, sx + ex, top);
948 g.drawLine(sx, bottom, sx + ex, bottom);
959 sy = offset + ((i - startSeq) * charHeight);
960 if (sx >= 0 && sx < visWidth)
962 g.drawLine(sx, oldY, sx, sy);
965 if (sx + ex < visWidth)
967 g.drawLine(sx + ex, oldY, sx + ex, sy);
976 if (sx + ex > visWidth)
980 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
982 ex = (endRes - startRes + 1) * charWidth;
987 g.drawLine(sx, top, sx + ex, top);
993 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1002 g.setStroke(new BasicStroke());
1004 if (groupIndex >= av.getAlignment().getGroups().size())
1009 group = av.getAlignment().getGroups().get(groupIndex);
1011 } while (groupIndex < av.getAlignment().getGroups().size());
1018 * Highlights search results in the visible region by rendering as white text
1019 * on a black background. Any previous highlighting is removed. Answers true
1020 * if any highlight was left on the visible alignment (so status bar should be
1021 * set to match), else false.
1023 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1024 * alignment had to be scrolled to show the highlighted region, then it should
1025 * be fully redrawn, otherwise a fast paint can be performed. This argument
1026 * could be removed if fast paint of scrolled wrapped alignment is coded in
1027 * future (JAL-2609).
1030 * @param noFastPaint
1033 public boolean highlightSearchResults(SearchResultsI results,
1034 boolean noFastPaint)
1040 boolean wrapped = av.getWrapAlignment();
1044 fastPaint = !noFastPaint;
1045 fastpainting = fastPaint;
1050 * to avoid redrawing the whole visible region, we instead
1051 * redraw just the minimal regions to remove previous highlights
1054 SearchResultsI previous = av.getSearchResults();
1055 av.setSearchResults(results);
1056 boolean redrawn = false;
1057 boolean drawn = false;
1060 redrawn = drawMappedPositionsWrapped(previous);
1061 drawn = drawMappedPositionsWrapped(results);
1066 redrawn = drawMappedPositions(previous);
1067 drawn = drawMappedPositions(results);
1072 * if highlights were either removed or added, repaint
1080 * return true only if highlights were added
1086 fastpainting = false;
1091 * Redraws the minimal rectangle in the visible region (if any) that includes
1092 * mapped positions of the given search results. Whether or not positions are
1093 * highlighted depends on the SearchResults set on the Viewport. This allows
1094 * this method to be called to either clear or set highlighting. Answers true
1095 * if any positions were drawn (in which case a repaint is still required),
1101 protected boolean drawMappedPositions(SearchResultsI results)
1103 if (results == null)
1109 * calculate the minimal rectangle to redraw that
1110 * includes both new and existing search results
1112 int firstSeq = Integer.MAX_VALUE;
1114 int firstCol = Integer.MAX_VALUE;
1116 boolean matchFound = false;
1118 ViewportRanges ranges = av.getRanges();
1119 int firstVisibleColumn = ranges.getStartRes();
1120 int lastVisibleColumn = ranges.getEndRes();
1121 AlignmentI alignment = av.getAlignment();
1122 if (av.hasHiddenColumns())
1124 firstVisibleColumn = alignment.getHiddenColumns()
1125 .adjustForHiddenColumns(firstVisibleColumn);
1126 lastVisibleColumn = alignment.getHiddenColumns()
1127 .adjustForHiddenColumns(lastVisibleColumn);
1130 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1131 .getEndSeq(); seqNo++)
1133 SequenceI seq = alignment.getSequenceAt(seqNo);
1135 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1137 if (visibleResults != null)
1139 for (int i = 0; i < visibleResults.length - 1; i += 2)
1141 int firstMatchedColumn = visibleResults[i];
1142 int lastMatchedColumn = visibleResults[i + 1];
1143 if (firstMatchedColumn <= lastVisibleColumn
1144 && lastMatchedColumn >= firstVisibleColumn)
1147 * found a search results match in the visible region -
1148 * remember the first and last sequence matched, and the first
1149 * and last visible columns in the matched positions
1152 firstSeq = Math.min(firstSeq, seqNo);
1153 lastSeq = Math.max(lastSeq, seqNo);
1154 firstMatchedColumn = Math.max(firstMatchedColumn,
1155 firstVisibleColumn);
1156 lastMatchedColumn = Math.min(lastMatchedColumn,
1158 firstCol = Math.min(firstCol, firstMatchedColumn);
1159 lastCol = Math.max(lastCol, lastMatchedColumn);
1167 if (av.hasHiddenColumns())
1169 firstCol = alignment.getHiddenColumns()
1170 .findColumnPosition(firstCol);
1171 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1173 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1174 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1175 gg.translate(transX, transY);
1176 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1177 gg.translate(-transX, -transY);
1184 public void propertyChange(PropertyChangeEvent evt)
1186 String eventName = evt.getPropertyName();
1188 // if (av.getWrapAlignment())
1190 // if (eventName.equals(ViewportRanges.STARTRES))
1198 if (eventName.equals(ViewportRanges.STARTRES))
1200 // Make sure we're not trying to draw a panel
1201 // larger than the visible window
1202 ViewportRanges vpRanges = av.getRanges();
1203 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1204 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1205 if (scrollX > range)
1209 else if (scrollX < -range)
1215 // Both scrolling and resizing change viewport ranges: scrolling changes
1216 // both start and end points, but resize only changes end values.
1217 // Here we only want to fastpaint on a scroll, with resize using a normal
1218 // paint, so scroll events are identified as changes to the horizontal or
1219 // vertical start value.
1220 if (eventName.equals(ViewportRanges.STARTRES))
1222 // scroll - startres and endres both change
1223 if (av.getWrapAlignment())
1225 fastPaintWrapped(scrollX);
1226 // fastPaintWrapped(scrollX > 0 ? 1 : -1); // to debug: 1 at a time
1230 fastPaint(scrollX, 0);
1233 else if (eventName.equals(ViewportRanges.STARTSEQ))
1235 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1241 * Does a minimal update of the image for a scroll movement. This method
1242 * handles scroll movements of up to one width of the wrapped alignment (one
1243 * click in the vertical scrollbar). Larger movements (for example after a
1244 * scroll to highlight a mapped position) trigger a full redraw instead.
1247 * number of positions scrolled (right if positive, left if negative)
1249 protected void fastPaintWrapped(int scrollX)
1251 if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
1254 * shift of more than one view width is
1255 * too complicated to handle in this method
1265 * relocate the regions of the alignment that are still visible
1267 shiftWrappedAlignment(-scrollX);
1270 * add new columns (scale above, sequence, annotation)
1271 * - at top left if scrollX < 0
1272 * - at right of last two widths if scrollX > 0
1273 * also West scale top left or East scale bottom right if shown
1277 fastPaintWrappedAddLeft(-scrollX);
1281 fastPaintWrappedAddRight(scrollX);
1288 * Draws the specified number of columns at the 'end' (bottom right) of a
1289 * wrapped alignment view, including scale above and right and annotations if
1290 * shown. Also draws the same number of columns at the right hand end of the
1291 * second last width shown, if the last width is not full height (so cannot
1292 * simply be copied from the graphics image).
1296 protected void fastPaintWrappedAddRight(int columns)
1304 * how many widths are visible? we will be adding
1305 * columns to the last visible width, right hand end
1307 int repeatHeight = getRepeatHeightWrapped();
1308 int canvasHeight = getHeight();
1309 int visibleWidths = canvasHeight / repeatHeight;
1310 int remainder = canvasHeight % repeatHeight;
1311 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1312 boolean lastWidthPartHeight = false;
1313 if (remainder >= (hgap + charHeight))
1316 lastWidthPartHeight = true;
1320 * limit visible widths to max widths of alignment
1322 ViewportRanges ranges = av.getRanges();
1323 int visibleAlignmentWidth = ranges.getVisibleAlignmentWidth();
1324 int viewportWidth = ranges.getViewportWidth();
1325 int maxWidths = visibleAlignmentWidth / viewportWidth;
1326 if (visibleAlignmentWidth % viewportWidth > 0)
1330 visibleWidths = Math.min(visibleWidths, maxWidths);
1331 int widthInColumns = (getWidth() - labelWidthEast - labelWidthWest)
1335 * draw full height alignment in the second last row, last columns, if the
1336 * last row was not full height
1338 if (lastWidthPartHeight)
1340 int widthsAbove = visibleWidths - 2;
1341 int ypos = repeatHeight * widthsAbove + hgap;
1342 int endRes = ranges.getEndRes();
1343 endRes += widthsAbove * viewportWidth;
1344 int startRes = endRes - columns;
1345 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1347 gg.translate(xOffset, 0);
1350 * white fill first to erase annotations
1352 gg.setColor(Color.white);
1353 gg.fillRect(labelWidthWest, ypos,
1354 (endRes - startRes + 1) * charWidth, repeatHeight);
1356 drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
1358 gg.translate(-xOffset, 0);
1362 * y-offset for drawing is height of widths above,
1365 int widthsAbove = visibleWidths - 1;
1366 int ypos = repeatHeight * widthsAbove + hgap;
1367 int endRes = ranges.getEndRes();
1368 endRes += widthsAbove * viewportWidth;
1369 endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
1372 * draw one extra column than strictly needed - this is a (harmless)
1373 * fudge to ensure scale marks get drawn (JAL-2636)
1375 int startRes = endRes - columns;
1378 * x-offset is x-start modulo viewport start residue;
1379 * doesn't include label West (offset is applied in drawWrappedRegion)
1382 int leftEndColumn = ranges.getStartRes() + widthsAbove
1383 * ranges.getViewportWidth();
1384 // startRes = Math.max(startRes - 0, leftEndColumn);
1385 int xOffset = ((startRes - ranges.getStartRes()) % viewportWidth)
1387 gg.translate(xOffset, 0);
1390 * white fill the region to be drawn including scale left or above;
1391 * extend to right hand margin so as to erase scale above when
1392 * scrolling right beyond end of alignment
1394 gg.setColor(Color.white);
1395 int width = getWidth() - labelWidthWest - xOffset;
1396 gg.fillRect(labelWidthWest, ypos - hgap, width, repeatHeight);
1398 gg.setFont(av.getFont());
1399 gg.setColor(Color.black);
1401 drawWrappedRegion(gg, startRes, endRes, canvasHeight, widthInColumns,
1403 gg.translate(-xOffset, 0);
1406 * draw scale right if shown, passing in the start/end columns
1407 * for the whole line, not just the last few columns
1409 if (av.getScaleRightWrapped())
1411 drawVerticalScale(gg, leftEndColumn, endRes, ypos, false);
1416 * Draws the specified number of columns at the 'start' (top left) of a
1417 * wrapped alignment view, including scale above and left and annotations if
1422 protected void fastPaintWrappedAddLeft(int columns)
1424 int startRes = av.getRanges().getStartRes();
1427 * draw one extra column than strictly needed - this is a (harmless)
1428 * fudge to ensure scale marks get drawn (JAL-2636)
1430 int endx = startRes + columns;
1434 * white fill the region to be drawn including scale left or above
1436 gg.setColor(Color.white);
1437 int height = getRepeatHeightWrapped();
1438 gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
1439 ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1441 gg.setFont(av.getFont());
1442 gg.setColor(Color.black);
1444 if (av.getScaleLeftWrapped())
1446 drawVerticalScale(gg, startRes, endx, ypos, true);
1449 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1451 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1455 * Shifts the visible alignment by the specified number of columns - left if
1456 * negative, right if positive. Includes scale above, left or right and
1457 * annotations (if shown). Does not draw newly visible columns.
1461 protected void shiftWrappedAlignment(int positions)
1468 int repeatHeight = getRepeatHeightWrapped();
1469 ViewportRanges ranges = av.getRanges();
1470 int xMax = ranges.getVisibleAlignmentWidth();
1471 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1473 int canvasHeight = getHeight();
1474 int visibleWidths = canvasHeight / repeatHeight;
1475 if (canvasHeight % repeatHeight > 0)
1479 int viewportWidth = ranges.getViewportWidth();
1480 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1482 int remainder = canvasHeight % repeatHeight;
1483 if (remainder >= (hgap + charHeight))
1491 * shift right (after scroll left)
1492 * for each wrapped width (starting with the last), copy (width-positions)
1493 * columns from the left margin to the right margin, and copy positions
1494 * columns from the right margin of the row above (if any) to the
1495 * left margin of the current row
1497 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1500 * get y-offset of last wrapped width
1502 int y = canvasHeight / repeatHeight * repeatHeight;
1503 int copyFromLeftStart = labelWidthWest;
1504 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1508 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1509 positions * charWidth, 0);
1512 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1513 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1516 if (av.getScaleLeftWrapped())
1518 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1521 if (av.getScaleRightWrapped())
1523 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1528 xpos -= viewportWidth;
1534 * shift left (after scroll right)
1535 * for each wrapped width (starting with the first), copy (width-positions)
1536 * columns from the right margin to the left margin, and copy positions
1537 * columns from the left margin of the row below (if any) to the
1538 * right margin of the current row
1540 int xpos = ranges.getStartRes();
1542 int copyFromRightStart = labelWidthWest - positions * charWidth;
1544 while (y < canvasHeight)
1546 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1547 positions * charWidth, 0);
1548 if (y + repeatHeight < canvasHeight - repeatHeight
1549 && (xpos + viewportWidth <= xMax))
1551 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1552 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1555 if (av.getScaleLeftWrapped())
1557 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1559 if (av.getScaleRightWrapped())
1561 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1565 xpos += viewportWidth;
1571 * Redraws any positions in the search results in the visible region of a
1572 * wrapped alignment. Any highlights are drawn depending on the search results
1573 * set on the Viewport, not the <code>results</code> argument. This allows
1574 * this method to be called either to clear highlights (passing the previous
1575 * search results), or to draw new highlights.
1580 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1582 if (results == null)
1587 boolean matchFound = false;
1589 int wrappedWidth = av.getWrappedWidth();
1590 int wrappedHeight = getRepeatHeightWrapped();
1592 ViewportRanges ranges = av.getRanges();
1593 int canvasHeight = getHeight();
1594 int repeats = canvasHeight / wrappedHeight;
1595 if (canvasHeight / wrappedHeight > 0)
1600 int firstVisibleColumn = ranges.getStartRes();
1601 int lastVisibleColumn = ranges.getStartRes() + repeats
1602 * ranges.getViewportWidth() - 1;
1604 AlignmentI alignment = av.getAlignment();
1605 if (av.hasHiddenColumns())
1607 firstVisibleColumn = alignment.getHiddenColumns()
1608 .adjustForHiddenColumns(firstVisibleColumn);
1609 lastVisibleColumn = alignment.getHiddenColumns()
1610 .adjustForHiddenColumns(lastVisibleColumn);
1613 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1615 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1616 .getEndSeq(); seqNo++)
1618 SequenceI seq = alignment.getSequenceAt(seqNo);
1620 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1622 if (visibleResults != null)
1624 for (int i = 0; i < visibleResults.length - 1; i += 2)
1626 int firstMatchedColumn = visibleResults[i];
1627 int lastMatchedColumn = visibleResults[i + 1];
1628 if (firstMatchedColumn <= lastVisibleColumn
1629 && lastMatchedColumn >= firstVisibleColumn)
1632 * found a search results match in the visible region
1634 firstMatchedColumn = Math.max(firstMatchedColumn,
1635 firstVisibleColumn);
1636 lastMatchedColumn = Math.min(lastMatchedColumn,
1640 * draw each mapped position separately (as contiguous positions may
1641 * wrap across lines)
1643 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1645 int displayColumn = mappedPos;
1646 if (av.hasHiddenColumns())
1648 displayColumn = alignment.getHiddenColumns()
1649 .findColumnPosition(displayColumn);
1653 * transX: offset from left edge of canvas to residue position
1655 int transX = labelWidthWest
1656 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1657 * av.getCharWidth();
1660 * transY: offset from top edge of canvas to residue position
1662 int transY = gapHeight;
1663 transY += (displayColumn - ranges.getStartRes())
1664 / wrappedWidth * wrappedHeight;
1665 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1668 * yOffset is from graphics origin to start of visible region
1670 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1671 if (transY < getHeight())
1674 gg.translate(transX, transY);
1675 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1677 gg.translate(-transX, -transY);
1689 * Answers the height in pixels of a repeating section of the wrapped
1690 * alignment, including space above, scale above if shown, sequences, and
1691 * annotation panel if shown
1695 protected int getRepeatHeightWrapped()
1697 // gap (and maybe scale) above
1698 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1701 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1703 // add annotations panel height if shown
1704 repeatHeight += getAnnotationHeight();
1706 return repeatHeight;