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 10 columns.
126 * the graphics context to draw on, positioned at the start (bottom
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
513 while ((ypos <= canvasHeight) && (startRes < maxwidth))
515 drawWrappedWidth(g, startRes, canvasHeight, cWidth, maxwidth, ypos);
517 ypos += sequencesHeight + annotationHeight + hgap;
524 * Draws one width of a wrapped alignment, including scales left, right or
525 * above, and annnotations, if shown
529 * @param canvasHeight
534 protected void drawWrappedWidth(Graphics g, int startRes,
535 int canvasHeight, int canvasWidth, int maxWidth, int ypos)
538 endx = startRes + canvasWidth - 1;
545 g.setFont(av.getFont());
546 g.setColor(Color.black);
548 if (av.getScaleLeftWrapped())
550 drawVerticalScale(g, startRes, endx, ypos, true);
553 if (av.getScaleRightWrapped())
555 drawVerticalScale(g, startRes, endx, ypos, false);
558 drawWrappedRegion(g, startRes, endx, canvasHeight, canvasWidth, ypos);
562 * Draws columns of a wrapped alignment from startRes to endRes, including
563 * scale above and annotations if shown, but not scale left or right.
568 * @param canvasHeight
572 protected void drawWrappedRegion(Graphics g, int startRes, int endRes,
573 int canvasHeight, int canvasWidth, int ypos)
575 g.translate(labelWidthWest, 0);
577 if (av.getScaleAboveWrapped())
579 drawNorthScale(g, startRes, endRes, ypos);
582 // todo can we let drawPanel() handle this?
583 if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
585 g.setColor(Color.blue);
586 HiddenColumns hidden = av.getAlignment().getHiddenColumns();
587 List<Integer> positions = hidden.findHiddenRegionPositions();
588 for (int pos : positions)
590 int res = pos - startRes;
592 if (res < 0 || res > endRes - startRes)
597 gg.fillPolygon(new int[] { res * charWidth - charHeight / 4,
598 res * charWidth + charHeight / 4, res * charWidth }, new int[] {
599 ypos - (charHeight / 2), ypos - (charHeight / 2),
600 ypos - (charHeight / 2) + 8 }, 3);
604 // When printing we have an extra clipped region,
605 // the Printable page which we need to account for here
606 Shape clip = g.getClip();
610 g.setClip(0, 0, canvasWidth * charWidth, canvasHeight);
614 g.setClip(0, (int) clip.getBounds().getY(), canvasWidth * charWidth,
615 (int) clip.getBounds().getHeight());
618 drawPanel(g, startRes, endRes, 0, av.getAlignment().getHeight() - 1, ypos);
620 int cHeight = av.getAlignment().getHeight() * charHeight;
622 if (av.isShowAnnotation())
624 g.translate(0, cHeight + ypos + 3);
625 if (annotations == null)
627 annotations = new AnnotationPanel(av);
630 annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
632 g.translate(0, -cHeight - ypos - 3);
635 g.translate(-labelWidthWest, 0);
638 AnnotationPanel annotations;
640 int getAnnotationHeight()
642 if (!av.isShowAnnotation())
647 if (annotations == null)
649 annotations = new AnnotationPanel(av);
652 return annotations.adjustPanelHeight();
656 * Draws the visible region of the alignment on the graphics context. If there
657 * are hidden column markers in the visible region, then each sub-region
658 * between the markers is drawn separately, followed by the hidden column
662 * the graphics context, positioned at the first residue to be drawn
664 * offset of the first column to draw (0..)
666 * offset of the last column to draw (0..)
668 * offset of the first sequence to draw (0..)
670 * offset of the last sequence to draw (0..)
672 * vertical offset at which to draw (for wrapped alignments)
674 public void drawPanel(Graphics g1, final int startRes, final int endRes,
675 final int startSeq, final int endSeq, final int yOffset)
678 if (!av.hasHiddenColumns())
680 draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
685 final int screenYMax = endRes - startRes;
686 int blockStart = startRes;
687 int blockEnd = endRes;
689 for (int[] region : av.getAlignment().getHiddenColumns()
690 .getHiddenColumnsCopy())
692 int hideStart = region[0];
693 int hideEnd = region[1];
695 if (hideStart <= blockStart)
697 blockStart += (hideEnd - hideStart) + 1;
702 * draw up to just before the next hidden region, or the end of
703 * the visible region, whichever comes first
705 blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
708 g1.translate(screenY * charWidth, 0);
710 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
713 * draw the downline of the hidden column marker (ScalePanel draws the
714 * triangle on top) if we reached it
716 if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
718 g1.setColor(Color.blue);
720 g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
721 0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
722 (endSeq - startSeq + 1) * charHeight + yOffset);
725 g1.translate(-screenY * charWidth, 0);
726 screenY += blockEnd - blockStart + 1;
727 blockStart = hideEnd + 1;
729 if (screenY > screenYMax)
731 // already rendered last block
736 if (screenY <= screenYMax)
738 // remaining visible region to render
739 blockEnd = blockStart + screenYMax - screenY;
740 g1.translate(screenY * charWidth, 0);
741 draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
743 g1.translate(-screenY * charWidth, 0);
750 * Draws a region of the visible alignment
754 * offset of the first column in the visible region (0..)
756 * offset of the last column in the visible region (0..)
758 * offset of the first sequence in the visible region (0..)
760 * offset of the last sequence in the visible region (0..)
762 * vertical offset at which to draw (for wrapped alignments)
764 private void draw(Graphics g, int startRes, int endRes, int startSeq,
765 int endSeq, int offset)
767 g.setFont(av.getFont());
768 sr.prepare(g, av.isRenderGaps());
772 // / First draw the sequences
773 // ///////////////////////////
774 for (int i = startSeq; i <= endSeq; i++)
776 nextSeq = av.getAlignment().getSequenceAt(i);
779 // occasionally, a race condition occurs such that the alignment row is
783 sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
784 startRes, endRes, offset + ((i - startSeq) * charHeight));
786 if (av.isShowSequenceFeatures())
788 fr.drawSequence(g, nextSeq, startRes, endRes, offset
789 + ((i - startSeq) * charHeight), false);
793 * highlight search Results once sequence has been drawn
795 if (av.hasSearchResults())
797 SearchResultsI searchResults = av.getSearchResults();
798 int[] visibleResults = searchResults.getResults(nextSeq,
800 if (visibleResults != null)
802 for (int r = 0; r < visibleResults.length; r += 2)
804 sr.drawHighlightedText(nextSeq, visibleResults[r],
805 visibleResults[r + 1], (visibleResults[r] - startRes)
807 + ((i - startSeq) * charHeight));
812 if (av.cursorMode && cursorY == i && cursorX >= startRes
813 && cursorX <= endRes)
815 sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
816 offset + ((i - startSeq) * charHeight));
820 if (av.getSelectionGroup() != null
821 || av.getAlignment().getGroups().size() > 0)
823 drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
828 void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
829 int startSeq, int endSeq, int offset)
831 Graphics2D g = (Graphics2D) g1;
833 // ///////////////////////////////////
834 // Now outline any areas if necessary
835 // ///////////////////////////////////
836 SequenceGroup group = av.getSelectionGroup();
842 int visWidth = (endRes - startRes + 1) * charWidth;
844 if ((group == null) && (av.getAlignment().getGroups().size() > 0))
846 group = av.getAlignment().getGroups().get(0);
856 boolean inGroup = false;
860 for (i = startSeq; i <= endSeq; i++)
862 sx = (group.getStartRes() - startRes) * charWidth;
863 sy = offset + ((i - startSeq) * charHeight);
864 ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
866 if (sx + ex < 0 || sx > visWidth)
871 if ((sx <= (endRes - startRes) * charWidth)
872 && group.getSequences(null).contains(
873 av.getAlignment().getSequenceAt(i)))
876 && !group.getSequences(null).contains(
877 av.getAlignment().getSequenceAt(i + 1)))
879 bottom = sy + charHeight;
884 if (((top == -1) && (i == 0))
885 || !group.getSequences(null).contains(
886 av.getAlignment().getSequenceAt(i - 1)))
894 if (group == av.getSelectionGroup())
896 g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
897 BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
899 g.setColor(Color.RED);
903 g.setStroke(new BasicStroke());
904 g.setColor(group.getOutlineColour());
912 if (sx >= 0 && sx < visWidth)
914 g.drawLine(sx, oldY, sx, sy);
917 if (sx + ex < visWidth)
919 g.drawLine(sx + ex, oldY, sx + ex, sy);
928 if (sx + ex > visWidth)
933 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
935 ex = (endRes - startRes + 1) * charWidth;
940 g.drawLine(sx, top, sx + ex, top);
946 g.drawLine(sx, bottom, sx + ex, bottom);
957 sy = offset + ((i - startSeq) * charHeight);
958 if (sx >= 0 && sx < visWidth)
960 g.drawLine(sx, oldY, sx, sy);
963 if (sx + ex < visWidth)
965 g.drawLine(sx + ex, oldY, sx + ex, sy);
974 if (sx + ex > visWidth)
978 else if (sx + ex >= (endRes - startRes + 1) * charWidth)
980 ex = (endRes - startRes + 1) * charWidth;
985 g.drawLine(sx, top, sx + ex, top);
991 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
1000 g.setStroke(new BasicStroke());
1002 if (groupIndex >= av.getAlignment().getGroups().size())
1007 group = av.getAlignment().getGroups().get(groupIndex);
1009 } while (groupIndex < av.getAlignment().getGroups().size());
1016 * Highlights search results in the visible region by rendering as white text
1017 * on a black background. Any previous highlighting is removed. Answers true
1018 * if any highlight was left on the visible alignment (so status bar should be
1019 * set to match), else false.
1021 * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
1022 * alignment had to be scrolled to show the highlighted region, then it should
1023 * be fully redrawn, otherwise a fast paint can be performed. This argument
1024 * could be removed if fast paint of scrolled wrapped alignment is coded in
1025 * future (JAL-2609).
1028 * @param noFastPaint
1031 public boolean highlightSearchResults(SearchResultsI results,
1032 boolean noFastPaint)
1038 boolean wrapped = av.getWrapAlignment();
1042 fastPaint = !noFastPaint;
1043 fastpainting = fastPaint;
1048 * to avoid redrawing the whole visible region, we instead
1049 * redraw just the minimal regions to remove previous highlights
1052 SearchResultsI previous = av.getSearchResults();
1053 av.setSearchResults(results);
1054 boolean redrawn = false;
1055 boolean drawn = false;
1058 redrawn = drawMappedPositionsWrapped(previous);
1059 drawn = drawMappedPositionsWrapped(results);
1064 redrawn = drawMappedPositions(previous);
1065 drawn = drawMappedPositions(results);
1070 * if highlights were either removed or added, repaint
1078 * return true only if highlights were added
1084 fastpainting = false;
1089 * Redraws the minimal rectangle in the visible region (if any) that includes
1090 * mapped positions of the given search results. Whether or not positions are
1091 * highlighted depends on the SearchResults set on the Viewport. This allows
1092 * this method to be called to either clear or set highlighting. Answers true
1093 * if any positions were drawn (in which case a repaint is still required),
1099 protected boolean drawMappedPositions(SearchResultsI results)
1101 if (results == null)
1107 * calculate the minimal rectangle to redraw that
1108 * includes both new and existing search results
1110 int firstSeq = Integer.MAX_VALUE;
1112 int firstCol = Integer.MAX_VALUE;
1114 boolean matchFound = false;
1116 ViewportRanges ranges = av.getRanges();
1117 int firstVisibleColumn = ranges.getStartRes();
1118 int lastVisibleColumn = ranges.getEndRes();
1119 AlignmentI alignment = av.getAlignment();
1120 if (av.hasHiddenColumns())
1122 firstVisibleColumn = alignment.getHiddenColumns()
1123 .adjustForHiddenColumns(firstVisibleColumn);
1124 lastVisibleColumn = alignment.getHiddenColumns()
1125 .adjustForHiddenColumns(lastVisibleColumn);
1128 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1129 .getEndSeq(); seqNo++)
1131 SequenceI seq = alignment.getSequenceAt(seqNo);
1133 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1135 if (visibleResults != null)
1137 for (int i = 0; i < visibleResults.length - 1; i += 2)
1139 int firstMatchedColumn = visibleResults[i];
1140 int lastMatchedColumn = visibleResults[i + 1];
1141 if (firstMatchedColumn <= lastVisibleColumn
1142 && lastMatchedColumn >= firstVisibleColumn)
1145 * found a search results match in the visible region -
1146 * remember the first and last sequence matched, and the first
1147 * and last visible columns in the matched positions
1150 firstSeq = Math.min(firstSeq, seqNo);
1151 lastSeq = Math.max(lastSeq, seqNo);
1152 firstMatchedColumn = Math.max(firstMatchedColumn,
1153 firstVisibleColumn);
1154 lastMatchedColumn = Math.min(lastMatchedColumn,
1156 firstCol = Math.min(firstCol, firstMatchedColumn);
1157 lastCol = Math.max(lastCol, lastMatchedColumn);
1165 if (av.hasHiddenColumns())
1167 firstCol = alignment.getHiddenColumns()
1168 .findColumnPosition(firstCol);
1169 lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
1171 int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
1172 int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
1173 gg.translate(transX, transY);
1174 drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
1175 gg.translate(-transX, -transY);
1182 public void propertyChange(PropertyChangeEvent evt)
1184 String eventName = evt.getPropertyName();
1186 // if (av.getWrapAlignment())
1188 // if (eventName.equals(ViewportRanges.STARTRES))
1196 if (eventName.equals(ViewportRanges.STARTRES))
1198 // Make sure we're not trying to draw a panel
1199 // larger than the visible window
1200 ViewportRanges vpRanges = av.getRanges();
1201 scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
1202 int range = vpRanges.getEndRes() - vpRanges.getStartRes();
1203 if (scrollX > range)
1207 else if (scrollX < -range)
1213 // Both scrolling and resizing change viewport ranges: scrolling changes
1214 // both start and end points, but resize only changes end values.
1215 // Here we only want to fastpaint on a scroll, with resize using a normal
1216 // paint, so scroll events are identified as changes to the horizontal or
1217 // vertical start value.
1218 if (eventName.equals(ViewportRanges.STARTRES))
1220 // scroll - startres and endres both change
1221 if (av.getWrapAlignment())
1223 fastPaintWrapped(scrollX);
1224 // fastPaintWrapped(scrollX > 0 ? 1 : -1); // to debug: 1 at a time
1228 fastPaint(scrollX, 0);
1231 else if (eventName.equals(ViewportRanges.STARTSEQ))
1233 fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
1239 * Does a minimal update of the image for a scroll movement. This method
1240 * handles scroll movements of up to one width of the wrapped alignment (one
1241 * click in the vertical scrollbar). Larger movements (for example after a
1242 * scroll to highlight a mapped position) trigger a full redraw instead.
1245 * number of positions scrolled (right if positive, left if negative)
1247 protected void fastPaintWrapped(int scrollX)
1249 if (Math.abs(scrollX) > av.getRanges().getViewportWidth())
1252 * shift of more than one view width is
1253 * too complicated to handle in this method
1263 * relocate the regions of the alignment that are still visible
1265 shiftWrappedAlignment(-scrollX);
1268 * add new columns (scale above, sequence, annotation)
1269 * at top left if scrollX < 0 or bottom right if scrollX > 0
1270 * also West scale top left or East scale bottom right if shown
1274 fastPaintWrappedTopLeft(-scrollX);
1278 // fastPaintWrappedBottomRight(scrollX);
1285 * Draws the specified number of columns at the 'end' (bottom right) of a
1286 * wrapped alignment view, including scale above and right and annotations if
1291 protected void fastPaintWrappedBottomRight(int columns)
1298 int repeatHeight = getRepeatHeightWrapped();
1299 ViewportRanges ranges = av.getRanges();
1300 int visibleWidths = getHeight() / repeatHeight;
1301 if (getHeight() % repeatHeight > 0)
1305 int viewportWidth = ranges.getViewportWidth();
1306 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1308 int startRes = av.getRanges().getStartRes();
1309 int endx = startRes + columns - 1;
1310 int ypos = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1311 ypos += repeatHeight * (visibleWidths - 1);
1313 gg.setFont(av.getFont());
1314 gg.setColor(Color.black);
1316 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1318 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1320 if (av.getScaleRightWrapped())
1322 drawVerticalScale(gg, startRes, endx, ypos, false);
1327 * Draws the specified number of columns at the 'start' (top left) of a
1328 * wrapped alignment view, including scale above and left and annotations if
1333 protected void fastPaintWrappedTopLeft(int columns)
1335 int startRes = av.getRanges().getStartRes();
1338 * draw one extra column than strictly needed - this is a (harmless)
1339 * fudge to ensure scale marks get drawn (JAL-2636)
1341 int endx = startRes + columns;
1345 * white fill the region to be drawn including scale left or above
1347 gg.setColor(Color.white);
1348 int height = getRepeatHeightWrapped();
1349 gg.fillRect(0, ypos, labelWidthWest + columns * charWidth, height);
1350 ypos += charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1352 gg.setFont(av.getFont());
1353 gg.setColor(Color.black);
1355 if (av.getScaleLeftWrapped())
1357 drawVerticalScale(gg, startRes, endx, ypos, true);
1360 int cWidth = (getWidth() - labelWidthEast - labelWidthWest) / charWidth;
1362 drawWrappedRegion(gg, startRes, endx, getHeight(), cWidth, ypos);
1366 * Shifts the visible alignment by the specified number of columns - left if
1367 * negative, right if positive. Includes scale above, left or right and
1368 * annotations (if shown). Does not draw newly visible columns.
1372 protected void shiftWrappedAlignment(int positions)
1379 int repeatHeight = getRepeatHeightWrapped();
1380 ViewportRanges ranges = av.getRanges();
1381 int widthToCopy = (ranges.getViewportWidth() - Math.abs(positions))
1383 int visibleWidths = getHeight() / repeatHeight;
1384 if (getHeight() % repeatHeight > 0)
1388 int viewportWidth = ranges.getViewportWidth();
1389 int hgap = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1394 * shift right (after scroll left)
1395 * for each wrapped width (starting with the last), copy (width-positions)
1396 * columns from the left margin to the right margin, and copy positions
1397 * columns from the right margin of the row above (if any) to the
1398 * left margin of the current row
1400 int xpos = ranges.getStartRes() + (visibleWidths - 1) * viewportWidth;
1403 * get y-offset of last wrapped width
1405 int y = getHeight() / repeatHeight * repeatHeight;
1406 int copyFromLeftStart = labelWidthWest;
1407 int copyFromRightStart = copyFromLeftStart + widthToCopy;
1411 // todo limit repeatHeight for a last part height width?
1412 gg.copyArea(copyFromLeftStart, y, widthToCopy, repeatHeight,
1413 positions * charWidth, 0);
1416 gg.copyArea(copyFromRightStart, y - repeatHeight, positions
1417 * charWidth, repeatHeight, -widthToCopy, repeatHeight);
1420 if (av.getScaleLeftWrapped())
1422 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1425 if (av.getScaleRightWrapped())
1427 drawVerticalScale(gg, xpos, xpos + viewportWidth - 1, y + hgap,
1432 xpos -= viewportWidth;
1438 * shift left (after scroll right)
1439 * for each wrapped width (starting with the first), copy (width-positions)
1440 * columns from the right margin to the left margin, and copy positions
1441 * columns from the left margin of the row below (if any) to the
1442 * right margin of the current row
1444 int xpos = ranges.getStartRes();
1446 int copyFromRightStart = labelWidthWest - positions * charWidth;
1448 while (y < getHeight())
1450 // todo limit repeatHeight for a last part height width?
1451 gg.copyArea(copyFromRightStart, y, widthToCopy, repeatHeight,
1452 positions * charWidth, 0);
1453 if (y + repeatHeight < getHeight())
1455 gg.copyArea(labelWidthWest, y + repeatHeight, -positions
1456 * charWidth, repeatHeight, widthToCopy, -repeatHeight);
1459 if (av.getScaleLeftWrapped())
1461 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, true);
1463 if (av.getScaleRightWrapped())
1465 drawVerticalScale(gg, xpos, xpos + viewportWidth, y + hgap, false);
1469 xpos += ranges.getViewportWidth();
1475 * Redraws any positions in the search results in the visible region of a
1476 * wrapped alignment. Any highlights are drawn depending on the search results
1477 * set on the Viewport, not the <code>results</code> argument. This allows
1478 * this method to be called either to clear highlights (passing the previous
1479 * search results), or to draw new highlights.
1484 protected boolean drawMappedPositionsWrapped(SearchResultsI results)
1486 if (results == null)
1491 boolean matchFound = false;
1493 int wrappedWidth = av.getWrappedWidth();
1494 int wrappedHeight = getRepeatHeightWrapped();
1496 ViewportRanges ranges = av.getRanges();
1497 int canvasHeight = getHeight();
1498 int repeats = canvasHeight / wrappedHeight;
1499 if (canvasHeight / wrappedHeight > 0)
1504 int firstVisibleColumn = ranges.getStartRes();
1505 int lastVisibleColumn = ranges.getStartRes() + repeats
1506 * ranges.getViewportWidth() - 1;
1508 AlignmentI alignment = av.getAlignment();
1509 if (av.hasHiddenColumns())
1511 firstVisibleColumn = alignment.getHiddenColumns()
1512 .adjustForHiddenColumns(firstVisibleColumn);
1513 lastVisibleColumn = alignment.getHiddenColumns()
1514 .adjustForHiddenColumns(lastVisibleColumn);
1517 int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1519 for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
1520 .getEndSeq(); seqNo++)
1522 SequenceI seq = alignment.getSequenceAt(seqNo);
1524 int[] visibleResults = results.getResults(seq, firstVisibleColumn,
1526 if (visibleResults != null)
1528 for (int i = 0; i < visibleResults.length - 1; i += 2)
1530 int firstMatchedColumn = visibleResults[i];
1531 int lastMatchedColumn = visibleResults[i + 1];
1532 if (firstMatchedColumn <= lastVisibleColumn
1533 && lastMatchedColumn >= firstVisibleColumn)
1536 * found a search results match in the visible region
1538 firstMatchedColumn = Math.max(firstMatchedColumn,
1539 firstVisibleColumn);
1540 lastMatchedColumn = Math.min(lastMatchedColumn,
1544 * draw each mapped position separately (as contiguous positions may
1545 * wrap across lines)
1547 for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
1549 int displayColumn = mappedPos;
1550 if (av.hasHiddenColumns())
1552 displayColumn = alignment.getHiddenColumns()
1553 .findColumnPosition(displayColumn);
1557 * transX: offset from left edge of canvas to residue position
1559 int transX = labelWidthWest
1560 + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
1561 * av.getCharWidth();
1564 * transY: offset from top edge of canvas to residue position
1566 int transY = gapHeight;
1567 transY += (displayColumn - ranges.getStartRes())
1568 / wrappedWidth * wrappedHeight;
1569 transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
1572 * yOffset is from graphics origin to start of visible region
1574 int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
1575 if (transY < getHeight())
1578 gg.translate(transX, transY);
1579 drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
1581 gg.translate(-transX, -transY);
1593 * Answers the height in pixels of a repeating section of the wrapped
1594 * alignment, including space above, scale above if shown, sequences, and
1595 * annotation panel if shown
1599 protected int getRepeatHeightWrapped()
1601 // gap (and maybe scale) above
1602 int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
1605 repeatHeight += av.getRanges().getViewportHeight() * charHeight;
1607 // add annotations panel height if shown
1608 repeatHeight += getAnnotationHeight();
1610 return repeatHeight;