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();
133 private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
136 for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
139 int mpos = mark.column; // (i - startx - 1)
144 String mstring = mark.text;
150 g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
152 g.drawLine((mpos * charWidth) + (charWidth / 2), (ypos + 2)
153 - (charHeight / 2), (mpos * charWidth) + (charWidth / 2),
160 * Draw the scale to the left or right of a wrapped alignment
164 * first column of wrapped width (0.. excluding any hidden columns)
166 * last column of wrapped width (0.. excluding any hidden columns)
168 * vertical offset at which to begin the scale
170 * if true, scale is left of residues, if false, scale is right
172 void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
177 if (av.hasHiddenColumns())
179 HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
180 startx = hiddenColumns.adjustForHiddenColumns(startx);
181 endx = hiddenColumns.adjustForHiddenColumns(endx);
183 FontMetrics fm = getFontMetrics(av.getFont());
185 for (int i = 0; i < av.getAlignment().getHeight(); i++)
187 SequenceI seq = av.getAlignment().getSequenceAt(i);
190 * find sequence position of first non-gapped position -
191 * to the right if scale left, to the left if scale right
193 int index = left ? startx : endx;
195 while (index >= startx && index <= endx)
197 if (!Comparison.isGap(seq.getCharAt(index)))
199 value = seq.findPosition(index);
215 * draw scale value, justified, with half a character width
216 * separation from the sequence data
218 int justify = fm.stringWidth(String.valueOf(value)) + charWidth;
219 int xpos = left ? labelWidthWest - justify + charWidth / 2
220 : getWidth() - justify - charWidth / 2;
222 g.drawString(String.valueOf(value), xpos, (ypos + (i * charHeight))
229 * need to make this thread safe move alignment rendering in response to
235 * shift up or down in repaint
237 public void fastPaint(int horizontal, int vertical)
239 if (fastpainting || gg == null)
247 ViewportRanges ranges = av.getRanges();
248 int startRes = ranges.getStartRes();
249 int endRes = ranges.getEndRes();
250 int startSeq = ranges.getStartSeq();
251 int endSeq = ranges.getEndSeq();
255 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
256 imgHeight, -horizontal * charWidth, -vertical * charHeight);
258 if (horizontal > 0) // scrollbar pulled right, image to the left
260 transX = (endRes - startRes - horizontal) * charWidth;
261 startRes = endRes - horizontal;
263 else if (horizontal < 0)
265 endRes = startRes - horizontal;
267 else if (vertical > 0) // scroll down
269 startSeq = endSeq - vertical;
271 if (startSeq < ranges.getStartSeq())
272 { // ie scrolling too fast, more than a page at a time
273 startSeq = ranges.getStartSeq();
277 transY = imgHeight - ((vertical + 1) * charHeight);
280 else if (vertical < 0)
282 endSeq = startSeq - vertical;
284 if (endSeq > ranges.getEndSeq())
286 endSeq = ranges.getEndSeq();
290 gg.translate(transX, transY);
291 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
292 gg.translate(-transX, -transY);
295 fastpainting = false;
299 public void paintComponent(Graphics g)
302 BufferedImage lcimg = img; // take reference since other threads may null
303 // img and call later.
304 super.paintComponent(g);
308 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
309 .getClipBounds().height)))
311 g.drawImage(lcimg, 0, 0, this);
316 // this draws the whole of the alignment
317 imgWidth = getWidth();
318 imgHeight = getHeight();
320 imgWidth -= (imgWidth % charWidth);
321 imgHeight -= (imgHeight % charHeight);
323 if ((imgWidth < 1) || (imgHeight < 1))
328 if (lcimg == null || imgWidth != lcimg.getWidth()
329 || imgHeight != lcimg.getHeight())
333 lcimg = img = new BufferedImage(imgWidth, imgHeight,
334 BufferedImage.TYPE_INT_RGB);
335 gg = (Graphics2D) img.getGraphics();
336 gg.setFont(av.getFont());
337 } catch (OutOfMemoryError er)
340 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
341 new OOMWarning("Creating alignment image for display", er);
349 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
350 RenderingHints.VALUE_ANTIALIAS_ON);
353 gg.setColor(Color.white);
354 gg.fillRect(0, 0, imgWidth, imgHeight);
356 ViewportRanges ranges = av.getRanges();
357 if (av.getWrapAlignment())
359 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
363 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
364 ranges.getStartSeq(), ranges.getEndSeq(), 0);
367 g.drawImage(lcimg, 0, 0, this);
372 * Returns the visible width of the canvas in residues, after allowing for
373 * East or West scales (if shown)
376 * the width in pixels (possibly including scales)
380 public int getWrappedCanvasWidth(int canvasWidth)
382 FontMetrics fm = getFontMetrics(av.getFont());
387 if (av.getScaleRightWrapped())
389 labelWidthEast = getLabelWidth(fm);
392 if (av.getScaleLeftWrapped())
394 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
398 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
402 * Returns a pixel width suitable for showing the largest sequence coordinate
403 * (end position) in the alignment. Returns 2 plus the number of decimal
404 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
409 protected int getLabelWidth(FontMetrics fm)
412 * find the biggest sequence end position we need to show
413 * (note this is not necessarily the sequence length)
416 AlignmentI alignment = av.getAlignment();
417 for (int i = 0; i < alignment.getHeight(); i++)
419 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
423 for (int i = maxWidth; i > 0; i /= 10)
428 return fm.stringWidth(ZEROS.substring(0, length));
438 * @param canvasHeight
443 public void drawWrappedPanel(Graphics g, int canvasWidth,
444 int canvasHeight, int startRes)
448 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
450 FontMetrics fm = getFontMetrics(av.getFont());
451 labelWidth = getLabelWidth(fm);
454 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
455 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
457 int hgap = charHeight;
458 if (av.getScaleAboveWrapped())
463 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
465 av.setWrappedWidth(cWidth);
467 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
470 int maxwidth = av.getAlignment().getWidth();
472 if (av.hasHiddenColumns())
474 maxwidth = av.getAlignment().getHiddenColumns()
475 .findColumnPosition(maxwidth);
478 int annotationHeight = getAnnotationHeight();
479 int sequencesHeight = av.getAlignment().getHeight() * charHeight;
481 while ((ypos <= canvasHeight) && (startRes < maxwidth))
483 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
485 ypos += sequencesHeight + annotationHeight + hgap;
494 * @param canvasHeight
499 protected void drawWrappedWidth(Graphics g, int startRes,
500 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
503 endx = startRes + canvasWidth - 1;
510 g.setFont(av.getFont());
511 g.setColor(Color.black);
513 if (av.getScaleLeftWrapped())
515 drawVerticalScale(g, startRes, endx, ypos, true);
518 if (av.getScaleRightWrapped())
520 drawVerticalScale(g, startRes, endx, ypos, false);
523 g.translate(labelWidthWest, 0);
525 if (av.getScaleAboveWrapped())
527 drawNorthScale(g, startRes, endx, ypos);
530 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
532 g.setColor(Color.blue);
533 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
534 List<Integer> positions = hidden.findHiddenRegionPositions();
535 for (int pos : positions)
537 int res = pos - startRes;
539 if (res < 0 || res > endx - startRes)
544 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
545 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
546 ypos - (charHeight / 2), ypos - (charHeight / 2),
547 ypos - (charHeight / 2) + 8 }, 3);
552 // When printing we have an extra clipped region,
553 // the Printable page which we need to account for here
554 Shape clip = g.getClip();
558 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
562 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
563 (int) clip.getBounds().getHeight());
566 drawPanel(g, startRes, endx, 0, av.getAlignment().getHeight() - 1, ypos);
568 int cHeight = av.getAlignment().getHeight() * charHeight;
570 if (av.isShowAnnotation())
572 g.translate(0, cHeight + ypos + 3);
573 if (annotations == null)
575 annotations = new AnnotationPanel(av);
578 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
580 g.translate(0, -cHeight - ypos - 3);
583 g.translate(-labelWidthWest, 0);
586 AnnotationPanel annotations;
588 int getAnnotationHeight()
590 if (!av.isShowAnnotation())
595 if (annotations == null)
597 annotations = new AnnotationPanel(av);
600 return annotations.adjustPanelHeight();
604 * Draws the visible region of the alignment on the graphics context. If there
605 * are hidden column markers in the visible region, then each sub-region
606 * between the markers is drawn separately, followed by the hidden column
611 * offset of the first column in the visible region (0..)
613 * offset of the last column in the visible region (0..)
615 * offset of the first sequence in the visible region (0..)
617 * offset of the last sequence in the visible region (0..)
619 * vertical offset at which to draw (for wrapped alignments)
621 public void drawPanel(Graphics g1, final int startRes, final int endRes,
622 final int startSeq, final int endSeq, final int yOffset)
625 if (!av.hasHiddenColumns())
627 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
632 final int screenYMax = endRes - startRes;
633 int blockStart = startRes;
634 int blockEnd = endRes;
636 for (int[] region : av.getAlignment().getHiddenColumns()
637 .getHiddenColumnsCopy())
639 int hideStart = region[0];
640 int hideEnd = region[1];
642 if (hideStart <= blockStart)
644 blockStart += (hideEnd - hideStart) + 1;
649 * draw up to just before the next hidden region, or the end of
650 * the visible region, whichever comes first
652 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
655 g1.translate(screenY * charWidth, 0);
657 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
660 * draw the downline of the hidden column marker (ScalePanel draws the
661 * triangle on top) if we reached it
663 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
665 g1.setColor(Color.blue);
667 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
668 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
669 (endSeq - startSeq + 1) * charHeight + yOffset);
672 g1.translate(-screenY * charWidth, 0);
673 screenY += blockEnd - blockStart + 1;
674 blockStart = hideEnd + 1;
676 if (screenY > screenYMax)
678 // already rendered last block
683 if (screenY <= screenYMax)
685 // remaining visible region to render
686 blockEnd = blockStart + screenYMax - screenY;
687 g1.translate(screenY * charWidth, 0);
688 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
690 g1.translate(-screenY * charWidth, 0);
697 * Draws a region of the visible alignment
701 * offset of the first column in the visible region (0..)
703 * offset of the last column in the visible region (0..)
705 * offset of the first sequence in the visible region (0..)
707 * offset of the last sequence in the visible region (0..)
709 * vertical offset at which to draw (for wrapped alignments)
711 private void draw(Graphics g, int startRes, int endRes, int startSeq,
712 int endSeq, int offset)
714 g.setFont(av.getFont());
715 sr.prepare(g, av.isRenderGaps());
719 // / First draw the sequences
720 // ///////////////////////////
721 for (int i = startSeq; i <= endSeq; i++)
723 nextSeq = av.getAlignment().getSequenceAt(i);
726 // occasionally, a race condition occurs such that the alignment row is
730 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
731 startRes, endRes, offset + ((i - startSeq) * charHeight));
733 if (av.isShowSequenceFeatures())
735 fr.drawSequence(g, nextSeq, startRes, endRes, offset
736 + ((i - startSeq) * charHeight), false);
740 * highlight search Results once sequence has been drawn
742 if (av.hasSearchResults())
744 SearchResultsI searchResults = av.getSearchResults();
745 int[] visibleResults = searchResults.getResults(nextSeq,
747 if (visibleResults != null)
749 for (int r = 0; r < visibleResults.length; r += 2)
751 sr.drawHighlightedText(nextSeq, visibleResults[r],
752 visibleResults[r + 1], (visibleResults[r] - startRes)
754 + ((i - startSeq) * charHeight));
759 if (av.cursorMode && cursorY == i && cursorX >= startRes
760 && cursorX <= endRes)
762 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
763 offset + ((i - startSeq) * charHeight));
767 if (av.getSelectionGroup() != null
768 || av.getAlignment().getGroups().size() > 0)
770 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
775 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
776 int startSeq, int endSeq, int offset)
778 Graphics2D g = (Graphics2D) g1;
780 // ///////////////////////////////////
781 // Now outline any areas if necessary
782 // ///////////////////////////////////
783 SequenceGroup group = av.getSelectionGroup();
789 int visWidth = (endRes - startRes + 1) * charWidth;
791 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
793 group = av.getAlignment().getGroups().get(0);
803 boolean inGroup = false;
807 for (i = startSeq; i <= endSeq; i++)
809 sx = (group.getStartRes() - startRes) * charWidth;
810 sy = offset + ((i - startSeq) * charHeight);
811 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
813 if (sx + ex < 0 || sx > visWidth)
818 if ((sx <= (endRes - startRes) * charWidth)
819 && group.getSequences(null).contains(
820 av.getAlignment().getSequenceAt(i)))
823 && !group.getSequences(null).contains(
824 av.getAlignment().getSequenceAt(i + 1)))
826 bottom = sy + charHeight;
831 if (((top == -1) && (i == 0))
832 || !group.getSequences(null).contains(
833 av.getAlignment().getSequenceAt(i - 1)))
841 if (group == av.getSelectionGroup())
843 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
844 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
846 g.setColor(Color.RED);
850 g.setStroke(new BasicStroke());
851 g.setColor(group.getOutlineColour());
859 if (sx >= 0 && sx < visWidth)
861 g.drawLine(sx, oldY, sx, sy);
864 if (sx + ex < visWidth)
866 g.drawLine(sx + ex, oldY, sx + ex, sy);
875 if (sx + ex > visWidth)
880 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
882 ex = (endRes - startRes + 1) * charWidth;
887 g.drawLine(sx, top, sx + ex, top);
893 g.drawLine(sx, bottom, sx + ex, bottom);
904 sy = offset + ((i - startSeq) * charHeight);
905 if (sx >= 0 && sx < visWidth)
907 g.drawLine(sx, oldY, sx, sy);
910 if (sx + ex < visWidth)
912 g.drawLine(sx + ex, oldY, sx + ex, sy);
921 if (sx + ex > visWidth)
925 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
927 ex = (endRes - startRes + 1) * charWidth;
932 g.drawLine(sx, top, sx + ex, top);
938 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
947 g.setStroke(new BasicStroke());
949 if (groupIndex >= av.getAlignment().getGroups().size())
954 group = av.getAlignment().getGroups().get(groupIndex);
956 } while (groupIndex < av.getAlignment().getGroups().size());
963 * Highlights search results in the visible region by rendering as white text
964 * on a black background. Any previous highlighting is removed. Answers true
965 * if any highlight was left on the visible alignment (so status bar should be
966 * set to match), else false.
968 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
969 * alignment had to be scrolled to show the highlighted region, then it should
970 * be fully redrawn, otherwise a fast paint can be performed. This argument
971 * could be removed if fast paint of scrolled wrapped alignment is coded in
978 public boolean highlightSearchResults(SearchResultsI results,
985 boolean wrapped = av.getWrapAlignment();
989 fastPaint = !noFastPaint;
990 fastpainting = fastPaint;
995 * to avoid redrawing the whole visible region, we instead
996 * redraw just the minimal regions to remove previous highlights
999 SearchResultsI previous = av.getSearchResults();
1000 av.setSearchResults(results);
1001 boolean redrawn = false;
1002 boolean drawn = false;
1005 redrawn = drawMappedPositionsWrapped(previous);
1006 drawn = drawMappedPositionsWrapped(results);
1011 redrawn = drawMappedPositions(previous);
1012 drawn = drawMappedPositions(results);
1017 * if highlights were either removed or added, repaint
1025 * return true only if highlights were added
1031 fastpainting = false;
1036 * Redraws the minimal rectangle in the visible region (if any) that includes
1037 * mapped positions of the given search results. Whether or not positions are
1038 * highlighted depends on the SearchResults set on the Viewport. This allows
1039 * this method to be called to either clear or set highlighting. Answers true
1040 * if any positions were drawn (in which case a repaint is still required),
1046 protected boolean drawMappedPositions(SearchResultsI results)
1048 if (results == null)
1054 * calculate the minimal rectangle to redraw that
1055 * includes both new and existing search results
1057 int firstSeq = Integer.MAX_VALUE;
1059 int firstCol = Integer.MAX_VALUE;
1061 boolean matchFound = false;
1063 ViewportRanges ranges = av.getRanges();
1064 int firstVisibleColumn = ranges.getStartRes();
1065 int lastVisibleColumn = ranges.getEndRes();
1066 AlignmentI alignment = av.getAlignment();
1067 if (av.hasHiddenColumns())
1069 firstVisibleColumn = alignment.getHiddenColumns()
1070 .adjustForHiddenColumns(firstVisibleColumn);
1071 lastVisibleColumn = alignment.getHiddenColumns()
1072 .adjustForHiddenColumns(lastVisibleColumn);
1075 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1076 .getEndSeq(); seqNo++)
1078 SequenceI seq = alignment.getSequenceAt(seqNo);
1080 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1082 if (visibleResults != null)
1084 for (int i = 0; i < visibleResults.length - 1; i += 2)
1086 int firstMatchedColumn = visibleResults[i];
1087 int lastMatchedColumn = visibleResults[i + 1];
1088 if (firstMatchedColumn <= lastVisibleColumn
1089 && lastMatchedColumn >= firstVisibleColumn)
1092 * found a search results match in the visible region -
1093 * remember the first and last sequence matched, and the first
1094 * and last visible columns in the matched positions
1097 firstSeq = Math.min(firstSeq, seqNo);
1098 lastSeq = Math.max(lastSeq, seqNo);
1099 firstMatchedColumn = Math.max(firstMatchedColumn,
1100 firstVisibleColumn);
1101 lastMatchedColumn = Math.min(lastMatchedColumn,
1103 firstCol = Math.min(firstCol, firstMatchedColumn);
1104 lastCol = Math.max(lastCol, lastMatchedColumn);
1112 if (av.hasHiddenColumns())
1114 firstCol = alignment.getHiddenColumns()
1115 .findColumnPosition(firstCol);
1116 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1118 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1119 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1120 gg.translate(transX, transY);
1121 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1122 gg.translate(-transX, -transY);
1129 public void propertyChange(PropertyChangeEvent evt)
1131 String eventName = evt.getPropertyName();
1133 if (true/*!av.getWrapAlignment()*/)
1136 if (eventName.equals(ViewportRanges.STARTRES))
1138 // Make sure we're not trying to draw a panel
1139 // larger than the visible window
1140 ViewportRanges vpRanges = av.getRanges();
1141 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1142 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1143 if (scrollX > range)
1147 else if (scrollX < -range)
1153 // Both scrolling and resizing change viewport ranges: scrolling changes
1154 // both start and end points, but resize only changes end values.
1155 // Here we only want to fastpaint on a scroll, with resize using a normal
1156 // paint, so scroll events are identified as changes to the horizontal or
1157 // vertical start value.
1158 if (eventName.equals(ViewportRanges.STARTRES))
1160 // scroll - startres and endres both change
1161 if (av.getWrapAlignment())
1163 fastPaintWrapped(scrollX);
1167 fastPaint(scrollX, 0);
1170 else if (eventName.equals(ViewportRanges.STARTSEQ))
1173 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1179 * Does a minimal update of the image for a scroll movement. This method
1180 * handles scroll movements of up to one width of the wrapped alignment (one
1181 * click in the vertical scrollbar). Larger movements (for example after a
1182 * scroll to highlight a mapped position) trigger a full redraw instead.
1185 * number of positions scrolled (right if positive, left if negative)
1187 protected void fastPaintWrapped(int scrollX)
1189 if (Math.abs(scrollX) > 0 /*av.getRanges().getViewportWidth()*/)
1192 * shift of more than one view width is too much
1193 * to handle in this method
1201 shiftWrappedAlignment(-scrollX);
1203 // add new columns (scale above, sequence, annotation)
1204 // at top left if scrollX < 0 or bottom right if scrollX > 0
1210 * Shifts the visible alignment by the specified number of columns - left if
1211 * negative, right if positive. Includes scale above, left or right and
1212 * annotations (if shown). Does not draw newly visible columns.
1216 protected void shiftWrappedAlignment(int positions)
1223 int repeatHeight = getRepeatHeightWrapped();
1224 ViewportRanges ranges = av.getRanges();
1225 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1227 int visibleWidths = getHeight() / repeatHeight;
1228 if (getHeight() % repeatHeight > 0)
1232 int viewportWidth = ranges.getViewportWidth();
1237 * shift right (after scroll left)
1238 * for each wrapped width (starting with the last), copy (width-positions)
1239 * columns from the left margin to the right margin, and copy positions
1240 * columns from the right margin of the row above (if any) to the
1241 * left margin of the current row
1243 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1246 * get y-offset of last wrapped width
1248 int y = getHeight() / repeatHeight * repeatHeight;
1249 int copyFromLeftStart = labelWidthWest;
1250 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1254 // todo limit repeatHeight for a last part height width
1255 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1256 positions * charWidth, 0);
1259 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1260 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1263 if (av.getScaleLeftWrapped())
1265 // drawVerticalScale(gg, xpos, xpos + viewportWidth, y, true);
1267 if (av.getScaleRightWrapped())
1269 // drawVerticalScale(gg, xpos, xpos + viewportWidth, y, false);
1273 xpos -= viewportWidth;
1279 * shift left (after scroll right)
1280 * for each wrapped width (starting with the first), copy (width-positions)
1281 * columns from the right margin to the left margin, and copy positions
1282 * columns from the left margin of the row below (if any) to the
1283 * right margin of the current row
1285 int xpos = ranges.getStartRes();
1287 int copyFromRightStart = labelWidthWest - positions * charWidth;
1289 while (y < getHeight())
1291 // todo limit repeatHeight for a last part height width
1292 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1293 positions * charWidth, 0);
1294 if (y + repeatHeight < getHeight())
1296 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1297 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1300 if (av.getScaleLeftWrapped())
1302 // drawVerticalScale(gg, xpos, xpos + viewportWidth, y, true);
1304 if (av.getScaleRightWrapped())
1306 // drawVerticalScale(gg, xpos, xpos + viewportWidth, y, false);
1310 xpos += ranges.getViewportWidth();
1316 * Redraws any positions in the search results in the visible region of a
1317 * wrapped alignment. Any highlights are drawn depending on the search results
1318 * set on the Viewport, not the <code>results</code> argument. This allows
1319 * this method to be called either to clear highlights (passing the previous
1320 * search results), or to draw new highlights.
1325 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1327 if (results == null)
1332 boolean matchFound = false;
1334 int wrappedWidth = av.getWrappedWidth();
1335 int wrappedHeight = getRepeatHeightWrapped();
1337 ViewportRanges ranges = av.getRanges();
1338 int canvasHeight = getHeight();
1339 int repeats = canvasHeight / wrappedHeight;
1340 if (canvasHeight / wrappedHeight > 0)
1345 int firstVisibleColumn = ranges.getStartRes();
1346 int lastVisibleColumn = ranges.getStartRes() + repeats
1347 * ranges.getViewportWidth() - 1;
1349 AlignmentI alignment = av.getAlignment();
1350 if (av.hasHiddenColumns())
1352 firstVisibleColumn = alignment.getHiddenColumns()
1353 .adjustForHiddenColumns(firstVisibleColumn);
1354 lastVisibleColumn = alignment.getHiddenColumns()
1355 .adjustForHiddenColumns(lastVisibleColumn);
1358 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1360 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1361 .getEndSeq(); seqNo++)
1363 SequenceI seq = alignment.getSequenceAt(seqNo);
1365 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1367 if (visibleResults != null)
1369 for (int i = 0; i < visibleResults.length - 1; i += 2)
1371 int firstMatchedColumn = visibleResults[i];
1372 int lastMatchedColumn = visibleResults[i + 1];
1373 if (firstMatchedColumn <= lastVisibleColumn
1374 && lastMatchedColumn >= firstVisibleColumn)
1377 * found a search results match in the visible region
1379 firstMatchedColumn = Math.max(firstMatchedColumn,
1380 firstVisibleColumn);
1381 lastMatchedColumn = Math.min(lastMatchedColumn,
1385 * draw each mapped position separately (as contiguous positions may
1386 * wrap across lines)
1388 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1390 int displayColumn = mappedPos;
1391 if (av.hasHiddenColumns())
1393 displayColumn = alignment.getHiddenColumns()
1394 .findColumnPosition(displayColumn);
1398 * transX: offset from left edge of canvas to residue position
1400 int transX = labelWidthWest
1401 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1402 * av.getCharWidth();
1405 * transY: offset from top edge of canvas to residue position
1407 int transY = gapHeight;
1408 transY += (displayColumn - ranges.getStartRes())
1409 / wrappedWidth * wrappedHeight;
1410 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1413 * yOffset is from graphics origin to start of visible region
1415 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1416 if (transY < getHeight())
1419 gg.translate(transX, transY);
1420 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1422 gg.translate(-transX, -transY);
1434 * Answers the height in pixels of a repeating section of the wrapped
1435 * alignment, including space above, scale above if shown, sequences, and
1436 * annotation panel if shown
1440 protected int getRepeatHeightWrapped()
1442 // gap (and maybe scale) above
1443 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1446 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1448 // add annotations panel height if shown
1449 repeatHeight += getAnnotationHeight();
1451 return repeatHeight;