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 String valueAsString = String.valueOf(value);
219 int justify = fm.stringWidth(valueAsString) + charWidth;
220 int xpos = left ? labelWidthWest - justify + charWidth / 2
221 : getWidth() - justify - charWidth / 2;
223 g.setColor(Color.white);
224 int y = (ypos + (i * charHeight)) - (charHeight / 5);
225 g.fillRect(xpos, y, justify, charHeight);
226 g.setColor(Color.black);
227 g.drawString(valueAsString, xpos, y);
233 * need to make this thread safe move alignment rendering in response to
239 * shift up or down in repaint
241 public void fastPaint(int horizontal, int vertical)
243 if (fastpainting || gg == null)
251 ViewportRanges ranges = av.getRanges();
252 int startRes = ranges.getStartRes();
253 int endRes = ranges.getEndRes();
254 int startSeq = ranges.getStartSeq();
255 int endSeq = ranges.getEndSeq();
259 gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
260 imgHeight, -horizontal * charWidth, -vertical * charHeight);
262 if (horizontal > 0) // scrollbar pulled right, image to the left
264 transX = (endRes - startRes - horizontal) * charWidth;
265 startRes = endRes - horizontal;
267 else if (horizontal < 0)
269 endRes = startRes - horizontal;
271 else if (vertical > 0) // scroll down
273 startSeq = endSeq - vertical;
275 if (startSeq < ranges.getStartSeq())
276 { // ie scrolling too fast, more than a page at a time
277 startSeq = ranges.getStartSeq();
281 transY = imgHeight - ((vertical + 1) * charHeight);
284 else if (vertical < 0)
286 endSeq = startSeq - vertical;
288 if (endSeq > ranges.getEndSeq())
290 endSeq = ranges.getEndSeq();
294 gg.translate(transX, transY);
295 drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
296 gg.translate(-transX, -transY);
299 fastpainting = false;
303 public void paintComponent(Graphics g)
306 BufferedImage lcimg = img; // take reference since other threads may null
307 // img and call later.
308 super.paintComponent(g);
312 || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
313 .getClipBounds().height)))
315 g.drawImage(lcimg, 0, 0, this);
320 // this draws the whole of the alignment
321 imgWidth = getWidth();
322 imgHeight = getHeight();
324 imgWidth -= (imgWidth % charWidth);
325 imgHeight -= (imgHeight % charHeight);
327 if ((imgWidth < 1) || (imgHeight < 1))
332 if (lcimg == null || imgWidth != lcimg.getWidth()
333 || imgHeight != lcimg.getHeight())
337 lcimg = img = new BufferedImage(imgWidth, imgHeight,
338 BufferedImage.TYPE_INT_RGB);
339 gg = (Graphics2D) img.getGraphics();
340 gg.setFont(av.getFont());
341 } catch (OutOfMemoryError er)
344 System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
345 new OOMWarning("Creating alignment image for display", er);
353 gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
354 RenderingHints.VALUE_ANTIALIAS_ON);
357 gg.setColor(Color.white);
358 gg.fillRect(0, 0, imgWidth, imgHeight);
360 ViewportRanges ranges = av.getRanges();
361 if (av.getWrapAlignment())
363 drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
367 drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
368 ranges.getStartSeq(), ranges.getEndSeq(), 0);
371 g.drawImage(lcimg, 0, 0, this);
376 * Returns the visible width of the canvas in residues, after allowing for
377 * East or West scales (if shown)
380 * the width in pixels (possibly including scales)
384 public int getWrappedCanvasWidth(int canvasWidth)
386 FontMetrics fm = getFontMetrics(av.getFont());
391 if (av.getScaleRightWrapped())
393 labelWidthEast = getLabelWidth(fm);
396 if (av.getScaleLeftWrapped())
398 labelWidthWest = labelWidthEast > 0 ? labelWidthEast
402 return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
406 * Returns a pixel width suitable for showing the largest sequence coordinate
407 * (end position) in the alignment. Returns 2 plus the number of decimal
408 * digits to be shown (3 for 1-10, 4 for 11-99 etc).
413 protected int getLabelWidth(FontMetrics fm)
416 * find the biggest sequence end position we need to show
417 * (note this is not necessarily the sequence length)
420 AlignmentI alignment = av.getAlignment();
421 for (int i = 0; i < alignment.getHeight(); i++)
423 maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
427 for (int i = maxWidth; i > 0; i /= 10)
432 return fm.stringWidth(ZEROS.substring(0, length));
442 * @param canvasHeight
447 public void drawWrappedPanel(Graphics g, int canvasWidth,
448 int canvasHeight, int startRes)
452 if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
454 FontMetrics fm = getFontMetrics(av.getFont());
455 labelWidth = getLabelWidth(fm);
458 labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
459 labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
461 int hgap = charHeight;
462 if (av.getScaleAboveWrapped())
467 int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
469 av.setWrappedWidth(cWidth);
471 av.getRanges().setViewportStartAndWidth(startRes, cWidth);
474 int maxwidth = av.getAlignment().getWidth();
476 if (av.hasHiddenColumns())
478 maxwidth = av.getAlignment().getHiddenColumns()
479 .findColumnPosition(maxwidth);
482 int annotationHeight = getAnnotationHeight();
483 int sequencesHeight = av.getAlignment().getHeight() * charHeight;
485 while ((ypos <= canvasHeight) && (startRes < maxwidth))
487 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
489 ypos += sequencesHeight + annotationHeight + hgap;
498 * @param canvasHeight
503 protected void drawWrappedWidth(Graphics g, int startRes,
504 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
507 endx = startRes + canvasWidth - 1;
514 g.setFont(av.getFont());
515 g.setColor(Color.black);
517 if (av.getScaleLeftWrapped())
519 drawVerticalScale(g, startRes, endx, ypos, true);
522 if (av.getScaleRightWrapped())
524 drawVerticalScale(g, startRes, endx, ypos, false);
527 g.translate(labelWidthWest, 0);
529 if (av.getScaleAboveWrapped())
531 drawNorthScale(g, startRes, endx, ypos);
534 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
536 g.setColor(Color.blue);
537 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
538 List<Integer> positions = hidden.findHiddenRegionPositions();
539 for (int pos : positions)
541 int res = pos - startRes;
543 if (res < 0 || res > endx - startRes)
548 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
549 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
550 ypos - (charHeight / 2), ypos - (charHeight / 2),
551 ypos - (charHeight / 2) + 8 }, 3);
556 // When printing we have an extra clipped region,
557 // the Printable page which we need to account for here
558 Shape clip = g.getClip();
562 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
566 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
567 (int) clip.getBounds().getHeight());
570 drawPanel(g, startRes, endx, 0, av.getAlignment().getHeight() - 1, ypos);
572 int cHeight = av.getAlignment().getHeight() * charHeight;
574 if (av.isShowAnnotation())
576 g.translate(0, cHeight + ypos + 3);
577 if (annotations == null)
579 annotations = new AnnotationPanel(av);
582 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
584 g.translate(0, -cHeight - ypos - 3);
587 g.translate(-labelWidthWest, 0);
590 AnnotationPanel annotations;
592 int getAnnotationHeight()
594 if (!av.isShowAnnotation())
599 if (annotations == null)
601 annotations = new AnnotationPanel(av);
604 return annotations.adjustPanelHeight();
608 * Draws the visible region of the alignment on the graphics context. If there
609 * are hidden column markers in the visible region, then each sub-region
610 * between the markers is drawn separately, followed by the hidden column
615 * offset of the first column in the visible region (0..)
617 * offset of the last column in the visible region (0..)
619 * offset of the first sequence in the visible region (0..)
621 * offset of the last sequence in the visible region (0..)
623 * vertical offset at which to draw (for wrapped alignments)
625 public void drawPanel(Graphics g1, final int startRes, final int endRes,
626 final int startSeq, final int endSeq, final int yOffset)
629 if (!av.hasHiddenColumns())
631 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
636 final int screenYMax = endRes - startRes;
637 int blockStart = startRes;
638 int blockEnd = endRes;
640 for (int[] region : av.getAlignment().getHiddenColumns()
641 .getHiddenColumnsCopy())
643 int hideStart = region[0];
644 int hideEnd = region[1];
646 if (hideStart <= blockStart)
648 blockStart += (hideEnd - hideStart) + 1;
653 * draw up to just before the next hidden region, or the end of
654 * the visible region, whichever comes first
656 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
659 g1.translate(screenY * charWidth, 0);
661 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
664 * draw the downline of the hidden column marker (ScalePanel draws the
665 * triangle on top) if we reached it
667 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
669 g1.setColor(Color.blue);
671 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
672 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
673 (endSeq - startSeq + 1) * charHeight + yOffset);
676 g1.translate(-screenY * charWidth, 0);
677 screenY += blockEnd - blockStart + 1;
678 blockStart = hideEnd + 1;
680 if (screenY > screenYMax)
682 // already rendered last block
687 if (screenY <= screenYMax)
689 // remaining visible region to render
690 blockEnd = blockStart + screenYMax - screenY;
691 g1.translate(screenY * charWidth, 0);
692 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
694 g1.translate(-screenY * charWidth, 0);
701 * Draws a region of the visible alignment
705 * offset of the first column in the visible region (0..)
707 * offset of the last column in the visible region (0..)
709 * offset of the first sequence in the visible region (0..)
711 * offset of the last sequence in the visible region (0..)
713 * vertical offset at which to draw (for wrapped alignments)
715 private void draw(Graphics g, int startRes, int endRes, int startSeq,
716 int endSeq, int offset)
718 g.setFont(av.getFont());
719 sr.prepare(g, av.isRenderGaps());
723 // / First draw the sequences
724 // ///////////////////////////
725 for (int i = startSeq; i <= endSeq; i++)
727 nextSeq = av.getAlignment().getSequenceAt(i);
730 // occasionally, a race condition occurs such that the alignment row is
734 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
735 startRes, endRes, offset + ((i - startSeq) * charHeight));
737 if (av.isShowSequenceFeatures())
739 fr.drawSequence(g, nextSeq, startRes, endRes, offset
740 + ((i - startSeq) * charHeight), false);
744 * highlight search Results once sequence has been drawn
746 if (av.hasSearchResults())
748 SearchResultsI searchResults = av.getSearchResults();
749 int[] visibleResults = searchResults.getResults(nextSeq,
751 if (visibleResults != null)
753 for (int r = 0; r < visibleResults.length; r += 2)
755 sr.drawHighlightedText(nextSeq, visibleResults[r],
756 visibleResults[r + 1], (visibleResults[r] - startRes)
758 + ((i - startSeq) * charHeight));
763 if (av.cursorMode && cursorY == i && cursorX >= startRes
764 && cursorX <= endRes)
766 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
767 offset + ((i - startSeq) * charHeight));
771 if (av.getSelectionGroup() != null
772 || av.getAlignment().getGroups().size() > 0)
774 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
779 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
780 int startSeq, int endSeq, int offset)
782 Graphics2D g = (Graphics2D) g1;
784 // ///////////////////////////////////
785 // Now outline any areas if necessary
786 // ///////////////////////////////////
787 SequenceGroup group = av.getSelectionGroup();
793 int visWidth = (endRes - startRes + 1) * charWidth;
795 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
797 group = av.getAlignment().getGroups().get(0);
807 boolean inGroup = false;
811 for (i = startSeq; i <= endSeq; i++)
813 sx = (group.getStartRes() - startRes) * charWidth;
814 sy = offset + ((i - startSeq) * charHeight);
815 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
817 if (sx + ex < 0 || sx > visWidth)
822 if ((sx <= (endRes - startRes) * charWidth)
823 && group.getSequences(null).contains(
824 av.getAlignment().getSequenceAt(i)))
827 && !group.getSequences(null).contains(
828 av.getAlignment().getSequenceAt(i + 1)))
830 bottom = sy + charHeight;
835 if (((top == -1) && (i == 0))
836 || !group.getSequences(null).contains(
837 av.getAlignment().getSequenceAt(i - 1)))
845 if (group == av.getSelectionGroup())
847 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
848 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
850 g.setColor(Color.RED);
854 g.setStroke(new BasicStroke());
855 g.setColor(group.getOutlineColour());
863 if (sx >= 0 && sx < visWidth)
865 g.drawLine(sx, oldY, sx, sy);
868 if (sx + ex < visWidth)
870 g.drawLine(sx + ex, oldY, sx + ex, sy);
879 if (sx + ex > visWidth)
884 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
886 ex = (endRes - startRes + 1) * charWidth;
891 g.drawLine(sx, top, sx + ex, top);
897 g.drawLine(sx, bottom, sx + ex, bottom);
908 sy = offset + ((i - startSeq) * charHeight);
909 if (sx >= 0 && sx < visWidth)
911 g.drawLine(sx, oldY, sx, sy);
914 if (sx + ex < visWidth)
916 g.drawLine(sx + ex, oldY, sx + ex, sy);
925 if (sx + ex > visWidth)
929 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
931 ex = (endRes - startRes + 1) * charWidth;
936 g.drawLine(sx, top, sx + ex, top);
942 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
951 g.setStroke(new BasicStroke());
953 if (groupIndex >= av.getAlignment().getGroups().size())
958 group = av.getAlignment().getGroups().get(groupIndex);
960 } while (groupIndex < av.getAlignment().getGroups().size());
967 * Highlights search results in the visible region by rendering as white text
968 * on a black background. Any previous highlighting is removed. Answers true
969 * if any highlight was left on the visible alignment (so status bar should be
970 * set to match), else false.
972 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
973 * alignment had to be scrolled to show the highlighted region, then it should
974 * be fully redrawn, otherwise a fast paint can be performed. This argument
975 * could be removed if fast paint of scrolled wrapped alignment is coded in
982 public boolean highlightSearchResults(SearchResultsI results,
989 boolean wrapped = av.getWrapAlignment();
993 fastPaint = !noFastPaint;
994 fastpainting = fastPaint;
999 * to avoid redrawing the whole visible region, we instead
1000 * redraw just the minimal regions to remove previous highlights
1003 SearchResultsI previous = av.getSearchResults();
1004 av.setSearchResults(results);
1005 boolean redrawn = false;
1006 boolean drawn = false;
1009 redrawn = drawMappedPositionsWrapped(previous);
1010 drawn = drawMappedPositionsWrapped(results);
1015 redrawn = drawMappedPositions(previous);
1016 drawn = drawMappedPositions(results);
1021 * if highlights were either removed or added, repaint
1029 * return true only if highlights were added
1035 fastpainting = false;
1040 * Redraws the minimal rectangle in the visible region (if any) that includes
1041 * mapped positions of the given search results. Whether or not positions are
1042 * highlighted depends on the SearchResults set on the Viewport. This allows
1043 * this method to be called to either clear or set highlighting. Answers true
1044 * if any positions were drawn (in which case a repaint is still required),
1050 protected boolean drawMappedPositions(SearchResultsI results)
1052 if (results == null)
1058 * calculate the minimal rectangle to redraw that
1059 * includes both new and existing search results
1061 int firstSeq = Integer.MAX_VALUE;
1063 int firstCol = Integer.MAX_VALUE;
1065 boolean matchFound = false;
1067 ViewportRanges ranges = av.getRanges();
1068 int firstVisibleColumn = ranges.getStartRes();
1069 int lastVisibleColumn = ranges.getEndRes();
1070 AlignmentI alignment = av.getAlignment();
1071 if (av.hasHiddenColumns())
1073 firstVisibleColumn = alignment.getHiddenColumns()
1074 .adjustForHiddenColumns(firstVisibleColumn);
1075 lastVisibleColumn = alignment.getHiddenColumns()
1076 .adjustForHiddenColumns(lastVisibleColumn);
1079 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1080 .getEndSeq(); seqNo++)
1082 SequenceI seq = alignment.getSequenceAt(seqNo);
1084 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1086 if (visibleResults != null)
1088 for (int i = 0; i < visibleResults.length - 1; i += 2)
1090 int firstMatchedColumn = visibleResults[i];
1091 int lastMatchedColumn = visibleResults[i + 1];
1092 if (firstMatchedColumn <= lastVisibleColumn
1093 && lastMatchedColumn >= firstVisibleColumn)
1096 * found a search results match in the visible region -
1097 * remember the first and last sequence matched, and the first
1098 * and last visible columns in the matched positions
1101 firstSeq = Math.min(firstSeq, seqNo);
1102 lastSeq = Math.max(lastSeq, seqNo);
1103 firstMatchedColumn = Math.max(firstMatchedColumn,
1104 firstVisibleColumn);
1105 lastMatchedColumn = Math.min(lastMatchedColumn,
1107 firstCol = Math.min(firstCol, firstMatchedColumn);
1108 lastCol = Math.max(lastCol, lastMatchedColumn);
1116 if (av.hasHiddenColumns())
1118 firstCol = alignment.getHiddenColumns()
1119 .findColumnPosition(firstCol);
1120 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1122 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1123 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1124 gg.translate(transX, transY);
1125 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1126 gg.translate(-transX, -transY);
1133 public void propertyChange(PropertyChangeEvent evt)
1135 String eventName = evt.getPropertyName();
1137 if (true/*!av.getWrapAlignment()*/)
1140 if (eventName.equals(ViewportRanges.STARTRES))
1142 // Make sure we're not trying to draw a panel
1143 // larger than the visible window
1144 ViewportRanges vpRanges = av.getRanges();
1145 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1146 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1147 if (scrollX > range)
1151 else if (scrollX < -range)
1157 // Both scrolling and resizing change viewport ranges: scrolling changes
1158 // both start and end points, but resize only changes end values.
1159 // Here we only want to fastpaint on a scroll, with resize using a normal
1160 // paint, so scroll events are identified as changes to the horizontal or
1161 // vertical start value.
1162 if (eventName.equals(ViewportRanges.STARTRES))
1164 // scroll - startres and endres both change
1165 if (av.getWrapAlignment())
1167 fastPaintWrapped(scrollX);
1171 fastPaint(scrollX, 0);
1174 else if (eventName.equals(ViewportRanges.STARTSEQ))
1177 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1183 * Does a minimal update of the image for a scroll movement. This method
1184 * handles scroll movements of up to one width of the wrapped alignment (one
1185 * click in the vertical scrollbar). Larger movements (for example after a
1186 * scroll to highlight a mapped position) trigger a full redraw instead.
1189 * number of positions scrolled (right if positive, left if negative)
1191 protected void fastPaintWrapped(int scrollX)
1193 if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
1196 * shift of more than one view width is too much
1197 * to handle in this method
1205 shiftWrappedAlignment(-scrollX);
1207 // add new columns (scale above, sequence, annotation)
1208 // at top left if scrollX < 0 or bottom right if scrollX > 0
1214 * Shifts the visible alignment by the specified number of columns - left if
1215 * negative, right if positive. Includes scale above, left or right and
1216 * annotations (if shown). Does not draw newly visible columns.
1220 protected void shiftWrappedAlignment(int positions)
1227 int repeatHeight = getRepeatHeightWrapped();
1228 ViewportRanges ranges = av.getRanges();
1229 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1231 int visibleWidths = getHeight() / repeatHeight;
1232 if (getHeight() % repeatHeight > 0)
1236 int viewportWidth = ranges.getViewportWidth();
1237 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1242 * shift right (after scroll left)
1243 * for each wrapped width (starting with the last), copy (width-positions)
1244 * columns from the left margin to the right margin, and copy positions
1245 * columns from the right margin of the row above (if any) to the
1246 * left margin of the current row
1248 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1251 * get y-offset of last wrapped width
1253 int y = getHeight() / repeatHeight * repeatHeight;
1254 int copyFromLeftStart = labelWidthWest;
1255 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1259 // todo limit repeatHeight for a last part height width
1260 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1261 positions * charWidth, 0);
1264 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1265 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1268 if (av.getScaleLeftWrapped())
1270 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1272 if (av.getScaleRightWrapped())
1274 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1278 xpos -= viewportWidth;
1284 * shift left (after scroll right)
1285 * for each wrapped width (starting with the first), copy (width-positions)
1286 * columns from the right margin to the left margin, and copy positions
1287 * columns from the left margin of the row below (if any) to the
1288 * right margin of the current row
1290 int xpos = ranges.getStartRes();
1292 int copyFromRightStart = labelWidthWest - positions * charWidth;
1294 while (y < getHeight())
1296 // todo limit repeatHeight for a last part height width
1297 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1298 positions * charWidth, 0);
1299 if (y + repeatHeight < getHeight())
1301 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1302 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1305 if (av.getScaleLeftWrapped())
1307 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1309 if (av.getScaleRightWrapped())
1311 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1315 xpos += ranges.getViewportWidth();
1321 * Redraws any positions in the search results in the visible region of a
1322 * wrapped alignment. Any highlights are drawn depending on the search results
1323 * set on the Viewport, not the <code>results</code> argument. This allows
1324 * this method to be called either to clear highlights (passing the previous
1325 * search results), or to draw new highlights.
1330 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1332 if (results == null)
1337 boolean matchFound = false;
1339 int wrappedWidth = av.getWrappedWidth();
1340 int wrappedHeight = getRepeatHeightWrapped();
1342 ViewportRanges ranges = av.getRanges();
1343 int canvasHeight = getHeight();
1344 int repeats = canvasHeight / wrappedHeight;
1345 if (canvasHeight / wrappedHeight > 0)
1350 int firstVisibleColumn = ranges.getStartRes();
1351 int lastVisibleColumn = ranges.getStartRes() + repeats
1352 * ranges.getViewportWidth() - 1;
1354 AlignmentI alignment = av.getAlignment();
1355 if (av.hasHiddenColumns())
1357 firstVisibleColumn = alignment.getHiddenColumns()
1358 .adjustForHiddenColumns(firstVisibleColumn);
1359 lastVisibleColumn = alignment.getHiddenColumns()
1360 .adjustForHiddenColumns(lastVisibleColumn);
1363 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1365 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1366 .getEndSeq(); seqNo++)
1368 SequenceI seq = alignment.getSequenceAt(seqNo);
1370 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1372 if (visibleResults != null)
1374 for (int i = 0; i < visibleResults.length - 1; i += 2)
1376 int firstMatchedColumn = visibleResults[i];
1377 int lastMatchedColumn = visibleResults[i + 1];
1378 if (firstMatchedColumn <= lastVisibleColumn
1379 && lastMatchedColumn >= firstVisibleColumn)
1382 * found a search results match in the visible region
1384 firstMatchedColumn = Math.max(firstMatchedColumn,
1385 firstVisibleColumn);
1386 lastMatchedColumn = Math.min(lastMatchedColumn,
1390 * draw each mapped position separately (as contiguous positions may
1391 * wrap across lines)
1393 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1395 int displayColumn = mappedPos;
1396 if (av.hasHiddenColumns())
1398 displayColumn = alignment.getHiddenColumns()
1399 .findColumnPosition(displayColumn);
1403 * transX: offset from left edge of canvas to residue position
1405 int transX = labelWidthWest
1406 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1407 * av.getCharWidth();
1410 * transY: offset from top edge of canvas to residue position
1412 int transY = gapHeight;
1413 transY += (displayColumn - ranges.getStartRes())
1414 / wrappedWidth * wrappedHeight;
1415 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1418 * yOffset is from graphics origin to start of visible region
1420 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1421 if (transY < getHeight())
1424 gg.translate(transX, transY);
1425 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1427 gg.translate(-transX, -transY);
1439 * Answers the height in pixels of a repeating section of the wrapped
1440 * alignment, including space above, scale above if shown, sequences, and
1441 * annotation panel if shown
1445 protected int getRepeatHeightWrapped()
1447 // gap (and maybe scale) above
1448 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1451 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1453 // add annotations panel height if shown
1454 repeatHeight += getAnnotationHeight();
1456 return repeatHeight;